Tree-sitterライブラリ(プログラムソースの解析を参照)を用いてビルドすると、Emacsはプログラムソースを解析して構文木を生成できます。この構文木は、プログラムソースのインデントコマンドの指針として利用できます。最大限の柔軟性を求めるなら、構文木を照会して各言語に応じてインデントを行うカスタムインデント関数を記述することも可能ですが、それには多大な手間がかかります。以下で説明するシンプルなインデントエンジンを使用する方が便利です。そうすれば、メジャーモードはインデントルールをいくつか記述するだけで済み、残りの処理はエンジンが担ってくれます。
パーサーベースのインデントエンジンを有効にするには、treesit-simple-indent-rules または treesit-indent-function のいずれかを設定し、次に treesit-major-mode-setup を呼び出します。(treesit-major-mode-setup が行うのは、indent-line-function の値を treesit-indent に、indent-region-function の値を treesit-indent-region に設定することだけです。)
この変数には、treesit-indent
によって呼び出される実際の関数が格納されます。デフォルト値は treesit-simple-indent
です。将来的には、より複雑な他のインデントエンジンを追加する可能性があります。
このローカル変数には、すべての言語のインデント規則が格納されています。これは (language rule…)
形式の要素のリストであり、language は言語シンボル、各 rule は (matcher anchor offset) 形式の要素からなるリストか、あるいは関数のいずれかです。
以下にリスト型の説明、続いて関数型の説明を記載します。
First, Emacs passes the smallest tree-sitter node at the beginning of the
current line to matcher; if it returns non-nil, this rule is applicable. Then Emacs passes the
node to anchor, which returns a buffer position.
Emacs takes the column number of that position, adds offset to it, and the result is the indentation column for
the current line.
matcher と anchor は関数であり、Emacs はこれらに対して便利なデフォルト値を提供しています。
各 matcher または anchor は、node、parent、bol の3つの引数を取る関数です。引数 bol
は、インデントが必要なバッファ位置、すなわち行頭以降の最初の非空白文字の位置です。引数 node
は、その位置から始まる最大のノード(かつルートノードではないもの)であり、parent は node
の親です。ただし、その位置が空白文字内または複数行文字列内にある場合は、その位置から始まるノードは存在しないため、node は nil となります。その場合、parent はその位置にまたがる最小のノードとなります。
matcher はルールが適用される場合に non-nil を、anchor はバッファ位置を返す必要があります。
offset は、整数、値が整数である変数、または整数を返す関数のいずれかです。関数の場合は、マッチャーやアンカーと同様に、node、parent、bol が渡されます。
ruleが関数である場合、ルールがマッチングルールとアンカーを併せて考慮する必要がある複雑なケースにおいて有用です。rule関数には、matcherと同じ引数(node、parent、bol)が渡されます。マッチした場合、ruleはコンス (anchor-pos . offset) を返す必要があり、ここでanchor-posはバッファ位置、offsetはインデントオフセットとなります。ruleがマッチしない場合は、nilを返す必要があります。
これは treesit-simple-indent-rules における matcher および anchor
のデフォルト値の一覧です。それぞれ、node、parent、bol
の3つの引数を取る関数を表しています。利用可能なデフォルト関数は以下の通りです:
no-node ¶このマッチャーは、node、parent、bol の3つの引数で呼び出される関数です。node が nil である場合、すなわち bol
から始まるノードが存在しない場合に、一致を示す non-nil を返します。これは、bol が空行にある場合や、複数行文字列の中にある場合などに該当します。
parent-is ¶このマッチャーは1つの引数 type を取る関数であり、node、parent、bol の3つの引数で呼び出され、parent の型が正規表現
type に一致する場合に non-nil(すなわちマッチ)を返す関数を返します。
node-is ¶このマッチャーは1つの引数 type を取る関数であり、node、parent、bol の3つの引数で呼び出され、node の型が正規表現 type に一致する場合に non-nil
を返す関数を返します。
field-is ¶このマッチャーは1つの引数 name を取る関数であり、node、parent、bol の3つの引数で呼び出され、parent 内の node のフィールド名が正規表現 name に一致する場合に
non-nil を返す関数を返します。
query ¶このマッチャーは1つの引数 query を取る関数であり、node、parent、bol の3つの引数で呼び出され、query を用いて parent をクエリした際に node がキャプチャされる場合に
non-nil を返す関数を返します(パターンマッチング Tree-sitter ノード を参照)。
match ¶このマッチャーは、5つの引数 node-type、parent-type、node-field、node-index-min、node-index-max
を取る関数です。これは、3つの引数 node、parent、bol で呼び出され、node の型が正規表現 node-type に一致し、parent の型が正規表現 parent-type
に一致し、node の parent
におけるフィールド名が正規表現 node-field に一致し、かつ node の兄弟要素内でのインデックスが node-index-min
から node-index-max の間にある場合に、non-nil を返す関数を返します。引数の値が nil
である場合、このマッチャーはその引数をチェックしません。例えば、親要素が argument_list
である場合の最初の子要素にマッチさせるには、次のようにします。
(match nil "argument_list" nil 0 0)
さらに、node-type は特殊値 null
となる場合があり、これは node の値が nil
のときに一致します。
n-p-gp ¶「node-parent-grandparent」の略であるこのマッチャーは、node-type、parent-type、grandparent-type
の3つの引数を取る関数です。これは、node、parent、bol の3つの引数で呼び出され、(1) node-type が node の型に一致し、(2) parent-type が parent の型に一致し、(3)
grandparent-type が parent
の親の型に一致する場合に、nil 以外の値を返す関数を返します。node-type、parent-type、grandparent-type のいずれかが nil
である場合、この関数はそれを判定しません。
comment-end ¶このマッチャーは、node、parent、bol の3つの引数で呼び出され、ポイントがコメント終了トークンより前にある場合に non-nil を返す関数です。コメント終了トークンは、正規表現 comment-end-skip によって定義されます。
catch-all ¶このマッチャーは、node、parent、bol の3つの引数で呼び出される関数です。常に non-nil
を返し、マッチしたことを示します。
first-sibling ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、parent の最初の子要素の開始位置を返す関数です。
nth-sibling ¶このアンカーは、2つの引数 n と、オプションの引数 named を引数とする関数です。これは、node、parent、bol の3つの引数で呼び出され、parent の n
番目の子要素の開始位置を返す関数を返します。named が nil 以外の場合、名前付きの子要素のみがカウントされます(名前付きノード を参照)。
parent ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、parent の開始位置を返す関数です。
grand-parent ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、parent の親の開始位置を返す関数です。
great-grand-parent ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、parent の親の親の開始位置を返す関数です。
parent-bol ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、parent の開始位置がある行における最初の非スペース文字を返す関数です。
standalone-parent ¶このアンカーは、node、parent、bol の3つの引数で呼び出される関数です。node
の最初の祖先ノード(親、祖父母など)のうち、自身の行から始まっているものを探し、そのノードの開始位置を返します。「自身の行から始まっている」とは、そのノードの開始位置がある行において、ノードの前に空白文字しか存在しないことを意味します。「自身の行から始まっている」の厳密な定義は、treesit-simple-indent-standalone-predicate
を設定することで緩和でき、メジャーモードによっては、メソッドチェーン時のインデントを容易にするためにそうしたい場合があります。
prev-sibling ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、node の前の兄弟要素の開始位置を返す関数です。
no-indent ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、node の開始位置を返す関数です。
prev-line ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、直前の行の最初の非空白文字を返す関数です。
column-0 ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、現在の行の先頭(0列目)を返す関数です。
comment-start ¶このアンカーは、node、parent、bol の3つの引数で呼び出され、コメント開始トークンの後の位置を返す関数です。コメント開始トークンは、正規表現
comment-start-skip で定義されています。この関数は、parent がコメントノードであることを前提としています。
prev-adaptive-prefix ¶このアンカーは、node、parent、bol の3つの引数で呼び出される関数です。adaptive-fill-regexp
を、直前の空でない行の先頭にあるテキストに照合しようとします。一致した場合はその一致箇所の末尾を返し、一致しなかった場合は nil を返します。ただし、現在の行が接頭辞(例:‘-’)で始まる場合は、2つの接頭辞が揃うように、代わりに直前の行の接頭辞の先頭を返します。このアンカーは、ブロックコメントにおける
indent-relative のようなインデント動作を実現する際に有用です。
パーサーベースのインデント規則を記述する際に役立つユーティリティ関数をいくつか紹介します。
このコマンドは、現在のバッファのインデントをメジャーモード mode と照合します。mode に従って現在のバッファをインデントし、その結果を現在のインデントと比較します。その後、差分を表示するバッファをポップアップで表示します。正しいインデント(ターゲット)は緑色で、現在のインデントは赤色で表示されます。
インデント規則を記述する際には、treesit-inspect-mode(Tree-sitter Language Grammar
を参照)を使用すると便利です。