Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2022-07-24 #80

Open
r7kamura opened this issue Jul 23, 2022 · 15 comments
Open

2022-07-24 #80

r7kamura opened this issue Jul 23, 2022 · 15 comments

Comments

@r7kamura
Copy link
Owner

r7kamura commented Jul 23, 2022

今回はvscode-rubyの読書メモを書き連ねていきます。

@r7kamura
Copy link
Owner Author

vscode-rubyに同梱されているlanguage-server-rubyという実装を読んでいる。

  • RubyではなくTypeScriptで実装されている
  • Rubyの言語解析機能
  • Language Server Protocolに従っている
  • 単独のプロセスとして動く
  • tree-sitterといういろいろな言語の構文木をつくるための実装を使っている
    • tree-sitterの関連実装として、tree-sitter-rubyというRuby向けの実装が存在する
    • language-server-rubyでもtree-sitter-rubyを使っているのだろうと予想している

@r7kamura
Copy link
Owner Author

vscode-rubyは、複数のパッケージを扱うmonorepoとして管理されている。

  • vscode-ruby
  • vscode-ruby-client
  • vscode-ruby-debugger
  • language-server-ruby

monorepoの管理にはLernaを使っているとのこと。

@r7kamura
Copy link
Owner Author

language-server-rubyについて詳しく見ていく。

package.jsonを見ると、次のパッケージがdependenciesに含まれていた。

  • web-tree-sitter
  • web-tree-sitter-ruby

web- prefix付きなのが気になるが、確かにtree-sitterが使われている。

package.jsonのmainフィールドにdist/index.jsとあり、npm run buildではesbuild.jsが利用されている。

esbuild.jsの中では、web-tree-sitter-ruby/tree-sitter-ruby.wasmが参照されている。
なるほど、WASM形式でビルドされてNode.js等で使えるように配布されているのがweb-tree-sitterなのかな。
ちなみにtree-sitterはRustで実装されている。

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 23, 2022

language-server-rubyのesbuild.jsを見ると、次の二つのことをやっているのが分かる。

  • tree-sitter{,-ruby}.wasmをコピーして、distディレクトリに入れる
  • src/inedx.tsをビルドして、distディレクトリに入れる

language-server-rubyのエントリポイントがsrc/index.jsであることが分かったので、ここを読み進めていく。

@r7kamura
Copy link
Owner Author

手元に環境にnodenvが入っているので、vscode-rubyのリポジトリのルートディレクトリで次のコマンドを実行するだけで環境構築を完了させられた。

nodenv install
npm install

これをやらないと、VSCodeが怒ってくる。

image

@r7kamura
Copy link
Owner Author

esbuildを使うのは初めてだけど、こういう感じで使うんだなあ。

esbuild というexecutableを実行するわけではなく、

node esbuild.js

と実行するというのが面白い。普通だったらオプションとかを渡したくなってCLIオプションを付けるためにCLIを用意するものだけど、esbuildの場合はどうせ設定ファイルを用意することになるので、そういうものを用意する必要は無いわという判断なのだろうか。まあ無くて良いなら無いに越したことはない。

@r7kamura
Copy link
Owner Author

// Don't die when attempting to pipe stdin to a bad spawn
// https://github.com/electron/electron/issues/13254
process.on('SIGPIPE', () => {
	log.error('SIGPIPE received');
});

とあり、そういえばVS CodeはElectronベースだから、Electronの不具合の影響を受けるんだったと思い出した。

@r7kamura
Copy link
Owner Author

基本のlanguage serverの実装はこういう感じのようだ。

import { createConnection, ProposedFeatures } from "vscode-languageserver";
cosnt connection = createConnection(ProposedFeatures.all);
// Customize connection as you like...
connection.listen();

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 23, 2022

let server;
connection.onInitialize(async (params) => {
  server = new Server(connection, params);
  server.initialize();
  return server.capabilities;
});

こんな感じのコードが書かれているから、実際には Server という内部実装の中でいろいろな処理をやっている。

なので、読むべきは次の実装:

  • Serverのconstructor
  • Server#initialize

capabilitiesというのは、「このlanguage serevrはDocumentHighlightに対応ています」のような情報を明示するためのオブジェクトらしい。

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 23, 2022

language-server-rubyでは、VS Codeの拡張として提供する各機能の単位を "Provider" と呼称しているようだ。VS Codeの用語なのか独自用語なのかはわからない。

language-server-rubyでは、次の6つのProviderを用意しているようだ。

  • FoldingRangeProvider
  • DocumentHighlightProvider
  • DocumentSymbolProvider
  • DocumentFormattingProvider
  • ConfigurationProvider
  • WorkspaceProvider

前者4つは初期化フェーズ中に用意され、後者2つは初期化フェーズ完了後に用意される。connection.onInitializeとconnection.onInitializedでそれぞれ用意されている。

  • Server#initialize
  • Server#setup

でそれぞれ実装されているが、このネーミングは少し分かりづらい…

  • Server#onConnectionInitialize
  • Server#onConnectionInitialized

とかで良かっただろうと思う。

その話はさておき、Server.tsの主な仕事はこのようにProviderを用意してあげることらしく、主な実装はつまりそれぞれのProviderに書かれているに違いない。

Providerは、初期化時にconnectionを受け取って、何か良い感じに動くもののようだということが分かっている。

@r7kamura
Copy link
Owner Author

各種Providerは基底Providerクラスを継承しているらしい。
各種Providerは .registerというstatic methodを持っていて、外部向けのインターフェースがこれ

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 24, 2022

Connection#onDocumentHighlight という、コールバック登録用のメソッドが生えているらしいDocumentHighlightProviderはこれをそのconstructorで呼び出している。ググってもonDocumentHighlightに関する情報は乏しい……

onDocumentHighlightに渡す引数はServerRequestHandler型で、実際のコード例を見ると、TextDocumentPositionParams型の引数を取るらしい。位置情報が与えられるので、それに応じて何か適切に動作しろということだろうか。Promise<DocumentHighlight[]> 型の値を返す関数として実装されている。

paramsはpositionとuriを持つObjectらしい。多分ファイルパスとその中での位置情報が入っている。

DocumentHighlightAnalyzer.analyze にこれらの値を渡してその返り値をそのまま返している。役割分担を整理するとこうだ:

  • Provider
    • Connection#onDocumentHighlight のことを知っている
    • 適切なハンドラー関数を用意してあげる責務を持つ
    • 解析については詳しいことは分からないのでAnalyzerに任している
  • Analyzer
    • Connectionについては詳しいことは知らない
    • コードの位置情報とファイルパスをもらって解析を行うだけ

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 24, 2022

DocumentHighlightAnalyzerでは、tree-sitterを利用した構文の解析と、DocumentHighlight[] の生成をやっている。どういう機能を持っているかというと、多分こんな感じ:

  • endにカーソルを載せているときは、対応するbeginやdoからendまでをハイライトする
  • beginやdoにカーソルを載せているときは、そこから対応するendまでをハイライトする

tree-sitter-rubyの生成する構文木の実例を見ながら進めていった方が分かりやすいかもしれない。

vscodeの "document highlight" という機能・概念を正確にするため、ぐぐってみた方が良さげ。

A document highlight is a range inside a text document which deserves special attention. Usually a document highlight is visualized by changing the background color of its range.

image

いろいろ調べていると、次のような言及があった。

textDocument/documentHighlightは、同じシンボルの使用箇所をエディタ上でハイライトする場合にも使用できます。

image

@r7kamura
Copy link
Owner Author

r7kamura commented Jul 24, 2022

image

この機能だな。endにカーソルが載っている状態だと、そのendと対応するdoの背景色が変わっている。

if elsif else end や case when end, begin rescue else ensure end のときは全部光ってほしいな。少し改良してみる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant