Monday, 19 April 2010

Programming Clojure with Vim

Update 2013-11-20: Please check out the 2013 edition of this article.

I have been using Vim for a decade and a half. It has become ingrained in how I do things. I use vi bindings in my browser (via Vimperator), my command line, my e-mail client, my newsreader, etc. Whenever I am editing a document in some strange environment, like Word, I inevitably end up inserting a load of aitches, jays, kays, and ells—much to my infuriation.

Unsuprisingly, when I started working with Clojure, I immediately sought a plug-in to provide the sorts of niceties we all expect in a development tool, such as syntax highlighting, completion, etc. Of course, with Clojure, I also wanted a REPL. However, getting a satisfactory REPL from Vim is easier said than done.

A word about SLIME

SLIME, the Superior Lisp Interaction Mode for Emacs, is an Emacs mode for working with Common Lisp. Even if you are not familiar with Emacs, it does not take long to realise how nice and convenient it is. Unsurprisingly, Clojure programmers have written the necessary plug-ins for a Clojure mode in Emacs with SLIME support. Often, Emacs is considered the preferred choice for working with Clojure.

Unfortunately for those of us who are hard-wired for vi, it is currently very difficult to implement a “Superior Lisp Interaction Mode for Vim”. This is because Vim lacks support for asynchronous subprocesses. Nevertheless, there are a number of Clojure options for Vim users. Most prominent among these is VimClojure.

VimClojure

VimClojure is a Vim plug-in developed by Meikel Brandmeyer. It provides support for Clojure syntax highlighting (including rainbow parentheses), indentation, and completion.

In addition, it supports a number of SLIME-type features, such as:

  • Evaluate and see the results of the current:
    • top-level s-expression
    • file
    • line
    • visual block
    • paragraph
  • Tell Clojure to require the current namespace with either a :reload or :reload-all
  • See the macro expansion of the current innermost s-expression with either macroexpand or macroexpand-1
  • Interactively execute (find-doc …)
  • For the current word or an interactively given word:
    • Lookup documentation using (doc …)
    • Lookup JavaDoc using (clojure.contrib.repl-utils/javadoc …)
    • See the source using (clojure.contrib.repl-utils/source …)
    • Open the source in Vim
    • Show the word’s metadaka, like (meta #'…)
  • Start a REPL within Vim

This is a pretty nice set of features that can make developing Clojure within Vim a much more delightful experience. For example, say one of your functions is failing a test. From Vim, you can fix the function and type ,et to send the entire (defn …) form to Clojure (assuming , is your LocalLeader). Now you can re-run your tests and see whether or not your fix worked.

Unfortunately, VimClojure is not without its idiosyncrasies.

Caveat lector

No tool is perfect, and think VimClojure does a very good job. Nonetheless, there a few things about which you should be aware:

  1. For most of the above functionality to work, The gorilla (nailgun) server for VimClojure must include your Clojure source directories in its class path.
  2. You can't mix VimClojure 2.1.2 with Clojure 1.2. The current mercurial version of VimClojure is no longer tied to a particular Clojure version.
  3. The REPL stinks. Admittedly, because of the above-mentioned Vim shortcoming, it's hard to get a REPL that is quite as nice as the one for SLIME. However, things that I normally expect to have, such as the ability to arrow keys to navigate the REPL history, are not supported.

Alternative Clojure plug-ins for Vim

VimClojure is not the only way to go with Clojure and Vim. Some of the alternatives include using VimClojure in off-line mode, using VimClojure in off-line mode with an alternative REPL, and Slimv, another Vim plug-in.

VimClojure (off-line)

It may be a stretch to call this a ‘VimClojure alternative’, but it is possible to use Vim without any of the SLIME-like features. In this mode, you keep the indentation, syntax highlighting, and completion. It may not be as powerful as the full VimClojure plug-in, but it saves you the trouble having to start a nailgun server and worry about class paths.

VimClojure (off-line) and slime.vim

This method is described by Lee Hinman in his article ‘How I develop Clojure with Vim’. This method uses a supplemental Vim script called slime.vim that works around Vim’s asynchronous shortcomings through the use of GNU Screen.

In this arrangement, you can run your REPL in a separate Screen and have Vim send commands to it using screen’s command line interface. I first encountered this plug-in when I tried some Lisp development in Vim. I do not use it because I think that VimClojure’s capabilities are much better.

Slimv

Slimv is a relatively unknown plug-in in the Clojure community. It is written by Tamas Kovacs, and originated as plug-in for Lisp programmers. However, in the past few versions, it has begun to support Clojure.

Slimv uses a Python script to launch a REPL, to which it sends commands in a way, in principle, not terribly unlike slime.vim. Instead of running a REPL using Screen, it launches the REPL in a new terminal.

Unlike slime.vim, it seems like it supports a much greater amount of functionality, much like VimClojure. In fact, it supports having a REPL inside of Vim that has the potential to be vastly superior to the one provided by VimClojure. In particular, it allows using the arrow keys in insert mode to navigate the REPL history.

Unfortunately, the current version is a bit buggy. For example, the REPL works, but after every command I get series of Vim errors that, although apparently harmless, make the experience intolerable.

The latest version also includes a paredit plug-in, that promises some of the features of Emacs’ paredit minor mode. Unfortunately, I found it fairly buggy. For example, in one case where I wanted to delete to the end of a line using D, it deleted my entire line. In another case when I wanted to move a one-line def using dd and p, I found that the p pasted nothing at all. Yanking and putting still worked, though.

I think Slimv has some great features, but until some of these bugs are worked out, it is not going to be my plug-in of choice. Unfortunately, there does not seem to be a project page or repository available anywhere for Slimv.

How I develop Clojure with Vim

My ideal Vim plug-in would combine all of the best features of both VimClojure and Slimv, eliminate all of the bugs, and make it simple to install and use. Unfortunately, no such plug-in exists, nor will I be taking the time to write it any time soon. So, I do the best I can with the tools that are available.

For a given Clojure project, my development environment consists of three things:

  1. A Clojure script that I use to launch nailgun, set up utility functions, and run my REPL.
  2. A shell script to set up my class path and run my script.
  3. Vim with VimClojure
The development script

This development script does a few things that make development easier. I will load clojure.contrib.repl-utils, start nailgun, and often times write a function that runs all of my unit tests. Here is an example of such a script:

(ns #^{:doc "A sample development.environment Clojure script"
       :author "Daniel Solano Gómez"}
  example-repl
  (:import com.martiansoftware.nailgun.NGServer
           java.net.InetAddress)
  (:use clojure.contrib.repl-utils
        clojure.test))

(defn- dotests []
  (require :reload-all :verbose 'example.test)
  (run-tests 'example.test))

(def ng-host "127.0.0.1")
(def ng-port 2113)
(def ng-server (NGServer. (InetAddress/getByName ng-host) ng-port))
(.start (Thread. ng-server))

(println "Welcome to your development REPL")
(clojure.main/repl)
(.shutdown ng-server false)

Without going into a lot of detail, this script accomplishes the following:

  1. Set up a namespace, making the necessary imports to get nailgun to run and including repl-utils so can (source …) from the REPL.
  2. Set up a convenience function that will reload all my namespaces and execute my tests. From the REPL, I only have to (dotests).
  3. Instantiate and start a nailgun server.
  4. Start the REPL with a friendly welcome message.
  5. When the REPL ends, shut down the nailgun server gracefully.

You can do other things in this script, such as set up a server for Compojure development or set values for vars like *warn-on-reflection*.

The shell script

The job of the shell script is to set up my class path and run the development script. I also use rlwrap to give me readline support with vi key bindings within the REPL.

A minimal example of such a script:

#!/bin/sh

CLOJURE_CP=/usr/share/clojure-1.1/lib/clojure.jar
CLOJURE_CONTRIB_CP=/usr/share/clojure-contrib-1.1/lib/clojure-contrib.jar
VIMCLOJURE_CP=/tmp/vimclojure-2.1.2/build/vimclojure.jar
PROJECT_CP=src

CLASSPATH="${CLOJURE_CP}:${CLOJURE_CONTRIB_CP}:${VIMCLOJURE_CP}:${PROJECT_CP}"

BREAK_CHARS="(){}[],^%$#@\"\";:''|\\"
rlwrap --remember -c -b "${BREAKCHARS}" -f "${HOME}/.clj_completions" java -cp ${CLASSPATH} clojure.main devrepl.clj
VimClojure

I usually develop within GNU Screen. In one screen, I launch my REPL, via ./devrepl, and in another I launch Vim. With this configuration, I have all of the goodies from VimClojure along with a REPL with readline support and Vim bindings.

On occassion when I have just launched my REPL, VimClojure will complain that it can not connect to the nailgun server. If I do a netstat -lt, I will in fact see that the REPL is not listening on the nailgun port. I have usually found that running (.isRunning ng-server) within the REPL remedies this situation. I suppose this is a bug in nailgun.

Concluding remarks

Is this a perfect set-up? No, not entirely. Am I missing out on some SLIMEy goodness? Probably. However, it gives me most of what I need to be able to develop Clojure productively.

If you have any good Vim and Clojure hints or tips, feel free to share them by posting a comment.

TrackBacks

Comments