Helix 高亮查询(highlights.scm)实战指南:Scopes 体系、优先级规则与自动化测试验证
Helix 高亮查询highlights.scm实战指南Scopes 体系、优先级规则与自动化测试验证【免费下载链接】helixA post-modern modal text editor.项目地址: https://gitcode.com/GitHub_Trending/he/helix本文基于 Helix 官方手册中的高亮查询指南完整讲解highlights.scm查询文件的编写方法如何为语法树节点分配 highlight scopefunction、type、keyword等、如何正确使用; inherits跨语言复用查询、如何理解同跨度后者胜 / 嵌套节点最内层胜两条优先级规则以及如何用cargo xtask query-check与cargo xtask highlight-check对查询进行语法校验和基于 caret 断言的优先级回归测试。读完本篇你能够为任意语言贡献或修改高亮查询并掌握捕获点选择与验证的完整工作流。什么是高亮查询从语法树到主题色的映射链highlights.scm查询负责把 tree-sitter 语法树中的节点与一个highlight scope如function、type、keyword关联起来主题theme再把每个 scope 映射为具体颜色。这是每一门语言都必需的一个查询文件——没有它编辑器就无法对该语言做任何语法着色。贡献 Helix 语言支持时查询文件必须放在固定位置runtime/queries/{language}/highlights.scm例如 Rust 语言的高亮查询就位于 runtime/queries/rust/highlights.scm。整个映射链可以概括为语法树节点 --(highlights.scm 捕获 scope)-- 捕获名 --(主题 toml 的 scope→style)-- 颜色/修饰符主题的 scope 到样式的解析规则是最长匹配若一个捕获名是function.builtin.static而主题中同时定义了function.builtin和function则使用更长的function.builtin键。Scopes 体系选择最具体的捕获完整的 scope 清单及其用途记录在手册的主题页book/src/themes.md 的 Scopes 一节该清单与 Sublime Text 的 scope 命名体系大体一致也参考了 TextMate scopes。核心语法高亮 scope 的组织结构如下取自主题文档的完整列表attribute— 类属性、HTML 标签属性type— 类型builtin— 语言内置原始类型int、usizeparameter— 泛型类型参数Tenumvariant— 枚举变体constructor— 构造器、结构体/记录字面量、值位置的类型名constantbuiltin— 语言内置常量true、false、nil等booleancharacterescapenumeric— 数字integerfloatstringregexp— 正则表达式specialpathurlsymbol— Erlang/Elixir 原子、Ruby 符号、Clojure 关键字commentline— 单行注释//documentation— 单行文档注释如 Rust 的///block— 块注释/* */documentation— 块文档注释如/** */unused— 未使用变量与模式如_、_foovariablemutable— 可变变量Rust 中的mutbuiltin— 语言保留变量self、this、supermutable— 可变语言变量如mut selfparameter— 函数参数mutable— 可变函数参数othermember— 复合数据类型结构体、联合体的字段private— 使用独特语法的私有字段目前仅 ECMAScript 系语言label— CSS 中的.class、#id等punctuationdelimiter— 逗号、冒号bracket— 括号、尖括号等special— 字符串插值括号keywordcontrolconditional—if、elserepeat—for、while、loopimport—import、exportreturnexceptionoperator—or、indirective— 预处理指令C 的#iffunction—fn、funcstorage— 描述存储方式的关键词type—class、function、var、letmodifier—static、mut、const、ref等存储修饰符operator—||、、function— 函数定义与调用public— 公共函数定义builtin— 语言内置函数method— 方法定义与调用obj.method()public— 公共方法定义private— 私有方法独特语法目前仅 ECMAScript 系macro— 宏调用Rust 的println!special— C 的预处理器tag— HTML 标签如bodybuiltinnamespace— 模块与命名空间std::collections、包名special— Rust 的derive、picker 中加粗的查询匹配项等markup—heading含marker与16各级标题、listunnumbered/numbered/checked/unchecked、bold、italic、strikethrough、linkurl/label/text、quote、rawinline/blockdiff— 版本控制变更plus— 新增含gutter边栏指示minus— 删除含gutterdelta— 修改moved重命名/移动、conflict冲突、gutterembedded— 嵌入在字符串模板中的插值表达式${…}选择原则匹配能准确描述该节点的最具体 scope。官方手册给出的典型例子一次方法调用应捕获为function.method而不是笼统的function一次普通的字段访问没有调用应捕获为variable.other.member。主题文档中另有用于编辑器界面的 scope 体系ui.background、ui.cursor.*、ui.statusline.*、ui.menu.*、ui.virtual.*、diagnostic.*等以及 popup/帮助窗口中使用的markup.normal.completion、markup.heading.hover等接口 scope完整键值表同样见 book/src/themes.md。这些是主题侧消费的 scope与highlights.scm中面向语法高亮的 scope 属同一套命名空间编写主题时可一并参考。跨语言复用; inherits:机制一个查询文件可以在第一行通过; inherits: lang声明复用另一门语言的查询避免为派生语言重复编写整套捕获。Helix 仓库中 JavaScript 系语言的继承链就是典型示例runtime/queries/typescript/highlights.scm 第 3 行声明; inherits: ecma,_typescriptruntime/queries/tsx/highlights.scm 第 3 行声明; inherits: ecma,_typescript,_jsx。也就是说tsx继承typescript而typescript又继承公共的ecma基础查询带下划线的目录名_typescript、_jsx表示中间产物层的共享查询见 runtime/queries/ecma/README.md 说明。继承有一个重要约束被继承的文件会针对每一个继承它的语法分别编译因此文件中的每一个捕获都必须在这些语法中同样合法。例如ecma层的查询要同时能被typescript、javascript、tsx等语法解析任何只针对单一语法的节点名都不能写进共享层。优先级规则两条规则决定谁赢得同一段文本当多个捕获匹配同一段文本时由以下两条规则决定最终生效的 scope同跨度后匹配者胜。覆盖相同字节区间的多个捕获中查询文件里靠后出现的 pattern 获胜。因此应当把通用规则放在前面、需要覆盖它的具体规则放在后面。嵌套节点最内层者胜。当父节点和子节点都覆盖某段文本时无论文件顺序如何子节点innermost的捕获获胜。规则 2 的一个常见后果捕获你要捕获的那个叶子节点。如果把function放在包裹调用的外层节点上它会输给内部 identifier 上的基础规则(identifier) variable——所以应当把function直接放在被调用的标识符节点本身。从源码结构可以印证这一最内层获胜的实现方式高亮器以作用域栈的形式工作捕获进入/离开节点时向栈上压入/弹出 scope取栈顶即当前字节的获胜捕获。helix-core/src/syntax.rs 中advance()返回HighlightEvent::Push/Refresh事件而测试工具中同样按active栈的last()栈顶读取获胜捕获见 xtask/src/main.rs。语法无法区分时的启发式大小写匹配当语法本身无法区分某个 scope 时例如 C 中全大写标识符既可能是宏也可能是常量常用大小写启发式配合#match?谓词过滤((identifier) constant (#match? constant ^[A-Z][A-Z_]*$))该谓词只保留匹配正则^[A-Z][A-Z_]*$全大写下划线开头的标识符。#match?谓词在仓库的查询集中被广泛使用例如 runtime/queries/bash/highlights.scm 即依赖此类谓词区分变量与常量。测试与验证query-check 与 highlight-check对高亮查询的验证分两层分别对应两类错误1.cargo xtask query-check [language]语法层校验确认查询对相应语法是合法的节点名存在、捕获名合规等。省略 language 参数时检查全部语言。这一层抓不到优先级错误——查询完全合法但捕获选错的写法它无法发现。2.cargo xtask highlight-check [language]真实高亮器回归测试该任务运行真正的高亮器对tests/query/highlights/language-id/name.ext下的语料文件做断言。语料采用 nvim-treesitter 风格的 caret 注释行在代码行下方写注释^字符的列位置对准上一行的 token后跟期望的获胜捕获foo(bar) // ^ function // ^^^ variable每个^断言其上方列位置处获胜捕获必须与capture完全一致期望名前的!表示取反断言该列不是某个捕获断言行必须是注释且首个^之前只有注释引导符不含字母数字以避免把代码里的^运算符如a ^ b误判为断言行。仓库中已有大量此类语料例如 tests/query/highlights/rust/calls.rsfn main() { invokeit(); // ^ function let s String::new(); // ^ type }该文件断言函数调用invokeit处获胜捕获是function而非基础的variableString::new中的类型位置是type——恰好就是前文两条优先级规则的直接回归用例。目前语料覆盖 rust、cpp、go、python、typescript、tsx、javascript、bash 等数十种语言全部位于 tests/query/highlights/ 目录。3.cargo xtask highlight-check --dump language file调试辅助对任意文件逐 span 打印获胜捕获用于编写断言时发现确切的capture名。输出格式为scopeTAB文本跳过纯空白 span实现见 xtask/src/main.rs。从实现上补充两点细节见 xtask/src/main.rs该工具会扫描全部语言查询文件中出现的捕获名highlights.scm与locals.scm把每个捕获名映射到它自己喂给高亮器从而直接读回获胜的capture原始名字无需手工维护 scope 列表其中local.definition.*前缀的 locals 捕获会被解析为引用实际应用的高亮local.前缀名除外高亮失败语法规格未构建时corpus 模式会打印skipped并跳过而非 panic允许只构建部分语法的开发环境运行对应语言的检查。小结编写高亮查询的自检清单结合手册与仓库实践编写或修改highlights.scm时可按以下清单自检文件位置正确runtime/queries/{language}/highlights.scm每个捕获选了最具体的 scopefunction.methodvsfunction、variable.other.member完整清单参照 book/src/themes.md共享规则在前、覆盖规则在后需要覆盖嵌套节点时把捕获放在叶子节点上使用; inherits:复用基础语言查询时确认所有捕获在每个继承它的语法中都合法语法无法区分的 scope 用#match?谓词如大小写正则做启发式过滤先跑cargo xtask query-check language验证合法性再为关键优先级场景在tests/query/highlights/language-id/下添加 caret 断言语料跑cargo xtask highlight-check language回归验证遇到不确定的捕获名用cargo xtask highlight-check --dump language file打印真实获胜结果。这样即可保证贡献的高亮查询既合法、又在真实高亮器中产生符合预期的着色结果。【免费下载链接】helixA post-modern modal text editor.项目地址: https://gitcode.com/GitHub_Trending/he/helix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考