深色表面上的水滴微距特写

一个被重复了数百遍的难题

终端模拟器看起来是个成熟到不能再成熟的东西,但 Ghostty 的作者 Mitchell Hashimoto 给了一组让人意外的数字:实现某种终端模拟的程序有数百个。除了我们熟悉的终端应用,还有 tmux、zellij 这类复用器;JetBrains 系列内置的 jediterm、VS Code 用的 Xterm.js、Zed 里嵌的 Alacritty;再往外,GitHub Actions 的彩色日志、Vercel 和 Render 构建日志里的行清除与重绘,都是终端模拟的简化版本。

它们绝大多数是各自为战的临时实现,没有共享代码。结果是 "不完整、有 bug、慢" 成为常态。他举了两个例子:Jediterm 没有正确处理中间字节,导致 "改变光标形状" 这个被广泛支持的序列会吞掉一个字符;Apple 的终端曾把 DCS 序列直接泄漏到输出里。

不是技术问题,是分工问题

作者给出的判断更值得记住:终端模拟不是 JetBrains、VS Code、GitHub、Vercel 的核心业务,为它投入时间是浪费,做不好又直接伤害体验。既然如此,正确的做法是把它做成一个稳定的、可复用的库,让上面这些产品各自专注于自己的业务。

这正是 libghostty 的目标。第一个面世的组件 libghostty-vt 承担最核心的一层:解析终端序列、维护终端状态(光标位置、样式、换行等)。它是零依赖的——连 libc 都不依赖——因此可以被嵌入任何语言生态。它继承了 Ghostty 在真实场景里打磨出来的东西:SIMD 优化的解析、完整的 Unicode 支持、内存优化、经过模糊测试和 Valgrind 检验的代码,以及对 Kitty 图形协议、Tmux 控制模式的兼容。

抽取的收益是双向的

一个常被忽略的细节是:抽成库之后,Ghostty 自己也变成了这个库的使用者。上游的任何改进都会回流到应用,应用里踩到的边界情况也会变成库的测试用例。这种 "自己的产品是库的第一个客户" 的结构,是内部组件化能不能活下来的分水岭——一旦库成了没人用的摆设,它就会烂掉。

内部系统同样适用

企业内部的重复实现一点都不少:对账逻辑、报表导出、权限校验、消息通知,往往每个项目各写一套,然后各自有各自的 bug,各自修各自的。判断要不要抽成内部库的标准,其实和终端模拟这件事一样——它是不是所有人都要做、但都不是任何人的核心业务?如果是,把它抽出去,比在每个项目里重写一遍便宜得多。