Hi, I'm Tomek.

I'm a software engineer and I solve problems by (not) writing Clojure. You can find me as @_tomekw on Twitter, @tomekw on Mastodon, or @tomekw on Github. I write (mostly) about programming.

(N)vim for Clojure development

Aug 24, 2020

Everything you need to write Clojure using (N)vim.

I used different editors and IDEs over the years: Netbeans, Vim, Emacs, and IntelliJ. And on all of them, I always enabled Vim keybindings.

Few months ago, after more than 10 years writing Ruby, I started my first commercial role as a Clojure Software Engineer at Commsor! I’m lazy, so I picked Cursive IDE (which is great BTW!), with IdeaVim plugin enabled. Everything worked out of the box, but after while I, obviously, started to envy my colleagues their custom workflows and setups…

Now that I’m sure I am Vim person to the bone - why not to give it a go? I decided to configure a pretty minimal, as close to the Vim philosophy, setup as possible. Here it is!

Choosing Vim distribution

I’m on MacOS, so I could either use MacVim distribution, console nvim, or some GUI client for it. I decided to try out VimR and I’m not disappointed:

$ brew install vimr

Essential plugins

I decided to manage my plugin dependencies with vim-plug. It keeps the configuration file small and readable, and just gets the job done:

" ~/.config/nvim/init.vim

call plug#begin()

" Plugins to install
Plug 'first/plugin-vim'
Plug 'second/plugin-vim'

call plug#end()

" Rest to the configuation file
" ...

To install them we can reloaded the current file (~/.config/nvim/init.vim) with :so % and run :PlugInstall.

I started by including:

Plug 'tpope/vim-sensible'
Plug 'vim-airline/vim-airline'

which provide universal set of defaults and a lean status line.

For file and project management I picked ctrlp.vim:

Plug 'ctrlpvim/ctrlp.vim'

The only custom configuration I added was setting up additional project root marker and a different user command to list files based on Git index:

let g:ctrlp_root_markers = ['deps.edn']
let g:ctrlp_user_command = ['.git', 'cd %s && git ls-files -co --exclude-standard']

Also, I chose Apprentice as my color scheme:

Plug 'romainl/Apprentice'

colorscheme apprentice

Mandatory screenshot:

Apprentice

The must-have plugin for everyone is:

Plug 'axelf4/vim-strip-trailing-whitespace'

which removes trailing whitespace on modified lines before saving.

Last but not least there is support for searching across the files in the project. ack.vim supported by ripgrep works perfectly! Importantly, it takes .gitignore settings into account.

$ brew install ripgrep

and

Plug 'mileszs/ack.vim'

Custom configuration sets the ripgrep as a backend, closes the search results popup after choosing the result, and stops the plugin from jumping to the first result automatically.

let g:ackprg = 'rg --vimgrep'
let g:ack_autoclose = 1
cnoreabbrev Ack Ack!

All of that alone would be a potent general-purpose Vim setup.

Clojure support

To efficiently write Clojure code I needed syntax highlighting, structural editing support, REPL management, and context-aware autocomplete.

These three plugins:

Plug 'guns/vim-clojure-highlight'
Plug 'guns/vim-clojure-static'
Plug 'luochen1990/rainbow'

give me syntax highlighting, indentation, and rainbow parentheses to better distinguish forms visually.

Next two plugins:

Plug 'guns/vim-sexp'
Plug 'tpope/vim-sexp-mappings-for-regular-people'

provide structural editing support with vim-like mappings.

These five:

Plug 'clojure-vim/vim-jack-in'
Plug 'radenling/vim-dispatch-neovim'
Plug 'SevereOverfl0w/vim-replant', { 'do': ':UpdateRemotePlugins' }
Plug 'tpope/vim-dispatch'
Plug 'tpope/vim-fireplace'

allow me to start and / or connect to existing nREPL session, with deps.edn support by just invoking:

:Clj -A:alias_1:alias_2

vim-replant adds handy keybindings for working with Clojure REPLs. Just invoke <LocalLeader>rf to refresh namespaces by automatically calling your common (stop)/(start) functions! Magic!

And you know what? All of this doesn’t need a single line of custom configuration. I love the sane defaults and conventions!

Context-aware autocomplete needs more work, but it’s worth the effort.

Plug 'clojure-vim/async-clj-omni'
Plug 'prabirshrestha/asyncomplete.vim'

These two plugins provide autocomplete which understands your code by using the existing REPL connection! asyncomplete doesn’t come with any sources enabled and it needs to be registered:

au User asyncomplete_setup call asyncomplete#register_source({
    \ 'name': 'async_clj_omni',
    \ 'whitelist': ['clojure'],
    \ 'completor': function('async_clj_omni#sources#complete'),
    \ })

And that’s all! It’s ready to help you keep all the parentheses balanced :)

The complete configuration file, with few additional plugins, can be found on my tomekw/dontfiles repo. I’m sure the current setup will grow in the future, but you can track the progress by following me on Twitter!

Do you like what to see here?

Consider subscribing to my newsletter. Get helpful guides, articles, and projects announcements. Low traffic, no spam.

Or subscribe to my Atom feed.