Setting up Neovim on NixOS
Of all the pieces of software I had to set up again when I switched to NixOS, Neovim took me the longest. This kind of surprised me at first: given that the Venn Diagram of NixOS users and Neovim users is fairly close to a circle — the set of people who are most productive when fiddling with configuration — I thought it’d be a breeze to get Neovim going on NixOS. Unfortunately, all that seems to have happened is, perhaps predictably, the evolution of a dozen different ways to go about this.
Between options like NixVim and mnw, I was fairly confused about how to go forward.
I realise now that the actual problem was that I understood neither Neovim nor Nix well enough to put them together myself.
Luckily, I came across this Gist via Reddit that lays out i.m.o. the most straightforward method.
The main thing to understand is that Nix is the Only Package Manager You Need.
No lazy.nvim.
No mason.nvim.
So not only do you install Neovim via Nix, you also install all of the plugins you need via Nix as well.
After that, you can configure them in much the same way you would on other systems.
Beyond this point, it’s assumed that you have a working NixOS setup with home-manager installed. If you don’t yet know how to do this, I recommend reading this book, specifically the “NixOS with Flakes” section.
Installation
To start off with, we’re going to use the home-manager Neovim module. This is a wrapper, or a program/script that automatically sets the appropriate environment variables and command line options to make the target program function within Nix’s idiosyncratic environment.
# home.nix
programs.neovim = {
enable = true;
};
If you sudo nixos-rebuild switch right now, you’ll able to launch nvim from the command line, but it won’t be anything exciting yet.
Let’s add some plugins.
# home.nix
# inside programs.neovim
plugins = with pkgs.vimPlugins; [
nvim-lspconfig
nvim-treesitter.withAllGrammars
telescope-nvim
catppuccin-nvim
conform-nvim
nvim-autopairs
comment-nvim
lualine-nvim
blink-cmp
todo-comments-nvim
trouble-nvim
undotree
];
I have a fairly minimalistic setup, but you can search for the plugins you need with the NixOS package search.
We’re installing the available tree-sitter grammars, pre-compiled, because they don’t really take up a lot of space, and :TSInstall won’t work properly if we try it later on (thanks, NixOS).
Also, I expect to be able to open a file and get some syntax highlighting at least without having to open a nix shell (more on this later).
While we’re in home.nix, let’s also add some extraPackages for Neovim.
The extraPackages attribute contains packages that should be made available to Neovim when the program is launched, but aren’t available globally otherwise.
We can use this to install formatters and language servers for commonly-used languages that we want available everywhere.
# home.nix
# inside programs.neovim
extraPackages = with pkgs; [
# formatters
ruff
stylua
alejandra
# language servers
pyright
lua-language-server
nil
];
For other packages, we’ll install them on a per-project basis, using flakes.
Configuration
We’re going to need a config file for Neovim to initialise some of the plugins we installed.
Generally, we would put an init.lua in .config/nvim; you can still do this, but you can also nix-ify your config and track it through home-manager.
# home.nix
xdg.configFile."nvim/init.lua" = {
enable = true;
source = ./dotfiles/nvim/init.lua;
};
Add that at the top-level of your output attribute set, and it will symlink .config/nvim/init.lua to dotfiles/nvim/init.lua, where dotfiles is a directory inside your nix config directory.
The main disadvantage of this is that you need to rebuild for your neovim config changes to take effect.
This absolutely hobbles your iteration speed, so I’d advise against it if you’re still tweaking your setup; once you’ve settled on a good setup, then it doesn’t matter at all.
I don’t think I’ve changed my neovim config in months.
I went with a single file configuration this time because I didn’t have a lot of customisation anyway, but you should just as easily be able to use a modular version.
Essentially, you should be able to just copy and paste your config functions from you lazy.nvim setup into your init.lua and it’ll probably work.
At least, it did for me but YMMV.
If you sudo nixos-rebuild switch now, you’ll have a minimal but fairly handy Neovim dev environment all set up!
Dev environments
The NixOS and Flakes book describes how to use flakes and dev shells to set up self-contained dev environments. I recommend adding direnv, or rather, nix-direnv on top for ease of use. But I digress, this post is about neovim.
Back when I was installing additionalPackages for neovim, I only installed the LSPs and formatters I thought I’d need most often.
The rest I install inside my dev shells, but I still need to tell neovim to use the packages available to it.
Editing init.lua each time clearly isn’t a good solution.
If only we had something like a flake for neovim, something that would let us layer extra, project-specific config on top of our main setup…
And we do!
Adding the following two lines to your init.lua lets you include .nvim.lua files in your projects that apply extra options to the editor.
exrc enables this behaviour, and secure makes neovim ask for confirmation before it loads a .nvim.lua file.
-- init.lua
vim.o.exrc = true
vim.o.secure = true
Here’s an example .nvim.lua from the Astro project for this site:
-- .nvim.lua
vim.lsp.enable("astro")
vim.lsp.enable("ts_ls")
vim.lsp.enable("cssls")
require("conform").formatters_by_ft.astro = { "prettierd" }
require("conform").formatters_by_ft.typescript = { "prettierd" }
Thoughts
After a few months using this as my main setup, I’m really happy with it. Nix has really simplified the way I manage my different projects. Also, I feel like I’ve understood a bit more about both Nix and Neovim through getting all of this up and running. At this point, my setup has settled into a comfortable local minima. I could probably improve my flow further, but I’m happy enough with it that I don’t really feel the urge to keep tinkering with my config too much.