🏟️

nvim-metalsを使わずにneovimでmetalsを使う(sbt builtin bsp)

2022/08/23に公開

対象

  • neovimユーザー
  • scalaのLanguage Serverとしてmetalsを使いたい
  • nvim-metalsを使わずに、nvim-lspconfigを使いたい
  • sbtプロジェクト
  • bloopではなくsbt組み込みのbspを使いたい

TL;DR

  • coursierをインストールして cs install metals でmetalsをインストールする
  • nvim-lspconfigでmetalsを有効にする
  • neovimでsbtプロジェクトを開いたらコマンドラインで lua vim.lsp.buf_request(0, "workspace/executeCommand", { command = 'metals.generate-bsp-config' }) を実行する

背景や動機

scalaのLanguage Server(以下、LS)であるmetalsは主要なテキストエディタにプラグイン(拡張機能)を提供しています。
https://scalameta.org/metals/docs/

metalsの利用者はこれらをインストールすれば手軽にmetalsの機能を利用できますが、その反面これらを使わずにmetalsを利用するのはややハードルが高く、公式ドキュメントにも特に記載がありません。

neovim向けにはnvim-metalsというプラグインを提供してくれています。
neovimには組み込みのLSPクライアントがあり、nvim-metalsはそのクライアント用の設定などを持っています。

neovim界隈ではnvim-lspconifigで各LSの設定をまとめて管理することが多く、nvim-metalsを使わなくてもnvim-lspconfigでmetalsの設定を1行追加すればmetalsが動きます。
しかし、これだけではうまく動かない機能がありるため別途対応が必要です。
https://github.com/neovim/nvim-lspconfig

nvim-metalsは様々な機能を持つフルスタックなプラグインなので、入れるだけでいろいろとうまく動きそうです(試してません)。
とはいえ、このような機能モリモリのプラグインの利用はできるだけ避けたい人もいると思います。
そう私です。

なぜ、使いたくないのかを言語化するのは少し難しいのですが、ざっくりと言うなら以下です。

  • 他の既存プラグイン(利用中のプラグイン)と責務が被る
  • 責務過多なモジュールはバグりやすく修正大変なことが多い
  • 細かく挙動をカスタマイズするのが難しい、もしくはできない
  • 単純に、easyだがsimpleじゃないものが好きじゃない

とりわけnvim-metalsに関しては、nvim-lspconfigのmetalsの設定と共存できないという制限があり、いろいろと不便に感じました。

少し否定的な意見を書きましたが、この手のプラグインを使うかどうかは完全に個人の好みの問題です。
とくに問題なければ公式が提供しているnvim-metalsを使うのが手軽だと思います。

この記事はフルスタックなプラグインを使いたくない人向けです。

本題

ここからは実際にmetalsをまともに使えるようになるまでの手順を周辺情報含めて書き連ねていきます。
長いと思ったらコマンドや作業手順だけ適当にピックアップしてください。

1. metalsのインストール

公式のプラグインを使わない場合、まず最初につまずくのがmetalsのインストール方法です。
ドキュメントには載ってませんし、調べてもほとんど出てきませんでした。
scalaのエコシステムになれている人であれば直感で解決できるのかもしれませんが、私はscala初心者なため苦戦しました。

結論としてはCoursierでインストールできました。
CoursierはScala製ツールをインストール機能があるようです。
Rubyの gem install や Goの go install のイメージですね。

https://get-coursier.io/docs/overview

Coursier is the Scala application and artifact manager. It can install Scala applications and setup your Scala development environment. It can also download and cache artifacts from the web.

CoursierはScalaアプリケーションおよびアーティファクトマネージャである。Scalaアプリケーションをインストールし、Scalaの開発環境を構築することができる。また、ウェブからアーティファクトをダウンロードし、キャッシュすることができる。

手順

2. nvim-lspconfigの設定

この記事ではnvim-lspconfigの設定方法については解説しないので、よくわからない方は他の記事を参考にしてください。

nvim-lspconfigのmetalsのドキュメントはこちらです。
https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#metals

require'lspconfig'.metals.setup{}

3. sbtの組み込みBSPを利用するように設定する

この取り組みで最も大変だった箇所です。

metalsの諸々

metalsはコード補完やコードジャンプのためにScalaプロジェクトをビルドします。
このとき使われるのが Build Server Protocol (以下、BSP) という仕組みだそうです。
BSPについては以下の記事などを参照してください。

BSPに関しては全体的によく分かっていないので、この記事の内容を正しいものだと思わず雰囲気だけ感じ取ってください。

metalsはデフォルトでbloopというBSPを使うようなのですが、手元のプロジェクトではこれがうまく動かずいろいろなトラブルが発生しました。
https://scalacenter.github.io/bloop/

もしかしたら、プロジェクトの構成によっては問題無く動くのかもしれません。
その場合はこのセクションの対応は不要です。

結論として、私の環境ではbloopではなくsbtの組み込みBSPを使うと解決しました。
BSPを切り替える方法はMetalsの公式ドキュメントにあります。
https://scalameta.org/metals/docs/build-tools/sbt/#generating-a-bspsbtjson-file-if-one-doesnt-exist

......

切り替え方法が書いてはありますが、残念ながらこれを見て対応できる猛者は少ないのではないでしょうか...

公式ドキュメントではVS Codeもしくは公式提供のプラグインを前提として解説が書かれているため、あまり参考になりません。
まあ当然ですよね。

解決策の模索

先程のドキュメントに「うまく動かなかったらコマンドパレットで metals.generate-bsp-config 実行してね(超意訳)」とありました。
なんとなくLSPっぽい雰囲気がします。

Generating a .bsp/sbt.json file if one doesn't exist
More than likely if you're using sbt >= 1.4.1, you'll have already see this file exist. However, if you're in a fresh workspace or it doesn't exist for some reason, you can execute a metals.generate-bsp-config command via the command palette, which will automatically detect that you're in a sbt workspace and generate the necessary file. After the file generation, Metals will then automatically connect to sbt. From this point on, you'll be using sbt instead of Bloop as your build server.

ドキュメントはVS Code向けの説明ですが、neovim向けの公式プラグインであるnvim-metalsにも対応するコマンドがありました。

https://github.com/scalameta/nvim-metals/blob/9629138151e927ab6826de726a73bb366f49d801/doc/metals.txt#L548

https://github.com/scalameta/nvim-metals/blob/main/lua/metals.lua#L111-L113

中身の処理としては、現在のバッファ向けにLSPの workspace/executeCommandmetals.generate-bsp-config コマンドを実行しているようです。

この関数をコンパクトなneovimコマンドにすると以下のようになります。
第一引数の0は現在のバッファを指します。

lua vim.lsp.buf_request(0, "workspace/executeCommand", { command = 'metals.generate-bsp-config' })

neovimでsbtプロジェクトを最初に開いたときに、このコマンドを実行してください。
プロジェクトのビルドが始まり、それが終われば補完やコードジャンプが使えるようになります。
タイミング悪くLS起動中に実行すると動かないので数秒待ってから実行しましょう。
一度実行したら次に開くときは不要です。

ちなみに lua コマンドはneovimのコマンドラインからluaのコードを直接実行するneovimのコマンドで、 vim.lsp はneovimの組み込みLSP Clientのluaインターフェースです。

4. おまけ: gitignore

metalsを使う場合は以下をgitignoreに登録しておくといいです。

.metals
metals.sbt

リポジトリのgitignoreに登録しづらい場合は、ユーザーのグローバルなgitignoreかローカルのリポジトリ限定のgitignoreに登録しましょう。

https://git-scm.com/docs/gitignore

終わりに

本記事ではnvim-metalsを使わずにnvim-lspconfigでmetalsを使う方法のとっかかりを解説しました。
基本的には metals.generate-bsp-config と同じ要領で必要なコマンドや設定のみnvim-metalsから真似してくればいいはずです。

一応基本的な機能は動くようになりましたが、nvim-lspconfigでは対応していない機能もあるので全機能を手軽に使いたいのであれば公式プラグインがいいかもしれません。
公式プラグインがもう少しシンプルなら使いたかったのですが...

少し愚痴ってしまいましたが、やはりLSPは凄いです。
これがなかった時代と比べればかなり楽に対処できました。
昔は 言語の数 × テキストエディタの数 だけコード補完ツールやテキストエディタプラグインがありましたし、インターフェースや挙動がバラバラで対応が大変でした。

BSPは今回始めて知りましたが便利そうなプロトコルですね。
今後の発展が楽しみです。

Discussion