實戰(zhàn)指南:Scopes 體系、優(yōu)先級規(guī)則與自動化測試驗證)
Helix 高亮查詢highlights.scm實戰(zhàn)指南Scopes 體系、優(yōu)先級規(guī)則與自動化測試驗證【免費下載鏈接】helixA post-modern modal text editor.項目地址: https://gitcode.com/GitHub_Trending/he/helix本文基于 Helix 官方手冊中的高亮查詢指南完整講解highlights.scm查詢文件的編寫方法如何為語法樹節(jié)點分配 highlight scopefunction、type、keyword等、如何正確使用; inherits跨語言復(fù)用查詢、如何理解同跨度后者勝 / 嵌套節(jié)點最內(nèi)層勝兩條優(yōu)先級規(guī)則以及如何用cargo xtask query-check與cargo xtask highlight-check對查詢進行語法校驗和基于 caret 斷言的優(yōu)先級回歸測試。讀完本篇你能夠為任意語言貢獻或修改高亮查詢并掌握捕獲點選擇與驗證的完整工作流。什么是高亮查詢從語法樹到主題色的映射鏈highlights.scm查詢負責(zé)把 tree-sitter 語法樹中的節(jié)點與一個highlight scope如function、type、keyword關(guān)聯(lián)起來主題theme再把每個 scope 映射為具體顏色。這是每一門語言都必需的一個查詢文件——沒有它編輯器就無法對該語言做任何語法著色。貢獻 Helix 語言支持時查詢文件必須放在固定位置runtime/queries/{language}/highlights.scm例如 Rust 語言的高亮查詢就位于 runtime/queries/rust/highlights.scm。整個映射鏈可以概括為語法樹節(jié)點 --(highlights.scm 捕獲 scope)-- 捕獲名 --(主題 toml 的 scope→style)-- 顏色/修飾符主題的 scope 到樣式的解析規(guī)則是最長匹配若一個捕獲名是function.builtin.static而主題中同時定義了function.builtin和function則使用更長的function.builtin鍵。Scopes 體系選擇最具體的捕獲完整的 scope 清單及其用途記錄在手冊的主題頁book/src/themes.md 的 Scopes 一節(jié)該清單與 Sublime Text 的 scope 命名體系大體一致也參考了 TextMate scopes。核心語法高亮 scope 的組織結(jié)構(gòu)如下取自主題文檔的完整列表attribute— 類屬性、HTML 標簽屬性type— 類型builtin— 語言內(nèi)置原始類型int、usizeparameter— 泛型類型參數(shù)Tenumvariant— 枚舉變體constructor— 構(gòu)造器、結(jié)構(gòu)體/記錄字面量、值位置的類型名constantbuiltin— 語言內(nèi)置常量true、false、nil等booleancharacterescapenumeric— 數(shù)字integerfloatstringregexp— 正則表達式specialpathurlsymbol— Erlang/Elixir 原子、Ruby 符號、Clojure 關(guān)鍵字commentline— 單行注釋//documentation— 單行文檔注釋如 Rust 的///block— 塊注釋/* */documentation— 塊文檔注釋如/** */unused— 未使用變量與模式如_、_foovariablemutable— 可變變量Rust 中的mutbuiltin— 語言保留變量self、this、supermutable— 可變語言變量如mut selfparameter— 函數(shù)參數(shù)mutable— 可變函數(shù)參數(shù)othermember— 復(fù)合數(shù)據(jù)類型結(jié)構(gòu)體、聯(lián)合體的字段private— 使用獨特語法的私有字段目前僅 ECMAScript 系語言label— CSS 中的.class、#id等punctuationdelimiter— 逗號、冒號bracket— 括號、尖括號等special— 字符串插值括號keywordcontrolconditional—if、elserepeat—for、while、loopimport—import、exportreturnexceptionoperator—or、indirective— 預(yù)處理指令C 的#iffunction—fn、funcstorage— 描述存儲方式的關(guān)鍵詞type—class、function、var、letmodifier—static、mut、const、ref等存儲修飾符operator—||、、function— 函數(shù)定義與調(diào)用public— 公共函數(shù)定義builtin— 語言內(nèi)置函數(shù)method— 方法定義與調(diào)用obj.method()public— 公共方法定義private— 私有方法獨特語法目前僅 ECMAScript 系macro— 宏調(diào)用Rust 的println!special— C 的預(yù)處理器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— 嵌入在字符串模板中的插值表達式${…}選擇原則匹配能準確描述該節(jié)點的最具體 scope。官方手冊給出的典型例子一次方法調(diào)用應(yīng)捕獲為function.method而不是籠統(tǒng)的function一次普通的字段訪問沒有調(diào)用應(yīng)捕獲為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。這些是主題側(cè)消費的 scope與highlights.scm中面向語法高亮的 scope 屬同一套命名空間編寫主題時可一并參考。跨語言復(fù)用; inherits:機制一個查詢文件可以在第一行通過; inherits: lang聲明復(fù)用另一門語言的查詢避免為派生語言重復(fù)編寫整套捕獲。Helix 倉庫中 JavaScript 系語言的繼承鏈就是典型示例runtime/queries/typescript/highlights.scm 第 3 行聲明; inherits: ecma,_typescriptruntime/queries/tsx/highlights.scm 第 3 行聲明; inherits: ecma,_typescript,_jsx。也就是說tsx繼承typescript而typescript又繼承公共的ecma基礎(chǔ)查詢帶下劃線的目錄名_typescript、_jsx表示中間產(chǎn)物層的共享查詢見 runtime/queries/ecma/README.md 說明。繼承有一個重要約束被繼承的文件會針對每一個繼承它的語法分別編譯因此文件中的每一個捕獲都必須在這些語法中同樣合法。例如ecma層的查詢要同時能被typescript、javascript、tsx等語法解析任何只針對單一語法的節(jié)點名都不能寫進共享層。優(yōu)先級規(guī)則兩條規(guī)則決定誰贏得同一段文本當(dāng)多個捕獲匹配同一段文本時由以下兩條規(guī)則決定最終生效的 scope同跨度后匹配者勝。覆蓋相同字節(jié)區(qū)間的多個捕獲中查詢文件里靠后出現(xiàn)的 pattern 獲勝。因此應(yīng)當(dāng)把通用規(guī)則放在前面、需要覆蓋它的具體規(guī)則放在后面。嵌套節(jié)點最內(nèi)層者勝。當(dāng)父節(jié)點和子節(jié)點都覆蓋某段文本時無論文件順序如何子節(jié)點innermost的捕獲獲勝。規(guī)則 2 的一個常見后果捕獲你要捕獲的那個葉子節(jié)點。如果把function放在包裹調(diào)用的外層節(jié)點上它會輸給內(nèi)部 identifier 上的基礎(chǔ)規(guī)則(identifier) variable——所以應(yīng)當(dāng)把function直接放在被調(diào)用的標識符節(jié)點本身。從源碼結(jié)構(gòu)可以印證這一最內(nèi)層獲勝的實現(xiàn)方式高亮器以作用域棧的形式工作捕獲進入/離開節(jié)點時向棧上壓入/彈出 scope取棧頂即當(dāng)前字節(jié)的獲勝捕獲。helix-core/src/syntax.rs 中advance()返回HighlightEvent::Push/Refresh事件而測試工具中同樣按active棧的last()棧頂讀取獲勝捕獲見 xtask/src/main.rs。語法無法區(qū)分時的啟發(fā)式大小寫匹配當(dāng)語法本身無法區(qū)分某個 scope 時例如 C 中全大寫標識符既可能是宏也可能是常量常用大小寫啟發(fā)式配合#match?謂詞過濾((identifier) constant (#match? constant ^[A-Z][A-Z_]*$))該謂詞只保留匹配正則^[A-Z][A-Z_]*$全大寫下劃線開頭的標識符。#match?謂詞在倉庫的查詢集中被廣泛使用例如 runtime/queries/bash/highlights.scm 即依賴此類謂詞區(qū)分變量與常量。測試與驗證query-check 與 highlight-check對高亮查詢的驗證分兩層分別對應(yīng)兩類錯誤1.cargo xtask query-check [language]語法層校驗確認查詢對相應(yīng)語法是合法的節(jié)點名存在、捕獲名合規(guī)等。省略 language 參數(shù)時檢查全部語言。這一層抓不到優(yōu)先級錯誤——查詢完全合法但捕獲選錯的寫法它無法發(fā)現(xiàn)。2.cargo xtask highlight-check [language]真實高亮器回歸測試該任務(wù)運行真正的高亮器對tests/query/highlights/language-id/name.ext下的語料文件做斷言。語料采用 nvim-treesitter 風(fēng)格的 caret 注釋行在代碼行下方寫注釋^字符的列位置對準上一行的 token后跟期望的獲勝捕獲foo(bar) // ^ function // ^^^ variable每個^斷言其上方列位置處獲勝捕獲必須與capture完全一致期望名前的!表示取反斷言該列不是某個捕獲斷言行必須是注釋且首個^之前只有注釋引導(dǎo)符不含字母數(shù)字以避免把代碼里的^運算符如a ^ b誤判為斷言行。倉庫中已有大量此類語料例如 tests/query/highlights/rust/calls.rsfn main() { invokeit(); // ^ function let s String::new(); // ^ type }該文件斷言函數(shù)調(diào)用invokeit處獲勝捕獲是function而非基礎(chǔ)的variableString::new中的類型位置是type——恰好就是前文兩條優(yōu)先級規(guī)則的直接回歸用例。目前語料覆蓋 rust、cpp、go、python、typescript、tsx、javascript、bash 等數(shù)十種語言全部位于 tests/query/highlights/ 目錄。3.cargo xtask highlight-check --dump language file調(diào)試輔助對任意文件逐 span 打印獲勝捕獲用于編寫斷言時發(fā)現(xiàn)確切的capture名。輸出格式為scopeTAB文本跳過純空白 span實現(xiàn)見 xtask/src/main.rs。從實現(xiàn)上補充兩點細節(jié)見 xtask/src/main.rs該工具會掃描全部語言查詢文件中出現(xiàn)的捕獲名highlights.scm與locals.scm把每個捕獲名映射到它自己喂給高亮器從而直接讀回獲勝的capture原始名字無需手工維護 scope 列表其中l(wèi)ocal.definition.*前綴的 locals 捕獲會被解析為引用實際應(yīng)用的高亮local.前綴名除外高亮失敗語法規(guī)格未構(gòu)建時corpus 模式會打印skipped并跳過而非 panic允許只構(gòu)建部分語法的開發(fā)環(huán)境運行對應(yīng)語言的檢查。小結(jié)編寫高亮查詢的自檢清單結(jié)合手冊與倉庫實踐編寫或修改highlights.scm時可按以下清單自檢文件位置正確runtime/queries/{language}/highlights.scm每個捕獲選了最具體的 scopefunction.methodvsfunction、variable.other.member完整清單參照 book/src/themes.md共享規(guī)則在前、覆蓋規(guī)則在后需要覆蓋嵌套節(jié)點時把捕獲放在葉子節(jié)點上使用; inherits:復(fù)用基礎(chǔ)語言查詢時確認所有捕獲在每個繼承它的語法中都合法語法無法區(qū)分的 scope 用#match?謂詞如大小寫正則做啟發(fā)式過濾先跑cargo xtask query-check language驗證合法性再為關(guān)鍵優(yōu)先級場景在tests/query/highlights/language-id/下添加 caret 斷言語料跑cargo xtask highlight-check language回歸驗證遇到不確定的捕獲名用cargo xtask highlight-check --dump language file打印真實獲勝結(jié)果。這樣即可保證貢獻的高亮查詢既合法、又在真實高亮器中產(chǎn)生符合預(yù)期的著色結(jié)果?!久赓M下載鏈接】helixA post-modern modal text editor.項目地址: https://gitcode.com/GitHub_Trending/he/helix創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考