dot

NixOS dotfiles
git clone https://git.echoz.io/dot.git
Log | Files | Refs

commit 457ba5e3fcb0ac0ae8a7b7b4cd33ff20917ebc97
parent c62998822919401d876db53c956ef5233d4a852d
Author: Chris <chris@echoz.io>
Date:   Fri, 28 Nov 2025 02:57:46 +0100

refactor(neovim): split up config, generate lsp config

Diffstat:
Amodules/neovim/cheat-sheet.txt | 10++++++++++
Mmodules/neovim/default.nix | 134++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Amodules/neovim/ftplugin/mail.lua | 1+
Amodules/neovim/lua/cheat-sheet.lua | 32++++++++++++++++++++++++++++++++
Amodules/neovim/lua/code.lua | 17+++++++++++++++++
Amodules/neovim/lua/general.lua | 19+++++++++++++++++++
Amodules/neovim/lua/listchars.lua | 31+++++++++++++++++++++++++++++++
Amodules/neovim/lua/netrw.lua | 13+++++++++++++
Amodules/neovim/lua/return-to-last-line.lua | 15+++++++++++++++
Amodules/neovim/lua/style.lua | 32++++++++++++++++++++++++++++++++
Amodules/neovim/lua/system-clipboard.lua | 12++++++++++++
Amodules/neovim/lua/undodir.lua | 8++++++++
Amodules/neovim/lua/wildmenu.lua | 13+++++++++++++
Amodules/neovim/lua/winbar.lua | 42++++++++++++++++++++++++++++++++++++++++++
Dmodules/neovim/neovim.lua | 168-------------------------------------------------------------------------------
15 files changed, 372 insertions(+), 175 deletions(-)

diff --git a/modules/neovim/cheat-sheet.txt b/modules/neovim/cheat-sheet.txt @@ -0,0 +1,10 @@ +i C-x C-o Omni completion +n K Hover +n,v C-w d Hover diagnostics +n,v gra Code action +n gri Go to implementation +n grt Go to type definition +n grr References +n grn Rename +n gO List symbols +vb I Insert before every line in block diff --git a/modules/neovim/default.nix b/modules/neovim/default.nix @@ -3,16 +3,142 @@ pkgs, ... }: +let + runtime = luaRuntime // lspRuntime // ftpluginRuntime; + + initLua = generateInitLua runtime { + cheat-sheet.file = ./cheat-sheet.txt; + init = { }; + }; + + luaRuntime = importLuaDir "lua" ./lua; + ftpluginRuntime = importLuaDir "ftplugin" ./ftplugin; + + lspRuntime = generateLspRuntime { + nixd = { + cmd = [ (lib.getExe pkgs.nixd) ]; + filetypes = [ "nix" ]; + root_markers = [ + "flake.nix" + ".git" + ]; + }; + + gopls = { + cmd = [ (lib.getExe pkgs.gopls) ]; + filetypes = [ + "go" + "gomod" + "gowork" + "gotmpl" + ]; + root_markers = [ + "go.work" + "go.mod" + ".git" + ]; + }; + + typescript-language-server = { + cmd = [ + (lib.getExe pkgs.typescript-language-server) + "--stdio" + ]; + filetypes = [ + "javascript" + "typescript" + ]; + root_markers = [ + [ + "jsconfig.json" + "tsconfig.json" + ] + "package.json" + ".git" + ]; + init_options.hostInfo = "neovim"; + }; + + ruff = { + cmd = [ + (lib.getExe pkgs.ruff) + "server" + ]; + filetypes = [ "python" ]; + root_markers = [ + "pyproject.toml" + "ruff.toml" + ".ruff.toml" + ".git" + ]; + }; + + lua-language-server = { + cmd = [ (lib.getExe pkgs.lua-language-server) ]; + filetypes = [ "lua" ]; + settings.Lua."diagnostics.globals" = [ "vim" ]; + }; + }; + + generateLspRuntime = + lsps: + (lib.mapAttrs' (name: config: { + name = "lsp/${name}.lua"; + value.text = "return ${lib.generators.toLua { } config}"; + }) lsps) + // { + "lua/lsp.lua".text = '' + local M = {} + function M.setup() + vim.lsp.enable(${lib.generators.toLua { indent=" "; } (builtins.attrNames lsps)}) + end + return M + ''; + }; + + importLuaDir = + prefix: + path: + lib.pipe (builtins.readDir path) [ + (lib.mapAttrs' ( + name: type: { + name = "${prefix}/${name}"; + value = + if type == "regular" && builtins.match "^.*.lua$" name != null then + { source = path + "/${name}"; } + else + null; + } + )) + (lib.filterAttrs (_: value: value != null)) + ]; + + generateInitLua = + runtime: args: + lib.pipe runtime [ + builtins.attrNames + (builtins.map (builtins.match "^lua/(.*).lua$")) + (builtins.filter builtins.isList) + (builtins.map builtins.head) + (builtins.map (name: "require('${name}').setup(${lib.generators.toLua { } (args.${name} or { })})")) + (builtins.concatStringsSep "\n") + ]; +in { programs.neovim = { enable = true; + + inherit runtime; + configure.customLuaRC = initLua; + defaultEditor = true; viAlias = true; vimAlias = true; - configure.customLuaRC = builtins.readFile ./neovim.lua; + withRuby = lib.mkDefault false; withPython3 = lib.mkDefault false; withNodeJs = lib.mkDefault false; + package = pkgs.neovim-unwrapped.overrideAttrs { version = "v0.12.0-dev"; src = pkgs.fetchFromGitHub { @@ -23,10 +149,4 @@ }; }; }; - - environment.systemPackages = with pkgs; [ - nixd - gopls - typescript-language-server - ]; } diff --git a/modules/neovim/ftplugin/mail.lua b/modules/neovim/ftplugin/mail.lua @@ -0,0 +1 @@ +vim.opt.textwidth = 72 diff --git a/modules/neovim/lua/cheat-sheet.lua b/modules/neovim/lua/cheat-sheet.lua @@ -0,0 +1,32 @@ +local M = {} + +function M.setup(opts) + M.file = opts.file or "/dev/null" + M.lines = {} + + for line in io.lines(M.file) do + table.insert(M.lines, line) + end + + vim.keymap.set({'n','v'}, '<C-/>', function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_keymap(buf, 'n', 'q', ':q<CR>', { + silent = true, + nowait = true, + }) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, M.lines) + local win = vim.api.nvim_open_win(buf, true, { + title = 'Cheat sheet', + relative = 'editor', + row = math.floor(vim.o.lines * 0.10), + col = math.floor(vim.o.columns * 0.10), + height = math.floor(vim.o.lines * 0.80), + width = math.floor(vim.o.columns * 0.80), + style = 'minimal', + title_pos = 'center', + focusable = true, + }) + end) +end + +return M diff --git a/modules/neovim/lua/code.lua b/modules/neovim/lua/code.lua @@ -0,0 +1,17 @@ +local M = {} + +function M.setup() + vim.opt.signcolumn = "yes" + vim.opt.completeopt = { "fuzzy", "menu", "menuone", "noinsert", "popup" } + + vim.diagnostic.config({ virtual_text = true }) + + vim.lsp.config('*', { + root_markers = { '.git' }, + on_attach = function(client, bufnr) + vim.lsp.completion.enable(true, client.id, bufnr, {}) + end, + }) +end + +return M diff --git a/modules/neovim/lua/general.lua b/modules/neovim/lua/general.lua @@ -0,0 +1,19 @@ +local M = {} + +function M.setup() + vim.opt.tabstop = 2 + vim.opt.shiftwidth = 2 + vim.opt.expandtab = true + + vim.opt.number = true + vim.opt.relativenumber = true + + vim.opt.scrolloff = 5 + + vim.opt.cursorline = true + vim.opt.textwidth = 100 + vim.opt.colorcolumn = '+1' + vim.opt.formatoptions = 'cqj' +end + +return M diff --git a/modules/neovim/lua/listchars.lua b/modules/neovim/lua/listchars.lua @@ -0,0 +1,31 @@ +local M = {} + +function M.setup() + vim.opt.list = true + vim.opt.listchars = { + trail = '~', + tab = '| ', + leadmultispace = ':' .. string.rep(' ', vim.opt.shiftwidth:get() - 1), + } + + local function update() + local listchars = vim.opt_local.listchars:get() + listchars.leadmultispace = ':' .. string.rep(' ', vim.opt_local.shiftwidth:get() - 1) + vim.opt_local.listchars = listchars + end + + local group = vim.api.nvim_create_augroup('ListcharsLeadmultispaceWidth', { clear = true }), + + vim.api.nvim_create_autocmd('OptionSet', { + group = group, + pattern = 'shiftwidth', + callback = update, + }) + + vim.api.nvim_create_autocmd({ 'FileType', 'BufWinEnter' }, { + group = group, + callback = update, + }) +end + +return M diff --git a/modules/neovim/lua/netrw.lua b/modules/neovim/lua/netrw.lua @@ -0,0 +1,13 @@ +local M = {} + +function M.setup() + vim.g.netrw_banner = 0 + + -- absolute 30 cols/rows + vim.g.netrw_winsize = -30 + + -- tree style + vim.g.netrw_liststyle = 3 +end + +return M diff --git a/modules/neovim/lua/return-to-last-line.lua b/modules/neovim/lua/return-to-last-line.lua @@ -0,0 +1,15 @@ +local M = {} + +function M.setup() + vim.api.nvim_create_autocmd('BufReadPost', { + group = vim.api.nvim_create_augroup('ReturnToLastLine', { clear = true }), + callback = function() + local last = vim.fn.line([['"]]) + if last > 1 and last < vim.fn.line("$") then + vim.cmd([[normal! g'"]]) + end + end + }) +end + +return M diff --git a/modules/neovim/lua/style.lua b/modules/neovim/lua/style.lua @@ -0,0 +1,32 @@ +local M = {} + +function M.setup() + vim.api.nvim_set_hl(0, 'Normal', { bg='none' }) + vim.api.nvim_set_hl(0, 'StatusLine', { bg='none' }) + vim.api.nvim_set_hl(0, 'StatusLineNC', { bg='none', fg='gray' }) + vim.api.nvim_set_hl(0, 'WinBar', { bg='none' }) + vim.api.nvim_set_hl(0, 'WinBarNC', { bg='none', fg='gray' }) + vim.api.nvim_set_hl(0, 'NormalFloat', { bg='none' }) + vim.api.nvim_set_hl(0, 'FloatBorder', { bg='none' }) + vim.api.nvim_set_hl(0, 'Pmenu', { bg='none' }) + vim.api.nvim_set_hl(0, 'PmenuBorder', { bg='none' }) + + vim.opt.winborder = "rounded"; + vim.opt.pumborder = "rounded"; + + vim.opt.cmdheight = 0 + + -- Raise cmdheight to make recording status visible + vim.api.nvim_create_autocmd("RecordingEnter", { + callback = function() + vim.opt.cmdheight = 1 + end, + }) + vim.api.nvim_create_autocmd("RecordingLeave", { + callback = function() + vim.opt.cmdheight = 0 + end, + }) +end + +return M diff --git a/modules/neovim/lua/system-clipboard.lua b/modules/neovim/lua/system-clipboard.lua @@ -0,0 +1,12 @@ +local M = {} + +function M.setup() + vim.keymap.set({'n','v'}, '<C-c>', '"+y') + vim.keymap.set({'n','v'}, '<C-v>', '"+p') + vim.keymap.set({'n','v'}, '<C-x>', '"+d') + + -- Use C-q for block visual mode + vim.keymap.set({'n','v'}, '<C-q>', '<C-v>', { noremap = true}) +end + +return M diff --git a/modules/neovim/lua/undodir.lua b/modules/neovim/lua/undodir.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup() + vim.opt.undofile = true + vim.opt.undodir = vim.fn.stdpath('data') .. '/undodir' +end + +return M diff --git a/modules/neovim/lua/wildmenu.lua b/modules/neovim/lua/wildmenu.lua @@ -0,0 +1,13 @@ +local M = {} + +function M.setup() + vim.opt.path = vim.o.path .. '**' + vim.opt.wildmenu = true + vim.opt.wildignore = { + "**/.direnv/**", + "**/node_modules/**", + "**/vendor/**", + }; +end + +return M diff --git a/modules/neovim/lua/winbar.lua b/modules/neovim/lua/winbar.lua @@ -0,0 +1,42 @@ +local M = {} + +function M.setup() + vim.api.nvim_create_autocmd({'BufEnter', 'BufAdd', 'BufDelete'}, { + group = vim.api.nvim_create_augroup("WinBarBuffers", { clear = true }), + pattern = "*", + callback = function() + local buffers = {} + local current = vim.api.nvim_win_get_buf(0) + + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if vim.bo[bufnr].buflisted then + local name = vim.fn.fnamemodify(vim.fn.bufname(bufnr), ":t") + if name == "" then name = "[No name]" end + + local highlight = bufnr == current and "WinBar" or "WinBarNC" + local modified = vim.bo[bufnr].modified and "*" or "" + + table.insert(buffers, string.format( + "%%#%s# %d:%s%s %%*", + highlight, bufnr, name, modified + )) + end + end + + if #buffers > 1 and vim.bo[current].buflisted then + vim.opt_local.winbar = table.concat(buffers) + vim.keymap.set({'n','v'}, '<C-n>', ':bn<cr>', { buffer = current }) + vim.keymap.set({'n','v'}, '<C-p>', ':bp<cr>', { buffer = current }) + + else + vim.opt_local.winbar = "" + pcall(function() + vim.keymap.del({'n','v'}, '<C-n>', { buffer = current }) + vim.keymap.del({'n','v'}, '<C-p>', { buffer = current }) + end) + end + end + }) +end + +return M diff --git a/modules/neovim/neovim.lua b/modules/neovim/neovim.lua @@ -1,168 +0,0 @@ -vim.opt.tabstop = 2 -vim.opt.shiftwidth = 0 -vim.opt.softtabstop = 0 -vim.opt.expandtab = true -vim.opt.number = true -vim.opt.relativenumber = true -vim.opt.scrolloff = 5 -vim.opt.path = vim.o.path .. '**' -vim.opt.wildmenu = true -vim.opt.wildignore = { - "**/.direnv/**", - "**/node_modules/**", - "**/vendor/**", -}; -vim.opt.cursorline = true -vim.opt.undofile = true -vim.opt.undodir = vim.fn.stdpath('data') .. '/undodir' -vim.opt.colorcolumn = '+1' -vim.opt.textwidth = 72 -vim.opt.formatoptions = 'cqj' -vim.keymap.set({'n','v'}, '<C-c>', '"+y') -vim.keymap.set({'n','v'}, '<C-v>', '"+p') -vim.keymap.set({'n','v'}, '<C-x>', '"+d') -vim.keymap.set({'n','v'}, '<C-q>', '<C-v>', { noremap = true}) -vim.keymap.set({'n','v'}, '<C-n>', ':bn<cr>') -vim.keymap.set({'n','v'}, '<C-p>', ':bp<cr>') -vim.api.nvim_create_autocmd('BufReadPost', { - group = vim.api.nvim_create_augroup('ReturnToLastLine', { clear = true }), - callback = function() - local last = vim.fn.line([['"]]) - if last > 1 and last < vim.fn.line("$") then - vim.cmd([[normal! g'"]]) - end - end -}) -vim.opt.list = true -vim.opt.listchars = { - trail = '~', - tab = '| ', - leadmultispace = ':' .. string.rep(' ', vim.opt.tabstop:get() - 1), -} -vim.api.nvim_create_autocmd('OptionSet', { - group = vim.api.nvim_create_augroup('ListcharsLeadmultispaceWidth', { clear = true }), - pattern = 'tabstop', - callback = function() - local listchars = vim.opt.listchars:get() - listchars.leadmultispace = ':' .. string.rep(' ', vim.opt.tabstop:get() - 1) - vim.opt.listchars = listchars - end -}) -vim.opt.cmdheight = 0 -vim.api.nvim_create_autocmd("RecordingEnter", { - callback = function() - vim.opt.cmdheight = 1 - end, -}) -vim.api.nvim_create_autocmd("RecordingLeave", { - callback = function() - vim.opt.cmdheight = 0 - end, -}) -vim.api.nvim_set_hl(0, 'Normal', { bg='none' }) -vim.api.nvim_set_hl(0, 'StatusLine', { bg='none' }) -vim.api.nvim_set_hl(0, 'WinBar', { bg='none' }) -vim.api.nvim_set_hl(0, 'WinBarNC', { bg='none', fg='gray' }) -vim.api.nvim_set_hl(0, 'NormalFloat', { bg='none' }) -vim.api.nvim_set_hl(0, 'FloatBorder', { bg='none' }) -vim.api.nvim_set_hl(0, 'Pmenu', { bg='none' }) -vim.api.nvim_set_hl(0, 'PmenuBorder', { bg='none' }) -vim.opt.winborder = "rounded"; -vim.opt.pumborder = "rounded"; -function _G.WinBar() - local buffers = {} - local current = vim.api.nvim_win_get_buf(0) - for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do - if vim.bo[bufnr].buflisted then - local name = vim.fn.fnamemodify(vim.fn.bufname(bufnr), ":t") - if name == "" then name = "[No name]" end - local highlight = bufnr == current and "WinBar" or "WinBarNC" - local modified = vim.bo[bufnr].modified and "*" or "" - table.insert(buffers, string.format( - "%%#%s# %d:%s%s %%*", - highlight, bufnr, name, modified - )) - end - end - return table.concat(buffers) -end -vim.api.nvim_create_autocmd({'BufEnter', 'BufAdd', 'BufDelete'}, { - group = vim.api.nvim_create_augroup("WinBarVisibility", { clear = true }), - pattern = "*", - callback = function() - local buffers = 0 - for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do - if vim.bo[bufnr].buflisted then - buffers = buffers + 1 - end - end - if buffers > 1 then - vim.opt.winbar = "%!v:lua.WinBar()" - else - vim.opt.winbar = "" - end - end -}) -vim.g.netrw_banner = 0 -vim.g.netrw_winsize = -30 -vim.g.netrw_liststyle = 3 -vim.diagnostic.config({ virtual_text = true }) -vim.opt.signcolumn = "yes" -vim.opt.completeopt = { "fuzzy", "menu", "menuone", "noinsert", "popup" } -vim.lsp.config('*', { - root_markers = { '.git' }, - on_attach = function(client, bufnr) - vim.lsp.completion.enable(true, client.id, bufnr, {}) - end, -}) -vim.lsp.config('nixd', { - cmd = { 'nixd' }, - filetypes = { 'nix' }, - root_markers = { 'flake.nix', '.git' }, -}) -vim.lsp.config('gopls', { - cmd = { 'gopls' }, - filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' }, - root_markers = { 'go.work', 'go.mod', '.git' }, -}) -vim.lsp.config('typescript-language-server', { - cmd = { 'typescript-language-server', '--stdio' }, - filetypes = { 'javascript', 'typescript' }, - root_markers = { 'jsconfig.json', 'tsconfig.json', 'package.json', '.git' }, - init_options = { hostInfo = 'neovim' }, -}) -vim.lsp.enable({ - 'nixd', - 'gopls', - 'typescript-language-server', -}) -vim.keymap.set({'n','v'}, '<C-/>', function() - local buf = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_keymap(buf, 'n', 'q', ':q<CR>', { - silent = true, - nowait = true, - }) - vim.api.nvim_buf_set_lines(buf, 0, -1, false, { - 'i C-x C-o Omni completion', - 'n K Hover', - 'n,v C-w d Hover diagnostics', - 'n,v gra Code action', - 'n gri Go to implementation', - 'n grt Go to type definition', - 'n grr References', - 'n grn Rename', - 'n gO List symbols', - 'vb I Insert before every line in block', - }) - local win = vim.api.nvim_open_win(buf, true, { - title = 'Cheat sheet', - relative = 'editor', - row = math.floor(vim.o.lines * 0.10), - col = math.floor(vim.o.columns * 0.10), - height = math.floor(vim.o.lines * 0.80), - width = math.floor(vim.o.columns * 0.80), - style = 'minimal', - title_pos = 'center', - focusable = true, - }) -end)