なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

VS Code / Shiki で自作言語をシンタックスハイライトしたい

みなさん自作プログラミング言語作っていますか?わたしは音楽を作りたかったのですが、 DAW 操作が苦手すぎて訳わからんので、音を鳴らせる言語を自作しました。 こんな感じでそれらしい音がそれらしいテンポでなるのですが、

song "Example" {
  tempo 160bpm
  meter 4/4
  key D minor

  pattern lead = sequence step 1/8 {
    note D4
    note F4
    note A4
    note C5
  }
}

プログラミング言語を作ると、エディタや Web ページでシンタックスハイライトしたくなりますよね。ということで、この記事では、自作言語の TextMate Grammar を用意し、VS Code / Shiki から読み込んでハイライトできるようにするまでを紹介します。実際にハイライト出来ている例は上のコードブロックの通りです。

VS Code でシンタックスハイライトする

VS Code で自作言語のシンタックスハイライトをするには、 TextMate Grammer ファイルを用意し、拡張機能経由で読み込む必要があります。 TextMate Grammar ファイルは、簡単に言えば正規表現を使ってソースコードの各部分に keyword.control や string.quoted といった scope を割り当て、対応するエディタやライブラリでシンタックスハイライトするためのものです。

ということで、 TextMate Grammer ファイルを準備します。必要最低限は次の通りです。

// @syntaxes/symphra.tmLanguage.json
{
  "name": "symphra",
  "scopeName": "source.symphra",
  "patterns": [],
  "repository": {}
}

今回は Shiki 側でも symphra という名前で扱いたいので、name は symphra としています。VS Code 側の言語識別子については、後述する package.json の languages.id と grammars.language で設定します。

次に、キーワードを追加します。必要最低限だとこんな感じ:

// @syntaxes/symphra.tmLanguage.json
{
  "name": "symphra",
  "scopeName": "source.symphra",
  "patterns": [
    {
      "include": "#keywords"
    }
  ],
  "repository": {
    "keywords": {
      "patterns": [
        {
          "name": "keyword.control.declaration.symphra",
          "match": "\\b(project|song|instrument|pattern|sequence|steps|sampled|sampler|drum_machine|soundfont|vst3|synth|supersaw|choose|rhythm|track|section|arrangement|master|limiter|arpeggiate)\\b"
        },
      ]
    }
  }
}

これを VSCode に読み込ませることで、キーワード、例えば sampled などをシンタックスハイライトすることが可能です。
VSCode に読み込ませるには、手順に沿って拡張機能ボイラープレートを作成しpackage.json に以下を追記することで、シンタックスハイライトが読み込まれます。

// @package.json
{
  // 他のフィールド......
  "contributes": {
    "languages": [
      {
        "id": "symphra",
        "extensions": [".sym"]
      }
    ],
    "grammars": [
      {
        "language": "symphra",
        "scopeName": "source.symphra",
        "path": "./syntaxes/symphra.tmLanguage.json"
      }
    ]
  }
}

この状態で拡張機能をデバッグすると、 hogefuga.sym ファイルで、先ほど作成した TextMate Grammer ファイルが読み込まれ、シンタックスハイライトがされるはずです。

Shiki でシンタックスハイライトする

Shiki のシンタックスハイライトでも、 TextMate Grammer ファイルがそのまま使えます。

import { createHighlighter } from "shiki";
import symphraGrammar from "./symphra.tmLanguage.json"; // 先ほど作成したファイル

const highlighter = await createHighlighter({
  themes: ["github-dark"],
  langs: [
    {
      ...symphraGrammar,
      name: "symphra"
    }
  ]
});

const html = highlighter.codeToHtml(`project { }`, { lang: "symphra", theme: "github-dark" });

簡単ですね。

はてなブログで Shiki を使って自作言語をシンタックスハイライトする

このブログははてなブログを使っているのですが、せっかく作ったならはてなブログでもシンタックスハイライトしたいですよね。 ということで、やり方は次の通り。以下のようなコードを詳細設定から head に組み込めば、該当部で自作言語のシンタックスハイライトができます。

<script type="module">
import { codeToHtml, createHighlighter } from 'https://esm.sh/shiki@4.4.2'

const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;


document.addEventListener('DOMContentLoaded', async (event) => {
  const grammar = await fetch("https://raw.githubusercontent.com/mika-f/Symphra/main/editors/vscode/syntaxes/symphra.tmLanguage.json").then((res) => res.json());
  const highlighter = await createHighlighter({
    langs: [
      {
        ...grammar,
        name: "symphra",
      },
    ],
    themes: ["dark-plus", "light-plus"],
  });

  const hasSymphraLanguage = (element) => {
    const lang = element.dataset["lang"].split(":")[0];
    return lang === "symphra";
  };

  document.querySelectorAll('pre.code').forEach(async (el) => {
    let html = "";
    if (hasSymphraLanguage(el)) {
      html = highlighter.codeToHtml(el.innerText, { lang: "symphra", theme: isDarkMode ? "dark-plus" : "light-plus" });
    } else {
      html = await codeToHtml(el.innerText, { theme: isDarkMode ? "dark-plus" : "light-plus", lang: el.dataset["lang"].split(":")[0] });
    }

    const wrapper = document.createElement("div");
    wrapper.innerHTML = html;
    el.replaceWith(wrapper.firstElementChild);
  });
});
</script>

このように、一度 TextMate Grammar を用意してしまえば VS Code だけでなく Shiki でも利用できるので、エディタとドキュメントの両方で同じシンタックスハイライトを比較的簡単に提供できます。ちなみに、この記事の Symphra のコードブロックも上記のコードで実際にハイライトしています。