I personally choose to work in Vim whenever possible, and have spent some time getting a smooth experience with Scala. Like with Bash and Unix tools, I enjoy reading about other developers’ workflows and tips, and hopefully mine help you!
My flavor of Vim is Neovim and my full init.vim + dotfiles are over on Github. For working in Scala in Vim, I use:
1. Syntax
vim-scala, not much to say there.
2. Completion
I use deoplete, and it has worked without issue. However, I don’t have strong preferences here and there are many other options in Vim.
2. Jump to File
FZF works really well and really fast. I used to use ctrlp but it struggled with the size of the codebase I worked in, and FZF works really well with tags. I still fall back onto ctrlp on machines where I don’t want to have to install the FZF binary.
3. Jump to definition
ctags + FZF can get you quite far for jumping to definition.
If you’ve never used tags before, imagine searching for definitions (e.g. def ([a-zA-Z0-9]*)
,
along with objects, traits, etc) across your codebase, recording the file and line where they occur.
You then have an index of definitions that you can quickly search across later. This is all tags
are, and FZF provides fuzzy search on top of that.
Here’s how I’ve set it up:
" FZF / Ctrlp for file navigation
if executable('fzf')
Plug '/usr/local/opt/fzf'
Plug 'junegunn/fzf.vim'
else
Plug 'ctrlpvim/ctrlp.vim'
endif
" Ripgrep for file indexing, sort of faster, but not really, but also why not use ripgrep for
everything
if executable('rg')
let $FZF_DEFAULT_COMMAND = 'rg --files --no-messages "" .'
endif
" Use FZF for files and tags if available, otherwise fall back onto CtrlP
" <leader>j will search for tag using word under cursor
let g:fzf_command_prefix = 'Fzf'
if executable('fzf')
nnoremap <leader>v :FzfFiles<cr>
nnoremap <leader>u :FzfTags<cr>
nnoremap <leader>j :call fzf#vim#tags("'".expand('<cword>'))<cr>
else
nnoremap <leader>v :CtrlP<Space><cr>
endif
For ctags, you also need to set up your ~/.ctags
file (below is from Derek
Wyatt’s), and on OSX you need
to install a newer version of ctags than the preinstalled version:
--langdef=scala
--langmap=scala:.scala
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\4/c,classes/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*object[ \t]+([a-zA-Z0-9_]+)/\4/o,objects/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*((abstract|final|sealed|implicit|lazy)[ \t ]*)*case class[ \t ]+([a-zA-Z0-9_]+)/\6/C,case classes/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*case object[ \t]+([a-zA-Z0-9_]+)/\4/O,case objects/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*trait[ \t]+([a-zA-Z0-9_]+)/\4/t,traits/
--regex-scala=/^[ \t]*type[ \t]+([a-zA-Z0-9_]+)/\1/T,types/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy|override|private[^ ]*(\[[a-z]*\])*|protected)[ \t]*)*def[ \t]+([a-zA-Z0-9_]+)/\4/m,methods/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy|override|private[^ ]*|protected)[ \t]*)*val[ \t]+([a-zA-Z0-9_]+)/\3/V,values/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy|override|private[^ ]*|protected)[ \t]*)*var[ \t]+([a-zA-Z0-9_]+)/\3/v,variables/
--regex-scala=/^[ \t]*package[ \t]+([a-zA-Z0-9_.]+)/\1/p,packages/
Afterwards this looks like:
4. Jump to error
I’ve had success with Sarsi. I’ll start up sbt with sarsi-sbt, and then running neovim from the same directory, sbt errors show up as quickfix errors. This works well if you’re already keeping sbt in a separate window/tmux pane, but maybe you’d prefer something like sbt-vim that just keeps sbt in the background.
I also use vim-togglelist to toggle the quickfix
window with <leader>q
. <leader>l
is mapped to the location list that other plugins use, like
Neomake, which I use for rust.
5. Searching & Finding Usages
Ripgrep holds up to its reputation of being ridiculously
fast, and I have alias rgs=rg -i -tscala
for search Scala. I also mapped it as a command in vim
:Rg
but I rarely use this because the number of results that will populate the window.
Other
Debugging & Types
Most of the time, I get by using println
for debugging and putting in a bogus type to see what the
type error is:
// Poor man's type inference
scala> val x: Int = PooledHttp1Client()
<console>:20: error: type mismatch;
found : org.http4s.client.Client
required: Int
val x: Int = PooledHttp1Client()
Other Tools
I’ve also seen others using
IntelliJ
Even if you prefer life in an IDE like IntelliJ, Vim can be a quick way of navigating and exploring projects. But for some things, I find Vim is not enough. I do occasionally work in IntelliJ, and can appreciate
- Context aware completion, e.g. accounting for if you’re using a Twitter future or standard library future
- Debugger. I rarely find myself needing a debugger in Scala, but when I do, it’s hard to work without it
- When I’m spending more time reading code than writing, navigating and exploring code is slightly
superior. I appreciate the visual editor, and being able to directly jump to
|+|
, when it’s your first time seeing it can be helpful.