From d00bf2cb2d5087164fa509a4f20a2be62a519044 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Tue, 21 Apr 2026 01:23:18 +0100 Subject: refactor: restructure to chezmoi source state Rename home/ contents to chezmoi naming conventions: - dot_ prefix for dotfiles and dot-dirs - private_dot_ for .gnupg and .ssh directories - private_ for 0600 files (nym.pub) - executable_ for scripts in .local/bin and display-toggle.sh - symlink_ for mimeapps.list symlink --- dot_config/nvim/lua/config/autocmds.lua | 128 +++++++++++++ dot_config/nvim/lua/config/keymaps.lua | 136 ++++++++++++++ dot_config/nvim/lua/config/options.lua | 114 ++++++++++++ dot_config/nvim/lua/plugins/ai.lua | 34 ++++ dot_config/nvim/lua/plugins/completion.lua | 86 +++++++++ dot_config/nvim/lua/plugins/debug.lua | 75 ++++++++ dot_config/nvim/lua/plugins/editing.lua | 61 +++++++ dot_config/nvim/lua/plugins/git.lua | 123 +++++++++++++ dot_config/nvim/lua/plugins/init.lua | 58 ++++++ dot_config/nvim/lua/plugins/lsp.lua | 280 +++++++++++++++++++++++++++++ dot_config/nvim/lua/plugins/runner.lua | 77 ++++++++ dot_config/nvim/lua/plugins/search.lua | 60 +++++++ dot_config/nvim/lua/plugins/session.lua | 77 ++++++++ dot_config/nvim/lua/plugins/treesitter.lua | 83 +++++++++ dot_config/nvim/lua/plugins/ui.lua | 58 ++++++ 15 files changed, 1450 insertions(+) create mode 100644 dot_config/nvim/lua/config/autocmds.lua create mode 100644 dot_config/nvim/lua/config/keymaps.lua create mode 100644 dot_config/nvim/lua/config/options.lua create mode 100644 dot_config/nvim/lua/plugins/ai.lua create mode 100644 dot_config/nvim/lua/plugins/completion.lua create mode 100644 dot_config/nvim/lua/plugins/debug.lua create mode 100644 dot_config/nvim/lua/plugins/editing.lua create mode 100644 dot_config/nvim/lua/plugins/git.lua create mode 100644 dot_config/nvim/lua/plugins/init.lua create mode 100644 dot_config/nvim/lua/plugins/lsp.lua create mode 100644 dot_config/nvim/lua/plugins/runner.lua create mode 100644 dot_config/nvim/lua/plugins/search.lua create mode 100644 dot_config/nvim/lua/plugins/session.lua create mode 100644 dot_config/nvim/lua/plugins/treesitter.lua create mode 100644 dot_config/nvim/lua/plugins/ui.lua (limited to 'dot_config/nvim/lua') diff --git a/dot_config/nvim/lua/config/autocmds.lua b/dot_config/nvim/lua/config/autocmds.lua new file mode 100644 index 0000000..2d1ea9b --- /dev/null +++ b/dot_config/nvim/lua/config/autocmds.lua @@ -0,0 +1,128 @@ +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 +autocmd("FileType", { + group = augroup("close_with_q"), + pattern = { + "PlenaryTestPopup", + "checkhealth", + "dbout", + "gitsigns-blame", + "help", + "lspinfo", + "notify", + "qf", + "startuptime", + }, + 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("sway"), + pattern = "*/sway/config", + command = "!swaymsg reload", +}) +autocmd("BufWritePost", { + group = augroup("waybar"), + pattern = "*/waybar/*", + command = "!killall -SIGUSR2 waybar", +}) +autocmd("BufWritePost", { + group = augroup("xdg-user-dirs"), + pattern = "user-dirs.dirs,user-dirs.locale", + command = "!xdg-user-dirs-update", +}) +autocmd("BufWritePost", { + group = augroup("mako"), + pattern = "*/mako/config", + command = "!makoctl reload", +}) +autocmd("BufWritePost", { + group = augroup("fc-cache"), + pattern = "fonts.conf", + command = "!fc-cache", +}) + +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.vim.treesitter.indentexpr()" + end + end, +}) diff --git a/dot_config/nvim/lua/config/keymaps.lua b/dot_config/nvim/lua/config/keymaps.lua new file mode 100644 index 0000000..366a37e --- /dev/null +++ b/dot_config/nvim/lua/config/keymaps.lua @@ -0,0 +1,136 @@ +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, "" .. r .. "", 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("", "nohlsearch") + +-- highlight last inserted text +nmap("gV", "`[v`]") + +nmap("", "") +nmap("", "") + +-- paste over selection without clobbering registers +vmap("p", '"_dP') + +-- Find and Replace binds +ncmdi("s", "%s/") +vcmdi("s", "s/") + +ncmd("x", "wall") +ncmd("z", "wqall") +ncmd("q", "quitall") + +vim.keymap.set( + "t", + "", + "", + { 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("yp", function() + vim.fn.setreg("+", vim.fn.expand("%")) +end, "[Y]ank [P]ath") + +local doas_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, doas aborted", vim.log.levels.WARN) + return false + end + local out = vim.fn.system(string.format("doas -S %s", _cmd), password .. "\n") + 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("DoasWrite", 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 doas_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("", true, false, true), + "n", + true + ) + end + vim.fn.delete(tmpfile) +end, { + nargs = "?", + desc = "Write using doas permissions", +}) diff --git a/dot_config/nvim/lua/config/options.lua b/dot_config/nvim/lua/config/options.lua new file mode 100644 index 0000000..b2409b5 --- /dev/null +++ b/dot_config/nvim/lua/config/options.lua @@ -0,0 +1,114 @@ +local opt = vim.o + +-- Persistence +opt.undofile = true -- persist undo history across sessions +opt.swapfile = false -- no swap files; rely on undofile for recovery + +-- Gutter +opt.number = true -- show line numbers +opt.cursorline = true -- highlight current line +opt.signcolumn = "auto:2" -- up to 2 sign columns, auto-hide when empty +opt.laststatus = 3 -- single global statusline + +-- Indentation (defaults; guess-indent overrides per-buffer) +opt.expandtab = true -- spaces instead of tabs +opt.shiftround = true -- round indent to shiftwidth multiples +opt.shiftwidth = 0 -- follow tabstop value +opt.softtabstop = -1 -- follow shiftwidth value +opt.tabstop = 4 -- 4-space tabs + +-- Search +opt.gdefault = true -- substitute all matches per line by default +opt.ignorecase = true -- case-insensitive search +opt.smartcase = true -- ...unless query has uppercase + +-- Splits +opt.splitbelow = true -- horizontal splits open below +opt.splitright = true -- vertical splits open right +opt.splitkeep = "screen" -- keep text position stable on split + +-- Line wrapping +opt.linebreak = true -- wrap at word boundaries +opt.breakindent = true -- indent wrapped lines +opt.textwidth = 80 -- wrap column for formatting +opt.colorcolumn = "+1" -- highlight column after textwidth +vim.opt.formatoptions:remove("t") -- don't auto-wrap text while typing + +-- Messages +opt.messagesopt = "wait:5000,history:500" -- message display timing and history depth + +vim.opt.shortmess:append({ a = true }) -- abbreviate all file messages + +-- Timing +opt.updatetime = 250 -- CursorHold delay (ms); affects gitsigns, diagnostics +opt.timeoutlen = 300 -- key sequence timeout (ms); affects which-key popup delay + +-- Completion and scrolling +vim.opt.completeopt = { "menuone", "noselect", "popup", "fuzzy", "nearest" } +opt.scrolloff = 999 -- keep cursor vertically centered +opt.sidescrolloff = 5 -- horizontal scroll margin + +-- Clipboard (deferred to avoid blocking startup on clipboard detection) +vim.schedule(function() + opt.clipboard = vim.env.SSH_TTY and "" or "unnamedplus" +end) + +opt.mouse = "a" -- enable mouse in all modes + +vim.opt.wildmode = { "longest", "full" } -- cmdline completion: longest match, then full menu + +vim.opt.cpoptions:remove({ "_" }) -- cw changes to end of word (not compatible vi behavior) + +-- Visible whitespace +vim.opt.listchars = { + tab = "> ", + space = "ยท", + extends = ">", + precedes = "<", + nbsp = "+", +} +opt.list = true -- show whitespace characters + +opt.confirm = true -- confirm before closing unsaved buffers + +opt.virtualedit = "block" -- allow cursor past end of line in visual block +opt.spelloptions = "camel" -- treat camelCase words as separate words for spell check + +-- Disable unused providers +vim.g.loaded_node_provider = 0 +vim.g.loaded_perl_provider = 0 +vim.g.loaded_python3_provider = 0 + +-- Diff +vim.opt.diffopt:append({ + hiddenoff = true, -- disable diff on hidden buffers + iblank = true, -- ignore blank line changes + iwhiteall = true, -- ignore all whitespace changes + algorithm = "histogram", -- better diff algorithm +}) + +-- Use ripgrep for :grep +if vim.fn.executable("rg") then + opt.grepprg = "rg\\ --vimgrep" + opt.grepformat = "%f:%l:%c:%m" +end + +-- Popup and window borders +opt.pumblend = 20 -- popup menu transparency +opt.pumborder = "rounded" +opt.winborder = "rounded" -- default border for floating windows + +-- Folding: set up treesitter-based folds but start with them closed. +-- Autocmd in autocmds.lua sets foldexpr per-buffer when treesitter is available. +vim.o.foldmethod = "expr" +vim.o.foldenable = false + +vim.g.mapleader = " " +vim.g.maplocalleader = "," + +-- Session persistence (for auto-session) +opt.sessionoptions = + "blank,buffers,curdir,help,tabpages,winsize,winpos,terminal,localoptions" + +opt.exrc = true -- source project-local .nvim.lua files + diff --git a/dot_config/nvim/lua/plugins/ai.lua b/dot_config/nvim/lua/plugins/ai.lua new file mode 100644 index 0000000..8c213b5 --- /dev/null +++ b/dot_config/nvim/lua/plugins/ai.lua @@ -0,0 +1,34 @@ +require("copilot").setup({ + suggestion = { enabled = false }, + panel = { enabled = false }, + server_opts_overrides = { + settings = { + telemetry = { + telemetryLevel = "off", + }, + }, + }, + nes = { + enabled = true, + keymap = { + accept_and_goto = "p", + accept = false, + dismiss = "", + }, + }, +}) + +-- Accept NES in insert mode (copilot.lua only binds normal mode) +vim.keymap.set("i", "", function() + local ok, nes = pcall(require, "copilot-lsp.nes") + if ok and nes.apply_pending_nes() then + return + end + -- Fallback: native (scroll window forward) + local key = vim.api.nvim_replace_termcodes("", true, false, true) + vim.api.nvim_feedkeys(key, "n", false) +end, { desc = "Accept Copilot NES / scroll forward" }) + +vim.keymap.set("n", "tc", function() + require("copilot.command").toggle() +end, { desc = "[T]oggle [C]opilot attachment" }) diff --git a/dot_config/nvim/lua/plugins/completion.lua b/dot_config/nvim/lua/plugins/completion.lua new file mode 100644 index 0000000..df24a5d --- /dev/null +++ b/dot_config/nvim/lua/plugins/completion.lua @@ -0,0 +1,86 @@ +require("blink.compat").setup({}) + +require("blink.cmp").setup({ + keymap = { + preset = "cmdline", + [""] = { "accept", "fallback" }, + }, + appearance = { + use_nvim_cmp_as_default = true, + }, + completion = { + menu = { + draw = { + columns = { { "kind_icon" }, { "label", gap = 1 } }, + components = { + label = { + text = function(ctx) + return require("colorful-menu").blink_components_text(ctx) + end, + highlight = function(ctx) + return require("colorful-menu").blink_components_highlight(ctx) + end, + }, + }, + }, + }, + list = { + selection = { + preselect = function() + return not require("blink.cmp").snippet_active({ direction = 1 }) + end, + }, + }, + documentation = { auto_show = true }, + }, + signature = { + enabled = true, + trigger = { + enabled = true, + show_on_keyword = true, + show_on_insert = true, + }, + }, + sources = { + default = { "lazydev", "lsp", "copilot", "snippets", "path", "buffer" }, + per_filetype = { + ["dap-repl"] = { "dap" }, + }, + providers = { + path = { + opts = { + get_cwd = vim.fn.getcwd, + }, + }, + copilot = { + name = "copilot", + module = "blink-copilot", + score_offset = 100, + async = true, + }, + lazydev = { + name = "LazyDev", + module = "lazydev.integrations.blink", + score_offset = 100, + }, + dap = { name = "dap", module = "blink.compat.source" }, + }, + }, +}) + +require("blink.pairs").setup({ + mappings = { + disabled_filetypes = {}, + }, + highlights = { + groups = { + "BlinkIndentOrange", + "BlinkIndentViolet", + "BlinkIndentBlue", + "BlinkIndentRed", + "BlinkIndentCyan", + "BlinkIndentYellow", + "BlinkIndentGreen", + }, + }, +}) diff --git a/dot_config/nvim/lua/plugins/debug.lua b/dot_config/nvim/lua/plugins/debug.lua new file mode 100644 index 0000000..bef0d1c --- /dev/null +++ b/dot_config/nvim/lua/plugins/debug.lua @@ -0,0 +1,75 @@ +vim.keymap.set("n", "td", function() + require("debugmaster").mode.toggle() +end, { desc = "[T]oggle [D]ebug mode" }) + +local dap = require("dap") + +local function get_env_vars() + local variables = vim.fn.environ() + table.insert(variables, { ASAN_OPTIONS = "detect_leaks=0" }) + return variables +end + +dap.adapters.lldb = { + type = "executable", + command = "lldb-dap", + name = "lldb", + env = get_env_vars, +} +dap.adapters.gdb = { + type = "executable", + command = "gdb", + args = { "--interpreter=dap" }, + env = get_env_vars, +} +dap.adapters.codelldb = { + type = "executable", + command = "codelldb", + env = get_env_vars, +} + +local function get_program() + local _program + vim.ui.input({ + prompt = "Program: ", + complete = "file_in_path", + }, function(res) + _program = res + end) + return vim.fn.system("which " .. _program):gsub("\n$", "") +end + +local function get_args() + local _args + vim.ui.input({ + prompt = "Args: ", + default = vim.fn.getreg("+"), + complete = "file", + }, function(res) + _args = res + end) + return require("dap.utils").splitstr(_args) +end + +dap.configurations.cpp = { + { + name = "codelldb Launch", + type = "codelldb", + request = "launch", + cwd = "${workspaceFolder}", + program = get_program, + args = get_args, + stopOnEntry = true, + console = "integratedTerminal", + }, +} + +dap.configurations.c = dap.configurations.cpp +dap.configurations.rust = dap.configurations.cpp + +require("nvim-dap-virtual-text").setup({}) +require("mason-nvim-dap").setup({ + automatic_installation = false, + handlers = {}, + ensure_installed = {}, +}) diff --git a/dot_config/nvim/lua/plugins/editing.lua b/dot_config/nvim/lua/plugins/editing.lua new file mode 100644 index 0000000..5175516 --- /dev/null +++ b/dot_config/nvim/lua/plugins/editing.lua @@ -0,0 +1,61 @@ +require("guess-indent").setup({}) + +require("various-textobjs").setup({ + keymaps = { + useDefaults = true, + }, +}) + +-- dial.nvim: enhanced increment/decrement on standard vim keys +vim.keymap.set("n", "", function() + return require("dial.map").inc_normal() +end, { expr = true, desc = "Increment" }) +vim.keymap.set("n", "", function() + return require("dial.map").dec_normal() +end, { expr = true, desc = "Decrement" }) +vim.keymap.set("v", "", function() + return require("dial.map").inc_visual() +end, { expr = true, desc = "Increment" }) +vim.keymap.set("v", "", function() + return require("dial.map").dec_visual() +end, { expr = true, desc = "Decrement" }) +vim.keymap.set("v", "g", function() + return require("dial.map").inc_gvisual() +end, { expr = true, desc = "Increment (sequential)" }) +vim.keymap.set("v", "g", function() + return require("dial.map").dec_gvisual() +end, { expr = true, desc = "Decrement (sequential)" }) + +-- refactoring.nvim +require("refactoring").setup({}) + +vim.keymap.set("x", "re", function() + require("refactoring").refactor("Extract Function") +end, { desc = "[R]efactor [E]xtract function" }) +vim.keymap.set("x", "rf", function() + require("refactoring").refactor("Extract Function To File") +end, { desc = "[R]efactor extract function to [F]ile" }) +vim.keymap.set("x", "rv", function() + require("refactoring").refactor("Extract Variable") +end, { desc = "[R]efactor extract [V]ariable" }) +vim.keymap.set("n", "rI", function() + require("refactoring").refactor("Inline Function") +end, { desc = "[R]efactor [I]nline function" }) +vim.keymap.set({ "x", "n" }, "ri", function() + require("refactoring").refactor("Inline Variable") +end, { desc = "[R]efactor [I]nline variable" }) +vim.keymap.set("n", "rb", function() + require("refactoring").refactor("Extract Block") +end, { desc = "[R]efactor extract [B]lock" }) +vim.keymap.set("n", "rB", function() + require("refactoring").refactor("Extract Block To File") +end, { desc = "[R]efactor extract [B]lock to file" }) +vim.keymap.set("n", "rp", function() + require("refactoring").debug.printf({}) +end, { desc = "[R]efactor [P]rint" }) +vim.keymap.set({ "x", "n" }, "rV", function() + require("refactoring").debug.print_var({}) +end, { desc = "[R]efactor [P]rint [V]ariable" }) +vim.keymap.set("n", "rc", function() + require("refactoring").debug.cleanup({}) +end, { desc = "[R]efactor [C]leanup" }) diff --git a/dot_config/nvim/lua/plugins/git.lua b/dot_config/nvim/lua/plugins/git.lua new file mode 100644 index 0000000..b052c33 --- /dev/null +++ b/dot_config/nvim/lua/plugins/git.lua @@ -0,0 +1,123 @@ +require("git-conflict").setup({ + disable_diagnostics = true, + default_mappings = { + next = "]x", + prev = "[x", + }, +}) + +require("neogit").setup({ + disable_commit_confirmation = true, + kind = "split", + console_timeout = 5000, + auto_show_console = false, +}) + +vim.keymap.set("n", "go", function() + require("neogit").open() +end, { desc = "neo[G]it [O]pen" }) + +require("gitlinker").setup({ + callbacks = { + ["git.sommerfeld.dev"] = function(url_data) + local url = require("gitlinker.hosts").get_base_https_url(url_data) + url = url .. "/tree/" .. url_data.file .. "?id=" .. url_data.rev + if url_data.lstart then + url = url .. "#n" .. url_data.lstart + end + return url + end, + }, +}) + +vim.keymap.set("n", "gy", function() + require("gitlinker").get_buf_range_url("n") +end) +vim.keymap.set("v", "gy", function() + require("gitlinker").get_buf_range_url("v") +end) + +require("gitsigns").setup({ + signs = { + change = { show_count = true }, + delete = { show_count = true }, + topdelete = { show_count = true }, + changedelete = { show_count = true }, + }, + numhl = true, + on_attach = function(bufnr) + local gs = require("gitsigns") + local function map(mode, l, r, desc) + vim.keymap.set(mode, l, r, { buffer = bufnr, desc = 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 + -- Navigation + nmap("]c", function() + if vim.wo.diff then + vim.cmd.normal({ "]c", bang = true }) + else + gs.nav_hunk("next") + end + end, "Jump to next git [c]hange") + + nmap("[c", function() + if vim.wo.diff then + vim.cmd.normal({ "[c", bang = true }) + else + gs.nav_hunk("prev") + end + end, "Jump to previous git [c]hange") + + -- Actions + nmap("hs", gs.stage_hunk, "git [s]tage hunk") + nmap("hr", gs.reset_hunk, "git [r]eset hunk") + vmap("hs", function() + gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) + end, "git [s]tage hunk") + vmap("hr", function() + gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) + end, "git [r]eset hunk") + nmap("hS", gs.stage_buffer, "git [S]tage buffer") + nmap("hR", gs.reset_buffer, "git [R]eset buffer") + nmap("hp", gs.preview_hunk, "git [p]review hunk") + nmap("hb", function() + gs.blame_line({ full = true }) + end, "git [b]lame line") + nmap( + "tb", + gs.toggle_current_line_blame, + "[T]oggle git show [b]lame line" + ) + nmap("hd", gs.diffthis, "git [d]iff against index") + nmap("hD", function() + gs.diffthis("~") + end, "git [D]iff against last commit") + nmap("hc", gs.change_base, "git [C]hange base to index") + nmap("hC", function() + gs.change_base("~") + end, "git [C]hange base to HEAD") + nmap( + "tgd", + gs.preview_hunk_inline, + "[T]oggle [G]it show [D]eleted" + ) + nmap("tgw", gs.toggle_word_diff, "[T]oggle [G]it [W]ord diff") + nmap( + "tgl", + gs.toggle_linehl, + "[T]oggle [G]it [L]ine highlighting" + ) + -- Text object + map( + { "o", "x" }, + "ih", + ":Gitsigns select_hunk", + "git [H]unk text object" + ) + end, +}) diff --git a/dot_config/nvim/lua/plugins/init.lua b/dot_config/nvim/lua/plugins/init.lua new file mode 100644 index 0000000..b106b6e --- /dev/null +++ b/dot_config/nvim/lua/plugins/init.lua @@ -0,0 +1,58 @@ +-- Seamless navigation between neovim splits and zellij panes +require("smart-splits").setup({}) +vim.keymap.set("n", "", require("smart-splits").move_cursor_left, { desc = "Move to left split/pane" }) +vim.keymap.set("n", "", require("smart-splits").move_cursor_down, { desc = "Move to below split/pane" }) +vim.keymap.set("n", "", require("smart-splits").move_cursor_up, { desc = "Move to above split/pane" }) +vim.keymap.set("n", "", require("smart-splits").move_cursor_right, { desc = "Move to right split/pane" }) + +require("which-key").setup({ + spec = { + { "g", group = "[G]oto" }, + { "yo", group = "Toggle options" }, + { "]", group = "Navigate to next" }, + { "[", group = "Navigate to previous" }, + { "c", group = "[C]ode", mode = { "n", "x" } }, + { "g", group = "[G]it" }, + { "h", group = "Git [H]unk", mode = { "n", "v" } }, + { "o", group = "[O]verseer" }, + { "r", group = "[R]efactor" }, + { "w", group = "[W]orkspace" }, + { "t", group = "[T]oggle" }, + }, +}) + +vim.keymap.set("n", "?", function() + require("which-key").show({ global = false }) +end, { desc = "Buffer Local Keymaps (which-key)" }) + +require("quicker").setup({ + keys = { + { + ">", + function() + require("quicker").expand({ + before = 2, + after = 2, + add_to_existing = true, + }) + end, + desc = "Expand quickfix context", + }, + { + "<", + function() + require("quicker").collapse() + end, + desc = "Collapse quickfix context", + }, + }, +}) + +vim.keymap.set("n", "tq", function() + require("quicker").toggle() +end, { desc = "[T]oggle [Q]uickfix" }) +vim.keymap.set("n", "tl", function() + require("quicker").toggle({ loclist = true }) +end, { desc = "[T]oggle [L]oclist" }) + +require("oil").setup({}) diff --git a/dot_config/nvim/lua/plugins/lsp.lua b/dot_config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..ddd5bea --- /dev/null +++ b/dot_config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,280 @@ +require("lazydev").setup({ + library = { + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, + }, +}) + +vim.lsp.enable("just") +pcall(vim.lsp.enable, "tblgen_lsp_server") + +require("fidget").setup({}) +require("mason").setup({}) +require("mason-lspconfig").setup({ + ensure_installed = {}, + automatic_installation = false, + handlers = { + function(server_name) + vim.lsp.enable(server_name) + end, + }, +}) +require("mason-tool-installer").setup({ + ensure_installed = { + "actionlint", + "autotools-language-server", + "basedpyright", + "bash-language-server", + "clangd", + "codelldb", + "codespell", + "css-lsp", + "dockerfile-language-server", + "gh", + "gh-actions-language-server", + "groovy-language-server", + "hadolint", + "html-lsp", + "jq", + "json-lsp", + "jsonlint", + "just-lsp", + "lua-language-server", + "markdownlint", + "mdformat", + "neocmakelsp", + "nginx-config-formatter", + "nginx-language-server", + "npm-groovy-lint", + "prettier", + "ruff", + "rust-analyzer", + "shellcheck", + "shellharden", + "shfmt", + "stylelint", + "stylua", + "systemd-lsp", + "systemdlint", + "typescript-language-server", + "typos", + "yaml-language-server", + "yamllint", + "yq", + }, +}) + +vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("lsp-attach", { clear = true }), + 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("", vim.lsp.buf.definition, "Goto definition") + nmap("gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration") + + 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("gvt", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_vsplit }) + end, "[G]oto in a [V]ertical split to [T]ype definition") + nmap("gxt", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_split }) + end, "[G]oto in a [X]horizontal split to [T]ype definition") + nmap("gtt", function() + fzf.lsp_typedefs({ jump1_action = fzf.actions.file_tabedit }) + end, "[G]oto in a [T]ab to [T]ype definition") + 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("gvr", function() + fzf.lsp_references({ jump1_action = fzf.actions.file_vsplit }) + end, "[G]oto in a [V]ertical split to [R]eferences") + nmap("gxr", function() + fzf.lsp_references({ jump1_action = fzf.actions.file_split }) + end, "[G]oto in a [X]horizontal split to [R]eferences") + nmap("gtr", function() + fzf.lsp_references({ jump1_action = fzf.actions.file_tabedit }) + end, "[G]oto in a [T]ab to [R]eferences") + nmap("ci", fzf.lsp_incoming_calls, "[C]ode [I]ncoming calls") + nmap("co", fzf.lsp_outgoing_calls, "[C]ode [O]utgoing calls") + nmap("gO", fzf.lsp_document_symbols, "d[O]ocument symbols") + nmap( + "ws", + fzf.lsp_live_workspace_symbols, + "[W]orkspace [S]ymbols" + ) + nmap( + "wd", + fzf.diagnostics_workspace, + "[W]orkspace [D]iagnostics" + ) + + 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.lsp.codelens.enable(true, { bufnr = bufnr }) + end + + if + client + and client:supports_method( + vim.lsp.protocol.Methods.textDocument_inlayHint, + event.buf + ) + then + nmap("th", function() + vim.lsp.inlay_hint.enable( + not vim.lsp.inlay_hint.is_enabled(event.buf) + ) + end, "[T]oggle Inlay [H]ints") + end + end, +}) + +require("conform").setup({ + formatters_by_ft = { + awk = { "gawk" }, + bash = { "shfmt" }, + cmake = { "cmake_format" }, + css = { "prettier", "stylelint" }, + groovy = { "npm-groovy-lint" }, + html = { "prettier" }, + javascript = { "prettier" }, + typescript = { "prettier" }, + jenkins = { "npm-groovy-lint" }, + json = { "jq", "jsonlint" }, + jsonc = { "prettier" }, + just = { "just" }, + markdown = { "mdformat" }, + nginx = { "nginxfmt" }, + lua = { "stylua" }, + python = { "ruff_format", "ruff_fix", "ruff_organize_imports" }, + rust = { "rustfmt" }, + sh = { "shfmt", "shellcheck", "shellharden" }, + yaml = { "yamllint" }, + zsh = { "shfmt", "shellcheck", "shellharden" }, + }, + default_format_opts = { + lsp_format = "fallback", + }, + formatters = { + shfmt = { + prepend_args = { "-i", "2" }, + }, + }, +}) +vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" + +vim.keymap.set("", "f", function() + require("conform").format({ async = true, lsp_fallback = true }) +end, { desc = "[F]ormat buffer" }) + +local lint = require("lint") +lint.linters_by_ft = { + css = { "stylelint" }, + dockerfile = { "hadolint" }, + groovy = { "npm-groovy-lint" }, + jenkins = { "npm-groovy-lint" }, + json = { "jsonlint" }, + markdown = { "markdownlint" }, + makefile = { "checkmake" }, + systemd = { "systemdlint" }, + yaml = { "yamllint", "yq" }, + ghaction = { "actionlint" }, + zsh = { "zsh" }, + ["*"] = { "codespell", "typos" }, +} +vim.api.nvim_create_autocmd({ "BufReadPost", "BufWritePost" }, { + group = vim.api.nvim_create_augroup("lint", { clear = true }), + callback = function() + if vim.opt_local.modifiable:get() then + lint.try_lint() + end + end, +}) + +require("tiny-inline-diagnostic").setup({ + options = { + show_source = { + if_many = true, + }, + set_arrow_to_diag_color = true, + multilines = { + enabled = true, + }, + show_all_diags_on_cursorline = true, + enable_on_select = true, + break_line = { + enabled = true, + }, + severity = { + vim.diagnostic.severity.ERROR, + vim.diagnostic.severity.WARN, + vim.diagnostic.severity.INFO, + vim.diagnostic.severity.HINT, + }, + }, +}) diff --git a/dot_config/nvim/lua/plugins/runner.lua b/dot_config/nvim/lua/plugins/runner.lua new file mode 100644 index 0000000..28e4e5f --- /dev/null +++ b/dot_config/nvim/lua/plugins/runner.lua @@ -0,0 +1,77 @@ +local overseer = require("overseer") +overseer.setup({}) +overseer.add_template_hook({ name = ".*" }, function(task_defn, util) + util.add_component(task_defn, { + "open_output", + on_start = "never", + on_complete = "failure", + direction = "vertical", + }) +end) + +vim.keymap.set("n", "to", function() + overseer.toggle() +end, { desc = "[T]oggle [O]verseer" }) +vim.keymap.set("n", "ob", function() + overseer.run_task({ name = "just build", disallow_prompt = true }) +end, { desc = "[O]verseer [B]uild" }) +vim.keymap.set("n", "oB", function() + overseer.run_task({ name = "just build" }) +end, { desc = "[O]verseer [B]uild" }) +vim.keymap.set("n", "ot", function() + overseer.run_task({ name = "just test", disallow_prompt = true }) +end, { desc = "[O]verseer [J]ust [T]est" }) +vim.keymap.set("n", "oT", function() + overseer.run_task({ name = "just test" }) +end, { desc = "[O]verseer [J]ust [T]est" }) +vim.keymap.set("n", "of", function() + overseer.run_task({ + name = "just test", + disallow_prompt = true, + params = { target = vim.fn.expand("%") }, + }) +end, { desc = "[O]verseer test [F]ile" }) +vim.keymap.set("n", "oF", function() + overseer.run_task({ + name = "just test", + params = { target = vim.fn.expand("%") }, + }) +end, { desc = "[O]verseer test [F]ile" }) +vim.keymap.set("n", "od", function() + overseer.run_task({ + name = "just debug=true test", + disallow_prompt = true, + params = { target = vim.fn.expand("%") }, + }) +end, { desc = "[O]verseer [d]ebug test file" }) +vim.keymap.set("n", "oD", function() + overseer.run_task({ + name = "just debug=true test", + params = { target = vim.fn.expand("%") }, + }) +end, { desc = "[O]verseer [D]ebug test file" }) +vim.keymap.set("n", "oa", function() + overseer.run_task({ + name = "just test_autofix", + disallow_prompt = true, + params = { target = vim.fn.expand("%") }, + }) +end, { desc = "[O]verseer [A]utofix" }) +vim.keymap.set("n", "or", function() + overseer.run_task() +end, { desc = "[O]verseer [R]un" }) +vim.keymap.set("n", "os", function() + vim.cmd("OverseerShell") +end, { desc = "[O]verseer [S]hell" }) +vim.keymap.set("n", "ol", function() + local tasks = overseer.list_tasks({ + sort = function(a, b) + return a.id > b.id + end, + }) + if vim.tbl_isempty(tasks) then + vim.notify("No tasks found", vim.log.levels.WARN) + else + overseer.run_action(tasks[1], "restart") + end +end, { desc = "[O]verseer run [L]ast" }) diff --git a/dot_config/nvim/lua/plugins/search.lua b/dot_config/nvim/lua/plugins/search.lua new file mode 100644 index 0000000..a36cddc --- /dev/null +++ b/dot_config/nvim/lua/plugins/search.lua @@ -0,0 +1,60 @@ +local fzflua = require("fzf-lua") +fzflua.setup({ + keymap = { + builtin = { + true, + [""] = "toggle-preview", + }, + }, + grep = { + hidden = true, + RIPGREP_CONFIG_PATH = "~/.config/ripgrep/ripgreprc", + }, + lsp = { + includeDeclaration = false, + }, + actions = { + files = { + true, + ["ctrl-x"] = fzflua.actions.file_split, + }, + }, +}) +fzflua.register_ui_select() + +vim.keymap.set("n", "b", function() + fzflua.buffers() +end, { desc = "fzf-lua [B]uffers" }) +vim.keymap.set("n", "/", function() + fzflua.live_grep() +end, { desc = "fzf-lua live grep" }) +vim.keymap.set("n", "f", function() + fzflua.files() +end, { desc = "fzf-lua [F]iles" }) +vim.keymap.set("n", "", function() + fzflua.global() +end, { desc = "fzf-lua global picker" }) +vim.keymap.set("n", "d", function() + fzflua.diagnostics() +end, { desc = "fzf-lua [D]iagnostics" }) +vim.keymap.set("n", "r", function() + fzflua.resume() +end, { desc = "fzf-lua [R]esume" }) +vim.keymap.set("n", "gc", function() + fzflua.git_bcommits() +end, { desc = "[G]it buffer [C]commits" }) +vim.keymap.set("v", "gc", function() + fzflua.git_bcommits_range() +end, { desc = "[G]it [C]commits for selected range" }) +vim.keymap.set("n", "gC", function() + fzflua.git_commits() +end, { desc = "[G]it (all) [C]commits" }) +vim.keymap.set("n", "gb", function() + fzflua.git_branches() +end, { desc = "[G]it [B]ranches" }) +vim.keymap.set("n", "gs", function() + fzflua.git_status() +end, { desc = "[G]it [S]tatus" }) +vim.keymap.set("n", "gS", function() + fzflua.git_stash() +end, { desc = "[G]it [S]tash" }) diff --git a/dot_config/nvim/lua/plugins/session.lua b/dot_config/nvim/lua/plugins/session.lua new file mode 100644 index 0000000..a094727 --- /dev/null +++ b/dot_config/nvim/lua/plugins/session.lua @@ -0,0 +1,77 @@ +local function get_cwd_as_name() + local dir = vim.fn.getcwd(0) + return dir:gsub("[^A-Za-z0-9]", "_") +end +local overseer = require("overseer") + +require("auto-session").setup({ + use_git_branch = true, + pre_save_cmds = { + function() + overseer.save_task_bundle( + get_cwd_as_name(), + nil, + { on_conflict = "overwrite" } + ) + end, + }, + pre_restore_cmds = { + function() + for _, task in ipairs(overseer.list_tasks({})) do + task:dispose(true) + end + end, + }, + post_restore_cmds = { + function() + overseer.load_task_bundle( + get_cwd_as_name(), + { ignore_missing = true, autostart = false } + ) + end, + }, + save_extra_data = function(_) + local ok, breakpoints = pcall(require, "dap.breakpoints") + if not ok or not breakpoints then + return + end + + local bps = {} + local breakpoints_by_buf = breakpoints.get() + for buf, buf_bps in pairs(breakpoints_by_buf) do + bps[vim.api.nvim_buf_get_name(buf)] = buf_bps + end + if vim.tbl_isempty(bps) then + return + end + local extra_data = { + breakpoints = bps, + } + return vim.fn.json_encode(extra_data) + end, + + restore_extra_data = function(_, extra_data) + local json = vim.fn.json_decode(extra_data) + + if json.breakpoints then + local ok, breakpoints = pcall(require, "dap.breakpoints") + + if not ok or not breakpoints then + return + end + vim.notify("restoring breakpoints") + for buf_name, buf_bps in pairs(json.breakpoints) do + for _, bp in pairs(buf_bps) do + local line = bp.line + local opts = { + condition = bp.condition, + log_message = bp.logMessage, + hit_condition = bp.hitCondition, + } + breakpoints.set(opts, vim.fn.bufnr(buf_name), line) + end + end + end + end, + suppressed_dirs = { "~/", "/" }, +}) diff --git a/dot_config/nvim/lua/plugins/treesitter.lua b/dot_config/nvim/lua/plugins/treesitter.lua new file mode 100644 index 0000000..a4a488c --- /dev/null +++ b/dot_config/nvim/lua/plugins/treesitter.lua @@ -0,0 +1,83 @@ +require("treewalker").setup({}) + +vim.keymap.set({ "n", "v" }, "", "Treewalker Up", { silent = true, desc = "Moves up to the previous neighbor node" }) +vim.keymap.set({ "n", "v" }, "", "Treewalker Down", { silent = true, desc = "Moves up to the next neighbor node" }) +vim.keymap.set({ "n", "v" }, "", "Treewalker Left", { silent = true, desc = "Moves to the first ancestor node that's on a different line from the current node" }) +vim.keymap.set({ "n", "v" }, "", "Treewalker Right", { silent = true, desc = "Moves to the next node down that's indented further than the current node" }) +vim.keymap.set("n", "", "Treewalker SwapUp", { silent = true, desc = "Swaps the highest node on the line upwards in the document" }) +vim.keymap.set("n", "", "Treewalker SwapDown", { silent = true, desc = "Swaps the biggest node on the line downward in the document" }) +vim.keymap.set("n", "", "Treewalker SwapLeft", { silent = true, desc = "Swap the node under the cursor with its previous neighbor" }) +vim.keymap.set("n", "", "Treewalker SwapRight", { silent = true, desc = "Swap the node under the cursor with its next neighbor" }) + +require("nvim-treesitter").install({ + "awk", + "bash", + "c", + "cmake", + "comment", + "cpp", + "css", + "csv", + "diff", + "dockerfile", + "doxygen", + "editorconfig", + "fortran", + "git_config", + "git_rebase", + "gitattributes", + "gitcommit", + "gitignore", + "groovy", + "gpg", + "hlsplaylist", + "html", + "http", + "ini", + "javascript", + "jq", + "jsdoc", + "json", + "just", + "llvm", + "lua", + "luadoc", + "luap", + "make", + "markdown", + "markdown_inline", + "query", + "passwd", + "printf", + "python", + "regex", + "readline", + "requirements", + "rust", + "sql", + "ssh_config", + "strace", + "tablegen", + "todotxt", + "toml", + "typescript", + "vim", + "vimdoc", + "xcompose", + "xml", + "xresources", + "yaml", +}) + +require("nvim-dap-repl-highlights").setup({}) +require("treesitter-context").setup({}) + +require("ts_context_commentstring").setup({ + enable_autocmd = false, +}) +local get_option = vim.filetype.get_option +vim.filetype.get_option = function(filetype, option) + return option == "commentstring" + and require("ts_context_commentstring.internal").calculate_commentstring() + or get_option(filetype, option) +end diff --git a/dot_config/nvim/lua/plugins/ui.lua b/dot_config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..50a2114 --- /dev/null +++ b/dot_config/nvim/lua/plugins/ui.lua @@ -0,0 +1,58 @@ +-- blink.indent (gruvbox setup is in init.lua) +require("blink.indent").setup({ + scope = { + highlights = { + "BlinkIndentOrange", + "BlinkIndentViolet", + "BlinkIndentBlue", + "BlinkIndentRed", + "BlinkIndentCyan", + "BlinkIndentYellow", + "BlinkIndentGreen", + }, + underline = { + enabled = true, + highlights = { + "BlinkIndentOrangeUnderline", + "BlinkIndentVioletUnderline", + "BlinkIndentBlueUnderline", + "BlinkIndentRedUnderline", + "BlinkIndentCyanUnderline", + "BlinkIndentYellowUnderline", + "BlinkIndentGreenUnderline", + }, + }, + }, +}) + +require("lualine").setup({ + options = { + icons_enabled = false, + theme = "gruvbox_dark", + component_separators = "", + section_separators = "|", + disabled_filetypes = { + winbar = { + "dap-view", + "dap-repl", + "dap-view-term", + }, + }, + }, + sections = { + lualine_a = { "filetype", { "filename", path = 1 } }, + lualine_b = { "%l/%L:%c:%o" }, + lualine_c = { "diff" }, + lualine_x = { "searchcount", "selectioncount" }, + lualine_y = { "overseer", "copilot" }, + lualine_z = { "diagnostics" }, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = { "filename" }, + lualine_x = {}, + lualine_y = {}, + lualine_z = {}, + }, +}) -- cgit v1.2.3-70-g09d2