<zackery.dev/>
Prompts

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

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!

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!