- Nix 53.3%
- Lua 46.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| config | ||
| .gitignore | ||
| alejandra.toml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
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 = 0so indent followstabstop.listis 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.
LspAttachcallsvim.lsp.completion.enable()for any server that advertisestextDocument/completion;completeoptis set tomenu,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 = falsedeliberately — the bundledwl-clipboarddragsxdg-utils→ perl/libwww into the closure (~200 MB), so vif relies on the host providingwl-copy/wl-paste. On a machine without them, yanks stay in Neovim's registers. - No Ruby or Python providers (
withRuby/withPython3are 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.vimis 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
- Create
config/plugins/<name>.nixwithplugins.<name>.enable = true;plus anysettingsand its keymaps. - Add it to the
importslist inconfig/plugins/default.nix. nix flake check.
Keep plugin-local keymaps in the plugin's own file — only editor-wide bindings
live in config/remaps.nix.