I’ve been asked a few times already to share my configuration to use Vim along with the Dotty Language Server. Given that Dotty 0.11.0-RC1 was released yesterday and that this release contains many improvements to Dotty IDE, there couldn’t be a better time to finally write this down.

If you’re in a hurry and know your way around Vim and Dotty, jump to the short instructions.

What is Dotty IDE?

Dotty IDE is the combination of two components:

  • The Dotty Language Server, which is a tool that implements the Language Server Protocol (LSP) and is used to answer the queries of the text editor
  • vscode-dotty, which is a VSCode extension that teaches VSCode how to start the Dotty Language Server and adds supports for some other features (worksheets, for instance).

VSCode is just one of the possible clients of the Dotty Language Server, therefore nothing prevents us from reusing it with another editor. That’s exactly the philosophy behind LSP.

Setting everything up

For the rest of this post, I’ll assume that you have already installed Vim, sbt and Coursier on your machine.

1 - Setting up a Dotty project

The Dotty Language Server works only with Dotty projects. If you don’t have a Dotty project already, you can set one up with:

$ sbt new lampepfl/dotty.g8

Once the project is ready, you can cd into the newly created directory and start sbt to make sure that you can compile with Dotty:

sbt:dotty-simple> run
(...)
[info] Packaging /Users/martin/foobar/target/scala-0.11/dotty-simple_0.11-0.1.0.jar ...                                                                                  
[info] Done packaging.
[info] Running Main
Hello world!
I was compiled by dotty :)
[success] Total time: 1 s, completed Dec 1, 2018 6:52:53 PM

If you have it installed and are curious, you can type launchIDE in sbt’s console to try VSCode out: try to hover over symbols, or type some code, and you’ll get some help from the language server. Let’s get something even nicer in a real editor!

2 - Installing Vim plugins

If you don’t use a plugin manager for Vim, I suggest that you use Vundle. The installation instructions are simple, and so is using Vundle.

I’ll assume that you use Vundle.

3 - We need an LSP client for Vim

There are a few LSP clients for Vim. Each has a different set of pros and cons. I am using vim-lsc, because:

  • It is lightweight
  • Almost everything works out of the box
  • It is easy to configure and hack
  • It has no dependencies
  • It works on Vim and NeoVim

The installation is simple, and so is the configuration.

For vim-lsc to work, you’ll need to have syntax highlighting enabled in Vim. Add the following to you .vimrc:

syntax enable

Then, add vim-lsc to your plugins in ~/.vimrc:

Plugin 'natebosch/vim-lsc'

To enable the default key bindings of vim-lsc, add the following to you .vimrc:

let g:lsc_auto_map = v:true

By itself, the LSP client is not very helpful yet. We still need to teach it how to actually start the Dotty Language Server when a Scala file is opened. Add the following to your .vimrc,

let g:lsc_server_commands = {
\ 'scala': "start-dotty-ide"
\ }

This will tell vim-lsc to run start-dotty-ide when a file whose filetype is scala is opened for the first time. We will create this script soon, but first, let’s install the plugins that we specified in .vimrc: exit Vim, restart it and type :PluginInstall.

To create the startup script, Put the following in a file called start-dotty-ide, and write it somewhere on your $PATH (you can also put it somewhere else, but remember to give the absolute path in your vim configuration):

#!/bin/sh
if [ -f ".dotty-ide.json" ]; then
    ARTIFACT="$(cat .dotty-ide-artifact)"
    coursier launch "$ARTIFACT" -M dotty.tools.languageserver.Main -- -stdio
fi

Don’t forget to make it executable:

$ chmod +x start-dotty-ide

This script will look in the directory where you opened Vim whether a file called .dotty-ide.json exists. If so, it’ll look into .dotty-ide-artifact to find the coordinates of the Dotty Language Server artifact. It’ll then give these coordinates to Coursier, which will download and start the Dotty Language Server.

Your .vimrc should look at least like this:

 " Install and set up Vundle
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#rc()
Plugin 'VundleVim/Vundle.vim'

 " Install the LSP client
Plugin 'natebosch/vim-lsc'

 " Enable syntax highlighting
syntax enable

 " Tell the LSP client how to start the server
let g:lsc_server_commands = {
\ 'scala': "start-dotty-ide"
\ }

 " Use the default bindings
let g:lsc_auto_map = v:true

4 - Create .dotty-ide.json

.dotty-ide.json contains a description of your build (what projects exists, what are their dependencies, what compiler options you use, and so on), and is necessary for the Dotty Language Server to do its job. It can be automatically created by sbt using the task configureIDE:

sbt:dotty-simple> configureIDE

Example of content of .dotty-ide.json:

[ {
  "id" : "root/compile",
  "compilerVersion" : "0.11.0-RC1",
  "compilerArguments" : [ ],
  "sourceDirectories" : [ "/Users/martin/Desktop/foobar/src/main/scala", (...) ],
  "dependencyClasspath" : [ (...) ],
  "classDirectory" : "/Users/martin/Desktop/foobar/target/scala-0.11/classes"
}, (...)
]

You should try to manually run start-dotty-ide now, so that you’ll notice if something is going wrong with your script, and to let Coursier fill its cache:

$ start-dotty-ide
(...)
https://repo1.maven.org/maven2/ch/epfl/lamp/dotty-compiler_0.11/0.11.0-RC1/dotty-compil…
  100.0% [##########] 9.3 MiB (2.1 MiB / s)
  Starting server

You can exit with CTRL-C.

5 - Let’s start Vim!

We’re all set. Start vim at the root of the project created by sbt new, and navigate to src/main/scala/Main.scala. Move the cursor to println on line 4, and press K (shift + k). On top of the screen, the preview window should show you the method’s signature: (x: Any): Unit.

Congratulations, you have a working setup of Dotty IDE with Vim!

6 - Wow, what can it do?

Dotty IDE and the Dotty Language Server are still actively in development, and so is vim-lsc. Nevertheless, many of the features that you’d expect from an IDE work:

Completions as you type:

Completions as you type

Go to definition:

Go to definition

Information on hover:

Information on hover

Find all references:

Find all references

Rename symbol:

Rename symbol

And more! Look at what vim-lsc supports to get an idea of what you can do. Dotty IDE supports all of those, except code actions.

Some configuration tips

You can configure how you want Vim to show you references to the symbol under the cursor, or how you want the currently active parameter to be highlighted when you request signature help. I find it more readable to have them underlined, so I added this to my .vimrc:

highlight lscReference cterm=underline
highlight lscCurrentParameter cterm=underline

I don’t really like the default configuration for the completion menu, so I use:

set completeopt=menu,menuone,longest
" Use <C-j/k> for going down/up in completion menu
inoremap <expr> <C-j> pumvisible() ? "\<C-n>" : "\<C-j>"
inoremap <expr> <C-k> pumvisible() ? "\<C-p>" : "\<C-k>"

Also, I want to be able to use <enter> to select the highlighted completion:

" Enter selects the highlighted completion when the menu is shown
imap <expr> <CR> pumvisible()
                 \ ? "\<C-Y>"
                 \ : "<Plug>delimitMateCR"

Note that the else branch uses <Plug>delimitMateCR, because I still want indentation to work properly. If you don’t use delimitMate, you can replace the else branch with "\<C-g>u\<CR>".

Finally, I don’t want the preview window to stick around when I use hover or signature help:

" Discard preview window when the cursor is moved
autocmd CursorMoved * if pumvisible() == 0|pclose|endif
autocmd InsertLeave * if pumvisible() == 0|pclose|endif

For the impatient

1 - Set up Vim

Minimal .vimrc:

set rtp+=~/.vim/bundle/Vundle.vim
call vundle#rc()
Plugin 'VundleVim/Vundle.vim'
Plugin 'natebosch/vim-lsc'

syntax enable

let g:lsc_server_commands = {
\ 'scala': "start-dotty-ide"
\ }

let g:lsc_auto_map = v:true

Exit Vim, restart it and type :PluginInstall.

2 - Write a script to start the Dotty Language Server

Write the following script in start-dotty-ide, make it executable and on your $PATH:

#!/bin/sh
if [ -f ".dotty-ide.json" ]; then
    ARTIFACT="$(cat .dotty-ide-artifact)"
    coursier launch "$ARTIFACT" -M dotty.tools.languageserver.Main -- -stdio
fi

3 - Create the configuration in sbt

sbt:your-project> configureIDE

4 - Start Vim

That’s it.

Troubleshooting

Dotty IDE is still in active development, and you will encounter errors and bugs. You can get more information about what’s happening by keeping a log of the communication between Vim and the Dotty Language Server. Create a new script start-dotty-ide-debug, with content:

#!/bin/sh
tee $HOME/log-in.txt | start-dotty-ide 2> $HOME/log-err.txt | tee $HOME/log-out.txt

Make it executable, and replace the command that is used to start the language server in your .vimrc. The messages sent by the client to the server can be read in $HOME/log-in.txt, the messages from the server to the client will be in $HOME/log-out.txt, and the error log of the server will be in $HOME/log-err.txt.

The last messages that were exchanged between the client and the server, along with stack traces can be invaluable when trying to reproduce bugs. Please include them in your bug reports!

The Dotty issue tracker lives on GitHub: https://github.com/lampepfl/dotty/issues