Configure Neovim via nixvim with Nix-managed plugins
Add an nvim Module that configures Neovim declaratively through nixvim, wired as a flake input and consumed as its home-manager module. Options, globals, keymaps, and plugin settings are typed Nix; the colorscheme call and two autocmds live in modules/nvim/config.lua via extraConfigLua. Plugins come from nixpkgs (no plugin manager, no runtime cloning); git, ripgrep, and fd are provided from Nix; treesitter grammars are built by Nix so no runtime compiler is needed. Functionally matches the previous config (plugins, keymaps, options, the nord colorscheme, markdown conceal, the Neogit blame toggle), verified headless against the generated init.
This commit is contained in:
24
.claude/tasks/0007-nvim-module.md
Normal file
24
.claude/tasks/0007-nvim-module.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
spec: laptop-mvi
|
||||
blocked-by: 0001-skeleton-and-building-host
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
An nvim `Module` that gives the primary user Neovim configured declaratively through **nixvim**, with **functional parity** to the operator's existing config.
|
||||
Parity is about the "what" — the same plugins, keymaps, options, colorscheme, and behaviour — not the "how".
|
||||
The mechanism is deliberately free to follow NixOS's declarative paradigm rather than transplanting the imperative lazy.nvim setup: plugins are managed by Nix (no plugin manager, no runtime cloning, no lockfile), and as much of the config as possible is expressed as typed Nix, with raw Lua kept only as an escape hatch.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] An nvim `Module` (following the `Enable convention`) is enabled on `neogaia`.
|
||||
- [x] Neovim is configured via **nixvim**, wired as a flake input (`nixvim.inputs.nixpkgs.follows = "nixpkgs"`), consumed as its home-manager module under `home-manager.users.<user>.programs.nixvim`.
|
||||
- [x] Functional parity with the previous config: the same plugins (neogit, diffview, gitsigns, oil, snacks, gbprod-nord, render-markdown, which-key, treesitter), keymaps, `vim` options, the `nord` colorscheme, the Neogit blame-toggle autocmd, and markdown concealment — verified headless against the generated config.
|
||||
- [x] Runtime dependencies `git`, `ripgrep`, and `fd` are provided by Nix; `gcc` is not needed because Nix builds the treesitter grammars.
|
||||
- [x] The `neogaia` toplevel still builds with the nvim `Module` enabled.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **nixvim, typed Nix first.** `modules/nvim/nvim.nix` enables `programs.nixvim` with `opts`, `globals`, `keymaps`, and typed `plugins.*` settings. Treesitter uses `plugins.treesitter` (`highlight.enable`, `indent.enable`, `grammarPackages` from the module's own `builtGrammars`) covering nix, lua, bash, fish, markdown, rust, python, java, kotlin, c, cpp, html, css, javascript, typescript, go.
|
||||
- **The imperative remainder** — the `gbprod/nord.nvim` setup + colorscheme call, the markdown `conceallevel` autocmd, and the Neogit blame-toggle `BufUnload` autocmd — lives in `modules/nvim/config.lua`, pulled in via `extraConfigLua = builtins.readFile ./config.lua`. `gbprod-nord` comes in through `extraPlugins` because nixvim's `colorschemes.nord` is a different plugin.
|
||||
- **Verification.** `nix build .#…programs.nixvim.build.package` exits 0 and the whole toplevel evaluates. The generated config was exercised headless (launched with `-u` the generated init and a scratch `HOME`, since the wrapper otherwise loads the dev host's real `~/.config/nvim`): all options, keymaps, plugins, the `nord` colorscheme, treesitter highlight + indent, and markdown conceal load with no errors.
|
||||
@@ -29,3 +29,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
- chaotic-nyx must **not** follow our `nixpkgs`, and its packages are built against chaotic's own pinned nixpkgs (its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path).
|
||||
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source; the tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.
|
||||
- The remote is self-hosted Gitea (`git.alexion.dev`); the forge CLI is `tea` (login `axi`), and `gh` is not installed.
|
||||
- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`); the config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`.
|
||||
- nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`).
|
||||
- Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users.<user>.programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows.
|
||||
- To reference the nixvim-built package's own attrs (e.g. treesitter `builtGrammars`) inside our NixOS module, give `home-manager.users.<user>` the module-function form (`hm: { programs.nixvim = { ... hm.config.programs.nixvim... }; }`), since the outer `config` is the NixOS config, not the home-manager one.
|
||||
- **Verifying a nixvim change headless:** `programs.nixvim.build.package`'s wrapper has **no `-u`**, so running `$OUT/bin/nvim` loads the caller's `~/.config/nvim` (the dev host's real config), *not* the built config — silently. To exercise the built config, launch with `-u "$(nix build --no-link --print-out-paths .#…programs.nixvim.build.initFile)"` and a scratch `HOME`/`XDG_CONFIG_HOME`. `conceallevel` is window-local: set it with `opt_local`/`vim.wo`, never `vim.bo[buf]` (which errors).
|
||||
|
||||
62
flake.lock
generated
62
flake.lock
generated
@@ -41,6 +41,27 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"nixvim",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1782949081,
|
||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-schemas": {
|
||||
"locked": {
|
||||
"lastModified": 1780327564,
|
||||
@@ -160,6 +181,28 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixvim": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784057377,
|
||||
"narHash": "sha256-yycNej5//EsRbV10moBoh+/63vXEwZD1ZFEiRm6C9rQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "07180a087e4a00720dc0731cbcd8dec796974381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"chaotic": "chaotic",
|
||||
@@ -167,7 +210,24 @@
|
||||
"home-manager": "home-manager_2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"nixvim": "nixvim"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1774449309,
|
||||
"narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "c29398b59d2048c4ab79345812849c9bd15e9150",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"ref": "future-26.11",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# Neovim configured declaratively in Nix. Must follow our nixpkgs so its
|
||||
# plugins build against the same package set.
|
||||
nixvim = {
|
||||
url = "github:nix-community/nixvim";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
# Declarative disk partitioning. Each Host declares its own layout; a Host
|
||||
# that preserves an existing pool simply declares none.
|
||||
disko = {
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
# tmux as the terminal multiplexer.
|
||||
modules.tmux.enable = true;
|
||||
|
||||
# Neovim, configured declaratively via nixvim.
|
||||
modules.nvim.enable = true;
|
||||
|
||||
# Locale preferences for the base system.
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_GB.UTF-8";
|
||||
|
||||
27
modules/nvim/config.lua
Normal file
27
modules/nvim/config.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
-- Imperative configuration that has no typed nixvim option: the colorscheme
|
||||
-- call and two autocmds. Everything expressible as Nix lives in ./nvim.nix.
|
||||
|
||||
-- gbprod/nord.nvim, provided as an extra plugin from nixpkgs.
|
||||
require("nord").setup({
|
||||
transparent = true,
|
||||
})
|
||||
vim.cmd.colorscheme("nord")
|
||||
|
||||
-- Conceal markdown syntax in markdown buffers (previously an after/ftplugin).
|
||||
-- conceallevel is window-local, so it is set with opt_local when the filetype
|
||||
-- is applied to the buffer's window.
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = "markdown",
|
||||
callback = function()
|
||||
vim.opt_local.conceallevel = 2
|
||||
end,
|
||||
})
|
||||
|
||||
-- Turn line blame off again once the Neogit status buffer is closed.
|
||||
vim.api.nvim_create_autocmd("BufUnload", {
|
||||
callback = function(args)
|
||||
if vim.bo[args.buf].filetype == "NeogitStatus" then
|
||||
require("gitsigns").toggle_current_line_blame(false)
|
||||
end
|
||||
end,
|
||||
})
|
||||
191
modules/nvim/nvim.nix
Normal file
191
modules/nvim/nvim.nix
Normal file
@@ -0,0 +1,191 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
# Neovim for the primary user, configured declaratively through nixvim. Options,
|
||||
# keymaps, and plugin settings are typed Nix; the imperative remainder (the
|
||||
# colorscheme call and the Neogit blame-toggle autocmd) lives in ./config.lua.
|
||||
# Plugins come from nixpkgs — no plugin manager and no runtime cloning — and
|
||||
# treesitter grammars are built by Nix, so no compiler is needed at runtime. git
|
||||
# backs the git plugins; ripgrep and fd back the picker.
|
||||
let
|
||||
cfg = config.modules.nvim;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.nvim.enable = lib.mkEnableOption "Neovim, configured declaratively via nixvim";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.sharedModules = [ inputs.nixvim.homeModules.nixvim ];
|
||||
|
||||
home-manager.users.${user} = hm: {
|
||||
programs.nixvim = {
|
||||
enable = true;
|
||||
|
||||
extraPackages = with pkgs; [
|
||||
git # neogit and gitsigns shell out to git
|
||||
ripgrep # backs the picker's live grep
|
||||
fd # backs the picker's file finder
|
||||
];
|
||||
|
||||
globals.mapleader = " ";
|
||||
globals.clipboard = "osc52"; # neovim's built-in OSC 52 provider, no external binary needed
|
||||
|
||||
opts = {
|
||||
clipboard = "unnamedplus";
|
||||
|
||||
number = true;
|
||||
relativenumber = true;
|
||||
|
||||
shiftwidth = 2;
|
||||
tabstop = 2;
|
||||
expandtab = true;
|
||||
|
||||
mouse = "a";
|
||||
|
||||
undofile = true;
|
||||
|
||||
ignorecase = true;
|
||||
smartcase = true;
|
||||
|
||||
splitright = true;
|
||||
splitbelow = true;
|
||||
|
||||
wrap = false;
|
||||
|
||||
scrolloff = 8;
|
||||
cursorline = true;
|
||||
};
|
||||
|
||||
keymaps = [
|
||||
{
|
||||
mode = "n";
|
||||
key = "<C-h>";
|
||||
action = "<C-w>h";
|
||||
options.desc = "Move focus left";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<C-j>";
|
||||
action = "<C-w>j";
|
||||
options.desc = "Move focus down";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<C-k>";
|
||||
action = "<C-w>k";
|
||||
options.desc = "Move focus up";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<C-l>";
|
||||
action = "<C-w>l";
|
||||
options.desc = "Move focus right";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<Esc>";
|
||||
action = "<cmd>nohlsearch<CR>";
|
||||
options.desc = "Clear search highlight";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<leader>e";
|
||||
action = "<cmd>Oil<CR>";
|
||||
options.desc = "Open file browser";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<leader>f";
|
||||
action.__raw = "function() require('snacks').picker.files() end";
|
||||
options.desc = "Find files";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<leader>s";
|
||||
action.__raw = "function() require('snacks').picker.grep() end";
|
||||
options.desc = "Search text";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<leader>b";
|
||||
action.__raw = "function() require('snacks').picker.buffers() end";
|
||||
options.desc = "Switch buffer";
|
||||
}
|
||||
{
|
||||
mode = "n";
|
||||
key = "<leader>g";
|
||||
action.__raw = ''
|
||||
function()
|
||||
require('gitsigns').toggle_current_line_blame(true)
|
||||
require('neogit').open()
|
||||
end
|
||||
'';
|
||||
options.desc = "Open git (Neogit)";
|
||||
}
|
||||
];
|
||||
|
||||
plugins = {
|
||||
gitsigns = {
|
||||
enable = true;
|
||||
settings.current_line_blame = false;
|
||||
};
|
||||
|
||||
neogit.enable = true;
|
||||
diffview.enable = true; # neogit's diff integration
|
||||
|
||||
oil = {
|
||||
enable = true;
|
||||
settings.view_options.show_hidden = true;
|
||||
};
|
||||
|
||||
snacks = {
|
||||
enable = true;
|
||||
settings = {
|
||||
picker.enabled = true;
|
||||
notifier.enabled = true;
|
||||
input.enabled = true;
|
||||
};
|
||||
};
|
||||
|
||||
which-key.enable = true;
|
||||
render-markdown.enable = true;
|
||||
|
||||
treesitter = {
|
||||
enable = true;
|
||||
highlight.enable = true;
|
||||
indent.enable = true;
|
||||
grammarPackages = with hm.config.programs.nixvim.plugins.treesitter.package.builtGrammars; [
|
||||
nix
|
||||
lua
|
||||
bash
|
||||
fish
|
||||
markdown
|
||||
markdown_inline
|
||||
rust
|
||||
python
|
||||
java
|
||||
kotlin
|
||||
c
|
||||
cpp
|
||||
html
|
||||
css
|
||||
javascript
|
||||
typescript
|
||||
go
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# gbprod/nord.nvim; nixvim's colorschemes.nord is a different plugin. Set
|
||||
# up in ./config.lua.
|
||||
extraPlugins = [ pkgs.vimPlugins.gbprod-nord ];
|
||||
|
||||
extraConfigLua = builtins.readFile ./config.lua;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user