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
ormacroexpand-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 #'…)
- Lookup documentation using
- 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:
- For most of the above functionality to work, The gorilla (nailgun) server for VimClojure must include your Clojure source directories in its class path.
- 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.
- 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:
- A Clojure script that I use to launch nailgun, set up utility functions, and run my REPL.
- A shell script to set up my class path and run my script.
- 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:
- Set up a namespace, making the necessary imports to get nailgun to run and
including
repl-utils
so can(source …)
from the REPL. - Set up a convenience function that will reload all my namespaces and execute
my tests. From the REPL, I only have to
(dotests)
. - Instantiate and start a nailgun server.
- Start the REPL with a friendly welcome message.
- 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
No trackbacks, yet.
Trackbacks are closed for this story.
Comments
-
On Tuesday, 20 Apr 2010 13:12, Meikel wrote the following:
Thanks for this in-depth review! Here some comments:
VimClojure 4.1.2 works perfectly well with Clojure 1.2. However you have to re-compile the clojure sources. (Admittedly this may count as "does not work" for the average user.)
The Repl supports command history via
<C-Up>
and<C-Down>
. I'm used to use the arrow keys and not the more puristic hjkl for navigation. So I didn't want to capture the "usual" keys. However these are only normal key mappings. You can defined eg.function CustomiseClojureRepl() if exists("b:vimclojure_repl") inoremap <buffer> <Up><Plug>ClojureReplUpHistory inoremap <buffer> <Down> <Plug>ClojureReplDownHistory endif endfunction
autocmd FileType clojure call CustomiseClojureRepl()
in your .vimrc. The Repl should now happily use the arrow keys for history navigation. Of course this doesn't remedy any of the other short comings of the Repl.
The current dev version also sports a helper function -
vimclojure.nails/start-server-thread
- which will power up a nailgun server in a daemon thread. So it will exit when your main process exits. -
On Thursday, 22 Apr 2010 09:31, Tamas Kovacs wrote the following:
Thanks for the review and also thanks for reporting the bugs found in Slimv. I try to fix them as soon as possible.
I also keep adding Paredit Mode features from paredit.el. The next ones to be added are Split/Join/Wrap/Splice, so stay tuned.
Please send comments, suggestions, bug reports, etc to me. My e-mail address is written in the Slimv documentation.
-
On Friday, 23 Apr 2010 08:12, Daniel Solano Gómez wrote the following:
Meikel:
Thank you for your feedback. I tried using <C-Up> and <C-Down>, but that did not seem to work for me. I have used both the released and dev versions of VimClojure, but I had not yet noticed the start-server-thread.
Tamas:
I look forward to seeing future releases of Slimv. I hope that more people will try it out and help you improve it. When I get the time to try it out more, I will send you some more detailed bug reports.
-
On Monday, 26 Apr 2010 07:05, Meikel wrote the following:
<C-{Up,Down}> not working might be a terminal problem. I never had trouble with the GUI versions of vim.
-
On Tuesday, 27 Apr 2010 16:10, Tamas Kovacs wrote the following:
I have updated Slimv with new paredit mode functions Split/Join/Wrap/Splice added. I have also fixed the delete/paste bugs (D and dd then p) you mentioned.
Unfortunately the series of Vim errors at REPL refresh you experienced don't show up on my machine, so I would be grateful if you could send me the exact error message.
-
On Tuesday, 16 Nov 2010 08:00, thomas wrote the following:
how do you get vi bindings in your command line?
-
On Wednesday, 14 Dec 2011 19:42, onk wrote the following:
@thomas, a year late: set -o vi
-
On Monday, 7 Oct 2013 11:56, Steve Mustafa wrote the following:
Have you had to alter anything in your toolchain setup to deal with the demise of vim-clojure and the introduction of vim-clojure-static?
-
On Monday, 7 Oct 2013 12:15, Daniel Solano Gómez wrote the following:
Hello, Steve,
I haven’t been doing a lot of Clojure programming lately, but I have made some changes. I am now using vim-fireplace. This has meant a few things:
1. It’s easier to start using Vim with a new project as all I have to do is
lein run
.2. I have to get used to vim-fireplace’s new keybindings.
3. I have lost the ability to have highlighting/completion based on a project's source files.
Overall, vim-fireplace's ability to work with nREPL has been worth it.
Comments are closed for this story.