My experience when switching to NixOS - Part 4

So I promised I will explain my vim setup in this part of the series. And of course, I will do this.

The requirements

The requirements are fairly easy here. I wanted to be able to use YouCompleteMe with vim, so I need a vim with Python support enabled. So, I absolutely need the configurable vim package from the NixOS package tree and I clearly must override it. And when doing this step, I can remove all the stuff from the vim package I do not need.

Another requirement was that I can replace vim with neovim. I’ve not done this yet, but I guess it should be fairly easy.

Because not all vim plugins are in the NixOS packages, I need to be able to build my own packages outside of the NixOS package tree and pack them into my store as well.

And, because vim needs to know all these plugins, I must be able to override the runtimepath variable in the vim configuration, the vimrc. So, I need to be able to compile the vimrc out of the package definition as well.

The setup

The directory setup here is fairly easy. I have my normal configuration folder whereas I created a folder vim inside the pkgs folder:

config/nixos/
    |
    +- ./pkgs/
        |
        +- vim.nix
        +- vim
            |
            +- override.nix
            +- pkgPlugins.nix
            +- plugins.nix
            +- vimrc/
                |
                +- colorscheme.vim
                +- filetypes.vim
                +- ...
            +- vimrc.nix

Let me elaborate what is located where here.

vim.nix

The top-level file for the vim packaging. This file is actually included in my machine-specific configuration or in the pkgs/basePackages.nix file, so my vim is always installed on every system. The latter is the way I want to do this, so I have the same vim on all systems.

This file returns a list.

vim/override.nix

This file simply contains the overridden vim package.

This file returns a package / derivation.

vim/pkgPlugins.nix

This file contains the already packaged plugins. I do not have to compile them, as they are already in the NixOS package tree, which makes certain things simple but others more complex, we will see why.

This file returns a list.

vim/plugins.nix

Self-packaged plugins. Period.

This file returns a list.

vim/vimrc.nix

This file uses all files in pkgs/vim/vimrc/*.vim and compiles them into one giant string and writes this one as vimrc file to the store. Actually, there is a pkgs/vim/vimrc/plugins.nix file in the directory which compiles pkgs/vim/vimrc/plugins/*.vim files into a string and returns that string, which simply gets included in the vimrc. I did this for abstraction, so the configuration for each file lives into the appropriate subdirectory.

The How

Lets start with the easy parts here first. To override vim, you don’t really have to do much things. What you have to do can be explained best with the appropriate nix file:

Override vim

{ pkgs, lib, vimrc }:

let
    vim = lib.overrideDerivation pkgs.vim_configurable (o: {
        aclSupport              = false;
        cscopeSupport           = true;
        darwinSupport           = false;
        fontsetSupport          = true;
        ftNixSupport            = true;
        gpmSupport              = true;
        hangulinputSupport      = false;
        luaSupport              = true;
        multibyteSupport        = true;
        mzschemeSupport         = true;
        netbeansSupport         = false;
        nlsSupport              = false;
        perlSupport             = false;
        pythonSupport           = true;
        rubySupport             = true;
        sniffSupport            = false;
        tclSupport              = false;
        ximSupport              = false;
        xsmpSupport             = false;
        xsmp_interactSupport    = false;

        postInstall = (o.postInstall or "") + ''
            ln -sf "${vimrc}" "$out/share/vim/vimrc"
        '';
    });
in vim

Of course, you have to make sure that this is the only occurance of vim in your package setup. And if you install vim in a user environment, it will override this as well, so be warned!

So I ripped out everything I don’t need here, which is for example tcl support. I don’t even know what tcl is, actually.

You also need to override the postInstall hook to ensure you link the vimrc to the appropriate path. The vimrc file must be passed to this function, as you can see in the first line.

Packaged plugins

The second thing which is fairly easy is the file for the plugins which are already in the NixOS package repository. What I do here is simple:

{ stdenv , pkgs }:

[
    pkgs.vimPlugins.youcompleteme
    # pkgs.vimPlugins.<some other plugin>
    # ...
]

And yes, it is that simple. I will not share my entire config files here, of course. You don’t need to know what kind of plugins I use in my vim setup!

More plugins!

Now comes the more difficult part. I actually copied these definitins from aszlig:

{ stdenv
, pkgs
, lib
, writeTextFile
, writeText
, buildEnv
, fetchgit
, vim_configurable
}:

with stdenv;

let

    # ... here were some things I did not need

    mkVimPlugins = plugins: buildEnv {
        name = "vim-plugins";
        paths = with lib; mapAttrsToList (const id) plugins;
        ignoreCollisions = true;
        postBuild = ''
            find -L "$out" -mindepth 1 -maxdepth 1 -type f -delete
        '';
    };

    pluginDeps = { };

    plugins = mkVimPlugins (pluginDeps // {

        vimvim-fugitive = fetchgit {
            url = "https://github.com/tpope/vim-fugitive";
            rev = "<your preffered revision>";
            sha256 = "<the sha256, generated with nix-prefetch-git>";
        };

        # ...

    });

in [plugins]

As you can see, this file is a bit longer than the previous ones. I only inserted the fugitive plugin for posting here, obviousely. So how does this work? Each plugin gets fetched with git (for fetching from vimscripts or other methods of fetching, look at aszligs repo, this file, actually). After it was fetched from git, it is inserted in a set which is afterwards fed to the mkVimPlugins function, which adds all the plugins to one package. All plugin pathes are added to the package then. Why you need the ignoreCollisions = true; line… I’m not sure but I guess it ensures that two plugins can have files with same names. I’m also not sure about the postBuild hook.

But that’s how it is supposed to work. It returns a list of plugins, then.

Lets build a vimrc!

Yes, I actually compile my vimrc out of nix expressions. That’s not that hard, it is actually string concatenation, because I do write .vim files.

{ stdenv, writeText, additionalRTP }:

let

    generic             = builtins.readFile ./vimrc/general.vim;
    # ...
    statusline          = builtins.readFile ./vimrc/statusline.vim;
    # ...

    plugins             = import ./vimrc/plugins.nix;

in

writeText "vimrc" ''
    ${generic}

    set runtimepath+=${additionalRTP}
    set runtimepath+=${additionalRTP}/after
    set runtimepath+=${additionalRTP}/share/vim-plugins

    ${statusline}

    ${plugins}
''

That’s really easy: I read the files which contain the vim configurations (general configuration, statusline, etc) and put them all together.

What I add as well: The additional runtime path. This is actually required because when installing vim packages which are not in the package tree of NixOS by now, vim cannot find these. When adding the additional runtime path here, I tell vim to look in these directories as well, ensuring that the plugins can be found.


The last point is obviousely one point I have to clean up, because I guess there should be a better way how to do this. As you might notice when setting up your vim configuration on NixOS, there is also an plugin manager for vim you can use from within NixOS/the nix packages. It gives you lazy loading of vim plugins and all these nice things you want to have. I’m not sure how this fits into the way how NixOS packages vim plugins, I guess I have to dig around here a bit.

Anyhow, it works. On my testing machine, which is an rather old machine (not that old, but also not the new shit, you know) vim starts up in about 1 second. This is obviousely way to much for a machine I want to use for developing software, but for this machine it is okay.

Maybe this will get better after cleaning up the setup, I’m not sure.


In the next part of the series, I will explain my bash(rc) setup, which is handled by nix, of course. Maybe I can also elaborate on my vim setup, if I managed to clean up the mentioned parts.