24.8.2 基于解析器的缩进

当 Emacs 基于 tree-sitter 库编译时(see Parsing Program Source), 能够解析程序源代码并生成语法树。 该语法树可用于指导程序源码的缩进命令。 为获得最大灵活性,可以为每种语言编写自定义缩进函数, 查询语法树并完成缩进,但这工作量较大。 更简便的方式是使用下文介绍的简易缩进引擎: 主模式只需编写若干缩进规则,其余工作由引擎完成。

要启用基于解析器的缩进引擎, 需设置 treesit-simple-indent-rulestreesit-indent-function, 然后调用 treesit-major-mode-setup。 (treesit-major-mode-setup 的作用就是将 indent-line-function 设为 treesit-indent,并将 indent-region-function 设为 treesit-indent-region。)

Variable: treesit-indent-function

该变量存储 treesit-indent 实际调用的函数。 默认值为 treesit-simple-indent。 未来可能会加入其他更复杂的缩进引擎。

缩进规则编写

Variable: treesit-simple-indent-rules

该局部变量存储每种语言的缩进规则。它是一个关联列表,元素格式为 (language . rules),其中 language 为语言符号,rules 为格式为 (matcher anchor offset) 的元素列表。

首先,Emacs 将当前行开头最小的 tree-sitter 节点传递给 matcher;若其返回非 nil,则该规则生效。随后 Emacs 将该节点传递给 anchor,由其返回一个缓冲区位置。Emacs 取该位置的列号,加上 offset,结果即为当前行的缩进列数。

matcheranchor 均为函数,Emacs 为其提供了便捷的默认实现。

每个 matcheranchor 均为接收三个参数的函数:nodeparentbol。参数 bol 为需要缩进的缓冲区位置,即行首之后首个非空白字符的位置。参数 node 为起始于该位置的最大节点(非根节点);parentnode 的父节点。但若该位置处于空白区域或多行字符串内部,则无节点起始于此,此时 nodenilparent 则为覆盖该位置的最小节点。

matcher 在规则生效时应返回非 nilanchor 则应返回一个缓冲区位置。

offset 可以是整数、值为整数的变量,或返回整数的函数。若为函数,则与匹配器、定位器一样,接收 nodeparentbol 三个参数。

Variable: treesit-simple-indent-presets

该变量为 treesit-simple-indent-rulesmatcheranchor 的默认函数列表。每个预设均代表一个接收三个参数的函数:nodeparentbol。可用的默认函数如下:

no-node

该匹配器函数接收三个参数:nodeparentbol。当 nodenil(即无节点起始于 bol)时返回非 nil,表示匹配成功。该情况常见于 bol 位于空行或多行字符串内部等场景。

parent-is

该匹配器接收一个参数 type,返回一个函数;该返回函数接收 nodeparentbol 三个参数,当 parent 的类型匹配正则表达式 type 时返回非 nil

node-is

该匹配器接收一个参数 type,返回一个函数;该返回函数接收 nodeparentbol 三个参数,当 node 的类型匹配正则表达式 type 时返回非 nil

field-is

该匹配器接收一个参数 name,返回一个函数;该返回函数接收 nodeparentbol 三个参数,当 nodeparent 中的字段名匹配正则表达式 name 时返回非 nil

query

该匹配器接收一个参数 query,返回一个函数;该返回函数接收 nodeparentbol 三个参数,当使用 query 查询 parent 能够捕获 node 时返回非 nil(see Pattern Matching Tree-sitter Nodes)。

match

该匹配器接收五个参数:node-typeparent-typenode-fieldnode-index-minnode-index-max,返回一个函数;该返回函数接收 nodeparentbol 三个参数,并在以下条件均满足时返回非 nilnode 类型匹配 node-typeparent 类型匹配 parent-typenodeparent 中的字段名匹配 node-field,且 node 在兄弟节点中的索引介于 node-index-minnode-index-max 之间。若某参数为 nil,则跳过该项检查。例如,匹配父节点为 argument_list 的首个子节点可使用:

(match nil "argument_list" nil 0 0)

此外,node-type 可使用特殊值 null,用于匹配 nodenil 的情况。

n-p-gp

为“节点-父节点-祖父节点”的缩写,该匹配器接收三个参数:node-typeparent-typegrandparent-type,返回一个函数;该返回函数接收 nodeparentbol 三个参数,并在以下条件均满足时返回非 nil:(1) node-type 匹配 node 类型;(2) parent-type 匹配 parent 类型;(3) grandparent-type 匹配 parent 的父节点类型。若任一类型参数为 nil,则跳过该项检查。

comment-end

该匹配器函数接收三个参数:nodeparentbol,当光标位于注释结束标记之前时返回非 nil。注释结束标记由正则表达式 comment-end-skip 定义。

catch-all

该匹配器函数接收三个参数:nodeparentbol,始终返回非 nil,表示匹配成功。

first-sibling

该定位器函数接收三个参数:nodeparentbol,返回 parent 首个子节点的起始位置。

nth-sibling

该定位器接收两个参数:n 与可选参数 named,返回一个函数;该返回函数接收 nodeparentbol 三个参数,返回 parent 的第 n 个子节点的起始位置。若 namednil,则仅统计具名子节点(see named node)。

parent

该定位器函数接收三个参数:nodeparentbol,返回 parent 的起始位置。

grand-parent

该定位器函数接收三个参数:nodeparentbol,返回 parent 的父节点起始位置。

great-grand-parent

该定位器函数接收三个参数:nodeparentbol,返回 parent 的父节点的父节点起始位置。

parent-bol

该定位器函数接收三个参数:nodeparentbol,返回 parent 起始所在行的首个非空白字符位置。

standalone-parent

该定位器函数接收三个参数:nodeparentbol。它查找 node 首个独占一行的祖先节点(父、祖父等),并返回该节点的起始位置。“独占一行”指节点起始所在行中,节点之前仅有空白字符。

prev-sibling

该定位器函数接收三个参数:nodeparentbol,返回 node 前一个兄弟节点的起始位置。

no-indent

该定位器函数接收三个参数:nodeparentbol,返回 node 的起始位置。

prev-line

该定位器函数接收三个参数:nodeparentbol,返回上一行的首个非空白字符位置。

column-0

该定位器函数接收三个参数:nodeparentbol,返回当前行首(第 0 列)位置。

comment-start

该定位器函数接收三个参数:nodeparentbol,返回注释起始标记之后的位置。注释起始标记由正则表达式 comment-start-skip 定义。该函数假定 parent 为注释节点。

prev-adaptive-prefix

该定位器函数接收三个参数:nodeparentbol。它尝试将 adaptive-fill-regexp 与上一个非空行开头的文本匹配。若匹配成功,则返回匹配结束位置,否则返回 nil。但若当前行以某种前缀(如 ‘-’)开头,则返回上一行前缀的起始位置,使两行前缀对齐。该定位器适用于实现块注释类 indent-relative 的缩进行为。

缩进工具函数

以下为若干可辅助编写基于解析器缩进规则的工具函数。

Command: treesit-check-indent mode

该命令按照主模式 mode 检查当前缓冲区的缩进。它会依据 mode 对缓冲区进行缩进,并与当前缩进结果对比,随后弹出缓冲区展示差异。正确缩进(目标值)以绿色显示,当前缩进以红色显示。

编写缩进规则时,使用 treesit-inspect-mode 同样很有帮助(see Tree-sitter Language Grammar)。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike