VT Parser 与 Terminal:从字节到状态机
解析器、UTF-8、CSI/OSC/DCS/APC 和终端语义的连接方式
VT Parser 与 Terminal:从字节到状态机
Ghostty 的兼容性核心在 src/terminal/。它没有把每个 escape sequence 写成一个巨大字符串匹配器,而是实现了接近 DEC ANSI parser 的状态机,再把语义分发给多个协议模块。
Parser 的输入输出
src/terminal/Parser.zig 的输入是单个 byte,输出是最多三个按顺序执行的 action。状态包括 ground、escape、csi_entry、csi_param、dcs_passthrough、osc_string、sos_pm_apc_string 等。
byte
→ table[byte][current_state]
→ transition action
→ next state
→ 0..3 Parser.Action三个 action 的设计对应状态转换的 exit / transition / entry 影响;调用者只需按顺序执行。MAX_INTERMEDIATE = 4,MAX_PARAMS = 24 是静态边界,避免常见路径为参数分配;超出极端输入时,项目会在测试和后续策略中控制行为,而不是无限吃内存。
UTF-8 和控制字节不能混读
终端输入同时包含 C0/C1 控制字节、ASCII、UTF-8 codepoint 和 escape 序列。UTF8Decoder.zig 负责把多字节字符组装成 codepoint;Parser 决定哪些 byte 是控制语义;StreamHandler 再把 print 的 u21 写入 cell。
这个顺序很重要:如果在 UTF-8 层面先把所有数据拼成字符串,就会丢失控制字节的边界;如果 Parser 直接按 codepoint 工作,又无法正确处理控制序列中的单字节参数。
专题分发
Terminal.zig 不把所有协议代码塞进一个函数,而是引用:
ansi.zig:常规 ANSI/ECMA 控制;csi.zig:CSI 命令与参数;osc.zig:标题、颜色、超链接、clipboard 等 OSC;dcs.zig/device_*:设备控制和响应;apc.zig/kitty/:Kitty graphics、glyph、keyboard 等扩展;sgr.zig/style.zig:样式和颜色;mouse.zig/paste.zig/focus.zig:输入报告。
Parser 只保证语法边界,Terminal 才知道“CSI 2J”意味着清屏、“OSC 0”意味着改标题、“DECSET 1049”意味着切换 alternate screen。
Terminal 是状态机的执行器
Terminal 聚合的状态包括:ScreenSet、cursor、scrolling region、tabstops、modes、colors、title、pwd、mouse event format、glyph glossary 和 dirty flags。每个 escape sequence 可能同时改变多个字段,例如 resize 需要处理滚动区域、cursor、页、shell redraw 和 renderer dirty state。
高频打印路径尽量直接写入当前 Screen/Page,而涉及外部世界的动作则通过 StreamHandler 上报给 surface/runtime:标题变化、bell、clipboard、打开 URL、child process 状态等不会让 Terminal 直接调用 Cocoa 或 GTK。
SGR 和动态颜色
Terminal 内部区分默认样式、palette、foreground/background/cursor 动态颜色。OSC 可以改变动态颜色,SGR 可以改变 cell style,renderer 最终读取的是统一的 terminal color/style view。这样协议层可以保留“unset / fallback”的语义,例如 cursor color 未设置时回退到 foreground,而不是在解析 OSC 时就把所有值固化成 RGB。
兼容性的来源
Ghostty 的实现策略可从 README 和模块命名中读到:有标准的按标准,没有标准时参考 xterm,再兼容主流终端已经形成的事实标准。工程上这意味着:
- Parser 的状态机要稳定;
- Terminal 的模式和屏幕语义要能容纳非标准扩展;
- 每个协议最好有独立测试和 synthetic/benchmark 入口;
- 新扩展不应绕过现有 dirty、mailbox 和响应管道。
失败与防线
- 参数数量固定上限防止恶意/异常序列造成无限分配;
- 解析和执行分离,便于 fuzz/test;
- synchronized output 有超时 reset,防止程序忘记结束导致画面冻结;
- 终端状态与 runtime 解耦,即使 clipboard 或窗口回调失败,parser 仍能继续处理后续输出。