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/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 diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po new file mode 100644 index 000000000..18cadddf5 --- /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 "Копирано в клипборда" 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/config/Config.zig b/src/config/Config.zig index ef8f48ee9..fec5b41fc 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. @@ -591,16 +598,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, - -/// 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. -@"selection-invert-fg-bg": bool = false, +/// 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": ?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 @@ -644,12 +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. -@"cursor-color": ?Color = null, - -/// Swap the foreground and background colors of the cell under the cursor. This -/// option overrides the `cursor-color` and `cursor-text` options. -@"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": ?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 @@ -699,7 +709,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.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": ?TerminalColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -2761,14 +2774,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. @@ -3826,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, @@ -3847,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` @@ -4409,6 +4463,65 @@ pub const Color = struct { } }; +/// 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) !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"; + return .{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: TerminalColor, 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( + TerminalColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try TerminalColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + TerminalColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try TerminalColor.parseCLI("black"), + ); + try testing.expectEqual( + TerminalColor.@"cell-foreground", + try TerminalColor.parseCLI("cell-foreground"), + ); + try testing.expectEqual( + TerminalColor.@"cell-background", + try TerminalColor.parseCLI("cell-background"), + ); + + try testing.expectError(error.InvalidValue, TerminalColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: TerminalColor = .@"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(); @@ -5342,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( @@ -5352,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(), + }, ); } @@ -8042,3 +8165,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( + TerminalColor.@"cell-foreground", + cfg.@"cursor-color", + ); + try testing.expectEqual( + TerminalColor.@"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( + TerminalColor.@"cell-background", + cfg.@"selection-foreground", + ); + try testing.expectEqual( + TerminalColor.@"cell-foreground", + cfg.@"selection-background", + ); + } +} 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; } 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); diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 84ecb2abe..df8b6550a 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -47,6 +47,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", "hu_HU.UTF-8", }; diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 810e17686..e7faf633f 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.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 @@ -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.TerminalColor, cursor_opacity: f64, - cursor_text: ?terminal.color.RGB, + cursor_text: ?configpkg.Config.TerminalColor, 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.TerminalColor, + selection_foreground: ?configpkg.Config.TerminalColor, 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 @@ -2577,28 +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.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 - 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; + // 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, + }; + } + + // If no configuration, then our selection background + // is our foreground color. + break :bg self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2618,20 +2592,31 @@ 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; - } + // 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 - // - 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 + 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, + }; + } + + break :fg self.background_color orelse self.default_background_color; + } + + break :fg if (style.flags.inverse) + final_bg else fg_style; }; @@ -2817,18 +2802,35 @@ 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 = cursor_color: { + // If an explicit cursor color was set by OSC 12, use that. + if (self.cursor_color) |v| break :cursor_color v; + + // 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 :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, + }; + }, + }; + + break :cursor_color self.foreground_color orelse self.default_foreground_color; }; self.addCursor(screen, style, cursor_color); @@ -2853,18 +2855,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, 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 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)); 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; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index b8f838cf9..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; @@ -847,6 +826,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: { @@ -886,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), @@ -894,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}); @@ -1004,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}); }; diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 8aaa87011..4b5b93641 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.TerminalColor, 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,10 +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_invert and opts.config.cursor_color != null) - opts.config.cursor_color.?.toTerminalRGB() - 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 1b4fdd3aa..040132f03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,10 +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_invert and config.cursor_color != null) - config.cursor_color.?.toTerminalRGB() - 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| {