nvim/lua/autocmds.lua
ray aedc1d7b86 Discard missing files from saved sessions
Sanitize nonexistent unmodified file buffers and stale argument-list entries after session load and before automatic save.
2026-10-28 19:38:17 +00:00

279 lines
8.3 KiB
Lua

-- Non-plugin autocommands (minimal placeholder for bootstrap)
local aug = vim.api.nvim_create_augroup("UserDefaults", { clear = true })
-- Templates (auto-populate new files from templates)
local template_aug = vim.api.nvim_create_augroup("Templates", { clear = true })
vim.api.nvim_create_autocmd("BufNewFile", {
group = template_aug,
pattern = "*.sh",
callback = function()
-- Read template at top of file, then move to end
vim.cmd("0read ~/.config/nvim/templates/template.sh")
vim.cmd("normal! G")
end,
})
-- Per-filetype indentation settings to match formatters
-- PHP: WordPress coding standards (tabs, displayed as 2 spaces)
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "php",
callback = function()
vim.opt_local.expandtab = false -- Use actual tabs (WordPress standard)
vim.opt_local.tabstop = 2 -- Display tabs as 2 spaces wide
vim.opt_local.shiftwidth = 2 -- Indent/outdent by 2 columns (one tab)
vim.opt_local.softtabstop = 2 -- Tab key inserts 2 columns (one tab)
end,
})
-- JavaScript, TypeScript, JSON, CSS: Prettier defaults (2 spaces)
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = { "javascript", "javascriptreact", "typescript", "typescriptreact", "json", "css", "scss", "html" },
callback = function()
vim.opt_local.expandtab = true -- Use spaces (Prettier default)
vim.opt_local.tabstop = 2 -- Display as 2 spaces
vim.opt_local.shiftwidth = 2 -- Indent by 2
vim.opt_local.softtabstop = 2
end,
})
-- Lua: stylua defaults (2 spaces)
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "lua",
callback = function()
vim.opt_local.expandtab = true -- Use spaces (common for Lua configs)
vim.opt_local.tabstop = 2 -- Display as 2 spaces
vim.opt_local.shiftwidth = 2 -- Indent by 2
vim.opt_local.softtabstop = 2
end,
})
-- Markdown: Prettier defaults (2 spaces) + spellcheck
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "markdown",
callback = function()
vim.opt_local.expandtab = true -- Use spaces
vim.opt_local.tabstop = 2 -- Display as 2 spaces
vim.opt_local.shiftwidth = 2 -- Indent by 2
vim.opt_local.softtabstop = 2
vim.opt_local.spell = true -- Enable spellcheck for markdown
end,
})
-- Python: Black defaults (4 spaces)
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "python",
callback = function()
vim.opt_local.expandtab = true -- Use spaces (PEP 8)
vim.opt_local.tabstop = 4 -- Display as 4 spaces
vim.opt_local.shiftwidth = 4 -- Indent by 4
vim.opt_local.softtabstop = 4
end,
})
-- Colorize diagnostic severity in quickfix/location list
vim.api.nvim_create_autocmd("FileType", {
group = aug,
pattern = "qf", -- Applies to both quickfix and location list
callback = function()
-- Disable spell checking in quickfix/location list windows
vim.opt_local.spell = false
-- Use matchadd() for higher priority highlighting
-- This will override the default qf syntax
vim.fn.matchadd("qfError", "\\<error\\>", 10)
vim.fn.matchadd("qfWarning", "\\<warning\\>", 10)
vim.fn.matchadd("qfInfo", "\\<info\\>", 10)
vim.fn.matchadd("qfHint", "\\<hint\\>", 10)
vim.fn.matchadd("qfNote", "\\<note\\>", 10)
-- Also match uppercase variants
vim.fn.matchadd("qfError", "\\<Error\\>", 10)
vim.fn.matchadd("qfWarning", "\\<Warning\\>", 10)
vim.fn.matchadd("qfInfo", "\\<Info\\>", 10)
vim.fn.matchadd("qfHint", "\\<Hint\\>", 10)
vim.fn.matchadd("qfNote", "\\<Note\\>", 10)
end,
})
-- 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", {
group = session_aug,
pattern = "*",
once = true,
callback = function()
-- Only auto-load if:
-- 1. Session.vim exists in cwd
-- 2. No files were specified on command line (vim.fn.argc() == 0)
-- 3. Not started with nvim -S (check if we already loaded a session)
if vim.fn.argc() == 0 and vim.fn.filereadable("Session.vim") == 1 and vim.v.this_session == "" then
vim.cmd("silent! source Session.vim")
end
end,
})
-- Ensure filetype is detected for current buffer after session load
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
if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == "" then
vim.api.nvim_exec_autocmds("BufReadPost", { buffer = buf })
end
end
end)
end,
})
local function is_netrw_session_buffer(buf)
return vim.api.nvim_buf_is_valid(buf)
and (vim.bo[buf].filetype == "netrw" or vim.api.nvim_buf_get_name(buf):match("NetrwTreeListing$") ~= nil)
end
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 name ~= ""
and vim.uv.fs_stat(name) ~= nil
and not is_netrw_session_buffer(buf)
then
return buf
end
end
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)
return
end
pcall(function()
vim.api.nvim_set_current_tabpage(tab)
vim.cmd("tabclose!")
end)
end
local function omit_netrw_from_session()
local tabs_with_netrw = {}
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
if is_netrw_session_buffer(vim.api.nvim_win_get_buf(win)) then
tabs_with_netrw[tab] = true
break
end
end
end
for tab in pairs(tabs_with_netrw) do
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 vim.wo[win].previewwindow then
pcall(vim.api.nvim_win_close, win, true)
end
end
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_tabpage_list_wins(tab) > 1 then
pcall(vim.api.nvim_win_close, win, true)
elseif #vim.api.nvim_list_tabpages() > 1 then
close_session_tab(tab)
else
vim.api.nvim_win_set_buf(win, session_fallback_buffer())
end
end
end
end
end
end
end
-- Auto-save session on exit, but ONLY if Session.vim already exists
vim.api.nvim_create_autocmd("VimLeavePre", {
group = session_aug,
pattern = "*",
callback = function()
-- 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,
})
return {}