言語サーバー(language server)を自作できるようにしておくと何かと便利そうなので、自分用の最低限の叩き台みたいなものを書いてみました。
今回作ったものの概要
- 言語サーバーを子プロセスとして起動して標準入力・標準出力で通信する一般的な方式
- 例として
test
という文字列を含む行に警告を表示する機能だけ-
textDocument/publishDiagnostics
を使う
-
- なくても動く部分は省略、エラーハンドリングは適当
動作イメージはこんな感じ:
準備
サーバー側を書くのが目的なので動作確認できればクライアントは何でもよいです。今回は Emacs を使いました。好きなものを使いましょう。
- Emacs 29.1 以上
- 必要なファイルは
langserver.rb
とtest_init.el
だけ(下記参照)- この2つを同じディレクトリに置く
これだけで試せます。
Emacs 29.1 以降だと Eglot が標準で付いてくるようになった( NEWS.29.1 )ので、用意するのはほんとにこれだけ。
test_init.el
Eglot 向けの設定です。
(progn
;; (1) 既存の設定を一旦消す
(setq eglot-server-programs '())
(let* ((this-dir (file-name-directory (buffer-file-name))) ; 作業ディレクトリのフルパス
(cmd (list "ruby" (expand-file-name "langserver.rb" this-dir)))) ; 言語サーバーを起動するコマンド
;; (2) 設定を追加する
(add-to-list 'eglot-server-programs
(cons 'sh-mode cmd))))
メジャーモードとサーバー起動コマンドの対応関係を連想リスト eglot-server-programs
に登録する、という内容ですね。
何かしらのメジャーモードと紐付けないといけないので今回は適当に sh-mode を選びました。お試し用なのでこれは何でもよいです。
ちなみにマニュアルに乗っている設定例はこんな感じ。
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(foo-mode . ("fools" "--stdio"))))
参考: Setting Up LSP Servers (Eglot: The Emacs Client for the Language Server Protocol) (www.gnu.org)
langserver.rb
Language Server Protocol に対応したミニ言語処理系を作る (zenn.dev / takl) を参考にさせてもらいました。
今回は素朴な書き方にしましたが
on_request "initialize" do |msg|
# ...
end
みたいに書けるようにすると Ruby っぽいかもしれません。
動かす
-
test_init.el
を開いてバッファの内容を評価する-
M-x eval-buffer
とかeval-last-sexp
とかで
-
- 適当なファイル
sample.sh
を用意して Emacs で開く- 自動的に sh-mode になる
-
M-x eglot
で言語サーバーを起動 -
sample.sh
を編集すると Eglot が言語サーバーとやりとりして画面上にフィードバックが表示される- 保存は不要。編集された現在のバッファの内容を元にフィードバックが表示される
動かす手順はこれだけです。Emacs + Eglot だとこれだけで試せるので手軽でよいですね。 クライアント側の用意が楽。
編集時の挙動はこんな感じです:
-
test
という文字列を含む行に赤い下線(と!!
)が表示される -
test
という文字列を含まなくなった行の赤い下線が消える - 警告が出ている箇所にカーソルを合わせるとメッセージがエコーエリアに表示される
- マウスカーソルのホバーでもメッセージが表示される
メモ
- これを育てて、昔作った自作言語の言語サーバーも書いてみたい(そのうち……)
- レスポンスを print した後の
$stdout.flush
を忘れててちょっとハマった - 今回は「
test
という文字列を含む行」に警告を表示するというだけの処理だったが、ここは他のコマンドやライブラリと連携させれば手軽にいろんなことができそう。 -
=>
を初めて使った
Eglot
- company mode などを用意しなくても Eglot だけでも使える
- 挙動を把握しきれてないが、サーバーを再起動したいときは
M-x eglot
すればよいっぽい。 - 関連付けの登録方法と
M-x eglot
だけ知っていればとりあえず使える。標準で入ってるし使い始めのハードルが低くて良い。
バージョン
Ruby 3.2.3 ... Ubuntu 24.04 のデフォルト
Emacs 29.3
Eglot 1.12.29
この記事を読んだ人は(ひょっとしたら)こちらも読んでいます