VS Codeのエージェントモードを拡張するには、を試してください!

シンタックスハイライトガイド

構文強調表示は、Visual Studio Code エディターに表示されるソースコードの色とスタイルを決定します。JavaScript の iffor のようなキーワードを、文字列やコメント、変数名とは異なる色で表示する役割を担っています。

構文強調表示には2つのコンポーネントがあります

  • トークン化: テキストをトークンのリストに分割する
  • テーマ設定: テーマやユーザー設定を使用して、トークンを特定の色とスタイルにマッピングする

詳細に入る前に、スコープインスペクターツールを試して、ソースファイルにどのようなトークンが存在し、どのようなテーマルールに一致するかを調べてみるのが良いでしょう。セマンティックトークンと構文トークンの両方を確認するには、TypeScript ファイルで組み込みテーマ (例: Dark+) を使用します。

トークン化

テキストのトークン化とは、テキストをセグメントに分割し、各セグメントをトークンタイプで分類することです。

VS Code のトークン化エンジンは、TextMate 文法によって提供されています。TextMate 文法は正規表現の構造化されたコレクションであり、plist (XML) または JSON ファイルとして記述されます。VS Code 拡張機能は、grammars 貢献点を通じて文法を提供できます。

TextMate トークン化エンジンはレンダラーと同じプロセスで実行され、ユーザーが入力するたびにトークンが更新されます。トークンは構文強調表示に使用されますが、ソースコードをコメント、文字列、正規表現の領域に分類するためにも使用されます。

リリース 1.43 から、VS Code は拡張機能がセマンティックトークンプロバイダーを介してトークン化を提供することも許可しています。セマンティックプロバイダーは通常、ソースファイルをより深く理解し、プロジェクトのコンテキストでシンボルを解決できる言語サーバーによって実装されます。たとえば、定数変数名は、宣言された場所だけでなく、プロジェクト全体で定数強調表示を使用してレンダリングできます。

セマンティックトークンに基づく強調表示は、TextMate ベースの構文強調表示への追加と見なされます。セマンティック強調表示は構文強調表示の上に重ねて表示されます。また、言語サーバーはプロジェクトの読み込みと分析に時間がかかる場合があるため、セマンティックトークンの強調表示は短い遅延の後に表示される場合があります。

この記事では、TextMate ベースのトークン化に焦点を当てています。セマンティックトークン化とテーマ設定については、セマンティック強調表示ガイドで説明されています。

TextMate 文法

VS Code は、構文トークン化エンジンとしてTextMate 文法を使用しています。TextMate エディターのために発明されましたが、オープンソースコミュニティによって作成および維持されている多数の言語バンドルがあるため、他の多くのエディターや IDE で採用されています。

TextMate 文法はOniguruma 正規表現に依存しており、通常は plist または JSON として記述されます。TextMate 文法に関する良い入門書はこちらにあり、既存の TextMate 文法を見て、その仕組みについて詳しく学ぶことができます。

TextMate トークンとスコープ

トークンとは、同じプログラム要素の一部である1つ以上の文字です。例となるトークンには、+* のような演算子、myVar のような変数名、"my string" のような文字列などがあります。

各トークンは、トークンのコンテキストを定義するスコープに関連付けられています。スコープは、現在のトークンのコンテキストを指定するドット区切りの識別子のリストです。たとえば、JavaScript の + 演算子には、keyword.operator.arithmetic.js のスコープがあります。

テーマは、構文強調表示を提供するためにスコープを色とスタイルにマッピングします。TextMate は、多くのテーマがターゲットとする一般的なスコープのリストを提供しています。文法をできるだけ広範囲にサポートしてもらうためには、新しいスコープを定義するのではなく、既存のスコープに基づいて構築するようにしてください。

スコープはネストされ、各トークンは親スコープのリストにも関連付けられます。以下の例は、スコープインスペクターを使用して、単純なJavaScript関数における+演算子のスコープ階層を示しています。最も具体的なスコープが上部に、より一般的な親スコープがその下にリストされています。

syntax highlighting scopes

親スコープの情報は、テーマ設定にも使用されます。テーマがスコープをターゲットにする場合、その親スコープを持つすべてのトークンは、テーマが個々のスコープに対してより具体的な色付けを提供しない限り、色付けされます。

基本的な文法の提供

VS Code は json TextMate 文法をサポートしています。これらは grammars 貢献点を介して提供されます。

各文法の貢献は、文法が適用される言語の識別子、文法のトークンのトップレベルスコープ名、および文法ファイルへの相対パスを指定します。以下の例は、架空の abc 言語に対する文法の貢献を示しています。

{
  "contributes": {
    "languages": [
      {
        "id": "abc",
        "extensions": [".abc"]
      }
    ],
    "grammars": [
      {
        "language": "abc",
        "scopeName": "source.abc",
        "path": "./syntaxes/abc.tmGrammar.json"
      }
    ]
  }
}

文法ファイル自体は、トップレベルのルールで構成されます。これは通常、プログラムのトップレベル要素をリストするpatternsセクションと、各要素を定義するrepositoryに分かれています。文法の他のルールは、{ "include": "#id" }を使用してrepositoryの要素を参照できます。

例の abc 文法は、文字 abc をキーワードとしてマークし、括弧のネストを式としてマークします。

{
  "scopeName": "source.abc",
  "patterns": [{ "include": "#expression" }],
  "repository": {
    "expression": {
      "patterns": [{ "include": "#letter" }, { "include": "#paren-expression" }]
    },
    "letter": {
      "match": "a|b|c",
      "name": "keyword.letter"
    },
    "paren-expression": {
      "begin": "\\(",
      "end": "\\)",
      "beginCaptures": {
        "0": { "name": "punctuation.paren.open" }
      },
      "endCaptures": {
        "0": { "name": "punctuation.paren.close" }
      },
      "name": "expression.group",
      "patterns": [{ "include": "#expression" }]
    }
  }
}

文法エンジンは、ドキュメント内のすべてのテキストに expression ルールを連続して適用しようとします。次のような単純なプログラムの場合:

a
(
    b
)
x
(
    (
        c
        xyz
    )
)
(
a

例の文法は、以下のスコープを生成します (最も具体的なスコープから最も具体的でないスコープへと左から右にリストされています)。

a               keyword.letter, source.abc
(               punctuation.paren.open, expression.group, source.abc
    b           keyword.letter, expression.group, source.abc
)               punctuation.paren.close, expression.group, source.abc
x               source.abc
(               punctuation.paren.open, expression.group, source.abc
    (           punctuation.paren.open, expression.group, expression.group, source.abc
        c       keyword.letter, expression.group, expression.group, source.abc
        xyz     expression.group, expression.group, source.abc
    )           punctuation.paren.close, expression.group, expression.group, source.abc
)               punctuation.paren.close, expression.group, source.abc
(               punctuation.paren.open, expression.group, source.abc
a               keyword.letter, expression.group, source.abc

xyz のようなルールに一致しないテキストは、現在のスコープに含まれることに注意してください。ファイルの最後の括弧は、end ルールが一致しなくても expression.group の一部です。これは、end ルールよりも前に end-of-document が見つかったためです。

埋め込み言語

文法に、HTML 内の CSS スタイルブロックなど、親言語内に埋め込み言語が含まれている場合、embeddedLanguages 貢献点を使用して、埋め込み言語を親言語とは異なるものとして VS Code に認識させることができます。これにより、角括弧の対応、コメント化、その他の基本的な言語機能が埋め込み言語で期待どおりに機能するようになります。

embeddedLanguages 貢献点は、埋め込み言語のスコープをトップレベルの言語スコープにマッピングします。以下の例では、meta.embedded.block.javascript スコープ内のすべてのトークンは JavaScript コンテンツとして扱われます。

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/abc.tmLanguage.json",
        "scopeName": "source.abc",
        "embeddedLanguages": {
          "meta.embedded.block.javascript": "javascript"
        }
      }
    ]
  }
}

これで、meta.embedded.block.javascript とマークされた一連のトークン内でコードをコメントアウトしたり、スニペットをトリガーしたりすると、正しい // JavaScript スタイルのコメントと正しい JavaScript スニペットが適用されます。

新しい文法拡張機能の開発

新しい文法拡張機能を素早く作成するには、VS Code の Yeoman テンプレートを使用して yo code を実行し、「New Language」オプションを選択します。

Selecting the 'new language' template in 'yo code'

Yeoman は、新しい拡張機能をスケルトン化するためのいくつかの基本的な質問をガイドします。新しい文法を作成するための重要な質問は次のとおりです。

  • Language id - 言語の一意の識別子。
  • Language name - 言語の人間が読める名前。
  • Scope names - 文法のルートとなる TextMate スコープ名。

Filling in the 'new language' questions

ジェネレーターは、新しい言語とその言語の新しい文法の両方を定義することを想定しています。既存の言語の文法を作成する場合は、対象言語の情報でこれらを入力し、生成された package.jsonlanguages 貢献点を削除するようにしてください。

すべての質問に答えると、Yeoman は以下の構造を持つ新しい拡張機能を作成します。

A new language extension

VS Code がすでに認識している言語に文法を提供する場合は、生成された package.json から languages 貢献点を削除することを忘れないでください。

既存の TextMate 文法の変換

yo code は、既存の TextMate 文法を VS Code 拡張機能に変換するのにも役立ちます。まず、yo code を実行し、Language extension を選択します。既存の文法ファイルを尋ねられたら、.tmLanguage または .json TextMate 文法ファイルへのフルパスを指定します。

Converting an existing TextMate grammar

YAML を使用した文法の記述

文法が複雑になるにつれて、JSONとして理解し、維持することが困難になることがあります。複雑な正規表現を記述したり、文法の側面を説明するためにコメントを追加する必要がある場合は、代わりに YAML を使用して文法を定義することを検討してください。

YAML 文法は JSON ベースの文法とまったく同じ構造を持っていますが、YAML のより簡潔な構文に加えて、複数行文字列やコメントなどの機能を使用できます。

A yaml grammar using multiline strings and comments

VS Code は JSON 文法のみを読み込むことができるため、YAML ベースの文法は JSON に変換する必要があります。js-yaml パッケージとコマンドラインツールを使用すると、これが簡単になります。

# Install js-yaml as a development only dependency in your extension
$ npm install js-yaml --save-dev

# Use the command-line tool to convert the yaml grammar to json
$ npx js-yaml syntaxes/abc.tmLanguage.yaml > syntaxes/abc.tmLanguage.json

インジェクション文法

インジェクション文法を使用すると、既存の文法を拡張できます。インジェクション文法は、既存の文法内の特定のスコープに注入される通常の TextMate 文法です。インジェクション文法の応用例:

  • コメント内の TODO などのキーワードを強調表示する。
  • 既存の文法に、より具体的なスコープ情報を追加する。
  • Markdown フェンスコードブロックに新しい言語の強調表示を追加する。

基本的なインジェクション文法の作成

インジェクション文法は、通常の文法と同様に package.json を通じて提供されます。ただし、インジェクション文法は language を指定する代わりに、injectTo を使用して、文法を注入するターゲット言語スコープのリストを指定します。

この例では、JavaScript のコメント内の TODO をキーワードとして強調表示する簡単なインジェクション文法を作成します。JavaScript ファイルにインジェクション文法を適用するには、injectTosource.js ターゲット言語スコープを使用します。

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "todo-comment.injection",
        "injectTo": ["source.js"]
      }
    ]
  }
}

文法自体は、トップレベルの injectionSelector エントリを除いて、標準の TextMate 文法です。injectionSelector は、注入された文法が適用されるスコープを指定するスコープセレクターです。この例では、すべての // コメント内の TODO という単語を強調表示したいと考えています。スコープインスペクターを使用して、JavaScript のダブルスラッシュコメントが comment.line.double-slash スコープを持つことを確認したので、インジェクションセレクターは L:comment.line.double-slash になります。

{
  "scopeName": "todo-comment.injection",
  "injectionSelector": "L:comment.line.double-slash",
  "patterns": [
    {
      "include": "#todo-keyword"
    }
  ],
  "repository": {
    "todo-keyword": {
      "match": "TODO",
      "name": "keyword.todo"
    }
  }
}

インジェクションセレクターの L: は、インジェクションが既存の文法ルールの左側に追加されることを意味します。これは基本的に、注入された文法のルールが既存の文法ルールよりも前に適用されることを意味します。

埋め込み言語

インジェクション文法は、埋め込み言語を親文法に提供することもできます。通常の文法と同様に、インジェクション文法は embeddedLanguages を使用して、埋め込み言語のスコープをトップレベルの言語スコープにマッピングできます。

例えば、JavaScript 文字列内の SQL クエリを強調表示する拡張機能は、embeddedLanguages を使用して、meta.embedded.inline.sql とマークされた文字列内のすべてのトークンが、角括弧の対応やスニペット選択などの基本的な言語機能のために SQL として扱われるようにすることができます。

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "sql-string.injection",
        "injectTo": ["source.js"],
        "embeddedLanguages": {
          "meta.embedded.inline.sql": "sql"
        }
      }
    ]
  }
}

トークンタイプと埋め込み言語

インジェクション言語の埋め込み言語には、もう一つ複雑な点があります。デフォルトでは、VS Code は文字列内のすべてのトークンを文字列コンテンツとして扱い、コメント内のすべてのトークンをトークンコンテンツとして扱います。文字列やコメント内では、角括弧の対応や自動閉じペアなどの機能が無効になるため、埋め込み言語が文字列やコメント内に現れる場合、これらの機能も埋め込み言語内で無効になります。

この動作を上書きするには、meta.embedded.* スコープを使用して、VS Code がトークンを文字列またはコメントコンテンツとしてマークするのをリセットできます。VS Code が埋め込み言語を適切に処理するように、埋め込み言語を常に meta.embedded.* スコープで囲むことをお勧めします。

文法に meta.embedded.* スコープを追加できない場合は、代わりに文法の貢献点の tokenTypes を使用して、特定のスコープをコンテンツモードにマッピングできます。以下の tokenTypes セクションは、my.sql.template.string スコープ内のコンテンツがソースコードとして扱われることを保証します。

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "sql-string.injection",
        "injectTo": ["source.js"],
        "embeddedLanguages": {
          "my.sql.template.string": "sql"
        },
        "tokenTypes": {
          "my.sql.template.string": "other"
        }
      }
    ]
  }
}

テーマ設定

テーマ設定とは、トークンに色とスタイルを割り当てることです。テーマ設定ルールはカラーテーマで指定されますが、ユーザーはユーザー設定でテーマ設定ルールをカスタマイズできます。

TextMate テーマルールは tokenColors で定義され、通常の TextMate テーマと同じ構文を持ちます。各ルールは TextMate スコープセレクターと結果の色とスタイルを定義します。

トークンの色とスタイルを評価する際、現在のトークンのスコープはルールのセレクターと照合され、各スタイルプロパティ (前景色、太字、斜体、下線) に最も具体的なルールが検索されます。

カラーテーマガイドでは、カラーテーマの作成方法について説明しています。セマンティックトークンのテーマ設定については、セマンティック強調表示ガイドで説明されています。

スコープインスペクター

VS Code に組み込まれているスコープインスペクターツールは、文法とセマンティックトークンのデバッグに役立ちます。ファイル内の現在の位置にあるトークンとセマンティックトークンのスコープ、およびそのトークンに適用されるテーマルールに関するメタデータを表示します。

コマンドパレットから Developer: Inspect Editor Tokens and Scopes コマンドでスコープインスペクターを起動するか、キーバインドを作成します。

{
  "key": "cmd+alt+shift+i",
  "command": "editor.action.inspectTMScopes"
}

scope inspector

スコープインスペクターは以下の情報を表示します。

  1. 現在のトークン。
  2. トークンに関するメタデータと、計算された外観に関する情報。埋め込み言語を使用している場合、ここで重要なエントリは languagetoken type です。
  3. セマンティックトークンセクションは、現在の言語にセマンティックトークンプロバイダーが利用可能で、現在のテーマがセマンティック強調表示をサポートしている場合に表示されます。現在のセマンティックトークンタイプと修飾子、およびセマンティックトークンタイプと修飾子に一致するテーマルールが表示されます。
  4. TextMate セクションは、現在の TextMate トークンのスコープリストを、最も具体的なスコープを先頭にして表示します。また、スコープに一致する最も具体的なテーマルールも表示されます。これは、トークンの現在のスタイルを担うテーマルールのみを表示し、上書きされたルールは表示しません。セマンティックトークンが存在する場合、テーマルールはセマンティックトークンに一致するルールと異なる場合にのみ表示されます。