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!