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 で独自に実装

それぞれをなぜ選んだのか、という話は今回はしません。興味のある方に向けて、現状のスタックをありのまま並べておきます。受け取り方はお任せします。


シェル統合の自動注入(#191)

それでは、最初の機能について見ていきましょう!

最近のターミナルエミュレータは、適切に設定すればシェルと連携して、あると嬉しい機能をたくさん実現できることをご存じでしょうか?少なくとも身近な開発者の友人に聞いた限り、ほとんどの人がこのことを知らなかったり、有効にしていなかったり、そもそも対応しているターミナルを使っていなかったりします。😢

ターミナルのシェル統合はさまざまなことを可能にします(詳しくは Kitty を参照)。その中でも、ワークフローを覚える必要もなく QOL を大きく向上させてくれる基本的な機能がいくつかあります。

  • リサイズ時の複雑なプロンプトの再描画。リサイズしても、凝ったプロンプトが崩れにくくなります。
  • 作業ディレクトリの報告。新しいタブやスプリットなどが、直前にフォーカスしていたターミナルの作業ディレクトリを引き継げるようになります。
  • アクティブなプロセスの検出。ユーザーがアイドル状態のシェルプロンプトで待機中であることをターミナルが把握できれば、確認なしで安全にターミナルを閉じられるようになります。

デモが大好きなので、例を一つ見てみましょう。複雑なプロンプトの再描画です。下の動画は Ghostty ではありません。macOS で人気の別のターミナルエミュレータです。シェル統合なしで fish のプロンプトをリサイズした様子です。

プロンプトが崩れてしまっているのがわかるでしょうか。 ターミナルエミュレータ自体は何も悪いことをしていません。テキストをリフローしているだけなのですが、シェル側が(現状)プロンプトが複数行に折り返されたことを知る術がないため、一番下の新しい行に再描画されて重複してしまうのです。

次は、シェル統合を設定した Ghostty です。

ずいぶん良くなりましたね!シェルはリサイズ時にプロンプトを再描画できることをターミナルに伝えられます。リサイズ時、Ghostty はターミナルの行をクリアします(チラつきが見えると思いますが、これは今後改善したいところです)。これによりテキストのリフローが抑えられ、余計な改行が生じず、きれいに再描画されます。

では、どうやって設定するのでしょうか? これを機能させるには、シェルごとに専用の設定(zsh なら zsh 用のコード、fish なら fish 用のコード、bash なら……ご想像にお任せします)が必要です。#191 より前は、手動での設定方法をドキュメントに書いていました。

しかし今では、Ghostty が 🪄自動的に🪄 やってくれます(今のところ fish と zsh に対応)。この機能は無効にもできますが、仕組みはシンプルで、シェルのパスから basename を取り、「fish」なら fish、「zsh」なら zsh と判断し、適切な環境変数を設定して、組み込みの設定ファイルがシェル起動時に読み込まれ、自動的にセットアップされるというものです。

この記事の執筆時点では Ghostty はまだ公開されていないので、現時点でシェル統合が最も充実しているのは Kitty です。Kitty もシェル統合を自動で行ってくれます。こうした機能を今すぐ使いたい方は、ぜひ Kitty を使ってみてください。その他のターミナルエミュレータでも、こうした機能の一部に対応しているものがあります!


フォントの自動イタリック化(#179)

ここでは一つのバグ修正と、一つの新機能について紹介します。

まずは基本から。レギュラー、ボールド、イタリック、ボールドイタリックは、それぞれ別のフォントフェイスです。MyAwesomeFont.ttf というフォントがあっても、それは単一のフェイス(おそらくレギュラー)でしかありません。かなり大雑把な説明ですが、だいたいはこうなっています。

ユーザーが MyAwesomeFont を使いたいと言ったとき、Ghostty はそのフォント自体だけでなく、それに紐づくすべてのフェイスを探さなければなりません。プログラミング用フォントの多くは、イタリックやボールドのフェイスを持っていません。(余談ですが、フォントとそれに紐づくフェイスを探す処理は、一般にフォントディスカバリーと呼ばれています。これはより汎用的な問題で、ユーザーがフォントを指定しなかった場合にデフォルトで何を使うか、といったことも含まれます。)

まずはバグの話

もう一つ重要な前提があります。フォントは、すべてのグリフ(描画可能な一文字)を備えているわけではありません。たとえば、一般的な等幅のプログラミング用フォントには、漢字が含まれていないことが多いでしょう(例:草=草 と表示されていれば、それです)。

設定したフォントに存在しない文字を描画しようとすると、Ghostty はその文字を持つ別のフォントを探しに行きます。ただ四角(いわゆる tofu)を表示したり、すべてのグリフを一つのフォントファイルに詰め込むことを強要するよりは、たいていこちらの方が親切です。見つけてきたグリフを設定フォントの見た目に馴染むように描画するにはいろいろと複雑な問題があるのですが、ここでは割愛します。とにかくそういう問題があるということと、その解決策を考えるのが個人的にはちょっとした苦行に感じられるということだけ覚えておいてください。

バグの内容はこうです。以前は、あるスタイルで文字がフォントに存在しなかった場合、Ghostty はそのスタイルでその文字を持つフォントを片っ端から探していました。その結果……とても汚い描画になっていました。等幅フォントにその文字がないと、プロポーショナルフォントが見つかってしまい、最終的にこんな見た目になっていたのです。

なんということでしょう。 修正内容はこうです。指定されたスタイルで文字が存在せず、そのスタイルが「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」を使用して翻訳されました。