My personal Neovim Nix-Flake
  • Nix 53.3%
  • Lua 46.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-01 17:45:16 +02:00
config plug 2026-09-01 17:45:16 +02:00
.gitignore stuff 2026-08-25 17:33:28 +02:00
alejandra.toml init 2026-02-26 15:21:31 +01:00
flake.lock a 2026-07-18 11:13:47 +02:00
flake.nix pkg 2026-08-26 14:33:14 +02:00
README.md plug 2026-09-01 17:45:16 +02:00

vif

A self-contained Neovim distribution built with Nixvim.

The whole editor — options, keymaps, plugins, language servers, formatters, treesitter grammars and the colorscheme — is declared in Nix and evaluated into a single nvim binary with everything on its own PATH. Nothing is installed into ~/.config/nvim, nothing is fetched at runtime, and no plugin manager is involved.

Design bias: stay close to stock Neovim. Built-in LSP, built-in completion (vim.lsp.completion), built-in statusline, built-in quickfix. Plugins are only added where Neovim has no answer at all.

Quick start

# from a local checkout
nix run .

# straight from the remote, without installing anything
nix run 'git+ssh://forgejo@git.cumsek.com:2222/Marti/vif.git'

# build a result symlink
nix build .
./result/bin/nvim

Requires Nix with flakes enabled (experimental-features = nix-command flakes).

Outputs

Output What it is
packages.default / packages.nvim The full config (~1.6 GiB closure — LSP servers and formatters dominate)
packages.minimal Editor-only build: options, keymaps, autocmds, fzf-lua, oil. No LSP, no formatters, no treesitter (~120 MiB)
checks.default / checks.minimal Nixvim test derivations — start the built editor headless and fail on any error

Systems: x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin.

minimal exists for remote boxes, containers and rescue shells where you want your muscle memory but not a gigabyte of toolchain.

Using it from another flake

{
  inputs.vif.url = "git+ssh://forgejo@git.cumsek.com:2222/Marti/vif.git";

  # NixOS
  environment.systemPackages = [inputs.vif.packages.${pkgs.system}.default];

  # or home-manager
  home.packages = [inputs.vif.packages.${pkgs.system}.default];
}

Both variants install a binary called nvim, so pick one per profile.

Layout

flake.nix                    outputs: full + minimal packages and their checks
config/
  default.nix               full config — imports everything below
  minimal.nix               editor-only subset
  options.nix               vim.opt/vim.g, providers, diagnostics, colorscheme
  remaps.nix                global keymaps, LSP keymaps, :CToggle
  autocmd.nix               yank highlight
  lsp.nix                   language servers + LspAttach completion wiring
  ansi.lua                  the "ansi" colorscheme (16-color, cterm only)
  plugins/
    default.nix             plugin imports
    auto-session.nix        session save/restore
    conform.nix             format-on-save
    fzf-lua.nix             pickers
    oil.nix                 file manager
    render-markdown.nix     in-buffer markdown rendering
    treesitter.nix          highlighting + indent, explicit grammar list

Every file is a Nixvim module, so anything in the Nixvim option reference can be dropped straight in.

Keymaps

Leader is <Space>.

Pickers and navigation

Key Action
<leader>pf fzf-lua files
<leader>pg fzf-lua live grep
<leader>pb fzf-lua buffers
<leader>ph fzf-lua help tags
<leader>pv oil (project view)
<C-q> inside fzf: select all + accept (sends results to quickfix)
<leader>m toggle in-buffer markdown rendering

Quickfix and diagnostics

Key Action
<leader>o toggle the quickfix window (:CToggle)
<C-j> / <C-k> next / previous quickfix item
<leader>pe project errors — all diagnostics into the quickfix list
<leader>e show the diagnostic under the cursor in a float

LSP (buffer-local, set on attach)

Key Action
K hover
gd definition
gr references
gi implementation
gt type definition
<leader>ca code action
<leader>rn rename
<C-space> (insert) trigger completion

Neovim's stock gr* LSP maps (gra, gri, grn, grr, grt, grx) are deleted so gr above is not shadowed by a pending-key timeout.

Editing

Key Action
<leader>P paste over selection without clobbering the register (capital, so the <leader>p… picker prefix doesn't make it wait out timeoutlen)
<leader>d delete into the black hole register
<C-d> / <C-u> half-page down / up, cursor centered
n / N next / previous search hit, centered and folds opened
<Esc> clear search highlight

Editor behaviour worth knowing

  • Tabs, width 4. shiftwidth = 0 so indent follows tabstop. list is on with tab and leading-space markers, so indentation is always visible.
  • No line numbers, no sign column icons, colorcolumn=80, cursorline, 10-line scrolloff, folds disabled.
  • Completion is Neovim's own. LspAttach calls vim.lsp.completion.enable() for any server that advertises textDocument/completion; completeopt is set to menu,menuone,popup,fuzzy,noinsert. There is no cmp/blink.
  • Diagnostics: virtual text and underline on, no sign column, sorted by severity.
  • Clipboard is unnamedplus. waylandSupport = false deliberately — the bundled wl-clipboard drags xdg-utils → perl/libwww into the closure (~200 MB), so vif relies on the host providing wl-copy/wl-paste. On a machine without them, yanks stay in Neovim's registers.
  • No Ruby or Python providers (withRuby/withPython3 are off). No remote plugins are used, so the provider hosts are dead weight.
  • Lua is byte-compiled at build time (performance.byteCompileLua).
  • Sessions are handled by auto-session, so Session.vim is gitignored.

Colorscheme

config/ansi.lua is a hand-written 16-color scheme that sets ctermfg/ctermbg only and forces termguicolors = false. Colors therefore come from your terminal's palette — vif looks like whatever theme your terminal is wearing. Syntax is deliberately flattened to nine base groups (Function, Keyword, String, Number, Type, Delimiter, Operator, Comment, Identifier) with every treesitter capture linked into one of them.

Language support

Language LSP Formatter
Bash / sh bashls shfmt
CSS / SCSS / Less cssls (unknown at-rules ignored, for Tailwind) prettier
Go gopls gofmt
HTML html prettier
JSON / JSONC jsonls prettier
Lua emmylua_ls stylua
Nix nil alejandra
Python pyright ruff format
SQL sqls sleek
Tailwind tailwindcss
Typst tinymist typstyle
TS / JS / TSX / JSX vtsls (prefers go-to-source-definition) prettier
XML / SVG xmllint --format (tab indent via XMLLINT_INDENT)
YAML yamlls

Formatters run on save via conform (lsp_format = "fallback", 1s timeout) and are installed automatically through conform's autoInstall, so they are on the editor's PATH without polluting yours.

nil is used instead of nixd on purpose: nixd statically links LLVM and adds roughly 600 MB to the closure, and alejandra covers formatting anyway.

zsh has no formatter on purpose either. shfmt cannot parse zsh-only syntax and fails outright, and the one formatter that copes (beautysh) pulls a full CPython into the closure for indent-only reformatting. Saving a .zsh file is a no-op rather than an error.

XML and SVG go through xmllint rather than prettier because prettier has no XML parser and errors on every save.

Treesitter grammars are pinned explicitly in config/plugins/treesitter.nix rather than pulled with allGrammars; add a language there when you add a server.

Development

nix flake check        # build both variants and start them headless
nix build .#minimal    # just the small one
nix flake update       # bump nixpkgs / nixvim

Nix files are formatted with alejandra using tabs (alejandra.toml), wired up as the flake's formatter:

nix fmt              # format
nix fmt -- --check . # verify

config/ansi.lua is Lua and gets formatted by the editor itself (stylua).

Adding a plugin

  1. Create config/plugins/<name>.nix with plugins.<name>.enable = true; plus any settings and its keymaps.
  2. Add it to the imports list in config/plugins/default.nix.
  3. nix flake check.

Keep plugin-local keymaps in the plugin's own file — only editor-wide bindings live in config/remaps.nix.