summaryrefslogtreecommitdiffstats
path: root/plugin
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-06-24 19:59:53 +0200
committerThomas Vanbesien <tvanbesi@proton.me>2026-06-24 20:06:02 +0200
commit046e3be5982104f9e889edf1490381ea8c3b959f (patch)
tree0ba6307103e243c6cd688dc1fb631df5f82a17aa /plugin
parentc4ff1dc44373f03e8e940670f9610a6005d960b0 (diff)
downloadnvim-config-046e3be5982104f9e889edf1490381ea8c3b959f.tar.gz
nvim-config-046e3be5982104f9e889edf1490381ea8c3b959f.zip
refactor(nvim): rewrite session plugin
Diffstat (limited to 'plugin')
-rw-r--r--plugin/50-session.lua195
1 files changed, 118 insertions, 77 deletions
diff --git a/plugin/50-session.lua b/plugin/50-session.lua
index 7b0061b..dc2c81f 100644
--- a/plugin/50-session.lua
+++ b/plugin/50-session.lua
@@ -1,6 +1,20 @@
--
--- Session plugin
+-- 50-session.lua
--
+-- * Saves, loads, deletes and restarts Vim sessions stored under stdpath("state")/sessions.
+-- * Augments :mksession with a JSON sidecar (dotfiles.session) for state it can't restore on its
+-- own: tab names and man:// windows.
+--
+-- User commands:
+-- `SessionSave`: save the session
+-- `SessionLoad`: load the session
+-- `SessionDelete`: delete the session
+-- `SessionRestart`: save the session, then restart Neovim into it
+-- `SessionExitSave`: save the session and quit
+-- `SessionExitNoSave`: quit without saving the session
+--
+
+local session = require("dotfiles.session")
vim.opt.sessionoptions:remove("folds")
@@ -8,47 +22,46 @@ local session_dir = vim.fn.stdpath("state") .. "/sessions"
local session_default = session_dir .. "/default.vim"
if not vim.uv.fs_stat(session_dir) then
vim.uv.fs_mkdir(session_dir, tonumber("755", 8))
- vim.notify("Sessions save directory created at " .. session_dir)
+ vim.notify("Sessions save directory created at " .. session_dir, vim.log.levels.INFO)
end
--- :mksession doesn't persist everything we want (e.g. tab-local t:tabname), so we keep a
--- sidecar JSON object alongside the .vim session for extra data. Tab names live under the
--- `tabnames` key, stored positionally because :mksession recreates tabs in their original
--- order, making the index stable across reloads.
-local function sidecar_path(path)
- return (path:gsub("%.vim$", "")) .. ".json"
-end
+----------------------------------------------------------------------------------------------------
+-- Sidecar providers
+----------------------------------------------------------------------------------------------------
-local function read_sidecar(path)
- local f = io.open(sidecar_path(path), "r")
- if not f then
- return {}
+-- Capture each tab's t:tabname (:mksession drops tab-local variables). Stored positionally because
+-- :mksession recreates tabs in their original order, making the index stable across reloads.
+local function save_tabnames()
+ local names = {}
+ for i, tp in ipairs(vim.api.nvim_list_tabpages()) do
+ -- pcall: nvim_tabpage_get_var errors when the variable isn't set on that tab
+ local ok, name = pcall(vim.api.nvim_tabpage_get_var, tp, "tabname")
+ names[i] = (ok and type(name) == "string") and name or ""
end
- local content = f:read("*a")
- f:close()
- local ok, data = pcall(vim.json.decode, content)
- return (ok and type(data) == "table") and data or {}
+ return names
end
-local function write_sidecar(path, data)
- local f = io.open(sidecar_path(path), "w")
- if f then
- f:write(vim.json.encode(data))
- f:close()
+-- Reapply the saved names to the recreated tabs by position, then redraw so they show at once.
+local function restore_tabnames(names)
+ for i, tp in ipairs(vim.api.nvim_list_tabpages()) do
+ if names[i] and names[i] ~= "" then
+ vim.api.nvim_tabpage_set_var(tp, "tabname", names[i])
+ end
end
+ vim.cmd.redrawtabline()
end
--- :mksession can't restore man:// buffers (they're :buftype=nofile, not backed by a file), so
+-- man:// windows: :mksession can't restore them (they're :buftype=nofile, not backed by a file), so
-- we record them ourselves, nested as tab index -> window number -> { buffer name, cursor line }.
--- Both indices are positional for the same reason as tab names above: :mksession recreates tabs
--- and their windows in order, so the ordinals are stable across reloads. Nesting by tab matters
--- because window numbers restart at 1 in each tabpage and would otherwise collide. The indices
--- are stringified so the sparse table encodes as a JSON object, not a null-padded array.
+-- Both indices are positional for the same reason as tab names above: :mksession recreates tabs and
+-- their windows in order, so the ordinals are stable across reloads. Nesting by tab matters because
+-- window numbers restart at 1 in each tabpage and would otherwise collide. The indices are
+-- stringified so the sparse table encodes as a JSON object, not a null-padded array.
--
--- Limitation: the cursor line is an index into the *rendered* man page, whose line wrapping
--- depends on MANWIDTH. If MANWIDTH differs on load, the page re-wraps and the saved line points
--- elsewhere. (It never changes in this setup, but the dependency is real.)
-local function man_windows()
+-- Limitation: the cursor line is an index into the *rendered* man page, whose line wrapping depends
+-- on MANWIDTH. If MANWIDTH differs on load, the page re-wraps and the saved line points elsewhere.
+-- (It never changes in this setup, but the dependency is real.)
+local function save_manpages()
local pages = {}
for ti, tp in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tp)) do
@@ -64,27 +77,13 @@ local function man_windows()
return pages
end
-local function save_session(path)
- vim.cmd.mksession({ path, bang = true })
- local names = {}
- for i, tp in ipairs(vim.api.nvim_list_tabpages()) do
- -- pcall: nvim_tabpage_get_var errors when the variable isn't set on that tab
- local ok, name = pcall(vim.api.nvim_tabpage_get_var, tp, "tabname")
- names[i] = (ok and type(name) == "string") and name or ""
- end
- write_sidecar(path, { tabnames = names, manpages = man_windows() })
-end
-
--- Reopen the man:// buffers recorded by man_windows(). Keys come back from JSON as strings,
--- hence the tonumber(). :edit man://... re-renders the page via the man plugin's BufReadCmd; we
--- run it window-scoped so the layout restored by :mksession is left untouched, then clamp the
--- saved cursor line to the (possibly re-wrapped) page.
-local function restore_man_windows(manpages)
- if not manpages then
- return
- end
+-- Reopen the man:// buffers recorded by save_manpages(). Keys come back from JSON as strings, hence
+-- the tonumber(). :edit man://... re-renders the page via the man plugin's BufReadCmd; we run it
+-- window-scoped so the layout restored by :mksession is left untouched, then clamp the saved cursor
+-- line to the (possibly re-wrapped) page.
+local function restore_manpages(pages)
local tabpages = vim.api.nvim_list_tabpages()
- for tk, wins in pairs(manpages) do
+ for tk, wins in pairs(pages) do
local tp = tabpages[tonumber(tk)]
if tp then
local by_number = {}
@@ -107,18 +106,21 @@ local function restore_man_windows(manpages)
end
end
+session.register("tabnames", save_tabnames, restore_tabnames)
+session.register("manpages", save_manpages, restore_manpages)
+
+----------------------------------------------------------------------------------------------------
+-- Session operations
+----------------------------------------------------------------------------------------------------
+
+local function save_session(path)
+ vim.cmd.mksession({ path, bang = true })
+ session.write(path)
+end
+
local function load_session(path)
vim.cmd.source(path)
- local data = read_sidecar(path)
- if data.tabnames then
- for i, tp in ipairs(vim.api.nvim_list_tabpages()) do
- if data.tabnames[i] and data.tabnames[i] ~= "" then
- vim.api.nvim_tabpage_set_var(tp, "tabname", data.tabnames[i])
- end
- end
- vim.cmd.redrawtabline()
- end
- restore_man_windows(data.manpages)
+ session.read(path)
end
local function reload_session(path)
@@ -128,12 +130,10 @@ end
local function delete_session(path)
vim.fs.rm(path)
- local sidecar = sidecar_path(path)
- if vim.uv.fs_stat(sidecar) then
- vim.fs.rm(sidecar)
- end
+ session.remove(path)
end
+-- Complete session names (without the .vim extension) from the sessions directory.
local function session_completefunc(arg_lead, _, _)
local completions = {}
for path in vim.fs.dir(session_dir) do
@@ -144,6 +144,7 @@ local function session_completefunc(arg_lead, _, _)
return completions
end
+-- Resolve a session name (or empty for the default) to an absolute `.vim` path, then run `op` on it.
local function session_op(base, op)
local path = #base > 0 and base or session_default
if not string.match(path, "^" .. session_dir) then
@@ -155,22 +156,62 @@ local function session_op(base, op)
op(path)
end
-vim.api.nvim_create_user_command("SessionSave", function(ev)
+----------------------------------------------------------------------------------------------------
+-- User commands
+----------------------------------------------------------------------------------------------------
+
+local function cmd_session_save(ev)
session_op(ev.args, save_session)
-end, { desc = "Save session", nargs = "?", complete = session_completefunc })
-vim.api.nvim_create_user_command("SessionLoad", function(ev)
+end
+
+local function cmd_session_load(ev)
session_op(ev.args, load_session)
-end, { desc = "Load session", nargs = "?", complete = session_completefunc })
-vim.api.nvim_create_user_command("SessionDelete", function(ev)
+end
+
+local function cmd_session_delete(ev)
session_op(ev.args, delete_session)
-end, { desc = "Delete session", nargs = "?", complete = session_completefunc })
-vim.api.nvim_create_user_command("SessionRestart", function(ev)
+end
+
+local function cmd_session_restart(ev)
session_op(ev.args, reload_session)
-end, { desc = "Reload session", nargs = "?", complete = session_completefunc })
-vim.api.nvim_create_user_command("SessionExitSave", function(ev)
+end
+
+local function cmd_session_exit_save(ev)
session_op(ev.args, save_session)
vim.cmd.qall()
-end, { desc = "Save session and exit", nargs = "?", complete = session_completefunc })
-vim.api.nvim_create_user_command("SessionExitNoSave", function()
+end
+
+local function cmd_session_exit_no_save()
vim.cmd.qall()
-end, { desc = "Exit without saving session", nargs = "?", complete = session_completefunc })
+end
+
+vim.api.nvim_create_user_command(
+ "SessionSave",
+ cmd_session_save,
+ { desc = "Save session", nargs = "?", complete = session_completefunc }
+)
+vim.api.nvim_create_user_command(
+ "SessionLoad",
+ cmd_session_load,
+ { desc = "Load session", nargs = "?", complete = session_completefunc }
+)
+vim.api.nvim_create_user_command(
+ "SessionDelete",
+ cmd_session_delete,
+ { desc = "Delete session", nargs = "?", complete = session_completefunc }
+)
+vim.api.nvim_create_user_command(
+ "SessionRestart",
+ cmd_session_restart,
+ { desc = "Reload session", nargs = "?", complete = session_completefunc }
+)
+vim.api.nvim_create_user_command(
+ "SessionExitSave",
+ cmd_session_exit_save,
+ { desc = "Save session and exit", nargs = "?", complete = session_completefunc }
+)
+vim.api.nvim_create_user_command(
+ "SessionExitNoSave",
+ cmd_session_exit_no_save,
+ { desc = "Exit without saving session", nargs = "?", complete = session_completefunc }
+)