Alan Devlin
6 min readFeb 23, 2018

NeoVim + Scala

I am going to walk you through my vim/scala setup. I try to walk a balance between replicating IDE like features and keeping things minimal. We are going to add the following features to vim:

  • Code completion /suggestion
  • Fuzzy searching for files
  • Linting
  • Jump to definition
  • (optional) Terminal window in drawer

This walk through is for neovim — it is adaptable for vim users though — you should be able to find analogous plugins for the ones that aren’t available for vim. If you are adapting this to run on vim — my advice is to make sure you have vim 8 or later. Neovim and vim 8 have support for asynchronous commands — which makes all the difference. Some of the commands we are going to be running will take a bit of time — and if you’re running vim they are going to lock up your whole editor.

(Step 0: https://github.com/neovim/neovim/wiki/Installing-Neovim)

Next we are going to turn on code completion via Ensime. Walk through the instructions for Ensime Vim.

(Step 1: http://ensime.github.io/)

Now to configure neovim. Copy the following to ~/.config/nvim/init.vim (make it if you don’t have it) :

" - VIM PLUG
call plug#begin('~/.local/share/nvim/plugged')
Plug 'ensime/ensime-vim', { 'do': ':UpdateRemotePlugins' }
Plug 'roxma/nvim-yarp'
Plug 'roxma/vim-hug-neovim-rpc'
Plug 'cloudhead/neovim-fuzzy'
Plug 'neomake/neomake'
"Code completion with Deoplete - enabled by ensime
if has('nvim')
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
else
Plug 'Shougo/deoplete.nvim'
Plug 'roxma/nvim-yarp'
Plug 'roxma/vim-hug-neovim-rpc'
endif
let g:deoplete#enable_at_startup = 1
let g:deoplete#sources={}
let g:deoplete#sources._=['buffer', 'member', 'tag', 'file', 'omni', 'ultisnips']
let g:deoplete#omni#input_patterns={}
let g:deoplete#omni#input_patterns.scala='[^. *\t]\.\w*'
" Initialize plugin system
call plug#end()
" Use deoplete
let g:deoplete#enable_at_startup = 1
" fuzzy finder with ctrl-p
nnoremap <C-p> :FuzzyOpen<CR>
" easier split navigations
nnoremap <C-J> <C-W><C-J>
nnoremap <C-K> <C-W><C-K>
nnoremap <C-L> <C-W><C-L>
nnoremap <C-H> <C-W><C-H>
"Linting with neomake
let g:neomake_sbt_maker = {
\ 'exe': 'sbt',
\ 'args': ['-Dsbt.log.noformat=true', 'compile'],
\ 'append_file': 0,
\ 'auto_enabled': 1,
\ 'output_stream': 'stdout',
\ 'errorformat':
\ '%E[%trror]\ %f:%l:\ %m,' .
\ '%-Z[error]\ %p^,' .
\ '%-C%.%#,' .
\ '%-G%.%#'
\ }
let g:neomake_enabled_makers = ['sbt']
let g:neomake_verbose=3
" Neomake on text change
autocmd InsertLeave,TextChanged * update | Neomake! sbt

Navigate to the root directory of your scala project and type nvim, and let the fun begin.

First of all, I don’t use any file explorer plug-ins like nerdTree — I prefer the built in one. Type :Explore and away you go — it’s basically all I need.

(I am using Daniela Sfregola’s excellent category theory tutorial for demonstration: https://github.com/DanielaSfregola/tutorial-cat.git)

Now you are going to have to install a few dependencies to make our nvim configuration work:

Once you have done that re-start nvim and run :PlugInstall — and restart again. Hopefully you will not get any errors — but run :CheckHealth and make sure python2 and python3 support is enabled, and everything looks fine.

You should be getting autocomplete suggestions now! Thanks to Ensime and deoplete

Also linting should be working courtesy of Neomake and sbt. In my config it should run every time you change text from normal mode or when you leave insert mode: (Notice the red ‘x’ in the side and the highlight on the offending character)

You will also get an explanation down the bottom:

If all went well with the installation you can also use neovim-fuzzy to search for files— which I really love — hit Ctrl-p:

For jump to definition — I prefer to use ctags (Ensime has this feature but for me the performance was not great). Install as per instructions below:

Here is my .ctags config for scala:

--langdef=scala                       --langmap=scala:.scala                       --regex-scala=/^[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\1/c,classes/                       --regex-scala=/.*class[ \t]+([a-zA-Z0-9_]+)/\1/c,classes/                       --regex-scala=/^[ \t]*trait[ \t]+([a-zA-Z0-9_]+)/\1/t,traits/                       --regex-scala=/.*trait[ \t]+([a-zA-Z0-9_]+)/\1/t,traits/                       --regex-scala=/^[ \t]*type[ \t]+([a-zA-Z0-9_]+)/\1/T,types/                       --regex-scala=/^[ \t]*def[ \t]+([a-zA-Z0-9_\?]+)/\1/m,methods/                       --regex-scala=/^[ \t]*val[ \t]+([a-zA-Z0-9_]+)/\1/C,constants/                       --regex-scala=/^[ \t]*var[ \t]+([a-zA-Z0-9_]+)/\1/l,local variables/                       --regex-scala=/^[ \t]*package[ \t]+([a-zA-Z0-9_.]+)/\1/p,packages/                       --regex-scala=/^[ \t]*case class[ \t]+([a-zA-Z0-9_]+)/\1/c,case classes/                       --regex-scala=/^[ \t]*final case class[ \t]+([a-zA-Z0-9_]+)/\1/c,case classes/                       --regex-scala=/^[ \t]*object[ \t]+([a-zA-Z0-9_]+)/\1/o,objects/                       --regex-scala=/.*object[ \t]+([a-zA-Z0-9_]+)/\1/o,objects/                       --regex-scala=/^[ \t]*private def[ \t]+([a-zA-Z0-9_]+)/\1/pd,defs/

which is saved in ~/.ctags

run : ctags -R .

in your project directory and hit Ctrl-] to jump to definition

And voila! there’s my nvim + scala setup. If you want to go one step further — you can add in tmux and tmuxinator to manage windows.

Install tmux: https://github.com/tmux/tmux/wiki

and https://github.com/tmuxinator/tmuxinator

I use the following config:

# ~/.tmuxinator/tutorial-cat.ymlname: tutorial-cat         # CHANGE to your chosen name
root: ~/dev/workspace/tutorial-cat #CHANGE to your root directory
# Optional tmux socket
# socket_name: foo
# Runs before everything. Use it to start daemons etc.
# pre: sudo /etc/rc.d/mysqld start
# Runs in each window and pane before window/pane specific commands. Useful for setting up interpreter versions.
#
# pre_window: rbenv shell 2.0.0-p247
# Pass command line options to tmux. Useful for specifying a different tmux.conf.
# tmux_options: -f ~/.tmux.mac.conf
# Change the command to call tmux. This can be used by derivatives/wrappers like byobu.
# tmux_command: byobu
# Specifies (by name or index) which window will be selected on project startup. If not set, the first window is used.
# startup_window: logs
# Controls whether the tmux session should be attached to automatically. Defaults to true.
# attach: false
# Runs after everything. Use it to attach to tmux with custom options etc.
# post: tmux -CC attach -t tutorial-cat
windows:
- neovim:
layout: "6d8e,204x57,0,0[204x44,0,0,28,204x12,0,45,29]"
panes:
- nvim:
- "nvim"
- sbt:
- "sbt"
- git:
layout: "6580,204x57,0,0,30"
panes:
- p:
-

Then you can type just mux tutorial-cat from terminal and you get the following setup:

The top pane is nvim opened in your project directory. The bottom is running sbt. hit Ctrl-b down-arrow to switch to the bottom pane. I like to run ~test in this pane to re-run your tests whenever you make changes. Ctrl-b n will change to the other window which is actually just a terminal window — I use it to run git commands.

You might notice that there’s a delay after you hit escape before you are returned to normal mode. If you insert set -sg escape-time 10 into .tmux.conf then this should go away.

All of this is of course just personal choice, all of the tools are highly customizable.

By the way I am using dracula as a theme and hack-regular font.