diff options
| author | 2026-04-17 10:53:12 +0100 | |
|---|---|---|
| committer | 2026-04-17 10:53:12 +0100 | |
| commit | d301dadc3fd279b0383eb0d37dc00dfdd17e9f2b (patch) | |
| tree | 5ad0fc476b14cfc38c54447d31018b5fdef38a5a /home/.config/nvim/lua/config | |
| parent | 3e3d394d014dded76e3aac9a615f5efa93dd1c59 (diff) | |
| download | dotfiles-d301dadc3fd279b0383eb0d37dc00dfdd17e9f2b.tar.gz dotfiles-d301dadc3fd279b0383eb0d37dc00dfdd17e9f2b.tar.bz2 dotfiles-d301dadc3fd279b0383eb0d37dc00dfdd17e9f2b.zip | |
refactor: move core config from after/plugin/ and cfg/ to lua/config/
after/plugin/ runs after ALL plugins with unpredictable ordering. Explicit
require() from init.lua after lazy.setup() is more predictable and standard.
- after/plugin/autocmds.lua → lua/config/autocmds.lua
- after/plugin/mappings.lua → lua/config/keymaps.lua
- cfg/options.lua → config/options.lua
- cfg/utils.lua → config/utils.lua
Creates a consistent lua/config/ namespace for all non-plugin configuration.
Diffstat (limited to 'home/.config/nvim/lua/config')
| -rw-r--r-- | home/.config/nvim/lua/config/autocmds.lua | 256 | ||||
| -rw-r--r-- | home/.config/nvim/lua/config/keymaps.lua | 156 | ||||
| -rw-r--r-- | home/.config/nvim/lua/config/options.lua | 103 | ||||
| -rw-r--r-- | home/.config/nvim/lua/config/utils.lua | 29 |
4 files changed, 544 insertions, 0 deletions
diff --git a/home/.config/nvim/lua/config/autocmds.lua b/home/.config/nvim/lua/config/autocmds.lua new file mode 100644 index 0000000..c97389f --- /dev/null +++ b/home/.config/nvim/lua/config/autocmds.lua @@ -0,0 +1,256 @@ +local function augroup(name) + return vim.api.nvim_create_augroup(name, { clear = true }) +end + +local autocmd = vim.api.nvim_create_autocmd + +-- Check if we need to reload the file when it changed +autocmd({ "FocusGained", "TermClose", "TermLeave" }, { + group = augroup("checktime"), + callback = function() + if vim.o.buftype ~= "nofile" then + vim.cmd("checktime") + end + end, +}) + +-- Highlight on yank +autocmd("TextYankPost", { + group = augroup("highlight_yank"), + callback = vim.hl.on_yank, +}) + +-- go to last loc when opening a buffer +autocmd("BufReadPost", { + group = augroup("last_loc"), + callback = function(event) + local exclude = { "gitcommit" } + local buf = event.buf + if + vim.tbl_contains(exclude, vim.bo[buf].filetype) or vim.b[buf].last_loc + then + return + end + vim.b[buf].last_loc = true + local mark = vim.api.nvim_buf_get_mark(buf, '"') + local lcount = vim.api.nvim_buf_line_count(buf) + if mark[1] > 0 and mark[1] <= lcount then + pcall(vim.api.nvim_win_set_cursor, 0, mark) + end + end, +}) + +-- close some filetypes with <q> +autocmd("FileType", { + group = augroup("close_with_q"), + pattern = { + "PlenaryTestPopup", + "checkhealth", + "dbout", + "gitsigns-blame", + "grug-far", + "help", + "lspinfo", + "neotest-output", + "neotest-output-panel", + "neotest-summary", + "notify", + "qf", + "spectre_panel", + "startuptime", + "tsplayground", + }, + callback = function(event) + vim.bo[event.buf].buflisted = false + vim.schedule(function() + vim.keymap.set("n", "q", function() + vim.cmd("close") + pcall(vim.api.nvim_buf_delete, event.buf, { force = true }) + end, { + buffer = event.buf, + silent = true, + desc = "Quit buffer", + }) + end) + end, +}) + +-- make it easier to close man-files when opened inline +autocmd("FileType", { + group = augroup("man_unlisted"), + pattern = { "man" }, + callback = function(event) + vim.bo[event.buf].buflisted = false + end, +}) + +-- Auto create dir when saving a file, in case some intermediate directory does not exist +autocmd({ "BufWritePre" }, { + group = augroup("auto_create_dir"), + callback = function(event) + if event.match:match("^%w%w+:[\\/][\\/]") then + return + end + local file = vim.uv.fs_realpath(event.match) or event.match + vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") + end, +}) + +autocmd("BufWritePost", { + group = augroup("bspwm"), + pattern = "*bspwmrc", + command = "!bspc wm --restart", +}) +autocmd("BufWritePost", { + group = augroup("polybar"), + pattern = "*/polybar/config", + command = "!polybar-msg cmd restart", +}) +autocmd("BufWritePost", { + group = augroup("xdg-user-dirs"), + pattern = "user-dirs.dirs,user-dirs.locale", + command = "!xdg-user-dirs-update", +}) +autocmd("BufWritePost", { + group = augroup("dunst"), + pattern = "dunstrc", + command = "!killall -SIGUSR2 dunst", +}) +autocmd("BufWritePost", { + group = augroup("fc-cache"), + pattern = "fonts.conf", + command = "!fc-cache", +}) + +autocmd("LspAttach", { + group = augroup("lsp-attach"), + callback = function(event) + local bufnr = event.buf + + local function map(mode, l, r, desc) + vim.keymap.set(mode, l, r, { buffer = bufnr, desc = "LSP: " .. desc }) + end + local function nmap(l, r, desc) + map("n", l, r, desc) + end + nmap("<c-]>", vim.lsp.buf.definition, "Goto definition") + nmap("gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration") + + -- fzf-lua LSP navigation + local fzf = require("fzf-lua") + nmap("gd", fzf.lsp_definitions, "[G]oto [D]efinition") + nmap("gvd", function() + fzf.lsp_definitions({ jump1_action = fzf.actions.file_vsplit }) + end, "[G]oto in a [V]ertical split to [D]efinition") + nmap("gxd", function() + fzf.lsp_definitions({ jump1_action = fzf.actions.file_split }) + end, "[G]oto in a [X]horizontal split to [D]efinition") + nmap("gtd", function() + fzf.lsp_definitions({ jump1_action = fzf.actions.file_tabedit }) + end, "[G]oto in a [T]ab to [D]efinition") + nmap("<leader>D", fzf.lsp_typedefs, "Type [D]efinition") + nmap("<leader>vD", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_vsplit }) + end, "Open in a [V]ertical split Type [D]efinition") + nmap("<leader>xD", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_split }) + end, "Open in a [X]horizontal split Type [D]efinition") + nmap("<leader>tD", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_tabedit }) + end, "Open in a [T]ab Type [D]efinition") + nmap("gri", fzf.lsp_implementations, "[G]oto [I]mplementation") + nmap("grvi", function() + fzf.lsp_implementations({ jump1_action = fzf.actions.file_vsplit }) + end, "[G]oto in a [V]ertical split to [I]mplementation") + nmap("grxi", function() + fzf.lsp_implementations({ jump1_action = fzf.actions.file_split }) + end, "[G]oto in a [X]horizontal split to [I]mplementation") + nmap("grti", function() + fzf.lsp_implementations({ jump1_action = fzf.actions.file_tabedit }) + end, "[G]oto in a [T]ab to [I]mplementation") + nmap("grr", fzf.lsp_references, "[G]oto [R]eferences") + nmap("<leader>ic", fzf.lsp_incoming_calls, "[I]ncoming [C]alls") + nmap("<leader>oc", fzf.lsp_outgoing_calls, "[O]utgoing [C]alls") + nmap("gO", fzf.lsp_document_symbols, "d[O]ocument symbols") + nmap("<leader>ws", fzf.lsp_live_workspace_symbols, "[W]orkspace [S]ymbols") + nmap("<leader>wd", fzf.diagnostics_workspace, "[W]orkspace [D]iagnostics") + + -- Highlight references under cursor + local client = vim.lsp.get_client_by_id(event.data.client_id) + if + client + and client:supports_method( + vim.lsp.protocol.Methods.textDocument_documentHighlight, + event.buf + ) + then + local highlight_augroup = + vim.api.nvim_create_augroup("lsp-highlight", { clear = false }) + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + buffer = event.buf, + group = highlight_augroup, + callback = vim.lsp.buf.document_highlight, + }) + + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { + buffer = event.buf, + group = highlight_augroup, + callback = vim.lsp.buf.clear_references, + }) + + vim.api.nvim_create_autocmd("LspDetach", { + group = vim.api.nvim_create_augroup("lsp-detach", { clear = true }), + callback = function(event2) + vim.lsp.buf.clear_references() + vim.api.nvim_clear_autocmds({ + group = "lsp-highlight", + buffer = event2.buf, + }) + end, + }) + end + + if + client + and client:supports_method( + vim.lsp.protocol.Methods.textDocument_codeLens, + event.buf + ) + then + vim.api.nvim_create_autocmd( + { "CursorHold", "CursorHoldI", "InsertLeave" }, + { + buffer = bufnr, + group = vim.api.nvim_create_augroup("codelens", { clear = true }), + callback = vim.lsp.codelens.refresh, + } + ) + end + + -- Toggle inlay hints + if + client + and client:supports_method( + vim.lsp.protocol.Methods.textDocument_inlayHint, + event.buf + ) + then + nmap("<leader>th", function() + vim.lsp.inlay_hint.enable( + not vim.lsp.inlay_hint.is_enabled({ bufnr = event.buf }) + ) + end, "[T]oggle Inlay [H]ints") + end + end, +}) + +autocmd("FileType", { + group = augroup("treesitter_start"), + pattern = { "*" }, + callback = function() + if pcall(vim.treesitter.start) then + vim.wo.foldexpr = "v:lua.vim.treesitter.foldexpr()" + vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" + end + end, +}) diff --git a/home/.config/nvim/lua/config/keymaps.lua b/home/.config/nvim/lua/config/keymaps.lua new file mode 100644 index 0000000..f198a95 --- /dev/null +++ b/home/.config/nvim/lua/config/keymaps.lua @@ -0,0 +1,156 @@ +local function map(mode, l, r, desc) + vim.keymap.set(mode, l, r, { desc = desc }) +end +local function cmd(mode, l, r, desc) + map(mode, l, "<cmd>" .. r .. "<cr>", desc) +end +local function cmdi(mode, l, r, desc) + map(mode, l, ":" .. r, desc) +end +local function nmap(l, r, desc) + map("n", l, r, desc) +end +local function vmap(l, r, desc) + map("v", l, r, desc) +end +local function nvmap(l, r, desc) + map({ "n", "v" }, l, r, desc) +end +local function ncmd(l, r, desc) + cmd("n", l, r, desc) +end +local function ncmdi(l, r, desc) + cmdi("n", l, r, desc) +end +local function vcmdi(l, r, desc) + cmdi("v", l, r, desc) +end + +ncmd("<esc>", "nohlsearch") + +nmap("<Space>", "<Nop>") + +-- make an accidental ; press also enter command mode +nmap(";", ":") + +-- highlight last inserted text +nmap("gV", "`[v`]") + +nmap("<down>", "<c-e>") +nmap("<up>", "<c-y>") + +-- go to first non-blank character of current line +nvmap("<c-a>", "^") +nvmap("<c-e>", "$") + +-- This extends p in visual mode (note the noremap), so that if you paste from +-- the unnamed (ie. default) register, that register content is not replaced by +-- the visual selection you just pasted over–which is the default behavior. +-- This enables the user to yank some text and paste it over several places in +-- a row, without using a named register +-- map.v('p', "p:if v:register == '"'<Bar>let @@=@0<Bar>endif<cr>") +vmap("p", 'p:let @+=@0<CR>:let @"=@0<CR>') + +-- Find and Replace binds +ncmdi("<localleader>s", "%s/") +vcmdi("<localleader>s", "s/") + +ncmd("<leader>x", "wall") +ncmd("<leader>z", "wqall") +ncmd("<leader>q", "quitall") + +vim.keymap.set( + "t", + "<esc><esc>", + "<c-\\><c-n>", + { silent = true, noremap = true, desc = "Exit terminal mode" } +) + +nmap("[w", function() + vim.diagnostic.jump({ + count = -vim.v.count1, + severity = { min = vim.diagnostic.severity.WARN }, + }) +end) +nmap("]w", function() + vim.diagnostic.jump({ + count = vim.v.count1, + severity = { min = vim.diagnostic.severity.WARN }, + }) +end) +nmap("[e", function() + vim.diagnostic.jump({ + count = -vim.v.count1, + severity = vim.diagnostic.severity.ERROR, + }) +end) +nmap("]e", function() + vim.diagnostic.jump({ + count = vim.v.count1, + severity = vim.diagnostic.severity.ERROR, + }) +end) + +nmap( + "<leader>oq", + vim.diagnostic.setloclist, + "[O]pen diagnostic [Q]uickfix list" +) + +nmap("yp", function() + vim.fn.setreg("+", vim.fn.expand("%")) +end, "[Y]ank [P]ath") + +local sudo_exec = function(_cmd) + vim.fn.inputsave() + local password = vim.fn.inputsecret("Password: ") + vim.fn.inputrestore() + if not password or #password == 0 then + vim.notify("Invalid password, sudo aborted", vim.log.levels.WARN) + return false + end + local out = vim.fn.system(string.format("sudo -p '' -S %s", _cmd), password) + if vim.v.shell_error ~= 0 then + print("\r\n") + vim.notify(out, vim.log.levels.ERROR) + return false + end + return true +end + +vim.api.nvim_create_user_command("SudoWrite", function(opts) + local tmpfile = vim.fn.tempname() + local filepath + if #opts.fargs == 1 then + filepath = opts.fargs[1] + else + filepath = vim.fn.expand("%") + end + if not filepath or #filepath == 0 then + vim.notify("E32: No file name", vim.log.levels.ERROR) + return + end + -- `bs=1048576` is equivalent to `bs=1M` for GNU dd or `bs=1m` for BSD dd + -- Both `bs=1M` and `bs=1m` are non-POSIX + local _cmd = string.format( + "dd if=%s of=%s bs=1048576", + vim.fn.shellescape(tmpfile), + vim.fn.shellescape(filepath) + ) + -- no need to check error as this fails the entire function + vim.api.nvim_exec2(string.format("write! %s", tmpfile), { output = true }) + if sudo_exec(_cmd) then + -- refreshes the buffer and prints the "written" message + vim.cmd.checktime() + -- exit command mode + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("<Esc>", true, false, true), + "n", + true + ) + end + vim.fn.delete(tmpfile) +end, { + nargs = "?", + desc = "Write using sudo permissions", +}) diff --git a/home/.config/nvim/lua/config/options.lua b/home/.config/nvim/lua/config/options.lua new file mode 100644 index 0000000..4b6d588 --- /dev/null +++ b/home/.config/nvim/lua/config/options.lua @@ -0,0 +1,103 @@ +local opt = vim.o + +opt.undofile = true +opt.swapfile = false +opt.shadafile = "NONE" + +opt.number = true +opt.cursorline = true +opt.signcolumn = "auto:2" +opt.laststatus = 3 + +opt.expandtab = true +opt.shiftround = true +opt.shiftwidth = 0 +opt.softtabstop = -1 +opt.tabstop = 4 + +opt.gdefault = true +opt.ignorecase = true +opt.smartcase = true + +opt.splitbelow = true +opt.splitright = true +opt.splitkeep = "screen" + +opt.linebreak = true +opt.breakindent = true +opt.textwidth = 80 +opt.colorcolumn = "+1" +vim.opt.formatoptions:remove("t") + +opt.messagesopt = "wait:5000,history:500" + +vim.opt.shortmess:append({ a = true }) + +opt.updatetime = 250 +opt.timeoutlen = 300 + +vim.opt.completeopt = { "menuone", "noselect", "popup", "fuzzy", "nearest" } +opt.scrolloff = 999 +opt.sidescrolloff = 5 + +vim.schedule(function() + opt.clipboard = vim.env.SSH_TTY and "" or "unnamedplus" +end) + +opt.mouse = "a" + +vim.opt.wildmode = { "longest", "full" } + +vim.opt.cpoptions:remove({ "_" }) + +vim.opt.listchars = { + tab = "> ", + space = "·", + extends = ">", + precedes = "<", + nbsp = "+", +} +opt.list = true + +opt.confirm = true + +opt.virtualedit = "block" +opt.spelloptions = "camel" + +vim.g.loaded_node_provider = 0 +vim.g.loaded_perl_provider = 0 +vim.g.loaded_python3_provider = 0 + +vim.opt.diffopt:append({ + hiddenoff = true, + iblank = true, + iwhiteall = true, + algorithm = "histogram", +}) + +if vim.fn.executable("rg") then + opt.grepprg = "rg\\ --vimgrep" + opt.grepformat = "%f:%l:%c:%m" +end + +opt.pumblend = 20 +opt.pumborder = "rounded" + +opt.winborder = "rounded" + +vim.o.foldmethod = "expr" +vim.o.foldenable = false + +vim.g.mapleader = " " +vim.g.maplocalleader = "," + +vim.diagnostic.config({ + virtual_text = false, + virtual_lines = false, +}) + +opt.sessionoptions = + "blank,buffers,curdir,help,tabpages,winsize,winpos,terminal,localoptions" + +vim.o.exrc = true + diff --git a/home/.config/nvim/lua/config/utils.lua b/home/.config/nvim/lua/config/utils.lua new file mode 100644 index 0000000..300d7a7 --- /dev/null +++ b/home/.config/nvim/lua/config/utils.lua @@ -0,0 +1,29 @@ +local M = {} +local gitsigns = require("gitsigns") +local conform = require("conform") + +function M.format_hunks(options) + local hunks = gitsigns.get_hunks() + if not hunks or vim.tbl_isempty(hunks) then + return + end + for _, hunk in ipairs(hunks) do + if hunk and hunk.added then + local start = hunk.added.start + local last = start + hunk.added.count + -- nvim_buf_get_lines uses zero-based indexing -> subtract from last + local last_hunk_line = + vim.api.nvim_buf_get_lines(0, last - 2, last - 1, true)[1] + local range = + { start = { start, 0 }, ["end"] = { last - 1, last_hunk_line:len() } } + options = vim.tbl_extend( + "force", + { range = range, lsp_fallback = true, quiet = true }, + options or {} + ) + conform.format(options) + end + end +end + +return M |
