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

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 from shortmess 🤔
  • 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: prompt.png

And Voila! voila.png

We have lsp info in NeoVim!

lsp-info.png

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!