補完定義編 の続き(のつもり)です。この記事の内容は、Sublime Text ドキュメントの Syntax Definitionsを元ネタとしています。
事前準備
AAAPackageDev をインストールします。
構文定義ファイル自体は Plist という XML もどきの形式ですが、長ったらしくて書くのが面倒なため、JSON で書きます。AAAPackageDev は JSON で書いた構文定義を Plist に変換してくれるツールです。
JSON-tmLanguage の作成
メニューから Tools > Packages > Package Development> New Syntax Definition とアクセスして、ファイルを作成します。エディタにこのように表示されているはずです。
{ "name": "Syntax Name",
"scopeName": "source.syntax_name",
"fileTypes": [""],
"patterns": [
],
"uuid": "9d286105-1dd9-464e-b197-3c24689d804e"
}
name エディタの右下に表示される名前です。このファイルを開いているときは、右下には Sublime Text Syntax Definition と表示されているはずです。この部分にここで指定した名前が入ります。
scopeName スコープの名称です。ソースファイルの場合は source. 、マークアップの場合は text. の後に、言語の小文字の短縮名をつけた文字列とします。補完定義編で指定したものと同じものを使います。
uuid 構文を識別する一意名です。このファイルを生成するたびに変わります。手で書き換えることはありません。
fileTypes この構文を適用するファイルの拡張子を指定します。補完定義編で指定したものと同じものを使います。
patterns 後述します。
tmLanguage への変換
- Tools > Build System で Json to tmLanguage を選択します。(メニューに表示されていない場合は、Sublime を再起動します)
-
F7
キーを押します。 - .JSON-tmLanguage ファイルと同じフォルダに*.tmLanguage* が作成されます。つまり、補完定義編で作成した .tmLanguage が上書きされます。
ここで fileTypes で指定した拡張子で何かファイルを作って、補完定義編で定義したキーワードをタイプしてみます。無事に補完されていたら大丈夫です。
構文定義の意味
構文定義により、ファイルの中身がスコープに区切られます。スコープは次の用途で使われます。
- 構文の色づけ
- マクロやスニペットで、対象範囲を特定する
現在は、ファイル全体が scopeName で指定されたスコープです。対象のファイルを開き、エディタの適当な場所をクリックして、command + alt+ p
(または ctrl+ alt + shift+ p
) を押してみると、source.構文名 と表示されるはずです。今から、構文パターンによって違う名前のスコープができるように pattern に追加していきます。
超単純なマッチパターン
基本形はこれ。
"patterns": [
{ "match": "\\$\\d+",
"name": "keyword.source.syntax_name",
"comment": "$1, $2... みたいな感じ"
}
],
この例では、"\$\d+" パターンにあてはまる部分の文字列が keyword.source.syntax_name スコープとなります。
match 構文パターンの正規表現。JSON 向けにエスケープして書きます。
name この構文パターンを識別する名前。
comment 構文パターンの説明。
マッチンググループを使ったマッチパターン
もうちょっと複雑にして、パターンにマッチンググループを入れてみます。カッコでくくると、マッチンググループとなります。
"patterns": [
{ "match": "\\$([A-Za-z][A-Za-z0-9_]+)",
"name": "keyword.syntax_name",
"captures": {
"1": { "name": "constant.numeric.syntax_name" }
},
"comment": "$PARAM1, $TM_SELECTION... みたいな感じ"
}
],
この例では、例えば $PARAM1 の場合、$PARAM1 全体が keyword.syntax_name スコープとなり、さらに PARAM1 の部分に constant.numeric.syntax_name スコープが追加されます。
captures マッチンググループごとのスコープ定義。巷の言語の正規表現と同じく、1から始まります。match パターンにマッチンググループがあっても、ここでスコープを定義してなければ、name で定義したスコープだけが適用されます。
開始要素・終了要素があるマッチパターン
基本的に
{ "name": "",
"begin": "",
"beginCaptures": {
"0": { "name": "" }
},
"end": "",
"endCaptures": {
"0": { "name": "" }
},
"patterns": [
{ "name": "",
"match": ""
}
],
"contentName": ""
}
name は同じです。
begin、end には構文の開始終了パターン(開始終了タグのようなもの)を定義します。
マッチンググループを作った場合、それぞれ、beginCaptures、endCaptures でグループごとのスコープを定義できます。
patterns は開始終了パターン以外の部分のマッチパターンです。後述。
contentName には patterns の部分に適用されるスコープの名前を定義します。(元ネタは開始終了パターンを含むと書いてありますが、間違いでは?)
例えばこのパターンで ${12:${34:TEST}} を解析すると、次のようになります。
"patterns": [
{ "name": "variable.complex.syntax_name",
"begin": "(\\$)(\\{)([0-9]+):",
"beginCaptures": {
"1": { "name": "keyword.syntax_name" },
"3": { "name": "constant.numeric.syntax_name" }
},
"patterns": [
{ "include": "$self" },
{ "name": "string.syntax_name",
"match": "."
}
],
"end": "\\}",
"comment": "${12:${34:TEST}} … みたいな感じ"
},
],
- 全体のスコープとして variable.complex.syntax_name が割り当てられます。
- ${12: と、${34:TEST} と、} に3分割されます。
- beginCaptures により、$ の部分には keyword.syntax_name スコープが割り当てられ、12 の部分には constant.numeric.syntax_name が割り当てられます。
- ${34:TEST} の解析が始まりますが、その前に
"include": "$self"
により、このパターンに再帰的に当てはめられます。つまり、2. の手順で3分割され、3. の手順で $ と 34 にそれぞれスコープが割り当てられます。 - 残った文字列 TEST に、```"include": "$self"`` でまたマッチング処理が行われますが、今回はマッチしません。このため、次の match のマッチング処理が行われ、(必ずヒットするので)この文字列に name で定義された string.syntax_name が割り当てられます。
マッチの順番
patterns に書かれている中で、上から順番にマッチされていくため、既にマッチされた部分の文字列は除外されていきます。このため、次のような書き方で、エスケープしていない記号を検出できます。
"patterns": [
{ "name": "constant.character.escape.syntax_name",
"match": "\\\\(\\$|\\>|\\<)"
},
{ "name": "invalid.ssraw",
"match": "(\\$|\\>|\\<)"
}
],
これまでの説明で一通りの構文パターンが書けます。TextMate のマニュアルを見ると、更に省力化して書く方法も載ってはいますが。
で、どうやって色がつくの?
この答えとなるようなものは元ネタに載っていないため、TextMate のリファレンスを参考にします。
スコープの名前の先頭が次の文字で始まっていれば、Sublime の機能によって自動的にそれなりの色がつきます。
- comment. コメント
- constant. 定数
- entity. クラスとか、ファンクションとか、タグとかの、ちょっと大きめの単位
- invalid. けしからんもの(どす黒い赤のマーカーがつく)
- keyword. キーワード
- markup. マークアップ文書のテキスト
- meta. 関数定義など (色がつくことはほとんどないので、単純に操作範囲を狭めたいときに利用)
- storage. 型とか修飾子とか
- string. 文字列
- support. (言語機能でなく)フレームワークで提供されるもの
- variable. 変数名
さらに細かく分かれているので、全部の説明はTextMate のリファレンスを参照してください。