Ghostty Devlog 001

Mitchell Hashimoto

Ghostty 开发日志 001

这是 Ghostty 👻 的首篇开发日志!Ghostty 是我自 2021 年以来作为业余项目一直在开发的终端模拟器。我最近分享了 Ghostty 的存在,自那以后它受到了大量的关注!

我还没有准备好将其向公众开放(但我保证计划会这么做,而且完全 FOSS),不过出于历史原因,我想开始写开发日志来分享关于这款终端的细节以及进展情况。我觉得其中有很多普遍有趣的工程话题可以分享,希望大家会喜欢,也能从中学到一两件事(或者,联系我来教我一两件事)。

这些开发日志都将以非常随意、近乎对话的语气来写。不会有固定的更新频率。内容会涵盖与终端相关的任何事情:功能、奇怪的 bug、我自己的无知等等。


技术栈

作为有史以来的第一篇开发日志,我们来聊聊 Ghostty 背后的技术栈。Ghostty 是一款跨平台(目前支持 macOS 和 Linux,但以将来会支持 Windows 的方式编写)、GPU 加速的终端模拟器。

  • 使用 Zig 编写
  • Libxev 事件循环(专门为 Ghostty 编写并提取出来)
  • 在 Linux 上使用 OpenGL,在 macOS 上使用 Metal
  • 在 Linux 上使用 Fontconfig、FreetypeHarfbuzz 进行字体渲染
  • 在 macOS 上使用 CoreTextHarfbuzz 进行字体渲染
  • 在 macOS 上使用 SwiftUI 前端,在 Linux 上使用 GTK,同时还提供 GLFW 选项
  • 几乎所有其他部分都是完全使用 Zig 自行编写的

我不打算讨论其中任何一项的“原因”。我只是按原样列出技术栈,供感兴趣的人参考。怎么看待它,随你。


自动 Shell 集成注入(#191)

来聊聊我们的第一个功能吧!

你知道现代终端模拟器可以与 shell 通信(在正确配置的情况下)以启用大量实用的功能吗?至少就我身边的开发者朋友圈子而言,大多数人并不知道这一点,也没有启用它,或者没有使用支持它的终端模拟器。😢

终端的 Shell 集成能做很多事情(参见 Kitty)。Shell 集成中有几项无需任何工作流知识就能显著提升生活质量的基础功能:

  • 调整大小时对复杂提示符进行重绘。这使得在使用现代、复杂的提示符时,调整终端大小的闪烁和错乱大大减少。
  • 工作目录上报。这使得新建标签页、分屏等可以继承先前获得焦点的终端的工作目录。
  • 活动进程检测。这可用于在终端模拟器知道用户正处于空闲的 shell 提示符时,无需向用户确认即可安全地关闭终端。

我喜欢演示,所以我们来看一个例子:复杂提示符重绘。下面的视频不是 Ghostty,而是 macOS 上另一款流行的终端模拟器。它展示了在没有 Shell 集成的忍况下调整我的 fish 提示符大小的效果。

注意提示符变得一团糟。终端模拟器在这里并没有做错任何事。它在重排文本,但由于 shell(目前)不知道提示符已被重排到多行,它会在底部的新行上重绘,从而产生大量重复。

接下来是已配置 Shell 集成的 Ghostty。

好多了!Shell 可以告知终端它可以在调整大小时重绘提示符。调整大小时,Ghostty 会清空终端行(你可以看到闪烁——我以后想改进这一点)。这可以防止文本重排,从而避免产生新行,并在调整大小时实现完美的重排效果。

好吧,但该如何配置呢?这需要针对特定 shell(zsh 用 zsh 代码,fish 用 fish 代码,bash 用的就是垃圾,等等)的配置才能生效。在 #191 之前,我只是记录了用户如何手动设置这些。

但现在,Ghostty 会 🪄自动神奇地🪄 完成这些(目前支持 fish 和 zsh)。此功能可以禁用,其工作原理是对你的 shell 做一次简单的 basename 判断,如果是“fish”就认为是 fish,如果是“zsh”就认为是 zsh,并设置正确的环境变量,以便随 shell 加载一些内置的配置文件并自动完成所有设置。

由于在撰写本文时 Ghostty 尚未公开,目前拥有最佳 Shell 集成的终端是 Kitty。Kitty 也支持自动 Shell 集成。如果你现在就想要这类功能,就用 Kitty。其他一些终端模拟器也支持其中的部分功能!


字体自动斜体化(#179)

这既修复了一个 bug,又新增了一个功能。

基础知识:常规、粗体、斜体和粗斜体是不同的字体字面。如果你有一个字体 MyAwesomeFont.ttf——它通常只是一个字面(很可能是常规体)。我在这里做了很多简化,但大致情况就是如此。

当用户说想使用 MyAwesomeFont 时,Ghostty 不仅要找到这个字体,还要找到与该字体关联的所有字面。许多编程字体并没有斜体或粗体字面。(顺带一提,查找字体及其所有关联字面的过程通常被称为 font discovery(字体发现) 问题。这是一个更宽泛的问题:如果用户没有指定字体,默认使用什么字体?等等。)

首先,是这个 Bug

另一个重要的背景知识:字体通常并不包含所有可能的字形(即可绘制的单个字符)。例如,标准的等宽编程字体很可能没有中文字形(例如,grass = 草,如果你能正常看到的话)。

如果程序试图绘制一个在你配置的字体中不存在的字符,Ghostty 会尝试寻找任何其他包含该字符的字体。这通常比仅仅渲染一个方框、强制要求你的字体文件包含世上所有字形要好。关于如何以与你配置的字体相匹配的方式绘制这个字形,其中还有一些复杂性,但本文不讨论这些——只需知道这些问题存在,而我个人觉得去琢磨解决方案简直就是一种自虐。

Bug 是这样的:以前,如果某个字符在你的字体中以给定的样式不存在,Ghostty 会去搜索任何以该样式包含该字符的字体。这导致……渲染效果非常难看,因为如果等宽字体没有某个字符,Ghostty 会找到一个非等宽字体,最终看起来就像这样:

哦不。Bug 修复方案是:如果某个字符以给定样式不存在,且该样式不是“regular”,则先尝试 regular。结果是样式被忽略了,但能正确渲染。不错。

注意,Ghostty 本来就会优先搜索等宽字体。似乎永远不选择使用非等宽(比例)字体看起来很合理,但像中文字形这样的字符在比例字体中通常也能很好地显示,所以我认为这种限制没有必要。

更进一步,用程序生成斜体

特别是对于斜体,我们可以做得更好。如果字体没有提供斜体,我们可以对常规字体进行倾斜来制造假斜体。它看起来不会那么好,因为它不是在品着单品手冲咖啡时精心设计出来的,但……也能凑合。

所以 Ghostty 现在就是这么做的。如果请求的是斜体字符而正在使用的字体不支持斜体,我们就会以倾斜的方式光栅化常规字形来制造假斜体:

上面的字体不支持斜体。这些斜体是假的。看起来还不错!而且肯定比完全不渲染斜体要好得多。

如果你好奇其内部原理:诸如 FreeType 或 CoreText 之类的字体光栅化库允许调用者提供一个变换矩阵。对于 CoreText,可以在使用 CTFontCreateCopyWithAttributes 初始化字体时提供变换矩阵。

对于 Ghostty,我们只是硬编码了一个 15 度的倾斜(见下文),但你可以看到如何自定义它以任意方式倾斜字形。

pub const italic_skew: macos.graphics.AffineTransform = .{
    .a = 1,
    .b = 0,
    .c = 0.267949, // approx. tan(15)
    .d = 1,
    .tx = 0,
    .ty = 0,
};

结语

至此,第一篇 Ghostty 开发日志就结束了。希望你学到了一些东西或觉得有趣。鉴于是第一篇,本来有更多得多的内容可以讲,但我会留到以后的开发日志中再说(不过,我会尽量聚焦于更新的进展)。

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

Boo. 👻

原文由 Mitchell Hashimoto 发布

本文章由 muse-spark-1.2-contributor 进行翻译