VSCode 拡張の Ruby LSP で Language Server が起動するまでを確認する

VSCode 拡張のエントリーポイント

VSCode 拡張としての実装は vscode ディレクトリ以下に格納されている。実装は TypeScript 。今インストールしている VSCode 拡張のバージョンが v0.9.7 なのでそのタグで確認していく。

https://github.com/Shopify/ruby-lsp/tree/vscode-ruby-lsp-v0.9.7/vscode

VSCode のドキュメントも確認した。

https://code.visualstudio.com/api/get-started/your-first-extension

vscode/src/extension.ts の activate メソッドがエントリーポイントということで良さそう。

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/extension.ts#L11

RubyLsp クラスを new して activate() をコールしている。

  extension = new RubyLsp(context, logger);
  await extension.activate();

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/extension.ts#L45-L46

RubyLSP クラス

The RubyLsp class represents an instance of the entire extension.

とのこと。拡張機能のメインクラスということでヨシ。

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/rubyLsp.ts#L22-L25

activate() → activateWorkspace() → runActivation() の順番にコールして、Workspace クラスを new して activate() → start() をコールしている。

    await workspace.activate();
    await workspace.start();

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/rubyLsp.ts#L233-L234

Workspace クラス start() メソッド

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/workspace.ts#L106

Client クラスを new して start() をコールしている。この時に Ruby LSP 拡張が起動する際に VSCode に表示される「Initializing Ruby LSP」というメッセージも一緒に設定しているみたい。Ruby LSP 拡張の初期化処理の本丸ということで良さそう。

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/workspace.ts#L169-L178

Client クラス

Client クラスには start() メソッドの定義がない。なのでこれは親クラスの LanguageClient から継承されたメソッド。

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/client.ts#L345

注目すべきは親クラスコンストラクタの2番目の引数値を取得する getLspExecutables() メソッド。

    super(
      LSP_NAME,
      getLspExecutables(workspaceFolder, ruby.env),

Language Server の起動コマンドが計算されている。

    run = {
      command: "bundle",
      args: ["exec", "ruby-lsp"],
      options: executableOptions,
    };

https://github.com/Shopify/ruby-lsp/blob/vscode-ruby-lsp-v0.9.7/vscode/src/client.ts#L116-L120

念の為 LanguageClient の実装も確認する。

https://github.com/microsoft/vscode-languageserver-node/blob/release/client/9.0.1/client/src/node/main.ts#L136-L138

ServerOptions 型で合っている。

https://github.com/microsoft/vscode-languageserver-node/blob/release/client/9.0.1/client/src/node/main.ts#L126

ruby-lsp コマンド

ここからは Ruby 実装。 v0.23.11 タグで確認していく。

まず gemspec の executables を確認。

  s.bindir = "exe"
  s.executables = ["ruby-lsp", "ruby-lsp-check", "ruby-lsp-launcher"]

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/ruby-lsp.gemspec#L17-L18

exe/ruby-lsp スクリプトが該当し、最終行で RubyLsp::Server を起動している。

RubyLsp::Server.new.start

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/exe/ruby-lsp#L154

RubyLsp::Server クラス、 RubyLsp::BaseServer クラス

start メソッドは定義されていないので親クラスの BaseServer を確認する。

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/lib/ruby_lsp/server.rb#L5

@reader.read のブロックがループしてそう。

    def start
      @reader.read do |message|

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/lib/ruby_lsp/base_server.rb#L45-L46

この @reader が何かと言うと、Transport::Stdio::Reader のインスタンス。language_server-protocol-ruby gem のクラスで README に書いてある通りの使い方をしているといった感じか。

https://github.com/mtsmfm/language_server-protocol-ruby?tab=readme-ov-file#usage

クライアントからリクエストが来たら処理され、@incoming_queue に message を格納。

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/lib/ruby_lsp/base_server.rb#L106

ワーカースレッドでデキューして process_message() で処理をすると。

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/lib/ruby_lsp/base_server.rb#L150-L166

process_message() で各リクエストに対する分岐が書かれている。

https://github.com/Shopify/ruby-lsp/blob/v0.23.11/lib/ruby_lsp/server.rb#L13

個別の処理はここから追いかけていけばOKになった。

次回

クライアントから hover リクエストが飛んできた時のサーバーの処理の流れを追いかけてみよう。