From aedc1d7b86be5786af2aa1095441360b24040bc2 Mon Sep 17 00:00:00 2001 From: ray Date: Wed, 28 Oct 2026 19:38:17 +0000 Subject: [PATCH] Discard missing files from saved sessions Sanitize nonexistent unmodified file buffers and stale argument-list entries after session load and before automatic save. --- LOG.md | 1 + README.md | 1 + lua/autocmds.lua | 66 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/LOG.md b/LOG.md index 689f6f7..e5ce052 100644 --- a/LOG.md +++ b/LOG.md @@ -226,6 +226,7 @@ Record every decision here with a short rationale. Append new entries; do not re - 2026-10-28: Replaced the migration-era `AGENTS.md` with a concise maintenance guide for the established configuration. Removed completed phase tracking, obsolete validation-workspace instructions, resolved issue notes, and the target-tree description; retained current architectural guardrails, documentation expectations, a smoke-test command, and a short history note. - 2026-10-28: Retired the completed migration's legacy configuration, vendored plugin archive, UltiSnips collection, validation workspaces, root test assets, superseded planning documents, and phase labels in active Lua comments. Git history now provides the historical archive. Persistent undo storage moved from the configuration repository to `stdpath('state')/undo`; the active README, architectural log, lockfile, colorscheme, spell data, and templates remain. - 2026-10-28: Diagnosed the Tree-sitter `TSNode:range()` failure as the known incompatibility between Neovim 0.12 and nvim-treesitter's locked legacy `master` branch. Migrated nvim-treesitter and nvim-treesitter-textobjects to their rewritten `main` branches: native Neovim highlighting and incremental selection replace the old modules, textobjects use explicit mappings, indentation uses the new plugin expression when a query exists, and autotag retains its direct supported setup. Installed the verified official Tree-sitter CLI 0.26.9 binary in `~/.local/bin` after the system package required interactive sudo and Cargo compilation required unavailable libclang. Reinstalled all configured parsers in the new site directory and removed obsolete generated parser copies from the plugin checkout. The plugin audit confirmed every plugin is current for its configured branch or release constraint; nvim-surround remains on the latest release rather than its newer unreleased `main` commit. +- 2026-10-28: Fixed stale deleted files persisting across session recreation. Deleting `Session.vim` did not clear the in-memory argument list or listed buffers, so `:mksession` serialized the removed diagnostics fixture again. Session load and exit-time save now discard unmodified nonexistent local-file buffers and missing argument-list entries before restoring or writing the session; real files, URI buffers, unnamed buffers, and modified buffers remain protected. ## Project-Local Configuration diff --git a/README.md b/README.md index 3201581..1859a3f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Session support is automatic but user-controlled: - On startup: auto-loads `Session.vim` if it exists (unless files specified on command line) - On exit: auto-saves to `Session.vim` if it already exists - Netrw and its preview windows are omitted from the saved layout; generated `NetrwTreeListing` buffers are not restorable +- Missing files are removed from restored buffers and argument lists, so deleted files cannot become persistent empty session buffers - Sessions are per-directory and opt-in (just create one to start using) **Stop using a session**: diff --git a/lua/autocmds.lua b/lua/autocmds.lua index 5f67a30..3ab254b 100644 --- a/lua/autocmds.lua +++ b/lua/autocmds.lua @@ -104,6 +104,7 @@ vim.api.nvim_create_autocmd("FileType", { -- Session management: auto-load on startup, auto-save on exit (only if Session.vim exists) local session_aug = vim.api.nvim_create_augroup("SessionManagement", { clear = true }) +local sanitize_session_files -- Auto-load Session.vim if it exists in the current directory vim.api.nvim_create_autocmd("VimEnter", { @@ -126,6 +127,8 @@ vim.api.nvim_create_autocmd("SessionLoadPost", { group = session_aug, pattern = "*", callback = function() + sanitize_session_files() + -- Trigger BufReadPost for all loaded buffers to ensure Treesitter/LSP attach vim.schedule(function() for _, buf in ipairs(vim.api.nvim_list_bufs()) do @@ -142,13 +145,17 @@ local function is_netrw_session_buffer(buf) and (vim.bo[buf].filetype == "netrw" or vim.api.nvim_buf_get_name(buf):match("NetrwTreeListing$") ~= nil) end -local function session_fallback_buffer() +local function session_fallback_buffer(excluded) + excluded = excluded or {} for _, buf in ipairs(vim.api.nvim_list_bufs()) do + local name = vim.api.nvim_buf_get_name(buf) if vim.api.nvim_buf_is_valid(buf) + and not excluded[buf] and vim.bo[buf].buflisted and vim.bo[buf].buftype == "" - and vim.api.nvim_buf_get_name(buf) ~= "" + and name ~= "" + and vim.uv.fs_stat(name) ~= nil and not is_netrw_session_buffer(buf) then return buf @@ -158,6 +165,55 @@ local function session_fallback_buffer() return vim.api.nvim_create_buf(true, false) end +local function is_local_file_path(path) + return path ~= "" and path:match("^%a[%w+.-]*://") == nil +end + +sanitize_session_files = function() + local missing = {} + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buftype == "" then + local name = vim.api.nvim_buf_get_name(buf) + if is_local_file_path(name) and vim.uv.fs_stat(name) == nil and not vim.bo[buf].modified then + missing[buf] = true + end + end + end + + if next(missing) then + local fallback + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if missing[buf] then + fallback = fallback or session_fallback_buffer(missing) + vim.api.nvim_win_set_buf(win, fallback) + end + end + + for buf in pairs(missing) do + if vim.api.nvim_buf_is_valid(buf) then + pcall(vim.api.nvim_buf_delete, buf, { force = true }) + end + end + end + + local args = vim.fn.argv() + local existing = {} + for _, arg in ipairs(args) do + local path = is_local_file_path(arg) and vim.fn.fnamemodify(arg, ":p") or nil + if not path or vim.uv.fs_stat(path) ~= nil then + table.insert(existing, arg) + end + end + + if #existing ~= #args then + vim.cmd("%argdelete") + for _, arg in ipairs(existing) do + vim.cmd.argadd({ args = { arg } }) + end + end +end + local function close_session_tab(tab) if vim.api.nvim_tabpage_close then pcall(vim.api.nvim_tabpage_close, tab, true) @@ -191,10 +247,7 @@ local function omit_netrw_from_session() if vim.api.nvim_tabpage_is_valid(tab) then for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do - if - vim.api.nvim_win_is_valid(win) - and is_netrw_session_buffer(vim.api.nvim_win_get_buf(win)) - then + if vim.api.nvim_win_is_valid(win) and is_netrw_session_buffer(vim.api.nvim_win_get_buf(win)) then if #vim.api.nvim_tabpage_list_wins(tab) > 1 then pcall(vim.api.nvim_win_close, win, true) elseif #vim.api.nvim_list_tabpages() > 1 then @@ -217,6 +270,7 @@ vim.api.nvim_create_autocmd("VimLeavePre", { -- Only save if Session.vim exists (user manually created it) if vim.fn.filereadable("Session.vim") == 1 then omit_netrw_from_session() + sanitize_session_files() vim.cmd("mksession! Session.vim") end end,