Nvim on Nix Setup

Luis Michael Ibarra - - 4 mins read

For a long time I’ve wanted to have a portable and reproduceable editor configuration.

I’ve gone through several phases until I feel comfortable with my dotfiles. Even though my dotfiles include a little more than my nvim configuration, it takes most of it.

.dotfile/

At first I used a .dotfile folder that I cloned and then symlink to the right places. It was a little painful to make updates as system dependencies and runtimes changed from my different devices. Also, the files weren´t tracked so I needed to track the changes in each system and then reconcile. Painful. I think I hit a straw when a rope version broke due my python version in 2 different systems.

Git bare repo

This is a variation of the previous one based on Attlassian’s article about it. The only improvement with this method was tracking and avoiding symlinks as I tracked and ignored only things related to dotfiles in my home directory. So, the challenge was only fixing and ensuring runtime was good. As a companion I used a mix of bash scripts and ansible to keep my system reproduceable. I started looking for a better solution when I work handed me a mac device.

Nix - current

This is where the fun really begins. From the NixOS website:

Nix is a purely functional package manager. This means that it treats packages like values in purely functional programming languages - they are built by functions that don’t have side-effects, and they never change after they have been built. Nix stores packages in the Nix store, usually the directory /nix/store, where each package has its own unique subdirectory such as /nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1/ where b6gvzjyb2pg0… is a unique identifier for the package that captures all its dependencies (it’s a cryptographic hash of the package’s build dependency graph). This enables many powerful features.

As Nix has all the dependencies then all the runtimes issues dissapeared but this forced me to learn the Nix way and redo all my dotfiles.

Even though I use NixOS as my day to day, my dotfiles configuration is based on home-manager due the fact I use different operating sytems and/or linux distributions so I need something that can be portable. Hence, I have a flake which calls config/nix/home.nix under my user. After that, everything else is regular nvim stuff. There are some bits that are outside of config but it’s just minimal and it depends on the use case.

    ~/nix-files/config
    ├── nix
    │   ├── common.nix
    │   ├── fzf.nix
    │   ├── git.nix
    │   ├── home.nix
    │   ├── nvim.nix
    │   ├── ssh.nix
    │   ├── tmux.nix
    │   └── zsh.nix
    ├── nvim
    │   ├── plugins
    │   │   ├── git-blame.lua
    │   │   ├── gitsigns.lua
    │   │   ├── go-nvim.lua
    │   │   ├── lualine-nvim.lua
    │   │   ├── noice-nvim.lua
    │   │   ├── nvim-cmp.lua
    │   │   ├── nvim-lspconfig.lua
    │   │   ├── nvim-tree.lua
    │   │   ├── nvim-treesitter.lua
    │   │   └── toggleterm.lua
    │   └── settings
    │       ├── basics.lua
    │       ├── basics.vim
    │       ├── keymaps.lua
    │       └── whichkey.lua
    └── tmux
        └── tmux.conf

6 directories, 23 files
  • home.nix: It imports the rest of the modules. This is just a loader script.

  • common.nix: It has all the common runtimes, LSPs and other utils for day to day. Think about apt or yum. The more I use nix the less useful I found installing runtimes as I started setting all my dependencies in each repository using nix and any program will have their dependencies installed at build time.

  • nvim.nix: It’s based on home-manager’s neovim module to manage nvim. It enables it, installs defined plugins, generates an init.lua based on all the extraConfig configuration files and loads them.

The extraConfig variable supports vimscript and lua configuration.

The plugins variable returns a list of all the plugins to be installed from nixpkgs unstable. This variable also allows building custom plugins generating a derivation of the plugin definition.

{ pkgs, pkgs-unstable, ... }:
{
  programs.neovim = {
    enable = true;
    vimAlias = true;
    #package = pkgs-unstable.neovim;
    extraConfig = ''
      source  $HOME/nix-files/config/nvim/settings/basics.vim
      luafile $HOME/nix-files/config/nvim/settings/basics.lua
      luafile $HOME/nix-files/config/nvim/settings/whichkey.lua
      luafile $HOME/nix-files/config/nvim/settings/keymaps.lua
      luafile $HOME/nix-files/config/nvim/plugins/nvim-tree.lua
      luafile $HOME/nix-files/config/nvim/plugins/nvim-treesitter.lua
      luafile $HOME/nix-files/config/nvim/plugins/lualine-nvim.lua
      luafile $HOME/nix-files/config/nvim/plugins/nvim-lspconfig.lua
      luafile $HOME/nix-files/config/nvim/plugins/nvim-cmp.lua
      luafile $HOME/nix-files/config/nvim/plugins/toggleterm.lua
      luafile $HOME/nix-files/config/nvim/plugins/gitsigns.lua
      luafile $HOME/nix-files/config/nvim/plugins/go-nvim.lua
      luafile $HOME/nix-files/config/nvim/plugins/git-blame.lua
      luafile $HOME/nix-files/config/nvim/plugins/noice-nvim.lua
    '';

    plugins = with pkgs-unstable.vimPlugins; [
        vim-nix
        vim-cue

        #colorscheme
        gruvbox

        #identation lines
        indentLine

        #File tree
        nvim-web-devicons
        nvim-tree-lua

        ...
        
        #treesitter
        nvim-treesitter
        nvim-treesitter.withAllGrammars
        nvim-treesitter-textobjects
        nvim-treesitter-context
        #custom plugins
        (pkgs-unstable.vimUtils.buildVimPlugin {
          name = "guihua";
          src = pkgs.fetchFromGitHub {
            owner = "ray-x";
            repo = "guihua.lua";
            rev = "225db770e36aae6a1e9e3a65578095c8eb4038d3"; # or whatever branch you want to build
            hash = "sha256-V5rlORFlhgjAT0n+LcpMNdY+rEqQpur/KGTGH6uFxMY=";
          };
        })
    ];
  };

}

Each vim or lua file is your regular nvim file which means you can use regular lua and com the vim.api without issues.

Caveats

As Nix has a read only filesystem, it makes a little tricky to manage things that write to the store like treesitter grammars. You are suggested either to download the data needed into the store at build time like nvim-treesitter.withAllGrammars or to have runtime configuration OUTSIDE the nix store so you won´t get an error message of not being able to write to a read-only filesystem.