- Lua 91.8%
- Nix 5.6%
- Vim Script 2.6%
| .github | ||
| .nvim/lsp | ||
| .vscode | ||
| .zed | ||
| colors | ||
| docs | ||
| lua | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| .luarc.json | ||
| .markdownlint.json | ||
| .mergify.yml | ||
| .nvim.lua | ||
| flake.lock | ||
| flake.nix | ||
| init.lua | ||
| nvim-pack-lock.json | ||
| package.nix | ||
| README.md | ||
| UPSTREAM.md | ||
Neovim, based on Spore
Our configuration now builds on spore/nvim-config. Editor configuration and plugin management are ordinary Lua, not Nixvim modules. Nix only packages Neovim, external executables, and native libraries.
Run
nix run .
nix build .
nix profile install .
The outputs packages.<system>.default and packages.<system>.neovim both
provide bin/nvim. Supported systems: x86_64-linux, aarch64-linux, aarch64-darwin.
The wrapper defaults to NVIM_APPNAME=nixvim, keeping existing Neovim
configuration and data separate. An explicitly set NVIM_APPNAME takes precedence.
The overlays.default output provides the configured wrapper as pkgs.neovim.
Add it to your NixOS or Home Manager configuration (with this flake named
nvim-config in your inputs):
nixpkgs.overlays = [ inputs.nvim-config.overlays.default ];
The Nix package bundles a fallback C/C++ compiler for Treesitter, overridable with
.override { cc = pkgs.clang; } (default: stdenv.cc). Project PATH tools take
precedence. Provide Cargo/Rust through your system or project PATH when installing
or updating Parinfer, e.g. nix shell nixpkgs#cargo nixpkgs#rustc -c nix run ..
Without Nix, clone this repository into your Neovim configuration directory and launch Neovim ≥ 0.12.1. Install Git, ripgrep, fd, fzf, curl, tar, gzip, unzip, a C compiler, tree-sitter CLI, Cargo/Rust, notmuch (including libnotmuch), file, and optionally lazygit and language servers. Linux clipboard support uses wl-clipboard; macOS uses the system pbcopy/pbpaste.
First startup and plugin updates
Plugins use Neovim's native vim.pack. First startup requires network access
and writes plugins, parser builds, and Parinfer into the user's data directory.
Wait for installation/build messages to finish before quitting. Restart after the
initial builds if a parser or Parinfer was not available when its buffer opened.
Nix builds do not fetch or build these plugins: this is intentionally not a fully
hermetic editor closure. Blink uses its Lua fuzzy matcher to avoid downloading a
prebuilt shared library that cannot run on NixOS.
The repository's nvim-pack-lock.json pins the initial plugin revisions. When
running the Nix package, it is copied into the profile's writable config directory
only if absent, never over an existing lockfile. Default XDG locations are:
- Config/lockfile:
~/.config/nixvim/nvim-pack-lock.json - Plugins/parsers:
~/.local/share/nixvim/ - Cache/state:
~/.cache/nixvim/,~/.local/state/nvim-spore/
Update plugins with :lua vim.pack.update() and review/confirm the changes.
To publish tested updates from the packaged profile, copy its lockfile back to
this checkout and commit it. To adopt the repository lockfile in an existing
profile, back up its current lockfile, copy the repository file over it, restart,
and run :lua vim.pack.update(nil, { target = 'lockfile' }); review and confirm.
nix flake update updates Neovim/tools, not plugins.
Input-method switching
im-select.nvim switches to the
platform's default English input method on leaving Insert or Command-line mode
and restores the previous input method on entering Insert mode.
Install and configure your input method separately, with its executable on PATH:
fcitx5-remote, ibus, or fcitx-remote on Linux; macism on macOS;
im-select.exe on Windows/WSL. These tools are not bundled by the Nix wrapper.
Linux without a supported executable skips setup.
Local email (notmuch.nvim)
yousefakbar/notmuch.nvim v0.5.0 is installed by vim.pack but loaded only when
opening mail. Normal startup does not inspect mail, index it, or contact a server.
Nix supplies notmuch, file, and the libnotmuch search path; without Nix,
libnotmuch must be visible to LuaJIT's dynamic loader.
The Home Manager mail configuration in ~/nixos-config indexes ~/Mail/fastmail
and ~/Mail/outlook together. Apply that configuration first, sync the desired
account manually, then run notmuch new or :MailIndex. The default configuration
is $XDG_CONFIG_HOME/notmuch/default/config (~/.config if XDG is unset);
NOTMUCH_CONFIG overrides it.
:Mail/<leader>mm: dashboard with account-specific saved queries.<leader>mi,<leader>mf,<leader>mo: combined, Fastmail, Outlook inboxes.:Mail <query>: arbitrary notmuch search; native:NmSearchbecomes available after first opening mail.:MailIndexand the plugin's%: refresh local index only, not IMAP.
Tags/read state are local. Home Manager disables flag synchronization, and
personal/mail.lua guards the plugin's otherwise unconditional native flag
exports. Sending and permanent file purging are blocked; continue using NeoMutt
for outgoing mail and cryptography, preserving Fastmail's signing policy.
Drafts and received-attachment cache live under the private Mail root in
.notmuch-drafts and .notmuch-attachments, excluded from indexing.
tests/mail.lua checks the real plugin and libnotmuch against synthetic messages;
run it through nixos-config/assets/scripts/notmuch-maildir-check.py as documented
in nixos-config/docs/notmuch-nvim.md. Recheck this integration before changing
the plugin pin.
Layout and upstream updates
lua/config/,lua/plugins.lua,lua/plugins/,lua/utils.lua,colors/: integrated runtime files. Imported files carry the full upstream MIT license and source revision in leading comments; see UPSTREAM.md.init.lua: bootstrap using upstream's module order, with errors surfaced rather than swallowed. Loads a single configuration runtime directory.lua/config/{completion,language}.lua: our own implementations of those modules; no duplicate completion/LSP setup.lua/personal/: our options, additional plugins, UI overrides, and keybindings.package.nix: Neovim wrapper and tool dependencies only.flake.nix: package outputs, formatting, checks, development shell.
The imported Lua is maintained in-place alongside our own configuration, with
normal formatting and linting. For an upstream refresh, review changes against
the revision recorded in UPSTREAM.md and each file's header, port them without
overwriting local changes or license notices, and test startup. Fennel sources,
duplicate bootstrap/lockfile, and overridden upstream modules are not included.
Our root bootstrap selects the modules explicitly; no Fennel compilation is needed.
Migration from Nixvim
Retained: two-space indentation, relative numbers, clipboard, session/fold
options, Catppuccin, Bufferline, virtual columns, Oil behavior, Blink's Tab and
signature bindings, ripgrep/LaTeX completion sources, LuaSnip/friendly-snippets,
Gitsigns, lazygit, Flash, Trouble, TODO comments, Neogen, comment-divider, and Suda.
Completion remains disabled in Lua and Markdown; vim.b.completion = false
disables it elsewhere (an unset variable now correctly allows completion).
VSCode skips plugin/UI/LSP startup and only loads editor options.
Intentional upstream substitutions:
| Former implementation | Replacement |
|---|---|
| Telescope | fzf-lua; <leader>/ and <leader><space> retain their functions |
| nvim-ufo | native Treesitter/LSP folds, with indent fallback; zR/zM still work |
| which-key | mini.clue |
| Comment.nvim / nvim-autopairs | mini.comment / nvim-insx |
| lualine / dressing / noice / notify | upstream mini.statusline, fzf UI select, native UI2 |
| nvim-config-local | native exrc and Neovim's trust mechanism (:trust) |
| markdown-preview | upstream render-markdown (in-editor rendering, not a browser preview) |
Buffers over 10,000 lines use indent folds; Treesitter is skipped over 1 MiB.
<leader>n (Noice dismiss) is retired with Noice. <leader>td uses inline hunk
preview because current Gitsigns removed toggle_deleted. Flash owns s/S;
upstream surround remains available on ys, yss, ds, cs, and visual gs.
Our buffer/Git prefix bindings replace upstream's immediate <leader>b/<leader>g
actions. Upstream's other file, language, terminal, REPL and debug mappings remain.
Language servers
Enabled: asm_lsp, ast_grep, bashls, lua_ls, neocmake, yamlls, dockerls, docker_compose_language_service, markdown_oxide, statix, hls, nixd, ruff, basedpyright, tinymist, agda_ls, and rocq_lsp. Upstream's different language-server selection is not enabled on top of ours.
As before, haskell-language-server, nixd, ruff, basedpyright, and tinymist must come from your system/project environment. Nix supplies fallback binaries for the other servers. The wrapper appends tools to PATH, so project tools win. There is no Mason or automatic language-server installation.
Rocq and Agda also use project-provided servers: put coq-lsp (the Rocq LSP
executable) and als (Agda Language Server)
on PATH, with versions matching your project's prover. Rocq roots are detected
from _RocqProject, _CoqProject, or .git; Agda uses *.agda-lib or .git.
Agda files (.agda, .lagda) get the agda filetype. Neovim detects Rocq .v
files from their contents; use :setfiletype coq for an empty or ambiguous file.
These integrations provide standard LSP features, not a proof-goal panel or
Agda-specific interaction commands.
LaTeX
VimTeX provides LaTeX editing, navigation,
compilation, and PDF viewing. It is pinned to v2.17 for Neovim 0.12.1 compatibility.
Compiler and PDF viewer executables come from your system/project environment;
VimTeX defaults to latexmk and its platform-default viewer.
For a project using Tectonic, add a trusted .nvim.lua at its root:
vim.g.vimtex_compiler_method = "tectonic"
vim.g.vimtex_compiler_tectonic = { out_dir = "build" }
Open Neovim from that directory and approve the native exrc trust prompt after
reviewing the file. These globals apply to the editor session; use a separate
session for projects needing another compiler. Tectonic is single-shot: run
:VimtexCompile (\\ll) after editing, :VimtexView (\\lv) to view the PDF,
and :VimtexInfo (\\li) to inspect the setup. No continuous watcher is added.
Searchable cheatsheets
Press <leader>? (Space then ?) or run :Cheatsheet to open
cheatsheet.nvim. Search by key,
description, section, or tag. config/n, config/x, etc. contain our final
startup global mappings and their mode; upstream mappings are included after
our overrides. Buffer-local and mappings registered later are not captured.
The picker also includes bundled Vim, Lua, Markdown, regex, Unicode and
installed-plugin reference sheets. Those bundled examples are reference material,
not necessarily bindings enabled in this configuration. <CR> displays a hint,
<C-y> copies the cheat to register 0, and <C-e> edits your personal sheet.
<A-CR> executes the selected cheat; use ordinary <CR> for a non-executing hint.
:CheatsheetEdit edits the writable cheatsheet.txt in your profile's config
directory (~/.config/nixvim/ by default), not the Nix store. Its format is
Description | keys or command, with optional ## Section @tag headings.
Telescope is included for this plugin; file/search bindings continue using
fzf-lua, and mini.clue remains the key-hint UI. Use :Cheatsheet without a bang;
the plugin's legacy floating-window fallback is not used.
Development and checks
direnv allow
nix fmt
nix flake check
The sandbox-safe check validates Lua syntax and local settings, keybindings, completion/LSP contracts, and lockfile entries without downloading plugins. It is not a substitute for a real startup check. To exercise real plugins with an isolated profile (network required on first run):
profile=$(mktemp -d)
export XDG_CONFIG_HOME="$profile/config" XDG_DATA_HOME="$profile/data"
export XDG_STATE_HOME="$profile/state" XDG_CACHE_HOME="$profile/cache"
export NVIM_APPNAME=nixvim
nix run . -- --headless '+luafile tests/startup.lua'
# Run again to check warm startup after initial native builds finish.
nix run . -- --headless '+luafile tests/startup.lua'
For interactive checks, run nix run . with the same environment, inspect
:checkhealth, open files, and exercise completion, folding, clipboard and pickers.
Delete the temporary profile when finished. All integrated runtime files participate
in the normal formatting/lint hooks.