Skip to content

言語サーバー(onion-lsp)

Onion Language Server は .on ファイル向けの IDE 機能を提供します。 VSCode 拡張(vscode-onion/)や Language Server Protocol に対応した 他のエディタから利用できます。

サーバーの起動

onion-lsp

サーバーは LSP を使って stdin/stdout で通信します。

サポートしている機能

機能 説明
シンタックス診断 入力中にコンパイルエラーを表示
ホバー キーワード、組み込み型、ユーザー定義シンボルの情報を表示
定義へ移動 ユーザー定義シンボルの定義位置へジャンプ
コード補完 キーワード、組み込み機能、モジュール、ユーザー定義シンボル
ドキュメントシンボル クラス、インターフェース、メソッド、enum、record、フィールドの概要表示
ワークスペースシンボル 開いているすべてのドキュメントからシンボルを検索
シグネチャヘルプ メソッド呼び出し入力中にシグネチャを表示
シンボルのリネーム カーソル位置のシンボルを現在のファイル内で一括リネーム
フォーマット 空白を整える。onion fmt と実装を共有
セマンティックトークン 識別子を「そのドキュメントが何と宣言しているか」で色分け

リネームがすること・しないこと

リネームは、開いているドキュメント内のコードとしての識別子の出現をすべて書き換えます。 コメントと文字列リテラルは対象外です——count のリネームで "count of items" や // count starts at zero が書き換わることはもうありません。一方 #{ … } の補間はコードなので、 "n=#{count}" の count は他と一緒にリネームされます。カーソルがコメントやリテラルの中に ある状態でのリネームは拒否します。そこにある語は何かへの参照ではないからです。

スコープは見ておらず、ファイルをまたぎません。 たまたま同名の無関係なローカル変数は まとめてリネームされますし、同名のローカル変数とフィールドも区別されません。他のファイル内の 参照には一切触れません。スコープを見たリネームには、実際のソース範囲を持つ型付き AST が必要ですが、 現在パーサが生成する span はトークン1個分の幅しかありません。そこが前提作業になります。 それまでは、エディタが表示する変更プレビューを確認してから適用してください。

セマンティックトークン

TextMate 文法では原理的に出せない色分けです。文法は「その行の形」から語の種類を決めるため、 Greeter も greet も count も同じ「語」にしか見えません。言語サーバーは各識別子を、 ドキュメントが実際に何と宣言しているか——クラス・インターフェース・enum・record・メソッド・ フィールド・ローカル——で分類します。

確信が持てないものは何も出しません。 ドキュメントが宣言していない識別子にはトークンを 返さず、エディタは TextMate 文法の結果をそのまま使います。セマンティックトークンは文法を 上書きするので、推測で出すと正しい答えを間違った答えで置き換えることになります。 ソフトキーワードを対象外にしているのも同じ理由です。conforms はレキサからは識別子として 届きます——そこがキーワードかどうかを決めるのはパーサの先読みだけなので、字句だけで色を 付けると conforms という名前のメソッドまで光ってしまいます。

キーワードと演算子の種別は、サーバー側に列挙せず生成されたパーサのトークン表から読み取り ます。文法にキーワードが増えても、ここを更新し忘れて分類が漏れることがありません。 TextMate 文法が 70% まで腐ったのはまさにその二重管理が原因でした。Onion のプリミティブ型は 同じ表の中で大文字始まりの項目なので、Int はキーワードではなく型として色が付きます。

テーマ側の対応が要ります。VS Code では対応を宣言しているテーマなら既定で有効で、 "editor.semanticHighlighting.enabled": true を設定すればどのテーマでも強制的に有効になります。

フォーマット

フォーマット機能は onion fmt と同じコードを呼びます。エディタと CI のチェックが 「整形済み」の定義で食い違うことがないようにするためです。食い違いがあると、 両者を行き来するたびにファイルが書き換わることになります。

句読点まわりを詰めます。インデントの再計算はせず、改行も動かさず、桁揃えの連続スペースも 潰さず、タブにも触れません。理由は プロジェクト CLI ページの onion fmt の節に書いてあります。

返すのはドキュメント全体を置き換える 1 個の編集です。最小差分の方がエディタの装飾は 保たれますが、2 つの文字列から最小差分を計算すると「整形後のテキストを再現しない編集」を 生む危険があります。保存のたびにバッファを壊すフォーマッタは、スクロール位置を飛ばす フォーマッタより悪いです。編集を返す前に、整形後のテキストを再度字句解析して元とトークン単位で 比較し、一致しない場合は編集を一切返しません。

クラスパスとスクリプトの依存(//> using)

上位のディレクトリに onion.toml があるファイルは、onion build がコンパイルするのと 同じように検証されます。つまりプロジェクトの target/classes と、onion.toml が宣言する 依存に対して検証します。onion.toml が無いファイルは単独のスクリプトとして、. と、 その //> using dep ディレクティブが解決する jar に対して検証されます。 パーサー、リゾルバー、キャッシュは onion script.on や onionc と共通なので、エディタは 実行時と同じ classpath を見ます。

  • 解決でエディタを止めません。 キャッシュにあるディレクティブの組はすぐに使われます。 キャッシュに無い組はバックグラウンドのスレッドで解決し、終わるまでの間、サーバーは そのスクリプトの診断を保留して、代わりにディレクティブの行に情報メッセージを1つだけ 表示します。解決が終わると自動でファイルを検証し直します。jar 無しで検証する方式を 選ばなかったのは、ダウンロードの間ずっと、依存が提供するすべての型が「見つからない」と 下線表示されてしまうからです。
  • 形式の誤ったディレクティブは、その行と桁に、onion や onionc と同じメッセージの エラーとして表示されます。ファイルの残りの部分は、そのスクリプトが最後に解決した jar に 対して引き続き検証されます。
  • 解決できない依存は、リゾルバーのメッセージを持つエラーとしてディレクティブの行に 表示され、ファイルはその依存無しで検証されます。失敗は記憶され、キーを打つたびに 再試行することはありません。ファイルを保存すると再試行します。
  • プロジェクト内ではディレクティブを使いません。 onion build が使わないのと同じです。 ディレクティブの行に、依存は onion.toml に宣言するよう促す警告が表示されます (ファイルを直接 onion file.on や onionc file.on に渡した場合は、引き続き読み取られます)。

シンボル対応

言語サーバーは開いているドキュメントから以下のシンボルをインデックス化します。

  • クラス、インターフェース、enum、record
  • メソッド(def)
  • クラスレベルのフィールド(val / var)
  • メソッド引数とローカル変数

シンボル情報は補完、ホバー、定義へ移動、ドキュメント/ワークスペースシンボル リクエストで利用されます。

VSCode 連携

vscode-onion 拡張をインストールしてください。.on ファイルを開くと自動的に onion-lsp が起動し、エディタに接続されます。