38.6 用户自定义 “对象(Things)” 与导航

在缓冲区中识别并查找特定的 对象(things) 通常非常实用,例如函数与类定义、语句、代码块、字符串、注释等。Emacs 允许用户定义哪种 tree-sitter 节点对应某一 “对象(things)”。这一功能支持许多便捷操作,比如跳转到下一个函数、选中光标处的代码块,或是交换两个函数参数。

Emacs 中的 “对象(things)” 功能独立于 tree-sitter 的模式匹配功能,虽然功能相对简洁,但更适用于在解析树中导航与遍历。

你可以使用 treesit-thing-settings 定义对象,使用 treesit-thing-definition 获取已定义对象的判定规则,使用 treesit-thing-defined-p 检查某一对象是否已定义。

Variable: treesit-thing-settings

这是一个 alist,用于为每种语言存储对象定义。每个条目的键是语言符号,值是对象定义列表,格式为 (thing pred),其中 thing 是代表该对象的符号,如 defunsexpsentencepred 用于指定哪种 tree-sitter 节点属于该对象。

pred 可以是匹配节点类型的正则表达式字符串;可以是一个以节点为参数、返回布尔值的函数,用于判断节点是否为该对象;也可以是 cons 对 (regexp . fn),同时结合正则表达式与函数—节点必须同时匹配正则表达式且满足函数条件,才会被视为该对象。

pred 还支持递归定义。可以写作 (or pred…),表示满足任意一个条件即为该对象;也可以写作 (not pred),表示不满足该条件即为该对象。

最后,pred 可以引用本列表中定义的其他对象。例如,(or sexp sentence) 定义的对象既是 alist 中其他规则定义的 sexp 对象,也是 sentence 对象。

以下是适用于 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")))

注意该示例经过简化用于教学,与实际 C 和 C++ 主模式中的对象定义不完全一致。

Emacs 内置函数已经在使用部分对象定义。命令 treesit-forward-sexp 在主模式定义了 sexp 时会使用该定义;treesit-forward-sentence 使用 sentence 定义。treesit-end-of-defun 等函数式移动函数使用 defun 定义(为兼容旧版,defun 定义会被 treesit-defun-type-regexp 覆盖)。主模式还可以定义 commentstringtext(通常包含注释与字符串)。

本节后续列出一些使用对象定义的函数。除下方函数外,其他部分函数也使用了对象功能,例如 treesit-search-forwardtreesit-induce-sparse-tree 等遍历树的函数。See 获取节点

Function: treesit-node-match-p node thing &optional ignore-missing

该函数检查 node 是否为 thing 对象。

如果是则返回非 nil,否则返回 nil。为方便使用,若 nodenil,函数直接返回 nil

thing 可以是对象符号(如 defun),也可以是直接定义对象的判定规则,如 "function_definition"(or comment string)

默认情况下,如果 thing 未定义或格式错误,函数会抛出 treesit-invalid-predicate 错误。如果 ignore-missingt,则对象未定义时不报错,直接返回 nil;但对象格式错误时仍会报错。

Function: treesit-thing-prev position thing

该函数返回 position 之前第一个属于指定 thing 的节点。若无此节点则返回 nil。函数保证,若返回节点,其结束位置一定小于等于 position。也就是说,该函数绝不会返回包含 position 的节点。

同样,thing 可以是符号或判定规则。

Function: treesit-thing-next position thing

该函数与 treesit-thing-prev 类似,仅返回 position 之后第一个属于该对象的节点。同样保证,若返回节点,其起始位置一定大于等于 position

Function: treesit-navigate-thing position arg side thing &optional tactic

该函数基于 treesit-thing-prevtreesit-thing-next 实现,提供导航命令所需的常用功能。它从 position 出发,移动 argthing 对象后返回目标位置。若可导航的对象数量不足,则返回 nil。函数不会移动光标。

arg 为正数表示向前移动对应个数的对象 thing;负数表示向后移动。若 sidebeg,则停在对象起始位置;若为 end,则停在对象结束位置。

treesit-thing-prev 相同,thing 可以是 treesit-thing-settings 中定义的符号,也可以是判定规则。

tactic 决定对象间的移动策略,可选值为 nestedtop-levelrestrictednilnestednil 表示普通嵌套导航:优先在同级兄弟节点间移动;若同级无更多节点,则向上移动到父节点,再遍历兄弟节点,依此类推。 top-level 表示仅在顶层对象间导航,忽略嵌套对象。 restricted 表示移动范围限制在包含 position 的对象内部(如果存在)。该策略适用于需要停留在当前嵌套层级、不向上跳转的命令。

Function: treesit-thing-at position thing &optional strict

该函数返回包含 position、且属于 thing 的最小节点;若无此节点则返回 nil

返回节点必须包含 position,即其起始位置 ≤ position,结束位置 ≥ position

strict 为非 nil,则使用严格比较,即起始位置必须严格小于 position,结束位置必须严格大于 position

thing 可以是 treesit-thing-settings 中定义的符号,也可以是判定规则。

此外还有一些便捷包装函数。 treesit-beginning-of-thing 将光标移至对象开头,treesit-end-of-thing 移至对象结尾,treesit-thing-at-point 返回光标处的对象。

还有专门使用 defun 定义的函数式命令(作为 treesit-defun-type-regexp 的备选),如 treesit-beginning-of-defuntreesit-end-of-defuntreesit-defun-at-point。此外,这些函数使用 treesit-defun-tactic 作为导航策略。相关详细说明见其他章节(see 使用 tree-sitter 开发主模式)。