Skip to content

Instantly share code, notes, and snippets.

@nat-418
Last active November 12, 2024 13:39
Show Gist options
  • Save nat-418/d76586da7a5d113ab90578ed56069509 to your computer and use it in GitHub Desktop.
Save nat-418/d76586da7a5d113ab90578ed56069509 to your computer and use it in GitHub Desktop.
Manage Neovim plugins (and more!) with Nix and Home Manager

Manage Neovim plugins (and more!) with Nix and Home Manager

Highly extensible software like Emacs, Vim, and Neovim tend to grow their own package managers. A software developer, for example, might want to install editor plugins that hook into a particular programming language's linter or language server. The programmer's text editor is therefore extended to support managing additional software to extend the text editor. If this loop continues for too long, the programmer's editor becomes more delicate and complex. The remedy for this problem is to manage software using dedicated tools apart from the programmer's editor itself, and the most powerful tool to do that in 2023 is Nix.

Put simply, using Nix and Home Manager allows users to declaratively configure their software in a reliable, reproducible, and largely self-documenting way. Once set up, a user can easily copy their configuration over to a new machine and get everything working quickly. Nix does have a learning curve, but climbing that curve is worth the effort.

To help newcomers try this, below is an example using Nix with Home Manager to configure Neovim without a Neovim-based plugin manager like packer.nvim or vim-plug. The following steps were tested on Debian 11:

  1. Install Nix. For this guide, we are going to follow the single-user installation instructions:

    $ sh <(curl -L https://nixos.org/nix/install) --no-daemon
  2. Add the Home Manager Nix channel:

    $ nix-channel --add https://github.com/nix-community/home-manager/archive/release-22.11.tar.gz home-manager
    $ nix-channel --update
    
  3. And then add this bit of configuration to your shell:

    export NIX_PATH=$HOME/.nix-defexpr/channels:/nix/var/nix/profiles/per-user/root/channels${NIX_PATH:+:$NIX_PATH}
    
  4. Install Home Manager:

    $ nix-shell '<home-manager>' -A install
    
  5. Edit the Home Manager configuration file found at ~/.config/nixpkgs/home.nix. The default should look something like this:

    { config, pkgs, ...}:
    {
      home.username = "test";
      home.homeDirectory = "/home/test";
      home.stateVersion = "22.11";
      programs.home-manager.enable = true;
    }
    # Comments in this file were removed for brevity.

    Use the Home Manager Option Search to find relevant configuration to add. Let's start with Neovim and git:

    { config, pkgs, ...}:
    {
      home.username = "test";
      home.homeDirectory = "/home/test";
      home.stateVersion = "22.11";
      programs.home-manager.enable = true;
      programs.git.enable = true;
      programs.neovim.enable = true;
    }
  6. Rebuild the Home Manager configuration: $ home-manager switch. This will either throw an error if the configuration file is errant, or spew a bunch of text and successfully install Neovim and git.

  7. Add more Neovim configuration. For this example, we want Neovim to be our default editor, and also be aliased to vi, vim, and vimdiff:

    { config, pkgs, ...}:
    {
      home.username = "test";
      home.homeDirectory = "/home/test";
      home.stateVersion = "22.11";
      programs.home-manager.enable = true;
      programs.git.enable = true;
      programs.neovim = {
        enable = true;
        defaultEditor = true;
        viAlias = true;
        vimAlias = true;
        vimdiffAlias = true;
      };
    }
  8. Rebuild the Home Manager configuration: $ home-manager switch.

  9. Add some plugins:

    { config, pkgs, ...}:
    {
      home.username = "test";
      home.homeDirectory = "/home/test";
      home.stateVersion = "22.11";
      programs.home-manager.enable = true;
      programs.git.enable = true;
      programs.neovim = {
        enable = true;
        defaultEditor = true;
        viAlias = true;
        vimAlias = true;
        vimdiffAlias = true;
        plugins = with pkgs.vimPlugins; [
          nvim-lspconfig
          nvim-treesitter.withAllGrammars
          plenary-nvim
          gruvbox-material
          mini-nvim
        ];
        # Use the Nix package search engine to find
        # even more plugins : https://search.nixos.org/packages
      };
    }
  10. Rebuild the Home Manager configuration: $ home-manager switch.

  11. Not every Neovim plugin is already packaged in the Nix package repository, so let's write a little Nix function to help us download packages hosted on GitHub:

{ config, pkgs, lib, ...}:

let
  fromGitHub = ref: repo: pkgs.vimUtils.buildVimPlugin {
    pname = "${lib.strings.sanitizeDerivationName repo}";
    version = ref;
    src = builtins.fetchGit {
      url = "https://github.com/${repo}.git";
      ref = ref;
    };
  };
in

{
  home.username = "test";
  home.homeDirectory = "/home/test";
  home.stateVersion = "22.11";
  programs.home-manager.enable = true;
  programs.git.enable = true;
  programs.neovim = {
    enable = true;
    defaultEditor = true;
    viAlias = true;
    vimAlias = true;
    vimdiffAlias = true;
    plugins = with pkgs.vimPlugins; [
      nvim-lspconfig
      nvim-treesitter.withAllGrammars
      plenary-nvim
      gruvbox-material
      mini-nvim
      (fromGitHub "HEAD" "elihunter173/dirbuf.nvim")
    ];
  };
}
  1. Rebuild the Home Manager configuration: $ home-manager switch.

Hopefully this is enough of an example to give a sense of how working with Nix and Home Manager is a significant improvement over configuring each application individually. Credit to Felix Breuer for that nice GitHub downloading function. There is much more to Nix and Home Manager then what was illustrated above, so please read their manuals, learn, explore, and enjoy.

@bashfulrobot
Copy link

Have you tried this out with a flake? The fromGitHub is precisely what I am looking for, but it results in error: in pure evaluation mode, 'fetchTree' requires a locked input.

@bashfulrobot
Copy link

ok, this worked:

fromGitHub = rev: ref: repo: pkgs.vimUtils.buildVimPluginFrom2Nix {
    pname = "${lib.strings.sanitizeDerivationName repo}";
    version = ref;
    src = builtins.fetchGit {
      url = "https://github.com/${repo}.git";
      ref = ref;
      rev = rev;
    };
  };

and then I call it with (fromGitHub "6422c3a651c3788881d01556cb2a90bdff7bf002" "master" "Shopify/shadowenv.vim") (commit, branch, and repo).

I am not a fan that I will have to update this periodically, but a byproduct of the reproducible build stuff.

@nat-418
Copy link
Author

nat-418 commented May 10, 2023

Have you tried this out with a flake? The fromGitHub is precisely what I am looking for, but it results in error: in pure evaluation mode, 'fetchTree' requires a locked input.

I don't use flakes.

I am not a fan that I will have to update this periodically, but a byproduct of the reproducible build stuff.

Yeah it's all about trade-offs.

Thanks for contributing these comments. I am sure others will find them useful.

@Uselessa
Copy link

im one of them thanks !!

@nilsherzig
Copy link

thanks :)

@alexpetrean80
Copy link

using this method i assume the config moves to something like the old packer way where each plugin is required and config'd, right? Or should you do something extra to have the lua plugins configurable?

@Lantianyou
Copy link

Thanks

@nat-418
Copy link
Author

nat-418 commented Jul 12, 2023

using this method i assume the config moves to something like the old packer way where each plugin is required and config'd, right? Or should you do something extra to have the lua plugins configurable?

If I understand what you mean correctly, using this method doesn't change how you configure your plugins using init.lua at all.

@nat-418
Copy link
Author

nat-418 commented Oct 1, 2023

I have written a new post that goes into using Home Manager to fully managing the whole Neovim configuration, not just plugins:

https://gist.github.com/nat-418/493d40b807132d2643a7058188bff1ca

@ttrei
Copy link

ttrei commented Dec 27, 2023

Note that in nixpkgs 23.11 pkgs.vimUtils.buildVimPluginFrom2Nix has been deprecated in favor of pkgs.vimUtils.buildVimPlugin.

@ttrei
Copy link

ttrei commented Dec 27, 2023

Regarding the treesitter plugin - I find that using nvim-treesitter.withAllGrammars leads to rather long rebuilds.
The list of supported grammars is long, no one person can be interested in all of them :)

You can specify only the languages you need, like this:

programs.neovim = {
  ...
  plugins = let
    nvim-treesitter-with-plugins = pkgs.vimPlugins.nvim-treesitter.withPlugins (treesitter-plugins:
      with treesitter-plugins; [
        bash
        c
        cpp
        lua
        nix
        python
        zig
      ]);
  in
    with pkgs.vimPlugins; [
      ...
      nvim-treesitter-with-plugins
    ];
};

@ibizaman
Copy link

ibizaman commented Jan 3, 2024

@bashfulrobot with flake, you would add the plug-in’s repo as a flake input.

@nat-418
Copy link
Author

nat-418 commented Jan 25, 2024

Note that in nixpkgs 23.11 pkgs.vimUtils.buildVimPluginFrom2Nix has been deprecated in favor of pkgs.vimUtils.buildVimPlugin.

Thanks, I have edited the guide accordingly.

@nat-418
Copy link
Author

nat-418 commented Jan 25, 2024

Regarding the treesitter plugin - I find that using nvim-treesitter.withAllGrammars leads to rather long rebuilds. The list of supported grammars is long, no one person can be interested in all of them :)

You can specify only the languages you need, like this:

programs.neovim = {
  ...
  plugins = let
    nvim-treesitter-with-plugins = pkgs.vimPlugins.nvim-treesitter.withPlugins (treesitter-plugins:
      with treesitter-plugins; [
        bash
        c
        cpp
        lua
        nix
        python
        zig
      ]);
  in
    with pkgs.vimPlugins; [
      ...
      nvim-treesitter-with-plugins
    ];
};

Thanks for the comment,. I personally like to have all of them. Good to note for users who might want only some.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment