Posted at

Sublime Text2 言語パッケージ作成:構文定義

More than 5 years have passed since last update.

補完定義編 の続き(のつもり)です。この記事の内容は、Sublime Text ドキュメントの Syntax Definitionsを元ネタとしています。


事前準備

AAAPackageDev をインストールします。

構文定義ファイル自体は Plist という XML もどきの形式ですが、長ったらしくて書くのが面倒なため、JSON で書きます。AAAPackageDev は JSON で書いた構文定義を Plist に変換してくれるツールです。


JSON-tmLanguage の作成

メニューから Tools > Packages > Package Development> New Syntax Definition とアクセスして、ファイルを作成します。エディタにこのように表示されているはずです。


***.JSON-tmLanguage

{ "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 への変換


  1. Tools > Build System で Json to tmLanguage を選択します。(メニューに表示されていない場合は、Sublime を再起動します)


  2. F7 キーを押します。


  3. .JSON-tmLanguage ファイルと同じフォルダに.tmLanguage が作成されます。つまり、補完定義編で作成した .tmLanguage が上書きされます。

ここで fileTypes で指定した拡張子で何かファイルを作って、補完定義編で定義したキーワードをタイプしてみます。無事に補完されていたら大丈夫です。


構文定義の意味

構文定義により、ファイルの中身がスコープに区切られます。スコープは次の用途で使われます。


  • 構文の色づけ

  • マクロやスニペットで、対象範囲を特定する

現在は、ファイル全体が scopeName で指定されたスコープです。対象のファイルを開き、エディタの適当な場所をクリックして、command + alt+ p (または ctrl+ shift+ p) を押してみると、source.構文名 と表示されるはずです。今から、構文パターンによって違う名前のスコープができるように pattern に追加していきます。


超単純なマッチパターン

基本形はこれ。


***.JSON-tmLanguage

  "patterns": [

{ "match": "\\$\\d+",
"name": "keyword.source.syntax_name",
"comment": "$1, $2... みたいな感じ"
}
],

この例では、"\$\d+" パターンにあてはまる部分の文字列が keyword.source.syntax_name スコープとなります。

match 構文パターンの正規表現。JSON 向けにエスケープして書きます。

name この構文パターンを識別する名前。

comment 構文パターンの説明。


マッチンググループを使ったマッチパターン

もうちょっと複雑にして、パターンにマッチンググループを入れてみます。カッコでくくると、マッチンググループとなります。


***.JSON-tmLanguage

  "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 は同じです。

beginend には構文の開始終了パターン(開始終了タグのようなもの)を定義します。

マッチンググループを作った場合、それぞれ、beginCapturesendCaptures でグループごとのスコープを定義できます。

patterns は開始終了パターン以外の部分のマッチパターンです。後述。

contentName には patterns の部分に適用されるスコープの名前を定義します。(元ネタは開始終了パターンを含むと書いてありますが、間違いでは?)

例えばこのパターンで ${12:${34:TEST}} を解析すると、次のようになります。


***.JSON-tmLanguage

   "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}} … みたいな感じ"
},
],


  1. 全体のスコープとして variable.complex.syntax_name が割り当てられます。

  2. ${12: と、${34:TEST} と、} に3分割されます。

  3. beginCaptures により、$ の部分には keyword.syntax_name スコープが割り当てられ、12 の部分には constant.numeric.syntax_name が割り当てられます。

  4. ${34:TEST} の解析が始まりますが、その前に "include": "$self" により、このパターンに再帰的に当てはめられます。つまり、2. の手順で3分割され、3. の手順で $ と 34 にそれぞれスコープが割り当てられます。

  5. 残った文字列 TEST に、`"include": "$self" でまたマッチング処理が行われますが、今回はマッチしません。このため、次の match のマッチング処理が行われ、(必ずヒットするので)この文字列に name で定義された string.syntax_name が割り当てられます。


マッチの順番

patterns に書かれている中で、上から順番にマッチされていくため、既にマッチされた部分の文字列は除外されていきます。このため、次のような書き方で、エスケープしていない記号を検出できます。


***.JSON-tmLanguage

   "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 のリファレンスを参照してください。