Using neovim for Scala development
2018-09-12
If you would like to use neovim for their Scala development workflow then this guide may get you started.
It is possible to simply edit the source files but some configuration and plugins will make your workflow way more productive. This includes using projects like ENSIME, SBT and several plugins for neovim.
Setting up SBT
As SBT is the current default for projects you need to configure it a bit. The ENSIME project provides the necessary pieces to work with text editors.
First modify your global plugins file which is in either
~/.sbt/0.13/plugins/plugins.sbt
or ~/.sbt/1.0/plugins/plugins.sbt
or
both to include ensime-sbt:
addSbtPlugin("org.ensime" % "sbt-ensime" % "2.6.1")
Afterwards it should be configured in your global sbt configuration which
is located in ~/.sbt/0.13/global.sbt
or ~/.sbt/1.0/global.sbt
:
// Custom settings for ENSIME
ensimeJavaFlags in ThisBuild := Seq(
"-Xss2m",
"-Xms2g",
"-Xmx2g",
"-XX:MaxMetaspaceSize=512m"
)
// You only need this if the scala version of your project does not match
// the scala version of ENSIME.
ensimeIgnoreScalaMismatch in ThisBuild := true
// Prevent Ctrl+C killing sbt.
cancelable in Global := true
// Enable coloured scala repl.
initialize ~= (_ => if (ConsoleLogger.formatEnabled) sys.props("scala.color") = "true")
Setting up neovim
At least the ensime-vim plugin will be needed. But to be more productive I suggest adding some more plugins:
- ctrlp
- deoplete
- rainbow_parentheses
- vim-scala
- vim-easytags
- tagbar
I maintain a complete vim configuration repository
which contains configuration files for exctags and neovim. To get you
started here is a more compact init.vim
file for neovim:
set nocompatible
set bs=2
set t_Co=256
" Install Vim-Plug if missing
" ---------------------------
if empty(glob('~/.local/share/nvim/site/autoload/plug.vim'))
silent !curl -fLSso ~/.local/share/nvim/site/autoload/plug.vim --create-dirs
\ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
endif
" Plugins via Vim-Plug
" --------------------
call plug#begin('~/.config/nvim/bundle')
Plug 'mileszs/ack.vim'
Plug 'Chiel92/vim-autoformat'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'Shougo/deoplete.nvim'
Plug 'ensime/ensime-vim'
Plug 'vim-scripts/FuzzyFinder'
Plug 'vim-scripts/L9'
Plug 'itchyny/lightline.vim'
Plug 'scrooloose/nerdcommenter'
Plug 'scrooloose/nerdtree'
Plug 'myusuf3/numbers.vim'
Plug 'junegunn/rainbow_parentheses.vim'
Plug 'scrooloose/syntastic'
Plug 'majutsushi/tagbar'
Plug 'tpope/vim-classpath'
Plug 'kchmck/vim-coffee-script'
Plug 'altercation/vim-colors-solarized'
Plug 'tpope/vim-dispatch'
Plug 'xolox/vim-easytags'
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'
Plug 'xolox/vim-misc'
Plug 'matze/vim-move'
Plug 'tpope/vim-projectionist'
Plug 'derekwyatt/vim-scala'
call plug#end()
" Some general settings
" ---------------------
let g:solarized_termcolors=256
colorscheme solarized
set background=light
set autoindent
set shiftwidth=2
set showmode
set showmatch
set ruler
set nojoinspaces
set cpo+=$
set whichwrap=""
set modelines=0
set nobackup
set encoding=utf-8
set wildmenu
set laststatus=2
set number
filetype plugin indent on
syntax enable
set hlsearch
set incsearch
se cursorline
" java support
" ------------
autocmd FileType java setlocal expandtab shiftwidth=4 tabstop=4 softtabstop=4
" File Browser
" ------------
" hide some files and remove stupid help
let g:explHideFiles='^\.,.*\.sw[po]$,.*\.pyc$'
let g:explDetailedHelp=0
map :Explore!<CR>
" Auto-Format
" -----------
noremap <F3> :Autoformat<CR>
let g:formatdef_scalafmt = "'scalafmt --stdin'"
let g:formatters_scala = ['scalafmt']
" Tagbar
" -------
nmap <F8> :TagbarToggle<CR>
let g:tagbar_left = 1
" NerdTree
" --------
nmap <F9> :NERDTreeToggle<CR>
" Tagbar Scala Support
" --------------------
let g:tagbar_type_scala = {
\ 'ctagstype' : 'Scala',
\ 'kinds' : [
\ 'p:packages:1',
\ 'V:values',
\ 'v:variables',
\ 'T:types',
\ 't:traits',
\ 'o:objects',
\ 'a:aclasses',
\ 'c:classes',
\ 'r:cclasses',
\ 'm:methods'
\ ]
\ }
" Syntastic
" ---------
let g:syntastic_mode_map = { 'mode': 'passive', 'active_filetypes': ['ruby', 'php', 'python'], 'passive_filetypes': ['scala'] }
" The Silver Searcher (via ack.vim)
" ---------------------------------
if executable('ag')
let g:ackprg = 'ag --nogroup --nocolor --column --vimgrep'
endif
" Save system files via :w!!
" --------------------------
cmap w!! %!sudo tee > /dev/null %
" Avoid easytags updating too often
" ---------------------------------
let g:easytags_updatetime_min=4000
" Deoplete (NeoComplete for nvim)
" -------------------------------
let g:deoplete#enable_at_startup = 1
autocmd InsertLeave,CompleteDone * if pumvisible() == 0 | pclose | endif
" Lightline configuration
" -----------------------
let g:lightline = {
\ 'colorscheme': 'solarized',
\ 'mode_map': { 'c': 'NORMAL' },
\ 'active': {
\ 'left': [ [ 'mode', 'paste' ], [ 'fugitive', 'filename' ] ]
\ },
\ 'component_function': {
\ 'modified': 'LightlineModified',
\ 'readonly': 'LightlineReadonly',
\ 'fugitive': 'LightlineFugitive',
\ 'filename': 'LightlineFilename',
\ 'fileformat': 'LightlineFileformat',
\ 'filetype': 'LightlineFiletype',
\ 'fileencoding': 'LightlineFileencoding',
\ 'mode': 'LightlineMode',
\ },
\ 'separator': { 'left': '', 'right': '' },
\ 'subseparator': { 'left': '', 'right': '' }
\ }
function! LightlineModified()
return &ft =~ 'help\|vimfiler\|gundo' ? '' : &modified ? '+' : &modifiable ? '' : '-'
endfunction
function! LightlineReadonly()
return &ft !~? 'help\|vimfiler\|gundo' && &readonly ? '' : ''
endfunction
function! LightlineFilename()
let fname = expand('%:t')
return fname == 'ControlP' && has_key(g:lightline, 'ctrlp_item') ? g:lightline.ctrlp_item :
\ fname == '__Tagbar__' ? g:lightline.fname :
\ fname =~ '__Gundo\|NERD_tree' ? '' :
\ &ft == 'vimfiler' ? vimfiler#get_status_string() :
\ &ft == 'unite' ? unite#get_status_string() :
\ &ft == 'vimshell' ? vimshell#get_status_string() :
\ ('' != LightlineReadonly() ? LightlineReadonly() . ' ' : '') .
\ ('' != fname ? fname : '[No Name]') .
\ ('' != LightlineModified() ? ' ' . LightlineModified() : '')
endfunction
function! LightlineFugitive()
if &ft !~? 'vimfiler\|gundo' && exists("*fugitive#head")
let branch = fugitive#head()
return branch !=# '' ? ' '.branch : ''
endif
return ''
endfunction
function! LightlineFileformat()
return winwidth(0) > 70 ? &fileformat : ''
endfunction
function! LightlineFiletype()
return winwidth(0) > 70 ? (&filetype !=# '' ? &filetype : 'no ft') : ''
endfunction
function! LightlineFileencoding()
return winwidth(0) > 70 ? (&fenc !=# '' ? &fenc : &enc) : ''
endfunction
function! LightlineMode()
let fname = expand('%:t')
return fname == '__Tagbar__' ? 'Tagbar' :
\ fname == 'ControlP' ? 'CtrlP' :
\ fname == '__Gundo__' ? 'Gundo' :
\ fname == '__Gundo_Preview__' ? 'Gundo Preview' :
\ fname =~ 'NERD_tree' ? 'NERDTree' :
\ &ft == 'unite' ? 'Unite' :
\ &ft == 'vimfiler' ? 'VimFiler' :
\ &ft == 'vimshell' ? 'VimShell' :
\ winwidth(0) > 60 ? lightline#mode() : ''
endfunction
function! CtrlPMark()
if expand('%:t') =~ 'ControlP' && has_key(g:lightline, 'ctrlp_item')
call lightline#link('iR'[g:lightline.ctrlp_regex])
return lightline#concatenate([g:lightline.ctrlp_prev, g:lightline.ctrlp_item
\ , g:lightline.ctrlp_next], 0)
else
return ''
endif
endfunction
let g:ctrlp_status_func = {
\ 'main': 'CtrlPStatusFunc_1',
\ 'prog': 'CtrlPStatusFunc_2',
\ }
function! CtrlPStatusFunc_1(focus, byfname, regex, prev, item, next, marked)
let g:lightline.ctrlp_regex = a:regex
let g:lightline.ctrlp_prev = a:prev
let g:lightline.ctrlp_item = a:item
let g:lightline.ctrlp_next = a:next
return lightline#statusline(0)
endfunction
function! CtrlPStatusFunc_2(str)
return lightline#statusline(0)
endfunction
let g:tagbar_status_func = 'TagbarStatusFunc'
function! TagbarStatusFunc(current, sort, fname, ...) abort
let g:lightline.fname = a:fname
return lightline#statusline(0)
endfunction
augroup AutoSyntastic
autocmd!
autocmd BufWritePost *.c,*.cpp call s:syntastic()
augroup END
function! s:syntastic()
SyntasticCheck
call lightline#update()
endfunction
let g:unite_force_overwrite_statusline = 0
let g:vimfiler_force_overwrite_statusline = 0
let g:vimshell_force_overwrite_statusline = 0
" Scala
" -----
" Indenting scaladoc the right way (vim-scala).
let g:scala_scaladoc_indent = 1
" Map some keys for ENSIME
au FileType java,scala nnoremap <leader>t :EnType<CR>
au FileType java,scala xnoremap <leader>t :EnType selection<CR>
au FileType java,scala nnoremap <leader>T :EnTypeCheck<CR>
au FileType java,scala nnoremap <leader>df :EnDeclaration<CR>
au FileType java,scala nnoremap <leader>db :EnDocBrowse<CR>
au FileType java,scala nnoremap <leader>i :EnInspectType<CR>
au FileType java,scala nnoremap <leader>I :EnSuggestImport<CR>
au FileType java,scala nnoremap <leader>r :EnRename<CR>
" Ctrl-P
" ------
let g:ctrlp_map = '<c-p>'
let g:ctrlp_cmd = 'CtrlPMixed'
set wildignore+=*/target/*
When editing a project file you can press <F8>
to get a tag browser on
the left side. And use <Ctrl+P>
to quickly find other files within your
project tree. Using <Ctrl+X>,<Ctr+O>
should trigger the autocompletion.
Setting up ctags
As neovim (and also vim) use tags files created by excuberant ctags by default
all you need is a basic configuration for which should reside in ~/.ctags
:
--exclude=_darcs
--exclude=.ensime_cache
--exclude=.git
--exclude=.pijul
--exclude=.svn
--exclude=log
--exclude=project/target
--exclude=public
--exclude=target
--exclude=tmp
--exclude=vendor
--recurse=yes
--tag-relative=yes
--langdef=Clojure
--langmap=Clojure:.clj
--regex-clojure=/\([ \t]*create-ns[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/n,namespace/
--regex-clojure=/\([ \t]*def[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/d,definition/
--regex-clojure=/\([ \t]*defn-?[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/f,function/
--regex-clojure=/\([ \t]*defmacro[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/m,macro/
--regex-clojure=/\([ \t]*definline[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/i,inline/
--regex-clojure=/\([ \t]*defmulti[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/a,multimethod definition/
--regex-clojure=/\([ \t]*defmethod[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/b,multimethod instance/
--regex-clojure=/\([ \t]*defonce[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/c,definition (once)/
--regex-clojure=/\([ \t]*defstruct[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/s,struct/
--regex-clojure=/\([ \t]*intern[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/v,intern/
--regex-clojure=/\([ \t]*ns[ \t]+([-[:alnum:]*+!_:\/.?]+)/\1/n,namespace/
--langdef=groovy
--langmap=groovy:.groovy
--regex-groovy=/^[ \t][(private|public|protected) ( \t)][A-Za-z0-9<>]+[ \t]+([A-Za-z0-9]+)[ \t](.)[ \t]{/\1/f,function,functions/
--regex-groovy=/^[ \t]*def[ \t]+([A-Za-z0-9_]+)[ \t]\=[ \t]{/\1/f,function,functions/
--regex-groovy=/^[ \t]*private def[ \t]+([A-Za-z0-9_]+)[ \t]/\1/v,private,private variables/
--regex-groovy=/^[ \t]def[ \t]+([A-Za-z0-9_]+)[ \t]/\1/u,public,public variables/
--regex-groovy=/^[ \t][abstract ( \t)][(private|public) ( \t)]class[ \t]+([A-Za-z0-9_]+)[ \t]/\1/c,class,classes/
--regex-groovy=/^[ \t][abstract ( \t)][(private|public) ( \t)]enum[ \t]+([A-Za-z0-9_]+)[ \t]/\1/c,class,classes/
--langdef=Scala
--langmap=Scala:.scala
--regex-Scala=/^[ \t]*class[ \t]*([a-zA-Z0-9_]+)/\1/c,classes/
--regex-Scala=/^[ \t]*object[ \t]*([a-zA-Z0-9_]+)/\1/o,objects/
--regex-Scala=/^[ \t]*trait[ \t]*([a-zA-Z0-9_]+)/\1/t,traits/
--regex-Scala=/^[ \t]*case[ \t]*class[ \t]*([a-zA-Z0-9_]+)/\1/r,cclasses/
--regex-Scala=/^[ \t]*abstract[ \t]*class[ \t]*([a-zA-Z0-9_]+)/\1/a,aclasses/
--regex-Scala=/^[ \t]*def[ \t]*([a-zA-Z0-9_=]+)[ \t]*.*[:=]/\1/m,methods/
--regex-Scala=/[ \t]*val[ \t]*([a-zA-Z0-9_]+)[ \t]*[:=]/\1/V,values/
--regex-Scala=/[ \t]*var[ \t]*([a-zA-Z0-9_]+)[ \t]*[:=]/\1/v,variables/
--regex-Scala=/^[ \t]*type[ \t]*([a-zA-Z0-9_]+)[ \t]*[\[<>=]/\1/T,types/
--regex-Scala=/^[ \t]*import[ \t]*([a-zA-Z0-9_{}., \t=>]+$)/\1/i,includes/
--regex-Scala=/^[ \t]*package[ \t]*([a-zA-Z0-9_.]+$)/\1/p,packages/
--languages=c,c++,clojure,html,java,lisp,make,ruby,scala,sh,sml,sql
You may want to configure it further but the mappings for scala should be enough to get you started.
To create a tags
file in your project root just issue the exctags .
command.
Wrapping up and workflow
Now you are ready to work on your scala projects using neovim. :-)
To get started you can use the following sequence of commands from within a project directory:
% exctags .
% sbt clean ensimeConfig test:compile ensimeServerIndex
The ENSIME indexing part might take a while so this is a good opportunity to get some tea or coffee. ;-)
When using shortcuts for the appropriate ENSIME commands you can navigate
your project with :EnDeclaration
, show documentation with :EnDocBrowse
and see typing information via :EnType
or typecheck the whole file with
:EnTypeCheck
.
Using the famous deoplete plugin
autocompletion should work with <Ctrl+X>,<Ctrl+O>
.
Remember to recompile your project frequently either by hand or by using
something like ~compile
on the sbt console.