【VSCode】シンタックスハイライトを自作する2ステップ

複数拡張機能のシンタックスハイライトが上書きされてしまう問題を示すイメージ画像 OSS開発
  • VSCodeでシンタックスハイライトを自作する方法を知りたい
  • シンタックスハイライトを設定して、拡張機能を使いやすくしたい!
  • 何とか設定できたけど、他のシンタックスハイライトが上書きしてしまう…

シンタックスハイライトとは、あらかじめ指定された記号などを異なる色やスタイルで表示する機能のことです[1]。VSCodeは拡張機能を作成できるため、「シンタックスハイライトを自作して使いやすくしたい」方もいらっしゃるでしょう。しかし、拡張機能に直接実装しようとすると、実は他のシンタックスハイライトを上書きしてしまいます。

私もまた、VSCode拡張機能の開発経験[2]があります。特にMarkdownに関する拡張機能が多く、%文字%{色}とするだけ色付きの生成できたり、:::note infoとすると注釈を作成できたりします。より使いやすくするためにそれぞれの拡張機能でシンタックスハイライトを作成しましたが、その際に一方が他方を上書きする問題に遭遇しました。

そこで、この記事ではシンタックスハイライトが上書きされてしまう問題を解説した上で、初心者でも簡単にシンタックスハイライトを実装できる手順を紹介します。具体的には拡張機能に直接実装するのではなく、自作テーマを用意しそこにシンタックスハイライトを追記していきます。

この記事を読むだけで、シンタックスハイライトを自由に追加作成できる環境を構築できます。どんなシンタックスハイライトでも追加できるようになるため、拡張機能がより使いやすくなるでしょう。

私が直面した問題への対処法を記事に凝縮しました。VSCodeでシンタックスハイライトを作成したい方は是非最後まで読んでください。

シンタックスハイライトの概要

シンタックスハイライトとは、特定の記号群やキーワードを任意の色やスタイルで表示できる、エディタの文字表示に関する機能です[1:1]。例えば、Markdown MojiColor%文字%{色}と入力すると指定した色でレンダリングされる拡張機能ですが、Markdownファイル上では通常の文字同様「白」のままです。

Markdownの記述例とシンタックスハイライト適用前の比較画像

シンタックスハイライトを適用すると、他の文字とは異なる色やスタイルを適用できます。例えば、%%{}の記号部分のみをオレンジにしてみましょう。

シンタックスハイライト適用後の記号がオレンジ色で表示された画像

このようにシンタックスハイライトを活用すると、自作拡張機能をより使いやすくできます。

シンタックスハイライトの作成ステップ(記号の検出と色割り当て)

ではさっそくシンタックスハイライトを実装してみましょう。拡張機能の中でシンタックスハイライトを実装するには、以下の手順を踏む必要があります。

  1. 拡張機能の作成
  2. ステップ1.記号を検出させる
  3. ステップ2.色を割り当てる
  4. 拡張機能の公開

拡張機能の作成

まずは、VSCode拡張機能を作成します。以下の記事を参照してください。

【VSCode】拡張機能を自作する方法 - Qiita
はじめにVScodeの拡張機能作成は大きく、自作する ← 【今回】公開する ← 【次回】の2つのプロセスを踏みます。今回は、「作る」方法を解説します。VScodeを作成するにはYeomanというアプリを使います…

ステップ1.記号の検出

シンタックスハイライトを適用する文字を検出させます。package.json"contributes": {}の中に、"grammars": []を設定しましょう。grammarsはファイルを参照するため、拡張機能に「syntaxes/任意の名前.tmLanguage.json」を作成します。

tmLanguage.json定義ファイルの構造や例を解説するイメージ画像

任意の名前.tmLanguage.jsonは、TextMate形式のシンタックス定義ファイル(tmLanguage.json)です。以下のような書き方で記号を検出させます。

JSON
// test.tmLanguage.json
{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", // スキーマ(構造や型の定義)
    "name": "test", // シンタックス定義ファイルの名前
    "scopeName": "source.markdown", // 適用されるファイル種別
    "patterns": [ // 適用されるルール
        {
            "include": "#markdown-mojicolor"
        },
        { 
            "include": "text.html.markdown" 
        }
    ],
    "repository": { // ルールの倉庫
        "markdown-mojicolor": {
            "patterns": [
                {
                    "begin": "(%)(.*?)(%)(\\{)",
                    "end": "\\}",
                    "beginCaptures": {
                        "1": { "name": "markdown-mojicolor.begin.markdown" },
                        "2": { "name": "markdown-mojicolor.color-text.markdown" },
                        "3": { "name": "markdown-mojicolor.end.markdown" },
                        "4": { "name": "markdown-mojicolor.bracket.begin.markdown" }
                    },
                    "endCaptures": {
                        "0": { "name": "markdown-mojicolor.bracket.end.markdown" }
                    }
                }
            ]
        }
    } 
}

各項目の役割を詳しく解説します。

1. $schema

JSON
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json"

このJSONファイルのスキーマ(構造や型の定義)を示します。 エディタの補完やバリデーションに使われますが、実行時には特に影響しません。

2. name

JSON
"name": "test"

このシンタックス定義ファイルの名前です。エディタのUIなどで参照されることがあります。

3. scopeName

JSON
"scopeName": "source.markdown"

この文法が適用されるファイル種別(スコープ名)です。ここではMarkdownファイル(source.markdown)に適用されます。

4. patterns

JSON
"patterns": [
    {
        "include": "#markdown-mojicolor"
    },
    { 
        "include": "text.html.markdown" 
    }
]

文法のトップレベルで適用するパターン(ルール)のリストです。

  • {"include": "#markdown-mojicolor"}

    → このファイルのrepository内のmarkdown-mojicolorというルールを適用します。
  • {"include": "text.html.markdown"}

    → 既存のMarkdown文法(TextMateの標準スコープ)を取り込みます。

これにより、独自ルールと標準Markdownルールの両方が適用されます。

5. repository

JSON
"repository": {
    "markdown-mojicolor": {
        "patterns": [
            {
                "begin": "(%)(.*?)(%)(\\{)",
                "end": "\\}",
                "beginCaptures": {
                    "1": { "name": "markdown-mojicolor.begin.markdown" },
                    "2": { "name": "markdown-mojicolor.color-text.markdown" },
                    "3": { "name": "markdown-mojicolor.end.markdown" },
                    "4": { "name": "markdown-mojicolor.bracket.begin.markdown" }
                },
                "endCaptures": {
                    "0": { "name": "markdown-mojicolor.bracket.end.markdown" }
                }
            }
        ]
    }
}

複数のルール(パターン)を再利用・整理するための「ルールの倉庫」です。

  • begin:

    "begin": "(%)(.*?)(%)(\\{)"
    • この正規表現にマッチしたら「ここからハイライト開始」
    • 例:%文字%{ のような部分
  • end:

    "end": "\\}"
    • この正規表現にマッチしたら「ここでハイライト終了」
    • 例:}
  • beginCaptures:

    • 1: "markdown-mojicolor.begin.markdown"

      → 最初の%にこのスコープを付与
    • 2: "markdown-mojicolor.color-text.markdown"

      %%の間の文字列(色名など)にこのスコープを付与
    • 3: "markdown-mojicolor.end.markdown"

      → 2つ目の%にこのスコープを付与
    • 4: "markdown-mojicolor.bracket.begin.markdown"

      {にこのスコープを付与
  • endCaptures:

    • 0: "markdown-mojicolor.bracket.end.markdown"

      → 終了の}にこのスコープを付与

このようにtmLanguage.jsonは、Markdownファイル内で%文字%{色名}のような独自記法を認識し、各部分に独自のスコープ名を割り当てます。これでシンタックスハイライトが適用される文字が検出されました。

ステップ2.色の割り当て

ステップ1 で検出した文字に独自の色やスタイルを割り当てます。package.json"contributes": {}の中に、"themes": []を設定しましょう。themesもまたファイルを参照するため、拡張機能に「themes/任意の名前-color-theme.json」を作成します。

カラーテーマファイルで色指定する箇所の説明・イメージ画像

任意の名前-color-theme.jsonは、Visual Studio Code用のカラーテーマファイルです。ステップ1で検出した特定のTextMateスコープに対して、シンタックスハイライトの色やスタイルを設定する役割を持ちます。今回は各記号をオレンジにしましょう。

JSON
// 任意の名前-color-theme.json
{
    "tokenColors": [ // カラーテーマの中核となる配列
        {
            "scope": "markdown-mojicolor.begin.markdown, markdown-mojicolor.end.markdown, markdown-mojicolor.bracket.begin.markdown, markdown-mojicolor.bracket.end.markdown", // 色を適用したいTextMateスコープ名をカンマ区切りで指定
            "settings": { // スコープに適用する色や装飾を指定
                "foreground": "#F57900"
            }
        }
    ]
}

各項目の役割を解説します。

  • tokenColors

    • カラーテーマの中核となる配列です。
    • ここに、どのスコープ(構文要素)にどんな色やスタイルを適用するかを定義します。
  • scope

    • 色を適用したいTextMateスコープ名をカンマ区切りで指定します。
    • ここでは、markdown-mojicolor.begin.markdownmarkdown-mojicolor.end.markdownmarkdown-mojicolor.bracket.begin.markdownmarkdown-mojicolor.bracket.end.markdownの4つのスコープに対して色を指定しています。
    • これらのスコープは、独自のTextMate文法(test.tmLanguage.json)で設定したものです。
  • settings

    • スコープに適用する色や装飾を指定します。
    • foregroundは文字色を表します。ここでは#F57900(オレンジ系)を指定しています。

このテーマファイルをVSCodeが読み込むことで、指定したスコープの文字色が自動的に#F57900で表示されます。検出した文字に独自の色が適用されるでしょう。

拡張機能の公開

最後に、拡張機能を公開しましょう。公開された拡張機能をインストールすると、シンタックスハイライトが適用されます。拡張機能の公開方法は以下をご参照ください。

【VSCode】自作した拡張機能を公開する方法 - Qiita
はじめに前回に引き続き、今度は自作したVScode拡張機能の公開方法を記述します。自作する ← 【前回】公開する ← 【今回】拡張機能の公開手順は以下の通りです。vsceのインストールA…

拡張機能をインストールしたあとに、テーマを変更します。今回は「test」という名前のテーマなので、「設定 → テーマ → 配色テーマ → test」を選びましょう。

配色テーマを変更してシンタックスハイライトを適用する様子を示すGIFアニメ

シンタックスハイライトが適用され、%%{}がオレンジになりました。

シンタックスハイライトの競合問題

複数拡張機能のシンタックスハイライトが上書きされてしまう問題を示すイメージ画像

package.json"contributes": {}の中に"grammars": []"themes": []を設定し、「syntaxes/任意の名前.tmLanguage.json」と「themes/任意の名前-color-theme.json」を作成すると、シンタックスハイライトが適用されます。

しかし、実はこのままだと既存のシンタックスハイライトを上書きしてしまいます。なぜなら、VSCodeの仕組み上、1つの言語に同時に複数のgrammarを適用することはできない からです。

JSON
// package.json
"contributes": {
  "grammars": [
    {
      "language": "markdown", // Markdownひとつに一つのgrammarしか適用できない
      "scopeName": "source.markdown",
      "path": "./syntaxes/test.tmLanguage.json"
    }
  ],
}

そのため、「自作の拡張機能を使いやすくしよう!」と善意でシンタックスハイライトを実装すると、思わぬトラブルを引き起こすでしょう。

拡張機能ごとのシンタックスハイライトを同時に適用する方法

シンタックスハイライトの上書き問題は、テーマを作成し、そこにシンタックスハイライトを追記する形で回避できるでしょう。VSCodeは拡張機能だけでなく、テーマも作成できます。yo codeを実行したあとに「New Color Theme」を選びましょう。中身は通常の拡張機能よりシンプルです。

yo code を実行したあとテーマを選んでいる画像

先程と同様、package.json"contributes": {}の中に"grammars": []"themes": []を設定し、「syntaxes/任意の名前.tmLanguage.json」と「themes/任意の名前-color-theme.json」を作成します。そして、ひとつのgrammarに全ての拡張機能のパターンを書き込みます。

JSON
// 任意の名前.tmLanguage.json
{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
    "name": "test",
    "scopeName": "source.markdown",
    "patterns": [
        {
            "include": "#markdown-info" // 拡張機能「Markdown Info」のシンタックスハイライト
        },
        {
            "include": "#markdown-mojicolor" // 拡張機能「Markdown MojiColor」 のシンタックスハイライト
        },
        { 
            "include": "text.html.markdown"  // 既存のMarkdownのシンタックスハイライトをそのまま取り込む
        }
    ],
    "repository": {
        "markdown-info": {
            "patterns": [
                {
                    "name": "markdown-info.info.markdown",
                    "begin": "^:::(note|message) info",
                    "end": "^:::$",
                    "patterns": [
                        {
                            "include": "$self"
                        }
                    ]
                },{
                    "name": "markdown-info.warn.markdown",
                    "begin": "^:::(note|message) warn",
                    "end": "^:::$",
                    "patterns": [
                        {
                            "include": "$self"
                        }
                    ]
                },{
                    "name": "markdown-info.alert.markdown",
                    "begin": "^:::(note|message) alert",
                    "end": "^:::$",
                    "patterns": [
                        {
                            "include": "$self"
                        }
                    ]
                },{
                    "name": "markdown-info.question.markdown",
                    "begin": "^:::(note|message) question",
                    "end": "^:::$",
                    "patterns": [
                        {
                            "include": "$self"
                        }
                    ]
                }
            ]
        },
        "markdown-mojicolor": {
            "patterns": [
                {
                    "begin": "(%)(.*?)(%)(\\{)",
                    "end": "\\}",
                    "beginCaptures": {
                        "1": { "name": "markdown-mojicolor.begin.markdown" },
                        "2": { "name": "markdown-mojicolor.color-text.markdown" },
                        "3": { "name": "markdown-mojicolor.end.markdown" },
                        "4": { "name": "markdown-mojicolor.bracket.begin.markdown" }
                    },
                    "endCaptures": {
                        "0": { "name": "markdown-mojicolor.bracket.end.markdown" }
                    }
                }
            ]
        }
    } 
}

"patterns": []を見れば、拡張機能ごとにパターンを取り込んでいることが分かります。text.html.markdownを読み込むと既存のMarkdownのシンタックスハイライトをそのまま取り込むため、忘れずに書き込みましょう。

新しい拡張機能をインストールしたり自作したりした際に、この自作テーマに書き込むことでシンタックスハイライトを追記できます。これで、シンタックスハイライトを自由に追加作成できる環境を構築できました。

まとめ

この記事では、VSCodeでシンタックスハイライトを自作する方法についてまとめました。VSCodeで独自のシンタックスハイライトを作成したい場合、他のシンタックスハイライトを上書きしてしまう問題について考慮しましょう。

  • シンタックスハイライトは「特定の記号やキーワードに色やスタイルを付けて表示する機能」であり、プログラムの可読性や拡張機能の使いやすさ向上に役立ちます。
  • 実装の流れは以下の2ステップです。
    1. 記号の検出
      • syntaxes/任意の名前.tmLanguage.json(TextMate形式)で、独自の記号やキーワードを検出し、それぞれに独自のスコープ名を割り当てます。
    2. 色の割り当て
      • themes/任意の名前-color-theme.json(カラーテーマファイル)で、スコープごとに色やスタイルを指定します。
  • しかし、VSCodeでは1つの言語に同時に複数のgrammarを適用できないため、拡張機能同士でシンタックスハイライトが上書きされるトラブルがよく起こります。
  • この競合を避けるには、自作テーマに全ての拡張機能の記号検出ルール(grammar)をまとめて管理し、シンタックスハイライトを「theme(カラーテーマ)」側に追加する方法がベストです。
  • そうすることで、どんな拡張機能や独自記法にも柔軟にシンタックスハイライトを追加でき、競合や上書き問題が解消されます。

VSCodeでシンタックスハイライトをより自由にカスタマイズ・追加したい方は、今回紹介した自作テーマを活用した方法をぜひ試してみてください。これにより、快適で自分仕様のエディタ環境が実現できるでしょう。


  1. シンタックスハイライトとは?意味を分かりやすく解説 – IT用語辞典 e-Words ↩︎ ↩︎

  2. Your Repositories ↩︎

よかったらハートマークを押してください!
大変励みになります😄
この記事を書いた人
WEBライター
ゆす

オープンソースソフトウェア(OSS)開発の問題解決方法や役立つ知識をシェアするために、ブログを立ち上げました。また、実際に体験したサービスのレポートも記事としてまとめています。

お仕事のご依頼やご質問がございましたら、お気軽にお問い合わせください。

ゆすをフォローする
OSS開発
\シェアする/
タイトルとURLをコピーしました