当 Emacs 基于 tree-sitter 库编译时(see Parsing Program Source), 能够解析程序源代码并生成语法树。 该语法树可用于指导程序源码的缩进命令。 为获得最大灵活性,可以为每种语言编写自定义缩进函数, 查询语法树并完成缩进,但这工作量较大。 更简便的方式是使用下文介绍的简易缩进引擎: 主模式只需编写若干缩进规则,其余工作由引擎完成。
要启用基于解析器的缩进引擎,
需设置 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 . rules),其中 language 为语言符号,rules 为格式为 (matcher anchor offset) 的元素列表。
首先,Emacs 将当前行开头最小的 tree-sitter 节点传递给 matcher;若其返回非 nil,则该规则生效。随后 Emacs 将该节点传递给 anchor,由其返回一个缓冲区位置。Emacs 取该位置的列号,加上 offset,结果即为当前行的缩进列数。
matcher 与 anchor 均为函数,Emacs 为其提供了便捷的默认实现。
每个 matcher 或 anchor 均为接收三个参数的函数:node、parent 与 bol。参数 bol 为需要缩进的缓冲区位置,即行首之后首个非空白字符的位置。参数 node 为起始于该位置的最大节点(非根节点);parent 为 node 的父节点。但若该位置处于空白区域或多行字符串内部,则无节点起始于此,此时 node 为 nil,parent 则为覆盖该位置的最小节点。
matcher 在规则生效时应返回非 nil,anchor 则应返回一个缓冲区位置。
offset 可以是整数、值为整数的变量,或返回整数的函数。若为函数,则与匹配器、定位器一样,接收 node、parent 与 bol 三个参数。
该变量为 treesit-simple-indent-rules 中 matcher 与 anchor 的默认函数列表。每个预设均代表一个接收三个参数的函数:node、parent 与 bol。可用的默认函数如下:
no-node ¶该匹配器函数接收三个参数:node、parent 与 bol。当 node 为 nil(即无节点起始于 bol)时返回非 nil,表示匹配成功。该情况常见于 bol 位于空行或多行字符串内部等场景。
parent-is ¶该匹配器接收一个参数 type,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,当 parent 的类型匹配正则表达式 type 时返回非 nil。
node-is ¶该匹配器接收一个参数 type,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,当 node 的类型匹配正则表达式 type 时返回非 nil。
field-is ¶该匹配器接收一个参数 name,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,当 node 在 parent 中的字段名匹配正则表达式 name 时返回非 nil。
query ¶该匹配器接收一个参数 query,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,当使用 query 查询 parent 能够捕获 node 时返回非 nil(see Pattern Matching Tree-sitter Nodes)。
match ¶该匹配器接收五个参数:node-type、parent-type、node-field、node-index-min 与 node-index-max,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,并在以下条件均满足时返回非 nil:node 类型匹配 node-type、parent 类型匹配 parent-type、node 在 parent 中的字段名匹配 node-field,且 node 在兄弟节点中的索引介于 node-index-min 与 node-index-max 之间。若某参数为 nil,则跳过该项检查。例如,匹配父节点为 argument_list 的首个子节点可使用:
(match nil "argument_list" nil 0 0)
此外,node-type 可使用特殊值 null,用于匹配 node 为 nil 的情况。
n-p-gp ¶为“节点-父节点-祖父节点”的缩写,该匹配器接收三个参数:node-type、parent-type 与 grandparent-type,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,并在以下条件均满足时返回非 nil:(1) node-type 匹配 node 类型;(2) parent-type 匹配 parent 类型;(3) grandparent-type 匹配 parent 的父节点类型。若任一类型参数为 nil,则跳过该项检查。
comment-end ¶该匹配器函数接收三个参数:node、parent 与 bol,当光标位于注释结束标记之前时返回非 nil。注释结束标记由正则表达式 comment-end-skip 定义。
catch-all ¶该匹配器函数接收三个参数:node、parent 与 bol,始终返回非 nil,表示匹配成功。
first-sibling ¶该定位器函数接收三个参数:node、parent 与 bol,返回 parent 首个子节点的起始位置。
nth-sibling ¶该定位器接收两个参数:n 与可选参数 named,返回一个函数;该返回函数接收 node、parent 与 bol 三个参数,返回 parent 的第 n 个子节点的起始位置。若 named 非 nil,则仅统计具名子节点(see named node)。
parent ¶该定位器函数接收三个参数:node、parent 与 bol,返回 parent 的起始位置。
grand-parent ¶该定位器函数接收三个参数:node、parent 与 bol,返回 parent 的父节点起始位置。
great-grand-parent ¶该定位器函数接收三个参数:node、parent 与 bol,返回 parent 的父节点的父节点起始位置。
parent-bol ¶该定位器函数接收三个参数:node、parent 与 bol,返回 parent 起始所在行的首个非空白字符位置。
standalone-parent ¶该定位器函数接收三个参数:node、parent 与 bol。它查找 node 首个独占一行的祖先节点(父、祖父等),并返回该节点的起始位置。“独占一行”指节点起始所在行中,节点之前仅有空白字符。
prev-sibling ¶该定位器函数接收三个参数:node、parent 与 bol,返回 node 前一个兄弟节点的起始位置。
no-indent ¶该定位器函数接收三个参数:node、parent 与 bol,返回 node 的起始位置。
prev-line ¶该定位器函数接收三个参数:node、parent 与 bol,返回上一行的首个非空白字符位置。
column-0 ¶该定位器函数接收三个参数:node、parent 与 bol,返回当前行首(第 0 列)位置。
comment-start ¶该定位器函数接收三个参数:node、parent 与 bol,返回注释起始标记之后的位置。注释起始标记由正则表达式 comment-start-skip 定义。该函数假定 parent 为注释节点。
prev-adaptive-prefix ¶该定位器函数接收三个参数:node、parent 与 bol。它尝试将 adaptive-fill-regexp 与上一个非空行开头的文本匹配。若匹配成功,则返回匹配结束位置,否则返回 nil。但若当前行以某种前缀(如 ‘-’)开头,则返回上一行前缀的起始位置,使两行前缀对齐。该定位器适用于实现块注释类 indent-relative 的缩进行为。
以下为若干可辅助编写基于解析器缩进规则的工具函数。
该命令按照主模式 mode 检查当前缓冲区的缩进。它会依据 mode 对缓冲区进行缩进,并与当前缩进结果对比,随后弹出缓冲区展示差异。正确缩进(目标值)以绿色显示,当前缩进以红色显示。
编写缩进规则时,使用 treesit-inspect-mode 同样很有帮助(see Tree-sitter Language Grammar)。