This is a repo for my personal nvim config, that is open for easy cloning in my nix config. No guarantees, on anything here actually working, so use at your own risk. I am building my personal config instead of using an established nvim distro such as lunar for the fun of it, and to learn the inner workings better. I highly recommend using an established nvim distro for everybody else.
A deliberately small, general-purpose Neovim setup that provides the parts that every project needs: editor settings, a file tree, fuzzy finding, completion, treesitter, and a handful of language servers for editing the config itself. Anything project-specific (extra language servers, formatters, per-project options) is layered on per project rather than baked in here.
Requires Neovim ≥ 0.12. Plugins are managed by lazy.nvim, which bootstraps itself on first launch.
init.lua entry point: options → lazy → keymaps
lua/config/
options.lua editor options (tabs, clipboard, exrc, …)
lazy.lua lazy.nvim bootstrap + plugin import
keymaps.lua global keymaps
lua/plugins/ one file per plugin (auto-imported by lazy)
flake.nix exposes the home-manager module + a `nix run`-able package
module.nix home-manager module: installs the tools below
packages.nix the actual tool list (language servers, build deps, direnv)
This repo is two things at once: the Neovim config and a flake that installs the external tools the config expects (language servers, a C compiler for treesitter, direnv).
-
Tools — import the home-manager module so the tooling lands on
PATH:# flake.nix inputs nvim-config.url = "path:/home/andre/82_nvim"; # or your git remote # home-manager modules imports = [ nvim-config.homeManagerModules.default ];
The package set lives in
packages.nix; anixos-rebuild switch(orhome-manager switch) makes it available. You can also try it ad-hoc withnix run/nix shellagainst this flake. -
Config — point Neovim at this repo's config (e.g. via
xdg.configFile."nvim".sourcein home-manager, or a plain checkout in~/.config/nvim).
| Area | Plugin |
|---|---|
| Plugin manager | folke/lazy.nvim |
| Colorscheme | rebelot/kanagawa.nvim |
| File tree | nvim-tree/nvim-tree.lua |
| Fuzzy find | nvim-telescope/telescope.nvim |
| Completion | hrsh7th/nvim-cmp |
| Syntax | nvim-treesitter/nvim-treesitter |
| LSP | neovim/nvim-lspconfig (native vim.lsp.config API) |
| Terminal | akinsho/toggleterm.nvim |
| Keymap hints | folke/which-key.nvim |
| Markdown | OXY2DEV/markview.nvim |
| Devshell env | direnv/direnv.vim |
Bundled language servers (for editing this config and basic scripting): lua_ls, nil (Nix), bashls, and basedpyright (Python). LSP is configured through Neovim's native vim.lsp.config() / vim.lsp.enable() API — nvim-lspconfig is kept only for the per-server defaults it ships.
The whole point of this setup: open nvim anywhere and let it adapt to the project, without starting it from inside a devshell or maintaining a forked config. There are two independent mechanisms.
When a project needs its own binaries on PATH: a language server not bundled here (rust-analyzer, gopls, clangd, …), a specific Python interpreter, formatters, etc. the idiomatic Nix answer is direnv driving the project's flake.nix devshell.
One-time setup (in home-manager):
programs.direnv = {
enable = true;
nix-direnv.enable = true; # fast, cached `use flake`
};This enables the shell hook and the use flake helper. The direnv binary itself is already provided by this repo's package set, and the direnv.vim plugin is included so the environment is pulled into a running nvim too.
Per project: add an .envrc at the project root:
# .envrc
use flakethen direnv allow once. Now:
- A shell that
cds into the project already has the devshell onPATH, sonvimlaunched from there sees the project's tools immediately. direnv.vimre-exports the environment into the running nvim on directory change (:cd/:tcd, which fireDirChanged). Merely opening a file in another subproject does not move the cwd, so it does not re-export. For that, register the server through the per-LSP helper below.
Note: the editor environment is process-global, so
direnv.vimis "last directory wins" and a server that's already running won't retroactively get a new environment (run:LspRestartafter a change). It remains the right tool for cwd-based consumers —:terminal,:!, and the like.
To run a language server against its buffer's nearest .envrc (independently per buffer, with no global cwd movement and without the binary being on nvim's own PATH) register it through config.direnv_lsp. It starts the server via direnv exec <nearest-.envrc-root> <binary>, so files under different .envrcs get separate clients with separate environments at the same time:
-- e.g. in a project's .nvim.lua (or the base config for a cross-project server)
require("config.direnv_lsp").server("texlab", "texlab", {
settings = { texlab = { build = { onSave = true } } },
})The server only starts in buffers that have an .envrc ancestor (so an always-registered server stays inert elsewhere), and reuses one client per project tree. direnv exec execve-replaces itself with the server, so :LspRestart and quitting clean up without leaving orphaned processes. If the .envrc exists but hasn't been direnv allowed, the server won't start and :LspLog shows direnv: error … is blocked — run direnv allow to fix it.
When a project needs its own Neovim settings or LSP registration rather than (or in addition to) extra binaries. Neovim's built-in exrc (enabled in options.lua) sources a .nvim.lua from the directory nvim starts in.
Create .nvim.lua at the project root, for example to register a server whose binary direnv puts on PATH:
-- .nvim.lua — project-local, layered on top of the base config
vim.lsp.config("rust_analyzer", {
settings = {
["rust-analyzer"] = { check = { command = "clippy" } },
},
})
vim.lsp.enable("rust_analyzer")
-- project-local options are fine here too
vim.opt_local.shiftwidth = 2The first time nvim encounters a given .nvim.lua it asks whether to trust it (via vim.secure) and remembers the file's hash, so an untrusted file in someone else's repo can't silently run code. Re-trust after edits with:trust.
Scope & limits: exrc runs once, at startup, from the launch directory. It's ideal for settings and LSP registration, not for adding brand-new lazy.nvim plugins at runtime.
The Python setup auto-discovers a venv (.venv, venv, .direnv/python*) walking up from the file, falling back to PATH. Two commands help when it guesses wrong:
:PythonSelect— pick a venv, aflake.nixdevshell, the system python, or a custom path; optionally persists the choice topyrightconfig.json.:PythonDebug— dump what the discovery logic and the running basedpyright client actually resolved, into a scratch buffer.