バッファ内の関数やクラスの定義、文、コードブロック、文字列、コメントといった特定の要素を、バッファで使用されているTree-sitter文法で定義されたノードタイプに基づいて識別・検索できることは、しばしば有用です。Emacsでは、Lispプログラムを用いて、それぞれの「要素」にどのようなTree-sitterノードが対応するかを定義できます。これにより、次の関数へのジャンプ、ポイントにあるコードブロックのマーク、2つの関数引数の入れ替えといった便利な機能が利用可能になります。
Emacsの「things」機能は、tree-sitterのパターンマッチング機能(Pattern Matching Tree-sitter Nodesを参照)とは独立しており、相対的に機能は限定的ですが、tree-sitterのパースツリーに基づいてバッファテキストをナビゲートしたり辿ったりするのに適しています。
treesit-thing-settings で要素を定義し、treesit-thing-definition で定義済み要素の述語を取得し、treesit-thing-defined-p で要素が定義されているかを判定できます。
これは、バッファで使用されている文法がサポートする各言語の Thing
定義のリストであり、バッファのメジャーモードによって定義される必要があります(デフォルト値は nil
です)。各エントリのキーは言語シンボル(例:C の場合は c、C++ の場合は cpp など)であり、値は (thing pred) 形式の Thing 定義のリストです。ここで thing は Thing を表すシンボルであり、pred はこの
thing としてどのような種類の Tree-sitter ノードが扱われるかを指定します。
thing
を定義するために使用されるシンボルは、メジャーモードにとって意味のあるものであれば何でも構いません(defun、defclass、sentence、comment、string など)。Tree-sitter ベースのナビゲーションコマンド(バランス式上での移動 を参照)をサポートするには、モード側で list と sexp の2つを定義する必要があります。
pred
は、ノードの型に一致する正規表現文字列、ノードを引数として受け取り、そのノードが対象に該当するかどうかを示すブール値を返す関数、あるいはコンス
(regexp . fn)(正規表現 regexp と関数 fn の組み合わせ)のいずれかであり得ます。ノードが対象に該当するためには、regexp に一致し、かつ fn を満たす必要があります。
pred も再帰的に定義できる。(or pred…) となる場合があり、これは pred のいずれか一つを満たせば、そのノードが当該の対象として認められることを意味する。(and pred…)
となる場合があり、これは pred
をすべて満たせば、そのノードが当該の対象として認められることを意味する。(not pred) となる場合があり、これは pred
を満たさないことで、そのノードが認められることを意味する。
最後に、predはこのリストで定義された他のthingを参照できます。例えば、(or sexp sentence)は、alist内の他のルールで定義されたsexpのものかsentenceのもののいずれかを定義します。
あらかじめ定義された述語として named と anonymous の2つがあり、それぞれ Tree-sitter
文法の名前付きノードと匿名ノードを判定します。これらを and
と組み合わせることで、マッチ範囲を絞り込むことができます。
C と C++ の例 treesit-thing-settings を以下に示します:
((c
(defun "function_definition")
(sexp (not "[](),[{}]"))
(comment "comment")
(string "raw_string_literal")
(text (or comment string)))
(cpp
(defun ("function_definition" . cpp-ts-mode-defun-valid-p))
(defclass "class_specifier")
(comment "comment")))
なお、この例は解説目的で改変されており、Tree-sitterベースのCおよびC++モードにおける定義と完全に一致するものではない点にご留意ください。
Emacsの組み込み関数は、すでにいくつかの「thing」定義を利用しています。コマンド treesit-forward-sexp は、メジャーモードが定義していれば sexp の定義を使用します(バランス式上の移動 を参照)。treesit-forward-list、treesit-down-list、treesit-up-list、treesit-show-paren-data は list
の定義を使用します(同名の関数との曖昧さを避けるため、そのシンボル list にはシンボルプロパティ
treesit-thing-symbol が付与されています)。treesit-forward-sentence は sentence の定義を使用します。treesit-end-of-defun のようなデファン移動関数は defun の定義を使用します(defun
の定義は、後方互換性のために treesit-defun-type-regexp
によって上書きされています)。メジャーモードでは、comment、string、text
の「thing」を定義することも可能です(コメントや文字列に対応させるため)。
本節の残りの部分では、thing定義を活用した関数をいくつか紹介します。以下に挙げる関数のほか、他の箇所に記載されている一部の関数でもthing機能が利用されています。例えば、treesit-search-forwardやtreesit-induce-sparse-treeといったツリー走査関数などが挙げられます。ノードの取得を参照してください。
この関数は、node が thing を表すかどうかを判定します。
node が thing を表す場合は non-nil を、そうでない場合は nil
を返します。便宜上、node が nil
である場合、この関数は単に nil を返します。
thingは、defunのような事物シンボルであるか、あるいは"function_definition"や(or comment string)のように単に事物を定義する述語であるかのいずれかです。
デフォルトでは、thing が未定義または不正な形式である場合、この関数は treesit-invalid-predicate エラーを通知します。ignore-missing が t である場合、thing が未定義であってもこの関数はエラーを通知せず、単に nil を返します。ただし、thing
が不正な形式の述語である場合は、引き続きエラーを通知します。
以下の関数は、要素の検索やそれらの間の移動を担っており、バッファに隣接する、あるいは入れ子になった複数のパーサーが含まれる場合があるという点に対処する必要があります。デフォルトでは、これらの関数は有用となるよう、その時点で関連するすべてのパーサーを、最も具体的(深く埋め込まれた)なものから最も一般的なものに順に検索します。Lispプログラムでは、これらの関数を他の関数の構成要素として使用する際、この挙動が意図したものであるか慎重に検討すべきです。意図しない場合は、パーサーまたは言語を明示的に指定してください。
この関数は、現在のバッファ内で position より前にある、指定された thing に該当する最初のノードを返します。そのようなノードが存在しない場合は、nil を返します。
ノードが返される場合、そのノードの終端位置は position 以下であることが保証されます。言い換えれば、この関数が position を包含するノードを返すことはありません。
繰り返しになりますが、thingは記号または述語のいずれかになり得ます。
parser が nil
でない場合は、そのパーサーの構文木のみを使用します。そうでない場合は、最も具体的(深く埋め込まれた)なものから最も具体的でないものの順に、各パーサーのカバー範囲を試します。position において同じ埋め込みレベル(パーサー埋め込みレベル
を参照)を持つパーサーが複数存在する場合、どのパーサーが最初に試行されるかは未定義です。parser
が言語シンボルである場合、関数は試行するパーサーをその言語用のものに限定します。
この関数は treesit-thing-prev と同様ですが、position の後にある最初のノード、すなわち thing を返します。また、ノードが返される場合、そのノードの開始位置が position 以上であることも保証します。parser
パラメータは treesit-thing-prev と同様です。
この関数は treesit-thing-prev と treesit-thing-next
をベースにしており、ナビゲーションコマンドで有用となる機能を提供します。position から thing の arg
個をまたいで移動した後の位置を返します。またがって移動すべき要素が十分にない場合は nil を返します。この関数はポイントを移動しません。
正の arg は thing
のインスタンス分だけ前方に移動することを意味し、負の arg は後方に移動することを意味します。side が beg の場合はこの関数は thing の開始位置を返し、end の場合は thing の終了位置を返します。
treesit-thing-prevと同様に、thingはtreesit-thing-settingsで定義された事物シンボルまたは述語となり得、parserはnil、パーサー、または言語シンボルのいずれかとなり得ます。その関数と同様に、parserはどのパーサーや言語を検索するかを決定します。利用可能なパーサーが複数ある場合、この関数は成功するまでそれぞれを試します。
tactic は、この関数が要素間をどのように移動するかを決定します。値は nested、top-level、restricted、parent-first、または
nil のいずれかになります。nested
または nil
は通常のネストされたナビゲーションを意味します。まず兄弟要素間の移動を試み、現在のレベルに兄弟要素が残っていない場合は、親要素へ、次にその兄弟要素へと順に移動します。top-level は、トップレベルの要素間のみを移動し、ネストされた要素を無視することを意味します。restricted は、position
を囲む要素が存在する場合、その要素内での移動が制限されることを意味します。この手法は、現在のネストレベルで停止し、上位へ移動したくないコマンドに有用です。parent-first
は、親要素が存在する場合はそこへ移動し、親要素がない場合は兄弟要素へ移動することを意味します。
この関数は、thing であり、かつ position
を包含する最小のノードを返します。そのようなノードが存在しない場合は、nil を返します。
返されるノードは position を包含していなければなりません。すなわち、その開始位置は position 以下であり、終了位置は position 以上である必要があります。
strict が nil
でない場合、この関数は厳密な比較を行います。すなわち、開始位置は position
より厳密に小さく、終了位置は position より厳密に大きくなければなりません。
thing は、treesit-thing-settings で定義された事物シンボル、または述語のいずれかとなり得る。
parser が nil でない場合は、そのパーサーの構文木のみを使用します。そうでない場合は、最も具体的(深く埋め込まれた)なものから最も具体的でないものの順に、各パーサーのカバー範囲を試行します。position において同じ埋め込みレベル(パーサー埋め込みレベル を参照)を持つパーサーが複数存在する場合、どのパーサーが最初に試行されるかは未定義です。parser は言語シンボルである場合もあります。
便利なラッパー関数もいくつか用意されています。treesit-beginning-of-thingはポイントを要素の先頭に移動し、treesit-end-of-thingは要素の末尾に移動し、treesit-thing-at-pointはポイントにある要素を返します。
また、treesit-beginning-of-defun、treesit-end-of-defun、treesit-defun-at-pointのように、treesit-defun-type-regexpのフォールバックとしてdefunの定義を明示的に用いるdefunコマンドもあります。さらに、これらの関数はナビゲーション戦術としてtreesit-defun-tacticを使用しています。これらについては、他のセクションでより詳しく解説されています(Tree-sitterによるメジャーモードの開発を参照してください)。