Ghostty 개발 로그 001
원문은 Mitchell Hashimoto님이 에 게재했습니다. 이 블로그 구독하기
Ghostty의 첫 개발 로그입니다 👻! Ghostty는 제가 2021년부터 사이드 프로젝트로 작업해 온 터미널 에뮬레이터입니다. 얼마 전 Ghostty의 존재를 공개했는데, 그 이후로 정말 많은 관심을 받고 있습니다!
아직 일반에 공개할 준비는 되지 않았습니다(하지만 완전한 FOSS로 공개할 계획임을 약속드립니다). 다만 기록을 남긴다는 의미에서 터미널에 대한 자세한 내용과 진행 상황을 공유하는 개발 로그를 시작하고 싶었습니다. 공유할 만한 흥미로운 엔지니어링 이야기가 많다고 생각하고, 이 글을 즐겁게 읽고 한두 가지라도 배워 가시길 바랍니다(혹은 연락해서 저에게 한두 가지를 가르쳐 주셔도 좋습니다).
이 개발 로그들은 모두 아주 캐주얼하고 거의 대화하듯 편안한 톤으로 작성될 예정입니다. 특정한 주기로 올라오지도 않습니다. 터미널과 관련된 모든 것 — 기능, 이상한 버그, 제가 몰랐던 것 등 — 을 다룰 것입니다.
기술 스택
첫 개발 로그인 만큼, 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로 직접 작성
여기서 각각을 왜 선택했는지는 이야기하지 않겠습니다. 그저 관심 있는 분들을 위해 기술 스택을 있는 그대로 정리해 둔 것입니다. 편하게 받아들여 주시면 됩니다.
자동 셸 통합 인젝션 (#191)
첫 번째 기능을 이야기해 보겠습니다!
최신 터미널 에뮬레이터가 셸과 통신하도록(제대로 설정된 경우) 설정하면 정말 유용한 기능들을 많이 쓸 수 있다는 걸 알고 계셨나요? 적어도 제 주변의 가까운 개발자 친구들에게 물어보면, 대부분 이 사실을 모르고, 설정해 두지도 않았으며, 이를 지원하는 터미널 에뮬레이터를 사용하지도 않더군요. 😢
터미널의 셸 통합은 많은 일을 합니다(Kitty 참고). 셸 통합으로 얻을 수 있는 기본 기능 중에는 별도의 워크플로를 익힐 필요 없이 삶의 질을 크게 높여 주는 것들이 몇 가지 있습니다:
- 리사이즈 시 복잡한 프롬프트 다시 그리기. 최신의 복잡한 프롬프트를 사용할 때 터미널 크기 조절이 훨씬 덜 깨집니다.
- 작업 디렉터리 보고. 새로운 탭이나 분할 창 등이 이전에 포커스된 터미널의 작업 디렉터리를 그대로 이어받게 합니다.
- 활성 프로세스 감지. 터미널 에뮬레이터가 사용자가 유휴 셸 프롬프트에 머물러 있음을 알면, 확인을 묻지 않고도 안전하게 터미널을 닫을 수 있습니다.
저는 데모를 좋아하니, 한 가지 예를 살펴보겠습니다: 복잡한 프롬프트 다시 그리기. 아래 영상은 Ghostty가 아닙니다. macOS의 다른 인기 터미널 에뮬레이터입니다. 셸 통합 없이 fish 프롬프트를 리사이즈하는 모습을 보여 줍니다.
프롬프트가 엉망이 되는 것을 보실 수 있습니다. 여기서 터미널 에뮬레이터가 잘못한 것은 없습니다. 텍스트를 리플로우하고 있을 뿐인데, 셸이 프롬프트가 여러 줄로 리플로우되었다는 사실을(현재로서는) 알지 못하고 알 수도 없기 때문에, 아래쪽 새 줄에 다시 그려지면서 잔뜩 중복되어 버립니다.
다음은 셸 통합이 설정된 Ghostty입니다.
훨씬 낫죠! 셸이 터미널에 리사이즈 시 프롬프트를 다시 그릴 수 있다고 알려 줄 수 있습니다. 리사이즈할 때 Ghostty는 터미널 라인을 지웁니다(깜빡이는 게 보이실 텐데, 나중에 개선하고 싶습니다). 이렇게 하면 텍스트 리플로우가 일어나지 않아 새 줄이 생기는 것을 막고, 리사이즈 시 완벽하게 다시 그릴 수 있습니다.
좋아요, 그런데 이걸 어떻게 설정하냐고요? 이걸 작동시키려면 셸별로(zsh용은 zsh 코드, fish용은 fish 코드, bash용은 엉망인 코드 등) 설정이 필요합니다. #191 이전에는 사용자가 이를 수동으로 설정하는 방법을 문서로만 안내했습니다.
하지만 이제는 Ghostty가 이를 🪄 마법처럼 자동으로 🪄 처리합니다(현재는 fish와 zsh 지원). 이 기능은 끌 수도 있지만, 동작 방식은 셸 경로에서 basename을 추출해 "fish"이면 fish로, "zsh"이면 zsh로 간주하고, 올바른 환경 변수를 설정해 내장된 설정 파일들이 셸과 함께 로드되어 이 모든 것을 자동으로 설정하도록 하는 것입니다.
이 글을 쓰는 시점에는 Ghostty가 아직 공개되지 않았으므로, 현재 셸 통합이 가장 잘 되어 있는 터미널은 Kitty입니다. Kitty도 자동 셸 통합을 지원합니다. 지금 당장 이런 기능을 쓰고 싶다면 Kitty를 사용해 보세요. 다른 터미널 에뮬레이터들도 이런 기능 중 일부를 지원합니다!
폰트 자동 이탤릭 처리 (#179)
이번 항목은 버그 하나를 고치고, 기능 하나를 추가합니다.
기초부터 설명하자면, regular, bold, italic, bold italic은 서로 다른 폰트 페이스입니다. MyAwesomeFont.ttf 같은 폰트가 있다면, 그건 그냥 하나의 페이스(아마 regular)일 뿐입니다. 많이 단순화해서 말하는 것이지만 일반적으로는 그렇습니다.
사용자가 MyAwesomeFont를 사용하고 싶다고 하면, Ghostty는 이 폰트를 찾는 것뿐만 아니라 이 폰트에 연결된 모든 페이스를 찾아야 합니다. 많은 프로그래밍용 폰트에는 이탤릭이나 볼드 페이스가 없습니다. (참고로, 폰트와 연결된 모든 페이스를 찾는 과정은 보통 폰트 탐색(font discovery) 문제라고 불립니다. 이는 더 일반적인 문제입니다: 사용자가 폰트를 지정하지 않으면 기본적으로 어떤 폰트를 사용해야 하는지 등.)
먼저, 버그부터
배경 지식 하나 더: 폰트는 종종 가능한 모든 글리프(하나의 렌더링 가능한 문자)를 가지고 있지 않습니다. 예를 들어, 일반적인 고정폭 프로그래밍 폰트에는 아마 중국어 문자(예: grass = 草, 화면에 제대로 보였다면)가 없을 것입니다.
프로그램이 설정한 폰트에 없는 문자를 그리려고 하면, Ghostty는 그 문자를 가지고 있는 다른 폰트를 찾아보려고 합니다. 존재하는 모든 글리프를 폰트 파일에 넣도록 강제하거나 그냥 네모 박스로 렌더링하는 것보다는 보통 이쪽이 더 나은 동작입니다. 이 글리프를 설정한 폰트와 어울리도록 그리는 데는 복잡한 문제들이 있지만, 지금은 그 이야기가 아닙니다 — 그냥 그런 문제들이 존재한다는 것만 알아 두시고, 해결책을 고민하는 과정은 개인적으로 일종의 자해처럼 느껴집니다.
버그는 이러했습니다: 이전에는 특정 스타일로 설정된 폰트에 문자가 존재하지 않으면, Ghostty는 그 스타일로 해당 문자를 가진 폰트를 찾아 나섰습니다. 그 결과... 정말 못생기게 렌더링되는 결과가 나왔습니다. 고정폭 폰트에 어떤 문자가 없으면 Ghostty가 비고정폭 폰트를 찾아와 아래처럼 보였기 때문입니다:

맙소사. 버그 수정 방법은 이렇습니다: 주어진 스타일에 문자가 존재하지 않고 그 스타일이 "regular"가 아니라면, 먼저 regular로 시도합니다. 결과적으로 스타일은 무시되지만 올바르게 렌더링됩니다. 좋습니다.
참고로 Ghostty는 이미 고정폭 폰트를 먼저 찾도록 되어 있습니다. 비고정폭(프로포셔널) 폰트를 절대 선택하지 않는 것이 합리적으로 보일 수도 있지만, 중국어 문자 같은 경우는 프로포셔널 폰트에서도 잘 표시되는 편이라 굳이 그런 제한을 둘 필요는 없다고 생각합니다.
더 나아가, 이탤릭을 프로그래밍 방식으로 만들기
특히 이탤릭의 경우, 더 잘할 수 있습니다. 폰트가 이탤릭을 제공하지 않으면, regular 폰트를 기울여 가짜 이탤릭을 만들 수 있습니다. 싱글 오리진 핸드드립을 홀짝이며 장인 정신으로 디자인된 것은 아니니 진짜만큼 좋아 보이지는 않겠지만... 그래도 쓸 만은 합니다.
그래서 Ghostty가 이제 하는 일이 바로 그것입니다. 이탤릭 문자가 요청됐는데 사용 중인 폰트가 이탤릭을 지원하지 않으면, regular 글리프를 기울여 래스터라이즈해서 가짜 이탤릭을 만듭니다:

위 폰트는 이탤릭을 지원하지 않습니다. 이탤릭은 가짜입니다. 그래도 괜찮아 보이죠! 그리고 이탤릭을 전혀 렌더링하지 않는 것보다는 확실히 낫습니다.
내부 동작이 궁금하시다면: 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 피드도 있습니다.
부우. 👻
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기