{ config, pkgs, ... }: let c = config.theme.colors; in { programs.neovim = { enable = true; viAlias = true; vimAlias = true; extraPackages = with pkgs; [ lua-language-server nil nixfmt ripgrep fd ]; # Set mapleader before any plugin config blocks run extraConfig = '' let g:mapleader = "\" let g:maplocalleader = "\" ''; plugins = with pkgs.vimPlugins; [ # ── Core dependencies ────────────────────────────────────────────── plenary-nvim nvim-web-devicons # ── Theme ────────────────────────────────────────────────────────── gruvbox-nvim # ── Syntax highlighting ──────────────────────────────────────────── { plugin = nvim-treesitter.withAllGrammars; type = "lua"; config = '' require("nvim-treesitter").setup({ highlight = { enable = true }, indent = { enable = true }, }) ''; } # ── Completion (must come before lspconfig) ──────────────────────── luasnip cmp-nvim-lsp cmp-buffer cmp-path cmp_luasnip { plugin = nvim-cmp; type = "lua"; config = '' local cmp = require("cmp") local luasnip = require("luasnip") cmp.setup({ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, mapping = cmp.mapping.preset.insert({ [""] = cmp.mapping.complete(), [""] = cmp.mapping.abort(), [""] = cmp.mapping.confirm({ select = false }), [""] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_next_item() elseif luasnip.expand_or_jumpable() then luasnip.expand_or_jump() else fallback() end end, { "i", "s" }), [""] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_prev_item() elseif luasnip.jumpable(-1) then luasnip.jump(-1) else fallback() end end, { "i", "s" }), }), sources = cmp.config.sources({ { name = "nvim_lsp" }, { name = "luasnip" }, { name = "buffer" }, { name = "path" }, }), }) ''; } # ── LSP ──────────────────────────────────────────────────────────── { plugin = nvim-lspconfig; type = "lua"; config = '' local capabilities = require("cmp_nvim_lsp").default_capabilities() vim.api.nvim_create_autocmd("LspAttach", { callback = function(event) local map = function(keys, func, desc) vim.keymap.set("n", keys, func, { buffer = event.buf, desc = desc }) end map("gd", vim.lsp.buf.definition, "Go to definition") map("gr", vim.lsp.buf.references, "Find references") map("K", vim.lsp.buf.hover, "Hover docs") map("rn", vim.lsp.buf.rename, "Rename symbol") map("ca", vim.lsp.buf.code_action, "Code action") map("ld", vim.diagnostic.open_float, "Show diagnostics") map("ln", vim.diagnostic.goto_next, "Next diagnostic") map("lp", vim.diagnostic.goto_prev, "Prev diagnostic") end, }) vim.lsp.config("lua_ls", { capabilities = capabilities, settings = { Lua = { diagnostics = { globals = { "vim" } }, }, }, }) vim.lsp.enable("lua_ls") vim.lsp.config("nil_ls", { capabilities = capabilities, }) vim.lsp.enable("nil_ls") ''; } # ── File explorer ────────────────────────────────────────────────── { plugin = nvim-tree-lua; type = "lua"; config = '' require("nvim-tree").setup() vim.keymap.set("n", "e", "NvimTreeToggle") ''; } # ── Statusline ───────────────────────────────────────────────────── { plugin = lualine-nvim; type = "lua"; config = '' require("lualine").setup({ options = { theme = "gruvbox-material" }, }) ''; } # ── Buffer tabs ──────────────────────────────────────────────────── { plugin = bufferline-nvim; type = "lua"; config = '' require("bufferline").setup() vim.keymap.set("n", "", "BufferLineCycleNext", { desc = "Next buffer" }) vim.keymap.set("n", "", "BufferLineCyclePrev", { desc = "Prev buffer" }) vim.keymap.set("n", "x", "bdelete", { desc = "Close buffer" }) ''; } # ── Fuzzy finder ─────────────────────────────────────────────────── { plugin = telescope-nvim; type = "lua"; config = '' local builtin = require("telescope.builtin") vim.keymap.set("n", "ff", builtin.find_files, { desc = "Find files" }) vim.keymap.set("n", "fg", builtin.live_grep, { desc = "Live grep" }) vim.keymap.set("n", "fb", builtin.buffers, { desc = "Find buffers" }) vim.keymap.set("n", "fr", builtin.oldfiles, { desc = "Recent files" }) vim.keymap.set("n", "fs", builtin.grep_string, { desc = "Find word under cursor" }) ''; } # ── Git ──────────────────────────────────────────────────────────── { plugin = gitsigns-nvim; type = "lua"; config = '' require("gitsigns").setup({ on_attach = function(bufnr) local gs = require("gitsigns") local map = function(mode, keys, func, desc) vim.keymap.set(mode, keys, func, { buffer = bufnr, desc = desc }) end map("n", "gn", gs.next_hunk, "Next hunk") map("n", "gp", gs.prev_hunk, "Prev hunk") map("n", "gh", gs.preview_hunk, "Preview hunk") map("n", "gb", gs.blame_line, "Blame line") map("n", "gs", gs.stage_hunk, "Stage hunk") map("n", "gr", gs.reset_hunk, "Reset hunk") end, }) ''; } # ── Comment toggling ─────────────────────────────────────────────── { plugin = comment-nvim; type = "lua"; config = ''require("Comment").setup()''; } # ── Indentation guides ───────────────────────────────────────────── { plugin = indent-blankline-nvim; type = "lua"; config = ''require("ibl").setup()''; } ]; initLua = '' require("gruvbox").setup({ contrast = "medium", transparent_mode = false, }) vim.cmd("colorscheme gruvbox") vim.opt.number = true vim.opt.relativenumber = true vim.opt.tabstop = 2 vim.opt.shiftwidth = 2 vim.opt.expandtab = true vim.opt.smartindent = true vim.opt.ignorecase = true vim.opt.smartcase = true vim.opt.hlsearch = false vim.opt.termguicolors = true vim.opt.signcolumn = "yes" vim.opt.cursorline = true vim.opt.scrolloff = 8 vim.opt.wrap = false vim.opt.splitright = true vim.opt.splitbelow = true vim.opt.clipboard = "unnamedplus" local map = vim.keymap.set map("n", "", "h") map("n", "", "j") map("n", "", "k") map("n", "", "l") map("v", "J", ":m '>+1gv=gv") map("v", "K", ":m '<-2gv=gv") map("n", "", "zz") map("n", "", "zz") map("n", "w", "w") map("n", "q", "q") ''; }; }