From 2850c3b58a30c3f4716695dd1108cbb78b9115a3 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 16:51:11 +0300 Subject: [PATCH 01/21] Adding Bulgarian localization. --- po/bg_BG.UTF-8.po | 275 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 2 files changed, 276 insertions(+) create mode 100644 po/bg_BG.UTF-8.po diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po new file mode 100644 index 000000000..e240fd6e7 --- /dev/null +++ b/po/bg_BG.UTF-8.po @@ -0,0 +1,275 @@ +# Bulgarian translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Damyan Bogoev , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-04-23 16:58+0800\n" +"PO-Revision-Date: 2025-05-19 11:34+0300\n" +"Last-Translator: Damyan Bogoev \n" +"Language-Team: Bulgarian \n" +"Language: bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Промяна на заглавието на терминала" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Оставете празно за възстановяване на заглавието по подразбиране." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Отказ" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "ОК" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Грешки в конфигурацията" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors " +"below, and either reload your configuration or ignore these errors." +msgstr "Открити са една или повече грешки в конфигурацията. Моля, прегледайте грешките по-долу и или презаредете конфигурацията си, или ги игнорирайте." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Игнорирай" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 +msgid "Reload Configuration" +msgstr "Презареди на конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Раздели нагоре" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Раздели надолу" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Раздели наляво" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Раздели надясно" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:16 +msgid "Execute a command…" +msgstr "Изпълнение на команда…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Копирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Постави" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Изчисти" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Нулирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Раздели" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Промяна на заглавие…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:255 +msgid "New Tab" +msgstr "Нов раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Затвори раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Нов прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Затвори прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Конфигурация" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Open Configuration" +msgstr "Отвори на конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Command Palette" +msgstr "Командна палитра" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Terminal Inspector" +msgstr "Инспектор на терминала" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +#: src/apprt/gtk/Window.zig:1024 +msgid "About Ghostty" +msgstr "За Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112 +msgid "Quit" +msgstr "Изход" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Разрешаване на достъп до клипборда" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да чете от клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Откажи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Позволи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да запише в клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Предупреждение: Потенциално опасно поставяне" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "Поставянето на този текст в терминала може да е опасно, тъй като изглежда, че може да бъдат изпълнени някои команди." + +#: src/apprt/gtk/Window.zig:208 +msgid "Main Menu" +msgstr "Главно меню" + +#: src/apprt/gtk/Window.zig:229 +msgid "View Open Tabs" +msgstr "Преглед на отворените раздели" + +#: src/apprt/gtk/Window.zig:256 +msgid "New Split" +msgstr "Ново разделяне" + +#: src/apprt/gtk/Window.zig:319 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Използвате дебъг версия на Ghostty! Производителността ще бъде намалена." + +#: src/apprt/gtk/Window.zig:765 +msgid "Reloaded the configuration" +msgstr "Конфигурацията е презаредена" + +#: src/apprt/gtk/Window.zig:1005 +msgid "Ghostty Developers" +msgstr "Разработчици на Ghostty" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Инспектор на терминала" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Затвори" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Изход от Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Затваряне на прозореца?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Затваряне на раздела?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Затваряне на разделянето?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Всички терминални сесии ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Всички терминални сесии в този прозорец ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Всички терминални сесии в този раздел ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Текущият процес в това разделяне ще бъде прекратен." + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Копирано в клипборда" \ No newline at end of file diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 6981d55a0..cc157611c 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -46,6 +46,7 @@ pub const locales = [_][:0]const u8{ "es_AR.UTF-8", "pt_BR.UTF-8", "ca_ES.UTF-8", + "bg_BG.UTF-8", "ga_IE.UTF-8", }; From 0653bcb16e38ba773d44025b8dc97987d587b4da Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:30 +0300 Subject: [PATCH 02/21] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index e240fd6e7..d0ec4b803 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -52,7 +52,7 @@ msgstr "Игнорирай" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 msgid "Reload Configuration" -msgstr "Презареди на конфигурацията" +msgstr "Презареди конфигурацията" #: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 From 4fe3a01f1bec1295008fadc175bbaa07ecd3a4ab Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:38 +0300 Subject: [PATCH 03/21] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index d0ec4b803..317c37a97 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -80,7 +80,7 @@ msgstr "Раздели надясно" #: src/apprt/gtk/ui/1.5/command-palette.blp:16 msgid "Execute a command…" -msgstr "Изпълнение на команда…" +msgstr "Изпълни команда…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 From 87df0004c99fa2ba4d98755acdb68eb3bd9259b2 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:43 +0300 Subject: [PATCH 04/21] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index 317c37a97..9b2773ca5 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -149,7 +149,7 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Open Configuration" -msgstr "Отвори на конфигурацията" +msgstr "Отвори конфигурацията" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Command Palette" From 9ad4537d03b62a452f44afaa0e65d7e134477b57 Mon Sep 17 00:00:00 2001 From: Damyan Bogoev Date: Mon, 30 Jun 2025 19:47:58 +0300 Subject: [PATCH 05/21] Update po/bg_BG.UTF-8.po Co-authored-by: Pavel Atanasov <37866329+reo101@users.noreply.github.com> --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index 9b2773ca5..b371cb04d 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -111,7 +111,7 @@ msgstr "Раздели" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Промяна на заглавие…" +msgstr "Промени заглавие…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" From 2a836b0ab7677ededb9efef18301b9bb3e53ab21 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:02:33 -0600 Subject: [PATCH 06/21] font/coretext: fix small memory leak --- src/font/shaper/coretext.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 1fd9719bb..1aaa029dc 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -109,7 +109,8 @@ pub const Shaper = struct { /// settings the font features of a CoreText font. fn makeFeaturesDict(feats: []const Feature) !*macos.foundation.Dictionary { const list = try macos.foundation.MutableArray.create(); - errdefer list.release(); + // The list will be retained by the dict once we add it to it. + defer list.release(); for (feats) |feat| { const value_num: c_int = @intCast(feat.value); From a91f9ed0e29797e726d278f0a81998136e7167f3 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:38:26 -0600 Subject: [PATCH 07/21] font/coretext: fix small memory leak --- src/font/discovery.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/font/discovery.zig b/src/font/discovery.zig index 9284f9486..6f51379b4 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -831,6 +831,9 @@ pub const CoreText = struct { i: usize, pub fn deinit(self: *DiscoverIterator) void { + for (self.list) |desc| { + desc.release(); + } self.alloc.free(self.list); self.* = undefined; } From 1f733c9e7fbd681dd5557009819515ed614a7119 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 11:48:30 -0600 Subject: [PATCH 08/21] renderer/metal: properly release texture descriptors Fixes memory leak. We always need to release these descriptors; the textures themselves will retain or copy them if necessary. --- src/renderer/metal/Target.zig | 2 +- src/renderer/metal/Texture.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/metal/Target.zig b/src/renderer/metal/Target.zig index fa62d3014..15780189b 100644 --- a/src/renderer/metal/Target.zig +++ b/src/renderer/metal/Target.zig @@ -68,7 +68,7 @@ pub fn init(opts: Options) !Self { const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("width", @as(c_ulong, @intCast(opts.width))); diff --git a/src/renderer/metal/Texture.zig b/src/renderer/metal/Texture.zig index 32820f8fc..5e6ef78d0 100644 --- a/src/renderer/metal/Texture.zig +++ b/src/renderer/metal/Texture.zig @@ -50,7 +50,7 @@ pub fn init( const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format)); From 5dd1ebb5836c2155976cf6adb227e7327e6d78d2 Mon Sep 17 00:00:00 2001 From: trag1c Date: Wed, 2 Jul 2025 22:17:17 +0200 Subject: [PATCH 09/21] add newline to end of file --- po/bg_BG.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po index b371cb04d..18cadddf5 100644 --- a/po/bg_BG.UTF-8.po +++ b/po/bg_BG.UTF-8.po @@ -272,4 +272,4 @@ msgstr "Текущият процес в това разделяне ще бъд #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" -msgstr "Копирано в клипборда" \ No newline at end of file +msgstr "Копирано в клипборда" From 8ed08aaecf19c9539a4679309fef849bee8207ba Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 16:16:33 -0600 Subject: [PATCH 10/21] deps: update zig-objc This update also fixes a memory leak that was caused by blocks not being deallocated and just collecting every single frame, slowly accumulating memory until OOM. --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 6 +++--- src/renderer/metal/Frame.zig | 18 ++++++++---------- src/renderer/metal/IOSurfaceLayer.zig | 25 ++++++++++--------------- 7 files changed, 30 insertions(+), 37 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 68d65fbe9..237720f35 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ }, .zig_objc = .{ // mitchellh/zig-objc - .url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - .hash = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", + .url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + .hash = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", .lazy = true, }, .zig_js = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 3099ca823..420893ef7 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -144,10 +144,10 @@ "url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz", "hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0=" }, - "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt": { + "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk": { "name": "zig_objc", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "hash": "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw=" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "hash": "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw=" }, "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy": { "name": "zig_wayland", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 133284201..6e4b86606 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -314,11 +314,11 @@ in }; } { - name = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt"; + name = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk"; path = fetchZigArtifact { name = "zig_objc"; - url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz"; - hash = "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw="; + url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz"; + hash = "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index bb0a27105..f05a789dd 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -29,6 +29,6 @@ https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.ta https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz -https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz +https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 08fa9568b..daf7e5cea 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -175,9 +175,9 @@ }, { "type": "archive", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", - "sha256": "ce7d6d47ac614a60e56b8509dedf869e2e0d8b747c75e48aded11eec31b3357c" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "dest": "vendor/p/zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", + "sha256": "a37be5eea7e44a2d1b2976ba256b85f76a8c1063fc01ffec85c8a9e67468e4dc" }, { "type": "archive", diff --git a/src/renderer/metal/Frame.zig b/src/renderer/metal/Frame.zig index 81b38e7b6..c766fb8ed 100644 --- a/src/renderer/metal/Frame.zig +++ b/src/renderer/metal/Frame.zig @@ -28,7 +28,7 @@ pub const Options = struct { /// MTLCommandBuffer buffer: objc.Object, -block: CompletionBlock, +block: CompletionBlock.Context, /// Begin encoding a frame. pub fn begin( @@ -47,7 +47,7 @@ pub fn begin( // Create our block to register for completion updates. // The block is deallocated by the objC runtime on success. - const block = try CompletionBlock.init( + const block = CompletionBlock.init( .{ .renderer = renderer, .target = target, @@ -55,7 +55,6 @@ pub fn begin( }, &bufferCompleted, ); - errdefer block.deinit(); return .{ .buffer = buffer, .block = block }; } @@ -114,24 +113,23 @@ pub inline fn complete(self: *Self, sync: bool) void { // If we don't need to complete synchronously, // we add our block as a completion handler. // - // It will be deallocated by the objc runtime on success. + // It will be copied when we add the handler, and then the + // copy will be deallocated by the objc runtime on success. if (!sync) { self.buffer.msgSend( void, objc.sel("addCompletedHandler:"), - .{self.block.context}, + .{&self.block}, ); } self.buffer.msgSend(void, objc.sel("commit"), .{}); // If we need to complete synchronously, we wait until - // the buffer is completed and call the callback directly, - // deiniting the block after we're done. + // the buffer is completed and invoke the block directly. if (sync) { self.buffer.msgSend(void, "waitUntilCompleted", .{}); - self.block.context.sync = true; - bufferCompleted(self.block.context, self.buffer.value); - self.block.deinit(); + self.block.sync = true; + CompletionBlock.invoke(&self.block, .{self.buffer.value}); } } diff --git a/src/renderer/metal/IOSurfaceLayer.zig b/src/renderer/metal/IOSurfaceLayer.zig index 9212bd5e1..5a6bf7307 100644 --- a/src/renderer/metal/IOSurfaceLayer.zig +++ b/src/renderer/metal/IOSurfaceLayer.zig @@ -54,13 +54,11 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // // We release in the callback after setting the contents. surface.retain(); - // We also need to retain the layer itself to make sure it - // isn't destroyed before the callback completes, since if - // that happens it will try to interact with a deallocated - // object. - _ = self.layer.retain(); + // NOTE: Since `self.layer` is passed as an `objc.c.id`, it's + // automatically retained when the block is copied, so we + // don't need to retain it ourselves like with the surface. - var block = try SetSurfaceBlock.init(.{ + var block = SetSurfaceBlock.init(.{ .layer = self.layer.value, .surface = surface, }, &setSurfaceCallback); @@ -68,15 +66,15 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // We check if we're on the main thread and run the block directly if so. const NSThread = objc.getClass("NSThread").?; if (NSThread.msgSend(bool, "isMainThread", .{})) { - setSurfaceCallback(block.context); - block.deinit(); + setSurfaceCallback(&block); } else { - // NOTE: The block will automatically be deallocated by the objc - // runtime once it's executed, so there's no need to deinit it. + // NOTE: The block will be copied when we pass it to dispatch_async, + // and then automatically be deallocated by the objc runtime + // once it's executed. macos.dispatch.dispatch_async( @ptrCast(macos.dispatch.queue.getMain()), - @ptrCast(block.context), + @ptrCast(&block), ); } } @@ -100,10 +98,7 @@ fn setSurfaceCallback( const surface: *IOSurface = block.surface; // See explanation of why we retain and release in `setSurface`. - defer { - surface.release(); - layer.release(); - } + defer surface.release(); // We check to see if the surface is the appropriate size for // the layer, if it's not then we discard it. This is because From f1f9d5eb4b1f027aff4c7a4ed52911a0903a7e64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 2 Jul 2025 16:21:31 -0700 Subject: [PATCH 11/21] Fix some config help that caused website errors when copied --- src/config/Config.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index ef8f48ee9..f36132ea9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2761,14 +2761,14 @@ else /// /// GTK CSS documentation can be found at the following links: /// -/// * - An overview of GTK CSS. -/// * - A comprehensive list +/// * https://docs.gtk.org/gtk4/css-overview.html - An overview of GTK CSS. +/// * https://docs.gtk.org/gtk4/css-properties.html - A comprehensive list /// of supported CSS properties. /// /// Launch Ghostty with `env GTK_DEBUG=interactive ghostty` to tweak Ghostty's /// CSS in real time using the GTK Inspector. Errors in your CSS files would /// also be reported in the terminal you started Ghostty from. See -/// for more +/// https://developer.gnome.org/documentation/tools/inspector.html for more /// information about the GTK Inspector. /// /// This configuration can be repeated multiple times to load multiple files. From 1270e04480c7925063ce2f037ff085566d2a0b45 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 2 Jul 2025 17:43:05 -0600 Subject: [PATCH 12/21] renderer/opengl: maybe fix issue with cell bg alignment By using integers for the fragcoords I may have stepped on an edge case which causes cell background positions to be shifted by 1 px under some circumstances. I couldn't reproduce that issue in a VM, so I'm making this commit for the user who was having the problem to test it. --- src/renderer/shaders/glsl/cell_bg.f.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/shaders/glsl/cell_bg.f.glsl b/src/renderer/shaders/glsl/cell_bg.f.glsl index 7ba6caaa6..fa48c6736 100644 --- a/src/renderer/shaders/glsl/cell_bg.f.glsl +++ b/src/renderer/shaders/glsl/cell_bg.f.glsl @@ -1,7 +1,7 @@ #include "common.glsl" // Position the origin to the upper left -layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord; +layout(origin_upper_left) in vec4 gl_FragCoord; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; From 182f8ddd1a00d9abcdcee5d7179ecabcdd126a0e Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Wed, 2 Jul 2025 17:37:30 -0700 Subject: [PATCH 13/21] Do not resolve the symbolic link for the initial working directory --- src/termio/Exec.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index b8f838cf9..598617a12 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -847,6 +847,15 @@ const Subprocess = struct { else null; + // Propagate the current working directory (CWD) to the shell, enabling + // the shell to display the current directory name rather than the + // resolved path for symbolic links. This is important and based + // on the same behavior in Konsole and Kitty (see the linked issues): + // https://bugs.kde.org/show_bug.cgi?id=242114 + // https://github.com/kovidgoyal/kitty/issues/1595 + // https://github.com/ghostty-org/ghostty/discussions/7769 + if (cwd) |pwd| try env.put("PWD", pwd); + // If we have a cgroup, then we copy that into our arena so the // memory remains valid when we start. const linux_cgroup: Command.LinuxCgroup = cgroup: { From 9e341a3d60212b361c45527a82e8c8f774e6cf47 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 18 Jan 2025 20:47:23 -0600 Subject: [PATCH 14/21] Created tagged union for selection colors, enabled parsing Implemented cell color for Metal Removed use of selection-invert-fg-bg Mirrored feature to OpenGL Added tests for SelectionColor Fixed selection on inverted cell behavior Implemented cell colors for cursor-text Implemented cell colors for cursor-color, removed uses of cursor-invert-fg-bg during rendering Updated docs for dynamically colored options Updated docstrings, cleaned up awkward formatting, and moved style computation to avoid unnecssary invocations Bump version in docstrings --- src/config/Config.zig | 73 +++++++++++++++++++++++++++++++++-- src/termio/Termio.zig | 11 +++--- src/termio/stream_handler.zig | 7 +++- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index f36132ea9..76f91f6a8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -591,8 +591,11 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// the selection color is just the inverted window background and foreground /// (note: not to be confused with the cell bg/fg). /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"selection-foreground": ?Color = null, -@"selection-background": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"selection-foreground": ?DynamicColor = null, +@"selection-background": ?DynamicColor = null, /// Swap the foreground and background colors of cells for selection. This /// option overrides the `selection-foreground` and `selection-background` @@ -600,6 +603,10 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// /// If you select across cells with differing foregrounds and backgrounds, the /// selection color will vary across the selection. +/// +/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// users should set `selection-foreground` and `selection-background` to +/// `cell-background` and `cell-foreground`, respectively. @"selection-invert-fg-bg": bool = false, /// Whether to clear selected text when typing. This defaults to `true`. @@ -645,10 +652,17 @@ palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-color": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"cursor-color": ?DynamicColor = null, /// Swap the foreground and background colors of the cell under the cursor. This /// option overrides the `cursor-color` and `cursor-text` options. +/// +/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// users should set `cursor-color` and `cursor-text` to `cell-foreground` and +/// `cell-background`, respectively. @"cursor-invert-fg-bg": bool = false, /// The opacity level (opposite of transparency) of the cursor. A value of 1 @@ -699,7 +713,10 @@ palette: Palette = .{}, /// The color of the text under the cursor. If this is not set, a default will /// be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-text": ?Color = null, +/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"cursor-text": ?DynamicColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -4409,6 +4426,54 @@ pub const Color = struct { } }; +/// Represents the color values that can be set to a non-static value. +/// +/// Can either be a Color or one of the special values +/// "cell-foreground" or "cell-background". +pub const DynamicColor = union(enum) { + color: Color, + @"cell-foreground", + @"cell-background", + + pub fn parseCLI(input_: ?[]const u8) !DynamicColor { + const input = input_ orelse return error.ValueRequired; + + if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; + if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; + + return DynamicColor{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + .@"cell-foreground", .@"cell-background" => try formatter.formatEntry([:0]const u8, @tagName(self)), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual(DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, try DynamicColor.parseCLI("#4e2a84")); + try testing.expectEqual(DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, try DynamicColor.parseCLI("black")); + try testing.expectEqual(DynamicColor{.@"cell-foreground"}, try DynamicColor.parseCLI("cell-foreground")); + try testing.expectEqual(DynamicColor{.@"cell-background"}, try DynamicColor.parseCLI("cell-background")); + + try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: DynamicColor = .{.@"cell-foreground"}; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); + } +}; + pub const ColorList = struct { const Self = @This(); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 8aaa87011..fda52c375 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,8 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.Color, - cursor_invert: bool, + cursor_color: ?configpkg.Config.DynamicColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -185,7 +184,6 @@ pub const DerivedConfig = struct { .cursor_style = config.@"cursor-style", .cursor_blink = config.@"cursor-style-blink", .cursor_color = config.@"cursor-color", - .cursor_invert = config.@"cursor-invert-fg-bg", .foreground = config.foreground, .background = config.background, .osc_color_report_format = config.@"osc-color-report-format", @@ -265,8 +263,11 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (!opts.config.cursor_invert and opts.config.cursor_color != null) - opts.config.cursor_color.?.toTerminalRGB() + const default_cursor_color = if (opts.config.cursor_color) |color| + switch (color) { + .color => color.color.toTerminalRGB(), + else => null, + } else null; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 1b4fdd3aa..9946b0b8a 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,8 +121,11 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (!config.cursor_invert and config.cursor_color != null) - config.cursor_color.?.toTerminalRGB() + self.default_cursor_color = if (config.cursor_color) |color| + switch (color) { + .color => color.color.toTerminalRGB(), + else => null, + } else null; From 95de1987615bd40f3c8afa0180bc1c0f1c184d7d Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Jul 2025 23:04:58 -0400 Subject: [PATCH 15/21] Squash and rebase, updated refactored version with selection and cursor cell color changes --- src/config/Config.zig | 10 +-- src/renderer/generic.zig | 159 +++++++++++++++++---------------------- 2 files changed, 76 insertions(+), 93 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 76f91f6a8..1fc2f0f71 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -591,7 +591,7 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// the selection color is just the inverted window background and foreground /// (note: not to be confused with the cell bg/fg). /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"selection-foreground": ?DynamicColor = null, @@ -604,7 +604,7 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// If you select across cells with differing foregrounds and backgrounds, the /// selection color will vary across the selection. /// -/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// Warning: This option has been deprecated as of version 1.2.0. Instead, /// users should set `selection-foreground` and `selection-background` to /// `cell-background` and `cell-foreground`, respectively. @"selection-invert-fg-bg": bool = false, @@ -652,7 +652,7 @@ palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"cursor-color": ?DynamicColor = null, @@ -660,7 +660,7 @@ palette: Palette = .{}, /// Swap the foreground and background colors of the cell under the cursor. This /// option overrides the `cursor-color` and `cursor-text` options. /// -/// Warning: This option has been deprecated as of version 1.1.1. Instead, +/// Warning: This option has been deprecated as of version 1.2.0. Instead, /// users should set `cursor-color` and `cursor-text` to `cell-foreground` and /// `cell-background`, respectively. @"cursor-invert-fg-bg": bool = false, @@ -713,7 +713,7 @@ palette: Palette = .{}, /// The color of the text under the cursor. If this is not set, a default will /// be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.1.1, this can also be set to `cell-foreground` to match +/// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. @"cursor-text": ?DynamicColor = null, diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 810e17686..617862e1c 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -133,12 +133,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// This is cursor color as set in the user's config, if any. If no cursor color /// is set in the user's config, then the cursor color is determined by the /// current foreground color. - default_cursor_color: ?terminal.color.RGB, - - /// When `cursor_color` is null, swap the foreground and background colors of - /// the cell under the cursor for the cursor color. Otherwise, use the default - /// foreground color as the cursor color. - cursor_invert: bool, + default_cursor_color: ?configpkg.Config.DynamicColor, /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of @@ -514,16 +509,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?terminal.color.RGB, - cursor_invert: bool, + cursor_color: ?configpkg.Config.DynamicColor, cursor_opacity: f64, - cursor_text: ?terminal.color.RGB, + cursor_text: ?configpkg.Config.DynamicColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?terminal.color.RGB, - selection_foreground: ?terminal.color.RGB, - invert_selection_fg_bg: bool, + selection_background: ?configpkg.Config.DynamicColor, + selection_foreground: ?configpkg.Config.DynamicColor, bold_is_bright: bool, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, @@ -571,8 +564,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { config.link.links.items, ); - const cursor_invert = config.@"cursor-invert-fg-bg"; - return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", @@ -581,36 +572,18 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .font_styles = font_styles, .font_shaping_break = config.@"font-shaping-break", - .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) - config.@"cursor-color".?.toTerminalRGB() - else - null, - - .cursor_invert = cursor_invert, - - .cursor_text = if (config.@"cursor-text") |txt| - txt.toTerminalRGB() - else - null, - + .cursor_color = config.@"cursor-color", + .cursor_text = config.@"cursor-text", .cursor_opacity = @max(0, @min(1, config.@"cursor-opacity")), .background = config.background.toTerminalRGB(), .foreground = config.foreground.toTerminalRGB(), - .invert_selection_fg_bg = config.@"selection-invert-fg-bg", .bold_is_bright = config.@"bold-is-bright", .min_contrast = @floatCast(config.@"minimum-contrast"), .padding_color = config.@"window-padding-color", - .selection_background = if (config.@"selection-background") |bg| - bg.toTerminalRGB() - else - null, - - .selection_foreground = if (config.@"selection-foreground") |bg| - bg.toTerminalRGB() - else - null, + .selection_background = config.@"selection-background", + .selection_foreground = config.@"selection-foreground", .custom_shaders = custom_shaders, .bg_image = bg_image, @@ -703,7 +676,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .default_background_color = options.config.background, .cursor_color = null, .default_cursor_color = options.config.cursor_color, - .cursor_invert = options.config.cursor_invert, // Render state .cells = .{}, @@ -2079,8 +2051,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Set our new colors self.default_background_color = config.background; self.default_foreground_color = config.foreground; - self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null; - self.cursor_invert = config.cursor_invert; + self.default_cursor_color = config.cursor_color; const bg_image_config_changed = self.config.bg_image_fit != config.bg_image_fit or @@ -2583,22 +2554,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.invert_selection_fg_bg) - if (style.flags.inverse) - // Cell is selected with invert selection fg/bg - // enabled, and the cell has the inverse style - // flag, so they cancel out and we get the normal - // bg color. - bg_style - else - // If it doesn't have the inverse style - // flag then we use the fg color instead. - fg_style + break :bg if (self.config.selection_background) |selection_color| + // Use the selection background if set, otherwise the default fg color. + switch (selection_color) { + .color => selection_color.color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, + } else - // If we don't have invert selection fg/bg set then we - // just use the selection background if set, otherwise - // the default fg color. - break :bg self.config.selection_background orelse self.foreground_color orelse self.default_foreground_color; + self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2618,20 +2582,26 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - if (selected and !self.config.invert_selection_fg_bg) { - // If we don't have invert selection fg/bg set - // then we just use the selection foreground if - // set, otherwise the default bg color. - break :fg self.config.selection_foreground orelse self.background_color orelse self.default_background_color; - } + const final_bg = bg_style orelse self.background_color orelse self.default_background_color; // Whether we need to use the bg color as our fg color: + // - Cell is selected, inverted, and set to cell-foreground + // - Cell is selected, not inverted, and set to cell-background // - Cell is inverted and not selected - // - Cell is selected and not inverted - // Note: if selected then invert sel fg / bg must be - // false since we separately handle it if true above. - break :fg if (style.flags.inverse != selected) - bg_style orelse self.background_color orelse self.default_background_color + if (selected) { + // Use the selection foreground if set, otherwise the default bg color. + break :fg if (self.config.selection_foreground) |selection_color| + switch (selection_color) { + .color => selection_color.color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, + } + else + self.background_color orelse self.default_background_color; + } + + break :fg if (style.flags.inverse) + final_bg else fg_style; }; @@ -2817,19 +2787,25 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse self.default_cursor_color orelse color: { - if (self.cursor_invert) { - // Use the foreground color from the cell under the cursor, if any. - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :color if (sty.flags.inverse) - // If the cell is reversed, use background color instead. - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color) - else - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color); - } else { - break :color self.foreground_color orelse self.default_foreground_color; + const cursor_color = self.cursor_color orelse if (self.default_cursor_color) |color| color: { + // If cursor-color is set, then compute the correct color. + // Otherwise, use the foreground color + if (color == .color) { + // Use the color set by cursor-color, if any. + break :color color.color.toTerminalRGB(); } - }; + + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + + break :color switch (color) { + // If the cell is reversed, use the opposite cell color instead. + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + else => unreachable, + }; + } else self.foreground_color orelse self.default_foreground_color; self.addCursor(screen, style, cursor_color); @@ -2853,18 +2829,25 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .wide, .spacer_tail => true, }; - const uniform_color = if (self.cursor_invert) blk: { - // Use the background color from the cell under the cursor, if any. + const uniform_color = if (self.config.cursor_text) |txt| blk: { + // If cursor-text is set, then compute the correct color. + // Otherwise, use the background color. + if (txt == .color) { + // Use the color set by cursor-text, if any. + break :blk txt.color.toTerminalRGB(); + } + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :blk if (sty.flags.inverse) - // If the cell is reversed, use foreground color instead. - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color) - else - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color); - } else if (self.config.cursor_text) |txt| - txt - else - self.background_color orelse self.default_background_color; + const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + + break :blk switch (txt) { + // If the cell is reversed, use the opposite cell color instead. + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + else => unreachable, + }; + } else self.background_color orelse self.default_background_color; self.uniforms.cursor_color = .{ uniform_color.r, From e87e5e73614aa7ef68bcaaf5814ce0d26b2bb1ea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 07:30:50 -0700 Subject: [PATCH 16/21] backwards compatibility handlers for removed fields --- src/config/Config.zig | 170 +++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 36 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 1fc2f0f71..3cb808179 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -62,6 +62,13 @@ pub const compatibility = std.StaticStringMap( // Ghostty 1.2 removed the `hidden` value from `gtk-tabs-location` and // moved it to `window-show-tab-bar`. .{ "gtk-tabs-location", compatGtkTabsLocation }, + + // Ghostty 1.2 lets you set `cell-foreground` and `cell-background` + // to match the cell foreground and background colors, respectively. + // This can be used with `cursor-color` and `cursor-text` to recreate + // this behavior. This applies to selection too. + .{ "cursor-invert-fg-bg", compatCursorInvertFgBg }, + .{ "selection-invert-fg-bg", compatSelectionInvertFgBg }, }); /// The font families to use. @@ -597,18 +604,6 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, @"selection-foreground": ?DynamicColor = null, @"selection-background": ?DynamicColor = null, -/// Swap the foreground and background colors of cells for selection. This -/// option overrides the `selection-foreground` and `selection-background` -/// options. -/// -/// If you select across cells with differing foregrounds and backgrounds, the -/// selection color will vary across the selection. -/// -/// Warning: This option has been deprecated as of version 1.2.0. Instead, -/// users should set `selection-foreground` and `selection-background` to -/// `cell-background` and `cell-foreground`, respectively. -@"selection-invert-fg-bg": bool = false, - /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as /// text input fields. If you set this to `false`, then the selected text @@ -651,19 +646,20 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. -/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -/// Since version 1.2.0, this can also be set to `cell-foreground` to match -/// the cell foreground color, or `cell-background` to match the cell -/// background color. -@"cursor-color": ?DynamicColor = null, - -/// Swap the foreground and background colors of the cell under the cursor. This -/// option overrides the `cursor-color` and `cursor-text` options. /// -/// Warning: This option has been deprecated as of version 1.2.0. Instead, -/// users should set `cursor-color` and `cursor-text` to `cell-foreground` and -/// `cell-background`, respectively. -@"cursor-invert-fg-bg": bool = false, +/// Direct colors can be specified as either hex (`#RRGGBB` or `RRGGBB`) +/// or a named X11 color. +/// +/// Additionally, special values can be used to set the color to match +/// other colors at runtime: +/// +/// * `cell-foreground` - Match the cell foreground color. +/// (Available since version 1.2.0) +/// +/// * `cell-background` - Match the cell background color. +/// (Available since version 1.2.0) +/// +@"cursor-color": ?DynamicColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -3843,10 +3839,6 @@ pub fn parseManuallyHook( return true; } -/// parseFieldManuallyFallback is a fallback called only when -/// parsing the field directly failed. It can be used to implement -/// backward compatibility. Since this is only called when parsing -/// fails, it doesn't impact happy-path performance. fn compatGtkTabsLocation( self: *Config, alloc: Allocator, @@ -3864,6 +3856,51 @@ fn compatGtkTabsLocation( return false; } +fn compatCursorInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "cursor-invert-fg-bg")); + + // We don't do anything if the value is unset, which is technically + // not EXACTLY the same as prior behavior since it would fallback + // to doing whatever cursor-color/cursor-text were set to, but + // I don't want to store what that is separately so this is close + // enough. + // + // Realistically, these fields were mutually exclusive so anyone + // relying on that behavior should just upgrade to the new + // cursor-color/cursor-text fields. + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"cursor-color" = .@"cell-foreground"; + self.@"cursor-text" = .@"cell-background"; + } + + return true; +} + +fn compatSelectionInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "selection-invert-fg-bg")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"selection-foreground" = .@"cell-background"; + self.@"selection-background" = .@"cell-foreground"; + } + + return true; +} + /// Create a shallow copy of this config. This will share all the memory /// allocated with the previous config but will have a new arena for /// any changes or new allocations. The config should have `deinit` @@ -4437,28 +4474,41 @@ pub const DynamicColor = union(enum) { pub fn parseCLI(input_: ?[]const u8) !DynamicColor { const input = input_ orelse return error.ValueRequired; - if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; - - return DynamicColor{ .color = try Color.parseCLI(input) }; + return .{ .color = try Color.parseCLI(input) }; } /// Used by Formatter pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { switch (self) { .color => try self.color.formatEntry(formatter), - .@"cell-foreground", .@"cell-background" => try formatter.formatEntry([:0]const u8, @tagName(self)), + + .@"cell-foreground", + .@"cell-background", + => try formatter.formatEntry([:0]const u8, @tagName(self)), } } test "parseCLI" { const testing = std.testing; - try testing.expectEqual(DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, try DynamicColor.parseCLI("#4e2a84")); - try testing.expectEqual(DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, try DynamicColor.parseCLI("black")); - try testing.expectEqual(DynamicColor{.@"cell-foreground"}, try DynamicColor.parseCLI("cell-foreground")); - try testing.expectEqual(DynamicColor{.@"cell-background"}, try DynamicColor.parseCLI("cell-background")); + try testing.expectEqual( + DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try DynamicColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try DynamicColor.parseCLI("black"), + ); + try testing.expectEqual( + DynamicColor{.@"cell-foreground"}, + try DynamicColor.parseCLI("cell-foreground"), + ); + try testing.expectEqual( + DynamicColor{.@"cell-background"}, + try DynamicColor.parseCLI("cell-background"), + ); try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); } @@ -8107,3 +8157,51 @@ test "theme specifying light/dark sets theme usage in conditional state" { try testing.expect(cfg._conditional_set.contains(.theme)); } } + +test "compatibility: removed cursor-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--cursor-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + DynamicColor.@"cell-foreground", + cfg.@"cursor-color", + ); + try testing.expectEqual( + DynamicColor.@"cell-background", + cfg.@"cursor-text", + ); + } +} + +test "compatibility: removed selection-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--selection-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + DynamicColor.@"cell-background", + cfg.@"selection-foreground", + ); + try testing.expectEqual( + DynamicColor.@"cell-foreground", + cfg.@"selection-background", + ); + } +} From 465ac5b1b7ad895951f459cfd4de578b14e0e741 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 09:25:55 -0700 Subject: [PATCH 17/21] clean up some of the color usage, use exhaustive switches --- src/config/Config.zig | 48 ++++++++-------- src/renderer/generic.zig | 100 +++++++++++++++++++++------------- src/termio/Termio.zig | 19 ++++--- src/termio/stream_handler.zig | 17 +++--- 4 files changed, 107 insertions(+), 77 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 3cb808179..5d9093bba 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -601,8 +601,8 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"selection-foreground": ?DynamicColor = null, -@"selection-background": ?DynamicColor = null, +@"selection-foreground": ?TerminalColor = null, +@"selection-background": ?TerminalColor = null, /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as @@ -659,7 +659,7 @@ palette: Palette = .{}, /// * `cell-background` - Match the cell background color. /// (Available since version 1.2.0) /// -@"cursor-color": ?DynamicColor = null, +@"cursor-color": ?TerminalColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -712,7 +712,7 @@ palette: Palette = .{}, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"cursor-text": ?DynamicColor = null, +@"cursor-text": ?TerminalColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -4463,16 +4463,14 @@ pub const Color = struct { } }; -/// Represents the color values that can be set to a non-static value. -/// -/// Can either be a Color or one of the special values -/// "cell-foreground" or "cell-background". -pub const DynamicColor = union(enum) { +/// Represents color values that can also reference special color +/// values such as "cell-foreground" or "cell-background". +pub const TerminalColor = union(enum) { color: Color, @"cell-foreground", @"cell-background", - pub fn parseCLI(input_: ?[]const u8) !DynamicColor { + pub fn parseCLI(input_: ?[]const u8) !TerminalColor { const input = input_ orelse return error.ValueRequired; if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; @@ -4480,7 +4478,7 @@ pub const DynamicColor = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { + pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { switch (self) { .color => try self.color.formatEntry(formatter), @@ -4494,23 +4492,23 @@ pub const DynamicColor = union(enum) { const testing = std.testing; try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, - try DynamicColor.parseCLI("#4e2a84"), + TerminalColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try TerminalColor.parseCLI("#4e2a84"), ); try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, - try DynamicColor.parseCLI("black"), + TerminalColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try TerminalColor.parseCLI("black"), ); try testing.expectEqual( - DynamicColor{.@"cell-foreground"}, - try DynamicColor.parseCLI("cell-foreground"), + TerminalColor{.@"cell-foreground"}, + try TerminalColor.parseCLI("cell-foreground"), ); try testing.expectEqual( - DynamicColor{.@"cell-background"}, - try DynamicColor.parseCLI("cell-background"), + TerminalColor{.@"cell-background"}, + try TerminalColor.parseCLI("cell-background"), ); - try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); + try testing.expectError(error.InvalidValue, TerminalColor.parseCLI("a")); } test "formatConfig" { @@ -4518,7 +4516,7 @@ pub const DynamicColor = union(enum) { var buf = std.ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var sc: DynamicColor = .{.@"cell-foreground"}; + var sc: TerminalColor = .{.@"cell-foreground"}; try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); } @@ -8172,11 +8170,11 @@ test "compatibility: removed cursor-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"cursor-color", ); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"cursor-text", ); } @@ -8196,11 +8194,11 @@ test "compatibility: removed selection-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"selection-foreground", ); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"selection-background", ); } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 617862e1c..e7faf633f 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -133,7 +133,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// This is cursor color as set in the user's config, if any. If no cursor color /// is set in the user's config, then the cursor color is determined by the /// current foreground color. - default_cursor_color: ?configpkg.Config.DynamicColor, + default_cursor_color: ?configpkg.Config.TerminalColor, /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of @@ -509,14 +509,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, cursor_opacity: f64, - cursor_text: ?configpkg.Config.DynamicColor, + cursor_text: ?configpkg.Config.TerminalColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?configpkg.Config.DynamicColor, - selection_foreground: ?configpkg.Config.DynamicColor, + selection_background: ?configpkg.Config.TerminalColor, + selection_foreground: ?configpkg.Config.TerminalColor, bold_is_bright: bool, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, @@ -2548,21 +2548,31 @@ pub fn Renderer(comptime GraphicsAPI: type) type { else false; + // The `_style` suffixed values are the colors based on + // the cell style (SGR), before applying any additional + // configuration, inversions, selections, etc. const bg_style = style.bg(cell, color_palette); - const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = style.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.selection_background) |selection_color| - // Use the selection background if set, otherwise the default fg color. - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // If we have an explicit selection background color + // specified int he config, use that + if (self.config.selection_background) |v| { + break :bg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, - } - else - self.foreground_color orelse self.default_foreground_color; + }; + } + + // If no configuration, then our selection background + // is our foreground color. + break :bg self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2582,22 +2592,27 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - const final_bg = bg_style orelse self.background_color orelse self.default_background_color; + // Our happy-path non-selection background color + // is our style or our configured defaults. + const final_bg = bg_style orelse + self.background_color orelse + self.default_background_color; // Whether we need to use the bg color as our fg color: // - Cell is selected, inverted, and set to cell-foreground // - Cell is selected, not inverted, and set to cell-background // - Cell is inverted and not selected if (selected) { - // Use the selection foreground if set, otherwise the default bg color. - break :fg if (self.config.selection_foreground) |selection_color| - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // Use the selection foreground if set + if (self.config.selection_foreground) |v| { + break :fg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, - } - else - self.background_color orelse self.default_background_color; + }; + } + + break :fg self.background_color orelse self.default_background_color; } break :fg if (style.flags.inverse) @@ -2787,25 +2802,36 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse if (self.default_cursor_color) |color| color: { - // If cursor-color is set, then compute the correct color. - // Otherwise, use the foreground color - if (color == .color) { - // Use the color set by cursor-color, if any. - break :color color.color.toTerminalRGB(); - } + const cursor_color = cursor_color: { + // If an explicit cursor color was set by OSC 12, use that. + if (self.cursor_color) |v| break :cursor_color v; - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; - const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + // Use our configured color if specified + if (self.default_cursor_color) |v| switch (v) { + .color => |color| break :cursor_color color.toTerminalRGB(), + inline .@"cell-foreground", + .@"cell-background", + => |_, tag| { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg( + screen.cursor.page_cell, + color_palette, + ) orelse self.background_color orelse self.default_background_color; - break :color switch (color) { - // If the cell is reversed, use the opposite cell color instead. - .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, - .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, - else => unreachable, + break :cursor_color switch (tag) { + .color => unreachable, + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + }; + }, }; - } else self.foreground_color orelse self.default_foreground_color; + + break :cursor_color self.foreground_color orelse self.default_foreground_color; + }; self.addCursor(screen, style, cursor_color); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index fda52c375..4b5b93641 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,7 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -263,13 +263,16 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (opts.config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + const default_cursor_color: ?terminalpkg.color.RGB = color: { + if (opts.config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; break :handler .{ .alloc = alloc, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 9946b0b8a..040132f03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,13 +121,16 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + self.default_cursor_color = color: { + if (config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; // If our cursor is the default, then we update it immediately. if (self.default_cursor) self.setCursorStyle(.default) catch |err| { From 32764f3a1d8aab3043c11170e2b4c691c3316ca4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 09:29:36 -0700 Subject: [PATCH 18/21] fix test syntax --- src/config/Config.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 5d9093bba..e140785bb 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4500,11 +4500,11 @@ pub const TerminalColor = union(enum) { try TerminalColor.parseCLI("black"), ); try testing.expectEqual( - TerminalColor{.@"cell-foreground"}, + TerminalColor.@"cell-foreground", try TerminalColor.parseCLI("cell-foreground"), ); try testing.expectEqual( - TerminalColor{.@"cell-background"}, + TerminalColor.@"cell-background", try TerminalColor.parseCLI("cell-background"), ); @@ -4516,7 +4516,7 @@ pub const TerminalColor = union(enum) { var buf = std.ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var sc: TerminalColor = .{.@"cell-foreground"}; + var sc: TerminalColor = .@"cell-foreground"; try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); } From e1be836283e0824f7f37dc9d94404ec4723c9050 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 13:45:38 -0700 Subject: [PATCH 19/21] config: for now, make goto_tab NOT performable on macOS Fixes #7786 Fixes regression from #7683 This is a band-aid fix. The issue is that performable keybinds don't show up in the reverse mapping that GUI toolkits use to find their key equivalents. The full explanation of why is already in Binding.zig. For macOS, we have a way to validate menu items before they're triggered so we ideally do want a way to get reverse mappings even with performable keybinds. But I think this wants to be optional and that's all a bigger change. For now, this is a simple fix that will work. --- src/config/Config.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index e140785bb..fec5b41fc 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -5455,7 +5455,14 @@ pub const Keybinds = struct { .mods = mods, }, .{ .goto_tab = (i - start) + 1 }, - .{ .performable = true }, + .{ + // On macOS we keep this not performable so that the + // keyboard shortcuts in tabs work. In the future the + // correct fix is to fix the reverse mapping lookup + // to allow us to lookup performable keybinds + // conditionally. + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } try self.set.putFlags( @@ -5465,7 +5472,10 @@ pub const Keybinds = struct { .mods = mods, }, .{ .last_tab = {} }, - .{ .performable = true }, + .{ + // See comment above with the numeric goto_tab + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } From e494d94fb326c043e062ab5f60704e891f927371 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 21:14:14 -0700 Subject: [PATCH 20/21] Handle `exec` failures more gracefully Fixes #7792 Our error handling for `exec` failing within the forked process never actually worked! It triggered all sorts of issues. We didn't catch this before because it used to be exceptionally hard to fail an exec because we used to wrap ALL commands in a `/bin/sh -c`. However, we now support direction execution, most notably when you do `ghostty -e ` but also via the `direct:` prefix on configured commands. This fixes up our exec failure handling by printing a useful error message and avoiding any errdefers in the child which was causing the double-close. --- src/Command.zig | 27 +++++++++++++++++--- src/termio/Exec.zig | 60 ++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/Command.zig b/src/Command.zig index 7ed026efe..1bddf8b82 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -188,10 +188,31 @@ fn startPosix(self: *Command, arena: Allocator) !void { // Finally, replace our process. // Note: we must use the "p"-variant of exec here because we // do not guarantee our command is looked up already in the path. - _ = posix.execvpeZ(self.path, argsZ, envp) catch null; + const err = posix.execvpeZ(self.path, argsZ, envp); - // If we are executing this code, the exec failed. In that scenario, - // we return a very specific error that can be detected to determine + // If we are executing this code, the exec failed. We're in the + // child process so there isn't much we can do. We try to output + // something reasonable. Its important to note we MUST NOT return + // any other error condition from here on out. + const stderr = std.io.getStdErr().writer(); + switch (err) { + error.FileNotFound => stderr.print( + \\Requested executable not found. Please verify the command is on + \\the PATH and try again. + \\ + , + .{}, + ) catch {}, + + else => stderr.print( + \\exec syscall failed with unexpected error: {} + \\ + , + .{err}, + ) catch {}, + } + + // We return a very specific error that can be detected to determine // we're in the child. return error.ExecFailedInChild; } diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 598617a12..15b6b8cd4 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -90,15 +90,13 @@ pub fn threadEnter( // Start our subprocess const pty_fds = self.subprocess.start(alloc) catch |err| { // If we specifically got this error then we are in the forked - // process and our child failed to execute. In that case - if (err != error.Termio) return err; + // process and our child failed to execute. If we DIDN'T + // get this specific error then we're in the parent and + // we need to bubble it up. + if (err != error.ExecFailedInChild) return err; - // Output an error message about the exec faililng and exit. - // This generally should NOT happen because we always wrap - // our command execution either in login (macOS) or /bin/sh - // (Linux) which are usually guaranteed to exist. Still, we - // want to handle this scenario. - execFailedInChild() catch {}; + // We're in the child. Nothing more we can do but abnormal exit. + // The Command will output some additional information. posix.exit(1); }; errdefer self.subprocess.stop(); @@ -272,25 +270,6 @@ pub fn resize( return try self.subprocess.resize(grid_size, screen_size); } -/// This outputs an error message when exec failed and we are the -/// child process. This returns so the caller should probably exit -/// after calling this. -/// -/// Note that this usually is only called under very very rare -/// circumstances because we wrap our command execution in login -/// (macOS) or /bin/sh (Linux). So this output can be pretty crude -/// because it should never happen. Notably, this is not the error -/// users see when `command` is invalid. -fn execFailedInChild() !void { - const stderr = std.io.getStdErr().writer(); - try stderr.writeAll("exec failed\n"); - try stderr.writeAll("press any key to exit\n"); - - var buf: [1]u8 = undefined; - var reader = std.io.getStdIn().reader(); - _ = try reader.read(&buf); -} - fn processExitCommon(td: *termio.Termio.ThreadData, exit_code: u32) void { assert(td.backend == .exec); const execdata = &td.backend.exec; @@ -895,6 +874,12 @@ const Subprocess = struct { } { assert(self.pty == null and self.command == null); + // This function is funny because on POSIX systems it can + // fail in the forked process. This is flipped to true if + // we're in an error state in the forked process (child + // process). + var in_child: bool = false; + // Create our pty var pty = try Pty.open(.{ .ws_row = @intCast(self.grid_size.rows), @@ -903,14 +888,14 @@ const Subprocess = struct { .ws_ypixel = @intCast(self.screen_size.height), }); self.pty = pty; - errdefer { + errdefer if (!in_child) { if (comptime builtin.os.tag != .windows) { _ = posix.close(pty.slave); } pty.deinit(); self.pty = null; - } + }; log.debug("starting command command={s}", .{self.args}); @@ -1013,7 +998,22 @@ const Subprocess = struct { .data = self, .linux_cgroup = self.linux_cgroup, }; - try cmd.start(alloc); + + cmd.start(alloc) catch |err| { + // We have to do this because start on Windows can't + // ever return ExecFailedInChild + const StartError = error{ExecFailedInChild} || @TypeOf(err); + switch (@as(StartError, err)) { + // If we fail in our child we need to flag it so our + // errdefers don't run. + error.ExecFailedInChild => { + in_child = true; + return err; + }, + + else => return err, + } + }; errdefer killCommand(&cmd) catch |err| { log.warn("error killing command during cleanup err={}", .{err}); }; From 95d9b1e627abed0543806cc6f1abad3e1d60384b Mon Sep 17 00:00:00 2001 From: Kat <65649991+00-kat@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:24:55 +0000 Subject: [PATCH 21/21] Request translators to update the CODEOWNERS file. --- po/README_TRANSLATORS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/po/README_TRANSLATORS.md b/po/README_TRANSLATORS.md index ca1e45faa..c02a5bd48 100644 --- a/po/README_TRANSLATORS.md +++ b/po/README_TRANSLATORS.md @@ -148,6 +148,18 @@ const locales = [_][]const u8{ You should then be able to run `zig build` and see your translations in action. +Before opening a pull request with the new translation file, you should also add +your locale to the `CODEOWNERS` file. Find the `# Localization` section near the +bottom and add a line like so (where `xx_YY` is your locale): + +```diff + # Localization + /po/README_TRANSLATORS.md @ghostty-org/localization + /po/com.mitchellh.ghostty.pot @ghostty-org/localization + /po/zh_CN.UTF-8.po @ghostty-org/zh_CN ++/po/xx_YY.UTF-8.po @ghostty-org/xx_YY +``` + ## Style Guide These are general style guidelines for translations. Naturally, the specific