Scala with Vim
#Preface
I use vim
(NeoVim) for my day to day TypeScript development. For my day job, I was tasked with doing some work in Scala. The README
for this application recommended installing IntelliJ IDEA and getting everything set up. While I have used IntelliJ for JVM development in the past, I would prefer to stick to the terminal with my familiar workflows. So it was time to dig into setting up my development environment for Scala.
#Local
The first thing I do when setting up a new language development stack (in neovim
or otherwise) is to get things running locally.
I should be able to, from the terminal
- Run
- Build
- Test
- (Lint/Format)
I’m on a Mac - so lets get sbt
(Scala Build Tools) and JDK ł
brew install sbt
brew install adoptopenjdk8
Now let’s get onto the first step - compiling the app with `sbt
sbt compile
Instantly I’m met with an archaic JVM error
java.lang.ClassCastException: class java.lang.UnsupportedOperationException cannot be cast to class xsbti.FullReload (java.lang.UnsupportedOperationException is in module java.base of loader 'bootstrap'; xsbti.FullReload is in unnamed module of loader 'app')
at sbt.internal.XMainConfiguration.run(XMainConfiguration.java:59)
at sbt.xMain.run(Main.scala:46)
at xsbt.boot.Launch$.$anonfun$run$1(Launch.scala:149)
at xsbt.boot.Launch$.withContextLoader(Launch.scala:176)
at xsbt.boot.Launch$.run(Launch.scala:149)
at xsbt.boot.Launch$.$anonfun$apply$1(Launch.scala:44)
at xsbt.boot.Launch$.launch(Launch.scala:159)
at xsbt.boot.Launch$.apply(Launch.scala:44)
at xsbt.boot.Launch$.apply(Launch.scala:21)
at xsbt.boot.Boot$.runImpl(Boot.scala:78)
at xsbt.boot.Boot$.run(Boot.scala:73)
at xsbt.boot.Boot$.main(Boot.scala:21)
at xsbt.boot.Boot.main(Boot.scala)
[error] [launcher] error during sbt launcher: java.lang.ClassCastException: class java.lang.UnsupportedOperationException cannot be cast to class xsbti.FullReload (java.lang.UnsupportedOperationException is in module java.base of loader 'bootstrap'; xsbti.FullReload is in unnamed module of loader 'app')
Time to RTFM! The Scala docs recommend the following to get going.
brew install coursier/formulas/coursier && cs setup
Neat! This (coursier
) is new since the last time I did Scala dev. After installing this, running:
sbt update
sbt compile
Gets me where I want to be! Now that we can run all the required commands lets move onto getting our editor setup.
#LSP
If you have used neovim
for development before, you may know that it uses a Language Server Protocol to show you editor feedback. The LSP shows you syntax errors, helps you run refactors, navigate to type definitions and more.
There’s a handy project called https://github.com/williamboman/nvim-lsp-installer that automates the install and setup of these language servers for you!
The easiest way to do this is to open a .scala
file and :LspInstall
Unfortunately - here is where we hit a wall
To Google!
It looks like there’s a community based Scala LSP https://github.com/scalameta/nvim-metals
Let’s install that!
#nvim-metals
Going through the pre-requisites for nvim-metals
- Ensure using nvim 0.7.0 or newer ✅
- Ensure Coursier is installed locally. ✅
- Remove
F
fromshortmess
🤔 - Ensure plenary.nvim is installed. ✅
At this point I think “what the heck is shortmess?”
:help shortmess
reveals that this will help show messages from nvim-metals
when doing setup! Neat! So lets move onto adding the plugin
use({'scalameta/nvim-metals', requires = { "nvim-lua/plenary.nvim" }})
A quick :PackerSync
and we have it installed, let’s set it up.
Docs point us to this file
https://github.com/scalameta/nvim-metals/discussions/39
Currently I don’t use dap so I create a nvim-metals.lua
file in my configs and fill it with:
local api = vim.api
local cmd = vim.cmd
local metals_config = require("metals").bare_config()
-- Example of settings
metals_config.settings = {
showImplicitArguments = true,
excludedPackages = { "akka.actor.typed.javadsl", "com.github.swagger.akka.javadsl" },
}
-- *READ THIS*
-- I *highly* recommend setting statusBarProvider to true, however if you do,
-- you *have* to have a setting to display this in your statusline or else
-- you'll not see any messages from metals. There is more info in the help
-- docs about this
-- metals_config.init_options.statusBarProvider = "on"
-- Example if you are using cmp how to make sure the correct capabilities for snippets are set
local capabilities = vim.lsp.protocol.make_client_capabilities()
metals_config.capabilities = require("cmp_nvim_lsp").update_capabilities(capabilities)
-- Debug settings if you're using nvim-dap
local dap = require("dap")
dap.configurations.scala = {
{
type = "scala",
request = "launch",
name = "RunOrTest",
metals = {
runType = "runOrTestFile",
--args = { "firstArg", "secondArg", "thirdArg" }, -- here just as an example
},
},
{
type = "scala",
request = "launch",
name = "Test Target",
metals = {
runType = "testTarget",
},
},
}
metals_config.on_attach = function(client, bufnr)
require("metals").setup_dap()
end
-- Autocmd that will actually be in charging of starting the whole thing
local nvim_metals_group = api.nvim_create_augroup("nvim-metals", { clear = true })
api.nvim_create_autocmd("FileType", {
-- NOTE: You may or may not want java included here. You will need it if you
-- want basic Java support but it may also conflict if you are using
-- something like nvim-jdtls which also works on a java filetype autocmd.
pattern = { "scala", "sbt", "java" },
callback = function()
require("metals").initialize_or_attach(metals_config)
end,
group = nvim_metals_group,
})
Testing booting up nvim
and it looks like no errors! Great!
Back to the Scala project, on opening a .scala
file I’m greeted with a prompt to :MetalsInstall
lets run that.
After installing I’m prompted with:
And Voila!
We have lsp info in NeoVim!
Obviously this setup process takes more know how than a simple IntelliJ project build/sync. However, NeoVim and the plethora of community plugins and support made this easy!