Twitter GitHub Facebook Instagram dirv.me

Daniel Irvine on building software

Vim snippets for Clojure

22 February 2015

UltiSnips is a Vim plugin for managing snippets: sections of text that can be inserted into any text file using a trigger word. For example, in Java you can type class<tab> and have a default class structure inserted into your file. Snippets in Vim are file type specific: a Java class expansion is different from a C# class expansion. They usually contain placeholder text that you can easily replace with your own. The class expansion has a placeholder for the class name, which you need to provide before the expanded text makes any sense.

Snippets are more useful in some languages, such as Java, than others, such as Clojure. Clojure is a terse language with little ceremony, and snippets tend to have little impact: here’s an example snippet provided by vim-snippets:

snippet def
        (def ${0})

This saves typing precisely one character: def<tab> performs the same job as (def<space>.

However, there are ways we can build useful snippets for Clojure. UltiSnips supports interpolation, meaning that instead of designating simple placeholders within a snippet (like the ${0} above), we can perform string manipulation via an actual programming language. In the example that follows I use VimL but you can also use Python, Perl or bash.

Initializing spec files

Here’s an example of where I’ve found UltiSnips useful for Clojure development. I use Speclj for writing Clojure tests. Spec files have a name like spec/fooproject/foo_spec.clj and always have a header that looks like this:

(ns fooproject.foo-spec
  (require [speclj.core :refer :all]
           [fooproject.foo :refer :all]))

We can use UltiSnip’s interpolation feature to generate this header from the filename. The tricky part is converting the filename to the correct namespace: from spec/fooproject/foo_spec.clj to fooproject.foo-spec. I’ve opted to do that using VimL functions which are then called from my snippet definitions.

function! CreateNsFromFile(file)
  let ns = substitute(a:file, '_', '-', 'g')
  return substitute(ns, '/', '.', 'g')
endfunction

function! CreateSrcNsFromSrcFile(file)
  return CreateNsFromFile(split(a:file, '/src/')[-1])
endfunction

function! CreateSpecNsFromSpecFile(file)
  return CreateNsFromFile(split(a:file, '/spec/')[-1])
endfunction

function! CreateSrcNsFromSpecFile(file)
  return substitute(CreateSpecNs(a:file), '-spec', '', 'g')
endfunction

These definitions are stored within ftplugin/clojure.vim, and use simple substitutions together with some knowledge of src and spec directory structures in order to figure out the correct namespace.

The spec snippet can be defined as follows.

snippet spec
  (ns `CreateSpecNsFromSpecFile(expand('%:p:r'))`
    (require [speclj.core :refer :all]
             [`CreateSrcNsFromSpecFile(expand('%:p:r'))` :refer :all]))

Note the use of expand(%:p:r) to retrieve the full file path minus the file extension. As an added benefit, we can also then define a better ns that also uses the filename to fill in a sensible default:

snippet ns
  (ns `CreateSrcNsFromSrcFile(expand('%:p:r'))`
      ${0})

The power of snippets

Vim is highly customizable and that means that we can often build features above and beyond what a fully-featured IDE could offer us. UltiSnips interpolation is a powerful feature that can aid even with writing in a terse language such as Clojure.

About the author

Daniel Irvine is a software craftsman at 8th Light, based in London. These days he prefers to code in Clojure and Ruby, despite having been a C++ and C# developer for the majority of his career.

For a longer bio please see danielirvine.com. To contact Daniel, send a tweet to @d_ir or use the comments section below.

Twitter GitHub Facebook Instagram dirv.me