diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-06-24 19:59:53 +0200 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-06-24 20:06:02 +0200 |
| commit | 046e3be5982104f9e889edf1490381ea8c3b959f (patch) | |
| tree | 0ba6307103e243c6cd688dc1fb631df5f82a17aa /plugin | |
| parent | c4ff1dc44373f03e8e940670f9610a6005d960b0 (diff) | |
| download | nvim-config-046e3be5982104f9e889edf1490381ea8c3b959f.tar.gz nvim-config-046e3be5982104f9e889edf1490381ea8c3b959f.zip | |
refactor(nvim): rewrite session plugin
Diffstat (limited to 'plugin')
| -rw-r--r-- | plugin/50-session.lua | 195 |
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 } +) |
