Rust 终端界面框架 Ratatui 维护者 Joshka 近日提出了一项名为 Cell Borders 的终端新协议(草案第 1 版),并在现代终端模拟器 Ghostty 的实验分支与 Ratatui 演示应用中跑通了原型。这套方案直接改动了延续几十年的字符终端底层规则——画一条边框线,终于不再需要消耗一整行或一整列的字符位置。

在一台 16 英寸笔记本电脑上,使用正常可读字号的终端窗口通常只有约 200×50 个单元格。横向 200 列绰绰有余,纵向 50 行却寸土寸金。过去开发者用 Unicode 制表符(U+2500 至 U+257F 共 128 个字符)画框,本质上是在格子里填字符,套两三层面板就要白白吃掉 6 到 8 行高度。Cell Borders 把线条直接画在单元格的边缘上,不仅把被吞掉的行高还给内容,还能做出单行圆角按钮、贴底输入框下划线,并在 btop 风格的盲文点阵图里直接叠加网格线。

终端画框的两种代价:占格字符 vs 边缘属性 传统 Unicode 制表符 损失 6-8 行 50 行终端嵌套面板时的纯边框损耗 • 每个面板四周各吞掉 1 行 1 列 • 盲文点阵图无法同时叠加网格线 • 复制文本必定连带竖线符号垃圾 Cell Borders 协议草案 v1 0 行列损耗 线条作为属性附着在单元格边缘 • 面板标签直接骑在顶边,按钮压至单行 • 约 1650 行代码补丁,空屏零内存开销 • 随单元格滚动擦除,复制文本干净无框

为什么不用现成的图形协议,非要造一套新转义序列

在 Ghostty 社区的提案讨论(Discussion #14324)中,最尖锐的质疑直指造轮子:既然 Kitty 图形协议已经支持在文本下方放置带像素偏移量的光栅图像,为什么还要发明一套全新的边框协议?

答案藏在终端界面框架的渲染管线里。以 Ratatui 为代表的现代终端框架,核心机制是逐帧比对单元格样式(diffing cell styles)。Kitty 图形协议管理的是独立于字符网格的像素贴图,而历史上的 DECterm 标尺线指令(DECDRLBR,主设备属性扩展 43)虽然也能不占字符位置画线,但它是一层脱节的画布——发送普通清屏指令擦不掉它,在行内插入或删除字符时,线条更不会跟着文字横向移动。

边框只有活在单元格属性里,才能跟着文字一起滚动、擦除和重排。

Joshka 在今年 9 月先用 Rio 终端做了概念验证(PR #1947,原采用 APC 序列,后因与上世纪 90 年代的 Kermit 95 命令冲突而关闭),随后切入 Ghostty 改用 DCS 转义序列帧格式 ESC P 1 + b cell-border;OP;FIELDS... ESC \。在这套设计下,边框和背景色完全同级:每个单元格利用原本就存在的内存对齐空隙(padding),塞进一个 16 位的边框 ID,指向全局共享的边框属性表。没有边框的屏幕零额外内存开销;即便把 65535 个 ID 全用光,极端内存占用也只有 10 到 15 MB。

为了解决相邻面板抢边框的冲突,协议引入了优先级(priority)机制。当两个相邻单元格在共享边缘都画了线,渲染器按优先级高低合并,激活窗口只需调高自身优先级,就不会被右侧或下方的邻居吃掉半边外框。此外,每个单元格支持最多 12 条线段(四条边各分两半,外加从中心引出的四条线段),从而与半格块字符(▖▗▘▝)精准对齐,连圆角外侧的背景色差都能通过指定外部填充色(outside color)补齐。


零超时回退很精巧,但 tmux 与双套布局才是生死关

任何试图扩展终端标准的提案,都会撞上两堵高墙:怎么在不认识新指令的老终端里优雅降级,以及怎么穿过终端复用器。

对于第一堵墙,Cell Borders 明确禁止开发者靠读取 TERM 或 TERM_PROGRAM 环境变量来瞎猜兼容性,而是用了一个时序技巧。应用启动时先发送一条 DCS 能力查询序列,紧接着发送所有终端都认识的主设备属性请求(DA1,即 \x1b[c)。由于终端按顺序处理输入流,如果应用先收到了 DA1 的回复,还没收到 cell-border 的回复,就能在 0 毫秒超时等待的情况下立刻判定当前终端不支持该协议,无缝切回传统的 Unicode 制表符模式。

零超时等待:DCS 能力检测与平滑回退管线 1. 连续发送双指令 先发 DCS 边框能力查询 紧跟标准 DA1 设备请求 2. 终端按序响应 未知 DCS 被老终端静默丢弃 DA1 必定触发标准应答 3. 零延迟分流布局 先收 DCS:启用零占格边缘线 先收 DA1:回退 Unicode 制表符

真正的死穴在第二堵墙。许多重度终端用户日常工作在 tmux 或 Zellij 等终端复用器里,而这类复用器维护着自己的一套虚拟网格。如果复用器只是把 Cell Borders 的转义序列简单透传(passthrough)给外层终端,一旦窗口分割或重绘,边框就会画错位置。因此,不支持该协议的复用器必须主动拦截并丢弃这些序列,逼迫内部应用退回字符画框;要想在 tmux 里用上新边框,tmux 自己必须重写底层状态机,像存背景色一样把边框存进虚拟网格。

对命令行工具开发者和 UI 框架维护者而言,现在是否要跟进这套协议,取决于两笔现实的工程账。

第一笔是双套尺寸布局的维护成本。过去的终端兼容性回退(比如从真彩色降级到 256 色)只改变视觉观感,不改变几何尺寸;而 Cell Borders 一旦触发回退,每个面板瞬间多吞掉 2 行 2 列,原本单行的圆角按钮会变成带方括号的文本,原本内嵌在顶边的标签得单独占一行。开发者在写 Ratatui、Bubbletea 或 Textual 应用时,布局引擎必须能同时应付这两套可用宽高完全不同的网格。

  • 风险.目前该协议仅由作者在 macOS 版的 Ghostty 分支中利用 AI 辅助写出了约 1650 行代码完成验证,Linux 下的 GTK 与 OpenGL 渲染管线尚未测试,且圆角抗锯齿生成的逐像素小四边形(quads)还没做过严格的帧率基准测试。

接下来最值得盯紧的变量有两个:一是 Ghostty 官方主线是否会正式合入这套約 1650 行的补丁,还是将其挡在核心之外;二是同样采用 Rust 编写、对现代终端特性更友好的复用器 Zellij 会不会率先原生支持它。如果连一家主流终端主线加一家复用器都没能跑通闭环,这套能让终端界面脱胎换骨的协议,很可能会重蹈当年 DEC 标尺线指令被历史尘封的覆辙。