My Clojure Toolchain: Vim

Oh, hi. You're back. Well, since you apparently didn't get sick of me talking about Clojure tools last time, let's keep going.

Vim is my terminal-based text editor of choice. It's highly configurable, but for me, it looks like this:

If you want to get into what's going on there - my .vimrc file isn't particularly organized, but if you're curious it looks like this. I use Solarized as my color scheme.

Why Use Vim?

This is one of those subjects that has inspired religious wars for decades. The short version goes like this: if you're a systems engineer, at some point you're going to need to familiarize a terminal-based text editor, and it should be one that you can expect to find on any reasonable UNIX system. Often, this boils down to a choice between Emacs and Vim (those being the two most mature and widely distributed such programs).

My reasons for using Vim are simple: several years ago, when the universe was still forming in the white heat of the big bang, I had the choice to learn Emacs or to learn Vim, and I chose to learn Vim.

...there isn't anything more to that story.

Honestly, at this point I'm familiar with Vim's tricks and idiosyncrasies, and just don't feel inclined to learn a new editor. There's no denying that Emacs has an inherent and [literally] built-in advantage when it comes to Lisps, but the best tool is often the one you know.

Vim Plugins

The world of Vim plugins is both huge and awesome. In addition to the Clojure-specific plugins listed below, I use vim-airline, Syntastic, and NERDTree, among others.

You didn't come here for me to talk about Vim in general, though, so let's get down to brass tacks: Vim plugins for Clojure.

vim-fireplace

Fireplace is the killer plugin for Vim and Clojure. It starts what @tpope calls a "quasi-REPL" that can be used to connect to a running nREPL server. By default, Leiningen will write a .nrepl-port file in the project's root that contains the integer port that the nREPL server is running on. When you open a Clojure file in Vim, Fireplace reads the port from the .nrepl-port file and opens a connection as a nREPL client.

You can also manually connect to a REPL server from Vim with the :Connect command, which is handy if you need to connect to a REPL that wasn't initialized from the current project directory.

If for some reason you don't have a nREPL server started (either through Leiningen or the application itself), there's also vim-leiningen which will shell out to Java to initialize one for you based on what the plugin can infer of your classpath. However, this tends to be a bit on the slow side, and in general it's much faster to just have an nREPL server already running in the background for you to connect to.

I use the fireplace REPL for a few major things:

  • code traversal
  • grabbing docstrings
  • running tests
  • hot-reloading the file I'm editing into the JVM

Some of these - in particular, docstrings and tests - are the sorts of things I'd be doing in a REPL anyways. The others are less so, but are hugely valuable for a code editor to have.

Code traversal

Let's start with code traversal - entering gf (goto file) when the cursor is over a namespace will take you to the namespace file, which is particularly great when you're wondering how the hell one of your dependencies works. Otherwise, you'd have to dig up the jar on your classpath and navigate its contents (or worse, look at the code on GitHub, which would involve - gasp! - leaving the terminal). Once within another namespace, hitting CTRL+w backs you out of the namespace into the original file.

Docstrings

I'm of the opinion that docstrings (and documentation at large) are underrated. Since I'm essentially an idiot at a typewriter, I tend to look up function documentation frequently.

When I'm at the REPL, this means a lot of (doc x). Fireplace gives me the ability to do that with :Doc x, and provides an even better shortcut by setting K as a hotkey that looks up the docstring for the symbol under the cursor.

Running tests

If you're in a test namespace, you can use the :RunTests command to kick off a conventional (clojure.test/run-tests), with the results loaded into a quickfix file

I also have a little Vim function (courtesy of my friend David Lowe) for running the test form under the current cursor. All this power and more can be yours for the low, low price of adding the following to your .vimrc (or wherever you store your Vim functions)!

function! TestToplevel() abort  
    "Eval the toplevel clojure form (a deftest) and then test-var the result."
    normal! ^
    let line1 = searchpair('(','',')', 'bcrn', g:fireplace#skip)
    let line2 = searchpair('(','',')', 'rn', g:fireplace#skip)
    let expr = join(getline(line1, line2), "\n")
    let var = fireplace#session_eval(expr)
    let result = fireplace#echo_session_eval("(clojure.test/test-var " . var . ")")
    return result
endfunction  
au Filetype clojure nmap <c-c><c-t> :call TestToplevel()<cr>  
Hot-reloading code into the JVM

This is, as far as I am concerned, the most important of Fireplace's features, and one I use pathologically. Using the :Require command is the equivalent of entering (require ... :reload) for the namespace in the current buffer, and I find myself doing this so often that I have it hotkeyed in my .vimrc as follows:

au Filetype clojure nmap <c-c><c-k> :Require<cr>  

My development flow looks something like this:

  1. Within Vim, make a few changes, write a new function, etc. Reload the changes into the current JVM with CTRL + C + CTRL + K.
  2. Switch tabs to a REPL, where I interactively play with and test my changes. This is done quickly by cycling through iTerm windows; ? + ? or ? + ? for windows in my current tab, and ? + ? or ? + ? to change between tabs.
  3. Switch tabs back to Vim, rinse, repeat.
  4. Every half hour or so, run some tests. You have been writing tests, haven't you? ;)

When one is just starting out on a project and/or building very small and composable functions, the REPL alone can be a sufficient development environment. However, as the size of the codebase grows, I've found it easier to iteratively build functions by working on them in their target namespace within my editor, where I can more easily visualize and maintain context for what I'm working on. Fireplace is an incredible tool for supporting such a workflow

paredit.vim

Paredit.vim is another absolutely critical plugin to have for the Vim Clojure developer's toolbox. A component of the larger slimv ("Slime for Vim"), Paredit attempts to maintain the balanced state of matched characters for parenthesis, brackets, double quotation marks, etc.

If that were all it did, however, it would only be passingly useful. Its true value lies in its support for what Emacs users refer to as "Slurpage" and "Barfage" - the ability to, with a keystroke or two, move existing arguments into or out of a given form.

For instance, let's say I have the following in my editor:

(println (+ 1 1) 1)

What I really want, however, is to have that third one inside the form doing the addition. Well, with Paredit.vim, I can do that easily with two keystrokes, <Leader> >:

Buh-whaaaaa? So spice! I have my <Leader> mapped to ,, so for me the keys are right next to each other , >. This is really handy because Paredit doesn't naively push parenthesis around simple words, but rather around lisp forms. As a result, if I were to try to do the same command a second time, it wouldn't have any effect, because the inner parenthesis cannot logically be pushed outside of its containing form.

In addition to that, Paredit is great for wrapping forms in different wrappers, which is handy for when you've written something that needs to be refactored into a function, or needs let bindings added at some point.

Extra Credit: If Paredit is a part of slimv, why not just use Slimv? Great question, reader! To be totally honest, I don't have the best answer, and in fact, slimv might be great for you. However:

  1. It comes with its own REPL, and I think the current consensus among Vim + Clojure nerds that the Fireplace REPL is better.
  2. Paredit.vim is pure vimscript, and is extremely lightweight. slimv is much larger, and is all over the place: some common lisp, some Clojure, some Scheme, some Python, some VimL.

Ultimately, I prefer simplicity, and at this point I've been working in Vim long enough that I really trust Tim Pope's taste over most other plugin developers. But hey, you do you.

vim-surround

Next in the list of extremely useful functions for dealing with parenthesis is another Tim Pope plugin, vim-surround. This one probably deserves a place in your list of plugins whether you're working in Clojure or not, especially for when it comes to working with strings. What I find it most useful for, though, is for getting rid of stuff. As the good lord Paredit gives, vim-surround taketh away.

For example, check this out: ds( ("delete" "surrounding" "parens"):

Manually wrangling parentheses and other surrounding markers is a huge pain in the neck. Paredit.vim and vim-surround give you a large selection of key bindings to make working with such enclosures fast and easy.

vim-eastwood

You know I have a habit of hyping my own plugins, right? Well, if not, you do now! In my last post on Leiningen, I talked about Eastwood, the Clojure lint tool. vim-eastwood is a Vim plugin for Eastwood bindings that sits on top of Syntastic.

It requires you to have Eastwood on the Clojure classpath (i.e. preferably in your ~/.lein/profiles.clj) and to have a running nREPL server that you've connected to with Fireplace. After that, it feeds quickfix info on your current file to Syntastic, which generates handy markers in the left gutter and shows lint messages at the bottom of the screen when you move your cursor over the area in question:

vim-cljfmt

Continuing down the road of shameless self-promotion is another of my Vim Clojure plugins, which provides convenient bindings for cljfmt, the Clojure formatting tool. vim-cljfmt allows you to format the contents of the current Vim buffer through the :Cljfmt command, which by default is run every time you write the buffer to a file, much like how gofmt works.

Observe:

Now, isn't that nice?

Honorable Mentions

It would be remiss of me not to mention vim-clojure-static and rainbow parentheses; both are widely cited as being critical plugins for the Clojure developer working in Vim.

The truth is that I don't give much thought to vim-clojure-static since it's been shipping with Vim ever since 7.3.803, which was released over a year ago. As for rainbow parentheses - you know, I can see the value, but ever since installing Paredit.vim I haven't really felt particularly confused about what depth of nested parentheses I was working in. Personal preference, perhaps - if it works for you, I wish you all the best in using it.

This isn't a plugin, but it still deserves a shoutout - the hotkey, %, lets you find the starting/terminating parenthesis/bracket/brace for the one you've currently got your cursor on top of:

Conclusion

Anybody who has had the questionable privilege of having worked with me knows that I am peculiarly fanatical about tooling. I'm easily frustrated by poor tools, and I suffer from the engineer's afflication of always searching for a more perfect workflow.

One of the things I like about Vim is that it doesn't attempt to constrain me. If I want a new feature, I can add it - in a pretty wide range of languages - and I can rely on the editor's rich plugin ecosystem to provide painless ways to bind that functionality into the program.

Admittedly, writing plugins in VimScript isn't as lovely as writing everything in Clojure, but now that I've written two plugins in it I can't say that it's the worst language, either. It's just one that needs some better documentation and introductory material, which smarter people than I are already working on.

People tend to be fanatical about their editors, and so I don't anticipate anybody switching to Vim on basis of what I've covered here. However, for those of you already using Vim, I hope that this discussion of my workflow has been helpful. And for those of you who are curious about what the world of Vim is like, I hope this post shows that choosing Emacs for Clojure doesn't have to be a foregone conclusion.

Discuss this post on Hacker News or on Twitter (@venantius)

Thanks to Keith Ballinger (@keithba), Bill Cauchois (@wcauchois) and Harry Wolff (@hswolff) for reading drafts of this post.