- 桌面应用
- 跨平台
【免费下载链接】native
Toolkit for building native desktop apps
editors/native-markup是 Native SDK 为.native标记视图(markup view)提供的编辑器工具集:一份 TextMate 语法文件、一个基于 stdio 的 LSP 语言服务器,以及一个零 npm 依赖的 VS Code 扩展。本指南以 editors/native-markup/README.md 为主干,结合 tools/native-sdk/markup.zig、tools/native-sdk/markup_lsp.zig 与 editors/native-markup/extension.js 等源码实现,完整讲解语法高亮、诊断、补全、悬停文档的底层原理,并给出 VS Code、Helix、Neovim 三个编辑器的可复制配置与无编辑器冒烟测试方法。读完本文,你将能把这套工具链接入任意支持 LSP 的编辑器,并理解其"模型无关、构建期校验"的设计边界。
一、整体架构:三件套如何分工
.native是 Native SDK 的声明式 UI 标记语言(语言本身见 skill-data/native-ui/SKILL.md),它编译为与手写canvas.Ui(Msg)构建器完全一致的原生 widget 树。为了让编写.native文件时获得现代编辑体验,editors/native-markup/目录提供了三块互补的组件:
| 组件 | 文件 | 职责 |
|---|---|---|
| TextMate 语法 | syntaxes/native-markup.tmLanguage.json | 标签、属性名、字符串、注释、{...}绑定表达式各有独立 scope;on-*事件属性与for/if/else结构标签被区分着色 |
| 语言服务器 | native markup lsp(实现见 markup_lsp.zig) | 通过 stdio 讲 LSP 协议:打开/变更时推送诊断、<后补全元素名、标签内补全属性/事件名、元素与属性的悬停文档 |
| VS Code 扩展 | package.json + extension.js | 零依赖的 LSP 客户端:自行实现 spawn、Content-Length 分帧、initialize、文档同步、诊断、补全、hover,无需npm install与打包器 |
三者共用同一个解析器与校验器:语言服务器在打开/变更文档时执行的诊断,与native markup check命令完全同源,因此"编辑器里看到的错误"和"命令行校验报出的错误"是同一套判词(含行列定位与教学式提示信息)。
二、TextMate 语法:作用域设计
语法文件scopeName为source.native-markup(VS Code 侧声明于 package.json 的grammars段)。顶层 pattern 依次包含四类规则:
#comment:<!-- ... -->块注释,scope 为comment.block.native-markup;#structure-tag:匹配</?后紧跟for|if|else(以(?![a-z0-9_-])防止误匹配format之类前缀),作为meta.tag.structure.native-markup,其中标签名落在keyword.control.structure.native-markup;#tag:匹配</?后跟小写开头的标识符[a-z][a-z0-9_-]*,标签名落在entity.name.tag.native-markup;#expression:匹配{...}边界内的嵌入表达式。
标签内部(#tag-innards)进一步区分:
#event-attribute:on-开头的属性名(on-[a-z][a-z0-9_-]*)落在entity.other.attribute-name.event.native-markup,与普通属性entity.other.attribute-name.native-markup视觉区分——这正是 README 所说"on-*事件属性被独立着色"的实现;#string:双引号字符串string.quoted.double.native-markup,内部还会继续识别{...}表达式,以及消息载荷分隔冒号:(punctuation.separator.message-payload.native-markup,对应on-press="open_note:{n.id}"这种tag:{payload}写法);#expression内部:==记为比较运算符keyword.operator.comparison.native-markup,形如a.b.c的路径记为variable.other.binding.native-markup,.为punctuation.accessor.native-markup。
配套的 language-configuration.json 定义了编辑行为:块注释<!--/-->、括号对</>与{/}、自动闭合对(含<!--/-->)、环绕对,以及分词器词元模式[a-zA-Z0-9_-]+。注意 Helix 目前没有.native的 tree-sitter 语法,因此其高亮依赖scope = "source.native-markup"加 TextMate 语法的近似呈现(详见下文 Helix 小节)。
三、语言服务器:native markup lsp的协议能力
语言服务器由 CLI 子命令native markup lsp启动,纯 stdio 传输。其完整能力声明在initialize响应中(markup_lsp.zig):
textDocumentSync = 1:全量文档同步(full-document sync),客户端每次变更推送整份文本;服务器对带range的增量变更直接跳过,容错处理;completionProvider.triggerCharacters = ["<", " "]:补全在输入<或空格时触发;hoverProvider = true。
3.1 诊断(diagnostics)
textDocument/didOpen与textDocument/didChange都会触发publishDiagnostics。诊断内容与native markup check共用同一解析器和ui_markup.validate:
- 结构性错误(未知元素、未知属性、未知事件属性、结构标签误用、表达式语法/越界、导入失败等)以Error(severity 1)上报;
- 文档校验干净后,可访问性(a11y)lint 的警告类发现(未命名的图片、冗余标签等)以Warning(severity 2)上报——与命令行检查行为一致(见 markup_lsp.zig);
- 行列位置从解析器 1-based 的
line/column转换为 LSP 的 0-based 坐标,诊断消息保留教学式文案(如unknown element、unknown attribute,命令行还会附上 "did you mean ...?" 拼写建议)。
3.2 补全(completion)
补全按当前光标所处的语法上下文分为三类(markup_lsp.zig):
- 元素名:在
<之后,返回全部元素文档(kind = class)与结构标签for/if/else(kind = keyword); - 属性/事件名:在标签内部,按元素名分发:
for只补each/as等 for 专用属性,if补 test 专用属性,avatar/image、dropdown-menu等会附带通用属性与on-*事件(kind = event),其余元素补通用属性集;else、step、context-menu等无属性的元素不返回任何属性项; - 图标名:在图标值属性的引号内,补全内置矢量图标名与
app:命名空间前缀(对应<icon name="search"/>与app:<name>两种引用方式,后者需应用在启动时通过canvas.icons.registerAppIcons注册)。
3.3 悬停文档(hover)
对元素名与属性名返回一行式说明文档,例如row的 "Flex container"。悬停内容来自服务器内置的文档表,与native markup check的词汇表一致。
3.4 设计边界:绑定路径与 Msg 标签不检查
README 明确说明:绑定路径({...})与on-*的 Msg 标签不会被语言服务器检查。原因在 markup_lsp.zig 的注释中写得很清楚——它们依赖应用具体的Model/Msg类型,而这两者只在应用构建时存在。因此:
- 编辑器中写错绑定路径不会报错(模型无关是 LSP v1 的刻意范围);
- 绑定与消息的校验发生在应用构建期(以及热重载时):
native check在应用目录内、且有新鲜的 model-contract 工件时,会针对应用真实的Model/Msg校验绑定、可迭代源、消息标签与表达式类型; - README 将"构建集成的绑定校验(build-integrated binding validation)"列为未来工作。
这一点决定了工具链的正确使用姿势:LSP 提供语法与词汇的即时反馈,类型级校验交给native check/zig build/ 热重载。
四、构建语言服务器
服务器并非独立产物,而是nativeCLI 的一个子命令。构建方式:
zig build # produces zig-out/bin/native构建产物zig-out/bin/native包含markup check、markup dump、markup lsp三个子命令(见 tools/native-sdk/markup.zig 的 usage 文本)。使用方式任选其一:
- 把
zig-out/bin加入PATH,编辑器直接以native markup lsp启动服务器; - 或者在编辑器配置中指向绝对路径(如 VS Code 的
native-markup.serverPath)。
markup lsp的入口在 markup.zig:用 64 KiB 缓冲的 stdio reader/writer 初始化markup_lsp.Server并run()。服务器主循环读取 Content-Length 分帧消息,直到收到exit或输入流结束(markup_lsp.zig);shutdown返回 null,未知带 id 的请求返回-32601 method not found,未识别通知则静默忽略。协议层只用 std(std.Io.Reader/std.Io.Writer+std.json)手写,无第三方依赖——这也让服务器可以被注入固定缓冲的测试直接驱动。
五、VS Code 集成:零依赖的 LSP 客户端
5.1 安装:符号链接方式
code --install-extension期望打包好的.vsix文件;本扩展刻意避免引入vsce/npm,因此官方推荐符号链接安装:
ln -s /path/to/native-sdk/editors/native-markup ~/.vscode/extensions/native-sdk.native-markup-0.1.0(示例路径中的native-sdk在本仓库中对应/data/web/disk1/git_repo/gh_mirrors/ze/native根目录,请替换为你的实际路径。)随后重载 VS Code 并打开一个.native文件。
如果native不在PATH上,在设置中指定服务器绝对路径:
{ "native-markup.serverPath": "/path/to/native-sdk/zig-out/bin/native" }native-markup.serverPath的默认值为"native",声明于 package.json 的contributes.configuration。另外务必删除旧的可能把*.native映射到html的files.associations配置,否则文件不会拾取native-markup语言 id。
5.2 扩展内部实现
extension.js 是一个"足够用的 LSP 客户端",其设计动机在文件头注释中写明:不依赖vscode-languageclient——那是个需要构建步骤的 npm 包,而本扩展直接手写协议,因此无需npm install、无需打包器、无构建链。
关键实现点:
- 进程与分帧:
spawn(serverPath, ["markup", "lsp"]);pumpMessages()用indexOf("\r\n\r\n")解析头部、正则content-length:\s*(\d+)提取长度、按字节数切分 JSON body;发送侧统一Content-Length: <len>\r\n\r\n<body>封装(extension.js); - 生命周期:
activate中创建诊断集合与输出通道,startServer()发送initialize(clientInfo: { name: "native-markup-vscode", version: "0.1.0" }),随后发送initialized并补发所有已打开文档的didOpen;deactivate走shutdown→exit→ 1 秒超时兜底 kill; - 文档同步:
didOpen/didChange(全量同步)/didClose按语言 id 过滤,仅处理native-markup文档;didClose同时清空该文档的诊断; - 诊断呈现:
publishDiagnostics通知被转换为vscode.Diagnostic,注意 LSP 的 severity 是 1-based、VS Code 是 0-based,做了(item.severity || 1) - 1的换算(extension.js); - 补全与 hover:注册
CompletionItemProvider(触发字符"<"、" ")与HoverProvider,将 LSP 的 completion kind(元素=Class、属性=Property、结构标签=Keyword、事件=Event)映射为 VS Code 对应类型(extension.js); - 启动失败提示:spawn 或进程
error事件会弹出警告消息,提示"用zig build构建 CLI 并设置native-markup.serverPath指向zig-out/bin/native"(extension.js)。
5.3 扩展元数据
package.json 汇总了关键元数据:publisher: native-sdk、version: 0.1.0、engines.vscode: ^1.75.0、激活事件onLanguage:native-markup,语言贡献包含 idnative-markup、扩展名.native、语法文件路径与language-configuration.json。
六、Helix 集成
在~/.config/helix/languages.toml中声明语言服务器与语言:
[language-server.native-markup-lsp] command = "native" args = ["markup", "lsp"] [[language]] name = "native-markup" scope = "source.native-markup" file-types = ["native"] comment-tokens = [] block-comment-tokens = { start = "<!--", end = "-->" } language-servers = ["native-markup-lsp"] auto-pairs = { '<' = '>', '{' = '}', '"' = '"' }由于 Helix 目前没有.native的 tree-sitter 语法,可给[[language]]块追加grammar = "html"获得近似高亮——诊断、补全与悬停无论如何都来自 LSP,不依赖语法树。
七、Neovim(0.10+)集成
在 init 配置中注册文件类型并按FileType自动启动 LSP:
vim.filetype.add({ extension = { native = "native-markup" } }) vim.api.nvim_create_autocmd("FileType", { pattern = "native-markup", callback = function(args) vim.lsp.start({ name = "native-markup-lsp", cmd = { "native", "markup", "lsp" }, root_dir = vim.fs.dirname(vim.fs.find({ "build.zig", ".git" }, { upward = true })[1]), }, { bufnr = args.buf }) end, })高亮方面两条路任选:把缓冲区按 HTML 处理(vim.treesitter.language.register("html", "native-markup")),或采用纯 LSP 配置——无论哪种,诊断、补全与悬停都照常工作。
八、冒烟测试:不依赖编辑器验证服务器
scripts/lsp-smoke.py 是一个端到端冒烟测试,直接用真实 Content-Length 分帧驱动服务器走完整协议流:
python3 editors/native-markup/scripts/lsp-smoke.py zig-out/bin/native测试脚本的断言流程(lsp-smoke.py)非常直观地展示了服务器的完整行为契约:
initialize→ 断言返回capabilities,且textDocumentSync == 1(全量同步);initialized→didOpen一个故意写错的文档(<column>\n <bogus />\n</column>)→ 断言收到publishDiagnostics,恰好 1 条诊断,消息为unknown element,且行列定位精确到1:2(0-based,指向<bogus,对应解析器 1-based 的 2 行 3 列);didChange为已修复的文档(<row gap="8"><text>hi {name}</text></row>)→ 断言诊断清空;completion(位置 0:5,即<row标签内部)→ 断言结果同时包含属性gap与事件on-press;hover(位置 0:2,即row元素名上)→ 断言悬停内容含Flex container;shutdown→exit→ 断言进程以 0 码干净退出,最后输出PASS。
该脚本是理解服务器协议细节的最佳教材:既能验证构建出的二进制是否工作,也展示了任何自定义编辑器客户端应发送的消息序列。
九、与其他markup子命令的关系
语言服务器不是孤立功能,它与native markup命令族的另外两个成员共享同一套解析/校验内核(tools/native-sdk/markup.zig):
native markup check <file.native> [more files...] [--strict]:解析并校验视图——语法、表达式形式、元素、属性、结构标签、<import>闭包、字体覆盖(tofu 字符会报错并指出码点)、可访问性(未命名的交互控件/单选组、无标签的纯图标控件、角色误用是错误;未命名的图片与冗余标签是警告)。在应用目录内且有新鲜的zig-out/model-contract.zon时(用native test刷新),还会对应用真实Model/Msg校验绑定、可迭代源、消息标签、表达式类型与app:图标引用,并报告未被任何视图使用的模型状态与 Msg 标签(--strict把警告提升为失败)。这正是语言服务器诊断的"命令行版本";native markup dump <file.native> [--out doc.nsui]:解析校验并规范化视图,编码为规范二进制 NSUI,再从解码后的二进制派生 JSON 检查视图(所见即产物所言),--out额外写出.nsui工件。
三者关系可以概括为:check是批处理校验(CI 友好),dump是二进制工件检视,lsp是交互式编辑体验——但共享同一解析器与校验器,保证"命令行的判词"与"编辑器里的红线"永远一致。
十、常见问题与排错要点
- 编辑器提示无法启动服务器:确认
zig build已产出zig-out/bin/native,且native-markup.serverPath(或 Helix/Neovim 的command)指向正确路径;VS Code 扩展的警告消息会直接给出这两条建议; .native文件没有高亮/没有语言服务:检查语言 id 是否为native-markup,并清理残留的files.associations: {"*.native": "html"}映射;Helix 用户确认scope = "source.native-markup"与语法文件 scopeName 一致;- 写错绑定路径编辑器不报错:这是设计使然(模型无关的 LSP v1 边界);请依赖
native check、zig build或应用热重载完成类型级校验; - 诊断位置与预期有偏差:解析器位置为 1-based 字节坐标,LSP 侧统一转换为 0-based;参考
lsp-smoke.py中断言的行列换算即可校准理解; - 怀疑旧二进制误报:若报 "unknown element/attribute" 而语法明明是对的,可能是
zig-out/bin/native过期(未覆盖新增语法);重建并对比native version即可(markup.zig 会主动提示这一可能)。
- 桌面应用
- 跨平台
【免费下载链接】native
Toolkit for building native desktop apps
相关推荐
Regal 编辑器集成全指南:在 VS Code、Neovim、Zed、Helix 与 Kakoune 中启用 Rego 语言服务器与调试支持
Regal 编辑器集成全指南:在 VS Code、Neovim、Zed、Helix 与 Kakoune 中启用 Rego 语言服务器与调试支持 Regal 是
后端认证鉴权云原生语言服务器协议(LSP):VS Code 语言扩展开发指南
语言服务器协议(LSP):VS Code 语言扩展开发指南 本文详细介绍了语言服务器协议(LSP)在VS Code扩展开发中的核心原理与实践应用。内容涵盖LSP
示例工程为 templ 语言配置 IDE 与编辑器支持:VS Code、Neovim、Vim、JetBrains、Helix、Emacs 全指南
为 templ 语言配置 IDE 与编辑器支持:VS Code、Neovim、Vim、JetBrains、Helix、Emacs 全指南 本篇指南以 templ
开发工具代码生成后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考