LSP ルーターを作った

最近は Emacs の LSP クライアント機能である Eglot を使って Ruby を書いたり読んだりしてる。 ruby-mode では LSP サーバーはデフォルトで Solargraph が使われてる。

半年くらい前に rubocop に LSP サーバー機能が搭載されたらしいんで使ってみた。

(add-to-list 'eglot-server-programs '(ruby-mode . ("rubocop" "--lsp")))

rubocop の機能であるコードのチェックはちゃんと使えたんだけど、Solargraph で使えてたコードジャンプとかが使えなくなった。まあそれはそう。

Eglot はモードごとに LSP サーバーを指定することはできるけど、同じモードに複数の LSP サーバーを指定することはできなそう。Emacs Lisp はよくわからないんでちゃんと調べてないんだけどたぶん。

じゃあ複数の LSP サーバーを束ねて一つの LSP サーバーのように振る舞うツールがどこかにあるんじゃないかな…と思って5分ほどググって探してみたけど見つからなかったので、自分で作ってみた。

gitlab.com

インストール:

gem install lsp_router

設定ファイル:

logfile '/tmp/lsp_router'
loglevel :info

server :rubocop do
  command 'rubocop --lsp'
end

server :solargraph do
  command 'solargraph stdio'
end

Emacs の設定:

(add-to-list 'eglot-server-programs '(ruby-mode . ("lsp_router" "--error=/tmp/lsp_router.err" "/dokka/lsp_router.conf")))

こんな感じでLSPプロトコルを振り分ける:

                             +--- rubocop
Emacs(Eglot) -- lsp_router --|
                             +--- solargraph

LSP (というか JSON-RPC?)は、Request と、それに対する Response と、応答が必要ない Notification から構成されてるらしい。

lsp_router は最初に各LSP サーバーの Capability を調べておいて、クライアントからの Request は対応するサーバーに振り分ける。Notification は全サーバーに送る。サーバーからの送信はそのままクライアントに垂れ流している。

LSP プロトコルは今回初めて軽く調べてみただけなんで変なことしてる可能性はあるけど、少なくとも Emacs からはそれなりに動いてる気配を感じてる。

余談

設定ファイルの文法は Ruby。

Ruby 3.1 から load に第2引数でモジュールを渡せるようになったのでそれを使ってる。便利。

3.1 より前で同じようなことをするには Hoge.module_eval(File.read(conf_file)) みたいな感じでやっててイマイチだったんで、満足。