Configure Vim for Darwin in nix-darwin

In a previous post I explored installing Nix and nix-darwin. The first program I installed was Vim though nixpkgs.vim_configurable in order to get +python3 support. I quickly realized that access to the system clipboard was gone. My setting

set clipboard^=unnamed

no longer did me any favors. So I did some digging.

$ vim --version
...
+acl               -farsi             +mouse_sgr         +tag_binary
+arabic            +file_in_path      -mouse_sysmouse    -tag_old_static
+autocmd           +find_in_path      +mouse_urxvt       -tag_any_white
+autochdir         +float             +mouse_xterm       -tcl
-autoservername    +folding           +multi_byte        +termguicolors
+balloon_eval      -footer            +multi_lang        +terminal
+balloon_eval_term +fork()            -mzscheme          +terminfo
+browse            +gettext           +netbeans_intg     +termresponse
++builtin_terms    -hangul_input      +num64             +textobjects
+byte_offset       +iconv             +packages          +textprop
+channel           +insert_expand     +path_extra        +timers
+cindent           +ipv6              -perl              +title
+clientserver      +job               +persistent_undo   +toolbar
+clipboard         +jumplist          +popupwin          +user_commands
+cmdline_compl     +keymap            +postscript        +vartabs
+cmdline_hist      +lambda            +printer           +vertsplit
+cmdline_info      +langmap           +profile           +virtualedit
+comments          +libcall           -python            +visual
+conceal           +linebreak         +python3           +visualextra
+cryptv            +lispindent        +quickfix          +viminfo
+cscope            +listcmds          +reltime           +vreplace
+cursorbind        +localmap          +rightleft         +wildignore
+cursorshape       +lua               +ruby              +wildmenu
+dialog_con_gui    +menu              +scrollbind        +windows
+diff              +mksession         +signs             +writebackup
+digraphs          +modify_fname      +smartindent       +X11
+dnd               +mouse             -sound             -xfontset
-ebcdic            +mouseshape        +spell             +xim
+emacs_tags        +mouse_dec         +startuptime       +xpm
+eval              -mouse_gpm         +statusline        -xsmp
+ex_extra          -mouse_jsbterm     -sun_workshop      +xterm_clipboard
+extra_search      +mouse_netterm     +syntax            -xterm_save
...

As it turns out, Vim was also compiled with +X11 and +xterm_clipboard. Aha! So Vim uses the X11 selection mechanism for copy and paste instead of macOS' system clipboard. That kind of sucks. So how to we get Vim to interoperate with the * or + registers?

We could of course install XQuartz and configure it to "Update Pasteboard immediately when new text is selected". That'll do the trick. But what if XQuartz is not running when we first start Vim? It will start automatically, but we get an unnacceptable start time. Waiting for several seconds in addition to having to install yet another thing I don't really want is not the way to go. There has to be another way.

Looking at the source for vim_configurable we see

, features          ? "huge" # One of tiny, small, normal, big or huge
, wrapPythonDrv     ? false
, guiSupport        ? config.vim.gui or (if stdenv.isDarwin then "gtk2" else "gtk3")
, luaSupport        ? config.vim.lua or true
, perlSupport       ? config.vim.perl or false      # Perl interpreter
, pythonSupport     ? config.vim.python or true     # Python interpreter
, rubySupport       ? config.vim.ruby or true       # Ruby interpreter
, nlsSupport        ? config.vim.nls or false       # Enable NLS (gettext())
, tclSupport        ? config.vim.tcl or false       # Include Tcl interpreter
, multibyteSupport  ? config.vim.multibyte or false # Enable multibyte editing support
, cscopeSupport     ? config.vim.cscope or true     # Enable cscope interface
, netbeansSupport   ? config.netbeans or true       # Enable NetBeans integration support.
, ximSupport        ? config.vim.xim or true        # less than 15KB, needed for deadkeys
, darwinSupport     ? config.vim.darwin or false    # Enable Darwin support
, ftNixSupport      ? config.vim.ftNix or true      # Add .nix filetype detection and minimal syntax highlighting support
, ...

guiSupport populates the --enable-gui flag and darwinSupport (false by default) will set the --disable-darwin flag. When darwinSupport is set to true, the --enable-darwin flag is set instead. Let's edit our darwin-configuration.nix

  ...
  
  environment.systemPackages = [
    ...
    config.programs.vim.package
    ...
  ];

  ...

  programs.vim.package = pkgs.vim_configurable.override {
    python = pkgs.python3;
    guiSupport = "no";
    darwinSupport = true;
  };

  ...

Then we $ darwin-rebuild switch and wait for the compilation to finish.

$ vim --version
...
+acl               -farsi             +mouse_sgr         +tag_binary
+arabic            +file_in_path      -mouse_sysmouse    -tag_old_static
+autocmd           +find_in_path      +mouse_urxvt       -tag_any_white
+autochdir         +float             +mouse_xterm       -tcl
-autoservername    +folding           +multi_byte        +termguicolors
-balloon_eval      -footer            +multi_lang        +terminal
+balloon_eval_term +fork()            -mzscheme          +terminfo
-browse            +gettext           +netbeans_intg     +termresponse
++builtin_terms    -hangul_input      +num64             +textobjects
+byte_offset       +iconv             +packages          +textprop
+channel           +insert_expand     +path_extra        +timers
+cindent           +ipv6              -perl              +title
-clientserver      +job               +persistent_undo   -toolbar
+clipboard         +jumplist          +popupwin          +user_commands
+cmdline_compl     +keymap            +postscript        +vartabs
+cmdline_hist      +lambda            +printer           +vertsplit
+cmdline_info      +langmap           +profile           +virtualedit
+comments          +libcall           -python            +visual
+conceal           +linebreak         +python3           +visualextra
+cryptv            +lispindent        +quickfix          +viminfo
+cscope            +listcmds          +reltime           +vreplace
+cursorbind        +localmap          +rightleft         +wildignore
+cursorshape       +lua               +ruby              +wildmenu
+dialog_con        +menu              +scrollbind        +windows
+diff              +mksession         +signs             +writebackup
+digraphs          +modify_fname      +smartindent       -X11
-dnd               +mouse             -sound             -xfontset
-ebcdic            -mouseshape        +spell             -xim
+emacs_tags        +mouse_dec         +startuptime       -xpm
+eval              -mouse_gpm         +statusline        -xsmp
+ex_extra          -mouse_jsbterm     -sun_workshop      -xterm_clipboard
+extra_search      +mouse_netterm     +syntax            -xterm_save
...

Boom! -X11 and -xterm_clipboard is exactly what we want to see. We now have access to the system clipboard again and everything is back to normal.

Heroes in a nix-shell, direnv power!

In this post I'll walk through seting up Nix with direnv to automatically provision a development environment with Nix Shell. I recently learned about this from a colleague of mine and wanted to try it out. While doing so, I found the official documentation on how to do it, which kind of makes this post redundant. But I'm mostly writing this for myself, so what the heck. Here goes.

Installing direnv

I'm using Nix Darwin, so I'll start by editing my darwin-configuration.nix to add direnv.

  environment.systemPackages = [
    ...
    pkgs.direnv
    ...
  ];

Cool. Let's go ahead and install that.

$ darwin-rebuild switch

Now it's time to hook direnv into our shell. Head over to the direnv website to find instructions for how to do it in your shell. Personally, I'm using zsh which means I've got to add

eval "$(direnv hook zsh)"

at the end of my .zshrc.

Once that's done, let's try out the demo from the direnv website.

# Create a new folder for demo purposes.
$ mkdir ~/my-project
$ cd ~/my-project

# Show that the FOO environment variable is not loaded.
$ echo ${FOO-nope}
nope

# Create a new .envrc. This file is bash code that is going to be loaded by
# direnv.
$ echo export FOO=foo > .envrc
.envrc is not allowed

# The security mechanism didn't allow to load the .envrc. Since we trust it,
# let's allow it's execution.
$ direnv allow .
direnv: reloading
direnv: loading .envrc
direnv export: +FOO

# Show that the FOO environment variable is loaded.
$ echo ${FOO-nope}
foo

# Exit the project
$ cd ..
direnv: unloading

# And now FOO is unset again
$ echo ${FOO-nope}
nope

So far so good.

Nix shell

Now let's create a shell.nix in the same project we created from the demo, right alongside the .envrc. Maybe we want it to be a go project.

{ pkgs ? import <nixpkgs> {} }:
  pkgs.mkShell {
    buildInputs = [
      pkgs.go
    ];
  }

Write the file, and let's edit our .envrc by sticking use_nix in it. The contents of your .direnv now look like this

use_nix
export FOO=foo

Once again, direnv is not allowed. We have to allow it again

$ direnv allow

That's kind of annoying, but once we allow it you should see some exciting stuff happeing. Go is being installed! Once the installation finishes, let's have a look

# Show which go binary is currently on the $PATH
$ which go
/nix/store/hzglx2vc1dziv8zxr9kxr8x4bgyanwnk-go-1.14.3/bin/go

# Back out of the project directory
$ cd ..
direnv: unloading

# Check the binary again (I'm expecting this to show the one installed with
# Homebrew)
$ which go
/usr/local/bin/go

That's pretty awesome! We can also move the environment variable from our .envrc into shell.nix.

{ pkgs ? import <nixpkgs> {} }:
  pkgs.mkShell {
    buildInputs = [
      pkgs.go
    ];

    FOO="foo";
  }

When we're keeping everything in our shell.nix there's no longer any need for running direnv allow after every change.

Next steps

This is a great way to create a reproducible development environment, but certainly the rabbit hole goes deeper. In How I Start: Nix, Christine Dodrill describes how to use lorri and niv to help manage the development shell and project dependencies. I'm excited to try these tools out and maybe write about them in a future post.

References