Using neovim for Scala development - Take 2
2019-03-27
This is an updated version of my previous guide and reflects the latests changes in the scala ecosystem. So if you would like to use neovim for your Scala development work flow then this guide will get you started.
Because ENSIME is dead and gone we will use the metals language server for Scala.
Getting started
First you should install coursier as it will be needed further on. This is as simply as this:
% curl -L -o ~/.local/bin/coursier https://git.io/coursier
The above command will install the coursier application into the .local/bin
directory of your home directory which should be available in the $PATH
of
your shell.
Now you need to bootstrap metals which is done like this:
% coursier bootstrap \
--java-opt -XX:+UseG1GC \
--java-opt -XX:+UseStringDeduplication \
--java-opt -Xss4m \
--java-opt -Xms100m \
--java-opt -Dmetals.client=vim-lsc \
--java-opt -Dmetals.sbt-script=/usr/local/bin/sbt \
org.scalameta:metals_2.12:0.4.4 \
-r bintray:scalacenter/releases \
-r sonatype:snapshots \
-o ~/.local/bin/metals-vim -f
This will put the metals binary configured for vim-lsc into the
.local/bin
directory in your home. The parameter
-Dmetals.sbt-script
above points to a custom sbt installation on my
machine. If you want to use the bundled launcher just omit that line.
Setting up neovim
The minimum you need on the neovim side are just two plugins: vim-lsc and vim-scala.
A minimal configuration using vim-plug looks like this:
Plug 'derekwyatt/vim-scala'
Plug 'natebosch/vim-lsc'
au BufRead,BufNewFile *.sbt set filetype=scala
let g:lsc_enable_autocomplete = v:false
let g:lsc_server_commands = {
\ 'scala': {
\ 'command': 'metals-vim',
\ 'log_level': 'Log'
\ }
\}
However for a more complete configuration, here is my current neovim configuration.
" ~./vimrc
" Kompatibilitätsmodus abschalten
set nocompatible
" Backspace (FreeBSD)
set bs=2
" 256 Farben
"set t_Co=256
" True Colour Farben
if (has("termguicolors"))
set termguicolors
endif
" 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 'docunext/closetag.vim'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'Shougo/deoplete.nvim'
Plug 'mattn/emmet-vim'
Plug 'itchyny/lightline.vim'
Plug 'scrooloose/nerdcommenter'
Plug 'myusuf3/numbers.vim'
Plug 'scrooloose/syntastic'
Plug 'majutsushi/tagbar'
Plug 'SirVer/ultisnips'
Plug 'xolox/vim-easytags'
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'
Plug 'godlygeek/tabular'
Plug 'xolox/vim-misc'
Plug 'matze/vim-move'
Plug 'vim-pandoc/vim-pandoc'
Plug 'vim-pandoc/vim-pandoc-after'
Plug 'vim-pandoc/vim-markdownfootnotes'
Plug 'vim-pandoc/vim-rmarkdown'
Plug 'vim-pandoc/vim-pandoc-syntax'
Plug 'derekwyatt/vim-scala'
Plug 'natebosch/vim-lsc'
Plug 'rust-lang/rust.vim'
Plug 'racer-rust/vim-racer'
" Colour schemes
Plug 'chriskempson/base16-vim'
Plug 'drewtempelmeyer/palenight.vim'
Plug 'altercation/vim-colors-solarized'
Plug 'whatyouhide/vim-gotham'
call plug#end()
" Some general settings
" ---------------------
let base16colorspace=256
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
" Reconfigure some keyboard shortcuts.
" ------------------------------------
let mapleader="´"
" Execute commands via u umlaut.
nnoremap ü :
" Re-map git diff shortcuts.
nnoremap ßc ]c
" Re-map spell checking shortcuts.
nnoremap ßs ]s
nnoremap c0 z=
" 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>
" Ignoring stderr is a workaround, see https://github.com/scalameta/scalafmt/issues/1236
let g:formatdef_scalafmt = "'scalafmt --stdin 2>/dev/null'"
let g:formatters_scala = ['scalafmt']
" Tagbar
" -------
nmap <F8> :TagbarToggle<CR>
let g:tagbar_left = 1
" 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'
\ ]
\ }
" Better Search
" -------------
set hlsearch
set incsearch
" Highlight the current line
" --------------------------
se cursorline
" 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
" UltiSnips
" ---------
let g:UltiSnipsExpandTrigger="<C-j>"
" Pandoc
" ------
let g:pandoc#spell#default_langs = [ "de_19", "en_gb" ]
" Rust
" ----
" Settings for autocomplete via rust-racer
set hidden
let g:racer_experimental_completer = 1
"au FileType rust nmap gd <Plug>(rust-def)
"au FileType rust nmap gs <Plug>(rust-def-split)
"au FileType rust nmap gx <Plug>(rust-def-vertical)
"au FileType rust nmap <leader>gd <Plug>(rust-doc)
" Scala
" -----
" Indenting scaladoc the right way (vim-scala).
let g:scala_scaladoc_indent = 1
" Map SBT configuration files to scala
au BufRead,BufNewFile *.sbt set filetype=scala
" Configuration for vim-lsc
let g:lsc_enable_autocomplete = v:false
let g:lsc_server_commands = {
\ 'scala': {
\ 'command': 'metals-vim',
\ 'log_level': 'Log'
\ }
\}
" Ctrl-P
" ------
let g:ctrlp_map = '<c-p>'
let g:ctrlp_cmd = 'CtrlPMixed'
set wildignore+=*/target/*
" 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 fullname = expand('%')
let gitversion = ''
if fullname =~? 'fugitive://.*/\.git//0/.*'
let gitversion = ' (INDEX)'
elseif fullname =~? 'fugitive://.*/\.git//2/.*'
let gitversion = ' (TARGET)'
elseif fullname =~? 'fugitive://.*/\.git//3/.*'
let gitversion = ' (MERGE)'
elseif &diff == 1
let gitversion = ' (WORK COPY)'
endif
let branch = fugitive#head()
return branch !=# '' ? ' '.branch.gitversion : ''
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
Please note the remapping of leader to umlaut keys! You may want to change that.
Now enjoy your new Scala experience on the terminal. :-)