feat(home): add declarative Neovim via nixvim
New `nixvim` flake input (release branch, follows nixpkgs) drives the whole editor from `modules/nvim.nix`, wired into `homeModules.default`. Minimal LSP baseline (`nixd`, `bashls`) exposed as `local.nvim.lspServers`; blink-cmp, Telescope, neo-tree, lualine, treesitter, colorizer, gitsigns, toggleterm, and a catppuccin theme. which-key groups plus `<leader>?`/`<leader>sk` give an in-editor keybind cheatsheet; per-directory session persistence with `sessionoptions` + a `PersistenceSavePre` neo-tree close for clean layouts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -122,7 +122,7 @@ let
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ];
|
||||
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ./nvim.nix ];
|
||||
|
||||
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
|
||||
# by name without wiring each through nix.
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
# Neovim, configured declaratively via nixvim. The editor — plugins, LSP,
|
||||
# completion, keymaps — is defined here and pinned by the flake lock; nothing is
|
||||
# downloaded at runtime, unlike an imperative distro (NvChad/LazyVim). The nixvim
|
||||
# home-manager module that provides `programs.nixvim` is imported for consumers by
|
||||
# `homeModules.default` in flake.nix, so this file only sets options.
|
||||
#
|
||||
# Consumer knobs (the option-with-default contract): `local.nvim.enable` to opt
|
||||
# out wholesale, `local.nvim.colorscheme` to swap theme in one line, and
|
||||
# `local.nvim.lspServers` to reshape the language set. Everything else is
|
||||
# opinionated — but since nixvim exposes every plugin as an option, a consumer can
|
||||
# still override any `programs.nixvim.plugins.*` directly from their own config.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.local.nvim;
|
||||
in
|
||||
{
|
||||
options.local.nvim = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable the nixvim-based Neovim setup.";
|
||||
};
|
||||
|
||||
colorscheme = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "catppuccin";
|
||||
example = "tokyonight";
|
||||
description = ''
|
||||
nixvim colorscheme module to enable (the attr under `colorschemes.*`,
|
||||
e.g. "catppuccin", "tokyonight", "gruvbox"). Catppuccin is tuned below
|
||||
(mocha flavour); for another scheme set its `colorschemes.<name>.settings`
|
||||
from your own config if you want to tweak it.
|
||||
'';
|
||||
};
|
||||
|
||||
lspServers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
"nixd" # Nix — this repo's own language, so a sensible public baseline
|
||||
"bashls" # Shell — likewise generic enough to default on
|
||||
];
|
||||
example = [ "nixd" "bashls" "rust_analyzer" "basedpyright" ];
|
||||
description = ''
|
||||
lspconfig server names to enable under `plugins.lsp.servers.<name>`.
|
||||
Each must exist in nixvim's server list and its binary in nixpkgs. The
|
||||
public default is deliberately minimal (Nix + shell); a consumer sets its
|
||||
own language set here — and any per-server tuning a pick needs (e.g.
|
||||
rust_analyzer's `installRustc`) via `programs.nixvim.plugins.lsp.servers`.
|
||||
Note: standalone Lua wants "lua_ls", but Lua embedded in Nix strings is
|
||||
invisible to any LSP, so it is not needed just to edit this config.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
programs.nixvim = {
|
||||
enable = true;
|
||||
|
||||
# Point nixvim at our (followed) nixpkgs instead of the rev it pins. Both
|
||||
# track nixos-26.05, so the version skew nixvim warns about does not arise,
|
||||
# and this keeps a single nixpkgs in the closure. Setting it explicitly is
|
||||
# also what silences that warning.
|
||||
nixpkgs.source = pkgs.path;
|
||||
|
||||
# System clipboard on Wayland: yanks go to / puts read from wl-clipboard,
|
||||
# so copy/paste crosses the nvim boundary. Enabling the provider pulls the
|
||||
# wl-clipboard binaries in as a dependency.
|
||||
clipboard = {
|
||||
register = "unnamedplus";
|
||||
providers.wl-copy.enable = true;
|
||||
};
|
||||
|
||||
globals = {
|
||||
mapleader = " ";
|
||||
maplocalleader = " ";
|
||||
};
|
||||
|
||||
opts = {
|
||||
number = true;
|
||||
relativenumber = true; # relative line numbers for quick j/k motions
|
||||
signcolumn = "yes"; # always show the gutter so text doesn't jump
|
||||
cursorline = true;
|
||||
termguicolors = true; # 24-bit colour, required by catppuccin & co.
|
||||
scrolloff = 8; # keep 8 lines of context around the cursor
|
||||
|
||||
expandtab = true; # spaces, not tabs
|
||||
shiftwidth = 2;
|
||||
tabstop = 2;
|
||||
smartindent = true;
|
||||
|
||||
ignorecase = true; # case-insensitive search…
|
||||
smartcase = true; # …unless the query has a capital
|
||||
undofile = true; # persistent undo across sessions
|
||||
splitright = true;
|
||||
splitbelow = true;
|
||||
|
||||
# What :mksession stores (persistence.nvim). Drops "blank" and "terminal"
|
||||
# from nvim's default so empty/plugin windows (neo-tree, toggleterm) aren't
|
||||
# serialised — they restore as broken empty splits otherwise. Adds
|
||||
# "globals", which some plugins rely on. Pairs with the PersistenceSavePre
|
||||
# neo-tree close below.
|
||||
sessionoptions = "buffers,curdir,folds,globals,tabpages,winsize";
|
||||
};
|
||||
|
||||
# Theme. Swap via local.nvim.colorscheme; catppuccin gets the mocha flavour.
|
||||
# mkMerge keeps the computed `${cfg.colorscheme}` key from colliding with the
|
||||
# literal `catppuccin` one when they happen to be the same attr.
|
||||
colorschemes = lib.mkMerge [
|
||||
{ ${cfg.colorscheme}.enable = true; }
|
||||
(lib.mkIf (cfg.colorscheme == "catppuccin") {
|
||||
catppuccin.settings.flavour = "mocha";
|
||||
})
|
||||
];
|
||||
|
||||
plugins = {
|
||||
# Shared icon set for the tree, statusline, completion menu.
|
||||
web-devicons.enable = true;
|
||||
|
||||
# Syntax via tree-sitter. The default grammar set is broad, so most
|
||||
# languages get highlighting without an LSP or any per-language wiring.
|
||||
treesitter = {
|
||||
enable = true;
|
||||
settings = {
|
||||
highlight.enable = true;
|
||||
indent.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
# LSP. Servers come from local.nvim.lspServers; blink-cmp feeds its
|
||||
# completion capabilities in automatically (setupLspCapabilities).
|
||||
lsp = {
|
||||
enable = true;
|
||||
servers = lib.genAttrs cfg.lspServers (_: { enable = true; });
|
||||
keymaps = {
|
||||
silent = true;
|
||||
lspBuf = {
|
||||
gd = "definition";
|
||||
gD = "declaration";
|
||||
K = "hover";
|
||||
"<leader>rn" = "rename";
|
||||
"<leader>ca" = "code_action";
|
||||
};
|
||||
# gr/gi (references, implementations) are wired to Telescope pickers in
|
||||
# the global keymaps below instead — fuzzy + preview browsing beats the
|
||||
# quickfix list that vim.lsp.buf.references would populate. gd stays a
|
||||
# direct jump here (usually one target; Ctrl-o returns).
|
||||
diagnostic = {
|
||||
# Idiomatic vim bracket pair for stepping a list, kept as the public
|
||||
# default. A consumer whose layout makes brackets awkward (they need
|
||||
# AltGr on QWERTZ) can add comfortable aliases from their own config:
|
||||
# this option is attrsOf, so extra keys merge in without conflict.
|
||||
"[d" = "goto_prev";
|
||||
"]d" = "goto_next";
|
||||
"<leader>ds" = "open_float"; # show the diagnostic under the cursor
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Completion. blink.cmp — faster and lighter to configure than nvim-cmp.
|
||||
blink-cmp = {
|
||||
enable = true;
|
||||
settings = {
|
||||
keymap.preset = "default"; # C-space open, C-y confirm, C-n/C-p move
|
||||
appearance.nerd_font_variant = "mono";
|
||||
sources.default = [ "lsp" "path" "snippets" "buffer" ];
|
||||
completion.documentation.auto_show = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Fuzzy finder — files, live grep, open buffers (buffer switching lives
|
||||
# here rather than a bufferline; see the S-h/S-l binds below too).
|
||||
telescope.enable = true;
|
||||
|
||||
# File tree.
|
||||
neo-tree.enable = true;
|
||||
|
||||
# Statusline, NvChad-ish blocks: powerline separators, one global bar,
|
||||
# theme following the colorscheme.
|
||||
lualine = {
|
||||
enable = true;
|
||||
settings.options = {
|
||||
theme = "auto";
|
||||
globalstatus = true;
|
||||
section_separators = { left = ""; right = ""; };
|
||||
component_separators = { left = ""; right = ""; };
|
||||
};
|
||||
};
|
||||
|
||||
# Inline colour swatches for #rrggbb / colour names.
|
||||
colorizer.enable = true;
|
||||
|
||||
# Git signs in the gutter.
|
||||
gitsigns.enable = true;
|
||||
|
||||
# Session persistence: auto-saves a session per project directory into XDG
|
||||
# state, restorable later. <leader>S restores the current dir's session; a
|
||||
# consumer wanting auto-restore on a bare `nvim` adds a VimEnter autocmd.
|
||||
persistence.enable = true;
|
||||
|
||||
# Floating terminal toggled with Ctrl-\ (shares nvim's cwd). Try it; if
|
||||
# a tiled foot next to nvim serves you better, drop this block.
|
||||
toggleterm = {
|
||||
enable = true;
|
||||
settings = {
|
||||
open_mapping = { __raw = "[[<c-\\>]]"; };
|
||||
direction = "float";
|
||||
};
|
||||
};
|
||||
|
||||
# Popup of the pending keybinds after leader — a live, always-accurate
|
||||
# cheatsheet. The group labels name each leader subtree so the popup reads
|
||||
# by category; <leader>? opens the full list on demand (keymaps below).
|
||||
which-key = {
|
||||
enable = true;
|
||||
settings.spec = [
|
||||
{ __unkeyed-1 = "<leader>f"; group = "Find (Telescope)"; }
|
||||
{ __unkeyed-1 = "<leader>d"; group = "Diagnostics"; }
|
||||
{ __unkeyed-1 = "<leader>b"; group = "Buffer"; }
|
||||
{ __unkeyed-1 = "<leader>c"; group = "Code"; }
|
||||
{ __unkeyed-1 = "<leader>r"; group = "Refactor"; }
|
||||
{ __unkeyed-1 = "<leader>s"; group = "Search"; }
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Global (non-LSP) keymaps. Deliberately small — a starting point to grow.
|
||||
keymaps = [
|
||||
{ mode = "n"; key = "<leader>w"; action = "<cmd>w<cr>"; options.desc = "Save"; }
|
||||
{ mode = "n"; key = "<leader>q"; action = "<cmd>q<cr>"; options.desc = "Quit window"; }
|
||||
{ mode = "n"; key = "<leader>Q"; action = "<cmd>qa<cr>"; options.desc = "Quit all (exit nvim)"; }
|
||||
# Restore the saved session for the current directory (persistence.nvim).
|
||||
{ mode = "n"; key = "<leader>S"; action.__raw = "function() require('persistence').load() end"; options.desc = "Restore session"; }
|
||||
{ mode = "n"; key = "<leader>e"; action = "<cmd>Neotree toggle<cr>"; options.desc = "File tree"; }
|
||||
{ mode = "n"; key = "<Esc>"; action = "<cmd>nohlsearch<cr>"; }
|
||||
|
||||
# Telescope
|
||||
{ mode = "n"; key = "<leader>ff"; action = "<cmd>Telescope find_files<cr>"; options.desc = "Find files"; }
|
||||
{ mode = "n"; key = "<leader>fg"; action = "<cmd>Telescope live_grep<cr>"; options.desc = "Live grep"; }
|
||||
{ mode = "n"; key = "<leader>fb"; action = "<cmd>Telescope buffers<cr>"; options.desc = "Buffers"; }
|
||||
{ mode = "n"; key = "<leader>fh"; action = "<cmd>Telescope help_tags<cr>"; options.desc = "Help tags"; }
|
||||
|
||||
# LSP navigation via Telescope pickers (fuzzy + preview) for the
|
||||
# many-result lookups; gd/gD stay direct jumps (plugins.lsp.keymaps above).
|
||||
{ mode = "n"; key = "gr"; action = "<cmd>Telescope lsp_references<cr>"; options.desc = "References"; }
|
||||
{ mode = "n"; key = "gi"; action = "<cmd>Telescope lsp_implementations<cr>"; options.desc = "Implementations"; }
|
||||
|
||||
# Buffer navigation (no bufferline: cycle with Shift-h/l, list via <leader>fb)
|
||||
{ mode = "n"; key = "<S-l>"; action = "<cmd>bnext<cr>"; options.desc = "Next buffer"; }
|
||||
{ mode = "n"; key = "<S-h>"; action = "<cmd>bprevious<cr>"; options.desc = "Prev buffer"; }
|
||||
{ mode = "n"; key = "<leader>bd"; action = "<cmd>bdelete<cr>"; options.desc = "Close buffer"; }
|
||||
|
||||
# Cheatsheet: <leader>? pops the full which-key list; <leader>sk fuzzy-
|
||||
# searches every active mapping via Telescope (catches non-leader binds too).
|
||||
{ mode = "n"; key = "<leader>?"; action = "<cmd>WhichKey<cr>"; options.desc = "All keybinds (which-key)"; }
|
||||
{ mode = "n"; key = "<leader>sk"; action = "<cmd>Telescope keymaps<cr>"; options.desc = "Search keymaps"; }
|
||||
];
|
||||
|
||||
# persistence saves the window layout via :mksession, but neo-tree's special
|
||||
# buffer doesn't serialise — a saved tree window restores as a blank split.
|
||||
# Close neo-tree just before a session is written so the layout stays clean.
|
||||
autoCmd = [
|
||||
{
|
||||
event = "User";
|
||||
pattern = "PersistenceSavePre";
|
||||
callback.__raw = ''function() pcall(vim.cmd, "Neotree close") end'';
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user