シンタックスハイライトガイド
構文強調表示は、Visual Studio Code エディターに表示されるソースコードの色とスタイルを決定します。JavaScript の if や for のようなキーワードを、文字列、コメント、変数名とは異なる色で表示する役割を担っています。
構文強調表示には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 関数内の + 演算子のスコープ階層を表示しています。最も具体的なスコープが一番上に表示され、より一般的な親スコープがその下に表示されます

親スコープ情報もテーマ設定に使用されます。テーマがスコープをターゲットとする場合、テーマが個々のスコープに対してより具体的な色付けを提供しない限り、その親スコープを持つすべてのトークンが色付けされます。
基本的な文法を貢献する
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 文法は、文字 a、b、c をキーワードとしてマークし、括弧の入れ子を式としてマークします。
{
"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 オプションを選択します

Yeoman は、新しい拡張機能の足場を組むためのいくつかの基本的な質問に案内します。新しい文法を作成するための重要な質問は次のとおりです
Language id- あなたの言語の一意の識別子。Language name- あなたの言語の人間が読める名前。Scope names- 文法のルート TextMate スコープ名。

ジェネレーターは、新しい言語とその言語の新しい文法の両方を定義したいと仮定しています。既存の言語の文法を作成している場合は、これらの情報にターゲット言語の情報を入力し、生成された package.json 内の languages 貢献ポイントを必ず削除してください。
すべての質問に答えた後、Yeoman は次の構造を持つ新しい拡張機能を作成します

VS Code が既に認識している言語に文法を貢献している場合は、生成された package.json 内の languages 貢献ポイントを必ず削除してください。
既存の TextMate 文法の変換
yo code は、既存の TextMate 文法を VS Code 拡張機能に変換するのにも役立ちます。ここでも、yo code を実行し、Language extension を選択することから始めます。既存の文法ファイルを尋ねられたら、.tmLanguage または .json TextMate 文法ファイルのいずれかへのフルパスを指定します

YAML を使用した文法の記述
文法が複雑になるにつれて、json として理解し、維持することが難しくなることがあります。複雑な正規表現を記述したり、文法の側面を説明するためにコメントを追加する必要があると感じた場合は、代わりに yaml を使用して文法を定義することを検討してください。
Yaml 文法は json ベースの文法とまったく同じ構造ですが、yaml のより簡潔な構文と、複数行文字列やコメントなどの機能を使用できます。

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 ファイルにインジェクション文法を適用するには、injectTo で source.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"
}
}
]
}
}
トークンタイプと埋め込み言語
インジェクション言語の埋め込み言語には、もう1つ複雑な点があります。デフォルトでは、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"
}

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