nvim/lua/config/autocmds.lua
Jeremie Fraeys e8979088e6
refactor: reorganize keymaps and autocommands
- Restructure mappings.lua for better organization
- Refactor autocmds.lua with improved event handling
2026-03-23 20:32:36 -04:00

174 lines
4.8 KiB
Lua
Executable file

local function augroup(name)
return vim.api.nvim_create_augroup('nvim_' .. name, { clear = true })
end
local autocmd = vim.api.nvim_create_autocmd
-- Remove trailing whitespace (runs before LSP format)
autocmd('BufWritePre', {
group = augroup('trailing_whitespace'),
pattern = '*',
command = [[%s/\s\+$//e]],
desc = 'Remove trailing whitespace on save',
})
-- Ensure file ends with a newline (skip binary/yaml)
autocmd('BufWritePre', {
group = augroup('fix_eol'),
pattern = '*',
callback = function()
local ft = vim.bo.filetype
local skip = { yaml = true, yaml_frontmatter = true }
if not skip[ft] and not vim.bo.eol and not vim.bo.binary then
vim.bo.eol = true
end
end,
desc = 'Ensure newline at EOF',
})
-- Auto-format on save via LSP
autocmd('BufWritePre', {
group = augroup('format_on_save'),
pattern = '*',
callback = function()
-- Only format if an LSP with formatting support is attached
local clients = vim.lsp.get_clients({ bufnr = 0 })
for _, client in ipairs(clients) do
if client:supports_method('textDocument/formatting', 0) then
vim.lsp.buf.format({ async = false, id = client.id })
return
end
end
end,
desc = 'Auto-format on save with LSP',
})
-- Highlight yanked text
autocmd('TextYankPost', {
group = augroup('highlight_yank'),
callback = function()
vim.hl.on_yank()
end,
desc = 'Highlight yanked text',
})
local function get_project_root()
local markers = { '.git', '.hg', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'package.json' }
local buf_dir = vim.fn.expand('%:p:h')
return vim.fs.root(buf_dir, markers) or buf_dir
end
local function make_relative(paths, root)
return vim.tbl_map(function(p)
return vim.fn.fnamemodify(p, ':~:.') -- strip home + make relative to cwd
end, paths)
end
local function fuzzy_find(text, _)
local root = get_project_root()
if vim.fn.executable('fd') == 1 then
local result = vim.fn.systemlist(
string.format(
'fd --type f --hidden --exclude .git -g "*%s*" %s',
vim.fn.escape(text, '"\\'),
vim.fn.shellescape(root)
)
)
if vim.v.shell_error == 0 and #result > 0 then
return make_relative(result, root)
end
end
if vim.fn.executable('rg') == 1 then
local result = vim.fn.systemlist(string.format("rg --files --hidden --glob '!.git' %s", vim.fn.shellescape(root)))
if vim.v.shell_error == 0 then
return make_relative(vim.fn.matchfuzzy(result, text), root)
end
end
local saved = vim.fn.getcwd()
vim.cmd('lcd ' .. vim.fn.fnameescape(root))
local files = vim.fn.glob('**/*', false, true)
vim.cmd('lcd ' .. vim.fn.fnameescape(saved))
return vim.fn.matchfuzzy(files, text)
end
_G.fuzzy_find = fuzzy_find
vim.opt.findfunc = 'v:lua.fuzzy_find'
-- Also anchor path to project root so gf works correctly
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, {
callback = function()
local root = get_project_root()
if root then
vim.opt_local.path = { root .. '/**' }
end
end,
desc = 'Anchor path to project root for gf/:find',
})
-- Restore last cursor position when reopening a file
autocmd('BufReadPost', {
group = augroup('restore_cursor'),
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local line_count = vim.api.nvim_buf_line_count(0)
if mark[1] > 0 and mark[1] <= line_count then
pcall(vim.api.nvim_win_set_cursor, 0, mark)
vim.cmd('normal! zz') -- centre the restored position
end
end,
desc = 'Restore last cursor position on open',
})
-- Keep splits balanced when Neovim is resized
autocmd('VimResized', {
group = augroup('resize_splits'),
callback = function()
vim.cmd('tabdo wincmd =')
end,
desc = 'Equalise splits on terminal resize',
})
vim.opt.wildmode = { 'longest:full', 'full' }
vim.opt.wildoptions = 'pum'
autocmd('CmdlineLeave', {
group = augroup('cmdline_completion'),
pattern = ':',
callback = function()
vim.opt.wildmode = {}
end,
desc = 'Disable wildmenu popup outside cmdline',
})
autocmd('TermOpen', {
group = augroup('terminal'),
pattern = '*',
callback = function()
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.signcolumn = 'no'
vim.cmd('startinsert')
vim.keymap.set('t', '<Esc><Esc>', '<C-\\><C-n>', {
buffer = true,
desc = 'Exit terminal mode',
})
end,
desc = 'Terminal: no line numbers, start in insert, bind Esc',
})
autocmd('VimEnter', {
group = augroup('lazy_autoupdate'),
callback = function()
-- Defer so the UI is fully ready before the update check runs
vim.schedule(function()
local ok, lazy_status = pcall(require, 'lazy.status')
if ok and lazy_status.has_updates() then
require('lazy').update({ show = false })
end
end)
end,
desc = 'Auto-update plugins on startup if updates are available',
})