{ config, lib, pkgs, inputs, ... }: let cfg = config.modules.editor.vim; in { options.modules.editor.vim = { enable = lib.my.mkBoolOpt false; }; config = lib.mkIf cfg.enable { home-manager.users.${config.user.name} = { imports = [ inputs.nixvim.homeManagerModules.nixvim ]; programs.nixvim = { enable = true; package = pkgs.unstable.neovim-unwrapped; vimAlias = true; globals.mapleader = " "; opts = { # Keep visual indentation on wrapped lines breakindent = true; # Hide command line unless needed cmdheight = 0; # Insert mode completion options completeopt = [ "menu" "menuone" "noselect" ]; # Raise a dialog asking if you wish to save the current file(s) confirm = true; # Copy previous indentation on autoindenting copyindent = true; # Highlight current line cursorline = true; # Enable linematch diff algorithm diffopt.__raw = /*lua*/ '' vim.list_extend(vim.opt.diffopt:get(), { "algorithm:histogram", "linematch:60" }) ''; # Expand to spaces expandtab = false; # Disable `~` on nonexistent lines fillchars = { eob = " "; }; # Enable fold with all code unfolded foldcolumn = "1"; foldenable = true; foldlevel = 99; foldlevelstart = 99; # Ignore case in search patterns ignorecase = true; # Show substitution preview in split window inccommand = "split"; # Infer casing on word completion infercase = true; # Global statusline laststatus = 3; # Wrap lines at 'breakat' linebreak = true; # Enable list mode list = true; # Set custom strings for list mode # - tabulations are shown as ‒▶ # - trailing spaces are shown as · # - multiple non-leading consecutive spaces are shown as bullets (·) # - non-breakable spaces are shown as ⎕ listchars = "tab:» ,trail:·,multispace:·,lead: ,nbsp:␣"; # Enable mouse support mouse = "a"; # Show line numbers number = true; # Preserve indentation as much as possible preserveindent = true; # Height of the popup menu pumheight = 10; # Display line numbers relative to current line relativenumber = false; # Number of spaces to use for indentation shiftwidth = 2; # Disable search count wrap and startup messages shortmess.__raw = /*lua*/ '' vim.tbl_deep_extend("force", vim.opt.shortmess:get(), { s = true, I = true }) ''; # Disable showing modes in command line showmode = false; # Show tabline when needed showtabline = 1; # Show signs column signcolumn = "yes"; # Override ignorecase if search pattern contains uppercase characters smartcase = true; # Number of spaces input on softtabstop = 2; # Open horizontal split below (:split) splitbelow = true; # Open vertical split to the right (:vsplit) splitright = true; # Number of spaces to represent a tabstop = 2; # Enables 24-bit RGB color termguicolors = true; # Shorter timeout duration timeoutlen = 500; # Set window title to the filename title = true; # Save undo history to undo file (in $XDG_STATE_HOME/nvim/undo) undofile = true; viewoptions.__raw = /*lua*/ '' vim.tbl_filter(function(val) return val ~= "curdir" end, vim.opt.viewoptions:get()) ''; # Enable virtual edit in visual block mode # This has the effect of selecting empty cells beyond lines boundaries virtualedit = "block"; # Disable line wrapping wrap = false; # Disable making a backup before overwriting a file writebackup = false; # Sync clipboard between OS and Neovim. clipboard = "unnamedplus"; }; keymaps = [ # Search { key = ""; action = "Telescope fd layout_strategy=vertical"; } { key = "sh"; action = "Telescope help_tags"; options.desc = "[S]earch [H]elp"; } { key = "sk"; action = "Telescope keymaps"; options.desc = "[S]earch [K]eymaps"; } { key = "ss"; action = "Telescope"; options.desc = "[S]earch [S]elect Telescope"; } { key = "/"; action = "Telescope live_grep layout_strategy=vertical"; options.desc = "Search with rg"; } { key = "sr"; action = "Telescope resume"; options.desc = "[S]earch [R]esume"; } { key = "cx"; action = "Telescope diagnostics layout_strategy=vertical"; options.desc = "Search diagnostics"; } # File { key = "fs"; action.__raw = /*lua*/'' function() vim.lsp.buf.format() vim.cmd.write() end ''; options.desc = "Format and save buffer"; } { mode = "n"; key = "fn"; action = "enew"; options.desc = "New File"; } # Project { key = "ps"; action = "wa"; options.desc = "Save all buffers"; } # Git { key = "gg"; action = "Neogit"; } # Buffers { key = "bb"; action = "Telescope buffers layout_strategy=vertical"; } { key = "bn"; action = "bnext"; options.desc = "Next buffer"; } { key = "bp"; action = "bprev"; options.desc = "Previous buffer"; } { key = "bl"; action = "e #"; options.desc = "Other buffer"; } { key = "bk"; action = "bd"; options.desc = "Delete buffer and Window"; } { key = "bd"; action = "bd"; options.desc = "Delete buffer and Window"; } # Windows { mode = "n"; key = "ww"; action = "p"; options = { desc = "Other Window"; remap = true; }; } { mode = "n"; key = "wd"; action = "c"; options = { desc = "Delete Window"; remap = true; }; } { mode = "n"; key = "ws"; action = "s"; options = { desc = "Split Window Below"; remap = true; }; } { mode = "n"; key = "wv"; action = "v"; options = { desc = "Split Window Right"; remap = true; }; } { mode = "n"; key = ""; action = "j"; options = { desc = "Go to Lower Winddow"; remap = true; }; } { mode = "n"; key = "wj"; action = "j"; options = { desc = "Go to Lower Winddow"; remap = true; }; } { mode = "n"; key = ""; action = "k"; options = { desc = "Go to Upper Winddow"; remap = true; }; } { mode = "n"; key = "wk"; action = "k"; options = { desc = "Go to Upper Winddow"; remap = true; }; } { mode = "n"; key = ""; action = "l"; options = { desc = "Go to Right Winddow"; remap = true; }; } # Move lines { mode = "n"; key = ""; action = "m .+1=="; options.desc = "Move Down"; } { mode = "n"; key = ""; action = "m .-2=="; options.desc = "Move Up"; } { mode = "i"; key = ""; action = "m .+1==gi"; options.desc = "Move Down"; } { mode = "i"; key = ""; action = "m .-2==gi"; options.desc = "Move Up"; } { mode = "v"; key = ""; action = ":m '>+1gv=gv"; options.desc = "Move Down"; } { mode = "v"; key = ""; action = ":m '<-2gv=gv"; options.desc = "Move Up"; } # Better indenting { mode = "v"; key = "<"; action = "", true, true, true) end if opts.skip_next and next ~= "" and next:match(opts.skip_next) then return o end if opts.skip_ts and #opts.skip_ts > 0 then local ok, captures = pcall(vim.treesitter.get_captures_at_pos, 0, cursor[1] - 1, math.max(cursor[2] - 1, 0)) for _, capture in ipairs(ok and captures or {}) do if vim.tbl_contains(opts.skip_ts, capture.capture) then return o end end end if opts.skip_unbalanced and next == c and c ~= o then local _, count_open = line:gsub(vim.pesc(pair:sub(1, 1)), "") local _, count_close = line:gsub(vim.pesc(pair:sub(2, 2)), "") if count_close > count_open then return o end end return open(pair, neigh_pattern) end end ''; } { pkg = pkgs.vimPlugins.lualine-nvim; event = "VeryLazy"; init = /*lua*/ '' function() vim.g.lualine_laststatus = vim.o.laststatus if vim.fn.argc(-1) > 0 then -- set an empty statusline till lualine loads vim.o.statusline = " " else -- hide the statusline on the starter page vim.o.laststatus = 0 end end ''; opts.__raw = /*lua*/ '' { options = { icons_enabled = true, } } ''; } { pkg = pkgs.unstable.vimPlugins.neogit; dependencies = [ pkgs.vimPlugins.plenary-nvim pkgs.vimPlugins.diffview-nvim pkgs.vimPlugins.telescope-nvim ]; event = "VeryLazy"; config = true; } { pkg = pkgs.vimPlugins.gitsigns-nvim; opts.__raw = /*lua*/ '' { on_attach = function(buffer) local gs = package.loaded.gitsigns local function map(mode, l ,r, desc) vim.keymap.set(mode, l, r, { buffer = buffer, desc = desc}) end map("n", "h;", function() if vim.wo.diff then vim.cmd.normal({ "]c", bang = true }) else gs.nav_hunk("next") end end, "Next Hunk") map("n", "h,", function() if vim.wo.diff then vim.cmd.normal({ "[c", bang = true }) else gs.nav_hunk("prev") end end, "Prev Hunk") map({ "n", "v" }, "gs", gs.stage_hunk, "Stage Hunk") map({ "n", "v" }, "gr", gs.reset_hunk, "Reset Hunk") map("n", "gS", gs.stage_buffer, "Stage Buffer") map("n", "gu", gs.undo_stage_hunk, "Undo Stage Hunk") map("n", "gR", gs.reset_buffer, "Reset Buffer") map("n", "gp", gs.preview_hunk_inline, "Preview Hunk Inline") map("n", "gb", function() gs.blame_line({ full = true }) end, "Blame Line") map("n", "gd", gs.diffthis, "Diff This") map("n", "gD", function() gs.diffthis("~") end, "Diff This ~") map('n', 'tb', gs.toggle_current_line_blame, "Toggle current line blame") map({ "o", "x" }, "ih", ":Gitsigns select_hunk", "GitSigns Select Hunk") end, } ''; } { pkg = (pkgs.vimUtils.buildVimPlugin { name = "yazi.nvim"; src = inputs.vim-yazi; }); event = "VeryLazy"; keys.__raw = /*lua*/ '' { { ".", function() require("yazi").yazi() end, desc = "Open file manager" }, { "", desc = "Decrement Selection", mode = "x" }, } ''; opts.__raw = /*lua*/ '' { open_for_directories = true, } ''; } { pkg = pkgs.unstable.vimPlugins.fidget-nvim; opts.__raw = /*lua*/ '' { logger = { level = vim.log.levels.WARN }, notification = { filter = vim.log.levels.INFO }, } ''; } { pkg = pkgs.unstable.vimPlugins.indent-blankline-nvim; event = "VeryLazy"; main = "ibl"; opts = { scope.enabled = false; }; } { pkg = pkgs.unstable.vimPlugins.vim-sleuth; } { pkg = pkgs.unstable.vimPlugins.comment-nvim; } { pkg = pkgs.vimPlugins.leap-nvim; config = /*lua*/ '' function (_, opts) local leap = require("leap") for k, v in pairs(opts) do leap.opts[k] = v end leap.add_default_mappings(true) end ''; } { pkg = pkgs.vimPlugins.telescope-nvim; dependencies = [ pkgs.vimPlugins.plenary-nvim pkgs.vimPlugins.nvim-web-devicons pkgs.vimPlugins.telescope-ui-select-nvim pkgs.vimPlugins.telescope-fzf-native-nvim ]; opts.__raw = /*lua*/ '' { defaults = { layout_config = { vertical = { width = 0.9 } }, }, } ''; } { pkg = pkgs.vimPlugins.nvim-treesitter.withAllGrammars; lazy.__raw = "vim.fn.argc(-1) == 0"; init = /*lua*/ '' function(plugin) -- PERF: add nvim-treesitter queries to the rtp and it's custom query predicates early -- This is needed because a bunch of plugins no longer `require("nvim-treesitter")`, which -- no longer trigger the **nvim-treesitter** module to be loaded in time. -- Luckily, the only things that those plugins need are the custom queries, which we make available -- during startup. require("lazy.core.loader").add_to_rtp(plugin) require("nvim-treesitter.query_predicates") end ''; cmd = [ "TSUpdateSync" "TSUpdate" "TSInstall" ]; keys.__raw = /*lua*/ '' { { "", desc = "Increment Selection" }, { "", desc = "Decrement Selection", mode = "x" }, } ''; opts.__raw = /*lua*/ '' { highlight = { enable = true }, indent = { enable = true }, ensure_installed = "all", parser_install_dir = vim.fs.joinpath(vim.fn.stdpath('data'), 'site'), incremental_selection = { enable = true, keymaps = { init_selection = "", node_incremental = "", scope_incremental = false, node_decremental = "", }, }, ignore_install = { "org" }, } ''; config = /*lua*/ '' function (_, opts) require("nvim-treesitter.configs").setup(opts) vim.opt.runtimepath:prepend(vim.fs.joinpath(vim.fn.stdpath('data'), 'site')) end ''; } { pkg = pkgs.unstable.vimPlugins.which-key-nvim; event = "VimEnter"; config = /*lua*/ '' function () require('which-key').setup() require('which-key').register { ['b'] = { name = 'Buffer', _ = 'which_key_ignore' }, ['c'] = { name = 'Code', _ = 'which_key_ignore' }, ['d'] = { name = 'Document', _ = 'which_key_ignore' }, ['f'] = { name = 'File', _ = 'which_key_ignore' }, ['g'] = { name = 'Git', _ = 'which_key_ignore' }, ['o'] = { name = 'Org', _ = 'which_key_ignore' }, ['n'] = { name = 'Org-roam', _ = 'which_key_ignore' }, ['p'] = { name = 'Project', _ = 'which_key_ignore' }, ['r'] = { name = 'Rename', _ = 'which_key_ignore' }, ['s'] = { name = 'Search', _ = 'which_key_ignore' }, ['w'] = { name = 'Window', _ = 'which_key_ignore' }, ['t'] = { name = 'Toggle', _ = 'which_key_ignore' }, } end ''; } { pkg = pkgs.vimPlugins.nvim-lspconfig; config = /*lua*/ '' function () vim.api.nvim_create_autocmd('LspAttach', { group = vim.api.nvim_create_augroup('lsp-attach', { clear = true }), callback = function(event) local map = function(keys, func, desc) vim.keymap.set('n', keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc }) end map('gd', require('telescope.builtin').lsp_definitions, 'Go to definition') map('gr', require('telescope.builtin').lsp_references, 'Go to references') map('gI', require('telescope.builtin').lsp_implementations, 'Goto implementation') map('gD', vim.lsp.buf.declaration, 'Go to declaration') map('gT', require('telescope.builtin').lsp_type_definitions, 'Type definition') map('cD', require('telescope.builtin').lsp_document_symbols, 'Document symbols') map('cw', require('telescope.builtin').lsp_dynamic_workspace_symbols, 'Workspace symbols ') map('cd', vim.diagnostic.open_float, 'Line diagnostics') map('c,', vim.diagnostic.goto_prev, 'Previous diagnostics') map('c;', vim.diagnostic.goto_next, 'Next diagnostics') map('cr', vim.lsp.buf.rename, 'Rename') map('ca', vim.lsp.buf.code_action, 'Code action') map('K', vim.lsp.buf.hover, 'Hover Documentation') end }) local has_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") local capabilities = vim.tbl_deep_extend( "force", {}, vim.lsp.protocol.make_client_capabilities(), has_cmp and cmp_nvim_lsp.default_capabilities() or {} ) local function setup(server, server_opts) local server_opts_with_caps = vim.tbl_deep_extend("force", { capabilities = vim.deepcopy(capabilities), }, server_opts) require("lspconfig")[server].setup(server_opts_with_caps) end setup("yamlls", {}) setup("typos_lsp", { init_options = { diagnosticSeverity = "Hint" } }) setup("tsserver", {}) setup("terraformls", {}) setup("sqls", {}) setup("nixd", {}) setup("marksman", {}) setup("lua_ls", {}) setup("jsonls", { cmd = { "${pkgs.vscode-langservers-extracted}/bin/vscode-json-language-server", "--stdio" } }) setup("html", { cmd = { "${pkgs.vscode-langservers-extracted}/bin/vscode-html-language-server", "--stdio" } }) setup("eslint", { cmd = { "${pkgs.vscode-langservers-extracted}/bin/vscode-eslint-language-server", "--stdio" } }) setup("dockerls", { cmd = { "${pkgs.dockerfile-language-server-nodejs}/bin/docker-langserver", "--stdio" } }) setup("docker_compose_language_service", {}) setup("cssls", { cmd = { "${pkgs.vscode-langservers-extracted}/bin/vscode-css-language-server", "--stdio" } }) setup("bashls", {}) setup("ansiblels", { cmd = { "${pkgs.ansible-language-server}/bin/ansible-language-server", "--stdio" } }) end ''; } { pkg = pkgs.unstable.vimPlugins.nvim-metals; dependencies = [ pkgs.vimPlugins.plenary-nvim ]; ft = [ "scala" "sbt" ]; opts.__raw = /*lua*/ '' function() local metals_config = require("metals").bare_config() metals_config.on_attach = function(client, bufnr) vim.keymap.set( "n", "me", function() require("telescope").extensions.metals.commands() end, { noremap=true, silent=true, buffer = bufn, desc = "Metals commands"} ) vim.keymap.set( "n", "co", "MetalsOrganizeImports", { noremap=true, silent=true, buffer = bufn, desc = "Organize imports"} ) end metals_config.init_options.statusBarProvider = "off" metals_config.settings = { showImplicitArguments = true, } return metals_config end ''; config = /*lua*/ '' function (self, metals_config) local nvim_metals_group = vim.api.nvim_create_augroup("nvim-metals", { clear = true }) vim.api.nvim_create_autocmd("FileType", { pattern = self.ft, callback = function() require("metals").initialize_or_attach(metals_config) end, group = nvim_metals_group, }) end ''; } { pkg = pkgs.unstable.vimPlugins.nvim-cmp; event = "InsertEnter"; dependencies = [ pkgs.unstable.vimPlugins.cmp-nvim-lsp pkgs.unstable.vimPlugins.cmp-path pkgs.unstable.vimPlugins.cmp-buffer ]; opts.__raw = /*lua*/ '' function() local cmp = require("cmp") return { mapping = { [""] = cmp.mapping.complete(), [""] = cmp.mapping.scroll_docs(-4), [""] = cmp.mapping.close(), [""] = cmp.mapping.scroll_docs(4), [""] = cmp.mapping.confirm({ select = true }), [""] = cmp.mapping.confirm({ select = true }), [""] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "s" }), [""] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "s" }), }, sources = { { name = "nvim_lsp" }, { name = "path" }, { name = "buffer" }, { name = "orgmode" }, }, } end ''; } # Disabled for now as it tries to write org grammar to its own directory in the nix store # https://github.com/nvim-orgmode/orgmode/blob/95fb795a422f0455e03d13a3f83525f1d00793ad/lua/orgmode/utils/treesitter/install.lua#L9 # { # pkg = pkgs.unstable.vimPlugins.orgmode; # event = "VeryLazy"; # ft = [ "org" ]; # config = /*lua*/ '' # function () # require('orgmode').setup({ # org_agend_files = '~/Nextcloud/Org/**/*', # org_default_notes_file = '~/Nextcloud/Org/refile.org', # }) # end # ''; # } # { # pkg = (pkgs.vimUtils.buildVimPlugin { # name = "org-roam.nvim"; # src = inputs.vim-org-roam; # }); # dependencies = [ pkgs.unstable.vimPlugins.orgmode ]; # event = "VeryLazy"; # ft = [ "org" ]; # config = /*lua*/ '' # function () # require('org-roam').setup({ # directory = '~/Nextcloud/OrgRoam', # }) # end # ''; # } ]; }; }; colorschemes.gruvbox = { enable = true; package = pkgs.unstable.vimPlugins.gruvbox-nvim; settings = { overrides = { Include = { link = "GruvboxRed"; }; "@constructor" = { link = "GruvboxYellow"; }; "@function.builtin" = { link = "GruvboxFg1"; }; "@function.call" = { link = "GruvboxFg1"; }; "@function.macro" = { link = "GruvboxFg1"; }; "@function.method.call.scala" = { link = "GruvboxFg1"; }; "@method.call" = { link = "GruvboxFg1"; }; "@variable.member.scala" = { link = "GruvboxFg1"; }; "@lsp.type.type.scala" = { link = ""; }; "@lsp.type.method.scala" = { link = ""; }; "@lsp.type.modifier.scala" = { link = "GruvboxOrange"; }; "@lsp.type.typeParameter.scala" = { link = "GruvboxAqua"; }; "@lsp.type.type.nix" = { link = ""; }; "@lsp.type.method.nix" = { link = ""; }; "@lsp.type.macro.nix" = { link = ""; }; "@lsp.type.interface.nix" = { link = ""; }; }; }; }; }; home.packages = with pkgs.unstable; [ ripgrep fd nodejs opentofu nixfmt-rfc-style nixpkgs-fmt coursier # LSP yaml-language-server typos-lsp nodePackages.typescript-language-server terraform-ls sqls nixd marksman lua-language-server docker-compose-language-service bash-language-server ]; }; }; }