aimdevel’s blog

勉強したことを書きます

vscode拡張機能のsemantic highlightを理解する

vscodeのhighlight拡張機能勉強の続きです。
今回はsemantic highlightの作り方をサンプルコードを読むことで理解します。
semantic highlightを使用すると、language serverからhighlightの設定をすることが可能になるので、単純な正規表現でのマッチング以外のプロジェクトの情報などを使用したhighlightが可能になるようです。

読んだコードはvscode拡張機能のサンプルリポジトリの以下です。

vscode-extension-samples/extension.ts at main · microsoft/vscode-extension-samples · GitHub

公式ドキュメントは以下です。

Semantic Highlight Guide | Visual Studio Code Extension API

処理の流れ

  1. テキストをパースして要素ごとに分解、タイプ分け
  2. パース結果の各要素をSemanticTokensBuilderに渡す。
  3. すべての要素を渡したらtokenをビルドする。
  4. tokenをreturnする。

次にソースコードの構成を見ていきます。

DocumentSemanticTokensProviderの処理

名前の通りsemantic tokenを提供するクラスです。このクラスのインスタンスvscode.languages.registerDocumentSemanticTokensProvider関数で登録することで機能が使えるようになります。
このクラスにはいくつかの関数が実装されていますが、主に見るべきは provideDocumentSemanticTokensのみです。

async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SemanticTokens> {
        const allTokens = this._parseText(document.getText());
        const builder = new vscode.SemanticTokensBuilder();
        allTokens.forEach((token) => {
            builder.push(token.line, token.startCharacter, token.length, this._encodeTokenType(token.tokenType), this._encodeTokenModifiers(token.tokenModifiers));
        });
        return builder.build();
    }

この関数内でテキストのパースからtokenをリターンするまでのすべての処理が行われています。
各処理を見ていきます。

async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SemanticTokens> {

関数の定義です。 この関数自体はvscode APIで定義されており、ここではその実装を行った形になっています。
引数のdocumentにはファイル内の文字列すべてが格納されています。tokenには非同期処理の中止を要求するする情報が入っているようです。そして、Promise<vscode.SemanticToken>で戻り値がSemanticTokenであることがわかります。

const allTokens = this._parseText(document.getText());

ここでファイルのテキストをパースし、tokenをビルドするために必要な情報を用意しています。 allTokensはリストになっていて、各要素は以下の構造です。この情報はSemanticTokensBuilderにpushする際に使用します。

interface IParsedToken {
    line: number;
    startCharacter: number;
    length: number;
    tokenType: string;
    tokenModifiers: string[];
}

次はSemanticTokensBuilderです。

const builder = new vscode.SemanticTokensBuilder();
allTokens.forEach((token) => {
            builder.push(token.line, token.startCharacter, token.length, this._encodeTokenType(token.tokenType), this._encodeTokenModifiers(token.tokenModifiers));
        });

初めにSemanticTokensBuilderのインスタンスを作成しています。
その後、先ほど作成したallTokenの各要素ごとにbuilderにpushを行います。
builder.pushに渡す引数を以下のサイトから引用します。
https://code.visualstudio.com/api/references/vscode-api#SemanticTokensBuilder

Parameter Description
line: number The token start line number (absolute value).
char: number The token start character (absolute value).
length: number The token length in characters.
tokenType: number The encoded token type.
tokenModifiers?: number The encoded token modifiers.

前半の3つの引数は、tokenの位置を表しています。 実際にどの位置をhighlightするかはここで決めているようです。
後半の2つの引数は、tokenのtypeとmodifierを示しています。 この指定によって実際のhighlightの仕方が決まります。
tokenの一般的なtypeとmodifierは以下に示されています。

Semantic Highlight Guide | Visual Studio Code Extension API

return builder.build();

最後にbuilderにpushしたtokenの情報をビルドしてリターンします。
これですべての処理が終了となります。

まとめ

今回はSemanticTokenProviderを使用したhighlightの方法を調べました。
サンプルコードの例では、あらかじめ定義したルールでhighlightを行っていましたが、language serverを使用すれば他のファイルの情報を用いてルールを作れる気がします。ただ、それを実現するには、複数ファイルをパースした情報を保持するlanguage serverの実装が必要そうです。

今後

  • language serverに入門する
  • 特定の言語の拡張機能を試作する