-- ~/.config/nvim/init.lua -- -- Simple Nvim v0.12+ config -- -- Depends (Alpine Linux): -- curl fzf git ripgrep tree-sitter-cli -- -- Set some environment variables: -- export ESCDELAY=0 -- export VISUAL=nvim -- export EDITOR=nvim -- -- Update plugins: -- :lua vim.pack.update() -- -- Install and update tools: -- :MasonToolsUpdate -- Byte-compile and cache Lua files (improves startup time.) vim.loader.enable() -- ========================================== -- == Coloring -- ========================================== -- Remove background color if you want. --vim.api.nvim_set_hl(0, "Normal", { bg = "NONE" }) --vim.api.nvim_set_hl(0, "NormalNC", { bg = "NONE" }) -- Create trailing whitespace highlight. vim.api.nvim_set_hl(0, "TrailingWhitespace", { bg = "DarkRed" }) -- Highligh the above highlight to all normal buffers (excludes terminal -- buffers, quickfix list buffers, etc.) vim.api.nvim_create_autocmd({ "BufWinEnter", "WinNew" }, { callback = function() vim.schedule(function() if vim.w.trailing_ws_match then return end if vim.bo.buftype == "" then vim.fn.matchadd("TrailingWhitespace", [[\s\+$]], 0) vim.w.trailing_ws_match = true end end) end, }) -- Override the default Todo highlight. vim.api.nvim_set_hl(0, "Todo", { fg = "LightRed", bold = true }) -- Highlight TODOs, FIXMEs, etc. We could install the -- tree-sitter-comment parser for more precise handling (not highlight -- TODOs outside comments for one), but it is noticeably slow. vim.api.nvim_create_autocmd({ "BufWinEnter", "WinNew" }, { callback = function() vim.schedule(function() if vim.w.todo_match then return end if vim.bo.buftype == "" then vim.fn.matchadd("Todo", [[\<\(TODO\|FIXME\|HACK\|NOTE\|XXX\)\>:\=]], 0) vim.w.todo_match = true end end) end, }) -- ========================================== -- == Options -- ========================================== -- Mapleaders. vim.g.mapleader = " " vim.g.maplocalleader = "\\" -- Don't show banner in netrw file explorer, and preview files right -- side vertically. vim.g.netrw_banner = 0 vim.g.netrw_preview = 1 vim.g.netrw_alto = 0 -- Use C instead of C++ syntax for *.h files. vim.g.c_syntax_for_h = true -- Enable project-local configuration. Execute any .nvim.lua, .nvimrc, -- or .exrc file found in the current-directory and all parent -- directories if the files are in the trust list. vim.o.exrc = true -- Enable line numbers and signcolumn, and limit signcolumn to one sign. vim.o.number = true vim.o.signcolumn = "yes:1" -- When splitting windows, put the new ones to the right and below. vim.o.splitright = true vim.o.splitbelow = true -- Enable autocompletion and scan current buffer, buffer from other -- windows, and loaded buffers for content (but limit latter to 20 -- matches). Also enable fuzzycompletion and the height of the limit -- completion menu popup. vim.o.autocomplete = true vim.o.complete = ".,w,b^20" vim.o.completeopt = "fuzzy,menuone,noselect" vim.o.pumheight = 10 vim.o.wildoptions = "fuzzy,tagfile" -- Always open buffers with all folds opened. vim.o.foldlevelstart = 99 -- Use undofiles, disable swapfiles, and autosave files when changing -- buffers etc. vim.o.undofile = true vim.o.swapfile = false vim.o.autowrite = true -- Use smarter indentation logic, round indent to multiple of -- shiftwidth, and set shiftwidth to size of tabs. See `:h indent.txt` -- for more info and indent method priority. vim.o.smartindent = true vim.o.shiftround = true vim.o.shiftwidth = 0 -- Always have 5 extra lines at the top and bottom and sides. vim.o.scrolloff = 5 vim.o.sidescrolloff = 5 -- Disable key code sequence completion timeout. vim.o.timeout = false vim.o.ttimeoutlen = 0 -- Better searching. vim.o.ignorecase = true vim.o.smartcase = true -- Use smartcase for a number of searching operations. vim.o.guicursor = "" vim.o.mousemodel = "extend" -- Abbreviate some messages. vim.o.shortmess = "FIOTlot" -- Enable virtual text diagnostics, disable underlines, sort diagnostics -- based on severity, and highlight linenumbers to match severity. vim.diagnostic.config({ virtual_text = true, underline = false, severity_sort = true, float = { source = true }, }) -- ========================================== -- == Custom commands -- ========================================== -- Fuzzyfinder. -- Open a file fuzzy finder in a terminal split window, using fzf. vim.api.nvim_create_user_command("FzfFind", function() local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_open_win(buf, true, { split = "below" }) vim.fn.jobstart({ "fzf", "--reverse" }, { on_exit = function() local fname = vim.api.nvim_buf_get_lines(buf, 0, 1, true)[1] vim.api.nvim_buf_delete(buf, { force = true }) if fname ~= "" then vim.cmd.edit(vim.fn.fnameescape(fname)) end end, term = true, }) vim.cmd.startinsert() end, {}) -- Livegrepper. -- Open a live grepper in a terminal split window, using fzf and -- ripgrep. vim.api.nvim_create_user_command("FzfGrep", function() local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_open_win(buf, true, { split = "below" }) local rg = "rg -Suug !.git --column --color=always --" vim.fn.jobstart({ "fzf", "--ansi", "--disabled", "--reverse", "--bind=change:reload:" .. rg .. " {q} || true", }, { env = { FZF_DEFAULT_COMMAND = rg .. " ''" }, on_exit = function() local fname, lnum, col = vim.api .nvim_buf_get_lines(buf, 0, 1, true)[1] :match("^(.+):(%d+):(%d+):.*$") vim.api.nvim_buf_delete(buf, { force = true }) if fname then vim.cmd.edit(vim.fn.fnameescape(fname)) vim.api.nvim_win_set_cursor(0, { tonumber(lnum), tonumber(col) - 1, }) end end, term = true, }) vim.cmd.startinsert() end, {}) -- ========================================== -- == Mappings -- ========================================== -- Unmap space. vim.keymap.set({ "n", "x" }, "", "") -- Toggle 'list' option. vim.keymap.set("n", "l", "set list!") -- Change current tab's working directory to directory of current file. vim.keymap.set("n", "g~", "tcd %:h") -- Open a netrw file explorer window. vim.keymap.set("n", "-", "Explore") -- Open a fuzzy file picker window and livegrep window. vim.keymap.set("n", "e", "FzfFind") vim.keymap.set("n", "/", "FzfGrep") vim.keymap.set("n", "f", ":find ") vim.keymap.set("n", "f", ":grep ") -- Indent and de-indent visually selected text. vim.keymap.set("x", "<", "", ">gv") -- Center screen on n/N and C-o/C-i. vim.keymap.set("n", "n", "nzzzv") vim.keymap.set("n", "N", "Nzzzv") vim.keymap.set("n", "", "zz") vim.keymap.set("n", "", "zz") -- Add current position to jumplist before gq and =. vim.keymap.set({ "n", "x" }, "gq", "m'gq") vim.keymap.set({ "n", "x" }, "=", "m'=") -- Navigate quickfix-list with C-j and C-k. -- Also see `:h make_makeprg`. vim.keymap.set("n", "", "cnextzz") vim.keymap.set("n", "", "cprevzz") -- Navigate location-list with M-j and M-k. vim.keymap.set("n", "", "lnextzz") vim.keymap.set("n", "", "lprevzz") -- Resize current window to max size with z and . To -- resize all windows to equal size, do =. vim.keymap.set("n", "z", "vertical resize | resize") vim.keymap.set("n", "", "vertical resize | resize") -- Open a terminal split window with t. When in a terminal, go -- to normal-mode with . vim.keymap.set("n", "t", "horizontal terminal") vim.keymap.set("t", "[", "") -- Toggle the undotree window. vim.keymap.set("n", "u", "Undotree") -- Toggle showing diagnostics in all buffers. vim.keymap.set("n", "d", function() vim.diagnostic.enable(not vim.diagnostic.is_enabled()) end) -- Add all diagnostics to the quickfix-list. vim.keymap.set("n", "grq", function() vim.diagnostic.setqflist({ open = false }) pcall(vim.cmd.cc) end) -- Add all diagnostics to the location-list. vim.keymap.set("n", "grl", function() vim.diagnostic.setloclist({ open = false }) pcall(vim.cmd.ll) end) -- ========================================== -- == Autocommands -- ========================================== -- Always display relative paths. -- https://github.com/vim/vim/issues/549 vim.api.nvim_create_autocmd("BufReadPost", { callback = function() vim.cmd.lcd(".") end, }) -- Clear jumplist on startup. vim.api.nvim_create_autocmd({ "VimEnter" }, { callback = function() vim.cmd.clearjumps() end, }) -- Restore last cursor position. vim.api.nvim_create_autocmd("BufWinEnter", { callback = function(ev) local lastpos = vim.api.nvim_buf_get_mark(ev.buf, '"') if pcall(vim.api.nvim_win_set_cursor, 0, lastpos) then vim.cmd.normal({ "zz", bang = true }) end end, }) -- Resize splits if window got resized. vim.api.nvim_create_autocmd("VimResized", { callback = function() local ctab = vim.fn.tabpagenr() vim.cmd.tabdo("wincmd =") vim.cmd.tabnext(ctab) end, }) -- Create parent directories when saving a file. vim.api.nvim_create_autocmd("BufWritePre", { callback = function(ev) if not ev.file:find("^%w+://") then vim.fn.mkdir(vim.fs.dirname(ev.file), "p") end end, }) -- Disable line numbers and highlight the textline of the cursor in -- quickfix-list and location-list windows. vim.api.nvim_create_autocmd("FileType", { pattern = "qf", callback = function() vim.wo[0][0].number = false vim.wo[0][0].cursorline = true end, }) -- Disable diagnostics when entering insert mode. vim.api.nvim_create_autocmd("ModeChanged", { pattern = "*:i", callback = function(ev) vim.diagnostic.enable(false, { bufnr = ev.buf }) end, }) -- Enable diagnostics when leaving insert mode. vim.api.nvim_create_autocmd("ModeChanged", { pattern = "i:*", callback = function(ev) vim.diagnostic.enable(true, { bufnr = ev.buf }) end, }) -- ========================================== -- == LSP -- ========================================== -- See `:h lsp-defaults` for default LSP keymaps and behavior. -- Enable some language servers. vim.lsp.enable({ "clangd", "gopls", "lua_ls", "ruff", "ty", }) -- When a buffer attaches a language server, do this. vim.api.nvim_create_autocmd("LspAttach", { callback = function(ev) local client = vim.lsp.get_client_by_id(ev.data.client_id) if client then -- Disable some annoying LSP features. vim.lsp.semantic_tokens.enable(false) vim.lsp.inlay_hint.enable(false) vim.lsp.document_color.enable(false) if client:supports_method("textDocument/completion") then -- Enable omnicompletion if language server has completion -- providing capabilities. Omnicompletion will by default be set -- to use it if it has. vim.bo[ev.buf].complete = "o" -- TODO: When https://github.com/neovim/neovim/pull/35346, you -- can do the following instead: --if not vim.bo[ev.buf].complete:find("o", 1, true) then -- vim.bo[ev.buf].complete = "o," .. vim.bo[ev.buf].complete --end -- Likewise enable some better LSP completion capabilities. vim.lsp.completion.enable(true, ev.data.client_id, ev.buf, { -- Optional formating of LSP completion items. convert = function(item) -- Cap field labels to 15 characters, and don't show content -- in () and {}. local label = item.label:gsub("%b()", ""):gsub("%b{}", "") local detail = item.detail or "" return { abbr = #label > 15 and label:sub(1, 14) .. "…" or label, menu = #detail > 15 and detail:sub(1, 14) .. "…" or detail, } end, }) end end end, }) -- ========================================== -- == Plugins -- ========================================== -- Install some plugins. See `:h vim.pack`. vim.pack.add({ "https://github.com/neovim/nvim-lspconfig", "https://github.com/nvim-treesitter/nvim-treesitter", "https://github.com/mason-org/mason.nvim", "https://github.com/WhoIsSethDaniel/mason-tool-installer", "https://github.com/stevearc/conform.nvim", "https://github.com/mfussenegger/nvim-lint", }, { confirm = false }) -- Builtin plugins -- Some builtin plugins that are shipped with nvim. -- ========================================== vim.cmd.packadd({ "cfilter", bang = true }) vim.cmd.packadd({ "nvim.undotree", bang = true }) vim.cmd.packadd({ "nvim.difftool", bang = true }) -- Plugin: nvim-treesitter -- Install up-to-date tree sitter parsers. See its documentation for -- available parsers. -- ========================================== require("nvim-treesitter").install({ -- Bundled parsers. "c", "lua", "markdown", "markdown_inline", "query", "vim", "vimdoc", -- Extra parsers. "bash", "diff", "git_rebase", "gitcommit", "go", "json", "python", "vhdl", }) -- Run `:TSUpdate` when the nvim-treesitter plugin is install or -- updated. vim.api.nvim_create_autocmd({ "PackChanged" }, { callback = function(ev) if ev.data.spec.name == "nvim-treesitter" and (ev.data.kind == "install" or ev.data.kind == "update") then vim.schedule(function() vim.cmd.TSUpdate() end) end end, }) -- Enable treesitter on supported filetypes. vim.api.nvim_create_autocmd("FileType", { callback = function(ev) if pcall(vim.treesitter.start) then -- If treesitter was enabled and folding queries are installed, -- use the queries for folding. if vim.treesitter.query.get(ev.match, "folds") then vim.wo[0][0].foldexpr = "v:lua.vim.treesitter.foldexpr()" vim.wo[0][0].foldmethod = "expr" end end end, }) -- Plugin: mason.nvim -- A package manager for editor related applications like LSPs, -- formatters and linters. -- ========================================== require("mason").setup() -- Plugin: mason-tool-installer -- A wrapper around mason to autoinstall packages by listing them in a -- table. -- ========================================== require("mason-tool-installer").setup({ run_on_start = false, ensure_installed = { "gofumpt", "gopls", "jq", "lua-language-server", "ruff", "shellcheck", "shfmt", "stylua", "ty", "vsg", }, }) -- Plugin: conform.nvim -- Enable specific formatters per filetype. See its documentation for -- available formatters. -- ========================================== require("conform").setup({ -- Map formatters to filetypes. formatters_by_ft = { go = { "gofumpt" }, json = { "jq" }, lua = { "stylua" }, markdown = { "injected" }, python = { "ruff_fix", "ruff_format", "ruff_organize_imports" }, sh = { "shfmt" }, vhdl = { "vsg" }, ["*"] = { "injected" }, ["_"] = { lsp_format = "prefer" }, }, }) -- Set formatexpr to call conforms formatexpr function. formatexpr is -- used by gq for formatting. To format something, do gq. vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" -- Plugin: nvim-lint -- A plugin to enable specific linters per filetype. See its -- documentation for available linters. -- ========================================== local lint = require("lint") -- Map linters to filetypes. lint.linters_by_ft = { json = { "jq" }, sh = { "shellcheck" }, vhdl = { "ghdl", "vsg" }, } -- Redefine args given to shellcheck. This is to make it read stdin, -- making it faster. lint.linters.shellcheck.args = { "-f", "json1", "-" } -- Append `-Wall` to ghdl. table.insert(lint.linters.ghdl.args, "-Wall") -- Enable linting on these events. vim.api.nvim_create_autocmd({ "FileType", "BufWritePost", "TextChanged" }, { callback = function() lint.try_lint(nil, { ignore_errors = true }) end, })