Architecture¶
Flake structure¶
nvix/
├── flake.nix # Entry point - flake-parts + nixvim
├── packages/default.nix # Three variants: bare, core, full
├── plugins/ # All plugin modules (auto-discovered)
│ ├── default.nix # Auto-exporter: *.nix → flake.nvixPlugins.*
│ ├── common/ # Core settings, options, keymaps, icons, colorscheme
│ ├── ai/ # Copilot + ChatGPT
│ ├── lang/ # Language-specific LSP + formatters
│ ├── lsp/ # LSP config, conform, mappings
│ ├── lualine/ # Statusline
│ ├── snacks/ # Snacks.nvim sub-modules
│ ├── blink-cmp.nix # Completion
│ ├── buffer.nix # Bufferline + harpoon
│ ├── git.nix # Gitsigns + diffview + git-conflict
│ ├── treesitter.nix # Treesitter + context + mini.ai
│ ├── noice.nix # UI replacement
│ ├── ux.nix # Colorizer, dressing, fidget, windows
│ ├── autosession.nix # Session persistence
│ ├── firenvim.nix # Browser integration
│ ├── leetcode.nix # LeetCode solver
│ └── tex.nix # LaTeX (vimtex + texlab + texpresso)
├── overlays/default.nix # nixpkgs overlays (kulala-core fix)
├── modules/flake/ # Flake-level: pkgs wiring, devshell
└── .github/workflows/ # CI: build + format check, docs deploy, flake update
Auto-discovery¶
plugins/default.nix scans plugins/ and exports every .nix file and
subdirectory as flake.nvixPlugins.<name>:
# plugins/default.nix (simplified)
forAllNixFiles ./. (fn: fn)
# → { common = ./common; git = ./git.nix; lsp = ./lsp; ... }
This means adding a new plugin is just creating a file - no registration needed.
Package assembly¶
packages/default.nix defines three module lists and passes each to
makeNixvimWithModule:
mkNixvim = module:
inputs'.nixvim.legacyPackages.makeNixvimWithModule {
inherit pkgs;
extraSpecialArgs = { inherit (flake) inputs self; };
inherit module;
};
Each module is a list of self.nvixPlugins.* - the auto-discovered
plugin files. Nixvim merges them and produces a wrapped nvim binary.
Shared options¶
plugins/common/options.nix defines custom options consumed across
modules:
| Option | Type | Default | Used by |
|---|---|---|---|
nvix.leader |
string | " " |
common/default.nix → globals.mapleader |
nvix.border |
enum | "rounded" |
LSP floats, lspsaga, diagnostic, dressing |
nvix.transparent |
bool | true |
kanagawa, tokyonight colorschemes |
nvix.icons |
attrs | {} |
git signs, diagnostic signs, lualine |
nvix.mkKey |
attrs | functions | All modules - mkKeymap, wKeyObj |
wKeyList |
list | [] |
which-key spec (accumulated across modules) |
Keymap helpers¶
plugins/common/functions.nix provides:
mkKeymap mode key action desc- standard keymap withsilent + noremapmkKeymapWithOpts mode key action desc opts- keymap with custom optionswKeyObj [key icon group]- which-key spec entry
These are available as config.nvix.mkKey.* in every module.
LSP strategy¶
plugins/lsp/default.nix auto-enables all lspconfig servers with
package = null - meaning LSP binaries are picked from the system PATH
or devshell, not installed by nvix. This avoids shipping every language
server in the nvim binary closure.
Exceptions:
rust_analyzer- disabled (handled byrustaceanvim)pylsp- enabled withoutpackage = null(nixvim builds custom derivation)vue_ls/volar- TS integrations disabled (assertpackage != null)
Live file watching¶
plugins/common/default.nix watches every open normal file with a libuv
fs_event and runs :checktime on change, so external edits (git,
formatters, AI edit tools) show up in the buffer without refocusing.
Buffers with unsaved changes are never touched. Atomic writers replace
files via rename, which orphans the watch - the watcher re-arms itself
on rename events to keep tracking the file at that path. autoread plus
a FocusGained → checktime autocmd act as fallback on focus return.
Overlays¶
overlays/default.nix patches kulala-core to fix a hardcoded WASM
path that breaks in Nix. Applied via flake-parts perSystem.
Devshell¶
modules/flake/devshell.nix provides:
just,nil(LSP),nixd(LSP),nix-output-monitor- Pre-commit hook:
treefmt(usesnixfmt) on all.nixfiles