夜、自作のアプリで使っている壁紙生成ルールのファイルを開いたとき、画面がやけに静かに見えました。palette: sunset も @when season == winter も、コメントも文字列も、すべて同じ灰色。JavaScript や Python なら一目で読める構造が、自分で決めた拡張子 .wprules になった途端に平坦なテキストに戻ってしまう。行を目で追うたびに、キーと値の境目を頭の中で引き直している自分に気づきました。
原因はエディタの性能ではありません。Antigravity は .wprules という拡張子を知らないので、色を塗る手がかり(言語定義)を持っていないだけです。逆に言えば、その手がかりを一枚渡せば色は通ります。ここでは、独自ファイルにハイライトを効かせる最小構成の拡張を、実際に動くコードで組み立てていきます。派手な機能は足しません。まず「読める」状態を最短で作ることに絞ります。個人開発でアプリを長く保守していると、自分だけが読む設定ファイルは少しずつ増えていきます。だからこそ、この一枚の投資は日々の視認性にそのまま返ってきます。
モノクロの正体 — ハイライトは「スコープ」を色に写す作業
シンタックスハイライトは、テキストを直接色付けしているわけではありません。まず文法(grammar)がテキストの各部分に keyword.control や string.quoted といったスコープ名 を割り当て、次にテーマがそのスコープ名に色を対応づけます。二段構えです。
独自ファイルがモノクロなのは、この一段目が存在しないからです。テーマは十分な色を持っているのに、塗るべき対象にスコープ名が付いていない。だから、私たちがやるべきことは色を選ぶことではなく、テキストに正しいスコープ名を割り当てる文法を書くこと に尽きます。
この設計は Antigravity 固有の発明ではなく、TextMate に由来し VS Code 系エディタが広く採用している方式です。Antigravity も同じ仕組みを踏襲しているため、TextMate 文法(.tmLanguage.json)をそのまま持ち込めます。
対象にするファイル
今回色を通すのは、次のような自作の .wprules です。壁紙生成の条件を宣言的に書くための、ごく小さな DSL だと考えてください。
# 冬の夕暮れプリセット
@when season == winter
palette: sunset
brightness: 0.82
title: "Winter Glow"
ここには4種類の意味の異なる要素があります。コメント(#)、ディレクティブ(@when ...)、キー(palette など)、値(数値・真偽・文字列)。この4つにそれぞれ違うスコープを割り当てれば、読むときの手がかりが一気に戻ってきます。
Step 1: 拡張のひな型を用意する
拡張はフォルダひとつで完結します。まず package.json で「この拡張は .wprules という言語を提供します」と宣言します。
{
"name" : "wprules-language" ,
"displayName" : "WP Rules" ,
"version" : "0.0.1" ,
"engines" : { "vscode" : "^1.90.0" },
"categories" : [ "Programming Languages" ],
"contributes" : {
"languages" : [
{
"id" : "wprules" ,
"aliases" : [ "WP Rules" , "wprules" ],
"extensions" : [ ".wprules" ],
"configuration" : "./language-configuration.json"
}
],
"grammars" : [
{
"language" : "wprules" ,
"scopeName" : "source.wprules" ,
"path" : "./syntaxes/wprules.tmLanguage.json"
}
]
}
}
ここで押さえておきたいのは、languages[].id(内部識別子 wprules)と grammars[].scopeName(文法のルート source.wprules)が別物だという点です。前者はファイルと言語を結び付ける名前、後者は文法ツリーの最上位スコープです。両者を取り違えると、言語は認識されるのに色が付かない、という分かりにくい状態になります。
Step 2: language-configuration.json でコメントと括弧を教える
色付けの前に、コメント記号や括弧の対応をエディタに伝えておきます。これは Cmd+/ でのコメントトグルや自動閉じ括弧に効いてきます。
{
"comments" : { "lineComment" : "#" },
"brackets" : [[ "{" , "}" ], [ "[" , "]" ]],
"autoClosingPairs" : [
{ "open" : " \" " , "close" : " \" " },
{ "open" : "[" , "close" : "]" }
],
"surroundingPairs" : [[ " \" " , " \" " ], [ "[" , "]" ]]
}
小さなファイルですが、これがあるだけで編集の手触りが実用に届きます。文法(色)と設定(挙動)は別ファイルに分かれている、と覚えておくと迷いません。
Step 3: TextMate 文法を書く
本体です。syntaxes/wprules.tmLanguage.json に、4要素それぞれのパターンとスコープを定義します。
{
"$schema" : "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json" ,
"name" : "WP Rules" ,
"scopeName" : "source.wprules" ,
"patterns" : [
{ "include" : "#comment" },
{ "include" : "#directive" },
{ "include" : "#pair" }
],
"repository" : {
"comment" : {
"match" : "#.*$" ,
"name" : "comment.line.number-sign.wprules"
},
"directive" : {
"begin" : "^ \\ s*(@[a-zA-Z]+) \\ b" ,
"beginCaptures" : {
"1" : { "name" : "keyword.control.directive.wprules" }
},
"end" : "$" ,
"patterns" : [
{ "match" : "==|!=|>=|<=|>|<" , "name" : "keyword.operator.comparison.wprules" },
{ "match" : " \\ b[a-zA-Z_][a-zA-Z0-9_]* \\ b" , "name" : "variable.other.wprules" }
]
},
"pair" : {
"begin" : "^ \\ s*([a-zA-Z_][ \\ w-]*) \\ s*(:)" ,
"beginCaptures" : {
"1" : { "name" : "entity.name.tag.wprules" },
"2" : { "name" : "punctuation.separator.key-value.wprules" }
},
"end" : "$" ,
"patterns" : [{ "include" : "#value" }]
},
"value" : {
"patterns" : [
{ "match" : " \" [^ \" ]* \" " , "name" : "string.quoted.double.wprules" },
{ "match" : " \\ b(true|false) \\ b" , "name" : "constant.language.boolean.wprules" },
{ "match" : " \\ b \\ d+( \\ . \\ d+)? \\ b" , "name" : "constant.numeric.wprules" }
]
}
}
}
パターンを repository に切り出し、patterns から #name で参照しています。こうすると各要素を独立に読み書きでき、後から値の種類を足すのも局所的な変更で済みます。
スコープ名は自作の造語ではなく、comment.line・keyword.control・entity.name.tag・string.quoted.double・constant.numeric という、広く使われている標準名に寄せています。これが3つ目の落とし穴(後述)を避けるための肝です。
Step 4: 読み込ませて確認する
拡張フォルダを Antigravity の拡張ディレクトリ(~/.antigravity/extensions/ 配下、環境により異なります)に置くか、拡張開発ホストで開いてリロードします。.wprules を開けば、キーとディレクティブとコメントと値が、それぞれ別の色で立ち上がってくるはずです。
色が付かない、あるいは意図と違う色になったときは、当て推量ではなくスコープ検査 で確かめます。コマンドパレットから「Developer: Inspect Editor Tokens and Scopes」に相当する機能を開き、カーソル位置のトークンに実際どのスコープが割り当てられているかを見ます。期待したスコープが出ていなければ文法側の問題、スコープは正しいのに色が地味ならテーマ側の話、と切り分けられます。この一手間が、原因のあたりを一瞬でつけてくれます。
落とし穴1: 正規表現は「並び順」で勝敗が決まる
TextMate 文法は、上から順にパターンを試し、最初にマッチしたものを採用します。つまり広すぎるパターンを先に置くと、後続の細かいパターンに到達しません 。
たとえば value の中で数値マッチ \b\d+...\b を、真偽値 \b(true|false)\b より前に置いても実害は出ませんが、もし「識別子は何でも変数扱い」という緩いパターンを真偽値より先に置くと、true が変数色に飲み込まれます。細かい・具体的なものを先に、広いものを後に——この順序を守るだけで、原因不明の色化けの大半は消えます。本番運用のワークフローに載せる前に、この注意点をここで対処しておくと安心です。
症状 ありがちな原因 確認する場所
一部だけ色が付かない 広いパターンが先に飲み込んでいる patterns の並び順
まったく色が付かない scopeName と grammar の対応ずれ package.json の grammars
色は付くが地味 非標準スコープ名でテーマ未対応 スコープ検査 → 標準名へ
落とし穴2: 値の中に別言語が混ざるとき
DSL の値として JSON やシェルの断片を書けるようにしている場合、その部分だけ別言語としてハイライトしたくなります。TextMate 文法はこれを埋め込み(injection / include) で表現できます。
"jsonValue" : {
"begin" : "(:) \\ s*(?= \\ {)" ,
"beginCaptures" : { "1" : { "name" : "punctuation.separator.key-value.wprules" } },
"end" : "(?<= \\ })" ,
"patterns" : [{ "include" : "source.json" }]
}
include に別の文法のルートスコープ(ここでは source.json)を指定すると、その範囲は JSON の文法で塗られます。ただし埋め込みは複雑さを一気に上げます。まずは自前の要素だけを確実に塗り、埋め込みは「本当に必要になってから」足すのが安全です。最初から欲張らない、という判断がそのまま保守のしやすさに直結します。
落とし穴3: テーマに依存しないスコープ命名
もっとも見落とされやすいのがこれです。スコープ名は自由に付けられますが、テーマが色を用意しているのは標準的なスコープ名だけ です。my.super.custom.key のような独自名を割り当てると、文法は正しく動いていてもテーマ側に対応する色がなく、結局モノクロに戻ります。
キーには entity.name.tag、コメントには comment.line、文字列には string.quoted.double——このように意味が近い標準名に寄せる ことで、ユーザーがどのテーマを使っていても妥当な色が乗ります。独自性を出すべきは命名ではなく、どの要素にどの意味を割り当てるかという設計判断のほうです。
エージェントに下書きさせて、人が検証する
文法づくりで手が止まりやすいのは、正規表現とスコープ命名の初速です。ここは Antigravity のエージェントに、サンプルの .wprules を数行渡して「この4要素に標準スコープを割り当てる tmLanguage.json の下書きを作って」と頼むと、骨格が一気に埋まります。
ただし生成物をそのまま信じないことが肝心です。エージェントの下書きは、非標準スコープを混ぜたり、パターンの並び順を無頓着にしたりしがちです。そこで人間側はスコープ検査で実際の割り当てを目視し、標準名かどうか・並び順が正しいかを確認する という検証に徹します。生成は速さ、検証は正しさ。私自身、独自ファイルを扱うときは、この分担を個人的に推奨しております。役割を分けると、独自ファイルのハイライトは短時間で実用品質に届きます。エディタのカスタマイズをさらに広げたい場合は、Antigravity Editor 上級カスタマイズ や会話幅と構文ハイライトの見直し も合わせてご覧いただければと思います。
まとめ — 次の一歩
独自ファイルがモノクロなのは、エディタの限界ではなく言語登録がないだけでした。package.json で言語を宣言し、language-configuration.json で挙動を教え、tmLanguage.json で標準スコープを割り当てる。この三点セットで、自分の DSL にも読むための色が戻ります。
次に手を動かすなら、いま自分が一番よく開く独自ファイルをひとつ選び、その4〜5種類の要素だけにスコープを付けてみてください。全部を一度に塗ろうとせず、コメントとキーだけでも色が付けば、読み違いは目に見えて減ります。小さな文法が一枚あるだけで、毎日の視線の負荷が軽くなる——その手応えを、まずは一ファイルで確かめていただければ嬉しいです。お読みいただきありがとうございました。