如何用 :tree-sitter-subtree 查看语法树来编写 Helix 查询
【免费下载链接】helixA post-modern modal text editor.项目地址: https://gitcode.com/GitHub_Trending/he/helix
编写 Helix 的 tree-sitter 查询文件(highlights.scm、rainbows.scm、textobjects.scm等)时,最难的部分往往不是查询语法本身,而是弄清楚当前文档被解析成了哪些节点、节点叫什么名字。:tree-sitter-subtree命令就是为这件事准备的:它显示跨越主选择区(primary selection)的最小子树,主要用于调试查询。本文基于 rainbows.scm 查询指南、新增语言指南 和 typable 命令参考,说明如何用这个命令从查看语法树走到写出可验证的查询。
前提条件:当前文件类型必须已经加载了 tree-sitter grammar。Helix 的按键映射文档明确标注了带 (TS) 标记的功能都要求该文件类型有 grammar 可用;没有 grammar 时无法解析出语法树,也就没有子树可看。如果你是在runtime目录里开发查询、而 Helix 找不到它们,需要把环境变量HELIX_RUNTIME指向你正在开发的runtime目录。
查看主选择区下的语法树
- 打开一个有 grammar 的文件,把光标(或选区)放在你想捕获的语法结构上。
- 在命令模式输入
:tree-sitter-subtree,也可以用它缩短的别名:ts-subtree。 - 命令会弹出显示最小的、能完整覆盖主选择区的子树,输出为 tsq 的 S 表达式格式,例如(以下来自官方文档示例):
(call function: (identifier) ; func arguments: (arguments ; (arg1, arg2, arg3) (identifier) ; arg1 (identifier) ; arg2 (identifier))) ; arg3选区决定了你看到的树的粒度:选区越窄,命令返回的子树就越小、越贴近叶子节点;选区越宽,看到的节点层级越高。所以实际使用时是反复调整的——先选中一个较大的结构确认节点名,再把选区缩小到具体要捕获的 token 上核对匿名节点。
读懂节点:命名节点与字面量
查询只能匹配 S 表达式里实际出现的写法,这是读树时最重要的规则。官方指南在讲 rainbow 查询时给出了说明:圆括号里带引号的文本捕获的是字面量节点(grammar 里以字符串形式写的节点,如"("、")"、"["、"]"),而不带引号的括号(如(identifier))对应 grammar 中规则的命名节点,查询里就用这些规则名来捕获。
以 rainbow bracket 为例,指南为 TSQ 语言写的括号捕获是:
["(" ")" "[" "]"] @rainbow.bracket而层级提升的 scope 捕获用的是命名节点:
[ (group) (named_node) (wildcard_node) (predicate) (alternation) ] @rainbow.scope选哪些节点作为@rainbow.scope,正是靠查看语法树确认的:指南建议对照 grammar 仓库里的grammar.js,找出直接包含括号字面量、且本身又需要切换颜色的父节点。仓库中 TSQ 的最终版本在 runtime/queries/tsq/rainbows.scm,可以作为对照。
标记语言会更麻烦。指南用 HTML 的<a>link</a>演示了一个典型案例:<、>、</是(start_tag)和(end_tag)的子节点,而不是(element)的直接子节点。由于默认规则要求@rainbow.bracket必须是@rainbow.scope节点的直接后代才能被高亮,这时需要给 scope 加上rainbow.include-children属性,允许间接后代也被高亮:
((element) @rainbow.scope (#set! rainbow.include-children))判断标准是:查看语法树后,如果提升嵌套层级的节点不是括号节点的直接父节点,就加这个属性;对绝大多数编程语言不需要。
把查询放进正确的目录
确定节点名后,按 新增语言指南 的约定放置查询文件:在runtime/queries/<name>/下创建文件。Helix 从该目录加载多个查询文件,其中只有highlights.scm是必需的:
| 文件 | 用途 |
|---|---|
highlights.scm | 语法高亮 |
injections.scm | 在字符串、代码围栏等区域嵌入其他语言 |
indents.scm | 缩进 |
textobjects.scm | 文本对象与导航(mif、]f等) |
locals.scm | 作用域跟踪,让局部变量有高亮区分 |
tags.scm | 文档/工作区符号选择器 |
rainbows.scm | rainbow bracket |
查询文件的第一行可以用; inherits: <lang>继承另一个语言的查询。高亮捕获(@function、@type等)的解析规则是:匹配最具体的 scope、捕获你真正想指的叶子节点,最后一个匹配的 pattern(以及最内层节点)胜出,详见 themes 文档。
验证查询是否有效
写出查询后,用 xtask 检查它能否对 grammar 正常编译:
cargo xtask query-check [language]该命令要求每个查询文件都能对 grammar 编译通过。指南同时提供了两个常见问题的排查方法:
- 运行 Helix 报查询相关错误时,可能需要更新 grammar:先
hx --grammar fetch拉取,再hx --grammar build重建过期的 grammar; - 如果某个 parser 导致段错误,需删除位于
runtime/grammars/<name>.so的已编译 parser。
另外指南还提到highlight-check和indent-check会在测试夹具上跑真实的高亮器和缩进器,能捕捉query-check发现不了的错误。
小结
这条路径的每一步都有明确的判断点:选区决定看到的树粒度,S 表达式里的引号决定节点是字面量还是命名节点,树中节点的父子关系决定是否需要rainbow.include-children,最后cargo xtask query-check给出查询能否编译的结论。整篇文章涉及的完整指南见 book/src/guides/rainbow_bracket_queries.md 和 book/src/guides/adding_languages.md。
【免费下载链接】helixA post-modern modal text editor.项目地址: https://gitcode.com/GitHub_Trending/he/helix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考