Ghostty 開發日誌 001
原文由 Mitchell Hashimoto 于 發布,訂閱此部落格
Ghostty 的第一篇開發日誌來了 👻!Ghostty 是我從 2021 年起以 side project 形式開發的終端機模擬器。我最近公開了 Ghostty 的存在,從那之後就獲得了大量的關注!
我還沒準備好對外公開(但我保證之後一定會完全以 FOSS 形式開源),不過基於一些歷史因素,我想先開始寫開發日誌,來分享關於這個終端機的細節與開發進度。我覺得有不少有趣的工程議題可以分享,也希望大家會喜歡,或多或少能學到點東西(或者,也歡迎來教我一兩招)。
這些開發日誌都會用非常輕鬆、近乎閒聊的語氣來寫。不會有固定的發文週期。內容會涵蓋與終端機相關的任何事情:功能、奇怪的 bug、我自己不懂的地方等等。
技術堆疊
既然這是第一篇開發日誌,就來聊聊 Ghostty 背後的技術堆疊吧。Ghostty 是一款跨平台(目前支援 macOS 與 Linux,但設計上未來也會支援 Windows)、GPU 加速的終端機模擬器。
- 使用 Zig 撰寫
- Libxev 事件迴圈(專為 Ghostty 撰寫並抽離出來)
- Linux 上使用 OpenGL,macOS 上使用 Metal
- Linux 上的字型渲染使用 Fontconfig、Freetype、Harfbuzz
- macOS 上的字型渲染使用 CoreText 與 Harfbuzz
- macOS 前端使用 SwiftUI,Linux 上使用 GTK,另外也提供 GLFW 選項
- 幾乎其他所有東西都是用 Zig 完全客製化撰寫
我不打算解釋選用這些技術的「原因」。就只是把目前的技術堆疊條列給有興趣的人參考。請自行解讀。
自動注入 Shell 整合 (#191)
來聊聊我們的第一個功能!
你知道現代的終端機模擬器只要經過正確設定,就能與 Shell 溝通來啟用超多好用的功能嗎?就我身邊親近的開發者朋友來看,大多數人都不知道這件事,也沒有啟用,或是根本沒在用有支援這類功能的終端機模擬器。😢
終端機的 Shell 整合可以做到很多事(可參考 Kitty 的說明)。其中有幾個基本功能,完全不需要改變工作流程,就能大幅提升使用體驗:
- 調整視窗大小時複雜提示字元的重繪。這能讓你在調整終端機大小時,即使使用現代、複雜的提示字元,也不會出現太多畫面錯亂。
- 工作目錄回報。這能讓新分頁、分割視窗等功能自動繼承先前聚焦之終端機的工作目錄。
- 作用中行程偵測。這能讓終端機模擬器在判斷使用者正停留在閒置的 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 不只要找到這個字型,還要找到跟這個字型相關的所有外觀。很多程式設計用的字型並沒有斜體或粗體的外觀。(順帶一提,尋找字型與所有相關外觀的過程,通常被稱為字型探索問題。這是個更廣泛的問題:如果你沒有指定字型,預設該用哪一套字型?等等。)
首先,是 Bug
另一個重要的背景知識:字型通常不會包含所有可能的字形(也就是單一可繪製的字元)。舉例來說,一套標準的等寬程式設計字型大概就不會包含中文字元(例如「草」這個字,如果有正確顯示的話就是草)。
如果程式嘗試繪製的字元不在你設定的字型裡,Ghostty 會嘗試去找任何有這個字元的其他字型。這通常會比直接顯示一個方框、強迫你的字型檔必須包含世上所有字形來得好。至於要用什麼方式繪製這個字形才能跟你設定的字型風格一致,這裡面還有很多複雜的細節,但這不是本文的重點——只要知道這些問題存在,而且光是想解法就讓我覺得像是一種自我折磨就好。
Bug 是這樣的:以前,如果某個字元在你字型的特定樣式中不存在,Ghostty 會去搜尋任何有該字元且符合該樣式的字型。結果就是……畫面變得超醜,因為如果某個等寬字型缺少某個字元,Ghostty 就會去找一個非等寬字型來補,結果看起來就像這樣:

天啊。修正方式是:如果某個字元在指定的樣式中不存在,且該樣式不是「一般體」,就先嘗試用一般體。結果就是會忽略樣式,但至少能正確渲染。很好。
注意,Ghostty 本來就會優先搜尋等寬字型。看起來好像應該永遠不要選用非等寬(比例)字型才合理,但像中文字元這類字元,用比例字型來顯示通常也沒問題,所以我覺得沒必要做這種限制。
更進一步,用程式自動產生斜體
對於斜體這種情況,我們其實可以做得更好。如果某個字型沒有提供斜體外觀,我們可以把一般體傾斜來做出假的斜體。雖然看起來不會像經過職人一邊啜飲單品手沖一邊精雕細琢出來的那麼好看,但……還是能過關。
所以 Ghostty 現在就是這麼做的。如果要求顯示斜體字元,但目前使用的字型不支援斜體,我們就會用傾斜的方式來光柵化一般體的字形,做出假斜體:

上面的字型本身不支援斜體。這些斜體都是假的。看起來還不錯吧!而且絕對比完全不顯示斜體來得好。
如果你好奇這在內部是怎麼運作的:像 FreeType 或 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 feed。
Boo. 👻
隨機一篇部落格
留言
登入後參與討論