Personal Neovim configuration
  • Lua 91.8%
  • Nix 5.6%
  • Vim Script 2.6%
Find a file
2026-10-20 11:27:16 +08:00
.github feat: init 2026-09-15 12:53:21 +08:00
.nvim/lsp feat: init 2026-09-15 12:53:21 +08:00
.vscode feat: init 2026-09-15 12:53:21 +08:00
.zed feat: init 2026-09-15 12:53:21 +08:00
colors feat: init 2026-09-15 12:53:21 +08:00
docs feat: mail 2026-10-17 19:42:59 +08:00
lua feat(im-select): init 2026-10-20 11:24:19 +08:00
tests feat: mail 2026-10-17 19:42:59 +08:00
.envrc feat: use neovim-nightly-overlay 2026-02-03 07:07:56 +08:00
.gitignore chore!: remove neo-tree 2025-11-13 00:56:00 +08:00
.luarc.json feat: init 2026-09-15 12:53:21 +08:00
.markdownlint.json fix: remove unused lsp, switch from prettier to markdownlint 2025-06-05 01:32:37 +08:00
.mergify.yml ci(Mergify): configuration update 2025-06-04 19:39:49 +08:00
.nvim.lua refacotr: full refactor 2025-07-29 19:44:26 +08:00
flake.lock feat: init 2026-09-15 12:53:21 +08:00
flake.nix feat: create overlay 2026-10-20 11:27:16 +08:00
init.lua feat: mail 2026-10-17 19:42:59 +08:00
nvim-pack-lock.json feat(im-select): init 2026-10-20 11:24:19 +08:00
package.nix chore: rebrand neutrally 2026-10-20 11:22:53 +08:00
README.md feat: create overlay 2026-10-20 11:27:16 +08:00
UPSTREAM.md feat: init 2026-09-15 12:53:21 +08:00

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 :NmSearch becomes available after first opening mail.
  • :MailIndex and 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.