Ghostty Devlog 001

Mitchell Hashimoto

Ghostty 開發日誌 001

Ghostty 👻 的創刊開發日誌!Ghostty 是我自 2021 年起作為業餘專案持續開發的終端機模擬器。我最近分享了 Ghostty 問世的消息,自此之後便獲得了大量的關注!

我還沒準備好將它對外公開(但我保證會這麼做,而且會是完全的 FOSS),不過基於一些歷史因素,我想開始撰寫開發日誌,分享關於這款終端機的細節與整體進度的報告。我覺得有許多值得分享的有趣工程內容,也希望大家會喜歡,或許還能從中學到一些東西(或者,來找我、教我一些東西)。

這些開發日誌都會以非常輕鬆、近乎閒聊的語氣撰寫。不會有固定的發布週期。內容將涵蓋與終端機相關的任何主題:功能、奇怪的錯誤、我自己的無知等等。


技術堆疊

既然這是第一篇開發日誌,就來聊聊 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 integration(Shell 整合) 能做到很多事(請參考 Kitty 的說明)。其中有幾個基礎功能完全不需要改變工作流程,就能大幅提升使用體驗:

  • 在調整視窗大小時重繪複雜的提示字元。這能讓你在調整終端機大小時,面對現代、複雜的提示字元時大幅減少畫面異常。
  • 工作目錄回報。這能讓新分頁、分割視窗等繼承先前聚焦終端機的工作目錄。
  • 作用中處理程序偵測。這可用於在終端機模擬器知道使用者正處於閒置的 Shell 提示字元時,安全地關閉終端機而無需要求使用者確認。

我很喜歡實際示範,所以來看一個例子:複雜提示字元的重繪。下面的影片並非 Ghostty,而是 macOS 上另一款熱門的終端機模擬器。這展示了在沒有 Shell integration 的情況下,調整我的 fish 提示字元大小時的情形。

請注意提示字元變得一團糟。終端機模擬器在這裡並沒有做錯任何事。它只是在重排文字,但因為 Shell(目前)不知道提示字元已被重排到多行,所以會在底部的新行重新繪製,導致大量重複。

接下來是已設定 Shell integration 的 Ghostty。

好多了!Shell 可以告知終端機它能在調整大小時重繪提示字元。調整大小時,Ghostty 會清除終端機的那一行(你可以看到閃爍——我之後想再改進這點)。這可以防止文字重排,進而避免產生新行,並達到完美的重排效果。

好,但要怎麼設定呢?這需要針對不同 Shell 的專屬設定(zsh 用 zsh 的程式碼、fish 用 fish 的程式碼、bash 則是一堆垃圾等等)才能運作。在 #191 之前,我只是撰寫文件說明使用者如何手動設定。

但現在,Ghostty 會 🪄 自動神奇地 🪄 幫你處理好這一切(目前支援 fish 與 zsh)。這項功能可以關閉,運作方式是對你的 Shell 執行簡單的 basename 判斷,如果名稱是「fish」就假定它是 fish,如果是「zsh」就假定它是「zsh」,然後設定正確的環境變數,讓一些內嵌的設定檔隨著你的 Shell 一併載入並自動完成所有設定。

由於撰寫本文時 Ghostty 尚未公開,目前擁有最佳 Shell integration 的終端機是 Kitty。Kitty 同樣具備自動 Shell integration 功能。如果你現在就想要這類功能,請使用 Kitty。其他終端機模擬器也支援其中一些功能!


自動將字型轉為斜體(#179)

這項改動修復了一個錯誤,並新增了一項功能。

基礎知識:常規、粗體、斜體以及粗斜體是不同的字型樣式。如果你有一個字型檔 MyAwesomeFont.ttf——它通常只包含單一樣式(大概是常規)。我在這裡做了很多簡化,但大致上就是如此。

當使用者說想使用 MyAwesomeFont 時,Ghostty 不僅要找到這個字型,還要找到與該字型相關的所有樣式。許多程式設計用的字型並沒有斜體或粗體樣式。(題外話,尋找字型及其所有相關樣式的過程,通常被稱為 font discovery(字型探索) 問題。這是個更廣泛的問題:如果使用者沒有指定字型,預設該使用哪個字型?等等。)

首先,談談這個錯誤

另一個重要的背景知識:字型通常不會包含所有可能的 glyph(字形)(即可繪製的單一字元)。例如,標準的等寬程式設計字型可能就沒有中文字元(例如,grass = 草,如果你的裝置有正確顯示的話)。

如果程式嘗試繪製一個不在你設定字型中的字元,Ghostty 會嘗試尋找任何包含該字元的其他字型。這通常比只顯示一個方框、強迫你的字型檔必須包含世上所有 glyph 來得好。關於如何以符合你設定字型的方式來繪製這個 glyph,還存在一些複雜的問題,但這不是本文的重點——只要知道這些問題存在就好,而要找出解決方案對我個人而言簡直像是一種自虐。

錯誤是這樣的:先前如果某個字元在你字型的特定樣式中不存在,Ghostty 會搜尋任何在該樣式中包含此字元的字型。結果……渲染效果非常醜陋,因為如果某個等寬字型沒有該字元,Ghostty 就會找到一個非等寬字型,最後看起來像這樣:

喔不。錯誤修正方式:如果某個字元在指定樣式中不存在,且該樣式不是「regular」,就先嘗試 regular。結果是樣式會被忽略,但能正確渲染。很酷吧。

請注意,Ghostty 本來就會優先嘗試搜尋等寬字型。永遠不選用非等寬(proportional)字型似乎很合理,但像是中文字元這類字元,使用比例字型通常也能正常顯示,所以我認為沒有必要加上這種限制。

更好的是,用程式自動產生斜體

特別是針對斜體,我們可以做得更好。如果某個字型沒有提供斜體,我們可以將常規字型傾斜來製造假斜體。雖然看起來不會那麼好,因為它不是在啜飲單品手沖咖啡時精雕細琢設計出來的,但……還算過得去。

所以 Ghostty 現在就是這麼做的。如果請求的是一個斜體字元,而正在使用的字型不支援斜體,我們就會將常規的 glyph 以傾斜的方式進行光柵化,製造出假斜體:

上面的字型不支援斜體。這些斜體是假的。看起來還不錯!而且絕對比完全不顯示斜體來得好。

如果你好奇內部是如何運作的:像是 FreeType 或 CoreText 這類字型光柵化函式庫允許呼叫者提供轉換矩陣。以 CoreText 來說,可以在透過使用 CTFontCreateCopyWithAttributes 初始化字型時提供轉換矩陣。

對 Ghostty 而言,我們只是寫死了一個 15 度的傾斜(如下所示),但你可以看出如何自訂這個數值,以任意方式傾斜 glyph。

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 進行翻譯