Ghostty Devlog 004

Mitchell Hashimoto

Ghostty 开发日志 004

大家好!欢迎阅读 Ghostty 👻 的第四期正式开发日志!

如果你错过了之前的开发日志,或者想进一步了解 Ghostty 是什么,请参阅本网站的 Ghostty 页面


社区动态

上周,我公布了我们的公开 Discord 服务器,并且还做了一场关于 Ghostty 与 Zig 的演讲。如果你错过了其中任何一个,请去看看。我通过 Discord 社区发放新的 beta 邀请。另外,我们的 Discord 社区已经发展到超过 600 人了。哇!

自上一期 Ghostty 开发日志以来,封闭测试(closed beta)的人数从大约 30 人增长到了约 100 人。项目早期,每一轮新的 beta 波次只有寥寥数人,但随着 Ghostty 变得越来越稳定,我们现在已经可以放心地每波增加 15 到 20 人了。我认为这是一个非常好的信号!


漂亮的 GUI

在深入终端内部机制和古老技术之前,我们先来聊聊 GUI 方面的改进,看看一些养眼的东西。如果你在寻找深度技术内容,后面的章节真的会钻进很深的细节。

macOS 和 Linux 上的原生 GUI 都有了长足进步。在 Linux 上,Ghostty 现在有了标题栏菜单、宽标签页(可配置)、“关于”窗口等等。与 Gnome Terminal 和其他基于 GTK 的终端并排对比时,Ghostty 融入得恰到好处:

同样在 Linux 上,现在已有多位 beta 测试者通过 Sway、Hyprland 及其他合成器(compositor)使用 Wayland。我们修复了许多问题,Ghostty 现在在 Wayland 上运行得非常好。

在 macOS 上,我们添加了 window-theme 设置,这样你可以把 Ghostty 实例永久设为深色或浅色模式,而不只是跟随系统。我个人在 macOS 上用浅色模式,但我的终端永远是深色的,所以我可以强制它为深色。我们还让未获得焦点的分屏变淡(可配置)。整体看起来非常漂亮:

Ghostty 的一项既定目标是提供原生的体验,让人感觉 Ghostty 就是为你所用的平台量身打造的。我们非常认真地对待 GUI 改进,确保 Ghostty 遵循其运行平台的惯用设计。上述改进展示了我们在这方面的进展。


我们发现了一个 Vim 的 Bug!还有其他淘气的程序。

在 Ghostty 存在的大部分时间里,它一直把自己标识为 TERM=xterm-ghostty。使用 xterm- 前缀是因为很多淘气的程序会对 TERM 做字符串匹配,以判断某个终端是否支持某组功能。这是错误的,非常淘气,请不要这样做!正确的做法是查询 terminfo 数据库

Terminfo 本身就是一个极其有趣的话题(主要因为它实在太邪门了)。我希望有一天能专门写一篇关于 terminfo 的博客文章。本期开发日志不会深入 terminfo 的细节。如果你想一睹这个古老怪兽的真容,可以在终端里运行 infocmp

在过去一个月里,我们一直在尝试去掉 xterm- 前缀,直接变成 TERM=ghostty。在这个过程中,我们发现自己 terminfo 数据库里有许多 bug,也发现了一些上游问题。我们一路都在同时修复这两者。

最近,我们被一个 Vim 的 bug 卡住了。Vim 9.0 支持 Kitty Keyboard Protocol,但它硬编码了支持该协议的终端列表,而没有正确地遵循 terminfo 数据库。这是一个 bug,一位 Ghostty 社区成员非常了不起地提交了一个修复它的 pull request。这个问题不影响 Neovim。

Vim 的重要性还是有一点的1,所以在这个 bug 被修复、下游发行版广泛更新之前,我们不得不遗憾地改回 TERM=xterm-ghostty。不过,我为我们的进展感到自豪,并且乐观地相信我们很快就能挣脱 xterm 的枷锁。

我们还发现了其他上游 bug,让我自豪的是,Ghostty 社区不仅报告问题,还提供修复。Tim 提交了一个 PR 来修复流行的 Go 库 tcell 中一个特别糟糕的问题。这是一个重要的库,因为 lazygitlazydocker 等流行的 TUI 应用都在使用它。如果没有这个修复,所有这些程序都会输出乱码,除非你明确地以 TERM=xterm-256color 运行它们。希望这个修复能尽快被合并!

这些 bug 和修复都不是 Ghostty 特有的,它们都应该能让程序在面对所有陌生的终端模拟器时更加健壮。

这项工作主要由 Tim Culverhouse(蒂姆·卡尔弗豪斯) 推动,我对他所做的一切深表感谢。


XTGETTCAP(#563)

Terminfo 的一个大问题在于,它依赖于一个本地文件,而运行中的程序必须读取该文件。这意味着,如果你 SSH 到一台远程主机,而该远程主机没有你的终端的 terminfo,程序就无法判断你的终端具备什么能力,可能会表现异常或欠佳。

XTGETTCAP 是终端的一项功能,它允许通过 VT 转义序列而不是读取本地文件来查询终端的 terminfo 数据库。这带来几个好处:程序不需要知道如何解析二进制格式或查找文件路径,而且即使程序在远程运行,也能查询 terminfo。

XTGETTCAP 的样子是这样的:

# Query for stylized underline support (key "Smulx")
ESC P + q 536D756C78 ESC \

# Response from the terminal (Smulx=\E[4:%p1%dm)
ESC P 1 + r 536D756C78=5C45343A25703125646D ESC \

一目了然,对吧?其实没那么糟糕。程序发送文本 ESC P + q <key> ESC \,其中 <key> 是一个十六进制编码的字符串。在上面的例子中,我们对 "Smulx" 做了十六进制编码。响应中则包含该 terminfo 条目的键和值,同样是十六进制编码的。

我们实现中特别酷的一点是,我们利用 Zig 的 comptime 能力在编译期生成所有可能的请求和响应。它们由生成我们实际 terminfo 文件的同一个源文件生成,因此我们的 terminfo 和 XTGETTCAP 始终完美同步。

XTGETTCAP 的性能其实完全无关紧要,但我们的 XTGETTCAP 实现快得离谱,因为它是一张编译期优化的查找表。我们不需要进行任何内存分配、字符串格式化等等。我们只需做一次简单的查找,找到一个稳定的字符指针,然后直接把它写入 pty。非常酷。

来看看生成这些内容的 Zig 代码片段。我热爱 Zig comptime。


可变字体(#345)

大多数字体提供一组固定的样式,例如 "Bold"、"Heavy"、"Medium"、"Italic" 等等。而可变字体(Variable Fonts)则提供单一的字型,使其各种轴(如字重、倾斜度等)可以在某个范围内以连续值进行配置。Ghostty 现在支持可变字体以及配置变体轴。

首先,我们来看看可变字体长什么样。Inconsolata 有一个可变字体版本,你可以在下面的视频中看到我可以精细地控制字符宽度和字重。

这是 Ghostty 使用 Inconsolata 时的默认外观,未设置任何字体变体轴:

下面是一个稍微修改了字重的配置。差异很细微,但你能看出来。先是配置,然后是截图。

font-family = Inconsolata
font-variation-bold = wght=500

为什么上面用的是 wght 而不是 weight?变体的键由字体本身决定,而且字体变体的键必须是四个字符。这些键没有标准化,所以我们不能安全地把 weight 转换成 wght。Ghostty 配置使用字面上的变体键,用户可以使用任何字体检查程序(例如 FontDrop)来确定有效的键。

在终端模拟器中,对可变字体的支持相对罕见,所以我为我们支持这一特性感到自豪(而且是跨平台的!)。这类功能让你能把自己的终端微调到完全符合你的期望,让它感觉恰到好处


尾声

再次感谢最近一轮的 beta 测试者。每一轮总会带来真正了不起的人,他们拥有某些专门领域的深厚知识,或者以新颖独特的方式使用终端来发现各种问题。

如果你想保持关注,请在 Twitter 或 Mastodon 上关注我(链接在页面页脚)。本博客也有 RSS 订阅源

Boo。👻

脚注

  1. Bram Moolenaar(布拉姆·穆勒纳尔)安息,感谢你给予我们的一切。

原文由 Mitchell Hashimoto 发布

本文章由 stealth/ox-alpha 进行翻译