From 30e5eabe9b1ec1245ffe930264dd0eb3a95c18e7 Mon Sep 17 00:00:00 2001 From: carl villads Date: Sat, 1 Aug 2026 10:06:39 +0200 Subject: [PATCH 001/392] Add 'da' to `CODEOWNERS` and `src/os/i18n_locales.zig` --- CODEOWNERS | 1 + src/os/i18n_locales.zig | 1 + 2 files changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index ed1217313..65c2f6d10 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -168,6 +168,7 @@ /po/be.po @ghostty-org/be_BY /po/bg.po @ghostty-org/bg_BG /po/ca.po @ghostty-org/ca_ES +/po/da.po @ghostty-org/da_DK /po/de.po @ghostty-org/de_DE /po/es_AR.po @ghostty-org/es_AR /po/es_BO.po @ghostty-org/es_BO diff --git a/src/os/i18n_locales.zig b/src/os/i18n_locales.zig index 5c1ee063f..54fa2c909 100644 --- a/src/os/i18n_locales.zig +++ b/src/os/i18n_locales.zig @@ -59,4 +59,5 @@ pub const locales = [_][:0]const u8{ "kk", "be", "eu", + "da", }; From af9ead68172ff4059930e32afc1b7d58a4234fe6 Mon Sep 17 00:00:00 2001 From: carl villads Date: Sat, 1 Aug 2026 10:23:36 +0200 Subject: [PATCH 002/392] Add danish translation file --- po/da.po | 373 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 po/da.po diff --git a/po/da.po b/po/da.po new file mode 100644 index 000000000..e141421fc --- /dev/null +++ b/po/da.po @@ -0,0 +1,373 @@ +# Danish translations for com.mitchellh.ghostty package. +# Copyright (C) 2026 "Mitchell Hashimoto, Ghostty contributors" +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Carl Villads Priisholm , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2026-07-26 04:59+0200\n" +"PO-Revision-Date: 2026-08-01 10:03+0200\n" +"Last-Translator: Carl Villads Priisholm \n" +"Language-Team: Danish \n" +"Language: da\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" + +#: dist/linux/ghostty_nautilus.py:53 +msgid "Open in Ghostty" +msgstr "Åben i Ghostty" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:197 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:201 +msgid "Authorize Clipboard Access" +msgstr "Autorisér adgang til udklipsholderen" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:17 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:17 +msgid "Deny" +msgstr "Afvis" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:18 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:18 +msgid "Allow" +msgstr "Tillad" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92 +msgid "Remember choice for this split" +msgstr "Husk mit valg for denne deling" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 +msgid "Reload configuration to show this prompt again" +msgstr "Genindlæs konfigurationen for at se denne prompt igen" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 +#: src/apprt/gtk/ui/1.5/title-dialog.blp:8 +msgid "Cancel" +msgstr "Annullér" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:8 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:85 +#: src/apprt/gtk/ui/1.3/surface-child-exited.blp:17 +msgid "Close" +msgstr "Luk" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6 +msgid "Configuration Errors" +msgstr "Konfigurationsfejl" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:7 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Der blev fundet en eller flere konfigurationsfejl. Venligst gennemse fejlene " +"nedenfor, og derefter enten genindlæs konfigurationen, eller ignorer disse fejl." + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +msgid "Ignore" +msgstr "Ignorer" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:11 +#: src/apprt/gtk/ui/1.2/surface.blp:373 src/apprt/gtk/ui/1.5/window.blp:300 +msgid "Reload Configuration" +msgstr "Genindlæs konfiguration" + +#: src/apprt/gtk/ui/1.2/debug-warning.blp:7 +#: src/apprt/gtk/ui/1.3/debug-warning.blp:6 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Du kører et debug build af Ghostty! Dette medfører en sænket ydeevne." + +#: src/apprt/gtk/ui/1.5/inspector-window.blp:5 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Terminal-inspektør" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:29 +msgid "Find…" +msgstr "Find…" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:64 +msgid "Previous Match" +msgstr "Forrige match" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:74 +msgid "Next Match" +msgstr "Næste match" + +#: src/apprt/gtk/ui/1.2/surface.blp:6 +msgid "Oh, no." +msgstr "Åh nej." + +#: src/apprt/gtk/ui/1.2/surface.blp:7 +msgid "Unable to acquire an OpenGL context for rendering." +msgstr "Det var ikke muligt at anskaffe en OpenGL kontekst for rendering." + +#: src/apprt/gtk/ui/1.2/surface.blp:99 +msgid "" +"This terminal is in read-only mode. You can still view, select, and scroll " +"through the content, but no input events will be sent to the running " +"application." +msgstr "" +"Denne terminal er i skrivebeskyttet tilstand. Du kan stadig se, vælge, " +"og scrolle gennem indholdet, men ingen anslag vil blive sendt til " +"det kørende program." + +#: src/apprt/gtk/ui/1.2/surface.blp:109 +msgid "Read-only" +msgstr "Skrivebeskyttet" + +#: src/apprt/gtk/ui/1.2/surface.blp:262 src/apprt/gtk/ui/1.5/window.blp:200 +msgid "Copy" +msgstr "Kopiér" + +#: src/apprt/gtk/ui/1.2/surface.blp:267 src/apprt/gtk/ui/1.5/window.blp:205 +msgid "Paste" +msgstr "Sæt ind" + +#: src/apprt/gtk/ui/1.2/surface.blp:272 +msgid "Notify on Next Command Finish" +msgstr "Giv mig besked når den næste kommando er færdig" + +#: src/apprt/gtk/ui/1.2/surface.blp:279 src/apprt/gtk/ui/1.5/window.blp:273 +msgid "Clear" +msgstr "Ryd" + +#: src/apprt/gtk/ui/1.2/surface.blp:284 src/apprt/gtk/ui/1.5/window.blp:278 +msgid "Reset" +msgstr "Nulstil" + +#: src/apprt/gtk/ui/1.2/surface.blp:291 src/apprt/gtk/ui/1.5/window.blp:242 +# I de andre oversættelser af 'split' bruges 'del' i stedet for 'opdel', men tror det er for tvetydigt at have 'del' stående alene. +msgid "Split" +msgstr "Opdel" + +#: src/apprt/gtk/ui/1.2/surface.blp:294 src/apprt/gtk/ui/1.5/window.blp:245 +msgid "Change Title…" +msgstr "Skift titel…" + +#: src/apprt/gtk/ui/1.2/surface.blp:299 src/apprt/gtk/ui/1.5/window.blp:177 +#: src/apprt/gtk/ui/1.5/window.blp:250 +msgid "Split Up" +msgstr "Del opad" + +#: src/apprt/gtk/ui/1.2/surface.blp:305 src/apprt/gtk/ui/1.5/window.blp:182 +#: src/apprt/gtk/ui/1.5/window.blp:255 +msgid "Split Down" +msgstr "Del nedad" + +#: src/apprt/gtk/ui/1.2/surface.blp:311 src/apprt/gtk/ui/1.5/window.blp:187 +#: src/apprt/gtk/ui/1.5/window.blp:260 +msgid "Split Left" +msgstr "Del mod venstre" + +#: src/apprt/gtk/ui/1.2/surface.blp:317 src/apprt/gtk/ui/1.5/window.blp:192 +#: src/apprt/gtk/ui/1.5/window.blp:265 +msgid "Split Right" +msgstr "Del mod højre" + +#: src/apprt/gtk/ui/1.2/surface.blp:323 +msgid "Close Split" +msgstr "Luk deling" + +#: src/apprt/gtk/ui/1.2/surface.blp:329 +msgid "Tab" +msgstr "Fane" + +#: src/apprt/gtk/ui/1.2/surface.blp:332 src/apprt/gtk/ui/1.5/window.blp:224 +#: src/apprt/gtk/ui/1.5/window.blp:320 +msgid "Change Tab Title…" +msgstr "Skift fanetitel…" + +#: src/apprt/gtk/ui/1.2/surface.blp:337 src/apprt/gtk/ui/1.5/window.blp:57 +#: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 +msgid "New Tab" +msgstr "Ny fane" + +#: src/apprt/gtk/ui/1.2/surface.blp:342 src/apprt/gtk/ui/1.5/window.blp:234 +msgid "Close Tab" +msgstr "Luk fane" + +#: src/apprt/gtk/ui/1.2/surface.blp:349 +msgid "Window" +msgstr "Vindue" + +#: src/apprt/gtk/ui/1.2/surface.blp:352 src/apprt/gtk/ui/1.5/window.blp:212 +msgid "New Window" +msgstr "Nyt vindue" + +#: src/apprt/gtk/ui/1.2/surface.blp:357 src/apprt/gtk/ui/1.5/window.blp:217 +msgid "Close Window" +msgstr "Luk vindue" + +#: src/apprt/gtk/ui/1.2/surface.blp:365 +msgid "Config" +msgstr "Konfiguration" + +#: src/apprt/gtk/ui/1.2/surface.blp:368 src/apprt/gtk/ui/1.5/window.blp:295 +msgid "Open Configuration" +msgstr "Åben konfigurering" + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:5 +msgid "Leave blank to restore the default title." +msgstr "Lad feltet stå tomt for at gendanne standardtitelen." + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:9 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/window.blp:58 src/apprt/gtk/ui/1.5/window.blp:108 +# Her bruges 'opdeling' igen, da jeg tror at 'deling' vil være for tvetydigt +msgid "New Split" +msgstr "Ny opdeling" + +#: src/apprt/gtk/ui/1.5/window.blp:68 src/apprt/gtk/ui/1.5/window.blp:126 +msgid "View Open Tabs" +msgstr "Vis åbne faner" + +#: src/apprt/gtk/ui/1.5/window.blp:78 src/apprt/gtk/ui/1.5/window.blp:140 +msgid "Main Menu" +msgstr "Hovedmenu" + +#: src/apprt/gtk/ui/1.5/window.blp:285 +msgid "Command Palette" +msgstr "Kommandopalet" + +#: src/apprt/gtk/ui/1.5/window.blp:290 +msgid "Terminal Inspector" +msgstr "Terminalinspektør" + +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1866 +msgid "About Ghostty" +msgstr "Om Ghostty" + +#: src/apprt/gtk/ui/1.5/window.blp:312 +msgid "Quit" +msgstr "Luk ned" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:17 +msgid "Execute a command…" +msgstr "Eksekver en kommando…" + +#: src/apprt/gtk/class/application.zig:1708 +msgid "The keybind was revoked by the system." +msgstr "Tastaturgenvejen blev ophævet af systemet." + +#: src/apprt/gtk/class/application.zig:1710 +msgid "The keybind was denied by the system." +msgstr "Tastaturgenvejen blev afvist af systemet." + +#: src/apprt/gtk/class/application.zig:1721 +msgid "Global keybind unavailable" +msgstr "Global tastaturgenvej utilgængelig" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Et program prøver at skrive til udklipsholderen. Indholdet af den " +"nuværende udklipsholder er vist nedenfor." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:202 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Et program prøver at læse fra udklipsholderen. Indholdet af den " +"nuværende udklipsholder er vist nedenfor." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:205 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Advarsel: Potentielt usikker indsætning" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:206 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Det kan være farligt at indsætte denne tekst i terminalen, da det ligner " +"at en eller flere kommandoer vil blive eksekveret." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:184 +msgid "Quit Ghostty?" +msgstr "Luk ned for Ghostty?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:185 +msgid "Close Tab?" +msgstr "Luk fane?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:186 +msgid "Close Window?" +msgstr "Luk vindue?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:187 +# Her bruges opdeling igen da 'deling' kunne være tvetydigt +msgid "Close Split?" +msgstr "Luk opdeling?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:193 +msgid "All terminal sessions will be terminated." +msgstr "Alle terminalsessioner vil blive afsluttet." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:194 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Alle terminalsessioner i denne fane vil blive afsluttet." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:195 +msgid "All terminal sessions in this window will be terminated." +msgstr "Alle terminalsessioner i dette vindue vil blive afsluttet." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:196 +msgid "The currently running process in this split will be terminated." +msgstr "Den kørende proces i denne opdeling vil blive afsluttet." + +#: src/apprt/gtk/class/surface.zig:1170 +msgid "Command Finished" +msgstr "Kommandoen er færdig" + +#: src/apprt/gtk/class/surface.zig:1171 +msgid "Command Succeeded" +msgstr "Kommandoen lykkedes" + +#: src/apprt/gtk/class/surface.zig:1172 +msgid "Command Failed" +msgstr "Kommandoen fejlede" + +#: src/apprt/gtk/class/surface_child_exited.zig:109 +msgid "Command succeeded" +msgstr "Kommandoen lykkedes" + +#: src/apprt/gtk/class/surface_child_exited.zig:113 +msgid "Command failed" +msgstr "Kommandoen fejlede" + +#: src/apprt/gtk/class/title_dialog.zig:225 +msgid "Change Terminal Title" +msgstr "Skift titlen på terminalen" + +#: src/apprt/gtk/class/title_dialog.zig:226 +msgid "Change Tab Title" +msgstr "Skift titlen på fanen" + +#: src/apprt/gtk/class/window.zig:1102 +msgid "Reloaded the configuration" +msgstr "Genindlæste konfigurationen" + +#: src/apprt/gtk/class/window.zig:1705 +msgid "Copied to clipboard" +msgstr "Kopieret til udklipsholder" + +#: src/apprt/gtk/class/window.zig:1707 +msgid "Cleared clipboard" +msgstr "Ryddet udklipsholder" + +#: src/apprt/gtk/class/window.zig:1847 +msgid "Ghostty Developers" +msgstr "Ghostty udviklerne" From bd381c4a524934f8ada8eae6accae16a484171e9 Mon Sep 17 00:00:00 2001 From: Carl Villads Priisholm <157688211+carlvillads@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:21:49 +0200 Subject: [PATCH 003/392] Update po/da.po MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Roed Schøler --- po/da.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/da.po b/po/da.po index e141421fc..c8fb58f21 100644 --- a/po/da.po +++ b/po/da.po @@ -44,7 +44,7 @@ msgstr "Husk mit valg for denne deling" #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 msgid "Reload configuration to show this prompt again" -msgstr "Genindlæs konfigurationen for at se denne prompt igen" +msgstr "Genindlæs konfigurationen for at se denne dialog igen" #: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 #: src/apprt/gtk/ui/1.5/title-dialog.blp:8 From ca5b3dc77bf4421f2d4784318d7431c7658468ef Mon Sep 17 00:00:00 2001 From: Carl Villads Priisholm <157688211+carlvillads@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:21:59 +0200 Subject: [PATCH 004/392] Update po/da.po MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Roed Schøler --- po/da.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/da.po b/po/da.po index c8fb58f21..55158a4a4 100644 --- a/po/da.po +++ b/po/da.po @@ -83,7 +83,7 @@ msgstr "Genindlæs konfiguration" msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ Du kører et debug build af Ghostty! Dette medfører en sænket ydeevne." +"⚠️ Du kører et debug build af Ghostty! Dette medfører en nedsat ydeevne." #: src/apprt/gtk/ui/1.5/inspector-window.blp:5 msgid "Ghostty: Terminal Inspector" From ac4dc12056c137378da49152b806edc01ba730d6 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 4 Aug 2026 09:00:14 -0400 Subject: [PATCH 005/392] terminal: size dynamic tabstops by bits Dynamic tabstop storage treated the number of columns above the inline capacity as a byte count even though each byte stores eight stops. This was wasteful, although in practice this is in the order of just bytes. Round the extra column count up to whole storage units and grow the existing slice with realloc, preserve existing stops. --- src/terminal/Tabstops.zig | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/terminal/Tabstops.zig b/src/terminal/Tabstops.zig index 8955447f2..7e74bc961 100644 --- a/src/terminal/Tabstops.zig +++ b/src/terminal/Tabstops.zig @@ -14,7 +14,6 @@ const tripwire = @import("../tripwire.zig"); const Allocator = std.mem.Allocator; const testing = std.testing; const assert = @import("../quirks.zig").inlineAssert; -const fastmem = @import("../fastmem.zig"); /// Unit is the type we use per tabstop unit (see file docs). const Unit = u8; @@ -138,24 +137,21 @@ pub fn resize( return; } - // What we need in the dynamic size - const size = cols - prealloc_columns; - if (size <= self.dynamic_stops.len) { + // Number of units needed beyond the preallocated columns. + const dynamic_count = std.math.divCeil( + usize, + cols - prealloc_columns, + unit_bits, + ) catch unreachable; + if (dynamic_count <= self.dynamic_stops.len) { self.cols = cols; return; } - // Note: we can probably try to realloc here but I'm not sure it matters. try tw.check(.dynamic_alloc); - const new = try alloc.alloc(Unit, size); - errdefer comptime unreachable; - @memset(new, 0); - if (self.dynamic_stops.len > 0) { - fastmem.copy(Unit, new, self.dynamic_stops); - alloc.free(self.dynamic_stops); - } - - self.dynamic_stops = new; + const old_len = self.dynamic_stops.len; + self.dynamic_stops = try alloc.realloc(self.dynamic_stops, dynamic_count); + @memset(self.dynamic_stops[old_len..], 0); self.cols = cols; } @@ -208,21 +204,27 @@ test "Tabstops: dynamic allocations" { var t: Tabstops = .{}; defer t.deinit(testing.allocator); - // Grow the capacity by 2. + // Grow by less than one unit to verify the allocation rounds up. const cap = t.capacity(); - try t.resize(testing.allocator, cap * 2); + try t.resize(testing.allocator, cap + 5); + try testing.expectEqual(cap + unit_bits, t.capacity()); // Set something that was out of range of the first - t.set(cap + 5); - try testing.expect(t.get(cap + 5)); - try testing.expect(!t.get(cap + 4)); + t.set(cap + 4); + try testing.expect(t.get(cap + 4)); + try testing.expect(!t.get(cap + 3)); + + // Growing again preserves existing stops and clears the new unit. + try t.resize(testing.allocator, cap + unit_bits + 1); + try testing.expect(t.get(cap + 4)); + try testing.expect(!t.get(cap + unit_bits)); // Prealloc still works try testing.expect(!t.get(5)); } test "Tabstops: resize to existing capacity does not allocate" { - var backing: [prealloc_columns]Unit = undefined; + var backing: [prealloc_count]Unit = undefined; var fixed = std.heap.FixedBufferAllocator.init(&backing); var t: Tabstops = .{}; From b104a3f0b3f7e021df5a773f68d5ff6130a8ce64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=92riks=20Remess?= Date: Tue, 4 Aug 2026 17:36:36 +0300 Subject: [PATCH 006/392] i18n - Latvian translation of command palette, typo fixes and more natural translations --- po/lv.po | 356 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 187 insertions(+), 169 deletions(-) diff --git a/po/lv.po b/po/lv.po index d7eb8bb3b..dfca25559 100644 --- a/po/lv.po +++ b/po/lv.po @@ -8,10 +8,10 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-09 03:24+0200\n" +"PO-Revision-Date: 2026-08-04 17:02+0300\n" "Last-Translator: Ēriks Remess \n" "Language-Team: Latvian\n" -"Language: LV\n" +"Language: lv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -40,11 +40,11 @@ msgstr "Atļaut" #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92 msgid "Remember choice for this split" -msgstr "Atcerēties izvēli šim sadalījumam" +msgstr "Atcerēties izvēli šim nodalījumam" #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 msgid "Reload configuration to show this prompt again" -msgstr "Pārlādējiet konfigurāciju, lai šo uzvedni rādītu atkal" +msgstr "Lai šo uzvedni parādītu vēlreiz, pārlādējiet konfigurāciju" #: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 #: src/apprt/gtk/ui/1.5/title-dialog.blp:8 @@ -172,7 +172,7 @@ msgstr "Sadalīt pa labi" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Aizvērt nodalījumu" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -226,7 +226,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." -msgstr "Atstāj tukšu, lai atjaunotu noklusēto virsrakstu." +msgstr "Atstājiet lauku tukšu, lai atjaunotu noklusēto virsrakstu." #: src/apprt/gtk/ui/1.5/title-dialog.blp:9 msgid "OK" @@ -234,7 +234,7 @@ msgstr "Labi" #: src/apprt/gtk/ui/1.5/window.blp:61 src/apprt/gtk/ui/1.5/window.blp:111 msgid "New Split" -msgstr "Jauns sadalījums" +msgstr "Jauns nodalījums" #: src/apprt/gtk/ui/1.5/window.blp:71 src/apprt/gtk/ui/1.5/window.blp:129 msgid "View Open Tabs" @@ -266,23 +266,23 @@ msgstr "Izpildīt komandu…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Sistēma atsauca taustiņu sasaisti." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Sistēma noraidīja taustiņu sasaisti." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Globālā taustiņu sasaiste nav pieejama" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Eksportēt termināļa ievades un izvades notikumus" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Eksportēt" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" @@ -313,8 +313,8 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Šī teksta ielīmēšana terminālī var būt bīstama, jo izskatās, ka var tikt " -"izpildītas komandas." +"Šī teksta ielīmēšana terminālī var būt bīstama, jo tas, iespējams, satur " +"izpildāmas komandas." #: src/apprt/gtk/class/close_confirmation_dialog.zig:184 msgid "Quit Ghostty?" @@ -330,7 +330,7 @@ msgstr "Aizvērt logu?" #: src/apprt/gtk/class/close_confirmation_dialog.zig:187 msgid "Close Split?" -msgstr "Aizvērt sadalījumu?" +msgstr "Aizvērt nodalījumu?" #: src/apprt/gtk/class/close_confirmation_dialog.zig:193 msgid "All terminal sessions will be terminated." @@ -346,7 +346,7 @@ msgstr "Visas termināļa sesijas šajā logā tiks pārtrauktas." #: src/apprt/gtk/class/close_confirmation_dialog.zig:196 msgid "The currently running process in this split will be terminated." -msgstr "Pašlaik palaistais process šajā sadalījumā tiks pārtraukts." +msgstr "Pašlaik palaistais process šajā nodalījumā tiks pārtraukts." #: src/apprt/gtk/class/surface.zig:1181 msgid "Command Finished" @@ -392,393 +392,409 @@ msgstr "Ghostty izstrādātāji" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Atiestatīt termināli" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Atiestatīt termināli tīrā stāvoklī." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Kopēt starpliktuvē" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Kopēt atlasīto tekstu starpliktuvē gan kā vienkāršu tekstu, gan ar " +"formatējumu." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Kopēt atlasi starpliktuvē kā vienkāršu tekstu" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Kopēt atlasīto tekstu starpliktuvē kā vienkāršu tekstu." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Kopēt atlasi starpliktuvē kā ANSI sekvences" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Kopēt atlasīto tekstu starpliktuvē kā ANSI vadības sekvences." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Kopēt atlasi starpliktuvē kā HTML" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Kopēt atlasīto tekstu starpliktuvē kā HTML." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Kopēt URL starpliktuvē" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Kopēt zem kursora esošo URL starpliktuvē." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Kopēt termināļa virsrakstu starpliktuvē" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Kopēt termināļa virsrakstu starpliktuvē. Ja termināļa virsraksts nav " +"iestatīts, šī darbība neko nemaina." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Ielīmēt no starpliktuves" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Ielīmēt galvenās starpliktuves saturu." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Ielīmēt no atlases" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Ielīmēt atlases starpliktuves saturu." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Sākt meklēšanu" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Sākt meklēšanu, ja tā vēl nav aktīva." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Meklēt atlasīto tekstu" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Sākt meklēt pašreiz atlasīto tekstu." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Beigt meklēšanu" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Beigt pašreizējo meklēšanu, ja tāda ir, un paslēpt GUI elementus." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Nākamais meklēšanas rezultāts" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Pāriet uz nākamo meklēšanas rezultātu, ja tāds ir." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Iepriekšējais meklēšanas rezultāts" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Pāriet uz iepriekšējo meklēšanas rezultātu, ja tāds ir." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Palielināt fonta izmēru" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Palielināt fonta izmēru par 1 punktu." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Samazināt fonta izmēru" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Samazināt fonta izmēru par 1 punktu." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Atiestatīt fonta izmēru" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Atiestatīt fonta izmēru uz noklusēto." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Notīrīt ekrānu" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Notīrīt ekrānu un ritināšanas vēsturi." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Atlasīt visu" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Atlasīt visu ekrānā redzamo tekstu." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Ritināt līdz sākumam" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Ritināt līdz ekrāna sākumam." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Ritināt līdz beigām" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Ritināt līdz ekrāna beigām." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Ritināt līdz atlasei" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Ritināt līdz atlasītajam tekstam." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Ritināt lapu augšup" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Ritināt ekrānu vienu lapu augšup." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Ritināt lapu lejup" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Ritināt ekrānu vienu lapu lejup." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt ekrānu pagaidu failā un kopēt tā ceļu" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Kopēt ekrāna saturu pagaidu failā un kopēt faila ceļu starpliktuvē." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt ekrānu pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "Kopēt ekrāna saturu pagaidu failā un ielīmēt faila ceļu." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Kopēt ekrānu pagaidu failā un atvērt" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Kopēt ekrāna saturu pagaidu failā un atvērt to." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt ekrānu kā HTML pagaidu failā un kopēt ceļu" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Kopēt ekrāna saturu kā HTML pagaidu failā un kopēt faila ceļu starpliktuvē." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt ekrānu kā HTML pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "Kopēt ekrāna saturu kā HTML pagaidu failā un ielīmēt faila ceļu." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Kopēt ekrānu kā HTML pagaidu failā un atvērt" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Kopēt ekrāna saturu kā HTML pagaidu failā un atvērt to." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt ekrānu kā ANSI sekvences pagaidu failā un kopēt ceļu" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Kopēt ekrāna saturu kā ANSI vadības sekvences pagaidu failā un kopēt faila " +"ceļu starpliktuvē." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt ekrānu kā ANSI sekvences pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Kopēt ekrāna saturu kā ANSI vadības sekvences pagaidu failā un ielīmēt faila " +"ceļu." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Kopēt ekrānu kā ANSI sekvences pagaidu failā un atvērt" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Kopēt ekrāna saturu kā ANSI vadības sekvences pagaidu failā un atvērt to." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt atlasi pagaidu failā un kopēt ceļu" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Kopēt atlases saturu pagaidu failā un kopēt faila ceļu starpliktuvē." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt atlasi pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "Kopēt atlases saturu pagaidu failā un ielīmēt faila ceļu." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Kopēt atlasi pagaidu failā un atvērt" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Kopēt atlases saturu pagaidu failā un atvērt to." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt atlasi kā HTML pagaidu failā un kopēt ceļu" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Kopēt atlases saturu kā HTML pagaidu failā un kopēt faila ceļu starpliktuvē." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt atlasi kā HTML pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "Kopēt atlases saturu kā HTML pagaidu failā un ielīmēt faila ceļu." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Kopēt atlasi kā HTML pagaidu failā un atvērt" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Kopēt atlases saturu kā HTML pagaidu failā un atvērt to." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Kopēt atlasi kā ANSI sekvences pagaidu failā un kopēt ceļu" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Kopēt atlases saturu kā ANSI vadības sekvences pagaidu failā un kopēt faila " +"ceļu starpliktuvē." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Kopēt atlasi kā ANSI sekvences pagaidu failā un ielīmēt ceļu" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Kopēt atlases saturu kā ANSI vadības sekvences pagaidu failā un ielīmēt " +"faila ceļu." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Kopēt atlasi kā ANSI sekvences pagaidu failā un atvērt" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Kopēt atlases saturu kā ANSI vadības sekvences pagaidu failā un atvērt to." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Atvērt jaunu logu." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Atvērt jaunu cilni." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Pārvietot cilni pa kreisi" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Pārvietot pašreizējo cilni pa kreisi." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Pārvietot cilni pa labi" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Pārvietot pašreizējo cilni pa labi." #: src/input/command.zig:446 msgid "Move Tab to New Window" @@ -790,159 +806,159 @@ msgstr "" #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Parādīt vai paslēpt ciļņu pārskatu" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Parādīt vai paslēpt ciļņu pārskatu." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Mainīt termināļa virsrakstu…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Parādīt aicinājumu ievadīt jaunu virsrakstu pašreizējam terminālim." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Parādīt aicinājumu ievadīt jaunu virsrakstu pašreizējai cilnei." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Sadalīt termināli pa kreisi." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Sadalīt termināli pa labi." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Sadalīt termināli augšup." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Sadalīt termināli lejup." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Fokusēt iepriekšējo nodalījumu" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Fokusēt iepriekšējo nodalījumu, ja tāds ir." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Fokusēt nākamo nodalījumu" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Fokusēt nākamo nodalījumu, ja tāds ir." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Fokusēt nodalījumu pa kreisi" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Fokusēt nodalījumu pa kreisi, ja tāds ir." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Fokusēt nodalījumu pa labi" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Fokusēt nodalījumu pa labi, ja tāds ir." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Fokusēt augšējo nodalījumu" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Fokusēt augšējo nodalījumu, ja tāds ir." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Fokusēt apakšējo nodalījumu" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Fokusēt apakšējo nodalījumu, ja tāds ir." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Fokusēt iepriekšējo logu" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Fokusēt iepriekšējo logu, ja tāds ir." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Fokusēt nākamo logu" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Fokusēt nākamo logu, ja tāds ir." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Palielināt nodalījumu vai atjaunot tā izmēru" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Palielināt pašreizējo nodalījumu vai atjaunot tā izmēru." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Ieslēgt vai izslēgt tikai lasīšanas režīmu" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Ieslēgt vai izslēgt tikai lasīšanas režīmu pašreizējā terminālī." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Izlīdzināt nodalījumu izmērus" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Izlīdzināt visu nodalījumu izmērus." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Atiestatīt loga izmēru" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Atiestatīt loga izmēru uz noklusēto." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Parādīt vai paslēpt inspektoru" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Parādīt vai paslēpt inspektoru." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Rādīt GTK inspektoru" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Rādīt GTK inspektoru." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Rādīt ekrāna tastatūru" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Rādīt ekrāna tastatūru, ja tā ir pieejama." #: src/input/command.zig:588 msgid "Open Config Using OS editor" @@ -962,140 +978,142 @@ msgstr "" #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Pārlādēt konfigurāciju" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Pārlādēt konfigurācijas failu." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Aizvērt termināli" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Aizvērt pašreizējo termināli." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Aizvērt pašreizējo cilni." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Aizvērt citas cilnes" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Aizvērt visas cilnes šajā logā, izņemot pašreizējo." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Aizvērt cilnes pa labi" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Aizvērt visas cilnes pa labi no pašreizējās." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Aizvērt pašreizējo logu." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Aizvērt visus logus" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Aizvērt visus logus." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Maksimizēt vai atjaunot logu" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Maksimizēt pašreizējo logu vai atjaunot tā iepriekšējo izmēru." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Ieslēgt vai izslēgt pilnekrāna režīmu" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Ieslēgt vai izslēgt pilnekrāna režīmu pašreizējam logam." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Parādīt vai paslēpt loga apdari" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Parādīt vai paslēpt loga apdari." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Ieslēgt vai izslēgt rādīšanu virspusē" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Ieslēgt vai izslēgt pašreizējā loga rādīšanu virs citiem logiem." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Ieslēgt vai izslēgt drošo ievadi" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Ieslēgt vai izslēgt drošās ievades režīmu." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Ieslēgt vai izslēgt peles notikumu nosūtīšanu" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Ieslēgt vai izslēgt peles notikumu nosūtīšanu termināļa lietotnēm." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Ieslēgt vai izslēgt fona caurspīdīgumu" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Ieslēgt vai izslēgt fona caurspīdīgumu logam, kas tika atvērts ar " +"caurspīdīgu fonu." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Pārbaudīt atjauninājumus" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Pārbaudīt lietotnes atjauninājumus." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Atsaukt" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Atsaukt pēdējo darbību." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Atkārtot" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Atkārtot pēdējo atsaukto darbību." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Iziet no lietotnes." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Ielaid mazu Ghostty savā terminālī." From b3514d56210e8a57a480fb4d0c82121e86df28e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=92riks=20Remess?= Date: Mon, 10 Aug 2026 17:36:30 +0300 Subject: [PATCH 007/392] i18n: Latvian translation. Additional strings. --- po/lv.po | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/po/lv.po b/po/lv.po index dfca25559..450130957 100644 --- a/po/lv.po +++ b/po/lv.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-08-04 17:02+0300\n" +"PO-Revision-Date: 2026-08-10 17:32+0300\n" "Last-Translator: Ēriks Remess \n" "Language-Team: Latvian\n" "Language: lv\n" @@ -218,11 +218,11 @@ msgstr "Konfigurācija" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Atvērt konfigurāciju noklusējuma redaktorā" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Atvērt konfigurāciju jaunā logā" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -286,7 +286,7 @@ msgstr "Eksportēt" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Konfigurācijas faila rediģēšana:" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -798,11 +798,11 @@ msgstr "Pārvietot pašreizējo cilni pa labi." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Pārvietot cilni uz jaunu logu" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Pārvietot pašreizējo cilni uz jaunu logu." #: src/input/command.zig:452 msgid "Toggle Tab Overview" @@ -962,19 +962,21 @@ msgstr "Rādīt ekrāna tastatūru, ja tā ir pieejama." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Atvērt konfigurāciju noklusējuma redaktorā" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Atvērt konfigurācijas failu noklusējuma redaktorā." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Atvērt konfigurāciju jaunā termināļa logā" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Atvērt konfigurācijas failu jaunā logā, izmantojot mainīgajā $EDITOR vai " +"$VISUAL norādīto redaktoru." #: src/input/command.zig:600 msgid "Reload Config" From 491806fbeb35b9153838085d6cf615cb977546e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=92riks=20Remess?= Date: Wed, 12 Aug 2026 22:14:43 +0300 Subject: [PATCH 008/392] i18n(lv): last two missing translations --- po/lv.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/lv.po b/po/lv.po index 450130957..7fda2157d 100644 --- a/po/lv.po +++ b/po/lv.po @@ -200,7 +200,7 @@ msgstr "Logs" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Mainīt loga virsrakstu…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -372,7 +372,7 @@ msgstr "Mainīt cilnes virsrakstu" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Mainīt loga virsrakstu" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" From e2662d7b63d4aee02f31164c96c180ebed8ddf0b Mon Sep 17 00:00:00 2001 From: Ilia Kravchenko Date: Wed, 12 Aug 2026 19:30:15 -0300 Subject: [PATCH 009/392] i18n: translate v1.4 command palette and remaining strings (be) --- po/be.po | 362 +++++++++++++++++++++++++++---------------------------- 1 file changed, 181 insertions(+), 181 deletions(-) diff --git a/po/be.po b/po/be.po index bc2dd6e97..bc3ab8582 100644 --- a/po/be.po +++ b/po/be.po @@ -175,7 +175,7 @@ msgstr "Падзяліць управа" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Закрыць падзел" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,7 +203,7 @@ msgstr "Акно" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Змяніць назву акна…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +221,11 @@ msgstr "Налады" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Адкрыць канфігурацыю ў сістэмным рэдактары" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Адкрыць канфігурацыю ў новым акне" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -269,27 +269,27 @@ msgstr "Выканаць каманду…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Спалучэнне клавіш было адкліканае сістэмай." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Спалучэнне клавіш было адхілена сістэмай." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Глабальнае спалучэнне клавіш недаступнае" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Экспартаваць падзеі ўводу-вываду тэрмінала" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Экспартаваць" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Рэдагаванне файла канфігурацыі" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -375,7 +375,7 @@ msgstr "Змяніць назву ўкладкі" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Змяніць назву акна" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -395,710 +395,710 @@ msgstr "Распрацоўшчыкі Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Скінуць тэрмінал" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Скінуць тэрмінал да чыстага стану." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Скапіяваць у буфер абмену" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "" +msgstr "Скапіяваць вылучаны тэкст у буфер абмену ў звычайным і фарматаваным выглядзе." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Скапіяваць вылучэнне як просты тэкст у буфер абмену" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Скапіяваць вылучаны тэкст як просты тэкст у буфер абмену." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Скапіяваць вылучэнне як ANSI-паслядоўнасці ў буфер абмену" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Скапіяваць вылучаны тэкст як кіруючыя паслядоўнасці ANSI у буфер абмену." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Скапіяваць вылучэнне як HTML у буфер абмену" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Скапіяваць вылучаны тэкст як HTML у буфер абмену." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Скапіяваць URL у буфер абмену" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Скапіяваць URL пад курсорам у буфер абмену." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Скапіяваць назву тэрмінала ў буфер абмену" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "" +msgstr "Скапіяваць назву тэрмінала ў буфер абмену. Калі назва тэрмінала не ўстаноўлена, дзеянне не мае эфекту." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Уставіць з буфера абмену" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Уставіць змест асноўнага буфера абмену." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Уставіць з вылучэння" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Уставіць змест буфера абмену вылучэння." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Пачаць пошук" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Пачаць пошук, калі ён яшчэ не актыўны." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Пошук па вылучэнні" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Пачаць пошук па бягучым вылучаным тэксце." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Скончыць пошук" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Скончыць бягучы пошук, калі ён ёсць, і схаваць элементы інтэрфейсу." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Наступны вынік пошуку" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Перайсці да наступнага выніку пошуку, калі ён ёсць." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Папярэдні вынік пошуку" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Перайсці да папярэдняга выніку пошуку, калі ён ёсць." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Павялічыць памер шрыфту" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Павялічыць памер шрыфту на 1 пункт." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Паменшыць памер шрыфту" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Паменшыць памер шрыфту на 1 пункт." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Скінуць памер шрыфту" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Скінуць памер шрыфту да прадвызначанага." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Ачысціць экран" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Ачысціць экран і гісторыю пракруткі." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Вылучыць усё" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Вылучыць увесь тэкст на экране." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Пракруціць да пачатку" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Пракруціць да пачатку экрана." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Пракруціць да канца" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Пракруціць да канца экрана." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Пракруціць да вылучэння" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Пракруціць да вылучанага тэксту." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Пракруціць старонку ўверх" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Пракруціць экран уверх на адну старонку." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Пракруціць старонку ўніз" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Пракруціць экран уніз на адну старонку." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць экран у часовы файл і скапіяваць шлях" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Скапіяваць змест экрана ў часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць экран у часовы файл і ўставіць шлях" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "Скапіяваць змест экрана ў часовы файл і ўставіць шлях да яго." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць экран у часовы файл і адкрыць" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Скапіяваць змест экрана ў часовы файл і адкрыць яго." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць экран як HTML у часовы файл і скапіяваць шлях" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "Скапіяваць змест экрана як HTML у часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць экран як HTML у часовы файл і ўставіць шлях" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "Скапіяваць змест экрана як HTML у часовы файл і ўставіць шлях да яго." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць экран як HTML у часовы файл і адкрыць" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Скапіяваць змест экрана як HTML у часовы файл і адкрыць яго." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць экран як ANSI-паслядоўнасці ў часовы файл і скапіяваць шлях" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "Скапіяваць змест экрана як кіруючыя паслядоўнасці ANSI у часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць экран як ANSI-паслядоўнасці ў часовы файл і ўставіць шлях" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "Скапіяваць змест экрана як кіруючыя паслядоўнасці ANSI у часовы файл і ўставіць шлях да яго." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць экран як ANSI-паслядоўнасці ў часовы файл і адкрыць" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "Скапіяваць змест экрана як кіруючыя паслядоўнасці ANSI у часовы файл і адкрыць яго." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць вылучэнне ў часовы файл і скапіяваць шлях" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Скапіяваць змест вылучэння ў часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць вылучэнне ў часовы файл і ўставіць шлях" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "Скапіяваць змест вылучэння ў часовы файл і ўставіць шлях да яго." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць вылучэнне ў часовы файл і адкрыць" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Скапіяваць змест вылучэння ў часовы файл і адкрыць яго." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць вылучэнне як HTML у часовы файл і скапіяваць шлях" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "Скапіяваць змест вылучэння як HTML у часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць вылучэнне як HTML у часовы файл і ўставіць шлях" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "Скапіяваць змест вылучэння як HTML у часовы файл і ўставіць шлях да яго." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць вылучэнне як HTML у часовы файл і адкрыць" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Скапіяваць змест вылучэння як HTML у часовы файл і адкрыць яго." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Скапіяваць вылучэнне як ANSI-паслядоўнасці ў часовы файл і скапіяваць шлях" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "Скапіяваць змест вылучэння як кіруючыя паслядоўнасці ANSI у часовы файл і скапіяваць шлях у буфер абмену." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Скапіяваць вылучэнне як ANSI-паслядоўнасці ў часовы файл і ўставіць шлях" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "Скапіяваць змест вылучэння як кіруючыя паслядоўнасці ANSI у часовы файл і ўставіць шлях да яго." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Скапіяваць вылучэнне як ANSI-паслядоўнасці ў часовы файл і адкрыць" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "Скапіяваць змест вылучэння як кіруючыя паслядоўнасці ANSI у часовы файл і адкрыць яго." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Адкрыць новае акно." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Адкрыць новую ўкладку." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Перамясціць укладку ўлева" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Перамясціць бягучую ўкладку ўлева." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Перамясціць укладку ўправа" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Перамясціць бягучую ўкладку ўправа." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Перамясціць укладку ў новае акно" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Перамясціць бягучую ўкладку ў новае акно." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Пераключыць агляд укладак" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Пераключыць агляд укладак." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Змяніць назву тэрмінала…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Запытаць новую назву для бягучага тэрмінала." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Запытаць новую назву для бягучай укладкі." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Падзяліць тэрмінал улева." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Падзяліць тэрмінал управа." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Падзяліць тэрмінал уверх." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Падзяліць тэрмінал уніз." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Фокус падзела: папярэдні" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Перавесці фокус на папярэдні падзел, калі ён ёсць." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Фокус падзела: наступны" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Перавесці фокус на наступны падзел, калі ён ёсць." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Фокус падзела: злева" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Перавесці фокус на падзел злева, калі ён існуе." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Фокус падзела: справа" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Перавесці фокус на падзел справа, калі ён існуе." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Фокус падзела: уверх" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Перавесці фокус на падзел зверху, калі ён існуе." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Фокус падзела: уніз" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Перавесці фокус на падзел знізу, калі ён існуе." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Фокус акна: папярэдняе" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Перавесці фокус на папярэдняе акно, калі яно ёсць." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Фокус акна: наступнае" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Перавесці фокус на наступнае акно, калі яно ёсць." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Пераключыць маштабаванне падзела" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Пераключыць стан маштабавання бягучага падзела." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Пераключыць рэжым толькі для чытання" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Пераключыць рэжым толькі для чытання для бягучага тэрмінала." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Выраўнаваць падзелы" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Выраўнаваць памер усіх падзелаў." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Скінуць памер акна" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Скінуць памер акна да прадвызначанага." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Пераключыць інспектар" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Пераключыць інспектар." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Паказаць інспектар GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Паказаць інспектар GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Паказаць экранную клавіятуру" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Паказаць экранную клавіятуру, калі яна даступная." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Адкрыць канфігурацыю ў сістэмным рэдактары" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Адкрыць файл канфігурацыі ў прадвызначаным рэдактары сістэмы." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Адкрыць канфігурацыю ў новым акне тэрмінала" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Адкрыць файл канфігурацыі ў новым акне з дапамогай $EDITOR або $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Перазагрузіць канфігурацыю" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Перазагрузіць файл канфігурацыі." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Закрыць тэрмінал" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Закрыць бягучы тэрмінал." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Закрыць бягучую ўкладку." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Закрыць іншыя ўкладкі" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Закрыць усе ўкладкі ў гэтым акне, акрамя бягучай." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Закрыць укладкі справа" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Закрыць усе ўкладкі справа ад бягучай." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Закрыць бягучае акно." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Закрыць усе вокны" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Закрыць усе вокны." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Пераключыць разгорнуты стан" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Пераключыць разгорнуты стан бягучага акна." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Пераключыць поўнаэкранны рэжым" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Пераключыць поўнаэкранны рэжым бягучага акна." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Пераключыць аздабленне акна" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Пераключыць аздабленне акна." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Пераключыць рэжым «заўсёды зверху»" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Пераключыць стан «заўсёды зверху» бягучага акна." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Пераключыць бяспечны ўвод" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Пераключыць рэжым бяспечнага ўводу." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Пераключыць перадачу падзей мышы" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Пераключыць, ці перадаюцца падзеі мышы праграмам тэрмінала." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Пераключыць непразрыстасць фону" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "Пераключыць непразрыстасць фону акна, якое было запушчана празрыстым." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Праверыць абнаўленні" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Праверыць наяўнасць абнаўленняў праграмы." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Адмяніць" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Адмяніць апошняе дзеянне." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Паўтарыць" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Паўтарыць апошняе адмененае дзеянне." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Выйсці з праграмы." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Дадайце трохі Ghostty ў свой тэрмінал." From 4bb135e2de19b2ce8bddec77ba964ddaebbadc1e Mon Sep 17 00:00:00 2001 From: derVedro Date: Thu, 13 Aug 2026 05:19:19 +0200 Subject: [PATCH 010/392] i18n(ru): start updating Russian translation --- po/ru.po | 293 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 168 insertions(+), 125 deletions(-) diff --git a/po/ru.po b/po/ru.po index b764e5fd5..ae2cc263b 100644 --- a/po/ru.po +++ b/po/ru.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2025-02-18 10:20+0100\n" -"Last-Translator: Ivan Bastrakov \n" +"PO-Revision-Date: 2026-08-12 23:12+0100\n" +"Last-Translator: derVedro \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" @@ -177,7 +177,7 @@ msgstr "Сплит вправо" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Закрыть сплит" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,9 +203,10 @@ msgstr "Закрыть вкладку" msgid "Window" msgstr "Окно" +#. ლʕಠᴥಠʔლ или же "Переименовать заголовок окна…" ? #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Переименовать окно…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -223,11 +224,11 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Открыть конфигурацию в системном редакторе" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Открыть конфигурацию в новом окне" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -251,7 +252,7 @@ msgstr "Главное меню" #: src/apprt/gtk/ui/1.5/window.blp:293 msgid "Command Palette" -msgstr "Палитра команд" +msgstr "Поиск команд" #: src/apprt/gtk/ui/1.5/window.blp:298 msgid "Terminal Inspector" @@ -269,6 +270,7 @@ msgstr "Выход" msgid "Execute a command…" msgstr "Выполнить команду…" +#. keybind - сочетание клавиш, привязка клавиш, биндинг клавиш? #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." msgstr "" @@ -287,11 +289,11 @@ msgstr "" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Экспортировать" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Редактирование файла конфигурации" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -377,7 +379,7 @@ msgstr "Переименовать вкладку" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Переименовать окно" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -397,414 +399,453 @@ msgstr "Разработчики Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Сбросить терминал" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Сбросить терминал в исходное состояние." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Копировать в буфер обмена" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Копировать выделенный текст в буфер обмена в обычном и в форматированном " +"виде." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Копировать выделение как обычный текст в буфер обмена" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Копировать выделенный текст как обычный текст в буфер обмена." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Копировать выделение как ANSI-последовательности в буфер обмена" +# ლʕಠᴥಠʔლ, escape-последовательности ANSI, экранированные ANSI? ...? #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Копировать выделенный текст как escape-последовательности ANSI в буфер обмена." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Копировать выделение как HTML в буфер обмена" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Копировать выделенный текст как HTML в буфер обмена." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Копировать URL в буфер обмена" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Копировать URL под курсором в буфер обмена." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Копировать заголовок терминала в буфер обмена" +#. ლʕಠᴥಠʔლ хммм... не очень. #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Копировать заголовок терминала в буфер обмена. Если заголовок не задан, " +"ничего не произойдёт." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Вставить из буфера обмена" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Вставить содержимое основного буфера обмена." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Вставить из выделения" +#. ლʕಠᴥಠʔლ это что за покемон? #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." msgstr "" #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Начать поиск" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Начать поиск, если он еще не активен." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Искать выделенное" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Начать поиск по текущему выделенному тексту." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Завершить поиск" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Завершить текущий поиск и скрыть элементы интерфейса." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Следующий результат поиска" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Перейти к следующему результату поиска, если он есть." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Предыдущий результат поиска" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Перейти к предыдущему результату поиска, если он есть." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Увеличить размер шрифта" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Увеличить размер шрифта на 1 пункт." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Уменьшить размер шрифта" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Уменьшить размер шрифта на 1 пункт." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Сбросить размер шрифта" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Сбросить размер шрифта до значения по умолчанию." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Очистить экран" +#. пока не ясно, что тут. #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Очистить экран и буфер прокрутки." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Выделить всё" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Выделить весь текст на экране." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Прокрутить в начало" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Прокрутить к началу экрана." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Прокрутить в конец" +#. или лучше "прокрутить на низ экрана" ? #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Прокрутить к концу экрана." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Прокрутить к выделению" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Прокрутить к выделенному тексту." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Прокрутить на страницу вверх" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Прокрутить экран на одну страницу вверх." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Прокрутить на страницу вниз" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Прокрутить экран на одну страницу вниз." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Скопировать экран во временный файл и скопировать путь" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Скопировать содержимое экрана во временный файл и скопировать путь к нему в " +"буфер обмена." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать экран во временный файл и вставить путь" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Скопировать содержимое экрана во временный файл и вставить путь к файлу." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Скопировать экран во временный файл и открыть" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Скопировать содержимое экрана во временный файл и открыть его." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Скопировать экран как HTML во временный файл и скопировать путь" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Скопировать содержимое экрана как HTML во временный файл и скопировать путь " +"к нему в буфер обмена." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать экран как HTML во временный файл и вставить путь" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Скопировать содержимое экрана как HTML во временный файл и вставить путь к " +"файлу." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Скопировать экран как HTML во временный файл и открыть" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Скопировать содержимое экрана как HTML во временный файл и открыть его." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Скопировать экран как ANSI-последовательности во временный файл и скопировать путь" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Скопировать содержимое экрана как escape-последовательности ANSI во " +"временный файл и скопировать путь к нему в буфер обмена." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать экран как ANSI-последовательности во временный файл и вставить путь" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Скопировать содержимое экрана как escape-последовательности ANSI во " +"временный файл и вставить путь к файлу." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Скопировать экран как ANSI-последовательности во временный файл и открыть" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Скопировать содержимое экрана как escape-последовательности ANSI во " +"временный файл и открыть его." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Скопировать выделение во временный файл и скопировать путь" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Скопировать содержимое выделения во временный файл и скопировать путь к " +"нему в буфер обмена." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать выделение во временный файл и вставить путь" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Скопировать содержимое выделения во временный файл и вставить путь к файлу." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Скопировать выделение во временный файл и открыть" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Скопировать содержимое выделения во временный файл и открыть его." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Скопировать выделение как HTML во временный файл и скопировать путь" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Скопировать содержимое выделения как HTML во временный файл и скопировать " +"путь к нему в буфер обмена." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать выделение как HTML во временный файл и вставить путь" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Скопировать содержимое выделения как HTML во временный файл и вставить путь " +"к файлу." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Скопировать выделение как HTML во временный файл и открыть" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Скопировать содержимое выделения как HTML во временный файл и открыть его." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Скопировать выделение как ANSI-последовательности во временный файл и " +"скопировать путь" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Скопировать содержимое выделения как escape-последовательности ANSI во " +"временный файл и скопировать путь к нему в буфер обмена." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Скопировать выделение как ANSI-последовательности во временный файл и вставить путь" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Скопировать содержимое выделения как escape-последовательности ANSI во " +"временный файл и вставить путь к файлу." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Скопировать выделение как ANSI-последовательности во временный файл и открыть" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Скопировать содержимое выделения как escape-последовательности ANSI во " +"временный файл и открыть его." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Открыть новое окно." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Открыть новую вкладку." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Переместить вкладку влево" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Переместить текущую вкладку влево." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Переместить вкладку вправо" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Переместить текущую вкладку вправо." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Переместить вкладку в новое окно" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Переместить текущую вкладку в новое окно." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Переключить обзор вкладок" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Переключить обзор вкладок." +#. "Переименовать заголовок терминала…" ? #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Переименовать терминал…" +#. тоже пока не ясно #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." msgstr "" @@ -815,20 +856,21 @@ msgstr "" #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Разделить терминал влево." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Разделить терминал вправо." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Разделить терминал вверх." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Разделить терминал вниз." +#. позже разберусь с этими фокусами, пока не ясно #: src/input/command.zig:500 msgid "Focus Split: Previous" msgstr "" @@ -893,6 +935,7 @@ msgstr "" msgid "Focus the next window, if any." msgstr "" +#. тоже пока не совсем понятно #: src/input/command.zig:545 msgid "Toggle Split Zoom" msgstr "" @@ -911,107 +954,107 @@ msgstr "" #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Выровнять сплиты" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Выровнять размер всех сплитов." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Сбросить размер окна" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Сбросить размер окна до значения по умолчанию." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Переключить инспектор" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Переключить инспектор." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Показать инспектор GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Показать инспектор GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Показать экранную клавиатуру" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Показать экранную клавиатуру, если она доступна." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Открыть конфигурацию в системном редакторе" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Открыть файл конфигурации в редакторе ОС по умолчанию." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Открыть конфигурацию в новом окне терминала" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Открыть файл конфигурации в новом окне с помощью $EDITOR или $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Перезагрузить конфигурацию" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Перезагрузить файл конфигурации." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Закрыть терминал" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Закрыть текущий терминал." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Закрыть текущую вкладку." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Закрыть другие вкладки" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Закрыть все вкладки в этом окне, кроме текущей." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Закрыть вкладки справа" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Закрыть все вкладки справа от текущей." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Закрыть текущее окно." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Закрыть все окна" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Закрыть все окна." #: src/input/command.zig:642 msgid "Toggle Maximize" @@ -1071,31 +1114,31 @@ msgstr "" #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Проверить наличие обновлений" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Проверить наличие обновлений приложения." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Отменить" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Отменить последнее действие." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Повторить" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Повторить последнее отменённое действие." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Выйти из приложения." #: src/input/command.zig:708 msgid "Ghostty" @@ -1103,4 +1146,4 @@ msgstr "" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Добавьте немного Ghostty в свой терминал." From 1d9fcdc151aedc72db1678e6e1df3425ed94939b Mon Sep 17 00:00:00 2001 From: AnmiTaliDev Date: Thu, 13 Aug 2026 12:08:24 +0500 Subject: [PATCH 011/392] i18n: complete Kazakh (kk) translation for v1.4 Translates the 180 remaining strings, mainly command palette entries introduced for v1.4 localization. --- po/kk.po | 363 +++++++++++++++++++++++++++---------------------------- 1 file changed, 181 insertions(+), 182 deletions(-) diff --git a/po/kk.po b/po/kk.po index ee9d47399..429553883 100644 --- a/po/kk.po +++ b/po/kk.po @@ -4,13 +4,12 @@ # Baurzhan Muftakhidinov , 2026. # AnmiTaliDev , 2026. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-06-20 17:07+0500\n" +"PO-Revision-Date: 2026-08-13 00:00+0500\n" "Last-Translator: AnmiTaliDev \n" "Language-Team: Kazakh \n" "Language: kk\n" @@ -205,7 +204,7 @@ msgstr "Терезе" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Терезе атауын өзгерту…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -223,11 +222,11 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Конфигурацияны ОЖ өңдегішінде ашу" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Конфигурацияны жаңа терезеде ашу" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -271,27 +270,27 @@ msgstr "Команданы орындау…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Пернелер тіркесімін жүйе қайтарып алды." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Пернелер тіркесіміне жүйе тыйым салды." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Жаһандық пернелер тіркесімі қолжетімсіз" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Терминал енгізу/шығару оқиғаларын экспорттау" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Экспорттау" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Конфигурация файлын өңдеу" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -377,7 +376,7 @@ msgstr "Бет атауын өзгерту" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Терезе атауын өзгерту" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -397,710 +396,710 @@ msgstr "Ghostty әзірлеушілері" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Терминалды қалпына келтіру" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Терминалды таза күйге келтіру." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Алмасу буферіне көшіру" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "" +msgstr "Таңдалған мәтінді жай және пішімделген түрде алмасу буферіне көшіру." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Таңдауды жай мәтін ретінде алмасу буферіне көшіру" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Таңдалған мәтінді жай мәтін ретінде алмасу буферіне көшіру." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Таңдауды ANSI тізбектері ретінде алмасу буферіне көшіру" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Таңдалған мәтінді ANSI escape-тізбектері ретінде алмасу буферіне көшіру." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Таңдауды HTML ретінде алмасу буферіне көшіру" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Таңдалған мәтінді HTML ретінде алмасу буферіне көшіру." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "URL мекенжайын алмасу буферіне көшіру" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Курсор астындағы URL мекенжайын алмасу буферіне көшіру." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Терминал атауын алмасу буферіне көшіру" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "" +msgstr "Терминал атауын алмасу буферіне көшіру. Терминал атауы орнатылмаған болса, бұл әрекеттің әсері болмайды." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Алмасу буферінен кірістіру" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Негізгі алмасу буферінің мазмұнын кірістіру." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Таңдаудан кірістіру" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Таңдау алмасу буферінің мазмұнын кірістіру." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Іздеуді бастау" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Егер іздеу әлі белсенді болмаса, оны бастау." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Таңдалғанды іздеу" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Ағымдағы таңдалған мәтінді іздеуді бастау." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Іздеуді аяқтау" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Ағымдағы іздеу болса, оны аяқтап, барлық графикалық интерфейс элементтерін жасыру." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Келесі іздеу нәтижесі" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Болса, келесі іздеу нәтижесіне өту." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Алдыңғы іздеу нәтижесі" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Болса, алдыңғы іздеу нәтижесіне өту." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Қаріп өлшемін үлкейту" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Қаріп өлшемін 1 пунктке үлкейту." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Қаріп өлшемін кішірейту" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Қаріп өлшемін 1 пунктке кішірейту." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Қаріп өлшемін қалпына келтіру" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Қаріп өлшемін бастапқы мәніне келтіру." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Экранды тазарту" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Экранды және айналдыру буферін тазарту." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Барлығын таңдау" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Экрандағы барлық мәтінді таңдау." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Жоғарғы жаққа айналдыру" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Экранның жоғарғы жағына айналдыру." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Төменгі жаққа айналдыру" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Экранның төменгі жағына айналдыру." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Таңдалғанға айналдыру" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Таңдалған мәтінге айналдыру." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Бір бетке жоғары айналдыру" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Экранды бір бетке жоғары айналдыру." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Бір бетке төмен айналдыру" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Экранды бір бетке төмен айналдыру." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Экранды уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Экран мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Экранды уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "Экран мазмұнын уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Экранды уақытша файлға көшіріп, ашу" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Экран мазмұнын уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Экранды HTML ретінде уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Экранды HTML ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Экранды HTML ретінде уақытша файлға көшіріп, ашу" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Экран мазмұнын HTML ретінде уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Экранды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Экранды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Экранды ANSI тізбектері ретінде уақытша файлға көшіріп, ашу" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Таңдауды уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Таңдау мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Таңдауды уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "Таңдау мазмұнын уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Таңдауды уақытша файлға көшіріп, ашу" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Таңдау мазмұнын уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Таңдауды HTML ретінде уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Таңдауды HTML ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Таңдауды HTML ретінде уақытша файлға көшіріп, ашу" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Таңдауды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын көшіру" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Таңдауды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Таңдауды ANSI тізбектері ретінде уақытша файлға көшіріп, ашу" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны ашу." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Жаңа терезе ашу." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Жаңа бет ашу." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Бетті солға жылжыту" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Ағымдағы бетті солға жылжыту." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Бетті оңға жылжыту" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Ағымдағы бетті оңға жылжыту." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Бетті жаңа терезеге жылжыту" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Ағымдағы бетті жаңа терезеге жылжыту." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Беттер шолуын қосу/өшіру" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Беттер шолуын қосу немесе өшіру." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Терминал атауын өзгерту…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Ағымдағы терминал үшін жаңа атау сұрау." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Ағымдағы бет үшін жаңа атау сұрау." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Терминалды солға бөлу." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Терминалды оңға бөлу." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Терминалды жоғары қарай бөлу." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Терминалды төмен қарай бөлу." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: алдыңғы" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Болса, алдыңғы бөлікке фокусты ауыстыру." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: келесі" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Болса, келесі бөлікке фокусты ауыстыру." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: сол жақ" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Бар болса, сол жақтағы бөлікке фокусты ауыстыру." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: оң жақ" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Бар болса, оң жақтағы бөлікке фокусты ауыстыру." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: жоғары" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Бар болса, жоғарыдағы бөлікке фокусты ауыстыру." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Фокусты бөлікке ауыстыру: төмен" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Бар болса, төмендегі бөлікке фокусты ауыстыру." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Фокусты терезеге ауыстыру: алдыңғы" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Болса, алдыңғы терезеге фокусты ауыстыру." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Фокусты терезеге ауыстыру: келесі" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Болса, келесі терезеге фокусты ауыстыру." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Бөлікті ұлғайтуды қосу/өшіру" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Ағымдағы бөліктің ұлғайту күйін қосу немесе өшіру." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Тек оқу режимін қосу/өшіру" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Ағымдағы терминал үшін тек оқу режимін қосу немесе өшіру." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Бөліктерді теңестіру" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Барлық бөліктердің өлшемін теңестіру." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Терезе өлшемін қалпына келтіру" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Терезе өлшемін бастапқы мәніне келтіру." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Инспекторды қосу/өшіру" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Инспекторды қосу немесе өшіру." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "GTK инспекторын көрсету" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "GTK инспекторын көрсету." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Экрандағы пернетақтаны көрсету" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Бар болса, экрандағы пернетақтаны көрсету." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Конфигурацияны ОЖ өңдегішімен ашу" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Конфигурация файлын ОЖ бастапқы өңдегішімен ашу." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Конфигурацияны жаңа терминал терезесінде ашу" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Конфигурация файлын $EDITOR немесе $VISUAL арқылы жаңа терезеде ашу." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Конфигурацияны қайта жүктеу" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Конфигурация файлын қайта жүктеу." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Терминалды жабу" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Ағымдағы терминалды жабу." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Ағымдағы бетті жабу." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Басқа беттерді жабу" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Осы терезедегі ағымдағыдан басқа барлық беттерді жабу." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Оң жақтағы беттерді жабу" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Ағымдағы беттің оң жағындағы барлық беттерді жабу." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Ағымдағы терезені жабу." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Барлық терезелерді жабу" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Барлық терезелерді жабу." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Жаюды қосу/өшіру" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Ағымдағы терезенің жайылған күйін қосу немесе өшіру." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Толық экранды қосу/өшіру" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Ағымдағы терезенің толық экран күйін қосу немесе өшіру." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Терезе безендірулерін қосу/өшіру" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Терезе безендірулерін қосу немесе өшіру." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Үстінде қалқып тұруды қосу/өшіру" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Ағымдағы терезенің үстінде қалқып тұру күйін қосу немесе өшіру." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Қауіпсіз енгізуді қосу/өшіру" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Қауіпсіз енгізу режимін қосу немесе өшіру." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Тінтуір туралы хабарлауды қосу/өшіру" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Тінтуір оқиғалары терминал қолданбаларына хабарлануын қосу немесе өшіру." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Фон мөлдірсіздігін қосу/өшіру" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "Мөлдір күйде іске қосылған терезенің фон мөлдірсіздігін қосу немесе өшіру." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Жаңартуларды тексеру" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Қолданба жаңартуларын тексеру." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Болдырмау" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Соңғы әрекетті болдырмау." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Қайталау" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Болдырылмаған соңғы әрекетті қайталау." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Қолданбадан шығу." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Терминалыңызға аздап Ghostty қосыңыз." \ No newline at end of file From 98b828a0232e411eb1e39b83de22fa3869faa51a Mon Sep 17 00:00:00 2001 From: Steven <4944823+steven-tk@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:50:32 +0200 Subject: [PATCH 012/392] config: flush autogenerated config template #13774 Flush the buffered writer after writing the template so newly created configuration files contain the expected guidance instead of being empty. --- src/config/Config.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/Config.zig b/src/config/Config.zig index 00b305486..814532b5d 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4121,6 +4121,7 @@ fn writeConfigTemplate(path: []const u8) !void { @embedFile("./config-template"), .{ .path = path }, ); + try writer.flush(); } /// Load configurations from the default configuration files. The default From 47136687d725d611a5b9d7fd59a13b1c4e360617 Mon Sep 17 00:00:00 2001 From: carl villads Date: Thu, 13 Aug 2026 12:57:09 +0200 Subject: [PATCH 013/392] add command palette translations --- po/da.po | 927 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 846 insertions(+), 81 deletions(-) diff --git a/po/da.po b/po/da.po index 55158a4a4..806e7a0dd 100644 --- a/po/da.po +++ b/po/da.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-07-26 04:59+0200\n" -"PO-Revision-Date: 2026-08-01 10:03+0200\n" +"POT-Creation-Date: 2026-08-10 10:06-0500\n" +"PO-Revision-Date: 2026-08-13 12:00+0200\n" "Last-Translator: Carl Villads Priisholm \n" "Language-Team: Danish \n" "Language: da\n" @@ -48,11 +48,12 @@ msgstr "Genindlæs konfigurationen for at se denne dialog igen" #: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 #: src/apprt/gtk/ui/1.5/title-dialog.blp:8 +#: src/apprt/gtk/class/application.zig:2263 msgid "Cancel" msgstr "Annullér" #: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:8 -#: src/apprt/gtk/ui/1.2/search-overlay.blp:85 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:90 #: src/apprt/gtk/ui/1.3/surface-child-exited.blp:17 msgid "Close" msgstr "Luk" @@ -61,20 +62,21 @@ msgstr "Luk" msgid "Configuration Errors" msgstr "Konfigurationsfejl" -#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:7 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:8 msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" "Der blev fundet en eller flere konfigurationsfejl. Venligst gennemse fejlene " -"nedenfor, og derefter enten genindlæs konfigurationen, eller ignorer disse fejl." +"nedenfor, og derefter enten genindlæs konfigurationen, eller ignorer disse " +"fejl." -#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:12 msgid "Ignore" msgstr "Ignorer" -#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:11 -#: src/apprt/gtk/ui/1.2/surface.blp:373 src/apprt/gtk/ui/1.5/window.blp:300 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:13 +#: src/apprt/gtk/ui/1.2/surface.blp:439 src/apprt/gtk/ui/1.5/window.blp:318 msgid "Reload Configuration" msgstr "Genindlæs konfiguration" @@ -93,161 +95,173 @@ msgstr "Ghostty: Terminal-inspektør" msgid "Find…" msgstr "Find…" -#: src/apprt/gtk/ui/1.2/search-overlay.blp:64 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:69 msgid "Previous Match" msgstr "Forrige match" -#: src/apprt/gtk/ui/1.2/search-overlay.blp:74 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:79 msgid "Next Match" msgstr "Næste match" -#: src/apprt/gtk/ui/1.2/surface.blp:6 +#: src/apprt/gtk/ui/1.2/surface.blp:7 msgid "Oh, no." msgstr "Åh nej." -#: src/apprt/gtk/ui/1.2/surface.blp:7 +#: src/apprt/gtk/ui/1.2/surface.blp:8 msgid "Unable to acquire an OpenGL context for rendering." msgstr "Det var ikke muligt at anskaffe en OpenGL kontekst for rendering." -#: src/apprt/gtk/ui/1.2/surface.blp:99 +#: src/apprt/gtk/ui/1.2/surface.blp:101 msgid "" "This terminal is in read-only mode. You can still view, select, and scroll " "through the content, but no input events will be sent to the running " "application." msgstr "" -"Denne terminal er i skrivebeskyttet tilstand. Du kan stadig se, vælge, " -"og scrolle gennem indholdet, men ingen anslag vil blive sendt til " -"det kørende program." +"Denne terminal er i skrivebeskyttet tilstand. Du kan stadig se, vælge, og " +"scrolle gennem indholdet, men ingen anslag vil blive sendt til det kørende " +"program." -#: src/apprt/gtk/ui/1.2/surface.blp:109 +#: src/apprt/gtk/ui/1.2/surface.blp:112 msgid "Read-only" msgstr "Skrivebeskyttet" -#: src/apprt/gtk/ui/1.2/surface.blp:262 src/apprt/gtk/ui/1.5/window.blp:200 +#: src/apprt/gtk/ui/1.2/surface.blp:316 src/apprt/gtk/ui/1.5/window.blp:203 msgid "Copy" msgstr "Kopiér" -#: src/apprt/gtk/ui/1.2/surface.blp:267 src/apprt/gtk/ui/1.5/window.blp:205 +#: src/apprt/gtk/ui/1.2/surface.blp:321 src/apprt/gtk/ui/1.5/window.blp:208 msgid "Paste" msgstr "Sæt ind" -#: src/apprt/gtk/ui/1.2/surface.blp:272 +#: src/apprt/gtk/ui/1.2/surface.blp:326 msgid "Notify on Next Command Finish" msgstr "Giv mig besked når den næste kommando er færdig" -#: src/apprt/gtk/ui/1.2/surface.blp:279 src/apprt/gtk/ui/1.5/window.blp:273 +#: src/apprt/gtk/ui/1.2/surface.blp:333 src/apprt/gtk/ui/1.5/window.blp:281 msgid "Clear" msgstr "Ryd" -#: src/apprt/gtk/ui/1.2/surface.blp:284 src/apprt/gtk/ui/1.5/window.blp:278 +#: src/apprt/gtk/ui/1.2/surface.blp:338 src/apprt/gtk/ui/1.5/window.blp:286 msgid "Reset" msgstr "Nulstil" -#: src/apprt/gtk/ui/1.2/surface.blp:291 src/apprt/gtk/ui/1.5/window.blp:242 # I de andre oversættelser af 'split' bruges 'del' i stedet for 'opdel', men tror det er for tvetydigt at have 'del' stående alene. +#: src/apprt/gtk/ui/1.2/surface.blp:345 src/apprt/gtk/ui/1.5/window.blp:250 msgid "Split" msgstr "Opdel" -#: src/apprt/gtk/ui/1.2/surface.blp:294 src/apprt/gtk/ui/1.5/window.blp:245 +#: src/apprt/gtk/ui/1.2/surface.blp:348 src/apprt/gtk/ui/1.5/window.blp:253 msgid "Change Title…" msgstr "Skift titel…" -#: src/apprt/gtk/ui/1.2/surface.blp:299 src/apprt/gtk/ui/1.5/window.blp:177 -#: src/apprt/gtk/ui/1.5/window.blp:250 +#: src/apprt/gtk/ui/1.2/surface.blp:353 src/apprt/gtk/ui/1.5/window.blp:180 +#: src/apprt/gtk/ui/1.5/window.blp:258 src/input/command.zig:487 msgid "Split Up" msgstr "Del opad" -#: src/apprt/gtk/ui/1.2/surface.blp:305 src/apprt/gtk/ui/1.5/window.blp:182 -#: src/apprt/gtk/ui/1.5/window.blp:255 +#: src/apprt/gtk/ui/1.2/surface.blp:359 src/apprt/gtk/ui/1.5/window.blp:185 +#: src/apprt/gtk/ui/1.5/window.blp:263 src/input/command.zig:492 msgid "Split Down" msgstr "Del nedad" -#: src/apprt/gtk/ui/1.2/surface.blp:311 src/apprt/gtk/ui/1.5/window.blp:187 -#: src/apprt/gtk/ui/1.5/window.blp:260 +#: src/apprt/gtk/ui/1.2/surface.blp:365 src/apprt/gtk/ui/1.5/window.blp:190 +#: src/apprt/gtk/ui/1.5/window.blp:268 src/input/command.zig:477 msgid "Split Left" msgstr "Del mod venstre" -#: src/apprt/gtk/ui/1.2/surface.blp:317 src/apprt/gtk/ui/1.5/window.blp:192 -#: src/apprt/gtk/ui/1.5/window.blp:265 +#: src/apprt/gtk/ui/1.2/surface.blp:371 src/apprt/gtk/ui/1.5/window.blp:195 +#: src/apprt/gtk/ui/1.5/window.blp:273 src/input/command.zig:482 msgid "Split Right" msgstr "Del mod højre" -#: src/apprt/gtk/ui/1.2/surface.blp:323 +#: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" msgstr "Luk deling" -#: src/apprt/gtk/ui/1.2/surface.blp:329 +#: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" msgstr "Fane" -#: src/apprt/gtk/ui/1.2/surface.blp:332 src/apprt/gtk/ui/1.5/window.blp:224 -#: src/apprt/gtk/ui/1.5/window.blp:320 +#: src/apprt/gtk/ui/1.2/surface.blp:386 src/apprt/gtk/ui/1.5/window.blp:232 +#: src/apprt/gtk/ui/1.5/window.blp:339 src/input/command.zig:464 msgid "Change Tab Title…" msgstr "Skift fanetitel…" -#: src/apprt/gtk/ui/1.2/surface.blp:337 src/apprt/gtk/ui/1.5/window.blp:57 -#: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 +#: src/apprt/gtk/ui/1.2/surface.blp:391 src/apprt/gtk/ui/1.5/window.blp:60 +#: src/apprt/gtk/ui/1.5/window.blp:110 src/apprt/gtk/ui/1.5/window.blp:237 +#: src/input/command.zig:427 msgid "New Tab" msgstr "Ny fane" -#: src/apprt/gtk/ui/1.2/surface.blp:342 src/apprt/gtk/ui/1.5/window.blp:234 +#: src/apprt/gtk/ui/1.2/surface.blp:396 src/apprt/gtk/ui/1.5/window.blp:242 +#: src/input/command.zig:613 msgid "Close Tab" msgstr "Luk fane" -#: src/apprt/gtk/ui/1.2/surface.blp:349 +#: src/apprt/gtk/ui/1.2/surface.blp:403 msgid "Window" msgstr "Vindue" -#: src/apprt/gtk/ui/1.2/surface.blp:352 src/apprt/gtk/ui/1.5/window.blp:212 +#: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 +msgid "Change Window Title…" +msgstr "Skift titel på vindue…" + +#: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 +#: src/input/command.zig:421 msgid "New Window" msgstr "Nyt vindue" -#: src/apprt/gtk/ui/1.2/surface.blp:357 src/apprt/gtk/ui/1.5/window.blp:217 +#: src/apprt/gtk/ui/1.2/surface.blp:416 src/apprt/gtk/ui/1.5/window.blp:225 +#: src/input/command.zig:630 msgid "Close Window" msgstr "Luk vindue" -#: src/apprt/gtk/ui/1.2/surface.blp:365 +#: src/apprt/gtk/ui/1.2/surface.blp:424 src/apprt/gtk/ui/1.5/window.blp:303 msgid "Config" msgstr "Konfiguration" -#: src/apprt/gtk/ui/1.2/surface.blp:368 src/apprt/gtk/ui/1.5/window.blp:295 -msgid "Open Configuration" -msgstr "Åben konfigurering" +#: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 +msgid "Open Configuration in OS Editor" +msgstr "Åben konfigurering i styresystemets redigeringsprogram" + +#: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 +msgid "Open Configuration in New Window" +msgstr "Åben konfigurering i nyt vindue" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." -msgstr "Lad feltet stå tomt for at gendanne standardtitelen." +msgstr "Lad feltet stå tomt for at gendanne standardtitlen." #: src/apprt/gtk/ui/1.5/title-dialog.blp:9 msgid "OK" msgstr "OK" -#: src/apprt/gtk/ui/1.5/window.blp:58 src/apprt/gtk/ui/1.5/window.blp:108 # Her bruges 'opdeling' igen, da jeg tror at 'deling' vil være for tvetydigt +#: src/apprt/gtk/ui/1.5/window.blp:61 src/apprt/gtk/ui/1.5/window.blp:111 msgid "New Split" msgstr "Ny opdeling" -#: src/apprt/gtk/ui/1.5/window.blp:68 src/apprt/gtk/ui/1.5/window.blp:126 +#: src/apprt/gtk/ui/1.5/window.blp:71 src/apprt/gtk/ui/1.5/window.blp:129 msgid "View Open Tabs" msgstr "Vis åbne faner" -#: src/apprt/gtk/ui/1.5/window.blp:78 src/apprt/gtk/ui/1.5/window.blp:140 +#: src/apprt/gtk/ui/1.5/window.blp:81 src/apprt/gtk/ui/1.5/window.blp:143 msgid "Main Menu" msgstr "Hovedmenu" -#: src/apprt/gtk/ui/1.5/window.blp:285 +#: src/apprt/gtk/ui/1.5/window.blp:293 msgid "Command Palette" msgstr "Kommandopalet" -#: src/apprt/gtk/ui/1.5/window.blp:290 +#: src/apprt/gtk/ui/1.5/window.blp:298 msgid "Terminal Inspector" msgstr "Terminalinspektør" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1866 +#: src/apprt/gtk/ui/1.5/window.blp:326 src/apprt/gtk/class/window.zig:1991 msgid "About Ghostty" msgstr "Om Ghostty" -#: src/apprt/gtk/ui/1.5/window.blp:312 +#: src/apprt/gtk/ui/1.5/window.blp:331 src/input/command.zig:702 msgid "Quit" msgstr "Luk ned" @@ -255,18 +269,30 @@ msgstr "Luk ned" msgid "Execute a command…" msgstr "Eksekver en kommando…" -#: src/apprt/gtk/class/application.zig:1708 +#: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." msgstr "Tastaturgenvejen blev ophævet af systemet." -#: src/apprt/gtk/class/application.zig:1710 +#: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." msgstr "Tastaturgenvejen blev afvist af systemet." -#: src/apprt/gtk/class/application.zig:1721 +#: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" msgstr "Global tastaturgenvej utilgængelig" +#: src/apprt/gtk/class/application.zig:2259 +msgid "Export Terminal IO Events" +msgstr "Eksportér terminal IO-begivenheder" + +#: src/apprt/gtk/class/application.zig:2262 +msgid "Export" +msgstr "Eksportér" + +#: src/apprt/gtk/class/application.zig:2783 +msgid "Editing configuration file" +msgstr "Redigerer konfigurationsfil" + #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" "An application is attempting to write to the clipboard. The current " @@ -292,8 +318,8 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Det kan være farligt at indsætte denne tekst i terminalen, da det ligner " -"at en eller flere kommandoer vil blive eksekveret." +"Det kan være farligt at indsætte denne tekst i terminalen, da det ligner at " +"en eller flere kommandoer vil blive eksekveret." #: src/apprt/gtk/class/close_confirmation_dialog.zig:184 msgid "Quit Ghostty?" @@ -307,8 +333,8 @@ msgstr "Luk fane?" msgid "Close Window?" msgstr "Luk vindue?" -#: src/apprt/gtk/class/close_confirmation_dialog.zig:187 # Her bruges opdeling igen da 'deling' kunne være tvetydigt +#: src/apprt/gtk/class/close_confirmation_dialog.zig:187 msgid "Close Split?" msgstr "Luk opdeling?" @@ -328,46 +354,785 @@ msgstr "Alle terminalsessioner i dette vindue vil blive afsluttet." msgid "The currently running process in this split will be terminated." msgstr "Den kørende proces i denne opdeling vil blive afsluttet." -#: src/apprt/gtk/class/surface.zig:1170 +#: src/apprt/gtk/class/surface.zig:1181 msgid "Command Finished" msgstr "Kommandoen er færdig" -#: src/apprt/gtk/class/surface.zig:1171 +#: src/apprt/gtk/class/surface.zig:1182 +#: src/apprt/gtk/class/surface_child_exited.zig:109 msgid "Command Succeeded" msgstr "Kommandoen lykkedes" -#: src/apprt/gtk/class/surface.zig:1172 +#: src/apprt/gtk/class/surface.zig:1183 +#: src/apprt/gtk/class/surface_child_exited.zig:113 msgid "Command Failed" msgstr "Kommandoen fejlede" -#: src/apprt/gtk/class/surface_child_exited.zig:109 -msgid "Command succeeded" -msgstr "Kommandoen lykkedes" - -#: src/apprt/gtk/class/surface_child_exited.zig:113 -msgid "Command failed" -msgstr "Kommandoen fejlede" - -#: src/apprt/gtk/class/title_dialog.zig:225 +#: src/apprt/gtk/class/title_dialog.zig:227 msgid "Change Terminal Title" -msgstr "Skift titlen på terminalen" +msgstr "Skift terminaltitlen" -#: src/apprt/gtk/class/title_dialog.zig:226 +#: src/apprt/gtk/class/title_dialog.zig:228 msgid "Change Tab Title" -msgstr "Skift titlen på fanen" +msgstr "Skift fanetitel" -#: src/apprt/gtk/class/window.zig:1102 +#: src/apprt/gtk/class/title_dialog.zig:229 +msgid "Change Window Title" +msgstr "Skift vinduestitel" + +#: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" -msgstr "Genindlæste konfigurationen" +msgstr "Konfigurationen blev genindlæst" -#: src/apprt/gtk/class/window.zig:1705 +#: src/apprt/gtk/class/window.zig:1830 msgid "Copied to clipboard" msgstr "Kopieret til udklipsholder" -#: src/apprt/gtk/class/window.zig:1707 +#: src/apprt/gtk/class/window.zig:1832 msgid "Cleared clipboard" -msgstr "Ryddet udklipsholder" +msgstr "Udklipsholderen blev ryddet" -#: src/apprt/gtk/class/window.zig:1847 +#: src/apprt/gtk/class/window.zig:1972 msgid "Ghostty Developers" msgstr "Ghostty udviklerne" + +#: src/input/command.zig:149 +msgid "Reset Terminal" +msgstr "Nulstil terminal" + +#: src/input/command.zig:150 +msgid "Reset the terminal to a clean state." +msgstr "Nulstil terminalen fuldstændigt." + +#: src/input/command.zig:155 +msgid "Copy to Clipboard" +msgstr "Kopiér til udklipsholder" + +#: src/input/command.zig:156 +msgid "" +"Copy the selected text to the clipboard in both plain and styled formats." +msgstr "" +"Kopiér den valgte tekst i både almindelig og stiliseret format til udklipsholderen." + +#: src/input/command.zig:159 +msgid "Copy Selection as Plain Text to Clipboard" +msgstr "Kopiér valg som almindelig tekst til udklipsholderen" + +#: src/input/command.zig:160 +msgid "Copy the selected text as plain text to the clipboard." +msgstr "Kopiér den valgte tekst som almindelig tekst til udklipsholderen." + +#: src/input/command.zig:163 +msgid "Copy Selection as ANSI Sequences to Clipboard" +msgstr "Kopiér valg som ANSI-sekvenser til udklipsholderen" + +#: src/input/command.zig:164 +msgid "Copy the selected text as ANSI escape sequences to the clipboard." +msgstr "Kopiér det valgte tekst som ANSI escape-sekvenser til udklipsholderen." + +#: src/input/command.zig:167 +msgid "Copy Selection as HTML to Clipboard" +msgstr "Kopiér valg som HTML til udklipsholderen" + +#: src/input/command.zig:168 +msgid "Copy the selected text as HTML to the clipboard." +msgstr "Kopiér det valgte tekst som HTML til udklipsholderen." + +#: src/input/command.zig:173 +msgid "Copy URL to Clipboard" +msgstr "Kopiér URL til udklipsholder" + +#: src/input/command.zig:174 +msgid "Copy the URL under the cursor to the clipboard." +msgstr "Kopiér URL'en under musen til udklipsholderen." + +#: src/input/command.zig:179 +msgid "Copy Terminal Title to Clipboard" +msgstr "Kopiér terminaltitel til udklipsholder" + +#: src/input/command.zig:180 +msgid "" +"Copy the terminal title to the clipboard. If the terminal title is not set " +"this has no effect." +msgstr "" +"Kopiér terminaltitlen til udklipsholderen. Hvis ingen terminaltitel er " +"angivet, vil dette have ingen effekt." + +#: src/input/command.zig:185 +msgid "Paste from Clipboard" +msgstr "Sæt ind fra udklipsholder" + +#: src/input/command.zig:186 +msgid "Paste the contents of the main clipboard." +msgstr "Indsæt indholdet fra den primære udklipsholder." + +#: src/input/command.zig:191 +msgid "Paste from Selection" +msgstr "Sæt ind fra markering" + +#: src/input/command.zig:192 +msgid "Paste the contents of the selection clipboard." +msgstr "Indsæt indholdet fra udklipsholderen for markeringer." + +#: src/input/command.zig:197 +msgid "Start Search" +msgstr "Start søgning" + +#: src/input/command.zig:198 +msgid "Start a search if one isn't already active." +msgstr "Start en søgning, hvis en ikke allerede er aktiv." + +#: src/input/command.zig:203 +msgid "Search Selection" +msgstr "Søg i markering" + +#: src/input/command.zig:204 +msgid "Start a search for the current text selection." +msgstr "Start en søgning i den aktuelle tekstmarkering." + +#: src/input/command.zig:209 +msgid "End Search" +msgstr "Afslut søgning" + +#: src/input/command.zig:210 +msgid "End the current search if any and hide any GUI elements." +msgstr "Afslut den nuværende søgning hvis den findes, og skjul eventuelle GUI-elementer." + +#: src/input/command.zig:215 +msgid "Next Search Result" +msgstr "Næste søgeresultat" + +#: src/input/command.zig:216 +msgid "Navigate to the next search result, if any." +msgstr "Navigér til det næste søgeresultat, hvis det findes." + +#: src/input/command.zig:219 +msgid "Previous Search Result" +msgstr "Forrige søgeresultat" + +#: src/input/command.zig:220 +msgid "Navigate to the previous search result, if any." +msgstr "Navigér til det forrige søgeresultat, hvis det findes." + +#: src/input/command.zig:225 +msgid "Increase Font Size" +msgstr "Øg skriftstørrelsen" + +#: src/input/command.zig:226 +msgid "Increase the font size by 1 point." +msgstr "Øg skriftstørrelsen med 1 punkt." + +#: src/input/command.zig:231 +msgid "Decrease Font Size" +msgstr "Formindsk skriftstørrelse" + +#: src/input/command.zig:232 +msgid "Decrease the font size by 1 point." +msgstr "Formindsk skriftstørrelsen med 1 punkt." + +#: src/input/command.zig:237 +msgid "Reset Font Size" +msgstr "Nulstil skriftstørrelse" + +#: src/input/command.zig:238 +msgid "Reset the font size to the default." +msgstr "Nulstil skriftstørrelsen til standardindstillingen." + +#: src/input/command.zig:243 +msgid "Clear Screen" +msgstr "Ryd skærm" + +#: src/input/command.zig:244 +msgid "Clear the screen and scrollback." +msgstr "Ryd skærmen og rul tilbage." + +#: src/input/command.zig:249 +msgid "Select All" +msgstr "Vælg alt" + +#: src/input/command.zig:250 +msgid "Select all text on the screen." +msgstr "Markér al tekst på skærmen." + +#: src/input/command.zig:255 +msgid "Scroll to Top" +msgstr "Rul til toppen" + +#: src/input/command.zig:256 +msgid "Scroll to the top of the screen." +msgstr "Rul til toppen af skærmen." + +#: src/input/command.zig:261 +msgid "Scroll to Bottom" +msgstr "Rul til bunden" + +#: src/input/command.zig:262 +msgid "Scroll to the bottom of the screen." +msgstr "Rul ned til bunden af skærmen." + +#: src/input/command.zig:267 +msgid "Scroll to Selection" +msgstr "Rul til markering" + +#: src/input/command.zig:268 +msgid "Scroll to the selected text." +msgstr "Rul hen til den markerede tekst." + +#: src/input/command.zig:273 +msgid "Scroll Page Up" +msgstr "Rul side op" + +#: src/input/command.zig:274 +msgid "Scroll the screen up by a page." +msgstr "Rul skærmen op med en side." + +#: src/input/command.zig:279 +msgid "Scroll Page Down" +msgstr "Rul side ned" + +#: src/input/command.zig:280 +msgid "Scroll the screen down by a page." +msgstr "Rul skærmen ned med en side." + +#: src/input/command.zig:286 +msgid "Copy Screen to Temporary File and Copy Path" +msgstr "Kopiér skærm til midlertidig fil og kopiér sti" + +#: src/input/command.zig:287 +msgid "" +"Copy the screen contents to a temporary file and copy the path to the " +"clipboard." +msgstr "" +"Kopiér indholdet af skærmen til en midlertidig fil, og kopiér stien " +"til udklipsholderen." + +#: src/input/command.zig:291 +msgid "Copy Screen to Temporary File and Paste Path" +msgstr "Kopiér skærm til midlertidig fil og indsæt sti" + +#: src/input/command.zig:292 +msgid "" +"Copy the screen contents to a temporary file and paste the path to the file." +msgstr "" +"Kopiér indholdet af skærmen til en midlertidig fil, og indsæt stien til filen." + +#: src/input/command.zig:296 +msgid "Copy Screen to Temporary File and Open" +msgstr "Kopiér skærm til midlertidig fil og åben" + +#: src/input/command.zig:297 +msgid "Copy the screen contents to a temporary file and open it." +msgstr "Kopiér indholdet af skærmen til en midlertidig fil, og åben den." + +#: src/input/command.zig:305 +msgid "Copy Screen as HTML to Temporary File and Copy Path" +msgstr "Kopiér skærm som HTML til en midlertidig fil og kopiér sti" + +#: src/input/command.zig:306 +msgid "" +"Copy the screen contents as HTML to a temporary file and copy the path to " +"the clipboard." +msgstr "" +"Kopiér indholdet af skærmen som HTML til en midlertidig fil, og kopiér stien " +"til udklipsholderen." + +#: src/input/command.zig:313 +msgid "Copy Screen as HTML to Temporary File and Paste Path" +msgstr "Kopiér skærmen som HTML til en midlertidig fil og indsæt sti" + +#: src/input/command.zig:314 +msgid "" +"Copy the screen contents as HTML to a temporary file and paste the path to " +"the file." +msgstr "" +"Kopiér indholdet af skærmen som HTML til en midlertidig fil, og indsæt stien " +"til filen." + +#: src/input/command.zig:321 +msgid "Copy Screen as HTML to Temporary File and Open" +msgstr "Kopiér skærm som HTML til en midlertidig fil og åben" + +#: src/input/command.zig:322 +msgid "Copy the screen contents as HTML to a temporary file and open it." +msgstr "Kopiér indholdet af skærmen som HTML til en midlertidig fil og åben den." + +#: src/input/command.zig:330 +msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" +msgstr "Kopiér skærm som ANSI-sekvenser til en midlertidig fil og kopiér sti" + +#: src/input/command.zig:331 +msgid "" +"Copy the screen contents as ANSI escape sequences to a temporary file and " +"copy the path to the clipboard." +msgstr "" +"Kopiér indholdet af skærmen som ANSI escape-sekvenser til en midlertidig " +"fil, og kopiér stien til udklipsholderen." + +#: src/input/command.zig:338 +msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" +msgstr "Kopiér skærm som ANSI-sekvenser til midlertidig fil og indsæt sti" + +#: src/input/command.zig:339 +msgid "" +"Copy the screen contents as ANSI escape sequences to a temporary file and " +"paste the path to the file." +msgstr "" +"Kopiér indholdet af skærmen som ANSI escape-sekvenser til en midlertidig " +"fil, og indsæt stien til filen." + +#: src/input/command.zig:346 +msgid "Copy Screen as ANSI Sequences to Temporary File and Open" +msgstr "Kopiér skærm som ANSI-sekvenser til midlertidig fil og åben" + +#: src/input/command.zig:347 +msgid "" +"Copy the screen contents as ANSI escape sequences to a temporary file and " +"open it." +msgstr "" +"Kopiér indholdet af skærmen som ANSI escape-sekvenser til en midlertidig " +"fil, og åben den." + +#: src/input/command.zig:354 +msgid "Copy Selection to Temporary File and Copy Path" +msgstr "Kopiér markering til midlertidig fil og kopiér sti" + +#: src/input/command.zig:355 +msgid "" +"Copy the selection contents to a temporary file and copy the path to the " +"clipboard." +msgstr "" +"Kopiér indholdet af markeringen til en midlertidig fil, og kopiér stien " +"til udklipsholderen." + +#: src/input/command.zig:359 +msgid "Copy Selection to Temporary File and Paste Path" +msgstr "Kopiér markering til midlertidig fil og indsæt sti" + +#: src/input/command.zig:360 +msgid "" +"Copy the selection contents to a temporary file and paste the path to the " +"file." +msgstr "" +"Kopiér indholdet af markeringen til en midlertidig fil, og indsæt stien " +"til filen." + +#: src/input/command.zig:364 +msgid "Copy Selection to Temporary File and Open" +msgstr "Kopiér markering til midlertidig fil og åben" + +#: src/input/command.zig:365 +msgid "Copy the selection contents to a temporary file and open it." +msgstr "Kopiér indholdet af markeringen til en midlertidig fil og åben den." + +#: src/input/command.zig:373 +msgid "Copy Selection as HTML to Temporary File and Copy Path" +msgstr "Kopiér markering som HTML til en midlertidig fil og kopiér sti" + +#: src/input/command.zig:374 +msgid "" +"Copy the selection contents as HTML to a temporary file and copy the path to " +"the clipboard." +msgstr "" +"Kopiér indholdet af markeringen som HTML til en midlertidig fil, og kopiér " +"stien til udklipsholderen." + +#: src/input/command.zig:381 +msgid "Copy Selection as HTML to Temporary File and Paste Path" +msgstr "Kopiér markering som HTML til midlertidig fil og indsæt sti" + +#: src/input/command.zig:382 +msgid "" +"Copy the selection contents as HTML to a temporary file and paste the path " +"to the file." +msgstr "" +"Kopiér indholdet af markeringen som HTML til en midlertidig fil, og indsæt " +"stien til filen." + +#: src/input/command.zig:389 +msgid "Copy Selection as HTML to Temporary File and Open" +msgstr "Kopiér markering som HTML til midlertidig fil og åben" + +#: src/input/command.zig:390 +msgid "Copy the selection contents as HTML to a temporary file and open it." +msgstr "Kopiér indholdet af markeringen som HTML til en midlertidig fil, og åben den." + +#: src/input/command.zig:398 +msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" +msgstr "Kopiér markering som ANSI-sekvenser til midlertidig fil og kopiér sti" + +#: src/input/command.zig:399 +msgid "" +"Copy the selection contents as ANSI escape sequences to a temporary file and " +"copy the path to the clipboard." +msgstr "" +"Kopiér indholdet af markeringen som ANSI escape-sekvenser til en midlertidig " +"fil, og kopiér stien til udklipsholderen." + +#: src/input/command.zig:406 +msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" +msgstr "Kopiér markering som ANSI-sekvenser til midlertidig fil og indsæt sti" + +#: src/input/command.zig:407 +msgid "" +"Copy the selection contents as ANSI escape sequences to a temporary file and " +"paste the path to the file." +msgstr "" +"Kopiér indholdet af markeringen som ANSI escape-sekvenser til en midlertidig " +"fil, og indsæt stien til filen." + +#: src/input/command.zig:414 +msgid "Copy Selection as ANSI Sequences to Temporary File and Open" +msgstr "Kopiér markering som ANSI-sekvenser til midlertidig fil og åben" + +#: src/input/command.zig:415 +msgid "" +"Copy the selection contents as ANSI escape sequences to a temporary file and " +"open it." +msgstr "" +"Kopiér indholdet af markeringen som ANSI escape-sekvenser til en midlertidig " +"fil, og åben den." + +#: src/input/command.zig:422 +msgid "Open a new window." +msgstr "Åben et nyt vindue." + +#: src/input/command.zig:428 +msgid "Open a new tab." +msgstr "Åben en ny fane." + +#: src/input/command.zig:434 +msgid "Move Tab Left" +msgstr "Ryk fane til venstre" + +#: src/input/command.zig:435 +msgid "Move the current tab to the left." +msgstr "Ryk den nuværende fane til venstre." + +#: src/input/command.zig:439 +msgid "Move Tab Right" +msgstr "Ryk fane til højre" + +#: src/input/command.zig:440 +msgid "Move the current tab to the right." +msgstr "Ryk den nuværende fane til højre." + +#: src/input/command.zig:446 +msgid "Move Tab to New Window" +msgstr "Ryk fane til nyt vindue" + +#: src/input/command.zig:447 +msgid "Move the current tab to a new window." +msgstr "Ryk den nuværende fane til et nyt vindue." + +#: src/input/command.zig:452 +msgid "Toggle Tab Overview" +msgstr "Slå faneoversigt til/fra" + +#: src/input/command.zig:453 +msgid "Toggle the tab overview." +msgstr "Slå faneoversigten til eller fra." + +#: src/input/command.zig:458 +msgid "Change Terminal Title…" +msgstr "Skift terminaltitel…" + +#: src/input/command.zig:459 +msgid "Prompt for a new title for the current terminal." +msgstr "Dialog for at skifte titlen på den nuværende terminal." + +#: src/input/command.zig:465 +msgid "Prompt for a new title for the current tab." +msgstr "Dialog for at skifte titlen for den nuværende fane." + +#: src/input/command.zig:478 +msgid "Split the terminal to the left." +msgstr "Del terminalen mod venstre." + +#: src/input/command.zig:483 +msgid "Split the terminal to the right." +msgstr "Del terminalen mod højre." + +#: src/input/command.zig:488 +msgid "Split the terminal up." +msgstr "Del terminalen opad." + +#: src/input/command.zig:493 +msgid "Split the terminal down." +msgstr "Del terminalen nedad." + +# See retskrivningsordbogen §11 stk. 3 for the rules for large or small letters after colon. +#: src/input/command.zig:500 +msgid "Focus Split: Previous" +msgstr "Fokusopdeling: Forrige" + +#: src/input/command.zig:501 +msgid "Focus the previous split, if any." +msgstr "Fokusér på den tidligere deling, hvis der er en." + +#: src/input/command.zig:505 +msgid "Focus Split: Next" +msgstr "Fokusopdeling: Næste" + +#: src/input/command.zig:506 +msgid "Focus the next split, if any." +msgstr "Fokusér på den næste deling, hvis der er en." + +#: src/input/command.zig:510 +msgid "Focus Split: Left" +msgstr "Fokusopdeling: Venstre" + +#: src/input/command.zig:511 +msgid "Focus the split to the left, if it exists." +msgstr "Fokusér på delingen til venstre, hvis den findes." + +#: src/input/command.zig:515 +msgid "Focus Split: Right" +msgstr "Fokusopdeling: Højre" + +#: src/input/command.zig:516 +msgid "Focus the split to the right, if it exists." +msgstr "Fokusér på delingen til højre, hvis den findes." + +#: src/input/command.zig:520 +msgid "Focus Split: Up" +msgstr "Fokusopdeling: Opad" + +#: src/input/command.zig:521 +msgid "Focus the split above, if it exists." +msgstr "Fokusér på ovenstående deling, hvis den findes." + +#: src/input/command.zig:525 +msgid "Focus Split: Down" +msgstr "Fokusopdeling: Nedad" + +#: src/input/command.zig:526 +msgid "Focus the split below, if it exists." +msgstr "Fokusér på nedenstående deling, hvis den findes." + +#: src/input/command.zig:533 +msgid "Focus Window: Previous" +msgstr "Fokusvindue: Forrige" + +#: src/input/command.zig:534 +msgid "Focus the previous window, if any." +msgstr "Fokusér på det tidligere vindue, hvis der er et." + +#: src/input/command.zig:538 +msgid "Focus Window: Next" +msgstr "Fokusvindue: Næste" + +#: src/input/command.zig:539 +msgid "Focus the next window, if any." +msgstr "Fokusér på det næste vindue, hvis der er et." + +#: src/input/command.zig:545 +msgid "Toggle Split Zoom" +msgstr "Slå delingszoom til/fra" + +#: src/input/command.zig:546 +msgid "Toggle the zoom state of the current split." +msgstr "Skift zoomtilstand for den aktuelle deling." + +#: src/input/command.zig:551 +msgid "Toggle Read-Only Mode" +msgstr "Slå skrivebeskyttet tilstand til/fra" + +#: src/input/command.zig:552 +msgid "Toggle read-only mode for the current surface." +msgstr "Slå skrivebeskyttet tilstand til eller fra for den aktuelle overflade." + +#: src/input/command.zig:557 +msgid "Equalize Splits" +msgstr "Udlign opdelinger" + +#: src/input/command.zig:558 +msgid "Equalize the size of all splits." +msgstr "Udlign størrelsen af alle opdelinger." + +#: src/input/command.zig:563 +msgid "Reset Window Size" +msgstr "Nulstil vinduesstørrelse" + +#: src/input/command.zig:564 +msgid "Reset the window size to the default." +msgstr "Nulstil vinduestørrelsen til standardindstillingen." + +#: src/input/command.zig:569 +msgid "Toggle Inspector" +msgstr "Slå inspektør til/fra" + +#: src/input/command.zig:570 +msgid "Toggle the inspector." +msgstr "Slå inspektøren til eller fra." + +#: src/input/command.zig:575 +msgid "Show the GTK Inspector" +msgstr "Vis GTK-inspektør" + +#: src/input/command.zig:576 +msgid "Show the GTK inspector." +msgstr "Vis GTK-inspektøren." + +#: src/input/command.zig:581 +msgid "Show On-Screen Keyboard" +msgstr "Vis tastatur på skærmen" + +#: src/input/command.zig:582 +msgid "Show the on-screen keyboard if present." +msgstr "Vis tastaturet på skærmen, hvis det er til stede." + +#: src/input/command.zig:588 +msgid "Open Config Using OS editor" +msgstr "Åben konfiguration med styresystemets redigeringsprogram" + +#: src/input/command.zig:589 +msgid "Open the config file with the OS's default editor." +msgstr "Åben konfigurationen med styresystemets standardredigeringsprogram." + +#: src/input/command.zig:593 +msgid "Open Config in New Terminal Window" +msgstr "Åben konfiguration i nyt terminalvindue" + +#: src/input/command.zig:594 +msgid "Open the config file in a new window using $EDITOR or $VISUAL." +msgstr "Åben konfigurationsfilen i et nyt vindue med $EDITOR eller $VISUAL." + +#: src/input/command.zig:600 +msgid "Reload Config" +msgstr "Genindlæs konfiguration" + +#: src/input/command.zig:601 +msgid "Reload the config file." +msgstr "Genindlæs konfigurationsfilen." + +#: src/input/command.zig:606 +msgid "Close Terminal" +msgstr "Luk terminal" + +#: src/input/command.zig:607 +msgid "Close the current terminal." +msgstr "Luk den nuværende terminal." + +#: src/input/command.zig:614 +msgid "Close the current tab." +msgstr "Luk den nuværende fane." + +#: src/input/command.zig:618 +msgid "Close Other Tabs" +msgstr "Luk andre faner" + +#: src/input/command.zig:619 +msgid "Close all tabs in this window except the current one." +msgstr "Luk alle faner i dette vindue, bortset fra den nuværende." + +#: src/input/command.zig:623 +msgid "Close Tabs to the Right" +msgstr "Luk alle faner til højre" + +#: src/input/command.zig:624 +msgid "Close all tabs to the right of the current one." +msgstr "Luk alle faner til højre for den nuværende." + +#: src/input/command.zig:631 +msgid "Close the current window." +msgstr "Luk det nuværende vindue." + +#: src/input/command.zig:636 +msgid "Close All Windows" +msgstr "Luk alle vinduer" + +#: src/input/command.zig:637 +msgid "Close all windows." +msgstr "Luk alle vinduer." + +#: src/input/command.zig:642 +msgid "Toggle Maximize" +msgstr "Slå maksimering til/fra" + +#: src/input/command.zig:643 +msgid "Toggle the maximized state of the current window." +msgstr "Slå den maksimerede tilstand af det nuværende vindue til eller fra." + +#: src/input/command.zig:648 +msgid "Toggle Fullscreen" +msgstr "Slå fuld skærm til/fra" + +#: src/input/command.zig:649 +msgid "Toggle the fullscreen state of the current window." +msgstr "Slå fuldskærmstilstanden til eller fra for det nuværende vindue." + +#: src/input/command.zig:654 +msgid "Toggle Window Decorations" +msgstr "Slå vinduesdekorationer til/fra" + +#: src/input/command.zig:655 +msgid "Toggle the window decorations." +msgstr "Slå vinduesdekorationerne til eller fra." + +#: src/input/command.zig:660 +msgid "Toggle Float on Top" +msgstr "Slå vis altid øverst til/fra" + +#: src/input/command.zig:661 +msgid "Toggle the float on top state of the current window." +msgstr "Slå tilstanden for altid at vise vinduet øverst til eller fra." + +#: src/input/command.zig:666 +msgid "Toggle Secure Input" +msgstr "Slå sikker input til/fra" + +#: src/input/command.zig:667 +msgid "Toggle secure input mode." +msgstr "Slå sikker inputstilstanden til eller fra." + +#: src/input/command.zig:672 +msgid "Toggle Mouse Reporting" +msgstr "Slå muserapportering til/fra" + +#: src/input/command.zig:673 +msgid "Toggle whether mouse events are reported to terminal applications." +msgstr "Slå til eller fra, om musehændelser skal rapporteres til terminalprogrammer." + +#: src/input/command.zig:678 +msgid "Toggle Background Opacity" +msgstr "Slå baggrundsgennemskinnelighed til/fra" + +#: src/input/command.zig:679 +msgid "Toggle the background opacity of a window that started transparent." +msgstr "Slå baggrundsgennemskinneligheden til eller fra for et vindue, der startede som gennemsigtigt." + +#: src/input/command.zig:684 +msgid "Check for Updates" +msgstr "Tjek efter opdateringer" + +#: src/input/command.zig:685 +msgid "Check for updates to the application." +msgstr "Tjek efter opdateringer for programmet." + +#: src/input/command.zig:690 +msgid "Undo" +msgstr "Fortryd" + +#: src/input/command.zig:691 +msgid "Undo the last action." +msgstr "Fortryd den sidste handling." + +#: src/input/command.zig:696 +msgid "Redo" +msgstr "Gentag" + +#: src/input/command.zig:697 +msgid "Redo the last undone action." +msgstr "Gentag den sidste fortrudte handling." + +#: src/input/command.zig:703 +msgid "Quit the application." +msgstr "Luk ned for programmet." + +#: src/input/command.zig:708 +msgid "Ghostty" +msgstr "Ghostty" + +#: src/input/command.zig:709 +msgid "Put a little Ghostty in your terminal." +msgstr "Sæt en lille Ghostty i din terminal." From dd22396bd798d90445e1b365a79da061f96e5dc9 Mon Sep 17 00:00:00 2001 From: Miguel P Z <60221874+MiguelElGallo@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:44:20 +0300 Subject: [PATCH 014/392] i18n: update es_BO translations for 1.4 --- po/es_BO.po | 356 +++++++++++++++++++++++++++++----------------------- 1 file changed, 201 insertions(+), 155 deletions(-) diff --git a/po/es_BO.po b/po/es_BO.po index 1887e288c..3a755f670 100644 --- a/po/es_BO.po +++ b/po/es_BO.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-12 17:46+0200\n" +"PO-Revision-Date: 2026-08-13 17:33+0300\n" "Last-Translator: Miguel Peredo \n" "Language-Team: Spanish \n" "Language: es_BO\n" @@ -175,7 +175,7 @@ msgstr "Dividir a la derecha" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Cerrar división" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,7 +203,7 @@ msgstr "Ventana" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Cambiar el título de la ventana…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +221,11 @@ msgstr "Configuración" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Abrir configuración en el editor del sistema operativo" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Abrir configuración en una ventana nueva" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -269,27 +269,27 @@ msgstr "Ejecutar comando…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "El sistema revocó el atajo de teclado." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "El sistema denegó el atajo de teclado." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Atajo de teclado global no disponible" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Exportar eventos de E/S de la terminal" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Exportar" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Editando el archivo de configuración" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -375,7 +375,7 @@ msgstr "Cambiar el título de la pestaña" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Cambiar el título de la ventana" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -395,710 +395,756 @@ msgstr "Desarrolladores de Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Restablecer terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Restablecer la terminal a un estado inicial." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Copiar al portapapeles" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Copiar el texto seleccionado al portapapeles en formatos de texto simple y " +"con estilo." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Copiar selección como texto simple al portapapeles" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Copiar el texto seleccionado como texto simple al portapapeles." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Copiar selección como secuencias ANSI al portapapeles" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." msgstr "" +"Copiar el texto seleccionado como secuencias de escape ANSI al portapapeles." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Copiar selección como HTML al portapapeles" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Copiar el texto seleccionado como HTML al portapapeles." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Copiar URL al portapapeles" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Copiar al portapapeles la URL que está debajo del cursor." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Copiar el título de la terminal al portapapeles" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Copiar el título de la terminal al portapapeles. Si el título no está " +"definido, esta acción no tiene efecto." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Pegar desde el portapapeles" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Pegar el contenido del portapapeles principal." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Pegar desde la selección" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Pegar el contenido del portapapeles de selección." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Iniciar búsqueda" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Iniciar una búsqueda si todavía no hay ninguna activa." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Buscar selección" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Iniciar una búsqueda del texto seleccionado actualmente." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Finalizar búsqueda" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Finalizar la búsqueda actual, si existe, y ocultar la interfaz gráfica." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Siguiente resultado de búsqueda" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Ir al siguiente resultado de búsqueda, si existe." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Resultado de búsqueda anterior" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Ir al resultado de búsqueda anterior, si existe." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Aumentar el tamaño de la fuente" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Aumentar el tamaño de la fuente en 1 punto." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Reducir el tamaño de la fuente" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Reducir el tamaño de la fuente en 1 punto." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Restablecer el tamaño de la fuente" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Restablecer el tamaño predeterminado de la fuente." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Limpiar pantalla" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Limpiar la pantalla y el historial de desplazamiento." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Seleccionar todo" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Seleccionar todo el texto de la pantalla." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Desplazarse al inicio" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Desplazarse al inicio de la pantalla." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Desplazarse al final" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Desplazarse al final de la pantalla." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Desplazarse a la selección" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Desplazarse hasta el texto seleccionado." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Desplazarse una página hacia arriba" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Desplazar la pantalla una página hacia arriba." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Desplazarse una página hacia abajo" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Desplazar la pantalla una página hacia abajo." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla a un archivo temporal y copiar la ruta" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copiar el contenido de la pantalla a un archivo temporal y copiar la ruta " +"al portapapeles." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla a un archivo temporal y pegar la ruta" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Copiar el contenido de la pantalla a un archivo temporal y pegar la ruta " +"del archivo." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla a un archivo temporal y abrirlo" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Copiar el contenido de la pantalla a un archivo temporal y abrirlo." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla como HTML a un archivo temporal y copiar la ruta" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copiar el contenido de la pantalla como HTML a un archivo temporal y copiar " +"la ruta al portapapeles." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla como HTML a un archivo temporal y pegar la ruta" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Copiar el contenido de la pantalla como HTML a un archivo temporal y pegar " +"la ruta del archivo." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla como HTML a un archivo temporal y abrirlo" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Copiar el contenido de la pantalla como HTML a un archivo temporal y abrirlo." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar pantalla como secuencias ANSI a un archivo temporal y copiar la ruta" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"archivo temporal y copiar la ruta al portapapeles." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar pantalla como secuencias ANSI a un archivo temporal y pegar la ruta" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"archivo temporal y pegar la ruta del archivo." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla como secuencias ANSI a un archivo temporal y abrirlo" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"archivo temporal y abrirlo." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección a un archivo temporal y copiar la ruta" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copiar el contenido de la selección a un archivo temporal y copiar la ruta " +"al portapapeles." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección a un archivo temporal y pegar la ruta" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Copiar el contenido de la selección a un archivo temporal y pegar la ruta " +"del archivo." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Copiar selección a un archivo temporal y abrirlo" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Copiar el contenido de la selección a un archivo temporal y abrirlo." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección como HTML a un archivo temporal y copiar la ruta" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copiar el contenido de la selección como HTML a un archivo temporal y " +"copiar la ruta al portapapeles." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección como HTML a un archivo temporal y pegar la ruta" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Copiar el contenido de la selección como HTML a un archivo temporal y pegar " +"la ruta del archivo." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar selección como HTML a un archivo temporal y abrirlo" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"Copiar el contenido de la selección como HTML a un archivo temporal y abrirlo." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar selección como secuencias ANSI a un archivo temporal y copiar la ruta" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"archivo temporal y copiar la ruta al portapapeles." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar selección como secuencias ANSI a un archivo temporal y pegar la ruta" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"archivo temporal y pegar la ruta del archivo." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar selección como secuencias ANSI a un archivo temporal y abrirlo" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"archivo temporal y abrirlo." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Abrir una ventana nueva." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Abrir una pestaña nueva." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Mover pestaña a la izquierda" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Mover la pestaña actual a la izquierda." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Mover pestaña a la derecha" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Mover la pestaña actual a la derecha." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Mover pestaña a una ventana nueva" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Mover la pestaña actual a una ventana nueva." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Alternar vista general de pestañas" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Mostrar u ocultar la vista general de pestañas." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Cambiar el título de la terminal…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Solicitar un título nuevo para la terminal actual." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Solicitar un título nuevo para la pestaña actual." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Dividir la terminal hacia la izquierda." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Dividir la terminal hacia la derecha." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Dividir la terminal hacia arriba." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Dividir la terminal hacia abajo." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Enfocar división: anterior" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Enfocar la división anterior, si existe." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Enfocar división: siguiente" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Enfocar la división siguiente, si existe." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Enfocar división: izquierda" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Enfocar la división de la izquierda, si existe." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Enfocar división: derecha" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Enfocar la división de la derecha, si existe." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Enfocar división: arriba" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Enfocar la división de arriba, si existe." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Enfocar división: abajo" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Enfocar la división de abajo, si existe." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Enfocar ventana: anterior" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Enfocar la ventana anterior, si existe." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Enfocar ventana: siguiente" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Enfocar la ventana siguiente, si existe." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Alternar zoom de la división" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Activar o desactivar el zoom de la división actual." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Alternar modo de solo lectura" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Activar o desactivar el modo de solo lectura de la terminal actual." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Igualar divisiones" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Igualar el tamaño de todas las divisiones." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Restablecer el tamaño de la ventana" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Restablecer el tamaño predeterminado de la ventana." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Alternar inspector" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Mostrar u ocultar el inspector." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Mostrar el inspector de GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Mostrar el inspector de GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Mostrar teclado en pantalla" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Mostrar el teclado en pantalla, si está disponible." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Abrir configuración con el editor del sistema operativo" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"Abrir el archivo de configuración con el editor predeterminado del sistema " +"operativo." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Abrir configuración en una ventana de terminal nueva" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Abrir el archivo de configuración en una ventana nueva usando $EDITOR o " +"$VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Recargar configuración" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Recargar el archivo de configuración." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Cerrar terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Cerrar la terminal actual." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Cerrar la pestaña actual." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Cerrar las demás pestañas" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Cerrar todas las pestañas de esta ventana, excepto la actual." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Cerrar pestañas a la derecha" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Cerrar todas las pestañas situadas a la derecha de la actual." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Cerrar la ventana actual." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Cerrar todas las ventanas" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Cerrar todas las ventanas." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Alternar maximización" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Maximizar o restaurar la ventana actual." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Alternar pantalla completa" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Activar o desactivar la pantalla completa de la ventana actual." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Alternar decoraciones de la ventana" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Mostrar u ocultar las decoraciones de la ventana." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Alternar ventana siempre visible" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Activar o desactivar el estado siempre visible de la ventana actual." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Alternar entrada segura" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Activar o desactivar el modo de entrada segura." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Alternar envío de eventos del mouse" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." msgstr "" +"Activar o desactivar el envío de eventos del mouse a las aplicaciones de " +"terminal." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Alternar opacidad del fondo" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Alternar la opacidad del fondo de una ventana que se inició transparente." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Buscar actualizaciones" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Buscar actualizaciones de la aplicación." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Deshacer" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Deshacer la última acción." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Rehacer" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Rehacer la última acción deshecha." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Salir de la aplicación." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Pon un poco de Ghostty en tu terminal." From aa25679da8c87b47e9b45410435bded87e283031 Mon Sep 17 00:00:00 2001 From: "Sl (Shahaf Levi)" Date: Thu, 13 Aug 2026 19:23:24 +0300 Subject: [PATCH 015/392] i18n: update Hebrew translations for v1.4 --- po/he.po | 370 +++++++++++++++++++++++++++---------------------------- 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/po/he.po b/po/he.po index fe94ced8c..322088287 100644 --- a/po/he.po +++ b/po/he.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-18 18:14+0300\n" +"PO-Revision-Date: 2026-08-13 19:14+0300\n" "Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd " "\n" "Language-Team: Hebrew \n" @@ -175,7 +175,7 @@ msgstr "פיצול ימינה" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "סגירת הפיצול" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -195,7 +195,7 @@ msgstr "כרטיסייה חדשה" #: src/apprt/gtk/ui/1.2/surface.blp:396 src/apprt/gtk/ui/1.5/window.blp:242 #: src/input/command.zig:613 msgid "Close Tab" -msgstr "סגור/י כרטיסייה" +msgstr "סגירת כרטיסייה" #: src/apprt/gtk/ui/1.2/surface.blp:403 msgid "Window" @@ -203,7 +203,7 @@ msgstr "חלון" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "שנה/י את כותרת החלון…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -213,7 +213,7 @@ msgstr "חלון חדש" #: src/apprt/gtk/ui/1.2/surface.blp:416 src/apprt/gtk/ui/1.5/window.blp:225 #: src/input/command.zig:630 msgid "Close Window" -msgstr "סגור/י חלון" +msgstr "סגירת החלון" #: src/apprt/gtk/ui/1.2/surface.blp:424 src/apprt/gtk/ui/1.5/window.blp:303 msgid "Config" @@ -221,11 +221,11 @@ msgstr "הגדרות" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "פתח/י הגדרות בעורך של מערכת ההפעלה" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "פתח/י הגדרות בחלון חדש" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -269,27 +269,27 @@ msgstr "הרץ/י פקודה…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "קיצור המקשים נשלל על ידי המערכת." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "קיצור המקשים נדחה על ידי המערכת." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "קיצור המקשים הגלובלי אינו זמין" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "ייצא/י אירועי קלט/פלט של המסוף" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "ייצוא" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "עריכת קובץ הגדרות" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -372,11 +372,11 @@ msgstr "שינוי כותרת הכרטיסייה" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "שינוי כותרת חלון" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" -msgstr "ההגדרות הוטענו מחדש" +msgstr "ההגדרות נטענו מחדש" #: src/apprt/gtk/class/window.zig:1830 msgid "Copied to clipboard" @@ -392,710 +392,710 @@ msgstr "המפתחים של Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "איפוס המסוף" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "אפס/י את המסוף למצב נקי." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "העתקה ללוח ההעתקה" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "" +msgstr "העתק/י את הטקסט הנבחר ללוח ההעתקה בפורמט טקסט פשוט ומעוצב." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "העתקת הבחירה כטקסט פשוט ללוח ההעתקה" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "העתק/י את הטקסט הנבחר כטקסט פשוט ללוח ההעתקה." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "העתקת הבחירה כרצפי ANSI ללוח ההעתקה" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "העתק/י את הטקסט הנבחר כרצפי בריחה של ANSI ללוח ההעתקה." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "העתקת הבחירה כHTML ללוח ההעתקה" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "העתק/י את הטקסט הנבחר כHTML ללוח ההעתקה." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "העתקת כתובת אתר ללוח ההעתקה" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "העתק/י את כתובת האתר שתחת הסמן ללוח ההעתקה." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "העתקת כותרת המסוף ללוח ההעתקה" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "" +msgstr "העתק/י את כותרת המסוף ללוח ההעתקה. אם כותרת המסוף לא הוגדרה, לא תהיה לכך השפעה." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "הדבקה מלוח ההעתקה" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "הדבק/י את התוכן של לוח ההעתקה הראשי." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "הדבקה מהבחירה" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "הדבק/י את התוכן של לוח ההעתקה של הבחירה." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "התחלת חיפוש" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "התחל/י חיפוש אם אינו פעיל כבר." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "חיפוש בטקסט הנבחר" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "התחל/י חיפוש עבור הטקסט הנבחר הנוכחי." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "סיום חיפוש" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "סיים/י את החיפוש הנוכחי אם קיים והסתר/י את רכיבי ממשק המשתמש." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "תוצאת החיפוש הבאה" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "נווט/י לתוצאת החיפוש הבאה, אם ישנה." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "תוצאת החיפוש הקודמת" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "נווט/י לתוצאת החיפוש הקודמת, אם ישנה." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "הגדלת גודל הגופן" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "הגדל/י את גודל הגופן בנקודה אחת." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "הקטנת גודל הגופן" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "הקטן/י את גודל הגופן בנקודה אחת." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "איפוס גודל הגופן" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "אפס/י את גודל הגופן לברירת המחדל." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "ניקוי מסך" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "נקה/י את המסך ואת היסטוריית הגלילה." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "בחר/י הכל" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "בחר/י את כל הטקסט במסך." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "גלילה למעלה" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "גלול/י לראש המסך." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "גלילה למטה" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "גלול/י לתחתית המסך." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "גלילה עד לבחירה" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "גלול/י לטקסט הנבחר." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "גלילה של דף אחד למעלה" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "גלול/י את המסך דף אחד למעלה." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "גלילה של דף אחד למטה" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "גלול/י את המסך דף אחד למטה." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "העתקת המסך לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "העתק/י את תוכן המסך לקובץ זמני ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת המסך לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "העתק/י את תוכן המסך לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "העתקת המסך לקובץ זמני ופתיחתו" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "העתק/י את תוכן המסך לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "העתקת המסך כHTML לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "העתק/י את תוכן המסך כHTML ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת המסך כHTML לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "העתק/י את תוכן המסך כHTML לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "העתקת המסך כHTML לקובץ זמני ופתיחתו" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "העתק/י את תוכן המסך כHTML לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "העתקת תוכן המסך כרצפי ANSI לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "העתק/י את תוכן המסך כרצפי בריחה של ANSI ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת תוכן המסך כרצפי ANSI לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "העתק/י את תוכן המסך כרצפי בריחה של ANSI לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "העתקת תוכן המסך כרצפי ANSI לקובץ זמני ופתיחתו" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "העתק/י את תוכן המסך כרצפי בריחה של ANSI לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "העתק/י את הבחירה לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "העתק/י את תוכן הבחירה ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת הבחירה לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "העתק/י את תוכן הבחירה לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "העתקת הבחירה לקובץ זמני ופתיחתו" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "העתק/י את תוכן הבחירה לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "העתקת הבחירה כHTML לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "העתק/י את תוכן הבחירה כHTML ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת הבחירה כHTML לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "העתקת תוכן הבחירה כHTML לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "העתקת הבחירה כHTML לקובץ זמני ופתיחתו" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "העתקת תוכן הבחירה כHTML לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "העתקת הבחירה כרצפי ANSI לקובץ זמני והעתקת הנתיב לקובץ" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "העתק/י את תוכן הבחירה כרצפי בריחה של ANSI ולאחר מכן העתק/י את הנתיב לקובץ הזמני אל לוח ההעתקה." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "העתקת הבחירה כרצפי ANSI לקובץ זמני והדבקת הנתיב לקובץ" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "העתק/י את תוכן הבחירה כרצפי בריחה של ANSI לקובץ זמני ולאחר מכן הדבק/י את הנתיב לקובץ אל המסוף." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "העתקת הבחירה כרצפי ANSI לקובץ זמני ופתיחתו" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "העתק/י את תוכן הבחירה כרצפי בריחה של ANSI לקובץ זמני ולאחר מכן פתח/י את הקובץ." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "פתח/י חלון חדש." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "פתח/י כרטיסייה חדשה." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "הזזת הכרטיסייה שמאלה" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "הזז/י את הכרטיסייה הנוכחית שמאלה." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "הזזת הכרטיסייה ימינה" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "הזז/י את הכרטיסייה הנוכחית ימינה." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "העברת הכרטיסייה לחלון חדש" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "העבר/י את הכרטיסייה הנוכחית לחלון חדש." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "הצגה/הסתרה של סקירת כרטיסיות" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "הצג/י או הסתר/י את סקירת הכרטיסיות." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "שנה/י את כותרת המסוף…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "בקש/י כותרת חדשה עבור המסוף הנוכחי." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "בקש/י כותרת חדשה עבור הכרטיסייה הנוכחית." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "פצל/י את המסוף שמאלה." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "פצל/י את המסוף ימינה." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "פצל/י את המסוף למעלה." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "פצל/י את המסוף למטה." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "התמקדות בפיצול: הקודם" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "התמקד/י בפיצול הקודם, אם הוא קיים." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "התמקדות בפיצול: הבא" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "התמקד/י בפיצול הבא, אם הוא קיים." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "התמקדות בפיצול: שמאלה" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "התמקד/י בפיצול שמאלה, אם הוא קיים." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "התמקדות בפיצול: ימינה" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "התמקד/י בפיצול ימינה, אם הוא קיים." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "התמקדות בפיצול: למעלה" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "התמקד/י בפיצול שלמעלה, אם הוא קיים." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "התמקדות בפיצול: למטה" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "התמקד/י בפיצול שלמטה, אם הוא קיים." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "התמקדות בחלון: הקודם" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "התמקד/י בחלון הקודם, אם הוא קיים." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "התמקדות בחלון: הבא" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "התמקד/י בחלון הבא, אם הוא קיים." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "שינוי מצב הזום של הפיצול" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "שנה/י את מצב הזום של הפיצול הנוכחי." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "שינוי המצב לקריאה בלבד" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "שנה/י את מצב הקריאה בלבד של המשטח הנוכחי." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "השוואת גודל פיצולים" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "השווה/י את המימדים של כל הפיצולים." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "איפוס גודל החלון" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "אפס/י את גודל החלון לברירת המחדל." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "הצגה/הסתרה של בודק המסוף" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "הצג/י או הסתר/י את בודק המסוף." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "הצגה/הסתרה של בודק הGTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "הצג/י או הסתר/י את בודק הGTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "הצגת מקלדת על המסך" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "הצג/י את המקלדת שעל המסך אם היא קיימת." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "פתיחת ההגדרות באמצעות עורך הטקסט של המערכת" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "פתח/י את קובץ ההגדרות בעורך טקסט שמוגדר כברירת המחדל של מערכת ההפעלה." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "פתיחת ההגדרות בחלון מסוף חדש" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "פתח/י את קובץ ההגדרות בחלון חדש באמצעות $EDITOR או $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "טעינת ההגדרות מחדש" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "טען/י מחדש את קובץ ההגדרות." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "סגירת המסוף" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "סגור/י את המסוף הנוכחי." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "סגור/י את הכרטיסייה הנוכחית." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "סגור/י כרטיסיות אחרות" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "סגור/י את כל הכרטיסיות בחלון זה למעט הנוכחית." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "סגירת כרטיסיות מימין" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "סגור/י את כל הכרטיסיות שנמצאות מימין לכרטיסייה הנוכחית." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "סגור/י את החלון הנוכחי." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "סגירת כל החלונות" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "סגור/י את כל החלונות." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "שינוי הגדלה למקסימום" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "שנה/י את מצב ההגדלה למקסימום של החלון הנוכחי." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "הפעלה/ביטול של מסך מלא" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "שנה/י את מצב המסך המלא של החלון הנוכחי." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "הצגה/הסתרה של עיטורי חלון" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "הצג/י או הסתר/י את עיטורי החלון." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "שינוי של מצב ציפה מעל" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "הפעל/י או בטל/י את מצב הציפה מעל של החלון הנוכחי." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "שינוי של מצב קלט מאובטח" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "הפעל/י או בטל/י את מצב הקלט המאובטח." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "שינוי של דיווחי העכבר" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "הפעל/י או בטל/י את הדיווח של אירועי עכבר לאפליקציות מסוף." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "שינוי של אטימות הרקע" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "שנה/י את אטימות הרקע של חלון שהתחיל כשקוף." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "בדיקת עדכונים" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "בדוק/י אם קיימים עדכונים לאפליקציה." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "ביטול פעולה" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "בטל/י את הפעולה האחרונה." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "ביצוע פעולה שוב" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "בצע/י שוב את הפעולה האחרונה שבוטלה." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "צא/י מהאפליקציה." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "הכנס/י קצת Ghostty למסוף שלך." From d7f782dbcc4471eb697cce0ce888c83787ca38f0 Mon Sep 17 00:00:00 2001 From: "Sl (Shahaf Levi)" Date: Thu, 13 Aug 2026 20:03:41 +0300 Subject: [PATCH 016/392] i18n: update Hebrew translations for v1.4 --- po/he.po | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/po/he.po b/po/he.po index 322088287..636706fcd 100644 --- a/po/he.po +++ b/po/he.po @@ -184,7 +184,7 @@ msgstr "כרטיסייה" #: src/apprt/gtk/ui/1.2/surface.blp:386 src/apprt/gtk/ui/1.5/window.blp:232 #: src/apprt/gtk/ui/1.5/window.blp:339 src/input/command.zig:464 msgid "Change Tab Title…" -msgstr "שנה/י את כותרת הכרטיסייה…" +msgstr "שינוי כותרת הכרטיסייה…" #: src/apprt/gtk/ui/1.2/surface.blp:391 src/apprt/gtk/ui/1.5/window.blp:60 #: src/apprt/gtk/ui/1.5/window.blp:110 src/apprt/gtk/ui/1.5/window.blp:237 @@ -203,7 +203,7 @@ msgstr "חלון" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "שנה/י את כותרת החלון…" +msgstr "שינוי כותרת החלון…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +221,15 @@ msgstr "הגדרות" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "פתח/י הגדרות בעורך של מערכת ההפעלה" +msgstr "פתיחת ההגדרות בעורך של מערכת ההפעלה" + +#: src/input/command.zig:589 +msgid "Open the config file with the OS's default editor." +msgstr "פתח/י את קובץ ההגדרות בעורך טקסט שמוגדר כברירת המחדל של מערכת ההפעלה #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "פתח/י הגדרות בחלון חדש" +msgstr "פתיחת ההגדרות בחלון חדש" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -281,7 +285,7 @@ msgstr "קיצור המקשים הגלובלי אינו זמין" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "ייצא/י אירועי קלט/פלט של המסוף" +msgstr "ייצוא אירועי קלט/פלט של המסוף" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" @@ -372,7 +376,7 @@ msgstr "שינוי כותרת הכרטיסייה" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "שינוי כותרת חלון" +msgstr "שינוי כותרת החלון" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -802,11 +806,11 @@ msgstr "שנה/י את כותרת המסוף…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "בקש/י כותרת חדשה עבור המסוף הנוכחי." +msgstr "הצג/י בקשה לכותרת חדשה עבור המסוף הנוכחי." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "בקש/י כותרת חדשה עבור הכרטיסייה הנוכחית." +msgstr "הצג/י בקשה לכותרת חדשה עבור הכרטיסייה הנוכחית." #: src/input/command.zig:478 msgid "Split the terminal to the left." @@ -950,7 +954,7 @@ msgstr "פתיחת ההגדרות באמצעות עורך הטקסט של המע #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "פתח/י את קובץ ההגדרות בעורך טקסט שמוגדר כברירת המחדל של מערכת ההפעלה." +msgstr "פתח/י את קובץ ההגדרות בעורך הטקסט שמוגדר כברירת המחדל של מערכת ההפעלה." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" From 7c1d0d414c4bfd976c6ae422f170a4b6249716d5 Mon Sep 17 00:00:00 2001 From: "Sl (Shahaf Levi)" Date: Thu, 13 Aug 2026 20:06:33 +0300 Subject: [PATCH 017/392] i18n: update Hebrew translations for v1.4 Removed translation for opening config file with default editor. --- po/he.po | 4 ---- 1 file changed, 4 deletions(-) diff --git a/po/he.po b/po/he.po index 636706fcd..3d9b792fb 100644 --- a/po/he.po +++ b/po/he.po @@ -223,10 +223,6 @@ msgstr "הגדרות" msgid "Open Configuration in OS Editor" msgstr "פתיחת ההגדרות בעורך של מערכת ההפעלה" -#: src/input/command.zig:589 -msgid "Open the config file with the OS's default editor." -msgstr "פתח/י את קובץ ההגדרות בעורך טקסט שמוגדר כברירת המחדל של מערכת ההפעלה - #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" msgstr "פתיחת ההגדרות בחלון חדש" From 7c4c7adadc8b080ab168ed0af48319185dcbd2ba Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 13 Aug 2026 12:20:40 -0500 Subject: [PATCH 018/392] pkg/wuffs: use C-only mirror of wuffs This prevents us from pulling in test images that trigger some anti-virus scanners. It's also smaller since it only has the necessary bits that we need. This also updates to the latest release: 0.4.0-alpha.10. --- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 8 ++++---- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 8 ++++---- pkg/wuffs/build.zig.zon | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build.zig.zon.json b/build.zig.zon.json index 9c6e316bf..b5fc75055 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -149,10 +149,10 @@ "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", "hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=" }, - "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": { + "N-V-__8AAP5JWgCGP_AD0teWpa4krRvE9VPZzvviGdbmN4jI": { "name": "wuffs", - "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", - "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" + "url": "git+https://github.com/google/wuffs-mirror-release-c.git?rev=v0.4.0-alpha.10#7411f488fe2e2c205c3d3b3d28638b7356522930", + "hash": "sha256-AMuAaCbNJYnSeac1B1IRSUh4rlE2KphT04/Ak4Pig5M=" }, "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-": { "name": "z2d", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 01bc76f67..fd44be9f5 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -368,12 +368,12 @@ in }; } { - name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs"; + name = "N-V-__8AAP5JWgCGP_AD0teWpa4krRvE9VPZzvviGdbmN4jI"; path = fetchZigArtifact { name = "wuffs"; - url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz"; - hash = "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="; - unpack = false; + url = "git+https://github.com/google/wuffs-mirror-release-c.git?rev=v0.4.0-alpha.10#7411f488fe2e2c205c3d3b3d28638b7356522930"; + hash = "sha256-AMuAaCbNJYnSeac1B1IRSUh4rlE2KphT04/Ak4Pig5M="; + unpack = true; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 9c726c1fc..4ddc0fe85 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -1,3 +1,4 @@ +git+https://github.com/google/wuffs-mirror-release-c.git?rev=v0.4.0-alpha.10#7411f488fe2e2c205c3d3b3d28638b7356522930 git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940 git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70 https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz @@ -28,7 +29,6 @@ https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.ta https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz -https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz https://deps.files.ghostty.org/z2d-7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index d03df3d72..d9c0954f0 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -180,10 +180,10 @@ "sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8" }, { - "type": "archive", - "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", - "dest": "vendor/p/N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs", - "sha256": "9e4cd20abe96e6c4c6ede9c3057108860126e7be2e2c3e35515476c250be1c13" + "type": "git", + "url": "https://github.com/google/wuffs-mirror-release-c.git", + "commit": "7411f488fe2e2c205c3d3b3d28638b7356522930", + "dest": "vendor/p/N-V-__8AAP5JWgCGP_AD0teWpa4krRvE9VPZzvviGdbmN4jI" }, { "type": "archive", diff --git a/pkg/wuffs/build.zig.zon b/pkg/wuffs/build.zig.zon index 1397d894b..f66d5c6c7 100644 --- a/pkg/wuffs/build.zig.zon +++ b/pkg/wuffs/build.zig.zon @@ -11,8 +11,8 @@ // google/wuffs .wuffs = .{ - .url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", - .hash = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs", + .url = "git+https://github.com/google/wuffs-mirror-release-c.git?rev=v0.4.0-alpha.10#7411f488fe2e2c205c3d3b3d28638b7356522930", + .hash = "N-V-__8AAP5JWgCGP_AD0teWpa4krRvE9VPZzvviGdbmN4jI", .lazy = true, }, From 47703753ad8f313601689d3eb6087469f5665d16 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:03:00 +0000 Subject: [PATCH 019/392] Update VOUCHED list (#13793) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13791#discussioncomment-18005730) from @jcollie. Vouch: @dolzenko Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index b8b62d1dc..937fb13bb 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -100,6 +100,7 @@ diaaeddin dkinzler dmehala dobbylee +dolzenko doprz douglance douglas From bb019cac272a953c6338b2ab709d6ae26725c3a5 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:03:40 +0000 Subject: [PATCH 020/392] Update VOUCHED list (#13794) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13792#discussioncomment-18005749) from @jcollie. Vouch: @dmunozv04 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 937fb13bb..954d23fd0 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -99,6 +99,7 @@ devsunb diaaeddin dkinzler dmehala +dmunozv04 dobbylee dolzenko doprz From 27d1642879adb0da7a363cbfca67c11a102cbc48 Mon Sep 17 00:00:00 2001 From: Emir SARI Date: Thu, 13 Aug 2026 00:25:49 +0300 Subject: [PATCH 021/392] Update Turkish translations --- po/tr.po | 358 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 195 insertions(+), 163 deletions(-) diff --git a/po/tr.po b/po/tr.po index d858522fd..2d796d9a2 100644 --- a/po/tr.po +++ b/po/tr.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-09 22:18+0300\n" +"PO-Revision-Date: 2026-08-13 20:33+0300\n" "Last-Translator: Emir SARI \n" "Language-Team: Turkish\n" "Language: tr\n" @@ -176,7 +176,7 @@ msgstr "Sağa Doğru Böl" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Bölmeyi Kapat" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -204,7 +204,7 @@ msgstr "Pencere" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Pencere Başlığını Değiştir…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -222,11 +222,11 @@ msgstr "Yapılandırma" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Yapılandırmayı İşletim Sistemi Düzenleyicisinde Aç" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Yapılandırmayı Yeni Pencerede Aç" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -270,27 +270,27 @@ msgstr "Bir komut çalıştır…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Tuş atama, sistem tarafından yürürlükten kaldırıldı." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Tuş atama, sistem tarafından reddedildi." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Global tuş atama kullanılamıyor" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Uçbirim G–Ç Olaylarını Dışa Aktar" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Dışa Aktar" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Yapılandırma dosyası düzenleniyor" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -376,7 +376,7 @@ msgstr "Sekme Başlığını Değiştir" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Pencere Başlığını Değiştir" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -396,710 +396,742 @@ msgstr "Ghostty Geliştiricileri" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Uçbirimi Sıfırla" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Uçbirimi, temiz bir duruma sıfırlayın." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Panoya Kopyala" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Seçili metni panoya hem düz hem de biçimlendirilmiş metin olarak kopyalayın." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Seçimi Panoya Düz Metin Olarak Kopyala" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Seçili metni panoya düz metin olarak kopyalayın." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Seçimi Panoya ANSI Dizisi Olarak Kopyala" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Seçili metni panoya ANSI kaçış dizileri olarak kopyalayın." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Seçimi Panoya HTML Olarak Kopyala" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Seçili metni panoya HTML olarak kopyalayın." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "URL’yi Panoya Kopyala" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "İmleç altındaki URL’yi panoya kopyalayın." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Uçbirim Başlığını Panoya Kopyala" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Uçbirim başlığını panoya kopyalayın. Uçbirimde başlık yoksa bunun bir etkisi " +"olmaz." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Panodan Yapıştır" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Ana panonun içeriğini yapıştırın." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Seçimden Yapıştır" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Seçim panosunun içeriğini yapıştırın." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Aramayı Başlat" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Hâlihazırda etkin bir tane yoksa bir arama başlatın." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Seçimde Ara" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Geçerli metin seçiminde bir arama başlatın." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Aramayı Sonlandır" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Varsa aramayı sonlandırın ve açık grafik arayüz ögelerini gizleyin." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Sonraki Arama Sonucu" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Varsa sonraki arama sonucuna gidin." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Önceki Arama Sonucu" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Varsa önceki arama sonucuna gidin." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Yazı Tipi Puntosunu Artır" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Yazı tipi puntosunu 1 artırın." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Yazı Tipi Puntosunu Azalt" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Yazı tipi puntosunu 1 azaltın." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Yazı Tipi Puntosunu Sıfırla" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Yazı tipi puntosunu öntanımlı değere sıfırlayın." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Ekranı Temizle" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Ekranı ve geçmişi temizleyin." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Tümünü Seç" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Ekrandaki tüm metni seçin." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "En Üste Kaydır" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Ekranın en başına kaydırın." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "En Alta Kaydır" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Ekranın en sonuna kaydırın." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Seçime Kaydır" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Seçili metne kaydırın." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Bir Sayfa Yukarı Kaydır" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Ekranı bir sayfa yukarı kaydırın." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Bir Sayfa Aşağı Kaydır" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Ekranı bir sayfa aşağı kaydırın." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Ekranı Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Ekran içeriğini bir geçici dosyaya koyar ve dosyanın yolunu panoya kopyalar." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Ekranı Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Ekran içeriğini bir geçici dosyaya koyar ve dosyaya olan yolu yapıştırır." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Ekranı Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Ekran içeriğini bir geçici dosyaya koyar ve dosyayı açar." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Ekranı HTML Olarak Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Ekran içeriğini HTML olarak bir geçici dosyaya koyar ve dosyanın yolunu " +"panoya kopyalar." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Ekranı HTML Olarak Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Ekran içeriğini HTML olarak bir geçici dosyaya koyar ve dosyaya olan yolu " +"yapıştırır." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Ekranı HTML Olarak Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Ekran içeriğini HTML olarak bir geçici dosyaya koyar ve dosyayı açar." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Ekranı ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Ekran içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyanın yolunu panoya kopyalar." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Ekranı ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Ekran içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyaya olan yolu yapıştırır." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Ekranı ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Ekran içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyayı açar." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Seçimi Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Seçim içeriğini bir geçici dosyaya koyar ve dosyanın yolunu panoya kopyalar." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Seçimi Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Seçim içeriğini bir geçici dosyaya koyar ve dosyaya olan yolu yapıştırır." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Seçimi Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Seçim içeriğini bir geçici dosyaya koyar ve dosyayı açar." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Seçimi HTML Olarak Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Seçim içeriğini HTML olarak bir geçici dosyaya koyar ve dosyanın yolunu " +"panoya kopyalar." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Seçimi HTML Olarak Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Seçim içeriğini HTML olarak bir geçici dosyaya koyar ve dosyaya olan yolu " +"yapıştırır." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Ekranı HTML Olarak Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Seçim içeriğini HTML olarak bir geçici dosyaya koyar ve dosyayı açar." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Seçimi ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Yolu Kopyala" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Seçim içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyanın yolunu panoya kopyalar." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Seçimi ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Yolu Yapıştır" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Seçim içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyaya olan yolu yapıştırır." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Seçimi ANSI Dizisi Olarak Geçici Dosyaya Yerleştir ve Dosyayı Aç" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Seçim içeriğini ANSI kaçış dizileri olarak bir geçici dosyaya koyar ve " +"dosyayı açar." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Yeni bir pencere açın." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Yeni bir sekme açın." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Sekmeyi Sola Taşı" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Geçerli sekmeyi sola doğru taşıyın." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Sekmeyi Sağa Taşı" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Geçerli sekmeyi sağa doğru taşıyın." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Sekmeyi Yeni Pencereye Taşı" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Geçerli sekmeyi yeni bir pencereye taşıyın." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Sekme Genel Görünümünü Aç/Kapat" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Sekme genel görünümünü açın/kapatın." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Uçbirim Başlığını Değiştir…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Geçerli uçbirim için yeni bir başlık koyun." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Geçerli sekme içir yeni bir başlık koyun." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Uçbirimi sola doğru bölün." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Uçbirimi sağa doğru bölün." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Uçbirimi yukarı doğru bölün." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Uçbirimi aşağı doğru bölün." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Bölmeye Odaklan: Önceki" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Varsa önceki bölmeye odaklanın." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Bölmeye Odaklan: Sonraki" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Varsa sonraki bölmeye odaklanın." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Bölmeye Odaklan: Sol" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Varsa soldaki bölmeye odaklanın." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Bölmeye Odaklan: Sağ" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Varsa sağdaki bölmeye odaklanın." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Bölmeye Odaklan: Yukarı" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Varsa yukarıdaki bölmeye odaklanın." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Bölmeye Odaklan: Aşağı" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Varsa aşağıdaki bölmeye odaklanın." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Pencereye Odaklan: Önceki" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Varsa önceki pencereye odaklanın." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Pencereye Odaklan: Sonraki" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Varsa sonraki pencereye odaklanın." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Bölme Yakınlaştırmasını Aç/Kapat" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Geçerli bölmenin yakınlaştırma durumunu açın/kapatın." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Salt Okunur Kipi Aç/Kapat" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Geçerli yüzey için salt okunur kipi açın/kapatın." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Bölmeleri Eşitle" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Tüm bölmelerin boyutlarını eşitleyin." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Pencere Boyutunu Sıfırla" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Pencere boyutunu öntanımlı değere sıfırlayın." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Denetçiyi Aç/Kapat" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Denetçiyi açın/kapatın." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "GTK Denetçisini Göster" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "GTK denetçisini gösterin." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Ekran Üzeri Klavyeyi Göster" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Varsa ekran üzeri klavyeyi gösterin." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Yapılandırmayı İşletim Sistemi Düzenleyicisini Kullanarak Aç" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"Yapılandırma dosyasını işletim sisteminin öntanımlı düzenleyicisini " +"kullanarak açın." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Yapılandırmayı Yeni Uçbirim Penceresinde Aç" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Yapılandırmayı yeni bir pencerede $EDITOR veya $VISUAL kullanarak açın." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Yapılandırmayı Yeniden Yükle" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Yapılandırma dosyasını yeniden yükleyin." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Uçbirimi Kapat" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Geçerli uçbirimi kapatın." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Geçerli sekmeyi kapatın." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Diğer Sekmeleri Kapat" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Geçerli sekme dışında, bu penceredeki tüm sekmeleri kapatın." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Sağdaki Sekmeleri Kapat" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Geçerli sekmenin sağındaki tüm sekmeleri kapatın." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Geçerli pencereyi kapatın." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Tüm Pencereleri Kapat" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Tüm pencereleri kapatın." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Ekranı Kapla/Küçült" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." msgstr "" +"Geçerli pencereyi ekranı kaplayacak şekilde büyütün veya eski hâline geri " +"alın." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Tam Ekranı Aç/Kapat" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Geçerli pencereyi tam ekran yapın veya eski hâline geri alın." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Pencere Dekorasyonlarını Aç/Kapat" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Pencere dekorasyonlarını açın/kapatın." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Pencereyi Yüzer Yap" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Geçerli pencerenin en üstte kalma durumunu açın/kapatın." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Güvenli Girişi Aç/Kapat" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Güvenli giriş kipini açın/kapatın." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Fare Bildirmeyi Aç/Kapat" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Fare olaylarını uçbirim uygulamalarına bildirmeyi açın/kapatın." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Arka Plan Matlığını Aç/Kapat" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "Saydam olarak açılmış bir pencerenin arka plan matlığını açın/kapatın." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Güncellemeleri Denetle" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Uygulamaya olan güncellemeleri denetleyin." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Geri Al" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Son eylemi geri alın." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Yinele" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Son geri alınmış eylemi yineleyin." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Uygulamadan çıkın." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Uçbiriminize minik bir Ghostty koyun." From 43fe699071c7dceb161dc3b0c04fce46ade36174 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:55:20 +0000 Subject: [PATCH 022/392] Update VOUCHED list (#13797) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13796#discussioncomment-18006390) from @jcollie. Vouch: @sghng Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 954d23fd0..63aaea05f 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -282,6 +282,7 @@ sanjj1 secrus seruman seyoungjeong +sghng shorsher silveirapf simonbcn From 8fd2013a09c9105507a98360318ec7f5e802d50b Mon Sep 17 00:00:00 2001 From: trag1c Date: Thu, 13 Aug 2026 21:09:31 +0200 Subject: [PATCH 023/392] i18n: add missing Polish translations + minor fixes --- po/pl.po | 363 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 198 insertions(+), 165 deletions(-) diff --git a/po/pl.po b/po/pl.po index 3d661ca3f..0ed5a2435 100644 --- a/po/pl.po +++ b/po/pl.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-11 14:12+0100\n" +"PO-Revision-Date: 2026-08-13 21:08+0100\n" "Last-Translator: trag1c \n" "Language-Team: Polish \n" "Language: pl\n" @@ -176,7 +176,7 @@ msgstr "Podziel w prawo" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Zamknij podział" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -204,7 +204,7 @@ msgstr "Okno" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Zmień tytuł okna…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -222,15 +222,15 @@ msgstr "Konfiguracja" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Otwórz konfigurację w systemowym edytorze" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Otwórz konfigurację w nowym oknie" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." -msgstr "Pozostaw puste by przywrócić domyślny tytuł." +msgstr "Pozostaw puste, by przywrócić domyślny tytuł." #: src/apprt/gtk/ui/1.5/title-dialog.blp:9 msgid "OK" @@ -270,27 +270,27 @@ msgstr "Wykonaj komendę…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Skrót klawiszowy został unieważniony przez system." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Skrót klawiszowy został odrzucony przez system." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Globalny skrót klawiszowy jest niedostępny" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Wyeksportuj zdarzenia IO terminala" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Eksportuj" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Edytowanie pliku konfiguracyjnego" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -350,7 +350,7 @@ msgstr "Wszystkie sesje terminala w obecnym oknie zostaną zakończone." #: src/apprt/gtk/class/close_confirmation_dialog.zig:196 msgid "The currently running process in this split will be terminated." -msgstr "Wszyskie trwające procesy w obecnym podziale zostaną zakończone." +msgstr "Wszystkie trwające procesy w obecnym podziale zostaną zakończone." #: src/apprt/gtk/class/surface.zig:1181 msgid "Command Finished" @@ -376,7 +376,7 @@ msgstr "Zmień tytuł karty" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Zmień tytuł okna" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -396,710 +396,743 @@ msgstr "Twórcy Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Zresetuj terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Zresetuj terminal do czystego stanu." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Skopiuj do schowka" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Skopiuj zaznaczony tekst do schowka jako zwykły tekst oraz z " +"zachowaniem formatowania." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Skopiuj zaznaczenie jako zwykły tekst do schowka" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Skopiuj zaznaczony tekst do schowka jako zwykły tekst." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Skopiuj zaznaczenie z sekwencjami ANSI do schowka" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Skopiuj zaznaczony tekst do schowka jako sekwencje ucieczki ANSI." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Skopiuj zaznaczenie jako HTML do schowka" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Skopiuj zaznaczony tekst do schowka jako HTML." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Skopiuj URL do schowka" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Skopiuj URL pod kursorem do schowka." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Skopiuj tytuł terminala do schowka" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Skopiuj tytuł terminala do schowka. Ta komenda nie ma efektu, jeśli tytuł " +"terminala nie został ustawiony." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Wklej ze schowka" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Wklej zawartość głównego schowka." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Wklej z zaznaczenia" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Wklej zawartość schowka zaznaczenia." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Rozpocznij wyszukiwanie" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Rozpocznij wyszukiwanie (jeśli nie jest już aktywne)." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Szukaj zaznaczenia" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Rozpocznij wyszukiwanie obecnie zaznaczonego tekstu." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Zakończ wyszukiwanie" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Zakończ obecne wyszukiwanie (jeśli trwa) i ukryj wszelkie elementy GUI." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Następny wynik wyszukiwania" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Przeskocz do następnego wyniku wyszukiwania (jeśli taki jest)." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Poprzedni wynik wyszukiwania" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Przeskocz do poprzedniego wyniku wyszukiwania (jeśli taki jest)." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Zwiększ rozmiar czcionki" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Zwiększ rozmiar czcionki o 1 punkt." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Zmniejsz rozmiar czcionki" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Zmniejsz rozmiar czcionki o 1 punkt." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Zresetuj rozmiar czcionki" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Zresetuj rozmiar czcionki do domyślnego." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Wyczyść ekran" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Wyczyść ekran i historię przewijania." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Zaznacz wszystko" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Zaznacz cały tekst na ekranie." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Przewiń do góry" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Przewiń do góry ekranu." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Przewiń do dołu" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Przewiń do dołu ekranu." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Przewiń do zaznaczenia" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Przewiń do zaznaczonego tekstu." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Przewiń o stronę w górę" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Przewiń ekran w górę o jedną stronę." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Przewiń o stronę w dół" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Przewiń ekran w dół o jedną stronę." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Skopiuj ekran do tymczasowego pliku i skopiuj ścieżkę" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Skopiuj zawartość ekranu do tymczasowego pliku i skopiuj jego ścieżkę " +"do schowka." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Skopiuj ekran do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Skopiuj zawartość ekranu do tymczasowego pliku i wklej jego ścieżkę." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Skopiuj ekran do tymczasowego pliku i otwórz" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Skopiuj zawartość ekranu do tymczasowego pliku i otwórz go." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Skopiuj ekran jako HTML do tymczasowego pliku i skopiuj ścieżkę" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Skopiuj zawartość ekranu jako HTML do tymczasowego pliku i skopiuj jego " +"ścieżkę do schowka." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Skopiuj ekran jako HTML do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Skopiuj zawartość ekranu jako HTML do tymczasowego pliku i wklej jego ścieżkę." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Skopiuj ekran jako HTML do tymczasowego pliku i otwórz" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Skopiuj zawartość ekranu jako HTML do tymczasowego pliku i otwórz go." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Skopiuj ekran jako sekwencje ANSI do tymczasowego pliku i otwórz" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Skopiuj zawartość ekranu jako sekwencje ucieczki ANSI do tymczasowego pliku i " +"wklej jego ścieżkę." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Skopiuj ekran jako sekwencje ANSI do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Skopiuj zawartość ekranu jako sekwencje ucieczki ANSI do tymczasowego pliku " +"i wklej jego ścieżkę." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Skopiuj ekran jako sekwencje ANSI do tymczasowego pliku i otwórz" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Skopiuj zawartość ekranu jako sekwencje ucieczki ANSI do tymczasowego pliku " +"i otwórz go." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Skopiuj zaznaczenie do tymczasowego pliku i skopiuj ścieżkę" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Skopiuj zawartość zaznaczenia do tymczasowego pliku i skopiuj jego ścieżkę " +"do schowka." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Skopiuj zaznaczenie do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Skopiuj zawartość zaznaczenia do tymczasowego pliku i wklej jego ścieżkę." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Skopiuj zaznaczenie do tymczasowego pliku i otwórz" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Skopiuj zawartość zaznaczenia do tymczasowego pliku i otwórz go." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Skopiuj zaznaczenie jako HTML do tymczasowego pliku i skopiuj ścieżkę" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Skopiuj zawartość zaznaczenia jako HTML do tymczasowego pliku i skopiuj jego " +"ścieżkę do schowka." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Skopiuj zaznaczenie jako HTML do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Skopiuj zawartość zaznaczenia jako HTML do tymczasowego pliku i wklej jego " +"ścieżkę." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Skopiuj zaznaczenie jako HTML do tymczasowego pliku i otwórz" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"Skopiuj zawartość zaznaczenia jako HTML do tymczasowego pliku i otwórz go." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Skopiuj zaznaczenie jako sekwencje ANSI do tymczasowego pliku i skopiuj " +"ścieżkę" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Skopiuj zawartość zaznaczenia jako sekwencje ucieczki ANSI do tymczasowego " +"pliku i skopiuj jego ścieżkę do schowka." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Skopiuj zaznaczenie jako sekwencje ANSI do tymczasowego pliku i wklej ścieżkę" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Skopiuj zawartość zaznaczenia jako sekwencje ucieczki ANSI do tymczasowego " +"pliku i wklej jego ścieżkę." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Skopiuj zaznaczenie jako sekwencje ANSI do tymczasowego pliku i otwórz" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Skopiuj zawartość zaznaczenia jako sekwencje ucieczki ANSI do tymczasowego " +"pliku i otwórz go." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Otwórz w nowym oknie." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Otwórz w nowej karcie." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Przenieś kartę w lewo" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Przenieś obecną kartę w lewo." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Przenieś kartę w prawo" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Przenieś obecną kartę w prawo." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Przenieś kartę do nowego okna" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Przenieś obecną kartę do nowego okna." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Przełącz widok kart" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Przełącz widok kart." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Zmień tytuł terminala…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Poproś o nowy tytuł dla obecnego terminala." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Poproś o nowy tytuł dla obecnej karty." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Podziel terminal w lewo." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Podziel terminal w prawo." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Podziel terminal w górę." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Podziel terminal w dół." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Przejdź do podziału: poprzedni" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Przejdź do poprzedniego podziału, jeśli taki jest." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Przejdź do podziału: następny" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Przejdź do następnego podziału, jeśli taki jest." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Przejdź do podziału: w lewo" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Przejdź do podziału po lewej, jeśli taki jest." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Przejdź do podziału: w prawo" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Przejdź do podziału po prawej, jeśli taki jest." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Przejdź do podziału: w górę" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Przejdź do podziału wyżej, jeśli taki jest." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Przejdź do podziału: w dół" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Przejdź do podziału niżej, jeśli taki jest." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Przejdź do okna: poprzednie" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Przejdź do poprzedniego okna, jeśli takie jest." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Przejdź do okna: następne" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Przejdź do następnego okna, jeśli takie jest." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Przełącz zbliżenie na podział" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Włącz/wyłącz zbliżenie na obecny podział." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Przełącz tryb tylko do odczytu" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Włącz/wyłącz tryb tylko do odczytu dla obecnej powierzchni." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Wyrównaj podziały" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Wyrównaj rozmiar wszystkich podziałów." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Zresetuj rozmiar okna" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Zresetuj rozmiar okna do domyślnego." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Przełącz inspektor" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Włącz/wyłącz inspektor." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Wyświetl inspektor GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Wyświetl inspektor GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Wyświetl klawiaturę ekranową" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Wyświetl klawiaturę ekranową, jeśli jest obecna." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Otwórz konfigurację używając edytora systemowego" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Otwórz plik konfiguracyjny w domyślnym edytorze systemu operacyjnego." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Otwórz konfigurację w nowym oknie terminala" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Otwórz plik konfiguracyjny w nowym oknie używając $EDITOR lub $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Przeładuj konfigurację" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Przeładuj plik konfiguracyjny." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Zamknij terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Zamknij obecny terminal." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Zamknij obecną kartę." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Zamknij inne karty" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Zamknij wszystkie karty w tym oknie poza obecną." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Zamknij karty po prawej" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Zamknij wszystkie karty po prawej od obecnej." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Zamknij obecne okno." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Zamknij wszystkie okna" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Zamknij wszystkie okna." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Przełącz maksymalizację" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Włącz/wyłącz maksymalizację obecnego okna." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Przełącz pełny ekran" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Przełącz stan pełnego ekranu obecnego okna." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Przełącz dekoracje okna" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Przełącz dekoracje okna." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Przełącz zawsze na wierzchu" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Włącz/wyłącz wyświetlanie obecnego okna zawsze na wierzchu." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Przełącz bezpieczne wprowadzanie" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Włącz/wyłącz tryb bezpiecznego wprowadzania." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Przełącz raportowanie myszy" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Włącz/wyłącz raportowanie zdarzeń myszy do aplikacji terminalowych." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Przełącz przezroczystość tła" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "Włącz/wyłącz przezroczystość tła w oknie powstałym jako przezroczyste." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Sprawdź aktualizacje" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Sprawdź dostępność aktualizacji dla aplikacji." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Cofnij" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Cofnij ostatnią akcję." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Powtórz" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Powtórz ostatnią cofniętą akcję." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Wyjdź z aplikacji." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Umieść małe Ghostty w twoim terminalu." From 987ee52751a7d87a7bee996cd2e249a01be2571b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Sz=C3=BCcs?= Date: Thu, 13 Aug 2026 21:23:51 +0200 Subject: [PATCH 024/392] Update and (hopefully) complete Hungarian translations in hu.po --- po/hu.po | 359 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 206 insertions(+), 153 deletions(-) diff --git a/po/hu.po b/po/hu.po index 4083235d8..98b0d0f5d 100644 --- a/po/hu.po +++ b/po/hu.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-26 21:00+0100\n" +"PO-Revision-Date: 2026-08-13 21:23+0200\n" "Last-Translator: Balázs Szücs \n" "Language-Team: Hungarian \n" "Language: hu\n" @@ -175,7 +175,7 @@ msgstr "Felosztás jobbra" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Felosztás bezárása" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,7 +203,7 @@ msgstr "Ablak" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Ablak címének módosítása…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +221,11 @@ msgstr "Konfiguráció" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Konfiguráció megnyitása az operációs rendszer szerkesztőjében" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Konfiguráció megnyitása új ablakban" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -269,27 +269,27 @@ msgstr "Parancs végrehajtása…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "A billentyűparancsot a rendszer visszavonta." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "A billentyűparancsot a rendszer megtagadta." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "A globális billentyűparancs nem áll rendelkezésre" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Terminál I/O események exportálása" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Exportálás" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Konfigurációs fájl szerkesztése" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -375,7 +375,7 @@ msgstr "Fül címének módosítása" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Ablak címének módosítása" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -395,710 +395,763 @@ msgstr "Ghostty fejlesztők" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Terminál visszaállítása" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "A terminál visszaállítása tiszta állapotba." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Másolás vágólapra" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"A kijelölt szöveg vágólapra másolása egyszerű és formázott formátumban is." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Kijelölés másolása egyszerű szövegként vágólapra" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "A kijelölt szöveg egyszerű szövegként vágólapra másolása." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Kijelölés másolása ANSI szekvenciákként vágólapra" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "A kijelölt szöveg ANSI escape szekvenciákként vágólapra másolása." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Kijelölés másolása HTML-ként vágólapra" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "A kijelölt szöveg HTML-ként vágólapra másolása." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "URL másolása vágólapra" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "A kurzor alatti URL vágólapra másolása." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Terminál címének másolása vágólapra" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"A terminál címének vágólapra másolása. Ha a terminál címe nincs beállítva, " +"ennek nincs hatása." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Beillesztés vágólapról" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "A fő vágólap tartalmának beillesztése." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Beillesztés kijelölésből" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "A kijelölési vágólap tartalmának beillesztése." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Keresés indítása" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Keresés indítása, ha még nincs aktív keresés." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Kijelölés keresése" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Keresés indítása a jelenlegi szövegkijelölésre." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Keresés befejezése" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "A jelenlegi keresés befejezése, ha van, és a GUI elemek elrejtése." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Következő keresési találat" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Ugrás a következő keresési találatra, ha van." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Előző keresési találat" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Ugrás az előző keresési találatra, ha van." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Betűméret növelése" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "A betűméret növelése 1 ponttal." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Betűméret csökkentése" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "A betűméret csökkentése 1 ponttal." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Betűméret visszaállítása" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "A betűméret visszaállítása az alapértelmezettre." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Képernyő törlése" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "A képernyő és a visszagörgethető előzmények törlése." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Összes kijelölése" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "A képernyőn lévő összes szöveg kijelölése." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Görgetés a tetejére" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Görgetés a képernyő tetejére." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Görgetés az aljára" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Görgetés a képernyő aljára." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Görgetés a kijelöléshez" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Görgetés a kijelölt szöveghez." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Görgetés egy oldalt felfelé" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "A képernyő görgetése felfelé egy oldallal." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Görgetés egy oldalt lefelé" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "A képernyő görgetése lefelé egy oldallal." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Képernyő másolása ideiglenes fájlba és útvonal másolása" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"A képernyő tartalmának másolása egy ideiglenes fájlba, és az útvonal " +"vágólapra másolása." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Képernyő másolása ideiglenes fájlba és útvonal beillesztése" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"A képernyő tartalmának másolása egy ideiglenes fájlba, és a fájl " +"útvonalának beillesztése." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Képernyő másolása ideiglenes fájlba és megnyitás" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "A képernyő tartalmának másolása egy ideiglenes fájlba, és megnyitása." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Képernyő másolása HTML-ként ideiglenes fájlba és útvonal másolása" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"A képernyő tartalmának HTML-ként másolása egy ideiglenes fájlba, és az " +"útvonal vágólapra másolása." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Képernyő másolása HTML-ként ideiglenes fájlba és útvonal beillesztése" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"A képernyő tartalmának HTML-ként másolása egy ideiglenes fájlba, és a fájl " +"útvonalának beillesztése." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Képernyő másolása HTML-ként ideiglenes fájlba és megnyitás" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"A képernyő tartalmának HTML-ként másolása egy ideiglenes fájlba, és " +"megnyitása." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Képernyő másolása ANSI szekvenciákként ideiglenes fájlba és útvonal " +"másolása" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"A képernyő tartalmának ANSI escape szekvenciákként másolása egy ideiglenes " +"fájlba, és az útvonal vágólapra másolása." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Képernyő másolása ANSI szekvenciákként ideiglenes fájlba és útvonal " +"beillesztése" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"A képernyő tartalmának ANSI escape szekvenciákként másolása egy ideiglenes " +"fájlba, és a fájl útvonalának beillesztése." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Képernyő másolása ANSI szekvenciákként ideiglenes fájlba és megnyitás" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"A képernyő tartalmának ANSI escape szekvenciákként másolása egy ideiglenes " +"fájlba, és megnyitása." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Kijelölés másolása ideiglenes fájlba és útvonal másolása" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"A kijelölés tartalmának másolása egy ideiglenes fájlba, és az útvonal " +"vágólapra másolása." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Kijelölés másolása ideiglenes fájlba és útvonal beillesztése" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"A kijelölés tartalmának másolása egy ideiglenes fájlba, és a fájl " +"útvonalának beillesztése." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Kijelölés másolása ideiglenes fájlba és megnyitás" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." msgstr "" +"A kijelölés tartalmának másolása egy ideiglenes fájlba, és megnyitása." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Kijelölés másolása HTML-ként ideiglenes fájlba és útvonal másolása" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"A kijelölés tartalmának HTML-ként másolása egy ideiglenes fájlba, és az " +"útvonal vágólapra másolása." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" msgstr "" +"Kijelölés másolása HTML-ként ideiglenes fájlba és útvonal beillesztése" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"A kijelölés tartalmának HTML-ként másolása egy ideiglenes fájlba, és a " +"fájl útvonalának beillesztése." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Kijelölés másolása HTML-ként ideiglenes fájlba és megnyitás" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"A kijelölés tartalmának HTML-ként másolása egy ideiglenes fájlba, és " +"megnyitása." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Kijelölés másolása ANSI szekvenciákként ideiglenes fájlba és útvonal " +"másolása" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"A kijelölés tartalmának ANSI escape szekvenciákként másolása egy " +"ideiglenes fájlba, és az útvonal vágólapra másolása." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Kijelölés másolása ANSI szekvenciákként ideiglenes fájlba és útvonal " +"beillesztése" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"A kijelölés tartalmának ANSI escape szekvenciákként másolása egy " +"ideiglenes fájlba, és a fájl útvonalának beillesztése." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" msgstr "" +"Kijelölés másolása ANSI szekvenciákként ideiglenes fájlba és megnyitás" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"A kijelölés tartalmának ANSI escape szekvenciákként másolása egy " +"ideiglenes fájlba, és megnyitása." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Új ablak megnyitása." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Új fül megnyitása." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Fül mozgatása balra" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "A jelenlegi fül balra mozgatása." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Fül mozgatása jobbra" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "A jelenlegi fül jobbra mozgatása." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Fül mozgatása új ablakba" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "A jelenlegi fül mozgatása egy új ablakba." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Fülnézet váltása" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "A fülnézet váltása." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Terminál címének módosítása…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Új cím megadásának kérése a jelenlegi terminálhoz." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Új cím megadásának kérése a jelenlegi fülhöz." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "A terminál felosztása balra." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "A terminál felosztása jobbra." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "A terminál felosztása felfelé." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "A terminál felosztása lefelé." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Fókusz a felosztásra: előző" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Fókusz az előző felosztásra, ha van." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Fókusz a felosztásra: következő" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Fókusz a következő felosztásra, ha van." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Fókusz a felosztásra: bal" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Fókusz a bal oldali felosztásra, ha létezik." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Fókusz a felosztásra: jobb" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Fókusz a jobb oldali felosztásra, ha létezik." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Fókusz a felosztásra: fel" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Fókusz a fölötte lévő felosztásra, ha létezik." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Fókusz a felosztásra: le" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Fókusz az alatta lévő felosztásra, ha létezik." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Fókusz az ablakra: előző" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Fókusz az előző ablakra, ha van." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Fókusz az ablakra: következő" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Fókusz a következő ablakra, ha van." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Felosztás nagyításának váltása" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "A jelenlegi felosztás nagyítási állapotának váltása." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Csak olvasható mód váltása" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "A csak olvasható mód váltása a jelenlegi felületen." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Felosztások egyenlítése" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Az összes felosztás méretének egyenlítése." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Ablakméret visszaállítása" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Az ablakméret visszaállítása az alapértelmezettre." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Vizsgáló váltása" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "A vizsgáló váltása." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "GTK-vizsgáló megjelenítése" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "A GTK-vizsgáló megjelenítése." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Képernyő-billentyűzet megjelenítése" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "A képernyő-billentyűzet megjelenítése, ha elérhető." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Konfiguráció megnyitása az operációs rendszer szerkesztőjével" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"A konfigurációs fájl megnyitása az operációs rendszer alapértelmezett " +"szerkesztőjével." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Konfiguráció megnyitása új terminálablakban" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"A konfigurációs fájl megnyitása egy új ablakban a $EDITOR vagy $VISUAL " +"használatával." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Konfiguráció frissítése" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "A konfigurációs fájl frissítése." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Terminál bezárása" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "A jelenlegi terminál bezárása." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "A jelenlegi fül bezárása." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Egyéb fülek bezárása" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Az ebben az ablakban lévő összes fül bezárása a jelenlegi kivételével." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Jobb oldali fülek bezárása" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "A jelenlegi fültől jobbra lévő összes fül bezárása." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "A jelenlegi ablak bezárása." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Összes ablak bezárása" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Az összes ablak bezárása." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Maximalizálás váltása" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "A jelenlegi ablak maximalizált állapotának váltása." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Teljes képernyő váltása" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "A jelenlegi ablak teljes képernyős állapotának váltása." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Ablakdíszítések váltása" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Az ablakdíszítések váltása." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Mindig felül váltása" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "A jelenlegi ablak mindig felül állapotának váltása." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Biztonságos beviteli mód váltása" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "A biztonságos beviteli mód váltása." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Egérjelentés váltása" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." msgstr "" +"Annak váltása, hogy az egéresemények eljutnak-e a " +"terminálalkalmazásokhoz." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Háttér átlátszóságának váltása" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"A háttér átlátszóságának váltása egy átlátszóként indított ablaknál." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Frissítések keresése" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Az alkalmazás frissítéseinek keresése." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Visszavonás" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Az utolsó művelet visszavonása." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Ismétlés" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Az utolsó visszavont művelet ismétlése." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Az alkalmazásból kilépés." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Egy kis Ghostty a terminálban." From 51992ab01ad7e6dfeced16615080b68f620bb122 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Aug 2026 12:10:44 -0700 Subject: [PATCH 025/392] libghostty: make device and point headers self-contained --- include/ghostty/vt/device.h | 2 ++ include/ghostty/vt/point.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/ghostty/vt/device.h b/include/ghostty/vt/device.h index 0a1567280..419748a86 100644 --- a/include/ghostty/vt/device.h +++ b/include/ghostty/vt/device.h @@ -11,6 +11,8 @@ #include #include +#include + /* DA1 conformance levels (Pp parameter). */ #define GHOSTTY_DA_CONFORMANCE_VT100 1 #define GHOSTTY_DA_CONFORMANCE_VT101 1 diff --git a/include/ghostty/vt/point.h b/include/ghostty/vt/point.h index 8b717f494..9cab2758b 100644 --- a/include/ghostty/vt/point.h +++ b/include/ghostty/vt/point.h @@ -9,6 +9,8 @@ #include +#include + #ifdef __cplusplus extern "C" { #endif From e4ec4f0f95f44b131c256127d26c67258104be5a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Aug 2026 12:22:21 -0700 Subject: [PATCH 026/392] libghostty: fix enum underlying type detection Use fixed int enum types for C++11, C23, Clang's fixed-enum extension, and GCC 13 or newer. Previously only finalized C23 mode selected an explicit underlying type, leaving C++ and common older C modes with implementation-defined enum types. --- include/ghostty/vt/types.h | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index 672faa67c..bd3d37d65 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -39,18 +39,21 @@ * The Zig side backs all C enums with c_int, so the C declarations * must use int as their underlying type to maintain ABI compatibility. * - * C23 (detected via __STDC_VERSION__ >= 202311L) supports explicit - * enum underlying types with `enum : int { ... }`. For pre-C23 - * compilers, which are free to choose any type that can represent - * all values (C11 §6.7.2.2), we add an INT_MAX sentinel as the last - * entry to force the compiler to use int. + * C++11 and C23 support explicit enum underlying types with + * `enum : int { ... }`. Clang and GCC 13+ also support this syntax as + * an extension in older C language modes, so use it when available. + * + * Other pre-C23 C compilers are free to choose any type that can + * represent all values (C11 §6.7.2.2). For those compilers, we add an + * INT_MAX sentinel as the last entry so the compatible type must be + * able to represent INT_MAX. The exact compatible type and its + * signedness remain implementation-defined in this fallback. * * INT_MAX is used rather than a fixed constant like 0xFFFFFFFF - * because enum constants must have type int (which is signed). - * Values above INT_MAX overflow signed int and are a constraint - * violation in standard C; compilers that accept them interpret them - * as negative values via two's complement, which can collide with - * legitimate negative enum values. + * because enum constants must have type int in pre-C23 C. Values above + * INT_MAX are a constraint violation there; compilers that accept them + * may interpret them as negative values via two's complement, which can + * collide with legitimate negative enum values. * * Usage: * @code @@ -61,7 +64,18 @@ * } Foo; * @endcode */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#if defined(__cplusplus) && \ + (__cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1700)) +#define GHOSTTY_ENUM_TYPED : int +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define GHOSTTY_ENUM_TYPED : int +#elif defined(__clang__) + #if __has_extension(c_fixed_enum) + #define GHOSTTY_ENUM_TYPED : int + #else + #define GHOSTTY_ENUM_TYPED + #endif +#elif defined(__GNUC__) && __GNUC__ >= 13 #define GHOSTTY_ENUM_TYPED : int #else #define GHOSTTY_ENUM_TYPED From 309440e07f3ff097fb4b11ab8cc92b01e29625e8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Aug 2026 12:27:20 -0700 Subject: [PATCH 027/392] libghostty: include all public structs in type JSON --- src/terminal/c/types.zig | 75 +++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig index df84d3ede..2f8a011cf 100644 --- a/src/terminal/c/types.zig +++ b/src/terminal/c/types.zig @@ -7,6 +7,7 @@ const std = @import("std"); const lib = @import("../lib.zig"); const color = @import("../color.zig"); +const sgr = @import("../sgr.zig"); const color_c = @import("color.zig"); const mouse_event = @import("mouse_event.zig"); const point = @import("../point.zig"); @@ -18,9 +19,11 @@ const selection = @import("selection.zig"); const selection_gesture = @import("selection_gesture.zig"); const render = @import("render.zig"); const style_c = @import("style.zig"); +const kitty_graphics = @import("kitty_graphics.zig"); const mouse_encode = @import("mouse_encode.zig"); const grid_ref = @import("grid_ref.zig"); const io = @import("io.zig"); +const sys = @import("sys.zig"); /// C: GhosttySurfacePosition pub const SurfacePosition = extern struct { @@ -38,6 +41,8 @@ pub const Codepoints = extern struct { pub const structs: std.StaticStringMap(StructInfo) = structs: { @setEvalBranchQuota(10_000); break :structs .initComptime(.{ + .{ "GhosttyAllocator", StructInfo.init(lib.alloc.Allocator) }, + .{ "GhosttyAllocatorVtable", StructInfo.init(lib.alloc.VTable) }, .{ "GhosttyBuffer", StructInfo.init(lib.Buffer) }, .{ "GhosttyClipboardContent", StructInfo.init(terminal.ClipboardContent) }, .{ "GhosttyClipboardWrite", StructInfo.init(terminal.ClipboardWrite) }, @@ -54,22 +59,28 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: { .{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) }, .{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) }, .{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) }, + .{ "GhosttyTerminalSelectionFormatOptions", StructInfo.init(selection.FormatOptions) }, .{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) }, .{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) }, .{ "GhosttyGridRef", StructInfo.init(grid_ref.CGridRef) }, + .{ "GhosttyKittyGraphicsPlacementRenderInfo", StructInfo.init(kitty_graphics.PlacementRenderInfo) }, .{ "GhosttyMouseEncoderSize", StructInfo.init(mouse_encode.Size) }, .{ "GhosttyMousePosition", StructInfo.init(mouse_event.Position) }, .{ "GhosttyPoint", StructInfo.init(point.Point.C) }, .{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) }, .{ "GhosttyReader", StructInfo.init(io.Reader) }, .{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) }, + .{ "GhosttyRenderStateRowSelection", StructInfo.init(render.RowSelection) }, .{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) }, .{ "GhosttySelectionGestureGeometry", StructInfo.init(selection_gesture.Geometry) }, + .{ "GhosttySgrAttribute", StructInfo.init(sgr.Attribute.C) }, + .{ "GhosttySgrUnknown", StructInfo.init(sgr.Attribute.Unknown.C) }, .{ "GhosttySizeReportSize", StructInfo.init(size_report.Size) }, .{ "GhosttyString", StructInfo.init(lib.String) }, .{ "GhosttySurfacePosition", StructInfo.init(SurfacePosition) }, .{ "GhosttyStyle", StructInfo.init(style_c.Style) }, .{ "GhosttyStyleColor", StructInfo.init(style_c.Color) }, + .{ "GhosttySysImage", StructInfo.init(sys.Image) }, .{ "GhosttyTerminalDesktopNotification", StructInfo.init(terminal.DesktopNotification) }, .{ "GhosttyTerminalModeConfig", StructInfo.init(terminal.ModeConfig) }, .{ "GhosttyTerminalProgressReport", StructInfo.init(terminal.ProgressReport) }, @@ -220,15 +231,61 @@ test "json parses" { const root = parsed.value.object; - // Verify we have all expected structs - try std.testing.expect(root.contains("GhosttyClipboardContent")); - try std.testing.expect(root.contains("GhosttyClipboardWrite")); - try std.testing.expect(root.contains("GhosttyFormatterTerminalOptions")); - try std.testing.expect(root.contains("GhosttyTerminalModeConfig")); - try std.testing.expect(root.contains("GhosttyTerminalUnknownSequence")); - try std.testing.expect(root.contains("GhosttyTerminalUnknownStringSequence")); - try std.testing.expect(root.contains("GhosttyReader")); - try std.testing.expect(root.contains("GhosttyWriter")); + // Verify we have every public struct declared by the C API headers. + const expected_structs = [_][]const u8{ + "GhosttyAllocator", + "GhosttyAllocatorVtable", + "GhosttyBuffer", + "GhosttyClipboardContent", + "GhosttyClipboardWrite", + "GhosttyCodepoints", + "GhosttyColorPaletteMask", + "GhosttyColorRgb", + "GhosttyColorX11Entry", + "GhosttyDeviceAttributes", + "GhosttyDeviceAttributesPrimary", + "GhosttyDeviceAttributesSecondary", + "GhosttyDeviceAttributesTertiary", + "GhosttyFormatterScreenExtra", + "GhosttyFormatterTerminalExtra", + "GhosttyFormatterTerminalOptions", + "GhosttyGridRef", + "GhosttyKittyGraphicsPlacementRenderInfo", + "GhosttyMouseEncoderSize", + "GhosttyMousePosition", + "GhosttyPoint", + "GhosttyPointCoordinate", + "GhosttyReader", + "GhosttyRenderStateColors", + "GhosttyRenderStateRowSelection", + "GhosttySelection", + "GhosttySelectionGestureBehaviors", + "GhosttySelectionGestureGeometry", + "GhosttySgrAttribute", + "GhosttySgrUnknown", + "GhosttySizeReportSize", + "GhosttyString", + "GhosttyStyle", + "GhosttyStyleColor", + "GhosttySurfacePosition", + "GhosttySysImage", + "GhosttyTerminalDesktopNotification", + "GhosttyTerminalModeConfig", + "GhosttyTerminalProgressReport", + "GhosttyTerminalScrollbar", + "GhosttyTerminalScrollViewport", + "GhosttyTerminalSelectLineOptions", + "GhosttyTerminalSelectWordBetweenOptions", + "GhosttyTerminalSelectWordOptions", + "GhosttyTerminalSelectionFormatOptions", + "GhosttyTerminalUnknownSequence", + "GhosttyTerminalUnknownStringSequence", + "GhosttyWriter", + }; + try std.testing.expectEqual(expected_structs.len, root.count()); + for (expected_structs) |name| { + try std.testing.expect(root.contains(name)); + } const clipboard_content = root.get("GhosttyClipboardContent").?.object; const clipboard_content_fields = clipboard_content.get("fields").?.object; From d930c74c4d8211d551d0cb99054f13338113f4f9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Aug 2026 12:30:56 -0700 Subject: [PATCH 028/392] libghostty: make sized initialization valid C++ Use an immediately invoked lambda for GHOSTTY_INIT_SIZED in C++ so the macro value-initializes every field before setting the ABI size. The previous C compound literal and designated initializer required compiler extensions in C++17 and C++20. Keep the existing standard compound literal for C callers. --- include/ghostty/vt/types.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index bd3d37d65..c66ee89d2 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -319,8 +319,17 @@ typedef struct { * opts.trim = true; * @endcode */ +#ifdef __cplusplus +#define GHOSTTY_INIT_SIZED(type) \ + ([]() noexcept { \ + type value{}; \ + value.size = sizeof(value); \ + return value; \ + }()) +#else #define GHOSTTY_INIT_SIZED(type) \ ((type){ .size = sizeof(type) }) +#endif /** * Return a pointer to a null-terminated JSON string describing the From 250a36fe6f55de3d2a910deb420f35b0cf6dc350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Sarasola?= Date: Thu, 13 Aug 2026 21:39:40 +0200 Subject: [PATCH 029/392] i18n: Update es_ES translations Update the translations for the next 1.4 release --- po/es_ES.po | 357 +++++++++++++++++++++++++++++----------------------- 1 file changed, 203 insertions(+), 154 deletions(-) diff --git a/po/es_ES.po b/po/es_ES.po index 5f2cd9c84..b146870a6 100644 --- a/po/es_ES.po +++ b/po/es_ES.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-18 10:57+0100\n" +"PO-Revision-Date: 2026-08-13 21:24+0200\n" "Last-Translator: José Miguel Sarasola \n" "Language-Team: \n" "Language: es_ES\n" @@ -176,7 +176,7 @@ msgstr "Dividir a la derecha" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Cerrar división" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -204,7 +204,7 @@ msgstr "Ventana" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Cambiar el título de la ventana…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -222,11 +222,11 @@ msgstr "Configuración" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Abrir configuración en el editor del sistema operativo" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Abrir configuración en una nueva ventana" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -270,27 +270,27 @@ msgstr "Ejecutar un comando…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "El atajo de teclado fue revocado por el sistema." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "El atajo de teclado fue denegado por el sistema." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Atajo de teclado global no disponible" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Exportar eventos de E/S del terminal" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Exportar" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Editando el fichero de configuración" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -376,7 +376,7 @@ msgstr "Cambiar el título de la pestaña" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Cambiar el título de la ventana" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -396,710 +396,759 @@ msgstr "Desarrolladores de Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Restablecer terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Restablecer el terminal a un estado limpio." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Copiar al portapapeles" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Copiar el texto seleccionado al portapapeles, tanto en texto plano como con " +"estilo." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Copiar selección como texto plano al portapapeles" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Copiar el texto seleccionado como texto plano al portapapeles." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Copiar selección como secuencias ANSI al portapapeles" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." msgstr "" +"Copiar el texto seleccionado como secuencias de escape ANSI al portapapeles." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Copiar selección como HTML al portapapeles" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Copiar el texto seleccionado como HTML al portapapeles." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Copiar URL al portapapeles" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Copiar la URL bajo el cursor al portapapeles." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Copiar título del terminal al portapapeles" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Copiar el título del terminal al portapapeles. Si el terminal no tiene " +"título, esta acción no tiene efecto." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Pegar desde el portapapeles" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Pegar el contenido del portapapeles principal." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Pegar desde la selección" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Pegar el contenido del portapapeles de selección." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Iniciar búsqueda" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Iniciar una búsqueda si no hay ninguna activa." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Buscar selección" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Iniciar una búsqueda para el texto seleccionado actualmente." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Finalizar búsqueda" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." msgstr "" +"Finalizar la búsqueda actual, si la hay, y ocultar los elementos de la " +"interfaz gráfica." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Siguiente resultado de búsqueda" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Ir al siguiente resultado de búsqueda, si lo hay." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Resultado de búsqueda anterior" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Ir al resultado de búsqueda anterior, si lo hay." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Aumentar el tamaño de la fuente" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Aumentar el tamaño de la fuente en 1 punto." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Reducir el tamaño de la fuente" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Reducir el tamaño de la fuente en 1 punto." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Restablecer tamaño de fuente" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Restablecer el tamaño de la fuente al valor predeterminado." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Limpiar pantalla" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Limpiar la pantalla y el historial de desplazamiento." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Seleccionar todo" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Seleccionar todo el texto de la pantalla." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Desplazar al inicio" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Desplazar hasta el inicio de la pantalla." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Desplazar al final" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Desplazar hasta el final de la pantalla." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Desplazar hasta la selección" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Desplazar hasta el texto seleccionado." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Desplazar una página hacia arriba" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Desplazar la pantalla una página hacia arriba." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Desplazar una página hacia abajo" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Desplazar la pantalla una página hacia abajo." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla a un fichero temporal y copiar la ruta" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copiar el contenido de la pantalla a un fichero temporal y copiar la ruta al " +"portapapeles." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla a fichero temporal y pegar la ruta" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Copiar el contenido de la pantalla a un fichero temporal y pegar la ruta del " +"fichero." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla a fichero temporal y abrir" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Copiar el contenido de la pantalla a un fichero temporal y abrirlo." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla como HTML a fichero temporal y copiar la ruta" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copiar el contenido de la pantalla como HTML a un fichero temporal y copiar " +"la ruta al portapapeles." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla como HTML a fichero temporal y pegar la ruta" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Copiar el contenido de la pantalla como HTML a un fichero temporal y pegar " +"la ruta del fichero." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla como HTML a fichero temporal y abrir" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Copiar el contenido de la pantalla como HTML a un fichero temporal y abrirlo." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar pantalla como secuencias ANSI a fichero temporal y copiar la ruta" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"fichero temporal y copiar la ruta al portapapeles." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar pantalla como secuencias ANSI a fichero temporal y pegar la ruta" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"fichero temporal y pegar la ruta del fichero." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla como secuencias ANSI a fichero temporal y abrir" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copiar el contenido de la pantalla como secuencias de escape ANSI a un " +"fichero temporal y abrirlo." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección a fichero temporal y copiar la ruta" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copiar el contenido de la selección a un fichero temporal y copiar la ruta " +"al portapapeles." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección a fichero temporal y pegar la ruta" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Copiar el contenido de la selección a un fichero temporal y pegar la ruta " +"del fichero." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Copiar selección a fichero temporal y abrir" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Copiar el contenido de la selección a un fichero temporal y abrirlo." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección como HTML a fichero temporal y copiar la ruta" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copiar el contenido de la selección como HTML a un fichero temporal y copiar " +"la ruta al portapapeles." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección como HTML a fichero temporal y pegar la ruta" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Copiar el contenido de la selección como HTML a un fichero temporal y pegar " +"la ruta del fichero." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar selección como HTML a fichero temporal y abrir" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"Copiar el contenido de la selección como HTML a un fichero temporal y " +"abrirlo." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar selección como secuencias ANSI a fichero temporal y copiar la ruta" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"fichero temporal y copiar la ruta al portapapeles." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar selección como secuencias ANSI a fichero temporal y pegar la ruta" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"fichero temporal y pegar la ruta del fichero." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar selección como secuencias ANSI a fichero temporal y abrir" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copiar el contenido de la selección como secuencias de escape ANSI a un " +"fichero temporal y abrirlo." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Abrir una nueva ventana." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Abrir una nueva pestaña." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Mover pestaña a la izquierda" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Mover la pestaña actual hacia la izquierda." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Mover pestaña a la derecha" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Mover la pestaña actual hacia la derecha." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Mover pestaña a una nueva ventana" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Mover la pestaña actual a una nueva ventana." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Alternar vista general de pestañas" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Alternar la vista general de pestañas." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Cambiar el título del terminal…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Pedir un nuevo título para el terminal actual." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Pedir un nuevo título para la pestaña actual." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Dividir el terminal hacia la izquierda." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Dividir el terminal hacia la derecha." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Dividir el terminal hacia arriba." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Dividir el terminal hacia abajo." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Enfocar división: anterior" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Dar foco a la división anterior, si existe." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Enfocar división: siguiente" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Dar foco a la siguiente división, si existe." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Enfocar división: izquierda" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Dar foco a la división de la izquierda, si existe." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Enfocar división: derecha" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Dar foco a la división de la derecha, si existe." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Enfocar división: arriba" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Dar foco a la división de arriba, si existe." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Enfocar división: abajo" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Dar foco a la división de abajo, si existe." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Enfocar ventana: anterior" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Dar foco a la ventana anterior, si existe." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Enfocar ventana: siguiente" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Dar foco a la siguiente ventana, si existe." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Alternar zoom de división" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Alternar el estado de zoom de la división actual." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Alternar modo de solo lectura" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Alternar el modo de solo lectura de la superficie actual." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Igualar divisiones" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Igualar el tamaño de todas las divisiones." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Restablecer tamaño de ventana" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Restablecer el tamaño de la ventana al valor predeterminado." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Alternar inspector" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Alternar el inspector." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Mostrar el inspector de GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Mostrar el inspector de GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Mostrar teclado en pantalla" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Mostrar el teclado en pantalla si está disponible." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Abrir configuración con el editor del sistema operativo" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"Abrir el fichero de configuración con el editor predeterminado del sistema " +"operativo." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Abrir configuración en una nueva ventana de terminal" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Abrir el fichero de configuración en una nueva ventana usando $EDITOR o " +"$VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Recargar configuración" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Recargar el fichero de configuración." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Cerrar terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Cerrar el terminal actual." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Cerrar la pestaña actual." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Cerrar otras pestañas" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Cerrar todas las pestañas de esta ventana excepto la actual." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Cerrar pestañas a la derecha" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Cerrar todas las pestañas a la derecha de la actual." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Cerrar la ventana actual." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Cerrar todas las ventanas" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Cerrar todas las ventanas." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Alternar maximizado" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Alternar el estado de maximizado de la ventana actual." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Alternar pantalla completa" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Alternar el estado de pantalla completa de la ventana actual." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Alternar decoraciones de ventana" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Alternar las decoraciones de la ventana." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Alternar modo siempre encima" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Alternar si la ventana actual permanece siempre encima de las demás." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Alternar entrada segura" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Alternar el modo de entrada segura." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Alternar informe del ratón" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." msgstr "" +"Alternar si los eventos del ratón se envían a las aplicaciones del terminal." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Alternar opacidad del fondo" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Alternar la opacidad del fondo de una ventana que se inició como " +"transparente." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Buscar actualizaciones" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Buscar actualizaciones de la aplicación." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Deshacer" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Deshacer la última acción." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Rehacer" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Rehacer la última acción deshecha." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Salir de la aplicación." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Pon un poco de Ghostty en tu terminal." From ece96a14cf4ff3497505067ec4821a201d2f2b70 Mon Sep 17 00:00:00 2001 From: Guilherme Tiscoski Date: Thu, 13 Aug 2026 17:54:58 -0500 Subject: [PATCH 030/392] i18n: update pt_BR translations --- po/pt_BR.po | 355 +++++++++++++++++++++++++++++----------------------- 1 file changed, 201 insertions(+), 154 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index 204d7a107..ff01074f6 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-18 10:50-0300\n" +"PO-Revision-Date: 2026-08-13 19:47-0300\n" "Last-Translator: Guilherme Tiscoski \n" "Language-Team: Brazilian Portuguese \n" @@ -178,7 +178,7 @@ msgstr "Dividir à direita" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Fechar divisão" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -206,7 +206,7 @@ msgstr "Janela" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Mudar título da janela…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -224,11 +224,11 @@ msgstr "Configurar" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Abrir configuração no editor do sistema operacional" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Abrir configuração em uma nova janela" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -272,27 +272,27 @@ msgstr "Executar um comando…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "O atalho de teclado foi revogado pelo sistema." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "O atalho de teclado foi negado pelo sistema." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Atalho de teclado global indisponível" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Exportar eventos de E/S do terminal" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Exportar" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Editando arquivo de configuração" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -378,7 +378,7 @@ msgstr "Mudar título da aba" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Mudar título da janela" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -398,710 +398,757 @@ msgstr "Desenvolvedores do Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Reiniciar terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Reinicia o terminal para um estado limpo." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Copiar para a área de transferência" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Copia o texto selecionado para a área de transferência nos formatos de texto " +"simples e estilizado." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Copiar seleção como texto simples para a área de transferência" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." msgstr "" +"Copia o texto selecionado como texto simples para a área de transferência." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Copiar seleção como sequências ANSI para a área de transferência" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." msgstr "" +"Copia o texto selecionado como sequências de escape ANSI para a área de " +"transferência." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Copiar seleção como HTML para a área de transferência" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Copia o texto selecionado como HTML para a área de transferência." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Copiar URL para a área de transferência" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Copia a URL sob o cursor para a área de transferência." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Copiar título do terminal para a área de transferência" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Copia o título do terminal para a área de transferência. Se o título do " +"terminal não estiver definido, isso não terá efeito." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Colar da área de transferência" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Cola o conteúdo da área de transferência principal." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Colar da seleção" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Cola o conteúdo da área de transferência da seleção." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Iniciar busca" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Inicia uma busca se ainda não houver uma ativa." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Buscar seleção" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Inicia uma busca pela seleção de texto atual." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Encerrar busca" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." msgstr "" +"Encerra a busca atual, se houver, e oculta todos os elementos da interface " +"gráfica." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Próximo resultado da busca" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Navega até o próximo resultado da busca, se houver." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Resultado anterior da busca" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Navega até o resultado anterior da busca, se houver." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Aumentar tamanho da fonte" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Aumenta o tamanho da fonte em 1 ponto." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Diminuir tamanho da fonte" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Diminui o tamanho da fonte em 1 ponto." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Restaurar tamanho da fonte" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Restaura o tamanho da fonte para o padrão." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Limpar tela" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Limpa a tela e o histórico de rolagem." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Selecionar tudo" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Seleciona todo o texto na tela." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Rolar para o início" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Rola para o início da tela." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Rolar para o final" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Rola para o final da tela." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Rolar até a seleção" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Rola até o texto selecionado." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Rolar uma página para cima" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Rola a tela uma página para cima." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Rolar uma página para baixo" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Rola a tela uma página para baixo." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar tela para arquivo temporário e copiar caminho" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copia o conteúdo da tela para um arquivo temporário e copia o caminho para a " +"área de transferência." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar tela para arquivo temporário e colar caminho" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Copia o conteúdo da tela para um arquivo temporário e cola o caminho do " +"arquivo." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Copiar tela para arquivo temporário e abrir" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Copia o conteúdo da tela para um arquivo temporário e o abre." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar tela como HTML para arquivo temporário e copiar caminho" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copia o conteúdo da tela como HTML para um arquivo temporário e copia o " +"caminho para a área de transferência." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar tela como HTML para arquivo temporário e colar caminho" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Copia o conteúdo da tela como HTML para um arquivo temporário e cola o " +"caminho do arquivo." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar tela como HTML para arquivo temporário e abrir" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Copia o conteúdo da tela como HTML para um arquivo temporário e o abre." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar tela como sequências ANSI para arquivo temporário e copiar caminho" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copia o conteúdo da tela como sequências de escape ANSI para um arquivo " +"temporário e copia o caminho para a área de transferência." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar tela como sequências ANSI para arquivo temporário e colar caminho" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copia o conteúdo da tela como sequências de escape ANSI para um arquivo " +"temporário e cola o caminho do arquivo." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar tela como sequências ANSI para arquivo temporário e abrir" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copia o conteúdo da tela como sequências de escape ANSI para um arquivo " +"temporário e o abre." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar seleção para arquivo temporário e copiar caminho" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copia o conteúdo da seleção para um arquivo temporário e copia o caminho " +"para a área de transferência." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar seleção para arquivo temporário e colar caminho" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Copia o conteúdo da seleção para um arquivo temporário e cola o caminho do " +"arquivo." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Copiar seleção para arquivo temporário e abrir" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Copia o conteúdo da seleção para um arquivo temporário e o abre." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar seleção como HTML para arquivo temporário e copiar caminho" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copia o conteúdo da seleção como HTML para um arquivo temporário e copia o " +"caminho para a área de transferência." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar seleção como HTML para arquivo temporário e colar caminho" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Copia o conteúdo da seleção como HTML para um arquivo temporário e cola o " +"caminho do arquivo." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar seleção como HTML para arquivo temporário e abrir" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"Copia o conteúdo da seleção como HTML para um arquivo temporário e o abre." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar seleção como sequências ANSI para arquivo temporário e copiar caminho" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copia o conteúdo da seleção como sequências de escape ANSI para um arquivo " +"temporário e copia o caminho para a área de transferência." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar seleção como sequências ANSI para arquivo temporário e colar caminho" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copia o conteúdo da seleção como sequências de escape ANSI para um arquivo " +"temporário e cola o caminho do arquivo." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Copiar seleção como sequências ANSI para arquivo temporário e abrir" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copia o conteúdo da seleção como sequências de escape ANSI para um arquivo " +"temporário e o abre." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Abre uma nova janela." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Abre uma nova aba." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Mover aba para a esquerda" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Move a aba atual para a esquerda." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Mover aba para a direita" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Move a aba atual para a direita." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Mover aba para uma nova janela" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Move a aba atual para uma nova janela." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Alternar visão geral das abas" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Alterna a visão geral das abas." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Mudar título do terminal…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Solicita um novo título para o terminal atual." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Solicita um novo título para a aba atual." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Divide o terminal à esquerda." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Divide o terminal à direita." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Divide o terminal para cima." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Divide o terminal para baixo." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Focar divisão: anterior" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Foca a divisão anterior, se houver." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Focar divisão: próxima" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Foca a próxima divisão, se houver." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Focar divisão: esquerda" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Foca a divisão à esquerda, se ela existir." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Focar divisão: direita" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Foca a divisão à direita, se ela existir." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Focar divisão: acima" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Foca a divisão acima, se ela existir." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Focar divisão: abaixo" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Foca a divisão abaixo, se ela existir." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Focar janela: anterior" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Foca a janela anterior, se houver." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Focar janela: próxima" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Foca a próxima janela, se houver." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Alternar zoom da divisão" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Alterna o estado de zoom da divisão atual." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Alternar modo somente leitura" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Alterna o modo somente leitura do terminal atual." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Equalizar divisões" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Equaliza o tamanho de todas as divisões." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Restaurar tamanho da janela" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Restaura o tamanho da janela para o padrão." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Alternar inspetor" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Alterna o inspetor." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Mostrar o inspetor GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Mostra o inspetor GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Mostrar teclado virtual" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Mostra o teclado virtual, se disponível." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Abrir configuração usando o editor do sistema operacional" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"Abre o arquivo de configuração com o editor padrão do sistema operacional." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Abrir configuração em uma nova janela do terminal" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Abre o arquivo de configuração em uma nova janela usando $EDITOR ou $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Recarregar configuração" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Recarrega o arquivo de configuração." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Fechar terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Fecha o terminal atual." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Fecha a aba atual." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Fechar outras abas" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Fecha todas as abas desta janela, exceto a atual." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Fechar abas à direita" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Fecha todas as abas à direita da atual." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Fecha a janela atual." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Fechar todas as janelas" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Fecha todas as janelas." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Alternar maximização" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Alterna o estado maximizado da janela atual." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Alternar tela cheia" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Alterna o estado de tela cheia da janela atual." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Alternar decorações da janela" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Alterna as decorações da janela." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Alternar janela sempre no topo" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Alterna se a janela atual deve permanecer sempre no topo." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Alternar entrada segura" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Alterna o modo de entrada segura." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Alternar envio de eventos do mouse" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Alterna o envio de eventos do mouse aos aplicativos do terminal." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Alternar opacidade do plano de fundo" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Alterna a opacidade do plano de fundo de uma janela iniciada com " +"transparência." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Verificar atualizações" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Verifica se há atualizações para o aplicativo." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Desfazer" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Desfaz a última ação." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Refazer" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Refaz a última ação desfeita." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Sai do aplicativo." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Coloca um pouco de Ghostty no seu terminal." From bfc200c854d33954caf307ae459b659dd38f77c1 Mon Sep 17 00:00:00 2001 From: reo101 Date: Thu, 13 Aug 2026 13:48:00 +0300 Subject: [PATCH 031/392] i18n: update bg_BG translations --- po/bg.po | 362 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 210 insertions(+), 152 deletions(-) diff --git a/po/bg.po b/po/bg.po index 2069f5235..a8fdf55d4 100644 --- a/po/bg.po +++ b/po/bg.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-09 22:07+0200\n" +"PO-Revision-Date: 2026-08-13 13:40+0300\n" "Last-Translator: reo101 \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -175,7 +175,7 @@ msgstr "Раздели надясно" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Затвори разделянето" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -184,7 +184,7 @@ msgstr "Раздел" #: src/apprt/gtk/ui/1.2/surface.blp:386 src/apprt/gtk/ui/1.5/window.blp:232 #: src/apprt/gtk/ui/1.5/window.blp:339 src/input/command.zig:464 msgid "Change Tab Title…" -msgstr "Смени името на таба…" +msgstr "Промени заглавието на раздела…" #: src/apprt/gtk/ui/1.2/surface.blp:391 src/apprt/gtk/ui/1.5/window.blp:60 #: src/apprt/gtk/ui/1.5/window.blp:110 src/apprt/gtk/ui/1.5/window.blp:237 @@ -203,7 +203,7 @@ msgstr "Прозорец" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Промени заглавието на прозореца…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +221,11 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Отвори конфигурацията в редактора на операционната система" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Отвори конфигурацията в нов прозорец" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -269,27 +269,27 @@ msgstr "Изпълни команда…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Клавишната комбинация е отменена от системата." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Клавишната комбинация е отказана от системата." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Глобалната клавишна комбинация не е достъпна" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Експортиране на входно-изходните събития на терминала" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Експортирай" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Редактиране на конфигурационния файл" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -371,11 +371,11 @@ msgstr "Промяна на заглавието на терминала" #: src/apprt/gtk/class/title_dialog.zig:228 msgid "Change Tab Title" -msgstr "Смени името на таба" +msgstr "Промяна на заглавието на раздела" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Промяна на заглавието на прозореца" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -395,710 +395,768 @@ msgstr "Разработчици на Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Нулиране на терминала" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Връщане на терминала в начално състояние." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Копиране в клипборда" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Копиране на избрания текст в клипборда както в обикновен, така и във " +"форматиран вид." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Копиране на избраното като обикновен текст в клипборда" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Копиране на избрания текст като обикновен текст в клипборда." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Копиране на избраното с ANSI форматиране в клипборда" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Копиране на избрания текст с ANSI форматиране в клипборда." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Копиране на избраното като HTML в клипборда" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Копиране на избрания текст като HTML в клипборда." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Копиране на URL адреса в клипборда" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Копиране на URL адреса под курсора в клипборда." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Копиране на заглавието на терминала в клипборда" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Копиране на заглавието на терминала в клипборда. Ако няма зададено заглавие, " +"действието няма ефект." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Поставяне от клипборда" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Поставяне на съдържанието на основния клипборд." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Поставяне от избраното" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Поставяне на съдържанието на клипборда за избраното." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Начало на търсенето" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Започване на търсене, ако в момента няма активно такова." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Търсене на избраното" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Започване на търсене на текущо избрания текст." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Край на търсенето" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." msgstr "" +"Прекратяване на текущото търсене, ако има такова, и скриване на всички " +"елементи на графичния интерфейс." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Следващ резултат от търсенето" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Преминаване към следващия резултат от търсенето, ако има такъв." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Предишен резултат от търсенето" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Преминаване към предишния резултат от търсенето, ако има такъв." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Увеличаване на размера на шрифта" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Увеличаване на размера на шрифта с 1 пункт." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Намаляване на размера на шрифта" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Намаляване на размера на шрифта с 1 пункт." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Възстановяване на размера на шрифта" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Възстановяване на размера на шрифта до стойността по подразбиране." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Изчистване на екрана" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Изчистване на екрана и буфера за превъртане назад." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Избиране на всичко" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Избиране на целия текст на екрана." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Превъртане до началото" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Превъртане до началото на екрана." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Превъртане до края" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Превъртане до края на екрана." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Превъртане до избраното" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Превъртане до избрания текст." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Превъртане с една страница нагоре" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Превъртане на екрана с една страница нагоре." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Превъртане с една страница надолу" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Превъртане на екрана с една страница надолу." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Записване на екрана във временен файл и копиране на пътя" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Съдържанието на екрана се записва във временен файл, а пътят до файла се " +"копира в клипборда." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Записване на екрана във временен файл и поставяне на пътя" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Съдържанието на екрана се записва във временен файл и се поставя пътят до " +"файла." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Записване на екрана във временен файл и отваряне на файла" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." msgstr "" +"Съдържанието на екрана се записва във временен файл, който след това се " +"отваря." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Записване на екрана като HTML във временен файл и копиране на пътя" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Съдържанието на екрана се записва във временен файл в HTML формат, а пътят " +"до файла се копира в клипборда." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Записване на екрана като HTML във временен файл и поставяне на пътя" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Съдържанието на екрана се записва във временен файл в HTML формат и се " +"поставя пътят до файла." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Записване на екрана като HTML във временен файл и отваряне на файла" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Съдържанието на екрана се записва във временен файл в HTML формат, който " +"след това се отваря." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Записване на екрана с ANSI форматиране във временен файл и копиране на пътя" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Съдържанието на екрана се записва във временен файл с ANSI форматиране, а " +"пътят до файла се копира в клипборда." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Записване на екрана с ANSI форматиране във временен файл и поставяне на пътя" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Съдържанието на екрана се записва във временен файл с ANSI форматиране и се " +"поставя пътят до файла." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" msgstr "" +"Записване на екрана с ANSI форматиране във временен файл и отваряне на файла" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Съдържанието на екрана се записва във временен файл с ANSI форматиране, " +"който след това се отваря." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Записване на избраното във временен файл и копиране на пътя" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Избраното съдържание се записва във временен файл, а пътят до файла се " +"копира в клипборда." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Записване на избраното във временен файл и поставяне на пътя" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Избраното съдържание се записва във временен файл и се поставя пътят до " +"файла." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Записване на избраното във временен файл и отваряне на файла" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." msgstr "" +"Избраното съдържание се записва във временен файл, който след това се отваря." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Записване на избраното като HTML във временен файл и копиране на пътя" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Избраното съдържание се записва във временен файл в HTML формат, а пътят до " +"файла се копира в клипборда." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Записване на избраното като HTML във временен файл и поставяне на пътя" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Избраното съдържание се записва във временен файл в HTML формат и се поставя " +"пътят до файла." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Записване на избраното като HTML във временен файл и отваряне на файла" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." msgstr "" +"Избраното съдържание се записва във временен файл в HTML формат, който след " +"това се отваря." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Записване на избраното с ANSI форматиране във временен файл и копиране на " +"пътя" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Избраното съдържание се записва във временен файл с ANSI форматиране, а " +"пътят до файла се копира в клипборда." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Записване на избраното с ANSI форматиране във временен файл и поставяне на " +"пътя" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Избраното съдържание се записва във временен файл с ANSI форматиране и се " +"поставя пътят до файла." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" msgstr "" +"Записване на избраното с ANSI форматиране във временен файл и отваряне на " +"файла" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Избраното съдържание се записва във временен файл с ANSI форматиране, който " +"след това се отваря." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Отваряне на нов прозорец." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Отваряне на нов раздел." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Преместване на раздела наляво" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Преместване на текущия раздел наляво." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Преместване на раздела надясно" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Преместване на текущия раздел надясно." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Преместване на раздела в нов прозорец" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Преместване на текущия раздел в нов прозорец." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Превключване на прегледа на разделите" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Превключване на прегледа на разделите." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Промяна на заглавието на терминала…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Показване на подкана за ново заглавие на текущия терминал." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Показване на подкана за ново заглавие на текущия раздел." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Разделяне на терминала наляво." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Разделяне на терминала надясно." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Разделяне на терминала нагоре." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Разделяне на терминала надолу." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Фокусиране на предишното разделяне" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Фокусиране на предишното разделяне, ако има такова." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Фокусиране на следващото разделяне" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Фокусиране на следващото разделяне, ако има такова." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Фокусиране на разделянето отляво" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Фокусиране на разделянето отляво, ако съществува." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Фокусиране на разделянето отдясно" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Фокусиране на разделянето отдясно, ако съществува." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Фокусиране на разделянето отгоре" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Фокусиране на разделянето отгоре, ако съществува." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Фокусиране на разделянето отдолу" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Фокусиране на разделянето отдолу, ако съществува." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Фокусиране на предишния прозорец" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Фокусиране на предишния прозорец, ако има такъв." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Фокусиране на следващия прозорец" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Фокусиране на следващия прозорец, ако има такъв." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Превключване на увеличението на разделянето" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Превключване на увеличението на текущото разделяне." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Превключване на режима само за четене" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Превключване на режима само за четене за текущия терминал." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Изравняване на разделянията" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Изравняване на размера на всички разделяния." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Възстановяване на размера на прозореца" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Възстановяване на размера на прозореца до стойността по подразбиране." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Превключване на инспектора" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Превключване на инспектора." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Показване на GTK инспектора" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Показване на GTK инспектора." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Показване на екранната клавиатура" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Показване на екранната клавиатура, ако е налична." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Отваряне на конфигурацията с редактора на операционната система" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." msgstr "" +"Отваряне на конфигурационния файл с редактора по подразбиране на " +"операционната система." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Отваряне на конфигурацията в нов терминален прозорец" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Отваряне на конфигурационния файл в нов прозорец чрез $EDITOR или $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Презареждане на конфигурацията" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Презареждане на конфигурационния файл." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Затваряне на терминала" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Затваряне на текущия терминал." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Затваряне на текущия раздел." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Затваряне на другите раздели" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Затваряне на всички раздели в този прозорец освен текущия." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Затваряне на разделите отдясно" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Затваряне на всички раздели вдясно от текущия." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Затваряне на текущия прозорец." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Затваряне на всички прозорци" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Затваряне на всички прозорци." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Превключване на максимизирането" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Превключване на максимизираното състояние на текущия прозорец." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Превключване на цял екран" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Превключване на състоянието на цял екран на текущия прозорец." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Превключване на декорациите на прозореца" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Показване или скриване на декорациите на прозореца." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Превключване на задържането на прозореца най-отгоре" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." msgstr "" +"Превключване дали текущият прозорец да остава над всички останали прозорци." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Превключване на защитеното въвеждане" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Превключване на режима за защитено въвеждане." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Превключване на предаването на събития от мишката" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." msgstr "" +"Превключване дали събитията от мишката да се предават на приложенията в " +"терминала." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Превключване на прозрачността на фона" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Превключване между прозрачен и непрозрачен фон за прозорец, стартиран с " +"прозрачен фон." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Проверка за обновления" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Проверка за обновления на приложението." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Отмяна" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Отмяна на последното действие." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Повтаряне" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Повтаряне на последното отменено действие." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Излизане от приложението." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Добавете малко Ghostty в терминала си." From 710b8723905533d623c5e64ba0c5e6662fe79713 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 02:34:51 +0000 Subject: [PATCH 032/392] Update VOUCHED list (#13805) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13804#discussioncomment-18010199) from @jcollie. Vouch: @pssalman Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 63aaea05f..484218791 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -255,6 +255,7 @@ poweruser64 prakhar54-byte prihlop priyans-hu +pssalman puzza007 qappell quinnypig From 08c80d253aefef517a010f16cc026aa4cccaa957 Mon Sep 17 00:00:00 2001 From: A-Lang Date: Sat, 8 Aug 2026 12:49:35 +0800 Subject: [PATCH 033/392] po/zh_TW: add missing translations --- po/zh_TW.po | 369 ++++++++++++++++++++++++++-------------------------- 1 file changed, 186 insertions(+), 183 deletions(-) diff --git a/po/zh_TW.po b/po/zh_TW.po index ce39415d9..bbe03d6da 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -2,14 +2,16 @@ # Copyright (C) 2025 Mitchell Hashimoto # This file is distributed under the same license as the com.mitchellh.ghostty package. # Peter Dave Hello , 2025. +# Yi-Jyun Pan , 2025. +# Alang Hsu , 2026. # msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-18 13:58+0800\n" -"Last-Translator: Yi-Jyun Pan \n" +"PO-Revision-Date: 2026-08-14 10:59+0800\n" +"Last-Translator: Alang Hsu \n" "Language-Team: Chinese (traditional)\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" @@ -170,7 +172,7 @@ msgstr "向右分割" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "關閉窗格" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -198,7 +200,7 @@ msgstr "視窗" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "變更視窗標題…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -216,11 +218,11 @@ msgstr "設定" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "使用作業系統編輯器開啟設定" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "在新視窗中開啟設定" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -264,27 +266,27 @@ msgstr "執行命令…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "系統已撤銷此按鍵綁定。" #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "系統已拒絕此按鍵綁定。" #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "無法使用全域按鍵綁定" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "匯出終端機 IO 事件" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "匯出" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "正在編輯設定檔" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -364,7 +366,7 @@ msgstr "變更分頁標題" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "變更視窗標題" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -384,710 +386,711 @@ msgstr "Ghostty 開發者" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "重設終端機" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "將終端機重設為乾淨狀態。" #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "複製到剪貼簿" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "" +msgstr "將選取的文字以純文字與樣式格式複製到剪貼簿。" #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "將選取範圍以純文字複製到剪貼簿" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "將選取的文字以純文字複製到剪貼簿。" #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "將選取範圍以 ANSI 跳脫序列複製到剪貼簿" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "將選取的文字以 ANSI 跳脫序列複製到剪貼簿。" #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "將選取範圍以 HTML 複製到剪貼簿" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "將選取的文字以 HTML 複製到剪貼簿。" #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "將游標下的 URL 複製到剪貼簿" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "將游標下的 URL 複製到剪貼簿。" #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "將終端機標題複製到剪貼簿" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "" +msgstr "將終端機標題複製到剪貼簿。若未設定終端機標題,此操作將不會有任何作用。" #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "從剪貼簿貼上" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "貼上主剪貼簿的內容。" #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "從選取範圍貼上" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "貼上選取剪貼簿的內容。" #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "開始搜尋" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "若目前沒有進行中的搜尋,則開始搜尋。" #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "搜尋選取範圍" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "為目前選取的文字開始搜尋。" #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "結束搜尋" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "結束目前的搜尋(若有)並隱藏所有 GUI 元件。" #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "下一個搜尋結果" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "前往下一個搜尋結果(若有)。" #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "上一個搜尋結果" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "前往上一個搜尋結果(若有)。" #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "放大字型" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "將字型大小放大 1 pt。" #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "縮小字型" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "將字型大小縮小 1 pt。" #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "重設字型大小" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "將字型大小重設為預設值。" #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "清除螢幕" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "清除螢幕與捲動緩衝區。" #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "全選" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "選取螢幕上的所有文字。" #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "捲動到頂端" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "捲動到螢幕頂端。" #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "捲動到底端" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "捲動到螢幕底端。" #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "捲動到選取範圍" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "捲動到選取的文字。" #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "向上捲動一頁" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "將螢幕向上捲動一頁。" #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "向下捲動一頁" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "將螢幕向下捲動一頁。" #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "將螢幕內容複製到暫存檔並複製路徑" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "將螢幕內容複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "將螢幕內容複製到暫存檔並貼上路徑" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "將螢幕內容複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "將螢幕內容複製到暫存檔並開啟" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "將螢幕內容複製到暫存檔並開啟。" #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔並複製路徑" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔並貼上路徑" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔並開啟" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "將螢幕內容以 HTML 複製到暫存檔並開啟。" #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔並複製路徑" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔並貼上路徑" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔並開啟" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "將螢幕內容以 ANSI 跳脫序列複製到暫存檔並開啟。" #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "將選取範圍複製到暫存檔並複製路徑" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "將選取範圍的內容複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "將選取範圍複製到暫存檔並貼上路徑" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "將選取範圍的內容複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "將選取範圍複製到暫存檔並開啟" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "將選取範圍的內容複製到暫存檔並開啟。" #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "將選取範圍以 HTML 複製到暫存檔並複製路徑" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "將選取範圍的內容以 HTML 複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "將選取範圍以 HTML 複製到暫存檔並貼上路徑" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "將選取範圍的內容以 HTML 複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "將選取範圍以 HTML 複製到暫存檔並開啟" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "將選取範圍的內容以 HTML 複製到暫存檔並開啟。" #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "將選取範圍以 ANSI 跳脫序列複製到暫存檔並複製路徑" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "將選取範圍的內容以 ANSI 跳脫序列複製到暫存檔,並將路徑複製到剪貼簿。" #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "將選取範圍以 ANSI 跳脫序列複製到暫存檔並貼上路徑" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "將選取範圍的內容以 ANSI 跳脫序列複製到暫存檔,並貼上該檔案的路徑。" #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "將選取範圍以 ANSI 跳脫序列複製到暫存檔並開啟" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "將選取範圍的內容以 ANSI 跳脫序列複製到暫存檔並開啟。" #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "開啟新視窗。" #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "開啟新分頁。" #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "向左移動分頁" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "將目前的分頁向左移動。" #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "向右移動分頁" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "將目前的分頁向右移動。" #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "將分頁移至新視窗" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "將目前的分頁移至新視窗。" #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "切換分頁總覽" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "切換分頁總覽。" #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "變更終端機標題…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "提示輸入目前終端機的新標題。" #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "提示輸入目前分頁的新標題。" #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "向左分割終端機。" #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "向右分割終端機。" #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "向上分割終端機。" #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "向下分割終端機。" #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "聚焦窗格:上一個" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "聚焦上一個窗格(若有)。" #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "聚焦窗格:下一個" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "聚焦下一個窗格(若有)。" #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "聚焦窗格:左" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "聚焦左側的窗格(若存在)。" #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "聚焦窗格:右" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "聚焦右側的窗格(若存在)。" #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "聚焦窗格:上" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "聚焦上方的窗格(若存在)。" #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "聚焦窗格:下" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "聚焦下方的窗格(若存在)。" #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "聚焦視窗:上一個" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "聚焦上一個視窗(若有)。" #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "聚焦視窗:下一個" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "聚焦下一個視窗(若有)。" #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "切換窗格縮放" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "切換目前窗格的縮放狀態。" #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "切換唯讀模式" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "切換目前終端機的唯讀模式。" #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "均分窗格" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "將所有窗格的大小均分。" #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "重設視窗大小" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "將視窗大小重設為預設值。" #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "切換檢查工具" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "切換檢查工具。" #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "顯示 GTK 檢查工具" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "顯示 GTK 檢查工具。" #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "顯示螢幕鍵盤" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "顯示螢幕鍵盤(若存在)。" #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "使用作業系統編輯器開啟設定" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "使用作業系統的預設編輯器開啟設定檔。" #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "在新終端機視窗中開啟設定" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "使用 $EDITOR 或 $VISUAL 在新視窗中開啟設定檔。" #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "重新載入設定" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "重新載入設定檔。" #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "關閉終端機" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "關閉目前終端機。" #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "關閉目前分頁。" #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "關閉其他分頁" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "關閉此視窗中目前分頁以外的所有分頁。" #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "關閉右側的分頁" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "關閉目前分頁右側的所有分頁。" #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "關閉目前視窗。" #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "關閉所有視窗" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "關閉所有視窗。" #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "切換最大化" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "切換目前視窗的最大化狀態。" #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "切換全螢幕" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "切換目前視窗的全螢幕狀態。" #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "切換視窗裝飾" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "切換視窗裝飾。" #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "切換置頂" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "切換目前視窗的置頂狀態。" #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "切換安全輸入" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "切換安全輸入模式。" #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "切換滑鼠回報" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "切換是否將滑鼠事件回報給終端機應用程式。" #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "切換背景不透明度" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "切換以透明狀態啟動之視窗的背景不透明度。" #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "檢查更新" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "檢查應用程式是否有更新。" #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "復原" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "復原上一個動作。" #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "重做" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "重做上一個已復原的動作。" #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "結束應用程式。" #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "讓你的終端機裡有點 Ghostty。" + From 365e0bd0083f3d51a7b7242ad8c5a025c7848c75 Mon Sep 17 00:00:00 2001 From: Alan Moyano Date: Fri, 14 Aug 2026 00:27:57 -0300 Subject: [PATCH 034/392] i18n: Updating `es_AR` for 1.4 (#13784) This PR also updates old translations to keep better consistency. AI Disclaimer: I translated manually all strings and then used an agent to review consistency and legibility and applied many suggestions. --- po/es_AR.po | 389 +++++++++++++++++++++++++++++----------------------- 1 file changed, 217 insertions(+), 172 deletions(-) diff --git a/po/es_AR.po b/po/es_AR.po index 31a87037b..6c471125b 100644 --- a/po/es_AR.po +++ b/po/es_AR.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-19 13:34-0300\n" +"PO-Revision-Date: 2026-08-13 12:56-0300\n" "Last-Translator: Alan Moyano \n" "Language-Team: Argentinian \n" "Language: es_AR\n" @@ -68,7 +68,7 @@ msgid "" "and either reload your configuration or ignore these errors." msgstr "" "Se encontraron uno o más errores de configuración. Por favor revisá los " -"errores a continuación, y recargá tu configuración o ignorá estos errores." +"errores y recargá tu configuración o ignoralos." #: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:12 msgid "Ignore" @@ -101,7 +101,7 @@ msgstr "Coincidencia anterior" #: src/apprt/gtk/ui/1.2/search-overlay.blp:79 msgid "Next Match" -msgstr "Coincidencia siguiente" +msgstr "Siguiente coincidencia" #: src/apprt/gtk/ui/1.2/surface.blp:7 msgid "Oh, no." @@ -109,7 +109,7 @@ msgstr "Uy, no." #: src/apprt/gtk/ui/1.2/surface.blp:8 msgid "Unable to acquire an OpenGL context for rendering." -msgstr "No se pudo obtener un contexto de OpenGL para el renderizado" +msgstr "No se pudo obtener un contexto de OpenGL para el renderizado." #: src/apprt/gtk/ui/1.2/surface.blp:101 msgid "" @@ -117,7 +117,7 @@ msgid "" "through the content, but no input events will be sent to the running " "application." msgstr "" -"Esta terminal está en modo solo lectura. Aún puedes ver, seleccionar y " +"Esta terminal está en modo solo lectura. Aún podés ver, seleccionar y " "desplazarte por el contenido, pero no se enviarán los eventos de entrada a " "la aplicación en ejecución." @@ -143,7 +143,7 @@ msgstr "Limpiar" #: src/apprt/gtk/ui/1.2/surface.blp:338 src/apprt/gtk/ui/1.5/window.blp:286 msgid "Reset" -msgstr "Reiniciar" +msgstr "Restablecer" #: src/apprt/gtk/ui/1.2/surface.blp:345 src/apprt/gtk/ui/1.5/window.blp:250 msgid "Split" @@ -166,16 +166,16 @@ msgstr "Dividir abajo" #: src/apprt/gtk/ui/1.2/surface.blp:365 src/apprt/gtk/ui/1.5/window.blp:190 #: src/apprt/gtk/ui/1.5/window.blp:268 src/input/command.zig:477 msgid "Split Left" -msgstr "Dividir a la izquierda" +msgstr "Dividir hacia la izquierda" #: src/apprt/gtk/ui/1.2/surface.blp:371 src/apprt/gtk/ui/1.5/window.blp:195 #: src/apprt/gtk/ui/1.5/window.blp:273 src/input/command.zig:482 msgid "Split Right" -msgstr "Dividir a la derecha" +msgstr "Dividir hacia la derecha" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Cerrar división" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,7 +203,7 @@ msgstr "Ventana" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Cambiar título de la ventana…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,15 +221,15 @@ msgstr "Configuración" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Abrir configuración en el editor predeterminado del sistema" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Abrir configuración en una nueva ventana" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." -msgstr "Dejar en blanco para restaurar el título predeterminado." +msgstr "Dejar en blanco para restablecer el título predeterminado." #: src/apprt/gtk/ui/1.5/title-dialog.blp:9 msgid "OK" @@ -269,27 +269,27 @@ msgstr "Ejecutar un comando…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "El sistema operativo revocó el atajo de teclado." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "El sistema operativo denegó el atajo de teclado." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Atajo de teclado global no disponible" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Exportar eventos de entrada/salida de la terminal" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Exportar" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Editando el archivo de configuración" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -309,14 +309,14 @@ msgstr "" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:205 msgid "Warning: Potentially Unsafe Paste" -msgstr "Advertencia: Pegado potencialmente inseguro" +msgstr "Advertencia: pegado potencialmente inseguro" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:206 msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Pegar este texto en la terminal puede ser peligroso ya que parece que " +"Pegar este texto en la terminal puede ser peligroso ya que, parece que " "algunos comandos podrían ejecutarse." #: src/apprt/gtk/class/close_confirmation_dialog.zig:184 @@ -337,19 +337,19 @@ msgstr "¿Cerrar división?" #: src/apprt/gtk/class/close_confirmation_dialog.zig:193 msgid "All terminal sessions will be terminated." -msgstr "Todas las sesiones de terminal serán terminadas." +msgstr "Todas las sesiones de terminal se finalizarán." #: src/apprt/gtk/class/close_confirmation_dialog.zig:194 msgid "All terminal sessions in this tab will be terminated." -msgstr "Todas las sesiones de terminal en esta pestaña serán terminadas." +msgstr "Todas las sesiones de terminal en esta pestaña se finalizarán." #: src/apprt/gtk/class/close_confirmation_dialog.zig:195 msgid "All terminal sessions in this window will be terminated." -msgstr "Todas las sesiones de terminal en esta ventana serán terminadas." +msgstr "Todas las sesiones de terminal en esta ventana se finalizarán." #: src/apprt/gtk/class/close_confirmation_dialog.zig:196 msgid "The currently running process in this split will be terminated." -msgstr "El proceso actualmente en ejecución en esta división será terminado." +msgstr "El proceso en ejecución en esta división se finalizará." #: src/apprt/gtk/class/surface.zig:1181 msgid "Command Finished" @@ -375,7 +375,7 @@ msgstr "Cambiar título de la pestaña" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Cambiar título de la ventana" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -383,11 +383,11 @@ msgstr "Configuración recargada" #: src/apprt/gtk/class/window.zig:1830 msgid "Copied to clipboard" -msgstr "Copiado al portapapeles" +msgstr "Se copió al portapapeles" #: src/apprt/gtk/class/window.zig:1832 msgid "Cleared clipboard" -msgstr "Portapapeles limpiado" +msgstr "Se limpió el portapapeles" #: src/apprt/gtk/class/window.zig:1972 msgid "Ghostty Developers" @@ -395,710 +395,755 @@ msgstr "Desarrolladores de Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Restablecer terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Restablece la terminal a un estado limpio." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Copiar al portapapeles" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Copia texto seleccionado al portapapeles como texto plano y con estilos." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Copiar selección al portapapeles como texto plano" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Copia el texto seleccionado al portapapeles como texto plano." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Copiar selección al portapapeles como secuencias ANSI" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." msgstr "" +"Copia el texto seleccionado al portapapeles como secuencias de escape ANSI." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Copiar selección al portapapeles como HTML" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Copia el texto seleccionado al portapapeles como HTML." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Copiar URL al portapapeles" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Copia la URL que está debajo del cursor al portapapeles." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Copiar título de la terminal al portapapeles" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Copia el título de la terminal al portapapeles. Si el título no está " +"definido, esto no tendrá efecto." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Pegar desde el portapapeles" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Pega el contenido del portapapeles principal." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Pegar desde la selección" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Pega el contenido del portapapeles de selección." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Iniciar búsqueda" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Inicia una búsqueda, si no hay otra activa." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Buscar selección" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Inicia una búsqueda del texto actualmente seleccionado." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Finalizar búsqueda" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." msgstr "" +"Finaliza la búsqueda actual, si la hay, y oculta los elementos " +"de la interfaz." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Siguiente resultado de búsqueda" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Navega al siguiente resultado de búsqueda, si lo hay." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Resultado de búsqueda anterior" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Navega al resultado de búsqueda anterior, si lo hay." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Aumentar el tamaño de la fuente" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Aumenta el tamaño de la fuente en 1 punto." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Reducir el tamaño de la fuente" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Reduce el tamaño de la fuente en 1 punto." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Restablecer el tamaño de la fuente" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Restablece el tamaño de la fuente a su valor predeterminado." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Limpiar pantalla" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Limpia la pantalla y el historial de desplazamiento." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Seleccionar todo" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Selecciona todo el texto en la pantalla." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Desplazar hacia el principio" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Desplaza hacia el principio de la pantalla." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Desplazar hacia el final" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Desplaza hacia el final de la pantalla." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Desplazar hasta la selección" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Desplaza hacia el texto seleccionado." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Desplazar página hacia arriba" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Desplaza la pantalla hacia arriba una página." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Desplazar página hacia abajo" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Desplaza la pantalla hacia abajo una página." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla en un archivo temporal y copiar ruta" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copia el contenido de la pantalla en un archivo temporal y copia su ruta " +"al portapapeles." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla en un archivo temporal y pegar ruta" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Copia el contenido de la pantalla en un archivo temporal y pega su ruta." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla en un archivo temporal y abrirlo" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Copia el contenido de la pantalla en un archivo temporal y lo abre." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar pantalla como HTML en un archivo temporal y copiar ruta" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copia el contenido de la pantalla como HTML en un archivo temporal y " +"copia su ruta al portapapeles." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar pantalla como HTML en un archivo temporal y pegar ruta" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Copia el contenido de la pantalla como HTML en un archivo temporal y pega " +"su ruta." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar pantalla como HTML en un archivo temporal y abrirlo" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Copia el contenido de la pantalla como HTML en un archivo temporal " +"y lo abre." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar pantalla como secuencias ANSI en un archivo temporal y copiar ruta" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copia el contenido de la pantalla como secuencias de escape ANSI en un " +"archivo temporal y copia su ruta al portapapeles." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar pantalla como secuencias ANSI en un archivo temporal y pegar ruta" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copia el contenido de la pantalla como secuencias de escape ANSI en un " +"archivo temporal y pega su ruta." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" msgstr "" +"Copiar pantalla como secuencias ANSI en un archivo temporal y abrirlo" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copia el contenido de la pantalla como secuencias de escape ANSI en un " +"archivo temporal y lo abre." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección en un archivo temporal y copiar ruta" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Copia el contenido de la selección en un archivo temporal y copia su ruta " +"al portapapeles." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección en un archivo temporal y pegar ruta" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." msgstr "" +"Copia el contenido de la selección en un archivo temporal y pega su ruta." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Copiar selección en un archivo temporal y abrirlo" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Copia el contenido de la selección en un archivo temporal y lo abre." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Copiar selección como HTML en un archivo temporal y copiar ruta" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Copia el contenido de la selección como HTML en un archivo temporal y " +"copia su ruta al portapapeles." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Copiar selección como HTML en un archivo temporal y pegar ruta" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Copia el contenido de la selección como HTML en un archivo temporal y pega " +"su ruta." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Copiar selección como HTML en un archivo temporal y abrirlo" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Copia el contenido de la selección como HTML en un archivo temporal y " +"lo abre." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Copiar selección como secuencias ANSI en un archivo temporal y copiar ruta" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Copia el contenido de la selección como secuencias de escape ANSI en un " +"archivo temporal y copia su ruta al portapapeles." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Copiar selección como secuencias ANSI en un archivo temporal y pegar ruta" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Copia el contenido de la selección como secuencias de escape ANSI en un " +"archivo temporal y pega su ruta." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" msgstr "" +"Copiar selección como secuencias ANSI en un archivo temporal y abrirlo" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Copia el contenido de la selección como secuencias de escape ANSI en un " +"archivo temporal y lo abre." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Abre una nueva ventana." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Abre una nueva pestaña." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Mover pestaña a la izquierda" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Mueve la pestaña actual a la izquierda." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Mover pestaña a la derecha" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Mueve la pestaña actual a la derecha." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Mover pestaña a una nueva ventana" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Mueve la pestaña actual a una nueva ventana." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Mostrar/ocultar pestañas abiertas" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Muestra u oculta la vista de pestañas abiertas." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Cambiar título de la terminal…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Solicita un nuevo título para la terminal actual." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Solicita un nuevo título para la pestaña actual." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Divide la terminal a la izquierda." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Divide la terminal a la derecha." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Divide la terminal hacia arriba." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Divide la terminal hacia abajo." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Enfocar división: anterior" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Enfoca la división anterior, si la hay." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Enfocar división: siguiente" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Enfoca la división siguiente, si la hay." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Enfocar división: izquierda" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Enfoca la división a la izquierda, si existe." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Enfocar división: derecha" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Enfoca la división a la derecha, si existe." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Enfocar división: arriba" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Enfoca la división por encima, si existe." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Enfocar división: abajo" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Enfoca la división por debajo, si existe." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Enfocar ventana: anterior" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Enfoca la ventana anterior, si la hay." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Enfocar ventana: siguiente" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Enfoca la ventana siguiente, si la hay." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Alternar zoom de división" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Alterna el estado del zoom de la división actual." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Alternar modo de solo lectura" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Alterna el modo de solo lectura para la terminal actual." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Igualar divisiones" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Iguala el tamaño de todas las divisiones." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Restablecer tamaño de ventana" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Restablece el tamaño de la ventana al valor predeterminado." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Alternar inspector" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Alterna el inspector." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Mostrar el inspector GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Muestra el inspector GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Mostrar teclado en pantalla" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Muestra el teclado en pantalla, si está presente." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Abrir configuración en el editor predeterminado del sistema operativo" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Abre el archivo de configuración con el editor predeterminado " +"del sistema operativo." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Abrir configuración en una nueva ventana de terminal" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Abre el archivo de configuración en una nueva ventana usando " +"$EDITOR o $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Recargar configuración" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Recarga el archivo de configuración." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Cerrar terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Cierra la terminal actual." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Cierra la pestaña actual." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Cerrar otras pestañas" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Cierra todas las pestañas en esta ventana excepto la actual." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Cerrar pestañas a la derecha" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Cierra todas las pestañas a la derecha de la actual." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Cierra la ventana actual." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Cerrar todas las ventanas" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Cierra todas las ventanas." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Maximizar/restaurar ventana" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Alterna el estado maximizado de la ventana actual." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Alternar pantalla completa" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Alterna el estado de pantalla completa de la ventana actual." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Alternar decoraciones de ventana" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Alterna las decoraciones de la ventana." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Alternar ventana flotante" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Alterna si la ventana actual se mantiene sobre las demás." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Alternar entrada segura" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Alterna el modo de entrada segura." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Alternar reporte de eventos del mouse" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." msgstr "" +"Alterna si los eventos del mouse se envían a las aplicaciones de terminal." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Alternar opacidad del fondo" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "Alterna la opacidad del fondo de una ventana que se abrió con " +"transparencia." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Buscar actualizaciones" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Busca actualizaciones de la aplicación." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Deshacer" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Deshace la última acción." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Rehacer" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Rehace la última acción deshecha." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Sale de la aplicación." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Poné un pequeño Ghostty en tu terminal." From 08df9f6e29a93e16d64c3c70cf7200dab4526a15 Mon Sep 17 00:00:00 2001 From: A-Lang Date: Fri, 14 Aug 2026 12:04:09 +0800 Subject: [PATCH 035/392] po/zh_TW: remove trailing blank line at EOF --- po/zh_TW.po | 1 - 1 file changed, 1 deletion(-) diff --git a/po/zh_TW.po b/po/zh_TW.po index bbe03d6da..b517d4e08 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -1093,4 +1093,3 @@ msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." msgstr "讓你的終端機裡有點 Ghostty。" - From 89a26a39eb01a7cf34a64f9329da8304bebd4d8e Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 06:12:09 +0000 Subject: [PATCH 036/392] Update VOUCHED list (#13808) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/12664#discussioncomment-18012616) from @pluiedev. Vouch: @Zlitus Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 484218791..c63e412a0 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -324,4 +324,5 @@ ydah zenangst zenyr zeshi09 +zlitus zubb From 93e7e7e9933a339032a0499043e0f0c6d7218a7a Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Tue, 4 Aug 2026 23:57:25 +0800 Subject: [PATCH 037/392] po/zh_CN: add missing translations Frankly the number of command palette entries is a bit ridiculous, but such is life --- po/zh_CN.po | 351 ++++++++++++++++++++++++++-------------------------- 1 file changed, 176 insertions(+), 175 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index 1abf16912..d957a8fc9 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -218,11 +218,11 @@ msgstr "配置" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "在系统编辑器内打开配置" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "在新窗口内打开配置" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -266,23 +266,23 @@ msgstr "选择要执行的命令…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "系统撤销了该快捷键。" #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "系统拒绝绑定该快捷键。" #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "全局快捷键不可用" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "导出终端输入输出事件" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "导出" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" @@ -386,393 +386,393 @@ msgstr "Ghostty 开发团队" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "重置终端" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "将终端重置回初始状态。" #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "复制到剪贴板" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "" +msgstr "同时以纯文本与样式文本两种格式,将选中的文本复制到剪贴板。" #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "将选区以纯文本格式复制到剪贴板" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "以纯文本格式,将选中的文本复制到剪贴板。" #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "将选区以 ANSI 序列格式复制到剪贴板" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "以 ANSI 转义序列格式,将选中的文本复制到剪贴板。" #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "将选区以 HTML 格式复制到剪贴板" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "以 HTML 格式,将选中的文本复制到剪贴板。" #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "复制 URL 到剪贴板" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "将光标下的 URL 复制到剪贴板。" #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "复制终端标题到剪贴板" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "" +msgstr "将终端标题复制到剪贴板。没有设置终端标题时没有任何效果。" #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "粘贴剪贴板内容" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "将主剪贴板的内容粘贴进终端。" #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "粘贴主选区内容" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "将主选区的内容粘贴进终端。" #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "开始查找" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "未在进行查找时,可开始新一次查找。" #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "在选区内查找" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "在选中的文本当中开始查找。" #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "结束查找" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "结束当前进行中的查找,并隐藏相关界面。" #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "下一个匹配项" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "如果可能,移动到下一个匹配项。" #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "上一个匹配项" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "如果可能,移动到上一个匹配项。" #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "放大字号" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "将字体大小增大 1pt。" #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "缩小字号" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "将字体大小缩小 1pt。" #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "重置字号" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "将字体大小重置到默认值。" #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "清除屏幕" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "清除屏幕和回滚内容。" #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "全选" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "选择屏幕上所有文本。" #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "滚动到顶部" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "滚动到屏幕顶部。" #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "滚动到底部" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "滚动到屏幕底部。" #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "滚动到选区" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "滚动到选中的文本。" #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "向上滚动一页" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "将屏幕向上滚动一页。" #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "向下滚动一页" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "将屏幕向下滚动一页。" #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "复制屏幕内容到临时文件并复制路径" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "将屏幕内容复制到一个临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "复制屏幕内容到临时文件并粘贴路径" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "将屏幕内容复制到一个临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "复制屏幕内容到临时文件并打开" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "将屏幕内容复制到一个临时文件,并打开。" #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "以 HTML 格式复制屏幕内容到临时文件并复制路径" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "将屏幕内容以 HTML 格式复制到一个临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "以 HTML 格式复制屏幕内容到临时文件并粘贴路径" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "将屏幕内容以 HTML 格式复制到一个临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "以 HTML 格式复制屏幕内容到临时文件并打开" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "将屏幕内容以 HTML 格式复制到一个临时文件,并打开。" #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "以 ANSI 序列格式复制屏幕内容到临时文件并复制路径" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "将屏幕内容以 ANSI 转义序列格式复制到一个临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "以 ANSI 序列格式复制屏幕内容到临时文件并粘贴路径" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "将屏幕内容以 ANSI 转义序列格式复制到一个临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "以 ANSI 序列格式复制屏幕内容到临时文件并打开" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "将屏幕内容以 ANSI 转义序列格式复制到一个临时文件,并打开。" #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "将选区复制到临时文件并复制路径" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "将选中文本复制到临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "复制选区到临时文件并粘贴路径" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "将选中文本复制到临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "复制选区到临时文件并打开" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "将选中文本复制到临时文件,并打开。" #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "以 HTML 格式复制选区到临时文件并复制路径" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "将选中文本以 HTML 格式复制到临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "以 HTML 格式复制选区到临时文件并粘贴路径" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "将选中文本以 HTML 格式复制到临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "以 HTML 格式复制选区到临时文件并打开" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "将选中文本以 HTML 格式复制到临时文件,并打开。" #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "以 ANSI 序列格式复制选区到临时文件并复制路径" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "" +msgstr "将选中文本以 ANSI 转义序列格式复制到临时文件,并将其路径复制到剪贴板。" #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "以 ANSI 序列格式复制选区到临时文件并粘贴路径" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "" +msgstr "将选中文本以 ANSI 转义序列格式复制到临时文件,并将其路径粘贴进终端。" #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "以 ANSI 序列格式复制选区到临时文件并打开" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "将选中文本以 ANSI 转义序列格式复制到临时文件,并打开。" #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "新建一个窗口。" #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "新建一个标签页。" #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "标签页左移" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "将当前标签页向左移动。" #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "标签页右移" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "将当前标签页向右移动。" #: src/input/command.zig:446 msgid "Move Tab to New Window" @@ -784,312 +784,313 @@ msgstr "" #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "切换标签页预览" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "切换标签页预览。" #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "更改终端标题…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "更改当前终端的标题。" #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "更改当前标签页的标题。" #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "向左创建新分屏。" #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "向右创建新分屏。" #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "向上创建新分屏。" #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "向下创建新分屏。" #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "聚焦上一个分屏" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "如果存在,将焦点移动到上一个分屏。" #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "聚焦下一个分屏" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "如果存在,将焦点移动到下一个分屏。" #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "聚焦左侧分屏" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "如果存在,将焦点移动到左侧的分屏。" #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "聚焦右侧分屏" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "如果存在,将焦点移动到右侧的分屏。" #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "聚焦上方分屏" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "如果存在,将焦点移动到上方的分屏。" #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "聚焦下方分屏" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "如果存在,将焦点移动到下方的分屏。" #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "聚焦上一个窗口" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "如果可能,将焦点移动到上一个窗口。" #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "聚焦下一个窗口" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "如果可能,将焦点移动到下一个窗口。" #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "切换分屏放大" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "切换当前分屏的放大状态。" #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "切换只读模式" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "切换当前分屏的只读状态。" #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "均分分屏大小" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "使所有分屏大小相等。" #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "重置窗口大小" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "将窗口重置到默认大小。" #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "切换调试器" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "切换终端调试器。" #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "显示 GTK 调试器" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "显示 GTK 调试器。" #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "显示屏幕键盘" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "如果可能,显示屏幕键盘。" #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "用系统编辑器打开配置" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "用系统默认的编辑器打开配置文件。" #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "在新终端窗口打开配置" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "在新终端窗口内用 $EDITOR 或 $VISUAL 程序打开配置文件。" #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "重新加载配置" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "重新加载配置文件。" #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "关闭终端" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "关闭当前终端。" #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "关闭当前标签页。" #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "关闭其他标签页" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "关闭当前窗口内除了当前标签页以外的所有标签页。" #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "关闭右侧标签页" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "关闭当前标签页右侧的所有标签页。" #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "关闭当前窗口。" #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "关闭所有窗口" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "关闭所有窗口。" #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "切换最大化" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "切换当前窗口的最大化状态。" #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "切换全屏" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "切换当前窗口的全屏状态。" #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "切换窗口装饰" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "切换是否显示窗口装饰,如标题栏、边框等。" #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "切换窗口置顶" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "切换当前窗口的置顶状态。" #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "切换安全输入" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "切换安全输入模式。" #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "切换鼠标事件上报" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "切换是否向终端应用上报鼠标事件。" #: src/input/command.zig:678 +# Opacity 实际指不透明度,但是这样表述太拗口了 msgid "Toggle Background Opacity" -msgstr "" +msgstr "切换背景透明度" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "切换最初透明的窗口的背景透明度。" #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "检查更新" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "检查应用是否有版本更新。" #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "撤销" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "撤销上一个操作。" #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "重做" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "重做上一个撤销的操作。" #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "退出应用。" #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "把一只小 Ghostty 塞进终端里。" From 1dd31f9e9458ac1bf44860d55021f9d54e53c646 Mon Sep 17 00:00:00 2001 From: derVedro Date: Fri, 14 Aug 2026 09:21:30 +0200 Subject: [PATCH 038/392] i18n(ru): work in progress on Russian translation --- po/ru.po | 74 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/po/ru.po b/po/ru.po index ae2cc263b..94bbcc2c4 100644 --- a/po/ru.po +++ b/po/ru.po @@ -4,20 +4,21 @@ # This file is distributed under the same license as the com.mitchellh.ghostty package. # blackzeshi , 2025. # Ivan Bastrakov , 2025. +# derVedro и зайчатки разума , 2026. +# msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-08-12 23:12+0100\n" -"Last-Translator: derVedro \n" -"Language-Team: Russian \n" +"PO-Revision-Date: 2026-08-13 15:43+0200\n" +"Last-Translator: derVedro \n" +"Language-Team: Russian\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" @@ -203,7 +204,6 @@ msgstr "Закрыть вкладку" msgid "Window" msgstr "Окно" -#. ლʕಠᴥಠʔლ или же "Переименовать заголовок окна…" ? #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" msgstr "Переименовать окно…" @@ -413,16 +413,16 @@ msgstr "Копировать в буфер обмена" msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" -"Копировать выделенный текст в буфер обмена в обычном и в форматированном " +"Копировать выделенный текст в буфер обмена в простом и в форматированном " "виде." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "Копировать выделение как обычный текст в буфер обмена" +msgstr "Копировать выделение как простой текст в буфер обмена" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "Копировать выделенный текст как обычный текст в буфер обмена." +msgstr "Копировать выделенный текст как простой текст в буфер обмена." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" @@ -431,7 +431,9 @@ msgstr "Копировать выделение как ANSI-последоват # ლʕಠᴥಠʔლ, escape-последовательности ANSI, экранированные ANSI? ...? #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "Копировать выделенный текст как escape-последовательности ANSI в буфер обмена." +msgstr "" +"Копировать выделенный текст как escape-последовательности ANSI в буфер " +"обмена." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" @@ -474,10 +476,10 @@ msgstr "Вставить содержимое основного буфера о msgid "Paste from Selection" msgstr "Вставить из выделения" -#. ლʕಠᴥಠʔლ это что за покемон? +#. ლʕಠᴥಠʔლ хмммм... #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Вставить содержимое буфера обмена." #: src/input/command.zig:197 msgid "Start Search" @@ -547,7 +549,6 @@ msgstr "Сбросить размер шрифта до значения по у msgid "Clear Screen" msgstr "Очистить экран" -#. пока не ясно, что тут. #: src/input/command.zig:244 msgid "Clear the screen and scrollback." msgstr "Очистить экран и буфер прокрутки." @@ -572,7 +573,6 @@ msgstr "Прокрутить к началу экрана." msgid "Scroll to Bottom" msgstr "Прокрутить в конец" -#. или лучше "прокрутить на низ экрана" ? #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." msgstr "Прокрутить к концу экрана." @@ -661,11 +661,14 @@ msgstr "Скопировать экран как HTML во временный ф #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "Скопировать содержимое экрана как HTML во временный файл и открыть его." +msgstr "" +"Скопировать содержимое экрана как HTML во временный файл и открыть его." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "Скопировать экран как ANSI-последовательности во временный файл и скопировать путь" +msgstr "" +"Скопировать экран как ANSI-последовательности во временный файл и " +"скопировать путь" #: src/input/command.zig:331 msgid "" @@ -677,7 +680,9 @@ msgstr "" #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "Скопировать экран как ANSI-последовательности во временный файл и вставить путь" +msgstr "" +"Скопировать экран как ANSI-последовательности во временный файл и вставить " +"путь" #: src/input/command.zig:339 msgid "" @@ -689,7 +694,8 @@ msgstr "" #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "Скопировать экран как ANSI-последовательности во временный файл и открыть" +msgstr "" +"Скопировать экран как ANSI-последовательности во временный файл и открыть" #: src/input/command.zig:347 msgid "" @@ -708,8 +714,8 @@ msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" -"Скопировать содержимое выделения во временный файл и скопировать путь к " -"нему в буфер обмена." +"Скопировать содержимое выделения во временный файл и скопировать путь к нему " +"в буфер обмена." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" @@ -760,7 +766,8 @@ msgstr "Скопировать выделение как HTML во времен #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "Скопировать содержимое выделения как HTML во временный файл и открыть его." +msgstr "" +"Скопировать содержимое выделения как HTML во временный файл и открыть его." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" @@ -778,7 +785,9 @@ msgstr "" #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "Скопировать выделение как ANSI-последовательности во временный файл и вставить путь" +msgstr "" +"Скопировать выделение как ANSI-последовательности во временный файл и " +"вставить путь" #: src/input/command.zig:407 msgid "" @@ -790,7 +799,8 @@ msgstr "" #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "Скопировать выделение как ANSI-последовательности во временный файл и открыть" +msgstr "" +"Скопировать выделение как ANSI-последовательности во временный файл и открыть" #: src/input/command.zig:415 msgid "" @@ -840,19 +850,19 @@ msgstr "Переключить обзор вкладок" msgid "Toggle the tab overview." msgstr "Переключить обзор вкладок." -#. "Переименовать заголовок терминала…" ? +#. трудно понять чем терминал отличается от окна, но это разные вещи #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "Переименовать терминал…" +msgstr "Переименовать заголовок терминала…" -#. тоже пока не ясно +#. тоже пока неясно #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Переименовать заголовок текущего терминала." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Переименовать заголовок текущей вкладки." #: src/input/command.zig:478 msgid "Split the terminal to the left." @@ -870,7 +880,7 @@ msgstr "Разделить терминал вверх." msgid "Split the terminal down." msgstr "Разделить терминал вниз." -#. позже разберусь с этими фокусами, пока не ясно +#. позже разберусь с этими фокусами, стоит переводить как "перейти" #: src/input/command.zig:500 msgid "Focus Split: Previous" msgstr "" @@ -935,7 +945,6 @@ msgstr "" msgid "Focus the next window, if any." msgstr "" -#. тоже пока не совсем понятно #: src/input/command.zig:545 msgid "Toggle Split Zoom" msgstr "" @@ -1142,8 +1151,9 @@ msgstr "Выйти из приложения." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" +#. пригласить маленького призрака в гостти? #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "Добавьте немного Ghostty в свой терминал." +msgstr "Добавить маленького госття в терминал." From 6cadad06f468745651a6bb53e64d31cc8fae9e24 Mon Sep 17 00:00:00 2001 From: "dolzhenko.e4" Date: Thu, 13 Aug 2026 20:47:56 +0300 Subject: [PATCH 039/392] termio: preserve UTF-8 in desktop notification truncation --- src/termio/stream_handler.zig | 45 +++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index ac269e7bc..12ba3b1cd 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -16,6 +16,17 @@ const posix = std.posix; const log = std.log.scoped(.io_handler); +/// Copy the longest valid UTF-8 prefix that fits in a sentinel-terminated +/// destination. Desktop notification strings are passed to platform APIs that +/// require valid UTF-8, so truncation must not split a multibyte codepoint. +fn copyUtf8Z(comptime capacity: usize, dst: *[capacity:0]u8, src: []const u8) void { + var len = @min(src.len, capacity); + while (len > 0 and !std.unicode.utf8ValidateSlice(src[0..len])) : (len -= 1) {} + + @memcpy(dst[0..len], src[0..len]); + dst[len] = 0; +} + /// This is used as the handler for the terminal.Stream type. This is /// stateful and is expected to live for the entire lifetime of the terminal. /// It is NOT VALID to stop a stream handler, create a new one, and use that @@ -1348,13 +1359,16 @@ pub const StreamHandler = struct { ) !void { var message = apprt.surface.Message{ .desktop_notification = undefined }; - const title_len = @min(title.len, message.desktop_notification.title.len); - @memcpy(message.desktop_notification.title[0..title_len], title[0..title_len]); - message.desktop_notification.title[title_len] = 0; - - const body_len = @min(body.len, message.desktop_notification.body.len); - @memcpy(message.desktop_notification.body[0..body_len], body[0..body_len]); - message.desktop_notification.body[body_len] = 0; + copyUtf8Z( + message.desktop_notification.title.len, + &message.desktop_notification.title, + title, + ); + copyUtf8Z( + message.desktop_notification.body.len, + &message.desktop_notification.body, + body, + ); self.surfaceMessageWriter(message); } @@ -1466,3 +1480,20 @@ pub const StreamHandler = struct { self.surfaceMessageWriter(.{ .progress_report = report }); } }; + +test "copyUtf8Z truncates at a UTF-8 codepoint boundary" { + var dst: [5:0]u8 = undefined; + + copyUtf8Z(dst.len, &dst, "abcdЯ"); + + try std.testing.expectEqualStrings("abcd", std.mem.sliceTo(&dst, 0)); + try std.testing.expect(std.unicode.utf8ValidateSlice(std.mem.sliceTo(&dst, 0))); +} + +test "copyUtf8Z preserves UTF-8 that fits" { + var dst: [5:0]u8 = undefined; + + copyUtf8Z(dst.len, &dst, "abcЯ"); + + try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); +} From 034f5843f21b7a3c9924d5e42ee34ee784699763 Mon Sep 17 00:00:00 2001 From: AnmiTaliDev Date: Fri, 14 Aug 2026 12:33:46 +0500 Subject: [PATCH 040/392] fix: apply kk translation review feedback --- po/kk.po | 139 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 54 deletions(-) diff --git a/po/kk.po b/po/kk.po index 429553883..6c5a3a8c3 100644 --- a/po/kk.po +++ b/po/kk.po @@ -222,7 +222,7 @@ msgstr "Конфигурация" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "Конфигурацияны ОЖ өңдегішінде ашу" +msgstr "Конфигурацияны ОЖ түзеткішінде ашу" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" @@ -278,7 +278,7 @@ msgstr "Пернелер тіркесіміне жүйе тыйым салды." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "Жаһандық пернелер тіркесімі қолжетімсіз" +msgstr "Глобалды пернелер тіркесімі қолжетімсіз" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" @@ -290,7 +290,7 @@ msgstr "Экспорттау" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "Конфигурация файлын өңдеу" +msgstr "Конфигурация файлын түзету" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -425,7 +425,8 @@ msgstr "Таңдауды ANSI тізбектері ретінде алмасу #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "Таңдалған мәтінді ANSI escape-тізбектері ретінде алмасу буферіне көшіру." +msgstr "" +"Таңдалған мәтінді ANSI escape-тізбектері ретінде алмасу буферіне көшіру." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" @@ -437,11 +438,11 @@ msgstr "Таңдалған мәтінді HTML ретінде алмасу бу #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "URL мекенжайын алмасу буферіне көшіру" +msgstr "URL адресін алмасу буферіне көшіру" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "Курсор астындағы URL мекенжайын алмасу буферіне көшіру." +msgstr "Курсор астындағы URL адресін алмасу буферіне көшіру." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" @@ -451,7 +452,9 @@ msgstr "Терминал атауын алмасу буферіне көшіру msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." -msgstr "Терминал атауын алмасу буферіне көшіру. Терминал атауы орнатылмаған болса, бұл әрекеттің әсері болмайды." +msgstr "" +"Терминал атауын алмасу буферіне көшіру. Терминал атауы орнатылмаған болса, " +"бұл әрекеттің әсері болмайды." #: src/input/command.zig:185 msgid "Paste from Clipboard" @@ -491,7 +494,9 @@ msgstr "Іздеуді аяқтау" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "Ағымдағы іздеу болса, оны аяқтап, барлық графикалық интерфейс элементтерін жасыру." +msgstr "" +"Ағымдағы іздеу болса, оны аяқтап, барлық графикалық интерфейс элементтерін " +"жасыру." #: src/input/command.zig:215 msgid "Next Search Result" @@ -499,7 +504,7 @@ msgstr "Келесі іздеу нәтижесі" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "Болса, келесі іздеу нәтижесіне өту." +msgstr "Бар болса, келесі іздеу нәтижесіне өту." #: src/input/command.zig:219 msgid "Previous Search Result" @@ -507,7 +512,7 @@ msgstr "Алдыңғы іздеу нәтижесі" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "Болса, алдыңғы іздеу нәтижесіне өту." +msgstr "Бар болса, алдыңғы іздеу нәтижесіне өту." #: src/input/command.zig:225 msgid "Increase Font Size" @@ -597,7 +602,8 @@ msgstr "Экранды уақытша файлға көшіріп, жолын к msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "Экран мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Экран мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" @@ -624,7 +630,9 @@ msgstr "Экранды HTML ретінде уақытша файлға көші msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу " +"буферіне көшіру." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" @@ -634,7 +642,8 @@ msgstr "Экранды HTML ретінде уақытша файлға көші msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." +msgstr "" +"Экран мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" @@ -652,17 +661,22 @@ msgstr "Экранды ANSI тізбектері ретінде уақытша msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл " +"жолын алмасу буферіне көшіру." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "Экранды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" +msgstr "" +"Экранды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын кірістіру." +msgstr "" +"Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл " +"жолын кірістіру." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" @@ -672,7 +686,9 @@ msgstr "Экранды ANSI тізбектері ретінде уақытша msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны ашу." +msgstr "" +"Экран мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны " +"ашу." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" @@ -682,7 +698,8 @@ msgstr "Таңдауды уақытша файлға көшіріп, жолын msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "Таңдау мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Таңдау мазмұнын уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" @@ -710,7 +727,9 @@ msgstr "Таңдауды HTML ретінде уақытша файлға көш msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын алмасу " +"буферіне көшіру." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" @@ -720,7 +739,8 @@ msgstr "Таңдауды HTML ретінде уақытша файлға көш msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." +msgstr "" +"Таңдау мазмұнын HTML ретінде уақытша файлға көшіріп, файл жолын кірістіру." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" @@ -738,17 +758,22 @@ msgstr "Таңдауды ANSI тізбектері ретінде уақытша msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." -msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын алмасу буферіне көшіру." +msgstr "" +"Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл " +"жолын алмасу буферіне көшіру." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "Таңдауды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" +msgstr "" +"Таңдауды ANSI тізбектері ретінде уақытша файлға көшіріп, жолын кірістіру" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." -msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл жолын кірістіру." +msgstr "" +"Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, файл " +"жолын кірістіру." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" @@ -758,7 +783,9 @@ msgstr "Таңдауды ANSI тізбектері ретінде уақытша msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны ашу." +msgstr "" +"Таңдау мазмұнын ANSI escape-тізбектері ретінде уақытша файлға көшіріп, оны " +"ашу." #: src/input/command.zig:422 msgid "Open a new window." @@ -794,11 +821,11 @@ msgstr "Ағымдағы бетті жаңа терезеге жылжыту." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "Беттер шолуын қосу/өшіру" +msgstr "Беттер шолуын іске қосу/сөндіру" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "Беттер шолуын қосу немесе өшіру." +msgstr "Беттер шолуын іске қосу немесе сөндіру." #: src/input/command.zig:458 msgid "Change Terminal Title…" @@ -834,7 +861,7 @@ msgstr "Фокусты бөлікке ауыстыру: алдыңғы" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "Болса, алдыңғы бөлікке фокусты ауыстыру." +msgstr "Бар болса, алдыңғы бөлікке фокусты ауыстыру." #: src/input/command.zig:505 msgid "Focus Split: Next" @@ -842,7 +869,7 @@ msgstr "Фокусты бөлікке ауыстыру: келесі" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "Болса, келесі бөлікке фокусты ауыстыру." +msgstr "Бар болса, келесі бөлікке фокусты ауыстыру." #: src/input/command.zig:510 msgid "Focus Split: Left" @@ -882,7 +909,7 @@ msgstr "Фокусты терезеге ауыстыру: алдыңғы" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "Болса, алдыңғы терезеге фокусты ауыстыру." +msgstr "Бар болса, алдыңғы терезеге фокусты ауыстыру." #: src/input/command.zig:538 msgid "Focus Window: Next" @@ -890,23 +917,23 @@ msgstr "Фокусты терезеге ауыстыру: келесі" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "Болса, келесі терезеге фокусты ауыстыру." +msgstr "Бар болса, келесі терезеге фокусты ауыстыру." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "Бөлікті ұлғайтуды қосу/өшіру" +msgstr "Бөлікті ұлғайтуды іске қосу/сөндіру" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "Ағымдағы бөліктің ұлғайту күйін қосу немесе өшіру." +msgstr "Ағымдағы бөліктің ұлғайту күйін іске қосу немесе сөндіру." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "Тек оқу режимін қосу/өшіру" +msgstr "Тек оқу режимін іске қосу/сөндіру" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "Ағымдағы терминал үшін тек оқу режимін қосу немесе өшіру." +msgstr "Ағымдағы терминал үшін тек оқу режимін іске қосу немесе сөндіру." #: src/input/command.zig:557 msgid "Equalize Splits" @@ -926,11 +953,11 @@ msgstr "Терезе өлшемін бастапқы мәніне келтіру #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "Инспекторды қосу/өшіру" +msgstr "Инспекторды іске қосу/сөндіру" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "Инспекторды қосу немесе өшіру." +msgstr "Инспекторды іске қосу немесе сөндіру." #: src/input/command.zig:575 msgid "Show the GTK Inspector" @@ -950,11 +977,11 @@ msgstr "Бар болса, экрандағы пернетақтаны көрс #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "Конфигурацияны ОЖ өңдегішімен ашу" +msgstr "Конфигурацияны ОЖ түзеткішімен ашу" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "Конфигурация файлын ОЖ бастапқы өңдегішімен ашу." +msgstr "Конфигурация файлын ОЖ бастапқы түзеткішімен ашу." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" @@ -1014,59 +1041,63 @@ msgstr "Барлық терезелерді жабу." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "Жаюды қосу/өшіру" +msgstr "Жаюды іске қосу/сөндіру" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "Ағымдағы терезенің жайылған күйін қосу немесе өшіру." +msgstr "Ағымдағы терезенің жайылған күйін іске қосу немесе сөндіру." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "Толық экранды қосу/өшіру" +msgstr "Толық экранды іске қосу/сөндіру" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "Ағымдағы терезенің толық экран күйін қосу немесе өшіру." +msgstr "Ағымдағы терезенің толық экран күйін іске қосу немесе сөндіру." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "Терезе безендірулерін қосу/өшіру" +msgstr "Терезе безендірулерін іске қосу/сөндіру" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "Терезе безендірулерін қосу немесе өшіру." +msgstr "Терезе безендірулерін іске қосу немесе сөндіру." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "Үстінде қалқып тұруды қосу/өшіру" +msgstr "Үстінде қалқып тұруды іске қосу/сөндіру" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "Ағымдағы терезенің үстінде қалқып тұру күйін қосу немесе өшіру." +msgstr "Ағымдағы терезенің үстінде қалқып тұру күйін іске қосу немесе сөндіру." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "Қауіпсіз енгізуді қосу/өшіру" +msgstr "Қауіпсіз енгізуді іске қосу/сөндіру" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "Қауіпсіз енгізу режимін қосу немесе өшіру." +msgstr "Қауіпсіз енгізу режимін іске қосу немесе сөндіру." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "Тінтуір туралы хабарлауды қосу/өшіру" +msgstr "Тышқан туралы хабарлауды іске қосу/сөндіру" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "Тінтуір оқиғалары терминал қолданбаларына хабарлануын қосу немесе өшіру." +msgstr "" +"Тышқан оқиғалары терминал қолданбаларына хабарлануын іске қосу немесе " +"сөндіру." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "Фон мөлдірсіздігін қосу/өшіру" +msgstr "Фон мөлдірсіздігін іске қосу/сөндіру" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "Мөлдір күйде іске қосылған терезенің фон мөлдірсіздігін қосу немесе өшіру." +msgstr "" +"Мөлдір күйде іске қосылған терезенің фон мөлдірсіздігін іске қосу немесе " +"сөндіру." #: src/input/command.zig:684 msgid "Check for Updates" @@ -1090,7 +1121,7 @@ msgstr "Қайталау" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "Болдырылмаған соңғы әрекетті қайталау." +msgstr "Соңғы болдырылмаған әрекетті қайталау." #: src/input/command.zig:703 msgid "Quit the application." @@ -1102,4 +1133,4 @@ msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "Терминалыңызға аздап Ghostty қосыңыз." \ No newline at end of file +msgstr "Терминалыңызға аздап Ghostty қосыңыз." From f2022fe88d57f1b93913de1087ec4f3e459ef3b7 Mon Sep 17 00:00:00 2001 From: Lukas <134181853+bo2themax@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:29:24 +0200 Subject: [PATCH 041/392] macOS: avoid holding SurfaceView when sending notifications --- .../Surface View/SurfaceView_AppKit.swift | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 35b99e436..6af4ed1f1 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -1787,20 +1787,31 @@ extension Ghostty { // Note the callback may be executed on a background thread as documented // so we need @MainActor since we're reading/writing view state. - Task { @MainActor in + // We use [weak self] here because we don't want to extend the surface's + // lifetime when a notification is triggered right before the surface closes. + Task { @MainActor [weak self] in do { try await UNUserNotificationCenter.current().add(request) + guard let focused = self?.focused else { + // We remove the notification if the surface is deallocated. + UNUserNotificationCenter.current() + .removeDeliveredNotifications(withIdentifiers: [uuid]) + return + } + // We need to keep track of this notification so we can remove it // under certain circumstances - notificationIdentifiers.insert(uuid) + self?.notificationIdentifiers.insert(uuid) // If we're focused then we schedule to remove the notification // after a few seconds. If we gain focus we automatically remove it // in focusDidChange. - if self.focused { - try await Task.sleep(for: .seconds(3)) - notificationIdentifiers.remove(uuid) + if focused { + // If the suspension is failed, we remove the notification anyway. + try? await Task.sleep(for: .seconds(3)) + self?.notificationIdentifiers.remove(uuid) + // We remove the notification if the surface is deallocated while we wait. UNUserNotificationCenter.current() .removeDeliveredNotifications(withIdentifiers: [uuid]) } From ae6d97ea71b8ad4bb0d3837cc807d6ae097d4145 Mon Sep 17 00:00:00 2001 From: "dolzhenko.e4" Date: Fri, 14 Aug 2026 16:17:19 +0300 Subject: [PATCH 042/392] termio: avoid rescanning UTF-8 prefixes --- src/termio/stream_handler.zig | 59 ++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 12ba3b1cd..1267649fc 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -16,12 +16,22 @@ const posix = std.posix; const log = std.log.scoped(.io_handler); -/// Copy the longest valid UTF-8 prefix that fits in a sentinel-terminated -/// destination. Desktop notification strings are passed to platform APIs that -/// require valid UTF-8, so truncation must not split a multibyte codepoint. +/// UTF-8 continuation bytes occupy the range 0x80 through 0xBF. +fn isUtf8ContinuationByte(byte: u8) bool { + return switch (byte) { + 0x80...0xBF => true, + else => false, + }; +} + +/// Copy as much of `src` as fits, backing up from a UTF-8 continuation byte +/// so valid input is never truncated in the middle of a codepoint. fn copyUtf8Z(comptime capacity: usize, dst: *[capacity:0]u8, src: []const u8) void { var len = @min(src.len, capacity); - while (len > 0 and !std.unicode.utf8ValidateSlice(src[0..len])) : (len -= 1) {} + while (len > 0 and + len < src.len and + isUtf8ContinuationByte(src[len])) : (len -= 1) + {} @memcpy(dst[0..len], src[0..len]); dst[len] = 0; @@ -1481,13 +1491,46 @@ pub const StreamHandler = struct { } }; -test "copyUtf8Z truncates at a UTF-8 codepoint boundary" { +test "copyUtf8Z handles len at the final byte of every UTF-8 sequence length" { + var dst_1_byte: [1:0]u8 = undefined; + const src_ending_in_1_byte_codepoint = "ab"; + try std.testing.expect(!isUtf8ContinuationByte( + src_ending_in_1_byte_codepoint[dst_1_byte.len], + )); + copyUtf8Z(dst_1_byte.len, &dst_1_byte, src_ending_in_1_byte_codepoint); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_1_byte, 0)); + + var dst_2_bytes: [2:0]u8 = undefined; + const src_ending_in_2_byte_codepoint = "aЯ"; + try std.testing.expect(isUtf8ContinuationByte( + src_ending_in_2_byte_codepoint[dst_2_bytes.len], + )); + copyUtf8Z(dst_2_bytes.len, &dst_2_bytes, src_ending_in_2_byte_codepoint); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_2_bytes, 0)); + + var dst_3_bytes: [3:0]u8 = undefined; + const src_ending_in_3_byte_codepoint = "a€"; + try std.testing.expect(isUtf8ContinuationByte( + src_ending_in_3_byte_codepoint[dst_3_bytes.len], + )); + copyUtf8Z(dst_3_bytes.len, &dst_3_bytes, src_ending_in_3_byte_codepoint); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_3_bytes, 0)); + + var dst_4_bytes: [4:0]u8 = undefined; + const src_ending_in_4_byte_codepoint = "a😀"; + try std.testing.expect(isUtf8ContinuationByte( + src_ending_in_4_byte_codepoint[dst_4_bytes.len], + )); + copyUtf8Z(dst_4_bytes.len, &dst_4_bytes, src_ending_in_4_byte_codepoint); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_4_bytes, 0)); +} + +test "copyUtf8Z keeps a complete codepoint at the truncation boundary" { var dst: [5:0]u8 = undefined; - copyUtf8Z(dst.len, &dst, "abcdЯ"); + copyUtf8Z(dst.len, &dst, "abcЯz"); - try std.testing.expectEqualStrings("abcd", std.mem.sliceTo(&dst, 0)); - try std.testing.expect(std.unicode.utf8ValidateSlice(std.mem.sliceTo(&dst, 0))); + try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); } test "copyUtf8Z preserves UTF-8 that fits" { From 375ce787466f7bd666f5d8b2e32182ade296dcf3 Mon Sep 17 00:00:00 2001 From: Uzair Aftab Date: Thu, 13 Aug 2026 16:11:29 +0200 Subject: [PATCH 043/392] i18n: Update Norwegian translations Co-authored-by: Aleksander Eriksen --- po/nb.po | 366 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 199 insertions(+), 167 deletions(-) diff --git a/po/nb.po b/po/nb.po index ca50776a3..01ce40ab9 100644 --- a/po/nb.po +++ b/po/nb.po @@ -2,7 +2,7 @@ # Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors # This file is distributed under the same license as the com.mitchellh.ghostty package. # Hanna Rose , 2025. -# Uzair Aftab , 2025. +# Uzair Aftab , 2025. # Christoffer Tønnessen , 2025. # cryptocode , 2025. # @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-12 15:50+0000\n" -"Last-Translator: Hanna Rose \n" +"PO-Revision-Date: 2026-08-12 21:34+0200\n" +"Last-Translator: Uzair Aftab \n" "Language-Team: Norwegian Bokmal \n" "Language: nb\n" "MIME-Version: 1.0\n" @@ -176,7 +176,7 @@ msgstr "Del til høyre" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Lukk delt vindu" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -204,7 +204,7 @@ msgstr "Vindu" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Endre vindustittel…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -222,11 +222,11 @@ msgstr "Konfigurasjon" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Åpne konfigurasjonen i OS editoren" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Åpne konfigurasjonen i et nytt vindu" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -270,27 +270,27 @@ msgstr "Kjør en kommando…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Tastatursnarveien ble tilbakekalt av systemet." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Tastatursnarveien ble avvist av systemet." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Global tastatursnarvei er utilgjengelig" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Eksporter terminalens IO-hendelser" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Eksporter" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Redigerer konfigurasjonsfilen" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -376,7 +376,7 @@ msgstr "Endre fanetittel" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Endre vindustittel" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -396,710 +396,742 @@ msgstr "Ghostty-utviklere" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Nullstill terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Nullstill terminalen til standardtilstanden." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Kopier til utklippstavlen" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Kopier merket tekst til utklippstavlen som formatert- og ren tekst." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Kopier markeringen som ren tekst til utklippstavlen" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Kopier merket tekst til utklippstavlen som ren tekst." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Kopier markeringen som ANSI-skiftesekvenser til utklippstavlen" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Kopier den merkede teksten som ANSI-skiftesekvenser til utklippstavlen." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Kopier markeringen som HTML til utklippstavlen" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Kopier den merkede teksten som HTML til utklippstavlen." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Kopier nettadressen til utklippstavlen" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Kopier nettadressen under markøren til utklippstavlen." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Kopier terminaltittel til utklippstavlen" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Kopier terminaltittelen til utklippstavlen. Hvis terminalen ikke har noen " +"tittel, skjer det ingenting." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Lim inn fra utklippstavlen" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Lim inn innholdet fra hovedutklippstavlen." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Lim inn fra markering" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Lim inn innholdet fra den aktive markeringen." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Start søk" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Start et søk hvis det ikke allerede pågår et." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Søk etter merket tekst" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Start et søk etter den merkede teksten." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Avslutt søk" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Avslutt et eventuelt søk og skjul alle GUI-elementer." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Neste søkeresultat" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Gå til neste søkeresultat dersom det finnes." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Forrige søkeresultat" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Gå til forrige søkeresultat dersom det finnes." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Øk skriftstørrelsen" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Øk skriftstørrelsen med 1 punkt." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Reduser skriftstørrelsen" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Reduser skriftstørrelsen med 1 punkt." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Gjenopprett standard skriftstørrelse" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Sett skriftstørrelsen tilbake til standardverdien." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Tøm skjermen" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Tøm skjermen og rulleminnet." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Velg alt" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Velg all tekst på skjermen." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Rull til toppen" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Rull til toppen av skjermen." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Rull til bunnen" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Rull til bunnen av skjermen." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Rull til markeringen" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Rull til merket tekst." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Rull én side opp" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Rull én side opp på skjermen." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Rull én side ned" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Rull én side ned på skjermen." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Kopier skjerminnhold til en midlertidig fil og kopier filstien" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "Kopier skjerminnholdet til en midlertidig fil og kopier filstien til utklippstavlen." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Kopier skjerminnhold til en midlertidig fil og lim inn filstien" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "Kopier skjerminnholdet til en midlertidig fil og lim inn filstien." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Kopier skjerminnhold til en midlertidig fil og åpne filen" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Kopier skjerminnholdet til en midlertidig fil og åpne filen." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Kopier skjerminnhold som HTML til en midlertidig fil og kopier filstien" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "Kopier skjerminnholdet som HTML til en midlertidig fil og kopier filstien til utklippstavlen." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Kopier skjerminnhold som HTML til en midlertidig fil og lim inn filstien" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "Kopier skjerminnholdet som HTML til en midlertidig fil og lim inn filstien." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Kopier skjerminnhold som HTML til en midlertidig fil og åpne filen" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Kopier skjerminnholdet som HTML til en midlertidig fil og åpne filen." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Kopier skjerminnhold som ANSI-skiftesekvenser til en midlertidig fil og " +"kopier filstien" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Kopier skjerminnholdet som ANSI-skiftesekvenser til en midlertidig fil og " +"kopier filstien til utklippstavlen." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Kopier skjerminnhold som ANSI-skiftesekvenser til en midlertidig fil og lim " +"inn filstien" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Kopier skjerminnholdet som ANSI-skiftesekvenser til en midlertidig fil og " +"lim inn filstien." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" msgstr "" +"Kopier skjerminnhold som ANSI-skiftesekvenser til en midlertidig fil og " +"åpne filen" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Kopier skjerminnholdet som ANSI-skiftesekvenser til en midlertidig fil og " +"åpne filen." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Kopier markeringen til en midlertidig fil og kopier filstien" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Kopier markeringen til en midlertidig fil og kopier filstien til " +"utklippstavlen." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Kopier markeringen til en midlertidig fil og lim inn filstien" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "Kopier markeringen til en midlertidig fil og lim inn filstien." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Kopier markeringen til en midlertidig fil og åpne filen" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Kopier markeringen til en midlertidig fil og åpne filen." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Kopier markeringen som HTML til en midlertidig fil og kopier filstien" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Kopier markeringen som HTML til en midlertidig fil og kopier filstien til " +"utklippstavlen." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Kopier markeringen som HTML til en midlertidig fil og lim inn filstien" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "Kopier markeringen som HTML til en midlertidig fil og lim inn filstien." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Kopier markeringen som HTML til en midlertidig fil og åpne filen" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Kopier markeringen som HTML til en midlertidig fil og åpne filen." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og kopier " +"filstien" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og " +"kopier filstien til utklippstavlen." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og lim inn " +"filstien" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og lim " +"inn filstien." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og åpne " +"filen" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Kopier markeringen som ANSI-skiftesekvenser til en midlertidig fil og åpne " +"filen." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Åpne et nytt vindu." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Åpne en ny fane." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Flytt den aktive fanen til venstre" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Flytt den aktive fanen til venstre." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Flytt den aktive fanen til høyre" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Flytt den aktive fanen til høyre." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Flytt den aktive fanen til et nytt vindu" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Flytt den aktive fanen til et nytt vindu." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Vis/skjul faneoversikten" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Vis eller skjul faneoversikten." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Endre terminaltittel…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Be om en ny tittel for den aktive terminalen." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Be om en ny tittel for den aktive fanen." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Del terminalen til venstre." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Del terminalen til høyre." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Del terminalen oppover." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Del terminalen nedover." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Fokuser forrige delte vindu" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Fokuser forrige delte vindu hvis det finnes." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Fokuser neste delte vindu" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Fokuser neste delte vindu hvis det finnes." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Fokuser det delte vinduet til venstre" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Fokuser det delte vinduet til venstre hvis det finnes." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Fokuser det delte vinduet til høyre" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Fokuser det delte vinduet til høyre hvis det finnes." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Fokuser det delte vinduet over" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Fokuser det delte vinduet over hvis det finnes." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Fokuser det delte vinduet under" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Fokuser det delte vinduet under hvis det finnes." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Fokuser forrige vindu" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Fokuser forrige vindu hvis det finnes." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Fokuser neste vindu" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Fokuser neste vindu hvis det finnes." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Slå zoom av/på for delt vindu" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Slå zoom av eller på for det aktive delte vinduet." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Slå skrivebeskyttet modus av/på" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Slå skrivebeskyttet modus av eller på for den aktive terminalen." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Gjør delte vinduer like store" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Gjør alle delte vinduer like store." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Tilbakestill vindusstørrelsen" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Tilbakestill vinduet til standardstørrelsen." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Vis/skjul terminalinspektøren" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Vis eller skjul terminalinspektøren." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Vis GTK-inspektøren" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Vis GTK-inspektøren." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Vis skjermtastatur" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Vis skjermtastaturet hvis det er tilgjengelig." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Åpne konfigurasjonen i standardprogrammet for tekstfiler" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Åpne konfigurasjonsfilen i standardprogrammet for tekstfiler." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Åpne konfigurasjonen i et nytt terminalvindu" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "Åpne konfigurasjonsfilen i et nytt vindu med $EDITOR eller $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Last konfigurasjonen på nytt" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Last konfigurasjonsfilen på nytt." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Lukk terminal" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Lukk den aktive terminalen." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Lukk den aktive fanen." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Lukk andre faner" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Lukk alle fanene i dette vinduet unntatt den aktive." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Lukk faner til høyre" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Lukk alle fanene til høyre for den aktive." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Lukk det aktive vinduet." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Lukk alle vinduer" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Lukk alle vinduer." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Maksimer/gjenopprett vinduet" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Maksimer eller gjenopprett det aktive vinduet." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Slå fullskjermmodus av/på" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Slå fullskjermmodus av eller på for det aktive vinduet." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Vis/skjul vindusdekorasjoner" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Vis/skjul vindusdekorasjoner." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Slå «alltid øverst» av/på" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Slå «alltid øverst» av/på for det aktive vinduet." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Slå sikker tastaturskriving av/på" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Slå sikker tastaturskriving av/på." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Aktiver/deaktiver sending av musehendelser" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Aktiver/deaktiver sending av musehendelser til terminalprogrammer." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Veksle bakgrunnens opasitet" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Veksle bakgrunnens opasitet for et vindu som startet gjennomsiktig." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Se etter oppdateringer" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Se etter programoppdateringer." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Angre" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Angre siste handling." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Gjør om" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Gjør om den siste angrede handlingen." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Avslutt programmet." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Legg en liten Ghostty i terminalen din." From f81dcadc82ea2afdcf2dc92929037701122f05b5 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:56:41 +0000 Subject: [PATCH 044/392] Update VOUCHED list (#13814) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13812#discussioncomment-18018163) from @mitchellh. Vouch: @elitex45 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index c63e412a0..4410c9875 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -110,6 +110,7 @@ drepper dzhlobo ekaterinepapava elias8 +elitex45 -enkr1 enzowilliam ephemera From cde7f93435eb40dfbc306e338c43418e8322220e Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Mon, 3 Aug 2026 22:38:18 -0400 Subject: [PATCH 045/392] renderer: simplify cell row storage Cell contents used our ArrayListCollection container to manage per-row foreground lists. This was the only place ArrayListCollection was used. We now own the row list slice directly, initialize cursor capacity to exactly one cell, and reallocate the contiguous background buffer in place when possible. Foreground rows still use exact sizes so resizes (which are infrequent) do not retain the high-water mark. --- src/datastruct/array_list_collection.zig | 44 ------- src/renderer/cell.zig | 150 +++++++++++++---------- src/renderer/generic.zig | 2 +- 3 files changed, 89 insertions(+), 107 deletions(-) delete mode 100644 src/datastruct/array_list_collection.zig diff --git a/src/datastruct/array_list_collection.zig b/src/datastruct/array_list_collection.zig deleted file mode 100644 index d3fbddb13..000000000 --- a/src/datastruct/array_list_collection.zig +++ /dev/null @@ -1,44 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -/// A collection of ArrayLists with methods for bulk operations. -pub fn ArrayListCollection(comptime T: type) type { - return struct { - const Self = ArrayListCollection(T); - const ArrayListT = std.ArrayListUnmanaged(T); - - // An array containing the lists that belong to this collection. - lists: []ArrayListT, - - // The collection will be initialized with empty ArrayLists. - pub fn init( - alloc: Allocator, - list_count: usize, - initial_capacity: usize, - ) Allocator.Error!Self { - const self: Self = .{ - .lists = try alloc.alloc(ArrayListT, list_count), - }; - - for (self.lists) |*list| { - list.* = try .initCapacity(alloc, initial_capacity); - } - - return self; - } - - pub fn deinit(self: *Self, alloc: Allocator) void { - for (self.lists) |*list| { - list.deinit(alloc); - } - alloc.free(self.lists); - } - - /// Clear all lists in the collection, retaining capacity. - pub fn reset(self: *Self) void { - for (self.lists) |*list| { - list.clearRetainingCapacity(); - } - } - }; -} diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index b9880fbde..8753d1ad9 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -5,9 +5,10 @@ const font = @import("../font/main.zig"); const terminal = @import("../terminal/main.zig"); const renderer = @import("../renderer.zig"); const shaderpkg = renderer.Renderer.API.shaders; -const ArrayListCollection = @import("../datastruct/array_list_collection.zig").ArrayListCollection; const symbols = @import("../unicode/symbols_table.zig").table; +const CellTextRow = std.ArrayListUnmanaged(shaderpkg.CellText); + /// The possible cell content keys that exist. pub const Key = enum { bg, @@ -47,11 +48,11 @@ pub const Contents = struct { /// /// Prefer accessing with `Contents.bgCell(row, col).*` instead /// of directly indexing in order to avoid integer size bugs. - bg_cells: []shaderpkg.CellBg = undefined, + bg_cells: []shaderpkg.CellBg = &.{}, - /// The ArrayListCollection which holds all of the foreground cells. When - /// sized with Contents.resize the individual ArrayLists are given enough - /// room that they can hold a single row with #cols glyphs, underlines, and + /// The lists which hold all of the foreground cells. When sized with + /// Contents.resize the individual ArrayLists are given enough room that + /// they can hold a single row with #cols glyphs, underlines, and /// strikethroughs; however, appendAssumeCapacity MUST NOT be used since /// it is possible to exceed this with combining glyphs that add a glyph /// but take up no column since they combine with the previous one, as @@ -70,11 +71,12 @@ pub const Contents = struct { /// /// Must be initialized by calling resize on the Contents struct before /// calling any operations. - fg_rows: ArrayListCollection(shaderpkg.CellText) = .{ .lists = &.{} }, + fg_rows: []CellTextRow = &.{}, pub fn deinit(self: *Contents, alloc: Allocator) void { alloc.free(self.bg_cells); - self.fg_rows.deinit(alloc); + for (self.fg_rows) |*row| row.deinit(alloc); + alloc.free(self.fg_rows); } /// Resize the cell contents for the given grid size. This will @@ -84,55 +86,50 @@ pub const Contents = struct { alloc: Allocator, size: renderer.GridSize, ) Allocator.Error!void { - self.size = size; + const row_count: usize = size.rows; - const cell_count = @as(usize, size.columns) * @as(usize, size.rows); + // The two extra lists hold cursor cells: index 0 is drawn before the + // row contents, and index row_count + 1 is drawn after them. + const fg_rows = try alloc.alloc(CellTextRow, row_count + 2); + @memset(fg_rows, .empty); + errdefer { + for (fg_rows) |*row| row.deinit(alloc); + alloc.free(fg_rows); + } - const bg_cells = try alloc.alloc(shaderpkg.CellBg, cell_count); - errdefer alloc.free(bg_cells); - @memset(bg_cells, .{ 0, 0, 0, 0 }); + // Foreground rows hold glyphs plus underlines, overlines, and + // strikethroughs. Three entries per column cover the common cases + // without reserving space for every decoration combination. We can't + // assume this capacity because combining glyphs and font substitutions + // can produce arbitrarily many glyphs in one column. + const fg_row_capacity = @as(usize, size.columns) * 3; - // The foreground lists can hold 3 types of items: - // - Glyphs - // - Underlines - // - Strikethroughs - // So we give them an initial capacity of size.columns * 3, which will - // avoid any further allocations in the vast majority of cases. Sadly - // we can not assume capacity though, since with combining glyphs that - // form a single grapheme, and multi-substitutions in fonts, the number - // of glyphs in a row is theoretically unlimited. - // - // We have size.rows + 2 lists because indexes 0 and size.rows - 1 are - // used for special lists containing the cursor cell which need to - // be first and last in the buffer, respectively. - var fg_rows: ArrayListCollection(shaderpkg.CellText) = try .init( - alloc, - size.rows + 2, - size.columns * 3, + // The cursor lists need just one cell. The rest get the full capacity. + fg_rows[0] = try .initCapacity(alloc, 1); + fg_rows[row_count + 1] = try .initCapacity(alloc, 1); + for (fg_rows[1 .. row_count + 1]) |*row| { + row.* = try .initCapacity(alloc, fg_row_capacity); + } + + const bg_cells = try alloc.realloc( + self.bg_cells, + row_count * @as(usize, size.columns), ); - errdefer fg_rows.deinit(alloc); - - // We don't need 3*cols worth of cells for the cursor lists, so we can - // replace them with smaller lists. This is technically a tiny bit of - // extra work but resize is not a hot function so it's worth it to not - // waste the memory. - fg_rows.lists[0].deinit(alloc); - fg_rows.lists[0] = try .initCapacity(alloc, 1); - fg_rows.lists[size.rows + 1].deinit(alloc); - fg_rows.lists[size.rows + 1] = try .initCapacity(alloc, 1); // Perform the swap, no going back from here. errdefer comptime unreachable; - alloc.free(self.bg_cells); - self.fg_rows.deinit(alloc); + for (self.fg_rows) |*row| row.deinit(alloc); + alloc.free(self.fg_rows); + self.size = size; self.bg_cells = bg_cells; self.fg_rows = fg_rows; + self.reset(); } /// Reset the cell contents to an empty state without resizing. pub fn reset(self: *Contents) void { @memset(self.bg_cells, .{ 0, 0, 0, 0 }); - self.fg_rows.reset(); + for (self.fg_rows) |*row| row.clearRetainingCapacity(); } /// Set the cursor value. If the value is null then the cursor is hidden. @@ -142,28 +139,28 @@ pub const Contents = struct { cursor_style: ?renderer.CursorStyle, ) void { if (self.size.rows == 0) return; - self.fg_rows.lists[0].clearRetainingCapacity(); - self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity(); + self.fg_rows[0].clearRetainingCapacity(); + self.fg_rows[self.size.rows + 1].clearRetainingCapacity(); const cell = v orelse return; const style = cursor_style orelse return; switch (style) { // Block cursors should be drawn first - .block => self.fg_rows.lists[0].appendAssumeCapacity(cell), + .block => self.fg_rows[0].appendAssumeCapacity(cell), // Other cursor styles should be drawn last - .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell), + .block_hollow, .bar, .underline, .lock => self.fg_rows[self.size.rows + 1].appendAssumeCapacity(cell), } } /// Returns the current cursor glyph if present, checking both cursor lists. pub fn getCursorGlyph(self: *Contents) ?shaderpkg.CellText { if (self.size.rows == 0) return null; - if (self.fg_rows.lists[0].items.len > 0) { - return self.fg_rows.lists[0].items[0]; + if (self.fg_rows[0].items.len > 0) { + return self.fg_rows[0].items[0]; } - if (self.fg_rows.lists[self.size.rows + 1].items.len > 0) { - return self.fg_rows.lists[self.size.rows + 1].items[0]; + if (self.fg_rows[self.size.rows + 1].items.len > 0) { + return self.fg_rows[self.size.rows + 1].items[0]; } return null; } @@ -201,7 +198,7 @@ pub const Contents = struct { // We have a special list containing the cursor cell at the start // of our fg row collection, so we need to add 1 to the y to get // the correct index. - => try self.fg_rows.lists[y + 1].append(alloc, cell), + => try self.fg_rows[y + 1].append(alloc, cell), } } @@ -214,7 +211,7 @@ pub const Contents = struct { // We have a special list containing the cursor cell at the start // of our fg row collection, so we need to add 1 to the y to get // the correct index. - self.fg_rows.lists[y + 1].clearRetainingCapacity(); + self.fg_rows[y + 1].clearRetainingCapacity(); } }; @@ -362,14 +359,14 @@ test Contents { // We should start off empty after resizing. for (0..rows) |y| { - try testing.expect(c.fg_rows.lists[y + 1].items.len == 0); + try testing.expect(c.fg_rows[y + 1].items.len == 0); for (0..cols) |x| { try testing.expectEqual(.{ 0, 0, 0, 0 }, c.bgCell(y, x).*); } } // And the cursor row should have a capacity of 1 and also be empty. - try testing.expect(c.fg_rows.lists[0].capacity == 1); - try testing.expect(c.fg_rows.lists[0].items.len == 0); + try testing.expect(c.fg_rows[0].capacity == 1); + try testing.expect(c.fg_rows[0].items.len == 0); // Add some contents. const bg_cell: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; @@ -382,12 +379,12 @@ test Contents { try c.add(alloc, .text, fg_cell); try testing.expectEqual(bg_cell, c.bgCell(1, 4).*); // The fg row index is offset by 1 because of the cursor list. - try testing.expectEqual(fg_cell, c.fg_rows.lists[2].items[0]); + try testing.expectEqual(fg_cell, c.fg_rows[2].items[0]); // And we should be able to clear it. c.clear(1); for (0..rows) |y| { - try testing.expect(c.fg_rows.lists[y + 1].items.len == 0); + try testing.expect(c.fg_rows[y + 1].items.len == 0); for (0..cols) |x| { try testing.expectEqual(.{ 0, 0, 0, 0 }, c.bgCell(y, x).*); } @@ -401,20 +398,49 @@ test Contents { .color = .{ 0, 0, 0, 1 }, }; c.setCursor(cursor_cell, .block); - try testing.expectEqual(cursor_cell, c.fg_rows.lists[0].items[0]); + try testing.expectEqual(cursor_cell, c.fg_rows[0].items[0]); try testing.expectEqual(cursor_cell, c.getCursorGlyph().?); // And remove it. c.setCursor(null, null); - try testing.expectEqual(0, c.fg_rows.lists[0].items.len); + try testing.expectEqual(0, c.fg_rows[0].items.len); try testing.expect(c.getCursorGlyph() == null); // Add a hollow cursor. c.setCursor(cursor_cell, .block_hollow); - try testing.expectEqual(cursor_cell, c.fg_rows.lists[rows + 1].items[0]); + try testing.expectEqual(cursor_cell, c.fg_rows[rows + 1].items[0]); try testing.expectEqual(cursor_cell, c.getCursorGlyph().?); } +test "Contents resize grows and shrinks" { + const testing = std.testing; + const alloc = testing.allocator; + + var c: Contents = .{}; + defer c.deinit(alloc); + + try c.resize(alloc, .{ .rows = 2, .columns = 2 }); + var cell: shaderpkg.CellText = .{ + .atlas = .grayscale, + .grid_pos = .{ 1, 1 }, + .color = .{ 0, 0, 0, 1 }, + }; + try c.add(alloc, .text, cell); + c.bgCell(1, 1).* = .{ 0, 0, 0, 1 }; + c.setCursor(cell, .bar); + + try c.resize(alloc, .{ .rows = 3, .columns = 4 }); + try testing.expectEqual(.{ 0, 0, 0, 0 }, c.bgCell(1, 1).*); + try testing.expectEqual(@as(usize, 0), c.fg_rows[2].items.len); + try testing.expectEqual(@as(?shaderpkg.CellText, null), c.getCursorGlyph()); + cell.grid_pos = .{ 3, 2 }; + try c.add(alloc, .text, cell); + + try c.resize(alloc, .{ .rows = 1, .columns = 1 }); + cell.grid_pos = .{ 0, 0 }; + try c.add(alloc, .text, cell); +} + test "Contents clear retains other content" { const testing = std.testing; const alloc = testing.allocator; @@ -452,7 +478,7 @@ test "Contents clear retains other content" { // Row 2 should still contain its cells. try testing.expectEqual(bg_cell_2, c.bgCell(2, 4).*); // Fg row index is +1 because of cursor list at start - try testing.expectEqual(fg_cell_2, c.fg_rows.lists[3].items[0]); + try testing.expectEqual(fg_cell_2, c.fg_rows[3].items[0]); } test "Contents clear last added content" { @@ -492,7 +518,7 @@ test "Contents clear last added content" { // Row 1 should still contain its cells. try testing.expectEqual(bg_cell_1, c.bgCell(1, 4).*); // Fg row index is +1 because of cursor list at start - try testing.expectEqual(fg_cell_1, c.fg_rows.lists[2].items[0]); + try testing.expectEqual(fg_cell_1, c.fg_rows[2].items[0]); } test "Contents with zero-sized screen" { diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 1337c5154..cd62a7bbd 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1576,7 +1576,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Setup our frame data try frame.uniforms.sync(&.{self.uniforms}); try frame.cells_bg.sync(self.cells.bg_cells); - const fg_count = try frame.cells.syncFromArrayLists(self.cells.fg_rows.lists); + const fg_count = try frame.cells.syncFromArrayLists(self.cells.fg_rows); // If our background image buffer has changed, sync it. if (frame.bg_image_buffer_modified != self.bg_image_buffer_modified) { From 1eceea92dac457f95858706f946be7d6b21e5885 Mon Sep 17 00:00:00 2001 From: dobbylee Date: Fri, 14 Aug 2026 23:43:44 +0900 Subject: [PATCH 046/392] i18n: Update ko_KR translations --- po/ko_KR.po | 367 +++++++++++++++++++++++++++------------------------- 1 file changed, 190 insertions(+), 177 deletions(-) diff --git a/po/ko_KR.po b/po/ko_KR.po index ffaf4c957..9d2e44916 100644 --- a/po/ko_KR.po +++ b/po/ko_KR.po @@ -2,14 +2,16 @@ # Copyright (C) 2025 Mitchell Hashimoto # This file is distributed under the same license as the com.mitchellh.ghostty package. # Ruben Engelbrecht , 2025. +# GyuYong Jung , 2026. +# dobbylee , 2026. # msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-11 12:50+0900\n" -"Last-Translator: GyuYong Jung \n" +"PO-Revision-Date: 2026-08-14 23:43+0900\n" +"Last-Translator: dobbylee \n" "Language-Team: Korean \n" "Language: ko\n" "MIME-Version: 1.0\n" @@ -172,7 +174,7 @@ msgstr "오른쪽으로 창 나누기" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "분할 닫기" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -200,7 +202,7 @@ msgstr "창" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "창 제목 변경…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -218,11 +220,11 @@ msgstr "설정" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "시스템 기본 편집기에서 설정 열기" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "새 창에서 설정 열기" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -266,27 +268,27 @@ msgstr "명령을 실행하세요…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "시스템에서 단축키 등록을 취소했습니다." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "시스템에서 단축키 등록을 거부했습니다." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "전역 단축키를 사용할 수 없음" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "터미널 입출력 이벤트 내보내기" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "내보내기" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "설정 파일 편집 중" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -372,7 +374,7 @@ msgstr "탭 제목 변경" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "창 제목 변경" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -392,710 +394,721 @@ msgstr "Ghostty 개발자들" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "터미널 초기화" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "터미널을 초기 상태로 되돌립니다." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "클립보드에 복사" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"선택한 텍스트를 일반 텍스트와 서식 있는 텍스트 형식으로 클립보드에 복사합니다." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "선택 영역을 일반 텍스트로 클립보드에 복사" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "선택한 텍스트를 일반 텍스트로 클립보드에 복사합니다." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "선택 영역을 ANSI 시퀀스로 클립보드에 복사" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "선택한 텍스트를 ANSI 이스케이프 시퀀스로 클립보드에 복사합니다." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "선택 영역을 HTML로 클립보드에 복사" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "선택한 텍스트를 HTML로 클립보드에 복사합니다." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "URL을 클립보드에 복사" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "마우스 커서가 가리키는 URL을 클립보드에 복사합니다." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "터미널 제목을 클립보드에 복사" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"터미널 제목을 클립보드에 복사합니다. 터미널 제목이 설정되지 않은 경우에는 " +"아무 작업도 하지 않습니다." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "클립보드에서 붙여넣기" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "기본 클립보드의 내용을 붙여넣습니다." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "선택 클립보드에서 붙여넣기" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "선택 클립보드의 내용을 붙여넣습니다." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "검색 시작" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "진행 중인 검색이 없으면 검색을 시작합니다." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "선택 영역 검색" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "현재 선택한 텍스트를 검색합니다." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "검색 종료" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "진행 중인 검색을 종료하고 관련 화면 요소를 숨깁니다." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "다음 검색 결과" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "다음 검색 결과가 있으면 해당 결과로 이동합니다." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "이전 검색 결과" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "이전 검색 결과가 있으면 해당 결과로 이동합니다." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "글꼴 크기 키우기" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "글꼴 크기를 1포인트 키웁니다." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "글꼴 크기 줄이기" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "글꼴 크기를 1포인트 줄입니다." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "글꼴 크기 초기화" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "글꼴 크기를 기본값으로 되돌립니다." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "화면 지우기" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "화면과 스크롤백을 지웁니다." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "모두 선택" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "화면의 모든 텍스트를 선택합니다." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "맨 위로 스크롤" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "화면 맨 위로 스크롤합니다." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "맨 아래로 스크롤" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "화면 맨 아래로 스크롤합니다." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "선택 영역으로 스크롤" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "선택한 텍스트가 있는 위치로 스크롤합니다." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "한 페이지 위로 스크롤" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "화면을 한 페이지 위로 스크롤합니다." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "한 페이지 아래로 스크롤" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "화면을 한 페이지 아래로 스크롤합니다." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "화면을 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "화면 내용을 임시 파일에 저장하고 파일 경로를 클립보드에 복사합니다." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "화면을 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." -msgstr "" +msgstr "화면 내용을 임시 파일에 저장하고 파일 경로를 붙여넣습니다." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "화면을 임시 파일에 저장하고 열기" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "화면 내용을 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "화면을 HTML 형식으로 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "화면 내용을 HTML 형식으로 임시 파일에 저장하고 파일 경로를 클립보드에 복사합니다." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "화면을 HTML 형식으로 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." -msgstr "" +msgstr "화면 내용을 HTML 형식으로 임시 파일에 저장하고 파일 경로를 붙여넣습니다." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "화면을 HTML 형식으로 임시 파일에 저장하고 열기" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." -msgstr "" +msgstr "화면 내용을 HTML 형식으로 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "화면을 ANSI 시퀀스로 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"화면 내용을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일 경로를 " +"클립보드에 복사합니다." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "화면을 ANSI 시퀀스로 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"화면 내용을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일 경로를 " +"붙여넣습니다." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "화면을 ANSI 시퀀스로 임시 파일에 저장하고 열기" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "화면 내용을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 파일 경로를 클립보드에 복사합니다." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 파일 경로를 붙여넣습니다." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 열기" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "선택 영역을 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 파일 경로를 클립보드에 복사합니다." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 파일 경로를 붙여넣습니다." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 열기" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "선택 영역을 HTML 형식으로 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "선택 영역을 ANSI 시퀀스로 임시 파일에 저장하고 경로 복사" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"선택 영역을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일 경로를 " +"클립보드에 복사합니다." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "선택 영역을 ANSI 시퀀스로 임시 파일에 저장하고 경로 붙여넣기" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"선택 영역을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일 경로를 " +"붙여넣습니다." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "선택 영역을 ANSI 시퀀스로 임시 파일에 저장하고 열기" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." -msgstr "" +msgstr "선택 영역을 ANSI 이스케이프 시퀀스로 임시 파일에 저장하고 파일을 엽니다." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "새 창을 엽니다." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "새 탭을 엽니다." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "탭을 왼쪽으로 이동" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "현재 탭을 왼쪽으로 이동합니다." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "탭을 오른쪽으로 이동" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "현재 탭을 오른쪽으로 이동합니다." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "탭을 새 창으로 이동" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "현재 탭을 새 창으로 이동합니다." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "탭 개요 표시 전환" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "탭 개요를 표시하거나 숨깁니다." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "터미널 제목 변경…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "현재 터미널의 새 제목을 입력합니다." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "현재 탭의 새 제목을 입력합니다." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "터미널을 왼쪽으로 나눕니다." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "터미널을 오른쪽으로 나눕니다." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "터미널을 위로 나눕니다." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "터미널을 아래로 나눕니다." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "이전 분할로 포커스 이동" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "이전 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "다음 분할로 포커스 이동" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "다음 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "왼쪽 분할로 포커스 이동" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "왼쪽 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "오른쪽 분할로 포커스 이동" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "오른쪽 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "위쪽 분할로 포커스 이동" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "위쪽 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "아래쪽 분할로 포커스 이동" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "아래쪽 분할이 있으면 포커스를 이동합니다." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "이전 창으로 포커스 이동" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "이전 창이 있으면 포커스를 이동합니다." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "다음 창으로 포커스 이동" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "다음 창이 있으면 포커스를 이동합니다." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "분할 확대/축소 전환" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "현재 분할을 확대하거나 원래 크기로 되돌립니다." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "읽기 전용 모드 전환" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "현재 터미널의 읽기 전용 모드를 켜거나 끕니다." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "분할 크기 균등화" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "모든 분할의 크기를 균등하게 맞춥니다." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "창 크기 초기화" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "창 크기를 기본값으로 되돌립니다." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "인스펙터 표시 전환" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "인스펙터를 표시하거나 숨깁니다." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "GTK 인스펙터 표시" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "GTK 인스펙터를 표시합니다." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "화상 키보드 표시" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "화상 키보드를 사용할 수 있으면 표시합니다." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "시스템 기본 편집기에서 설정 열기" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "시스템 기본 편집기로 설정 파일을 엽니다." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "새 터미널 창에서 설정 열기" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." -msgstr "" +msgstr "$EDITOR 또는 $VISUAL을 사용하여 새 창에서 설정 파일을 엽니다." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "설정 값 다시 불러오기" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "설정 파일을 다시 불러옵니다." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "터미널 닫기" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "현재 터미널을 닫습니다." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "현재 탭을 닫습니다." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "다른 탭 닫기" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "이 창에서 현재 탭을 제외한 모든 탭을 닫습니다." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "오른쪽 탭 닫기" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "현재 탭 오른쪽의 모든 탭을 닫습니다." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "현재 창을 닫습니다." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "모든 창 닫기" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "모든 창을 닫습니다." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "창 최대화 전환" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "현재 창을 최대화하거나 원래 크기로 복원합니다." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "전체 화면 전환" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "현재 창의 전체 화면 모드를 켜거나 끕니다." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "창 장식 표시 전환" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "창 장식을 표시하거나 숨깁니다." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "항상 위에 표시 전환" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "현재 창의 항상 위에 표시 기능을 켜거나 끕니다." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "보안 입력 전환" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "보안 입력 모드를 켜거나 끕니다." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "마우스 이벤트 전달 전환" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "터미널 애플리케이션에 마우스 이벤트를 전달하는 기능을 켜거나 끕니다." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "배경 불투명도 전환" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." -msgstr "" +msgstr "투명하게 시작된 창의 배경을 불투명하게 표시하거나 원래 투명도로 되돌립니다." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "업데이트 확인" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "애플리케이션의 업데이트를 확인합니다." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "실행 취소" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "마지막 작업을 실행 취소합니다." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "다시 실행" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "마지막으로 실행 취소한 작업을 다시 실행합니다." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "애플리케이션을 종료합니다." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Ghostty" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "터미널에 작은 유령 하나를 입력합니다." From ecbeb60ca5327186301900ccef208143ff46ef22 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 14 Aug 2026 11:29:38 -0400 Subject: [PATCH 047/392] macos: send all insertText commits as key events Previously, insertText commits without marked text were delivered via sendText, which applies paste semantics and wraps the text in bracketed paste when the program enables it. macOS dictation and other input methods often commit without marked text, so programs treated dictated text as a paste and applied paste-specific handling. insertText is only invoked by input methods (IME, dictation, emoji picker, character viewer); real paste operations use a separate path. Send every non-empty commit through the key event path so programs interpret input method text as typed input. Typing is unaffected (the accumulator path returns earlier) and Cmd+V pastes are unaffected. The helper is renamed from committedPreeditTextAction to committedTextAction since it no longer only handles preedit commits. --- .../Surface View/SurfaceView_AppKit.swift | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 35b99e436..0581f7aeb 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -1212,7 +1212,7 @@ extension Ghostty { continue } - _ = committedPreeditTextAction(action, text: text) + _ = committedTextAction(action, text: text) } if shouldReplayCommittedPreeditKey(translationEvent) { @@ -1512,7 +1512,7 @@ extension Ghostty { } } - private func committedPreeditTextAction( + private func committedTextAction( _ action: ghostty_input_action_e, text: String ) -> Bool { @@ -2063,7 +2063,6 @@ extension Ghostty.SurfaceView: NSTextInputClient { func insertText(_ string: Any, replacementRange: NSRange) { // We must have an associated event guard NSApp.currentEvent != nil else { return } - guard let surfaceModel else { return } // We want the string view of the any value var chars = "" @@ -2089,8 +2088,6 @@ extension Ghostty.SurfaceView: NSTextInputClient { return } - let hadMarkedText = hasMarkedText() - // If insertText is called, our preedit must be over. unmarkText() @@ -2102,14 +2099,11 @@ extension Ghostty.SurfaceView: NSTextInputClient { return } - if hadMarkedText, !chars.isEmpty { - // Send preedit commits as key events instead of raw text for - // keybind interpretation by programs. - _ = committedPreeditTextAction(GHOSTTY_ACTION_PRESS, text: chars) - return + // All committed text (IME, dictation, etc.) must be sent as key + // events so programs treat it as typed input, never as a paste. + if !chars.isEmpty { + _ = committedTextAction(GHOSTTY_ACTION_PRESS, text: chars) } - - surfaceModel.sendText(chars) } /// This function needs to exist for two reasons: From e3056658d05bdd54db9fd37a02c196bfe81cfe39 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 07:55:31 -0700 Subject: [PATCH 048/392] libghostty: faster render state updates and C API reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves the performance of render state plus C API reads. I specifically benchmarked the C API call and found a lot of overhead in the C API layer which this cleans up. The impact of these changes will be less visible to Zig consumers but moderately improve there. All benchmark numbers below are via the C API. Highlights: - full rebuilds are **1.71x faster (11.4µs to 6.6µs per 120x80 frame)** - single-dirty-row updates (e.g. the TUI/prompt steady state) are *1.44x faster** - full-frame reads through the C API are **1.2x to 1.8x faster** ## Changes * endUpdate skips unchanged style runs. * `GRAPHEMES_UTF8` getter gets a fast path for single ASCII codepoints (the overwhelming majority of cells). * The bg/fg color getters no longer copy the full 28-byte style. Instead, they switch directly on the one color field they need. * The `get_multi` variants validate the handle and position once per batch instead of per key. * Iterator positions are sentinel values instead of Zig optionals. The optional tagging overhead was showing up in benchmarks. * `colors_get` reads through a pointer instead of copying the ~1KB colors struct to the stack per call. * The palette conversion is vectorized. The 4-byte padded RGB to 3-byte was not being auto-vectorized. Explicitly vectorize it. Something like a 4x speedup on NEON. ## Benchmarks | Benchmark | Before | After | Speedup | |---|---|---|---| | update (forced full rebuild) | 11.4 µs/frame | 6.6 µs/frame | 1.71x | | update (single dirty row) | 143 ns | 99 ns | 1.44x | | read cell style/bg/fg/selected | 10.3 ns/cell | 8.8 ns/cell | 1.17x | | read cell via get_multi | 9.6 ns/cell | 6.9 ns/cell | 1.40x | | read cell UTF-8 text | 4.9 ns/cell | 2.7 ns/cell | 1.78x | | colors_get + palette | 213 ns/call | 45 ns/call | 4.58x | Clean updates (no terminal changes) and the raw cell read paths are unchanged. **AI usage:** Driven by Fable primarily, reviewed everything and rewrote all human-language (comments) since Fable in particular does really bad at that. This commit message too. --- src/terminal/c/render.zig | 297 +++++++++++++++++++++++++++----------- src/terminal/color.zig | 73 +++++++++- src/terminal/render.zig | 165 +++++++++++++++++++-- 3 files changed, 439 insertions(+), 96 deletions(-) diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index e70e7a4b7..60e67e609 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -22,11 +22,18 @@ const RenderStateWrapper = struct { state: renderpkg.RenderState = .empty, }; +/// The "before the first element" position for the iterator wrappers +/// below. Represented as a sentinel index rather than an optional because +/// optional codegens into something that is less efficient than this. +const position_none = std.math.maxInt(usize); + const RowIteratorWrapper = struct { alloc: std.mem.Allocator, - /// The current index (also y value) into the row list. - y: ?size.CellCountInt, + /// The current index (also y value) into the row list, or + /// `position_none` if iteration hasn't started. Always validate + /// against `raws.len` before use. + y: usize, /// These are the raw pointers into the render state data. raws: []const page.Row, @@ -41,7 +48,12 @@ const RowIteratorWrapper = struct { const RowCellsWrapper = struct { alloc: std.mem.Allocator, - x: ?size.CellCountInt, + + /// The current index (also x value) into the cell list, or + /// `position_none` if iteration hasn't started. Always validate + /// against `raws.len` before use. + x: usize, + raws: []const page.Cell, graphemes: []const []const u21, styles: []const Style, @@ -212,21 +224,8 @@ pub fn get( data: Data, out: ?*anyopaque, ) callconv(lib.calling_conv) Result { - if (comptime std.debug.runtime_safety) { - _ = std.enums.fromInt(Data, @intFromEnum(data)) orelse { - log.warn("render_state_get invalid data value={d}", .{@intFromEnum(data)}); - return .invalid_value; - }; - } - - return switch (data) { - .invalid => .invalid_value, - inline else => |comptime_data| getTyped( - state_, - comptime_data, - @ptrCast(@alignCast(out)), - ), - }; + const state = state_ orelse return .invalid_value; + return getDispatch(state, data, out); } pub fn get_multi( @@ -239,8 +238,16 @@ pub fn get_multi( const k = keys orelse return .invalid_value; const v = values orelse return .invalid_value; + // Unwrap the handle once for the whole batch rather than per key. + // A null handle fails on the first key, matching the per-call + // behavior. + const state: ?*RenderStateWrapper = state_; + for (0..count) |i| { - const result = get(state_, k[i], v[i]); + const result = if (state) |s| + getDispatch(s, k[i], v[i]) + else + Result.invalid_value; if (result != .success) { if (out_written) |w| w.* = i; return result; @@ -250,12 +257,33 @@ pub fn get_multi( return .success; } +inline fn getDispatch( + state: *RenderStateWrapper, + data: Data, + out: ?*anyopaque, +) Result { + if (comptime std.debug.runtime_safety) { + _ = std.enums.fromInt(Data, @intFromEnum(data)) orelse { + log.warn("render_state_get invalid data value={d}", .{@intFromEnum(data)}); + return .invalid_value; + }; + } + + return switch (data) { + .invalid => .invalid_value, + inline else => |comptime_data| getTyped( + state, + comptime_data, + @ptrCast(@alignCast(out)), + ), + }; +} + fn getTyped( - state_: RenderState, + state: *RenderStateWrapper, comptime data: Data, out: *data.OutType(), ) Result { - const state = state_ orelse return .invalid_value; switch (data) { .invalid => return .invalid_value, .cols => out.* = state.state.cols, @@ -266,7 +294,7 @@ fn getTyped( const row_data = state.state.row_data.slice(); it.* = .{ .alloc = it.alloc, - .y = null, + .y = position_none, .raws = row_data.items(.raw), .cells = row_data.items(.cells), .selection = row_data.items(.selection), @@ -347,7 +375,7 @@ pub fn colors_get( const out_size = out_colors.size; if (out_size < @sizeOf(usize)) return .invalid_value; - const colors = state.state.colors; + const colors = &state.state.colors; if (lib.structSizedFieldFits( Colors, out_size, @@ -387,9 +415,10 @@ pub fn colors_get( if (out_size > palette_offset) { const available = out_size - palette_offset; const max_entries = @min(colors.palette.len, available / @sizeOf(colorpkg.RGB.C)); - for (0..max_entries) |i| { - out_colors.palette[i] = colors.palette[i].cval(); - } + colorpkg.paletteCvalSlice( + colors.palette[0..max_entries], + out_colors.palette[0..max_entries], + ); } } @@ -426,7 +455,8 @@ pub fn row_iterator_free(iterator_: RowIterator) callconv(lib.calling_conv) void pub fn row_iterator_next(iterator_: RowIterator) callconv(lib.calling_conv) bool { const it = iterator_ orelse return false; - const next_y: size.CellCountInt = if (it.y) |y| y + 1 else 0; + // The none sentinel wraps to zero. + const next_y = it.y +% 1; if (next_y >= it.raws.len) return false; it.y = next_y; return true; @@ -456,7 +486,8 @@ pub fn row_cells_new( pub fn row_cells_next(cells_: RowCells) callconv(lib.calling_conv) bool { const cells = cells_ orelse return false; - const next_x: size.CellCountInt = if (cells.x) |x| x + 1 else 0; + // The none sentinel wraps to zero. + const next_x = cells.x +% 1; if (next_x >= cells.raws.len) return false; cells.x = next_x; return true; @@ -508,22 +539,10 @@ pub fn row_cells_get( data: RowCellsData, out: ?*anyopaque, ) callconv(lib.calling_conv) Result { - if (comptime std.debug.runtime_safety) { - _ = std.enums.fromInt(RowCellsData, @intFromEnum(data)) orelse { - log.warn("render_state_row_cells_get invalid data value={d}", .{@intFromEnum(data)}); - return .invalid_value; - }; - } - if (out == null) return .invalid_value; - - return switch (data) { - .invalid => .invalid_value, - inline else => |comptime_data| rowCellsGetTyped( - cells_, - comptime_data, - @ptrCast(@alignCast(out)), - ), - }; + const cells = cells_ orelse return .invalid_value; + const x = cells.x; + if (x >= cells.raws.len) return .invalid_value; + return rowCellsGetDispatch(cells, x, data, out); } pub fn row_cells_get_multi( @@ -536,8 +555,21 @@ pub fn row_cells_get_multi( const k = keys orelse return .invalid_value; const v = values orelse return .invalid_value; + // Unwrap the handle and position once for the whole batch rather + // than per key. An invalid handle/position fails on the first + // key, matching the per-call behavior. + const unwrapped: ?struct { *RowCellsWrapper, usize } = valid: { + const cells = cells_ orelse break :valid null; + const x = cells.x; + if (x >= cells.raws.len) break :valid null; + break :valid .{ cells, x }; + }; + for (0..count) |i| { - const result = row_cells_get(cells_, k[i], v[i]); + const result = if (unwrapped) |u| + rowCellsGetDispatch(u[0], u[1], k[i], v[i]) + else + Result.invalid_value; if (result != .success) { if (out_written) |w| w.* = i; return result; @@ -547,13 +579,37 @@ pub fn row_cells_get_multi( return .success; } -fn rowCellsGetTyped( - cells_: RowCells, +inline fn rowCellsGetDispatch( + cells: *const RowCellsWrapper, + x: usize, + data: RowCellsData, + out: ?*anyopaque, +) Result { + if (comptime std.debug.runtime_safety) { + _ = std.enums.fromInt(RowCellsData, @intFromEnum(data)) orelse { + log.warn("render_state_row_cells_get invalid data value={d}", .{@intFromEnum(data)}); + return .invalid_value; + }; + } + if (out == null) return .invalid_value; + + return switch (data) { + .invalid => .invalid_value, + inline else => |comptime_data| rowCellsGetTypedInner( + cells, + x, + comptime_data, + @ptrCast(@alignCast(out)), + ), + }; +} + +fn rowCellsGetTypedInner( + cells: *const RowCellsWrapper, + x: usize, comptime data: RowCellsData, out: *data.OutType(), ) Result { - const cells = cells_ orelse return .invalid_value; - const x = cells.x orelse return .invalid_value; const cell = cells.raws[x]; switch (data) { .invalid => return .invalid_value, @@ -561,7 +617,7 @@ fn rowCellsGetTyped( .style => out.* = if (cell.hasStyling()) style_c.Style.fromStyle(cells.styles[x]) else - style_c.Style.fromStyle(.{}), + comptime style_c.Style.fromStyle(.{}), .graphemes_len => { if (!cell.hasText()) { out.* = 0; @@ -580,15 +636,37 @@ fn rowCellsGetTyped( } }, .bg_color => { - const s: Style = if (cell.hasStyling()) cells.styles[x] else .{}; - const bg = s.bg(&cell, cells.palette) orelse return .invalid_value; - out.* = bg.cval(); + // Avoid copying the full struct when only partial changes happen. + switch (cell.content_tag) { + .bg_color_palette => { + out.* = cells.palette[cell.content.color_palette.data].cval(); + }, + + .bg_color_rgb => { + const rgb = cell.content.color_rgb; + out.* = .{ .r = rgb.r, .g = rgb.g, .b = rgb.b }; + }, + + .codepoint, + .codepoint_grapheme, + => { + // The default style has no background. + if (!cell.hasStyling()) return .invalid_value; + switch (cells.styles[x].bg_color) { + .none => return .invalid_value, + .palette => |idx| out.* = cells.palette[idx].cval(), + .rgb => |rgb| out.* = rgb.cval(), + } + }, + } }, .fg_color => { - const s: Style = if (cell.hasStyling()) cells.styles[x] else .{}; - if (s.fg_color == .none) return .invalid_value; - const fg = s.fg(.{ .default = .{}, .palette = cells.palette }); - out.* = fg.cval(); + if (!cell.hasStyling()) return .invalid_value; + switch (cells.styles[x].fg_color) { + .none => return .invalid_value, + .palette => |idx| out.* = cells.palette[idx].cval(), + .rgb => |rgb| out.* = rgb.cval(), + } }, .selected => out.* = if (cells.selection) |sel| x >= sel[0] and x <= sel[1] @@ -606,11 +684,32 @@ fn rowCellsGetGraphemesUtf8( extra: []const u21, out: *lib.Buffer, ) Result { + if (!cell.hasText()) { + out.len = 0; + return .success; + } + + const first = cell.codepoint(); + + // Fast path: a single ASCII codepoint written to an adequately + // sized buffer. This is the overwhelmingly common case for + // terminal content. + if (first < 0x80 and extra.len == 0) { + @branchHint(.likely); + if (out.ptr) |ptr| { + if (out.cap >= 1) { + ptr[0] = @intCast(first); + out.len = 1; + return .success; + } + } + out.len = 1; + return .out_of_space; + } + out.len = 0; - if (!cell.hasText()) return .success; - - var needed: usize = std.unicode.utf8CodepointSequenceLength(cell.codepoint()) catch + var needed: usize = std.unicode.utf8CodepointSequenceLength(first) catch return .invalid_value; for (extra) |cp| { needed += std.unicode.utf8CodepointSequenceLength(cp) catch @@ -622,7 +721,7 @@ fn rowCellsGetGraphemesUtf8( const buf = out.ptr.?[0..out.cap]; var i: usize = 0; - i += std.unicode.utf8Encode(cell.codepoint(), buf[i..]) catch + i += std.unicode.utf8Encode(first, buf[i..]) catch return .invalid_value; for (extra) |cp| { i += std.unicode.utf8Encode(cp, buf[i..]) catch @@ -670,21 +769,10 @@ pub fn row_get( data: RowData, out: ?*anyopaque, ) callconv(lib.calling_conv) Result { - if (comptime std.debug.runtime_safety) { - _ = std.enums.fromInt(RowData, @intFromEnum(data)) orelse { - log.warn("render_state_row_get invalid data value={d}", .{@intFromEnum(data)}); - return .invalid_value; - }; - } - - return switch (data) { - .invalid => .invalid_value, - inline else => |comptime_data| rowGetTyped( - iterator_, - comptime_data, - @ptrCast(@alignCast(out)), - ), - }; + const it = iterator_ orelse return .invalid_value; + const y = it.y; + if (y >= it.raws.len) return .invalid_value; + return rowGetDispatch(it, y, data, out); } pub fn row_get_multi( @@ -697,8 +785,21 @@ pub fn row_get_multi( const k = keys orelse return .invalid_value; const v = values orelse return .invalid_value; + // Unwrap the handle and position once for the whole batch rather + // than per key. An invalid handle/position fails on the first + // key, matching the per-call behavior. + const unwrapped: ?struct { *RowIteratorWrapper, usize } = valid: { + const it = iterator_ orelse break :valid null; + const y = it.y; + if (y >= it.raws.len) break :valid null; + break :valid .{ it, y }; + }; + for (0..count) |i| { - const result = row_get(iterator_, k[i], v[i]); + const result = if (unwrapped) |u| + rowGetDispatch(u[0], u[1], k[i], v[i]) + else + Result.invalid_value; if (result != .success) { if (out_written) |w| w.* = i; return result; @@ -708,13 +809,36 @@ pub fn row_get_multi( return .success; } +inline fn rowGetDispatch( + it: *RowIteratorWrapper, + y: usize, + data: RowData, + out: ?*anyopaque, +) Result { + if (comptime std.debug.runtime_safety) { + _ = std.enums.fromInt(RowData, @intFromEnum(data)) orelse { + log.warn("render_state_row_get invalid data value={d}", .{@intFromEnum(data)}); + return .invalid_value; + }; + } + + return switch (data) { + .invalid => .invalid_value, + inline else => |comptime_data| rowGetTyped( + it, + y, + comptime_data, + @ptrCast(@alignCast(out)), + ), + }; +} + fn rowGetTyped( - iterator_: RowIterator, + it: *RowIteratorWrapper, + y: usize, comptime data: RowData, out: *data.OutType(), ) Result { - const it = iterator_ orelse return .invalid_value; - const y = it.y orelse return .invalid_value; switch (data) { .invalid => return .invalid_value, .dirty => out.* = it.dirty[y], @@ -724,7 +848,7 @@ fn rowGetTyped( const cell_data = it.cells[y].slice(); cells.* = .{ .alloc = cells.alloc, - .x = null, + .x = position_none, .raws = cell_data.items(.raw), .graphemes = cell_data.items(.grapheme), .styles = cell_data.items(.style), @@ -772,7 +896,8 @@ fn rowSetTyped( value: *const option.InType(), ) Result { const it = iterator_ orelse return .invalid_value; - const y = it.y orelse return .invalid_value; + const y = it.y; + if (y >= it.raws.len) return .invalid_value; switch (option) { .dirty => it.dirty[y] = value.*, } @@ -977,7 +1102,7 @@ test "render: row iterator new/free" { const iterator_ptr = iterator.?; const row_data = state.?.state.row_data.slice(); - try testing.expectEqual(@as(?size.CellCountInt, null), iterator_ptr.y); + try testing.expectEqual(position_none, iterator_ptr.y); try testing.expectEqual(row_data.items(.raw).len, iterator_ptr.raws.len); try testing.expectEqual(row_data.items(.cells).len, iterator_ptr.cells.len); try testing.expectEqual(row_data.items(.selection).len, iterator_ptr.selection.len); @@ -1498,16 +1623,16 @@ test "render: row iterator next" { } try testing.expect(row_iterator_next(iterator)); - try testing.expectEqual(@as(?size.CellCountInt, 0), iterator.?.y); + try testing.expectEqual(@as(usize, 0), iterator.?.y); var i: size.CellCountInt = 1; while (i < rows) : (i += 1) { try testing.expect(row_iterator_next(iterator)); - try testing.expectEqual(@as(?size.CellCountInt, i), iterator.?.y); + try testing.expectEqual(i, iterator.?.y); } try testing.expect(!row_iterator_next(iterator)); - try testing.expectEqual(@as(?size.CellCountInt, rows - 1), iterator.?.y); + try testing.expectEqual(@as(usize, rows - 1), iterator.?.y); } test "render: update" { diff --git a/src/terminal/color.zig b/src/terminal/color.zig index c04e19e84..8bf327eac 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -1,6 +1,7 @@ const colorpkg = @This(); const std = @import("std"); +const builtin = @import("builtin"); const assert = @import("../quirks.zig").inlineAssert; const x11_color = @import("x11_color.zig"); @@ -128,10 +129,48 @@ pub const PaletteC = [256]RGB.C; /// Convert a Palette to a PaletteC. pub fn paletteCval(palette: *const Palette) PaletteC { var result: PaletteC = undefined; - for (&result, palette) |*dst, src| dst.* = src.cval(); + paletteCvalSlice(palette[0..], result[0..]); return result; } +/// Convert a slice of palette entries to their C representation. +/// Asserts that both slices are the same length. +pub fn paletteCvalSlice(src: []const RGB, dst: []RGB.C) void { + assert(src.len == dst.len); + + var i: usize = 0; + + // For CPUs that are LE, we can do some clever byte shuffling + // with vectorization to do a 4-to-3 conversion. + if (comptime builtin.cpu.arch.endian() == .little) { + // Process 4 entries at a time: one 16-byte load, one byte + // shuffle dropping the padding byte of each entry, and one + // 16-byte store. The store intentionally overlaps the next + // group by 4 (garbage) bytes so that it stays a single + // vector store; the loop bound guarantees the overlap stays + // in bounds and every overlapped byte is rewritten by the + // next iteration or the scalar tail. + // + // Note the input load is a memory-level reinterpretation + // (pointer cast) because a value-level @bitCast of packed + // structs operates on the 24-bit value bits, not the 4-byte + // in-memory representation. + const dst_bytes: [*]u8 = @ptrCast(dst.ptr); + while (i + 6 <= src.len) : (i += 4) { + const in: @Vector(16, u8) = @as( + *const [16]u8, + @ptrCast(src[i..][0..4]), + ).*; + const out: [16]u8 = @shuffle(u8, in, undefined, [16]i32{ + 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15, + }); + dst_bytes[i * 3 ..][0..16].* = out; + } + } + + for (src[i..], dst[i..]) |entry, *out| out.* = entry.cval(); +} + /// Convert a PaletteC to a Palette. pub fn paletteZval(palette: *const PaletteC) Palette { var result: Palette = undefined; @@ -1276,3 +1315,35 @@ test "LAB.toRgb" { try testing.expectEqual(expected.b, actual.b); } } + +test paletteCvalSlice { + const testing = std.testing; + + // Every length from empty through a full palette, so that the + // vectorized groups, the overlapping store bound, and the scalar + // tail are all exercised at their edges. + var src: Palette = undefined; + for (&src, 0..) |*rgb, i| rgb.* = .{ + .r = @intCast(i % 256), + .g = @intCast((i * 7 + 1) % 256), + .b = @intCast((i * 13 + 2) % 256), + }; + + var dst: PaletteC = undefined; + for (0..src.len + 1) |len| { + // Poison the destination so unwritten bytes are detected. + @memset(std.mem.asBytes(&dst), 0xAA); + + paletteCvalSlice(src[0..len], dst[0..len]); + for (src[0..len], dst[0..len]) |rgb, c| { + try testing.expectEqual(rgb.r, c.r); + try testing.expectEqual(rgb.g, c.g); + try testing.expectEqual(rgb.b, c.b); + } + + // Nothing beyond the requested length may be written. + for (std.mem.asBytes(&dst)[len * 3 ..]) |b| { + try testing.expectEqual(@as(u8, 0xAA), b); + } + } +} diff --git a/src/terminal/render.zig b/src/terminal/render.zig index 867cd6224..11db57a2a 100644 --- a/src/terminal/render.zig +++ b/src/terminal/render.zig @@ -235,6 +235,21 @@ pub const RenderState = struct { /// The highlights within this row. highlights: std.ArrayList(Highlight), + + /// The style runs applied to this row's per-cell style data + /// by the last `endUpdate` that touched it. This is what lets + /// `endUpdate` skip the (comparatively large) per-cell style + /// fill when a rebuilt row produced identical runs, which is + /// the common case: text changes far more often than styling. + /// + /// The invariant is that this always describes the current + /// contents of the cell style data for the covered ranges. It + /// is cleared whenever the cell storage is reallocated. + /// + /// This uses the general allocator (NOT the row arena) + /// because it must survive row rebuilds. `endUpdate` cannot + /// allocate, so `beginUpdate` reserves the capacity. + applied_styles: std.ArrayList(StyleRun), }; pub const Highlight = struct { @@ -304,10 +319,12 @@ pub const RenderState = struct { for ( self.row_data.items(.arena), self.row_data.items(.cells), - ) |state, *cells| { + self.row_data.items(.applied_styles), + ) |state, *cells, *applied| { var arena: ArenaAllocator = state.promote(alloc); arena.deinit(); cells.deinit(alloc); + applied.deinit(alloc); } self.row_data.deinit(alloc); self.pending_styles.deinit(alloc); @@ -464,6 +481,7 @@ pub const RenderState = struct { .dirty = true, .selection = null, .highlights = .empty, + .applied_styles = .empty, }); } } else { @@ -471,10 +489,12 @@ pub const RenderState = struct { for ( row_data.items(.arena)[self.rows..], row_data.items(.cells)[self.rows..], - ) |state, *cell| { + row_data.items(.applied_styles)[self.rows..], + ) |state, *cell, *applied| { var arena: ArenaAllocator = state.promote(alloc); arena.deinit(); cell.deinit(alloc); + applied.deinit(alloc); } self.row_data.shrinkRetainingCapacity(self.rows); } @@ -490,6 +510,7 @@ pub const RenderState = struct { const row_sels = row_data.items(.selection); const row_highlights = row_data.items(.highlights); const row_dirties = row_data.items(.dirty); + const row_applied = row_data.items(.applied_styles); // If we're redrawing then every row will be rebuilt, superseding // any pending style runs from prior updates. Clearing also @@ -512,6 +533,7 @@ pub const RenderState = struct { .highlights = row_highlights, .dirties = row_dirties, .pending_styles = &self.pending_styles, + .applied_styles = row_applied, }; var y: usize = 0; var any_dirty: bool = false; @@ -735,22 +757,73 @@ pub const RenderState = struct { const row_data = self.row_data.slice(); const row_cells = row_data.items(.cells); - for (self.pending_styles.items) |run| { + const row_applied = row_data.items(.applied_styles); + + // Process the pending runs one row segment at a time. All the + // runs for a row are appended contiguously by a single + // beginUpdate, so a segment boundary is simply a change in y. + const runs = self.pending_styles.items; + var i: usize = 0; + while (i < runs.len) { + const y = runs[i].y; + var j = i + 1; + while (j < runs.len and runs[j].y == y) j += 1; + const segment = runs[i..j]; + i = j; + // Defensive: the row data may have changed shape if the // caller violated ordering (e.g. an error path skipped an // endUpdate between updates). Any update that changes // dimensions clears the pending list (redraw), so this // should never actually trigger, but the cost is trivial. - if (run.y >= row_cells.len) continue; - const styles = row_cells[run.y].slice().items(.style); - const end = @min(run.end, styles.len); - const start = @min(run.start, end); + if (y >= row_cells.len) continue; - @memset(styles[start..end], run.style); + // If the segment matches the runs already denormalized + // into this row's cell data then the per-cell styles are + // already correct and the (comparatively large) fill can + // be skipped entirely. This is the common case: rebuilt + // rows usually keep their styling (only the text + // changed), and full redraws of an unchanged screen keep + // both. + const applied = &row_applied[y]; + if (runsEql(applied.items, segment)) continue; + + for (segment) |run| { + const styles = row_cells[run.y].slice().items(.style); + const end = @min(run.end, styles.len); + const start = @min(run.start, end); + + @memset(styles[start..end], run.style); + } + + // Record what we applied so the next rebuild of this row + // can skip the fill. beginUpdate reserved the capacity + // (we cannot allocate here); if it doesn't fit (e.g. + // segments merged across multiple begins without an end) + // leave the cache empty, which never matches and simply + // means the next rebuild applies its runs. + applied.clearRetainingCapacity(); + if (applied.capacity >= segment.len) { + applied.appendSliceAssumeCapacity(segment); + } } self.pending_styles.clearRetainingCapacity(); } + /// Returns true if the two style run lists denormalize to + /// identical per-cell style data. This is a semantic comparison + /// (styles are compared field-wise, never by bytes, since padding + /// is undefined). + fn runsEql(a: []const StyleRun, b: []const StyleRun) bool { + if (a.len != b.len) return false; + for (a, b) |ar, br| { + if (ar.start != br.start or + ar.end != br.end or + !ar.style.eql(br.style)) return false; + } + return true; + } + /// Update the highlights in the render state from the given flattened /// highlights. Because this uses flattened highlights, it does not require /// reading from the terminal state so it should be done outside of @@ -1022,6 +1095,7 @@ const RowBuilder = struct { highlights: []std.ArrayList(RenderState.Highlight), dirties: []bool, pending_styles: *std.ArrayList(RenderState.StyleRun), + applied_styles: []std.ArrayList(RenderState.StyleRun), fn row( b: *const RowBuilder, @@ -1058,7 +1132,14 @@ const RowBuilder = struct { // Our per-row arena is only used for temporary allocations // pertaining to cells directly (e.g. graphemes, hyperlinks). const cells: *std.MultiArrayList(RenderState.Cell) = &b.cells[vy]; - if (cells.len != b.cols) try cells.resize(b.alloc, b.cols); + if (cells.len != b.cols) { + // The cell storage (including the per-cell style data) is + // being reallocated, so the applied style cache no longer + // describes it. Clear it before the resize so an error + // can't leave it stale. + b.applied_styles[vy].clearRetainingCapacity(); + try cells.resize(b.alloc, b.cols); + } // We always copy our raw cell data. In the case we have no // managed memory, we can skip setting any other fields. @@ -1077,6 +1158,7 @@ const RowBuilder = struct { const arena_alloc = arena.allocator(); const cells_grapheme = cells_slice.items(.grapheme); const n = page_cells.len; + const runs_start = b.pending_styles.items.len; var x: usize = 0; scan: while (x < n) { // Skip runs of plain cells a group at a time. Cells that @@ -1210,6 +1292,15 @@ const RowBuilder = struct { }, } } + + // Reserve the applied style cache capacity for the runs we + // appended so that endUpdate (which cannot allocate) is able + // to record what it applies. See Row.applied_styles. + const runs_added = b.pending_styles.items.len - runs_start; + if (runs_added > 0) try b.applied_styles[vy].ensureTotalCapacity( + b.alloc, + runs_added, + ); } }; @@ -1601,6 +1692,62 @@ test "begin and end update" { } } +test "endUpdate skips unchanged style runs" { + const testing = std.testing; + const alloc = testing.allocator; + const io = testing.io; + + var t = try Terminal.init(io, alloc, .{ + .cols = 10, + .rows = 3, + }); + defer t.deinit(alloc); + + var s = t.vtStream(); + defer s.deinit(); + s.nextSlice("\x1b[1mAB"); // Bold + + var state: RenderState = .empty; + defer state.deinit(alloc); + try state.update(alloc, &t); + + // The applied cache should record the bold run for row 0. + { + const row_data = state.row_data.slice(); + const applied = row_data.items(.applied_styles); + try testing.expectEqual(1, applied[0].items.len); + try testing.expect(applied[0].items[0].style.flags.bold); + try testing.expect(state.row_data.items(.cells)[0].get(0).style.flags.bold); + } + + // Rewrite the text without changing the styling: the row is + // rebuilt, the run matches the cache, and the styles must remain + // correct (the fill is skipped internally). + s.nextSlice("\x1b[1;1H\x1b[1mXY"); + try state.update(alloc, &t); + { + const cells = &state.row_data.items(.cells)[0]; + try testing.expectEqual('X', cells.get(0).raw.codepoint()); + try testing.expect(cells.get(0).style.flags.bold); + try testing.expect(cells.get(1).style.flags.bold); + } + + // Change the styling: the cache mismatches and the new styles + // must be applied and recorded. + s.nextSlice("\x1b[1;1H\x1b[0;3mZW"); // Italic + try state.update(alloc, &t); + { + const cells = &state.row_data.items(.cells)[0]; + try testing.expectEqual('Z', cells.get(0).raw.codepoint()); + try testing.expect(!cells.get(0).style.flags.bold); + try testing.expect(cells.get(0).style.flags.italic); + + const applied = state.row_data.items(.applied_styles); + try testing.expectEqual(1, applied[0].items.len); + try testing.expect(applied[0].items[0].style.flags.italic); + } +} + test "bg color cells" { const testing = std.testing; const alloc = testing.allocator; From 8a0a9faf8577c72fa8a287fb72694018a9c80f23 Mon Sep 17 00:00:00 2001 From: Guilherme Tiscoski Date: Fri, 14 Aug 2026 11:27:31 -0500 Subject: [PATCH 049/392] i18n: use "Redefinir" for reset actions in pt_BR The "Reset" menu item and the "Reset Terminal" command palette entry were translated as "Reiniciar", which reads as restarting the terminal session or process. These actions perform a VT reset that clears terminal state without restarting the shell, so "Redefinir" conveys the correct meaning and matches the standard pt_BR terminology used by other terminal emulators. --- po/pt_BR.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index ff01074f6..3c25759da 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-08-13 19:47-0300\n" +"PO-Revision-Date: 2026-08-14 11:26-0500\n" "Last-Translator: Guilherme Tiscoski \n" "Language-Team: Brazilian Portuguese \n" @@ -146,7 +146,7 @@ msgstr "Limpar" #: src/apprt/gtk/ui/1.2/surface.blp:338 src/apprt/gtk/ui/1.5/window.blp:286 msgid "Reset" -msgstr "Reiniciar" +msgstr "Redefinir" #: src/apprt/gtk/ui/1.2/surface.blp:345 src/apprt/gtk/ui/1.5/window.blp:250 msgid "Split" @@ -398,11 +398,11 @@ msgstr "Desenvolvedores do Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "Reiniciar terminal" +msgstr "Redefinir terminal" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "Reinicia o terminal para um estado limpo." +msgstr "Redefine o terminal para um estado limpo." #: src/input/command.zig:155 msgid "Copy to Clipboard" From 87f69a12ee28445cd95e39060fd626ccfc27b5e0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 09:10:46 -0700 Subject: [PATCH 050/392] libghostty: much faster vt_write on wasm targets This makes `ghostty_terminal_vt_write` on wasm32-freestanding anywhere from 1.4x to 13x faster depending on the input, measured in V8 via Node for Chrome as well as `jsc` for Safari. ## Changes * stream: the batched parse path (bulk UTF-8 decode, print_slice runs) is used even when `build_options.simd` is false. The per-byte loop is now debug-only. * simd/vt: the scalar `utf8DecodeUntilControlSeq` gets a vectorized ASCII bulk path that is compatible with wasm simd128. * style: on wasm, `Style.eql` compares canonical `PackedStyle` forms which is faster by like 11%. On native its slower so we only do this for wasm. * build: wasm targets now default to the `simd128` CPU feature since every browser engine has supported it for years. Opt out with `-Dcpu=generic`. * PACKAGING.md documents the wasm build, including `wasm-opt` notes. ## Benchmarks | Workload | Before | After | Speedup | |---|---|---|---| | ascii | 85 MB/s | 1070 MB/s | 12.5x | | ascii-wrap | 84 MB/s | 1103 MB/s | 13.1x | | clear-redraw | 85 MB/s | 913 MB/s | 10.7x | | scroll | 79 MB/s | 304 MB/s | 3.8x | | cursor | 120 MB/s | 255 MB/s | 2.1x | | utf8 | 99 MB/s | 169 MB/s | 1.7x | | sgr16 | 81 MB/s | 133 MB/s | 1.6x | | sgr-truecolor | 62 MB/s | 88 MB/s | 1.4x | End result: wasm at roughly 50-85% of the native ReleaseFast+SIMD build on the same workloads. Plain ASCII was at 6% of native before. **AI usage:** Lots of Fable help. As always, the human language stuff like this commit and comments were rewritten by me. --- PACKAGING.md | 32 +++++++++++++ nix/devShell.nix | 8 ++++ src/build/Config.zig | 19 ++++++++ src/simd/vt.zig | 99 ++++++++++++++++++++--------------------- src/terminal/stream.zig | 9 ++-- src/terminal/style.zig | 11 +++++ 6 files changed, 124 insertions(+), 54 deletions(-) diff --git a/PACKAGING.md b/PACKAGING.md index 1483b8591..ec2ef3dee 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -122,3 +122,35 @@ relevant to package maintainers: often necessary for system packages to specify a specific minimum Linux version, glibc, etc. Run `zig targets` to a get a full list of available targets. + +## WebAssembly (libghostty-vt) + +libghostty-vt can be built for WebAssembly for use in browsers and other +wasm runtimes: + +```sh +zig build -Demit-lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall +``` + +This produces `zig-out/bin/ghostty-vt.wasm`. + +Some notes for packaging the wasm module: + +- The build enables the `simd128` feature by default. Every browser engine + has supported it for years (Chrome 91, Firefox 89, Safari 16.4) and it is + a large performance win for VT parsing. If you target an unusual runtime + without SIMD support, opt out with `-Dcpu=generic`. + +- `ReleaseSmall` is the recommended optimization mode for the web. Running + the result through [Binaryen's](https://github.com/WebAssembly/binaryen) + `wasm-opt -O3` shrinks it by roughly a further 10% without hurting + performance. + +- `ReleaseFast` measures 10-20% faster than `ReleaseSmall` on escape-heavy + terminal workloads, but the artifact is dominated by DWARF debug info. + If you want the speed, strip it: `wasm-opt -O3 --strip-dwarf` reduces a + ReleaseFast build from over 5MB to roughly 1.1MB (versus roughly 0.8MB + for ReleaseSmall). When invoking `wasm-opt`, pass the feature flags for + what the module uses, e.g. `--enable-simd --enable-bulk-memory +--enable-sign-ext --enable-nontrapping-float-to-int --enable-multivalue +--enable-reference-types`. diff --git a/nix/devShell.nix b/nix/devShell.nix index 609a8a31f..c94892414 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -24,7 +24,11 @@ #, vulkan-loader # unused vttest, wabt, + wasm-tools, wasmtime, + binaryen, + twiggy, + wizer, wraptest, zig, zip, @@ -127,8 +131,12 @@ in kaitai-struct-compiler # wasm + binaryen + twiggy wabt + wasm-tools wasmtime + wizer # Localization gettext diff --git a/src/build/Config.zig b/src/build/Config.zig index 07e047ef5..09ab1a915 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -106,6 +106,25 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf result = b.resolveTargetQuery(query); } + // On wasm, default to enabling the simd128 feature. Every + // browser engine has supported it since 2023 or earlier + // (Chrome 91, Firefox 89, Safari 16.4) and it is a large + // performance win for the terminal hot paths (50%+ on + // print-heavy VT streams). Only apply when no explicit CPU + // was requested; targets for exotic non-browser runtimes + // can opt out with `-Dcpu=generic`. + if (result.result.cpu.arch.isWasm() and + result.query.cpu_model == .determined_by_arch_os and + result.query.cpu_features_add.isEmpty() and + result.query.cpu_features_sub.isEmpty()) + { + var query = result.query; + query.cpu_features_add.addFeature( + @intFromEnum(std.Target.wasm.Feature.simd128), + ); + result = b.resolveTargetQuery(query); + } + // The full Ghostty build no longer supports iOS; Fail early // with a clear message rather than partway through the build. if (result.result.os.tag == .ios and !emit_lib_vt) { diff --git a/src/simd/vt.zig b/src/simd/vt.zig index 807285f71..ded4ab3f5 100644 --- a/src/simd/vt.zig +++ b/src/simd/vt.zig @@ -53,11 +53,29 @@ fn utf8DecodeUntilControlSeqScalar( while (decode_offset < decode.len) { const b0 = decode[decode_offset]; - // ASCII fast path + // ASCII fast path. Use vectorization if it is available. This + // path is only run when simd=false, but that only controls our C++ + // simd builds. We can still rely on Zig intrinsics for platforms + // like wasm32+simd128. if (b0 < 0x80) { - output[decode_count] = b0; - decode_count += 1; - decode_offset += 1; + if (comptime std.simd.suggestVectorLength(u8)) |vl| { + const V = @Vector(vl, u8); + while (decode_offset + vl <= decode.len) { + const v: V = decode[decode_offset..][0..vl].*; + if (@reduce(.Or, v >= @as(V, @splat(0x80)))) break; + const w: @Vector(vl, u32) = @intCast(v); + output[decode_count..][0..vl].* = w; + decode_count += vl; + decode_offset += vl; + } + } + while (decode_offset < decode.len) { + const b = decode[decode_offset]; + if (b >= 0x80) break; + output[decode_count] = b; + decode_count += 1; + decode_offset += 1; + } continue; } @@ -70,15 +88,27 @@ fn utf8DecodeUntilControlSeqScalar( continue; } - // Multi-byte sequence. Determine expected length and the valid - // range for each continuation byte per Unicode Table 3-7. - const seq = utf8SeqInfo(b0); + // Multi-byte sequence. Only the first continuation byte has a + // lead-dependent valid range per Unicode Table 3-7; later + // continuation bytes are always 80-BF. Range validity per + // Table 3-7 excludes overlong, surrogate, and out-of-range + // encodings, so a fully valid sequence can be decoded by + // direct bit assembly with no further checks. + const seq_len: usize = if (b0 < 0xE0) 2 else if (b0 < 0xF0) 3 else 4; + const cb1_lo: u8, const cb1_hi: u8 = switch (b0) { + 0xE0 => .{ 0xA0, 0xBF }, + 0xED => .{ 0x80, 0x9F }, + 0xF0 => .{ 0x90, 0xBF }, + 0xF4 => .{ 0x80, 0x8F }, + else => .{ 0x80, 0xBF }, + }; // Check how many continuation bytes form a valid prefix (the - // maximal subpart). We check each byte against its specific - // valid range. + // maximal subpart), accumulating codepoint bits as we go. The + // lead byte contributes its low 7-len bits. + var cp: u32 = b0 & (@as(u32, 0x7F) >> @intCast(seq_len)); var valid: usize = 1; // lead byte is valid - for (0..seq.len - 1) |ci| { + while (valid < seq_len) { if (decode_offset + valid >= decode.len) { // The sequence is cut off by the end of the decode // region. If the region ends at the true end of the @@ -94,29 +124,17 @@ fn utf8DecodeUntilControlSeqScalar( break; } const cb = decode[decode_offset + valid]; - if (cb < seq.ranges[ci][0] or cb > seq.ranges[ci][1]) { - // Byte doesn't match expected range. The maximal - // subpart ends here. - break; - } + const lo: u8 = if (valid == 1) cb1_lo else 0x80; + const hi: u8 = if (valid == 1) cb1_hi else 0xBF; + if (cb < lo or cb > hi) break; + cp = (cp << 6) | (cb & 0x3F); valid += 1; } - if (valid == seq.len) { - // Full sequence present and structurally valid. Decode it. - // (Structural validity per Table 3-7 guarantees decode success.) - const cp_bytes = decode[decode_offset..][0..seq.len]; - if (std.unicode.utf8Decode(cp_bytes)) |cp| { - output[decode_count] = @intCast(cp); - decode_count += 1; - decode_offset += seq.len; - } else |_| { - // Should not happen given Table 3-7 validation, but - // be safe: emit FFFD for the lead byte. - output[decode_count] = 0xFFFD; - decode_count += 1; - decode_offset += 1; - } + if (valid == seq_len) { + output[decode_count] = cp; + decode_count += 1; + decode_offset += seq_len; } else { // Incomplete/ill-formed: the maximal subpart (valid bytes) // maps to a single FFFD. @@ -132,27 +150,6 @@ fn utf8DecodeUntilControlSeqScalar( }; } -const Utf8SeqInfo = struct { - len: u3, - ranges: [3][2]u8, -}; - -/// Returns the expected byte count and valid continuation byte ranges -/// for a UTF-8 sequence based on its lead byte, per Unicode Table 3-7. -fn utf8SeqInfo(lead: u8) Utf8SeqInfo { - return switch (lead) { - 0xC2...0xDF => .{ .len = 2, .ranges = .{ .{ 0x80, 0xBF }, .{ 0, 0 }, .{ 0, 0 } } }, - 0xE0 => .{ .len = 3, .ranges = .{ .{ 0xA0, 0xBF }, .{ 0x80, 0xBF }, .{ 0, 0 } } }, - 0xE1...0xEC => .{ .len = 3, .ranges = .{ .{ 0x80, 0xBF }, .{ 0x80, 0xBF }, .{ 0, 0 } } }, - 0xED => .{ .len = 3, .ranges = .{ .{ 0x80, 0x9F }, .{ 0x80, 0xBF }, .{ 0, 0 } } }, - 0xEE...0xEF => .{ .len = 3, .ranges = .{ .{ 0x80, 0xBF }, .{ 0x80, 0xBF }, .{ 0, 0 } } }, - 0xF0 => .{ .len = 4, .ranges = .{ .{ 0x90, 0xBF }, .{ 0x80, 0xBF }, .{ 0x80, 0xBF } } }, - 0xF1...0xF3 => .{ .len = 4, .ranges = .{ .{ 0x80, 0xBF }, .{ 0x80, 0xBF }, .{ 0x80, 0xBF } } }, - 0xF4 => .{ .len = 4, .ranges = .{ .{ 0x80, 0x8F }, .{ 0x80, 0xBF }, .{ 0x80, 0xBF } } }, - else => unreachable, - }; -} - // Differential test: the SIMD implementation must agree with the // scalar implementation on any input. Exercises random mixtures of // ASCII, escapes, controls, valid and invalid UTF-8, at various diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 729b6fc04..afbfa719c 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -646,9 +646,12 @@ pub fn Stream(comptime H: type) type { } inline fn nextSliceUntracked(self: *Self, input: []const u8) void { - // Disable SIMD optimizations if build requests it or if our - // manual debug mode is on. - if (comptime debug or !build_options.simd) { + // Byte-at-a-time parsing in debug mode only. Even without + // SIMD support (build_options.simd == false, e.g. wasm), the + // batched path below is much faster than per-byte dispatch + // because it decodes UTF-8 in bulk (via scalar fallbacks) and + // hands printable runs to the handler as print_slice actions. + if (comptime debug) { for (input) |c| self.nextUntracked(c); return; } diff --git a/src/terminal/style.zig b/src/terminal/style.zig index ee4c0e562..ce20e4fac 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = @import("../quirks.zig").inlineAssert; const fastprint = @import("../fastprint.zig"); const color = @import("color.zig"); @@ -94,6 +95,16 @@ pub const Style = struct { /// True if the style is equal to another style. pub fn eql(self: Style, other: Style) bool { + // On wasm, comparing packed forms wins (~13% on SGR-heavy streams). + // On native, the branchy early-exit field compare below measures + // ~5% faster (unequal styles usually differ in the first + // field or two), so each target keeps its own strategy. + if (comptime builtin.cpu.arch.isWasm()) { + const a: u128 = @bitCast(PackedStyle.fromStyle(self)); + const b: u128 = @bitCast(PackedStyle.fromStyle(other)); + return a == b; + } + return self.flags == other.flags and self.fg_color.eql(other.fg_color) and self.bg_color.eql(other.bg_color) and From 29a70bc3674e7a95c47a54f9660833a86c87f3b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 10:46:00 -0700 Subject: [PATCH 051/392] ci: publish wasm tip artifacts This builds and publishes `ghostty-vt.wasm` binaries into our tip GitHub releases. These are built with the proper optimization, `simd128` CPU feature set, and run through `wasm-opt`. This allows wasm consumers to use libghostty without a Zig toolkit. Published two: `ghostty-vt.wasm` and `ghostty-vt-small.wasm`. The latter is ReleaseSmall, but is 10 to 20% slower. Users choice. --- .github/workflows/release-tip.yml | 129 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 80 +++++++++++++++++- 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index dd1a23c03..e2363f93f 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -409,6 +409,135 @@ jobs: echo "Release URLs:" echo " XCFramework: https://tip.files.ghostty.org/${GHOSTTY_COMMIT_LONG}/ghostty-vt.xcframework.zip" + build-lib-vt-wasm: + needs: [setup] + if: | + needs.setup.outputs.should_skip != 'true' && + ( + github.event_name == 'workflow_dispatch' || + ( + github.repository_owner == 'ghostty-org' && + github.ref_name == 'main' + ) + ) + runs-on: namespace-profile-ghostty-sm + env: + GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }} + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1 + with: + path: | + /nix + /zig + + - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build ReleaseFast WASM + run: | + nix develop -c zig build \ + -Demit-lib-vt \ + -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseFast + + - name: Optimize ReleaseFast WASM + run: | + nix develop -c wasm-opt -O3 --strip-dwarf \ + --enable-simd \ + --enable-bulk-memory \ + --enable-sign-ext \ + --enable-nontrapping-float-to-int \ + --enable-multivalue \ + --enable-reference-types \ + zig-out/bin/ghostty-vt.wasm \ + -o ghostty-vt.wasm + + - name: Build ReleaseSmall WASM + run: | + nix develop -c zig build \ + -Demit-lib-vt \ + -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseSmall + + - name: Optimize ReleaseSmall WASM + run: | + nix develop -c wasm-opt -O3 \ + --enable-simd \ + --enable-bulk-memory \ + --enable-sign-ext \ + --enable-nontrapping-float-to-int \ + --enable-multivalue \ + --enable-reference-types \ + zig-out/bin/ghostty-vt.wasm \ + -o ghostty-vt-small.wasm + + - name: Verify WASM simd128 + run: | + # wasm-tools names the simd128 proposal "simd". The artifact must + # fail validation when that feature is disabled. + for artifact in ghostty-vt.wasm ghostty-vt-small.wasm; do + nix develop -c wasm-tools validate "${artifact}" + if nix develop -c wasm-tools validate \ + --features=-simd "${artifact}"; then + echo "${artifact} unexpectedly validates with simd128 disabled" >&2 + exit 1 + fi + echo "Verified ${artifact} requires simd128" + done + + - name: Sign WASM + run: | + echo -n "${{ secrets.MINISIGN_KEY }}" > minisign.key + echo -n "${{ secrets.MINISIGN_PASSWORD }}" > minisign.password + nix develop -c minisign -S -m ghostty-vt.wasm -s minisign.key < minisign.password + nix develop -c minisign -S -m ghostty-vt-small.wasm -s minisign.key < minisign.password + + - name: Update Release + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 + with: + name: 'Ghostty Tip ("Nightly")' + prerelease: true + tag_name: tip + target_commitish: ${{ github.sha }} + files: | + ghostty-vt.wasm + ghostty-vt.wasm.minisig + ghostty-vt-small.wasm + ghostty-vt-small.wasm.minisig + token: ${{ secrets.GH_RELEASE_TOKEN }} + + - name: Prep R2 Storage + run: | + mkdir -p blob/${GHOSTTY_COMMIT_LONG} + cp ghostty-vt.wasm blob/${GHOSTTY_COMMIT_LONG}/ghostty-vt.wasm + cp ghostty-vt-small.wasm blob/${GHOSTTY_COMMIT_LONG}/ghostty-vt-small.wasm + - name: Upload to R2 + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 + with: + r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} + r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} + r2-secret-access-key: ${{ secrets.CF_R2_TIP_SECRET_KEY }} + r2-bucket: ghostty-tip + source-dir: blob + destination-dir: ./ + + - name: Echo Release URLs + run: | + echo "Release URLs:" + echo " WASM (ReleaseFast): https://tip.files.ghostty.org/${GHOSTTY_COMMIT_LONG}/ghostty-vt.wasm" + echo " WASM (ReleaseSmall): https://tip.files.ghostty.org/${GHOSTTY_COMMIT_LONG}/ghostty-vt-small.wasm" + build-macos: needs: [setup] if: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec8b8844e..d73672a0f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,6 +94,7 @@ jobs: - build-cmake - build-flatpak - build-libghostty-vt + - build-libghostty-vt-wasm - build-libghostty-vt-android - build-libghostty-vt-macos - build-libghostty-vt-windows @@ -616,7 +617,6 @@ jobs: x86_64-windows-gnu, # doesn't work yet, we need a way to find msvc libc/c++ headers # x86_64-windows-msvc - wasm32-freestanding, ] runs-on: namespace-profile-ghostty-sm needs: test @@ -648,6 +648,84 @@ jobs: nix develop -c zig build -Demit-lib-vt \ -Dtarget=${{ matrix.target }} + build-libghostty-vt-wasm: + runs-on: namespace-profile-ghostty-sm + needs: test + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build ReleaseFast WASM + run: | + nix develop -c zig build \ + -Demit-lib-vt \ + -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseFast + + - name: Optimize ReleaseFast WASM + run: | + nix develop -c wasm-opt -O3 --strip-dwarf \ + --enable-simd \ + --enable-bulk-memory \ + --enable-sign-ext \ + --enable-nontrapping-float-to-int \ + --enable-multivalue \ + --enable-reference-types \ + zig-out/bin/ghostty-vt.wasm \ + -o ghostty-vt.wasm + + - name: Build ReleaseSmall WASM + run: | + nix develop -c zig build \ + -Demit-lib-vt \ + -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseSmall + + - name: Optimize ReleaseSmall WASM + run: | + nix develop -c wasm-opt -O3 \ + --enable-simd \ + --enable-bulk-memory \ + --enable-sign-ext \ + --enable-nontrapping-float-to-int \ + --enable-multivalue \ + --enable-reference-types \ + zig-out/bin/ghostty-vt.wasm \ + -o ghostty-vt-small.wasm + + - name: Verify WASM simd128 + run: | + # wasm-tools names the simd128 proposal "simd". The artifact must + # fail validation when that feature is disabled. + for artifact in ghostty-vt.wasm ghostty-vt-small.wasm; do + nix develop -c wasm-tools validate "${artifact}" + if nix develop -c wasm-tools validate \ + --features=-simd "${artifact}"; then + echo "${artifact} unexpectedly validates with simd128 disabled" >&2 + exit 1 + fi + echo "Verified ${artifact} requires simd128" + done + # lib-vt requires macOS runner for macOS/iOS builds because it requires the `apple_sdk` path build-libghostty-vt-macos: strategy: From 74a233b5439c6721cee8ae1f453c308e02ca9d74 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 10:36:56 -0700 Subject: [PATCH 052/392] libghostty: faster render state reads and updates on wasm targets This makes the `ghostty_render_state_*` C API significantly faster on wasm32-freestanding, measured in V8 via Node for Chrome. Also verified in `jsc` for Safari. The major change is a new bulk row read API that makes full-screen cell reads roughly 10x faster for wasm embedders. This should help any embedder with high FFI overhead, such as Go, Python, etc. too. Non-wasm performance is not impacted, all benchmarks were run on my mac too w/ no regressions (two of the changes are native wins as well). ## Changes * color: the "vectorized" palette conversion loop was silently scalarized by LLVM into per-byte ops because it loaded/stored through array-typed pointers. Zig 0.16 disables the LLVM loop vectorizer, so manually vectorized loops must go through vector-typed pointers. * C styles: major optimizations to converting Zig styles to C styles. This is a heavy operation for render state. * render: `endUpdate`'s style-run fill (`@memset` with a struct value) re-loaded its source every iteration and stored field by field. Now manually vectorized. * render: new `GHOSTTY_RENDER_STATE_ROW_DATA_CELLS_RAW` returns a borrowed `GhosttyCellsView` of the current row's raw cell values, valid until the next update. One call per row instead of 3-6 calls per cell. ## Benchmarks | Benchmark | Before | After | Speedup | |---|---|---|---| | colors_get | 114 ns | 35 ns | 3.3x | | style get, per styled cell | 7.8 ns | 6.7 ns | 1.2x | | raw+style read, per cell | 8.6 ns | 7.7 ns | 1.1x | | full-screen text read, per cell | 7.5 ns | 0.7 ns | 10.7x | | full-screen text+style read, per cell | 8.6 ns | 1.7 ns | 5.1x | | render state update, styled full frame | 3.4 us | 2.6 us | 1.3x | **AI usage:** Fable did the implementation and benchmarking and drafted this message. Comments were partially rewritten by me. --- include/ghostty/vt/render.h | 13 ++++ include/ghostty/vt/screen.h | 17 +++++ src/terminal/c/cell.zig | 6 ++ src/terminal/c/render.zig | 81 +++++++++++++++++++++-- src/terminal/c/style.zig | 124 ++++++++++++++++++++++++++++-------- src/terminal/color.zig | 25 ++++++-- src/terminal/render.zig | 32 +++++++++- 7 files changed, 258 insertions(+), 40 deletions(-) diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 2e3f2f71e..044d1734b 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -236,6 +236,19 @@ typedef enum GHOSTTY_ENUM_TYPED { /** Row-local selected cell range (GhosttyRenderStateRowSelection). */ GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION = 4, + + /** A borrowed view of the raw cell values for the current row + * (GhosttyCellsView). One value per column, identical to querying + * GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW for each cell. The view + * is only valid as long as the underlying render state is not + * updated; it is unsafe to use after updating the render state. + * + * This is the bulk alternative to iterating cells one at a time. + * It lets callers with expensive call boundaries (e.g. WebAssembly + * embedders) read an entire row with a single call, then drill + * into the cells iterator only for cells that need managed data + * (styles, graphemes). */ + GHOSTTY_RENDER_STATE_ROW_DATA_CELLS_RAW = 5, GHOSTTY_RENDER_STATE_ROW_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyRenderStateRowData; diff --git a/include/ghostty/vt/screen.h b/include/ghostty/vt/screen.h index 9f639b583..0d009a781 100644 --- a/include/ghostty/vt/screen.h +++ b/include/ghostty/vt/screen.h @@ -50,6 +50,23 @@ typedef uint64_t GhosttyCell; */ typedef uint64_t GhosttyRow; +/** + * A borrowed view of contiguous raw cell values. + * + * The memory is not owned by this struct. The pointer is only valid + * for the lifetime documented by the API that produces it. Each value + * is queried via ghostty_cell_get() like any other GhosttyCell. + * + * @ingroup screen + */ +typedef struct { + /** Pointer to len contiguous cell values. */ + const GhosttyCell* ptr; + + /** Number of cells. */ + size_t len; +} GhosttyCellsView; + /** * Cell content tag. * diff --git a/src/terminal/c/cell.zig b/src/terminal/c/cell.zig index cb76cb2fd..8444cfd97 100644 --- a/src/terminal/c/cell.zig +++ b/src/terminal/c/cell.zig @@ -10,6 +10,12 @@ const Result = @import("result.zig").Result; /// C: GhosttyCell pub const CCell = u64; +/// C: GhosttyCellsView +pub const CellsView = extern struct { + ptr: ?[*]const CCell, + len: usize, +}; + /// C: GhosttyCellContentTag pub const ContentTag = enum(c_int) { codepoint = 0, diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index 60e67e609..c5383558c 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -12,6 +12,7 @@ const terminal_c = @import("terminal.zig"); const ZigTerminal = @import("../Terminal.zig"); const renderpkg = @import("../render.zig"); const Result = @import("result.zig").Result; +const cell_c = @import("cell.zig"); const row = @import("row.zig"); const style_c = @import("style.zig"); @@ -614,10 +615,11 @@ fn rowCellsGetTypedInner( switch (data) { .invalid => return .invalid_value, .raw => out.* = cell.cval(), - .style => out.* = if (cell.hasStyling()) - style_c.Style.fromStyle(cells.styles[x]) - else - comptime style_c.Style.fromStyle(.{}), + .style => if (cell.hasStyling()) { + style_c.Style.write(cells.styles[x], out); + } else { + out.* = style_c.Style.default; + }, .graphemes_len => { if (!cell.hasText()) { out.* = 0; @@ -739,6 +741,7 @@ pub const RowData = enum(c_int) { raw = 2, cells = 3, selection = 4, + cells_raw = 5, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: RowData) type { @@ -748,6 +751,7 @@ pub const RowData = enum(c_int) { .raw => row.CRow, .cells => RowCells, .selection => RowSelection, + .cells_raw => cell_c.CellsView, }; } }; @@ -864,6 +868,13 @@ fn rowGetTyped( out.start_x = sel[0]; out.end_x = sel[1]; }, + .cells_raw => { + const raws: []const page.Cell = it.cells[y].items(.raw); + out.* = .{ + .ptr = @ptrCast(raws.ptr), + .len = raws.len, + }; + }, } return .success; @@ -1326,6 +1337,68 @@ test "render: row get selection" { try testing.expectEqual(Result.no_value, row_get(it, .selection, @ptrCast(&sel))); } +test "render: row get cells_raw" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + 10, + 3, + )); + defer terminal_c.free(terminal); + + terminal_c.vt_write(terminal, "AB", 2); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + + try testing.expectEqual(Result.success, update(state, terminal)); + + var it: RowIterator = null; + try testing.expectEqual(Result.success, row_iterator_new( + &lib.alloc.test_allocator, + &it, + )); + defer row_iterator_free(it); + + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it))); + + // Not positioned on a row yet. + var view: cell_c.CellsView = undefined; + try testing.expectEqual(Result.invalid_value, row_get(it, .cells_raw, @ptrCast(&view))); + + try testing.expect(row_iterator_next(it)); + try testing.expectEqual(Result.success, row_get(it, .cells_raw, @ptrCast(&view))); + try testing.expectEqual(@as(usize, 10), view.len); + + // The view must match the per-cell raw reads. + var cells: RowCells = null; + try testing.expectEqual(Result.success, row_cells_new( + &lib.alloc.test_allocator, + &cells, + )); + defer row_cells_free(cells); + try testing.expectEqual(Result.success, row_get(it, .cells, @ptrCast(&cells))); + const ptr = view.ptr.?; + var x: u16 = 0; + while (x < view.len) : (x += 1) { + var raw: page.Cell.C = undefined; + try testing.expectEqual(Result.success, row_cells_select(cells, x)); + try testing.expectEqual(Result.success, row_cells_get(cells, .raw, @ptrCast(&raw))); + try testing.expectEqual(raw, ptr[x]); + } + + // Contents sanity: first two cells hold our text. + const first: page.Cell = @bitCast(ptr[0]); + const second: page.Cell = @bitCast(ptr[1]); + try testing.expectEqual(@as(u21, 'A'), first.codepoint()); + try testing.expectEqual(@as(u21, 'B'), second.codepoint()); +} + test "render: row cells get selected" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( diff --git a/src/terminal/c/style.zig b/src/terminal/c/style.zig index 82729fa94..bea4d074c 100644 --- a/src/terminal/c/style.zig +++ b/src/terminal/c/style.zig @@ -25,20 +25,32 @@ pub const Color = extern struct { tag: ColorTag, value: ColorValue, + // fromColor converts the tag as a plain integer, which requires + // the internal and C tag values to line up. + comptime { + const Tag = std.meta.Tag(style.Style.Color); + for (@typeInfo(Tag).@"enum".fields) |f| { + assert(f.value == @intFromEnum(@field(ColorTag, f.name))); + } + } + pub fn fromColor(c: style.Style.Color) Color { - return switch (c) { - .none => .{ - .tag = .none, - .value = .{ ._padding = 0 }, - }, - .palette => |idx| .{ - .tag = .palette, - .value = .{ .palette = idx }, - }, - .rgb => |rgb| .{ - .tag = .rgb, - .value = .{ .rgb = rgb.cval() }, - }, + // The value is built as a single integer rather than through + // the per-variant union fields: the C union layout puts + // .palette at byte 0 and .rgb at bytes 0-2, which is exactly + // the internal payload representation, so each variant is the + // payload zero-extended to the u64 union backing. This keeps + // the conversion to two stores (tag + value) per color, which + // matters for the per-cell style reads in the render state + // API. + const value: u64 = switch (c) { + .none => 0, + .palette => |idx| idx, + .rgb => |rgb| @as(u24, @bitCast(rgb)), + }; + return .{ + .tag = @enumFromInt(@intFromEnum(std.meta.activeTag(c))), + .value = .{ ._padding = value }, }; } }; @@ -59,27 +71,83 @@ pub const Style = extern struct { overline: bool, underline: c_int, - pub fn fromStyle(s: style.Style) Style { - return .{ - .fg_color = .fromColor(s.fg_color), - .bg_color = .fromColor(s.bg_color), - .underline_color = .fromColor(s.underline_color), - .bold = s.flags.bold, - .italic = s.flags.italic, - .faint = s.flags.faint, - .blink = s.flags.blink, - .inverse = s.flags.inverse, - .invisible = s.flags.invisible, - .strikethrough = s.flags.strikethrough, - .overline = s.flags.overline, - .underline = @intFromEnum(s.flags.underline), + /// The default (empty) style in C representation. C + pub const default: Style = .{ + .fg_color = .{ .tag = .none, .value = .{ ._padding = 0 } }, + .bg_color = .{ .tag = .none, .value = .{ ._padding = 0 } }, + .underline_color = .{ .tag = .none, .value = .{ ._padding = 0 } }, + .bold = false, + .italic = false, + .faint = false, + .blink = false, + .inverse = false, + .invisible = false, + .strikethrough = false, + .overline = false, + .underline = 0, + }; + + // fromStyle writes the eight bool fields as one 8-byte store, + // which requires them to be consecutive and to match the low + // eight flag bits in order. + comptime { + const flag_names = [8][:0]const u8{ + "bold", "italic", "faint", "blink", + "inverse", "invisible", "strikethrough", "overline", }; + const base = @offsetOf(Style, "bold"); + for (flag_names, 0..) |name, i| { + assert(@offsetOf(Style, name) == base + i); + assert(@bitOffsetOf(@FieldType(style.Style, "flags"), name) == i); + } + } + + /// Write the C representation of the given style directly through + /// the out pointer. The hot per-cell style read in the render + /// state API uses this rather than `out.* = fromStyle(s)`: the + /// by-value form materializes a stack temporary plus a + /// @sizeOf(Style)-byte copy that LLVM does not elide. + pub fn write(s: style.Style, out: *Style) void { + out.size = @sizeOf(Style); + out.fg_color = .fromColor(s.fg_color); + out.bg_color = .fromColor(s.bg_color); + out.underline_color = .fromColor(s.underline_color); + + // I know this looks scary, but this results in truly measurable + // performance improvements due to how often styles are written + // especially in render loops. We vectorize our 8-bit to 8-byte + // write. + // + // We have to align(1) because we're ptrCasting a u8 pointer. + @as(*align(1) u64, @ptrCast(&out.bold)).* = bytes: { + // Spread the low eight flag bits (the eight bool fields, in + // order) into one byte each and write them with a single + // 8-byte store. + const flag_bits: u8 = @truncate(@as(u16, @bitCast(s.flags))); + const masks: @Vector(8, u8) = .{ 1, 2, 4, 8, 16, 32, 64, 128 }; + const hits = (@as(@Vector(8, u8), @splat(flag_bits)) & masks) == masks; + const bytes: @Vector(8, u8) = @select( + u8, + hits, + @as(@Vector(8, u8), @splat(1)), + @as(@Vector(8, u8), @splat(0)), + ); + break :bytes @bitCast(bytes); + }; + + out.underline = @intFromEnum(s.flags.underline); + } + + pub fn fromStyle(s: style.Style) Style { + var result: Style = undefined; + write(s, &result); + return result; } }; /// Returns the default style. pub fn default_style(result: *Style) callconv(lib.calling_conv) void { - result.* = .fromStyle(.{}); + result.* = .default; assert(result.size == @sizeOf(Style)); } diff --git a/src/terminal/color.zig b/src/terminal/color.zig index 8bf327eac..3df9a0e6a 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -155,16 +155,27 @@ pub fn paletteCvalSlice(src: []const RGB, dst: []RGB.C) void { // (pointer cast) because a value-level @bitCast of packed // structs operates on the 24-bit value bits, not the 4-byte // in-memory representation. + const V = @Vector(16, u8); const dst_bytes: [*]u8 = @ptrCast(dst.ptr); while (i + 6 <= src.len) : (i += 4) { - const in: @Vector(16, u8) = @as( - *const [16]u8, - @ptrCast(src[i..][0..4]), - ).*; - const out: [16]u8 = @shuffle(u8, in, undefined, [16]i32{ - 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15, + // All intermediaries have to also go through a Vector type + // to ensure LLVM lowers as vectorized ops. In Zig 0.16 the + // LLVM auto-vectorizer is disabled so this is required. + const in: V = @as(*align(4) const V, @ptrCast(src[i..][0..4])).*; + const out: V = @shuffle(u8, in, undefined, [16]i32{ + 0, 1, 2, + 4, 5, 6, + 8, 9, 10, + 12, 13, 14, + + // Overflow we don't care about + 3, 7, 11, + 15, }); - dst_bytes[i * 3 ..][0..16].* = out; + + // Scary looking but safe, mapping a byte pointer to the full + // vector type (hence align 1). + @as(*align(1) V, @ptrCast(dst_bytes[i * 3 ..][0..16])).* = out; } } diff --git a/src/terminal/render.zig b/src/terminal/render.zig index 11db57a2a..0c9bca318 100644 --- a/src/terminal/render.zig +++ b/src/terminal/render.zig @@ -793,7 +793,7 @@ pub const RenderState = struct { const end = @min(run.end, styles.len); const start = @min(run.start, end); - @memset(styles[start..end], run.style); + fillStyles(styles[start..end], run.style); } // Record what we applied so the next rebuild of this row @@ -810,6 +810,36 @@ pub const RenderState = struct { self.pending_styles.clearRetainingCapacity(); } + /// Fill a slice of styles with one value. + /// + /// This is equivalent to `@memset(dst, value)` but manually vectorized: + /// `@memset` with a struct value lowers to a per-element field copy + /// that reloads the source at every iteration because LLVM cannot + /// prove the destination doesn't alias it. And, Zig 0.16 disables + /// auto-vectorization due to an LLVM bug. + /// + /// This complexity is justified by accounting for ~10% of the endUpdate + /// times under heavily styled cases. + fn fillStyles(dst: []Style, value: Style) void { + // Each element is written as two overlapping 16-byte vector + // stores held in registers, which requires 16 <= size <= 32. + const elem_size = @sizeOf(Style); + comptime assert(elem_size >= 16 and elem_size <= 32); + + const V = @Vector(16, u8); + const src: *align(@alignOf(Style)) const [elem_size]u8 = @ptrCast(&value); + const lo = @as(*align(4) const V, @ptrCast(src[0..16])).*; + const hi = @as(*align(1) const V, @ptrCast(src[elem_size - 16 ..][0..16])).*; + + const dst_bytes = std.mem.sliceAsBytes(dst); + var off: usize = 0; + for (0..dst.len) |_| { + @as(*align(1) V, @ptrCast(dst_bytes[off..][0..16])).* = lo; + @as(*align(1) V, @ptrCast(dst_bytes[off + elem_size - 16 ..][0..16])).* = hi; + off += elem_size; + } + } + /// Returns true if the two style run lists denormalize to /// identical per-cell style data. This is a semantic comparison /// (styles are compared field-wise, never by bytes, since padding From 3d9b2b483cd807c05e72e2827559572c38bcbe66 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 12:35:34 -0700 Subject: [PATCH 053/392] libghostty: much faster grapheme-heavy IO throughput Processing grapheme-heavy input (ZWJ sequences, emoji modifiers, flags, combining marks) through is now almost 3x faster. ### Primary Change: PageList Capacity Projection This workload was heavily bound by `PageList.increaseCapacity` because pathological cases of single-dimensional growth cause repeated page capacity doublings which get increasingly expensive because each time we do a full allocation + clone. So the major change is that for grapheme bytes in particular, when we reach a capacity limit, we take the current usage for the current set of rows and project it out to the remaining capacity of rows. Basically, we assume that a similar workload will continue. So rather than doubling, we're _guessing_ how much you're going to need. In the real world, I'm not really sure if this matters at all. There are no regressions on any regular corpus streams (asciinema, wikipedia dumps, etc.). ### Other Changes There are some other changes here, all found on the path to improving grapheme IO throughput: * The bitmap allocator now maintains `search_start` hint we update on every allocation so that future free-scans are much faster. This is the lowest possible place we don't have a full bitmap. * For wasm32, we use an alternate hashing structure for small keys since Wyhash's 64bit * 64bit multiplication is very very slow because wasm has no widening instruction. * Terminal `printSlice` now checks the fast path compatibility once up front rather than on every fast-path attempt. ### Benchmarks Data: ZWJ family/profession sequences, skin-tone modifiers, flags, and combining marks streamed in 64 KiB chunks into an 80x24 terminal, default modes. | Benchmark | Before | After | Speedup | |---|---|---|---| | wasm, V8, 16 MiB stream | 52 MB/s | 151 MB/s | 2.9x | | native, terminal-stream, 64 MiB | 894 ms | 305 ms | 2.9x | Sorry the native stuff is in ms, that's how our native `ghostty-bench` does things versus the custom little V8 harness. **AI usage:** Developed alongside Fable: profiling, implementation, and benchmarks. All human language messages written myself. Validated myself. --- src/terminal/PageList.zig | 184 ++++++++++++++++++++++++++++++ src/terminal/Terminal.zig | 133 ++++++++++++++------- src/terminal/bitmap_allocator.zig | 109 +++++++++++++++++- src/terminal/hash_map.zig | 24 ++++ src/terminal/style.zig | 8 ++ 5 files changed, 417 insertions(+), 41 deletions(-) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 429a3633b..f68733f8c 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -4168,6 +4168,64 @@ pub fn increaseCapacity( if (layout.total_size > size.max_page_size) { return error.OutOfSpace; } + + // Doubling alone has a really bad behavior in that if you're + // in a pathological scenario with only one dimension of cap + // increase, you have to pay repeat double costs. Each time you + // double we have to reclone the entire page. As the page gets + // bigger this gets more expensive. + // + // Instead, for some dimensions, we do something else: we look + // at the current utilization, and project that to every row + // in the page capacity (if it fits). We just assume that your + // future workload will look the one you're currently filling. + // If we're wrong, its some wasted capacity but future growth + // still works in other dimensions. + // + // This only applies to the current page being grown. I previously + // tried a high-water-mark style solution to preallocate pages, + // which does work really well, but its not clear when you reset + // the mark. + project: { + // The dimensions we project must measure their current + // usage in the same units as the capacity field. + const used: u64 = switch (comptime tag) { + .grapheme_bytes => page.grapheme_alloc.usedBytes(page.memory), + + // Living item count. Note the capacity field is a + // requested item count that Layout.init rounds in + // both directions (table to the next power of two, + // items to the load factor of that), so this is an + // approximation in capacity units; the headroom + // below absorbs the error and an undershoot only + // costs one more (non-ladder) growth event. + .styles => page.styles.count(), + + else => break :project, + }; + if (used == 0 or page.size.rows == 0) break :project; + + // Full-page need at current density, plus 25% headroom + // for chunk rounding and fragmentation. + const density = used * @as(u64, cap.rows) / page.size.rows; + const projected_raw = density + density / 4; + + // Bound the jump to 32× the pre-growth capacity. Arbitrary + // choice until we can show otherwise. + const projected = @min( + projected_raw, + @as(u64, old) * 32, + std.math.maxInt(Int), + ); + if (projected <= @field(cap, field_name)) break :project; + + // Only take the projection if the resulting page still fits. + var proj_cap = cap; + @field(proj_cap, field_name) = @intCast(projected); + if (Page.layout(proj_cap).total_size > size.max_page_size) + break :project; + cap = proj_cap; + } }, }; @@ -11734,6 +11792,68 @@ test "PageList increaseCapacity to increase styles" { } } +test "PageList increaseCapacity styles projects capacity from page density" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); + defer s.deinit(); + + const original_cap = s.pages.first.?.capacity().styles; + + // Write styled cells so the page has a measurable per-row style + // density (unlike the plain doubling test above, which grows a + // page with no styles in use). + const bold: stylepkg.Style = .{ .flags = .{ .bold = true } }; + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..s.rows) |y| { + for (0..s.cols) |x| { + const rac = page.getRowAndCell(x, y); + const style_id = try page.styles.add(page.memory, bold); + rac.row.styled = true; + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = @intCast(x + 1) } }, + .style_id = style_id, + }; + } + } + } + + _ = try s.increaseCapacity(s.pages.first.?, .styles); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + // The page uses only two active rows out of thousands of rows + // of capacity, so the projected full-page need saturates the + // 32x-per-event growth bound instead of merely doubling. + try testing.expectEqual( + original_cap * 32, + page.capacity.styles, + ); + + // All cell content and styles are preserved by the growth. + for (0..s.rows) |y| { + for (0..s.cols) |x| { + const rac = page.getRowAndCell(x, y); + try testing.expectEqual( + @as(u21, @intCast(x + 1)), + rac.cell.content.codepoint.data, + ); + try testing.expect(rac.cell.style_id != stylepkg.default_id); + try testing.expect(bold.eql( + page.styles.get(page.memory, rac.cell.style_id).*, + )); + } + } + } +} + test "PageList increaseCapacity to increase graphemes" { const testing = std.testing; const alloc = testing.allocator; @@ -11778,6 +11898,70 @@ test "PageList increaseCapacity to increase graphemes" { } } +test "PageList increaseCapacity graphemes projects capacity from page density" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); + defer s.deinit(); + + const original_cap = s.pages.first.?.capacity().grapheme_bytes; + + // Write cells with grapheme data so the page has a measurable + // per-row grapheme density (unlike the plain doubling test above, + // which grows a page with no grapheme usage). + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..s.rows) |y| { + for (0..s.cols) |x| { + const rac = page.getRowAndCell(x, y); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = @intCast(x + 1) } }, + }; + try page.appendGrapheme(rac.row, rac.cell, 0x0301); + try page.appendGrapheme(rac.row, rac.cell, 0x0302); + } + } + } + + _ = try s.increaseCapacity(s.pages.first.?, .grapheme_bytes); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + // The page uses only two active rows out of thousands of rows + // of capacity, so the projected full-page need saturates the + // 32x-per-event growth bound instead of merely doubling. + try testing.expectEqual( + original_cap * 32, + page.capacity.grapheme_bytes, + ); + + // All cell and grapheme content is preserved by the growth. + try testing.expectEqual( + @as(usize, s.rows * s.cols), + page.graphemeCount(), + ); + for (0..s.rows) |y| { + for (0..s.cols) |x| { + const rac = page.getRowAndCell(x, y); + try testing.expectEqual( + @as(u21, @intCast(x + 1)), + rac.cell.content.codepoint.data, + ); + const cps = page.lookupGrapheme(rac.cell).?; + try testing.expectEqual(@as(usize, 2), cps.len); + try testing.expectEqual(@as(u21, 0x0301), cps[0]); + try testing.expectEqual(@as(u21, 0x0302), cps[1]); + } + } + } +} + test "PageList increaseCapacity to increase hyperlinks" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index b157994f5..ce22ae084 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -522,11 +522,55 @@ pub fn printRepeat(self: *Terminal, count_req: usize) !void { /// slower per-codepoint path. They're less common and this is optimized /// for the aforementioned cases. pub fn printSlice(self: *Terminal, cps: []const u32) !void { + // Check if we can do the fast path up front. If we can't + // we need to go back to scalar `print`. + const fast = fast: { + // Only the main display is supported. + if (self.status_display != .main) break :fast false; + + // Modes that require per-codepoint handling in print(). + // Wraparound is required (its the default) so that our + // row-fill logic below can assume soft-wrap semantics. Insert + // mode shifts cells per print. + if (self.modes.get(.insert)) break :fast false; + if (!self.modes.get(.wraparound)) break :fast false; + + // Charset must map ASCII as-is (true unless a DEC special + // charset is actively invoked, which is rare). + const screen: *Screen = self.screens.active; + if (screen.charset.single_shift != null) break :fast false; + switch (screen.charset.charsets.get(screen.charset.gl)) { + .utf8, .ascii => {}, + else => break :fast false, + } + + // Hyperlinks require per-cell map bookkeeping. + if (screen.cursor.hyperlink_id != 0) break :fast false; + + break :fast true; + }; + if (!fast) { + for (cps) |cp| try self.print(@intCast(cp)); + return; + } + + const grapheme_cluster = self.modes.get(.grapheme_cluster); + + // When grapheme clustering is enabled and a left margin is set, + // print() consults the cell left of the margin after wrapping, + // which we can't reason about here. Restrict the fast path to + // the [0x10, 0xFF] range in that case (those never cluster). + const allow_unicode = !grapheme_cluster or self.scrolling_region.left == 0; + var i: usize = 0; while (i < cps.len) { // Try the fast-path print first. This will return the number of // codepoints it consumed. - const consumed = try self.printSliceFast(cps[i..]); + const consumed = try self.printSliceFast( + cps[i..], + grapheme_cluster, + allow_unicode, + ); if (consumed > 0) { i += consumed; continue; @@ -548,32 +592,16 @@ pub fn printSlice(self: *Terminal, cps: []const u32) !void { /// /// The fast path handles runs of narrow (width 1) and wide (width 2) /// codepoints being written to simple cells. Everything else (zero -/// width codepoints, grapheme cluster continuations, insert mode, -/// charset mapping, hyperlinks, complex cells, etc.) is rejected so -/// `print` can handle it with full generality. -fn printSliceFast(self: *Terminal, cps: []const u32) !usize { - // Only the main display is supported. - if (self.status_display != .main) return 0; - - // Modes that require per-codepoint handling in print(). Wraparound - // is required (its the default) so that our row-fill logic below can - // assume soft-wrap semantics. Insert mode shifts cells per print. - if (self.modes.get(.insert)) return 0; - if (!self.modes.get(.wraparound)) return 0; - +/// width codepoints, grapheme cluster continuations, complex cells, +/// etc.) is rejected so `print` can handle it with full generality. +fn printSliceFast( + self: *Terminal, + cps: []const u32, + grapheme_cluster: bool, + allow_unicode: bool, +) !usize { const screen: *Screen = self.screens.active; - // Charset must map ASCII as-is (true unless a DEC special charset - // is actively invoked, which is rare). - if (screen.charset.single_shift != null) return 0; - switch (screen.charset.charsets.get(screen.charset.gl)) { - .utf8, .ascii => {}, - else => return 0, - } - - // Hyperlinks require per-cell map bookkeeping. - if (screen.cursor.hyperlink_id != 0) return 0; - // Codepoints in [0x10, 0xFF] are always narrow (width 1, matching // the c <= 0xFF fast path in print) and can never interact with // grapheme clustering (which requires a codepoint > 0xFF). @@ -584,13 +612,6 @@ fn printSliceFast(self: *Terminal, cps: []const u32) !usize { // 2027) is enabled, if they are a grapheme break from the // previously printed codepoint (so print would never attach them // to the previous cell). - const grapheme_cluster = self.modes.get(.grapheme_cluster); - - // When grapheme clustering is enabled and a left margin is set, - // print() consults the cell left of the margin after wrapping, - // which we can't reason about here. Restrict the fast path to - // the [0x10, 0xFF] range in that case (those never cluster). - const allow_unicode = !grapheme_cluster or self.scrolling_region.left == 0; // Codepoints in [0x10, 0xFF] are always narrow: print() // hardcodes width 1 for c <= 0xFF (no width table lookup). @@ -619,14 +640,46 @@ fn printSliceFast(self: *Terminal, cps: []const u32) !usize { } // The first codepoint requires care when grapheme clustering is - // enabled: print() examines the previous *cell* which can hold - // state (grapheme data) that we can't cheaply reason about here. - // Note this includes the pending-wrap state: print() may attach - // to the pending cell *instead of wrapping*. We only take the - // first codepoint if the cursor is at column zero with no pending - // wrap, where print() skips clustering entirely. - if (grapheme_cluster) { - if (screen.cursor.pending_wrap or screen.cursor.x != 0) return 0; + // enabled: print() may attach it to the previous *cell* instead + // of writing a new one. Take the first codepoint only when we can + // determine — computing exactly what print() would — that it + // starts a new cluster. At column zero with no pending wrap, + // print() skips clustering entirely. Otherwise resolve the + // previous cell the way print() does and check for a break. + // + // Note the pending-wrap rejection: print() may attach to the + // pending cell *instead of wrapping*, which we can't model here. + if (grapheme_cluster and screen.cursor.x != 0) gate: { + if (screen.cursor.pending_wrap) return 0; + + // Resolve the content cell to our left exactly like print(): + // if the immediate left cell is a wide spacer tail, the + // content lives one further left. (A spacer tail can never + // be at column zero — its wide half would have to be in the + // previous row — so the second cursorCellLeft is in bounds.) + const immediate = screen.cursorCellLeft(1); + const prev: *Cell = switch (immediate.wide) { + .spacer_tail => screen.cursorCellLeft(2), + else => immediate, + }; + + // An empty previous cell is necessarily a grapheme break. + if (prev.codepoint() == 0) break :gate; + + // Grapheme data on the previous cell requires the full + // cluster state machine replay; only print() can do that. + if (prev.hasGrapheme()) return 0; + + // A simple single-codepoint previous cell: print() would run + // exactly this break check from the default state. + var state: uucode.grapheme.BreakState = .default; + if (!unicode.graphemeBreak( + prev.content.codepoint.data, + @intCast(cp0), + &state, + )) return 0; + } else if (grapheme_cluster) { + if (screen.cursor.pending_wrap) return 0; } // The width lookup is a runtime value while printSliceFill is diff --git a/src/terminal/bitmap_allocator.zig b/src/terminal/bitmap_allocator.zig index 787f0ace0..a300cf2e8 100644 --- a/src/terminal/bitmap_allocator.zig +++ b/src/terminal/bitmap_allocator.zig @@ -44,6 +44,11 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type { bitmap: Offset(u64), bitmap_count: usize, + /// Lowest bitmap word index that may contain a free bit; words + /// below it are fully allocated, so alloc scans can start here + /// instead of at zero. + search_start: usize = 0, + /// The contiguous buffer of chunks. chunks: Offset(u8), @@ -95,9 +100,21 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type { return error.OutOfMemory; // Find the index of the free chunk. This also marks it as used. + // Words below search_start have no free bits, so no free span + // can start in or cross them and it is safe to skip them. const bitmaps = self.bitmap.ptr(base); - const idx = findFreeChunks(bitmaps[0..self.bitmap_count], chunk_count) orelse + const start = @min(self.search_start, self.bitmap_count); + const rel = findFreeChunks(bitmaps[start..self.bitmap_count], chunk_count) orelse return error.OutOfMemory; + const idx = start * bitmap_bit_size + rel; + + // Advance past any words the allocation just filled so the + // next scan starts at the first word with a free bit. + self.search_start = start: { + var new_start = start; + while (new_start < self.bitmap_count and bitmaps[new_start] == 0) new_start += 1; + break :start new_start; + }; const chunks = self.chunks.ptr(base); const ptr: [*]T = @ptrCast(@alignCast(&chunks[idx * chunk_size])); @@ -116,6 +133,12 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type { const chunks = self.chunks.ptr(base); const chunk_idx = @divExact(@intFromPtr(slice.ptr) - @intFromPtr(chunks), chunk_size); + // The freed word gains free bits, so scans must not skip it. + self.search_start = @min( + self.search_start, + @divFloor(chunk_idx, bitmap_bit_size), + ); + const bitmaps = self.bitmap.ptr(base); // Current bitmap index. @@ -659,6 +682,90 @@ test "BitmapAllocator alloc and free one bitmap" { ); } +test "BitmapAllocator search hint skips full words" { + const Alloc = BitmapAllocator(1); + // Capacity such that we'll have 3 bitmaps. + const cap = Alloc.bitmap_bit_size * 3; + + const testing = std.testing; + const alloc = testing.allocator; + const layout = Alloc.layout(cap); + const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size); + defer alloc.free(buf); + + var bm = Alloc.init(.init(buf), layout); + try testing.expectEqual(@as(usize, 0), bm.search_start); + + // Fill the first bitmap word exactly with single-chunk allocations. + var first: []u8 = undefined; + for (0..Alloc.bitmap_bit_size) |i| { + const slice = try bm.alloc(u8, buf, 1); + if (i == 0) first = slice; + } + + // The first word is exhausted so scans start at the second word. + try testing.expectEqual(@as(usize, 1), bm.search_start); + + // The next allocation is the first chunk of the second word, so + // the skipped-word index arithmetic must still yield the right + // chunk address. + const next = try bm.alloc(u8, buf, 1); + try testing.expectEqual( + @intFromPtr(first.ptr) + Alloc.bitmap_bit_size, + @intFromPtr(next.ptr), + ); + + // Freeing a chunk in the first word lowers the hint so the freed + // space is found again. + bm.free(buf, first); + try testing.expectEqual(@as(usize, 0), bm.search_start); + const again = try bm.alloc(u8, buf, 1); + try testing.expectEqual(@intFromPtr(first.ptr), @intFromPtr(again.ptr)); +} + +test "BitmapAllocator search hint does not skip partial words" { + const Alloc = BitmapAllocator(1); + // Capacity such that we'll have 2 bitmaps. + const cap = Alloc.bitmap_bit_size * 2; + + const testing = std.testing; + const alloc = testing.allocator; + const layout = Alloc.layout(cap); + const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size); + defer alloc.free(buf); + + var bm = Alloc.init(.init(buf), layout); + + // Allocate all but 4 chunks of the first word. + var first: []u8 = undefined; + for (0..Alloc.bitmap_bit_size - 4) |i| { + const slice = try bm.alloc(u8, buf, 1); + if (i == 0) first = slice; + } + + // An 8-chunk run can't fit the 4 remaining bits (small runs never + // span words), so it comes from the second word — but the hint + // must stay at the first word, which still has free bits. + const big = try bm.alloc(u8, buf, 8); + try testing.expectEqual( + @intFromPtr(first.ptr) + Alloc.bitmap_bit_size, + @intFromPtr(big.ptr), + ); + try testing.expectEqual(@as(usize, 0), bm.search_start); + + // A 4-chunk run fits the first word's remaining bits exactly; a + // hint that skipped the partial word would wrongly place this in + // the second word (or report OutOfMemory once that filled). + const small = try bm.alloc(u8, buf, 4); + try testing.expectEqual( + @intFromPtr(first.ptr) + Alloc.bitmap_bit_size - 4, + @intFromPtr(small.ptr), + ); + + // Now the first word is full and the hint advances past it. + try testing.expectEqual(@as(usize, 1), bm.search_start); +} + test "BitmapAllocator alloc and free half bitmap" { const Alloc = BitmapAllocator(1); // Capacity such that we'll have 3 bitmaps. diff --git a/src/terminal/hash_map.zig b/src/terminal/hash_map.zig index abef87e3b..6f3b8b3aa 100644 --- a/src/terminal/hash_map.zig +++ b/src/terminal/hash_map.zig @@ -40,6 +40,7 @@ //! by any removal. const std = @import("std"); +const builtin = @import("builtin"); const assert = @import("../quirks.zig").inlineAssert; const mem = std.mem; const Allocator = mem.Allocator; @@ -74,6 +75,29 @@ fn AutoContext(comptime K: type) type { pub fn hash(_: @This(), key: K) u64 { if (comptime std.meta.hasUniqueRepresentation(K)) { + // On wasm32, Wyhash's 64x64 -> 128 multiplies lower to + // __multi3 libcalls (wasm has no widening multiply + // instruction), which makes the hash itself the + // dominant cost of small-key map operations. + // + // Keys here fit in a u64, so use a splitmix64-style finalizer + // instead: two native i64.mul with full avalanche in + // both the low bits (slot index) and high bits + // (metadata fingerprint). + if (comptime builtin.cpu.arch.isWasm() and @sizeOf(K) <= 8) { + var x: u64 = 0; + @memcpy( + std.mem.asBytes(&x)[0..@sizeOf(K)], + std.mem.asBytes(&key), + ); + x ^= x >> 33; + x *%= 0xff51afd7ed558ccd; + x ^= x >> 33; + x *%= 0xc4ceb9fe1a85ec53; + x ^= x >> 33; + return x; + } + // LLVM 21 (Zig 0.16) failed to inline this which resulted // in a measurable almost 2x slowdown on our hyperlink map // benchmark. So, force it. diff --git a/src/terminal/style.zig b/src/terminal/style.zig index ce20e4fac..701356e98 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -90,6 +90,14 @@ pub const Style = struct { /// True if the style is the default style. pub inline fn default(self: Style) bool { + // On wasm, eql converts both sides to packed form; the default + // side is comptime-known, so bake it and convert only self. + // This is called on every SGR change so it's worth it. + if (comptime builtin.cpu.arch.isWasm()) { + const d: u128 = comptime @bitCast(PackedStyle.fromStyle(.{})); + return @as(u128, @bitCast(PackedStyle.fromStyle(self))) == d; + } + return self.eql(.{}); } From 88ed6bebf4abfa8deb1b0cdd73dde4b07c7e0a3e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 13:24:20 -0700 Subject: [PATCH 054/392] libghostty: much faster wide-character reflow on resize Resizing a terminal whose buffer is heavy with wide characters (CJK, emoji) is now 3-5x faster on the reflow path. This was relatively simple work. We already have a bulk fast path for same-style cells. We previously omitted ANY wide characters from this. We relaxed this by making it work with complete wide pairs (wide followed by spacer tail). ### Benchmarks Resize dance (13 column resizes, 80 -> 40 -> 132 and back and forth again) over a ~2,000-row scrollback buffer. | Workload | Before | After | Speedup | |---|---|---|---| | cjk | 0.938 | 0.296 | 3.2x | | emoji | 0.875 | 0.191 | 4.6x | | mixed build-log | 0.306 | 0.155 | 2.0x | | grapheme | 2.449 | 1.831 | 1.3x | | ascii-short | 0.115 | 0.117 | 1.0x | | ascii-long | 0.109 | 0.112 | 1.0x | | latin | 0.093 | 0.095 | 1.0x | | sgr-truecolor | 0.939 | 0.965 | 1.0x | **AI usage:** Profiled, implemented, benchmarked, and written by Fable. Plan validated by me before doing it, I wrote all the comments/commits/blah. --- src/terminal/PageList.zig | 458 +++++++++++++++++++++++++++++++++++--- 1 file changed, 429 insertions(+), 29 deletions(-) diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index f68733f8c..672370af2 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -1711,18 +1711,20 @@ const ReflowCursor = struct { // Fast path: bulk-copy a run of simple cells directly // into the destination row. The vast majority of cells - // are narrow, have no managed memory (graphemes, - // hyperlinks), and share a single style in long runs, so - // this avoids the per-cell state machine below for most - // of the work. Rows with tracked pins take the slow path - // so pin remapping behaves identically. + // are narrow cells or complete wide pairs, have no + // managed memory (graphemes, hyperlinks), and share a + // single style in long runs, so this avoids the + // per-cell state machine below for most of the work. + // Rows with tracked pins take the slow path so pin + // remapping behaves identically. if (!row_has_pins) { const max_run = @min( cols_len - x, @as(usize, self.page.size.cols) - self.x, ); - const run = bulkRunLength(cells[x..][0..max_run]); - if (run > 0 and self.copyRun(cells[x..][0..run], src_page)) { + const window = cells[x..][0..max_run]; + const run = bulkRunLength(window); + if (run > 0 and self.copyRun(window[0..run], src_page)) { x += run; continue; } @@ -1898,9 +1900,21 @@ const ReflowCursor = struct { fn bulkRunLength(cells: []const pagepkg.Cell) usize { if (cells.len == 0) return 0; const first = cells[0]; - if (!bulkCopyable(first)) return 0; - const run_pattern = BulkRunMask.pattern(first); + // A run may start on a bulk-copyable narrow cell or on a wide pair. + const pair_start = first.content_tag == .codepoint and + first.wide == .wide and + !first.hyperlink; + if (!pair_start and !bulkCopyable(first)) return 0; + + // Patterns for each cell shape admitted to the run. + var proto = first; + proto.wide = .narrow; + const narrow_pattern = BulkRunMask.pattern(proto); + proto.wide = .wide; + const wide_pattern = BulkRunMask.pattern(proto); + proto.wide = .spacer_tail; + const tail_pattern = BulkRunMask.pattern(proto); // Only text cells can contain a placeholder; for bg color // tags the content bits are a color, so we skip the check for @@ -1919,28 +1933,50 @@ const ReflowCursor = struct { )); }; - var len: usize = 1; + var len: usize = 0; + outer: while (true) { + // Vectorized scan: check whole groups of narrow cells at + // once. If a group fully matches, the run extends by the + // whole group; otherwise fall through to the scalar loop + // below, which finds the exact end of the run within it + // or continues through a wide pair. + while (cells.len - len >= bulk_group_len) { + if (!BulkRunMask.eql(cells, len, narrow_pattern)) break; + if (check_placeholder and PlaceholderMask.eqlAny( + cells, + len, + placeholder_pattern, + )) break; + len += bulk_group_len; + } - // Vectorized scan: check whole groups of cells at once. If a - // group fully matches, the run extends by the whole group; - // otherwise fall through to the scalar loop below, which - // finds the exact end of the run within it. - while (cells.len - len >= bulk_group_len) { - if (!BulkRunMask.eql(cells, len, run_pattern)) break; - if (check_placeholder and PlaceholderMask.eqlAny( - cells, - len, - placeholder_pattern, - )) break; - len += bulk_group_len; - } + while (len < cells.len) { + const cell = cells[len]; + if (BulkRunMask.eqlScalar(cell, narrow_pattern)) { + if (check_placeholder and PlaceholderMask.eqlScalar( + cell, + placeholder_pattern, + )) break :outer; + len += 1; + continue; + } - while (len < cells.len) : (len += 1) { - if (!BulkRunMask.eqlScalar(cells[len], run_pattern)) break; - if (check_placeholder and PlaceholderMask.eqlScalar( - cells[len], - placeholder_pattern, - )) break; + // Wide pairs are consumed atomically so the run + // (or window) end can never split a pair. + if (len + 1 < cells.len and + BulkRunMask.eqlScalar(cell, wide_pattern) and + BulkRunMask.eqlScalar(cells[len + 1], tail_pattern)) + { + len += 2; + // Re-enter the vectorized loop: narrow groups + // commonly follow a stretch of pairs. + continue :outer; + } + + break :outer; + } + + break; } return len; @@ -18207,6 +18243,370 @@ test "PageList resize reflow less cols to wrap a wide char" { } } +test "PageList resize reflow less cols wide char bulk run" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 8, .rows = 1, .max_size = 0 }); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + // A full row of wide character pairs so the reflow takes + // the bulk run path. + for (0..4) |i| { + { + const rac = page.getRowAndCell(i * 2, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = @intCast(0x4E00 + i) } }, + .wide = .wide, + }; + } + { + const rac = page.getRowAndCell(i * 2 + 1, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 0 } }, + .wide = .spacer_tail, + }; + } + } + } + + // Resize to exactly two pairs per row: runs end on the row + // boundary with no spacer heads needed. + try s.resize(.{ .cols = 4, .reflow = true }); + try testing.expectEqual(@as(usize, 4), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..4) |i| { + const y = i / 2; + const x = (i % 2) * 2; + { + const rac = page.getRowAndCell(x, y); + try testing.expectEqual( + @as(u21, @intCast(0x4E00 + i)), + rac.cell.content.codepoint.data, + ); + try testing.expectEqual(pagepkg.Cell.Wide.wide, rac.cell.wide); + } + { + const rac = page.getRowAndCell(x + 1, y); + try testing.expectEqual(@as(u21, 0), rac.cell.content.codepoint.data); + try testing.expectEqual(pagepkg.Cell.Wide.spacer_tail, rac.cell.wide); + } + } + + { + const rac = page.getRowAndCell(0, 0); + try testing.expect(rac.row.wrap); + try testing.expect(!rac.row.wrap_continuation); + } + { + const rac = page.getRowAndCell(0, 1); + try testing.expect(!rac.row.wrap); + try testing.expect(rac.row.wrap_continuation); + } + } +} + +test "PageList resize reflow less cols wide char bulk run odd cols spacer head" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 8, .rows = 1, .max_size = 0 }); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..4) |i| { + { + const rac = page.getRowAndCell(i * 2, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = @intCast(0x4E00 + i) } }, + .wide = .wide, + }; + } + { + const rac = page.getRowAndCell(i * 2 + 1, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 0 } }, + .wide = .spacer_tail, + }; + } + } + } + + // Resize to an odd number of columns: the bulk run must stop a + // pair short of the row boundary and the slow path inserts a + // spacer head in the final column. + try s.resize(.{ .cols = 5, .reflow = true }); + try testing.expectEqual(@as(usize, 5), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..4) |i| { + const y = i / 2; + const x = (i % 2) * 2; + { + const rac = page.getRowAndCell(x, y); + try testing.expectEqual( + @as(u21, @intCast(0x4E00 + i)), + rac.cell.content.codepoint.data, + ); + try testing.expectEqual(pagepkg.Cell.Wide.wide, rac.cell.wide); + } + { + const rac = page.getRowAndCell(x + 1, y); + try testing.expectEqual(@as(u21, 0), rac.cell.content.codepoint.data); + try testing.expectEqual(pagepkg.Cell.Wide.spacer_tail, rac.cell.wide); + } + } + + { + const rac = page.getRowAndCell(4, 0); + try testing.expectEqual(@as(u21, 0), rac.cell.content.codepoint.data); + try testing.expectEqual(pagepkg.Cell.Wide.spacer_head, rac.cell.wide); + try testing.expect(rac.row.wrap); + } + { + const rac = page.getRowAndCell(4, 1); + try testing.expectEqual(pagepkg.Cell.Wide.narrow, rac.cell.wide); + try testing.expect(rac.row.wrap_continuation); + } + } +} + +test "PageList resize reflow less cols wide char bulk run mixed narrow round trip" { + const testing = std.testing; + const alloc = testing.allocator; + + // The emoji-in-prose shape: single wide pairs separated by + // narrow cells, which must all share a single bulk run. + const Shape = struct { cp: u21, wide: pagepkg.Cell.Wide }; + const shape: []const Shape = &.{ + .{ .cp = 0x4E00, .wide = .wide }, + .{ .cp = 0, .wide = .spacer_tail }, + .{ .cp = 'x', .wide = .narrow }, + .{ .cp = 0x4E01, .wide = .wide }, + .{ .cp = 0, .wide = .spacer_tail }, + .{ .cp = 'y', .wide = .narrow }, + .{ .cp = 0x4E02, .wide = .wide }, + .{ .cp = 0, .wide = .spacer_tail }, + .{ .cp = 'z', .wide = .narrow }, + }; + + var s = try init(alloc, .{ .cols = 9, .rows = 1, .max_size = 0 }); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + for (shape, 0..) |c, x| { + const rac = page.getRowAndCell(x, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = c.cp } }, + .wide = c.wide, + }; + } + } + + // Shrink: the run ends exactly on the row boundary after the + // second narrow cell. + try s.resize(.{ .cols = 6, .reflow = true }); + try testing.expectEqual(@as(usize, 6), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + for (shape[0..6], 0..) |c, x| { + const rac = page.getRowAndCell(x, 0); + try testing.expectEqual(c.cp, rac.cell.content.codepoint.data); + try testing.expectEqual(c.wide, rac.cell.wide); + } + for (shape[6..], 0..) |c, x| { + const rac = page.getRowAndCell(x, 1); + try testing.expectEqual(c.cp, rac.cell.content.codepoint.data); + try testing.expectEqual(c.wide, rac.cell.wide); + } + try testing.expect(page.getRowAndCell(0, 0).row.wrap); + try testing.expect(page.getRowAndCell(0, 1).row.wrap_continuation); + } + + // Grow back: the wrapped rows must rejoin into the original + // single-row layout. + try s.resize(.{ .cols = 9, .reflow = true }); + try testing.expectEqual(@as(usize, 9), s.cols); + try testing.expectEqual(@as(usize, 1), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + for (shape, 0..) |c, x| { + const rac = page.getRowAndCell(x, 0); + try testing.expectEqual(c.cp, rac.cell.content.codepoint.data); + try testing.expectEqual(c.wide, rac.cell.wide); + } + try testing.expect(!page.getRowAndCell(0, 0).row.wrap); + } +} + +test "PageList resize reflow less cols wide char bulk run styled" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 8, .rows = 1, .max_size = 0 }); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + // Create a style + const style: stylepkg.Style = .{ .flags = .{ .bold = true } }; + const style_id = try page.styles.add(page.memory, style); + + // Styled pairs: the tail shares the wide cell's style, as + // the print path writes them. + for (0..4) |i| { + { + const rac = page.getRowAndCell(i * 2, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = @intCast(0x4E00 + i) } }, + .wide = .wide, + .style_id = style_id, + }; + page.styles.use(page.memory, style_id); + } + { + const rac = page.getRowAndCell(i * 2 + 1, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 0 } }, + .wide = .spacer_tail, + .style_id = style_id, + }; + page.styles.use(page.memory, style_id); + } + } + + // We're over-counted by 1 because `add` implies `use`. + page.styles.release(page.memory, style_id); + } + + // Resize + try s.resize(.{ .cols = 4, .reflow = true }); + try testing.expectEqual(@as(usize, 4), s.cols); + try testing.expectEqual(@as(usize, 2), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + for (0..2) |y| { + for (0..4) |x| { + const rac = page.getRowAndCell(x, y); + const style_id = rac.cell.style_id; + try testing.expect(style_id != 0); + + const style = page.styles.get(page.memory, style_id); + try testing.expect(style.flags.bold); + try testing.expect(rac.row.styled); + try testing.expectEqual( + if (x % 2 == 0) pagepkg.Cell.Wide.wide else pagepkg.Cell.Wide.spacer_tail, + rac.cell.wide, + ); + } + } + } +} + +test "PageList resize reflow less cols wide char bulk run degenerate spacer tail style" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 4, .rows = 1, .max_size = 0 }); + defer s.deinit(); + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + // A styled wide cell whose tail does NOT share its style. + // This can't come from the print path, but the run scan must + // reject the pair (slow path) rather than rewrite the tail + // to the wide cell's style. + const style: stylepkg.Style = .{ .flags = .{ .bold = true } }; + const style_id = try page.styles.add(page.memory, style); + + { + const rac = page.getRowAndCell(0, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 0x4E00 } }, + .wide = .wide, + .style_id = style_id, + }; + } + { + const rac = page.getRowAndCell(1, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 0 } }, + .wide = .spacer_tail, + }; + } + { + const rac = page.getRowAndCell(2, 0); + rac.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 'x' } }, + }; + } + } + + // Resize + try s.resize(.{ .cols = 3, .reflow = true }); + try testing.expectEqual(@as(usize, 3), s.cols); + try testing.expectEqual(@as(usize, 1), s.totalRows()); + + { + try testing.expect(s.pages.first == s.pages.last); + const page = s.pages.first.?.page(); + + { + const rac = page.getRowAndCell(0, 0); + try testing.expectEqual(@as(u21, 0x4E00), rac.cell.content.codepoint.data); + try testing.expectEqual(pagepkg.Cell.Wide.wide, rac.cell.wide); + try testing.expect(rac.cell.style_id != 0); + const style = page.styles.get(page.memory, rac.cell.style_id); + try testing.expect(style.flags.bold); + } + { + const rac = page.getRowAndCell(1, 0); + try testing.expectEqual(pagepkg.Cell.Wide.spacer_tail, rac.cell.wide); + try testing.expectEqual(stylepkg.default_id, rac.cell.style_id); + } + { + const rac = page.getRowAndCell(2, 0); + try testing.expectEqual(@as(u21, 'x'), rac.cell.content.codepoint.data); + try testing.expectEqual(pagepkg.Cell.Wide.narrow, rac.cell.wide); + } + } +} + test "PageList resize reflow less cols to wrap a multi-codepoint grapheme with a spacer head" { const testing = std.testing; const alloc = testing.allocator; From d61920d80e0e6d2c2a058c96d1b916c7300ddab5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 20:23:50 -0700 Subject: [PATCH 055/392] lib-vt: disable logging in wasm release builds --- src/lib_vt.zig | 19 +++++++++---------- src/os/wasm/log.zig | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 66da515a8..3b66930f2 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -391,16 +391,15 @@ pub const std_options: std.Options = opts: { var options: std.Options = .{}; if (builtin.target.cpu.arch.isWasm()) { - // Wasm builds we specifically want to optimize for space with small - // releases so we bump up to warn. Everything else acts pretty normal. - options.log_level = switch (builtin.mode) { - .Debug => .debug, - .ReleaseSmall => .warn, - else => .info, - }; - - // Wasm doesn't have access to stdio so we have a custom log function. - options.logFn = @import("os/wasm/log.zig").log; + // In non-debug modes, we want to ship effectively no logging + // warn and lower add ~200KB at the time of this comment. + if (builtin.mode == .Debug) { + options.log_level = .debug; + options.logFn = @import("os/wasm/log.zig").log; + } else { + options.log_level = .err; + options.logFn = @import("os/wasm/log.zig").noop; + } } else if (terminal.options.c_abi) { // For C ABI builds, use a custom log function that dispatches to an // embedder-provided callback (or silently discards when none is set). diff --git a/src/os/wasm/log.zig b/src/os/wasm/log.zig index faa885c6e..5c5a8f66c 100644 --- a/src/os/wasm/log.zig +++ b/src/os/wasm/log.zig @@ -4,6 +4,22 @@ const wasm = @import("../wasm.zig"); // Use the correct implementation pub const log = Freestanding.log; +/// A log function that discards everything. Since the format string and +/// scope are comptime parameters, using this compiles out all logging +/// machinery: call sites, format strings, and the std.fmt code they +/// reference (~220KB in a ReleaseFast wasm build). +pub fn noop( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype, +) void { + _ = level; + _ = scope; + _ = format; + _ = args; +} + /// Freestanding implementation calls an extern "log" function. pub const Freestanding = struct { // The function std.log will call. From 51a4311ef18b0971c112967cf24a538a2c71ea36 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 20:44:18 -0700 Subject: [PATCH 056/392] terminal: clean up overzealous inlining --- src/terminal/Terminal.zig | 9 ++++++- src/terminal/formatter.zig | 50 +++++++++++++++----------------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index ce22ae084..e1729b461 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -498,7 +498,14 @@ pub fn printRepeat(self: *Terminal, count_req: usize) !void { // identical to calling print per codepoint: ineligible characters // or terminal states (insert mode, grapheme clustering, hyperlinks, // etc.) fall back to the per-codepoint print() path internally. - var buf: [4096]u32 = @splat(c); + // + // The buffer is filled with a runtime-bounded loop rather than + // `= @splat(c)`: a comptime-known 4096-element splat gets fully + // unrolled into ~33KB of consecutive stores (LLVM won't re-roll + // or vectorize it, see quirks_memset.zig), and it would fill the + // whole buffer even for the typical small repeat counts. + var buf: [4096]u32 = undefined; + for (buf[0..@min(remaining, buf.len)]) |*cp| cp.* = c; while (remaining > 0) { const n = @min(remaining, buf.len); try self.printSlice(buf[0..n]); diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index ab9edbffe..676a502dc 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -1268,32 +1268,17 @@ pub const PageFormatter = struct { if (style_id == invalid_style_id) break :fast; } - // Specialized on point tracking so that the common - // non-tracking case has zero per-cell overhead. - const consumed = if (self.point_map == null) - try self.writeCellRun( - emit, - false, - writer, - cells_subset[cell_i..], - x, - y, - style_id, - current_hyperlink_id, - &blank_cells, - ) - else - try self.writeCellRun( - emit, - true, - writer, - cells_subset[cell_i..], - x, - y, - style_id, - current_hyperlink_id, - &blank_cells, - ); + const consumed = try self.writeCellRun( + emit, + self.point_map != null, + writer, + cells_subset[cell_i..], + x, + y, + style_id, + current_hyperlink_id, + &blank_cells, + ); // Zero cells consumed means the first cell isn't // eligible for the fast path; handle it below. @@ -1579,10 +1564,15 @@ pub const PageFormatter = struct { /// Blank cell accounting matches the slow path: accumulated blanks /// are only materialized once a non-blank cell is found, and any /// remainder is written back to `blank_cells`. - fn writeCellRun( + // Deliberately not inlined: this is instantiated per emit format and + // inlining every copy into formatWithStateEmit's row loop bloats the + // binary. track_points is a runtime bool for the same reason: a + // comptime bool doubles the instantiation count for one predictable + // branch per emitted cell. + noinline fn writeCellRun( self: *const PageFormatter, comptime emit: Format, - comptime track_points: bool, + track_points: bool, writer: *std.Io.Writer, cells: []const Cell, run_x: size.CellCountInt, @@ -1666,7 +1656,7 @@ pub const PageFormatter = struct { // This cell produces output: materialize accumulated blanks. if (pending > 0) { - if (comptime track_points) try self.appendBlankPoints( + if (track_points) try self.appendBlankPoints( &self.point_map.?, pending, x, @@ -1718,7 +1708,7 @@ pub const PageFormatter = struct { } // All of the cell's bytes map to the cell's coordinate. - if (comptime track_points) { + if (track_points) { const map = &self.point_map.?; map.map.appendNTimes( map.alloc, From e84dd3015543d695e51993e02a01283cfdab2439 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 03:47:57 +0000 Subject: [PATCH 057/392] Update VOUCHED list (#13829) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13828#discussioncomment-18024891) from @jcollie. Vouch: @diego-moment Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 4410c9875..7b2f608a0 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -97,6 +97,7 @@ deblasis dervedro devsunb diaaeddin +diego-moment dkinzler dmehala dmunozv04 From 348f714ff97a4b323ee2ce195bb16387ba6a1dbe Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 04:55:55 +0000 Subject: [PATCH 058/392] Update VOUCHED list (#13833) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13831#discussioncomment-18025282) from @jcollie. Vouch: @DiegoArmstrong Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 7b2f608a0..2582ddff0 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -98,6 +98,7 @@ dervedro devsunb diaaeddin diego-moment +diegoarmstrong dkinzler dmehala dmunozv04 From 1fdbb8c912231bdbe039614a70f10772a3e50d23 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Aug 2026 21:02:46 -0700 Subject: [PATCH 059/392] libghostty: -Dvt-features to compile out unused features This introduces a `-Dvt-features` build option for libghostty-vt that compiles out optional feature areas, primarily so size-conscious embedders (e.g. wasm) can significantly trim the binary. The flag is similar to `-Dcpu`, `+feature` or `feature` to enable it, `-feature` to disable, magic word `all` to turn all features on or off. Example: `-Dvt-features=-all,+render-state` builds only the render state API. ### Sizes | Build | Bytes | Brotli | |---|---|---| | default (all features) | 876,500 | 218,309 | | web interactive (`-all,+render-state,+input-encode,+selection,+color,+grid-introspection`) | 661,119 | 168,994 | | read-only viewer (`-all,+render-state`) | 537,441 | 132,858 | | bare VT core (`-all`) | 515,422 | 125,756 | | xterm.js browser bundle (incl. renderers) | 488,663 | 99,311 | | @xterm/headless | 182,672 | 39,651 | Note: xterm versions are stable as of this commit. --- .github/workflows/test.yml | 47 +++++ PACKAGING.md | 21 ++ build.zig | 6 + src/build/Config.zig | 34 ++++ src/lib_vt.zig | 315 ++++++++++++++++-------------- src/terminal/Terminal.zig | 2 + src/terminal/apc.zig | 60 ++++-- src/terminal/build_options.zig | 254 ++++++++++++++++++++++-- src/terminal/c/kitty_graphics.zig | 2 + src/terminal/c/terminal.zig | 2 + src/terminal/stream_terminal.zig | 4 +- 11 files changed, 570 insertions(+), 177 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d73672a0f..e45793f22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -95,6 +95,7 @@ jobs: - build-flatpak - build-libghostty-vt - build-libghostty-vt-wasm + - build-libghostty-vt-features - build-libghostty-vt-android - build-libghostty-vt-macos - build-libghostty-vt-windows @@ -726,6 +727,52 @@ jobs: echo "Verified ${artifact} requires simd128" done + # Compile-only checks for the -Dvt-features flags so that future changes + # don't regress any feature combination. + build-libghostty-vt-features: + runs-on: namespace-profile-ghostty-sm + needs: test + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Compile feature flag matrix + run: | + features="snapshot formatter selection render_state input_encode color grid_introspection glyph_protocol kitty_graphics" + configs="-all" + for f in $features; do configs="$configs -all,+$f"; done + + for cfg in $configs; do + echo "::group::-Dvt-features=$cfg" + # The wasm lib build compiles the C API export block; the + # test build compiles every module test without running any. + nix develop -c zig build -Demit-lib-vt \ + "-Dvt-features=$cfg" \ + -Dtarget=wasm32-freestanding + nix develop -c zig build test-lib-vt-build \ + "-Dvt-features=$cfg" + echo "::endgroup::" + done + # lib-vt requires macOS runner for macOS/iOS builds because it requires the `apple_sdk` path build-libghostty-vt-macos: strategy: diff --git a/PACKAGING.md b/PACKAGING.md index ec2ef3dee..8cb422517 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -141,6 +141,27 @@ Some notes for packaging the wasm module: a large performance win for VT parsing. If you target an unusual runtime without SIMD support, opt out with `-Dcpu=generic`. +- Optional feature areas can be compiled out with `-Dvt-features` to + significantly reduce binary size. The flag takes comma-separated + modifications applied to the default all-enabled feature set, + `-Dcpu`-style: `+feature` (or bare `feature`) enables, `-feature` + disables, and the special name `all` refers to every feature. Hyphens + and underscores are interchangeable in feature names. For example, a + read-only terminal viewer only needs the render state API: + + ```sh + zig build -Demit-lib-vt -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseSmall -Dvt-features=-all,+render-state + ``` + + This roughly halves the compressed module size versus the default + build. An interactive terminal typically wants + `-Dvt-features=-all,+render-state,+input-encode,+selection,+color`. + Disabled features drop both their C API exports and any escape + sequence handling (the sequences are still consumed and safely + ignored). See the `Features` struct in `src/terminal/build_options.zig` + for the full list of features and what each one covers. + - `ReleaseSmall` is the recommended optimization mode for the web. Running the result through [Binaryen's](https://github.com/WebAssembly/binaryen) `wasm-opt -O3` shrinks it by roughly a further 10% without hurting diff --git a/build.zig b/build.zig index acf19ac91..1c56cadf8 100644 --- a/build.zig +++ b/build.zig @@ -69,6 +69,10 @@ pub fn build(b: *std.Build) !void { "test-lib-vt", "Run libghostty-vt tests", ); + const test_lib_vt_build_step = b.step( + "test-lib-vt-build", + "Build libghostty-vt tests without running them (compile check)", + ); const test_valgrind_step = b.step( "test-valgrind", "Run tests under valgrind", @@ -329,6 +333,7 @@ pub fn build(b: *std.Build) !void { }); const mod_vt_test_run = b.addRunArtifact(mod_vt_test); test_lib_vt_step.dependOn(&mod_vt_test_run.step); + test_lib_vt_build_step.dependOn(&mod_vt_test.step); const mod_vt_c_test = b.addTest(.{ .root_module = mod.vt_c, @@ -336,6 +341,7 @@ pub fn build(b: *std.Build) !void { }); const mod_vt_c_test_run = b.addRunArtifact(mod_vt_c_test); test_lib_vt_step.dependOn(&mod_vt_c_test_run.step); + test_lib_vt_build_step.dependOn(&mod_vt_c_test.step); } // Tests (skip when building libghostty-vt) diff --git a/src/build/Config.zig b/src/build/Config.zig index 09ab1a915..c90f3781b 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -62,6 +62,10 @@ emit_xcframework: bool = false, emit_webdata: bool = false, emit_unicode_table_gen: bool = false, +/// Feature gates for libghostty-vt artifacts (-Dvt-features). The full +/// Ghostty application ignores this and always enables everything. +vt_features: TerminalBuildOptions.Features = .{}, + /// True when Ghostty is being built as a dependency of another project /// rather than as the root project. is_dep: bool = false, @@ -421,6 +425,30 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf config.emit_lib_vt = emit_lib_vt; + config.vt_features = features: { + const list = b.option( + []const u8, + "vt-features", + "Comma-separated libghostty-vt feature modifications applied " ++ + "to the default all-enabled set, -Dcpu style: `+feature` " ++ + "or `feature` enables, `-feature` disables, and `all` " ++ + "means every feature (e.g. `-all,+render-state` for a " ++ + "render-only build). Only applies to lib artifacts.", + ) orelse break :features .{}; + break :features TerminalBuildOptions.Features.parse(list) catch { + var valid: std.ArrayList(u8) = .empty; + inline for (@typeInfo(TerminalBuildOptions.Features).@"struct".fields) |field| { + if (valid.items.len > 0) try valid.appendSlice(b.allocator, ", "); + try valid.appendSlice(b.allocator, field.name); + } + std.log.err( + "-Dvt-features={s} contains an unknown feature. Valid features: all, {s}", + .{ list, valid.items }, + ); + return error.UnknownVtFeature; + }; + }; + config.emit_exe = b.option( bool, "emit-exe", @@ -684,6 +712,12 @@ pub fn terminalOptions( .simd = self.simd, .oniguruma = true, .c_abi = false, + // The application requires every feature; only lib artifacts + // may trim them. + .features = switch (artifact) { + .ghostty => .{}, + .lib => self.vt_features, + }, .version = switch (artifact) { .ghostty => self.version, .lib => self.lib_version, diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 3b66930f2..f7ce5b386 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -180,44 +180,50 @@ comptime { _ = @import("quirks_memset.zig"); const c = terminal.c_api; - @export(&c.key_event_new, .{ .name = "ghostty_key_event_new" }); - @export(&c.key_event_free, .{ .name = "ghostty_key_event_free" }); - @export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" }); - @export(&c.key_event_get_action, .{ .name = "ghostty_key_event_get_action" }); - @export(&c.key_event_set_key, .{ .name = "ghostty_key_event_set_key" }); - @export(&c.key_event_get_key, .{ .name = "ghostty_key_event_get_key" }); - @export(&c.key_event_set_mods, .{ .name = "ghostty_key_event_set_mods" }); - @export(&c.key_event_get_mods, .{ .name = "ghostty_key_event_get_mods" }); - @export(&c.key_event_set_consumed_mods, .{ .name = "ghostty_key_event_set_consumed_mods" }); - @export(&c.key_event_get_consumed_mods, .{ .name = "ghostty_key_event_get_consumed_mods" }); - @export(&c.key_event_set_composing, .{ .name = "ghostty_key_event_set_composing" }); - @export(&c.key_event_get_composing, .{ .name = "ghostty_key_event_get_composing" }); - @export(&c.key_event_set_utf8, .{ .name = "ghostty_key_event_set_utf8" }); - @export(&c.key_event_get_utf8, .{ .name = "ghostty_key_event_get_utf8" }); - @export(&c.key_event_set_unshifted_codepoint, .{ .name = "ghostty_key_event_set_unshifted_codepoint" }); - @export(&c.key_event_get_unshifted_codepoint, .{ .name = "ghostty_key_event_get_unshifted_codepoint" }); - @export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" }); - @export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" }); - @export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" }); - @export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" }); - @export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" }); - @export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" }); - @export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" }); - @export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" }); - @export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" }); - @export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" }); - @export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" }); - @export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" }); - @export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" }); - @export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" }); - @export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" }); - @export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" }); - @export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" }); - @export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" }); - @export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" }); - @export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" }); - @export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" }); - @export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" }); + const features = terminal.options; + if (features.input_encode) { + @export(&c.key_event_new, .{ .name = "ghostty_key_event_new" }); + @export(&c.key_event_free, .{ .name = "ghostty_key_event_free" }); + @export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" }); + @export(&c.key_event_get_action, .{ .name = "ghostty_key_event_get_action" }); + @export(&c.key_event_set_key, .{ .name = "ghostty_key_event_set_key" }); + @export(&c.key_event_get_key, .{ .name = "ghostty_key_event_get_key" }); + @export(&c.key_event_set_mods, .{ .name = "ghostty_key_event_set_mods" }); + @export(&c.key_event_get_mods, .{ .name = "ghostty_key_event_get_mods" }); + @export(&c.key_event_set_consumed_mods, .{ .name = "ghostty_key_event_set_consumed_mods" }); + @export(&c.key_event_get_consumed_mods, .{ .name = "ghostty_key_event_get_consumed_mods" }); + @export(&c.key_event_set_composing, .{ .name = "ghostty_key_event_set_composing" }); + @export(&c.key_event_get_composing, .{ .name = "ghostty_key_event_get_composing" }); + @export(&c.key_event_set_utf8, .{ .name = "ghostty_key_event_set_utf8" }); + @export(&c.key_event_get_utf8, .{ .name = "ghostty_key_event_get_utf8" }); + @export(&c.key_event_set_unshifted_codepoint, .{ .name = "ghostty_key_event_set_unshifted_codepoint" }); + @export(&c.key_event_get_unshifted_codepoint, .{ .name = "ghostty_key_event_get_unshifted_codepoint" }); + @export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" }); + @export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" }); + @export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" }); + @export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" }); + @export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" }); + @export(&c.focus_encode, .{ .name = "ghostty_focus_encode" }); + @export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" }); + @export(&c.paste_encode, .{ .name = "ghostty_paste_encode" }); + @export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" }); + @export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" }); + @export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" }); + @export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" }); + @export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" }); + @export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" }); + @export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" }); + @export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" }); + @export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" }); + @export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" }); + @export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" }); + @export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" }); + @export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" }); + @export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" }); + @export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" }); + @export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" }); + @export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" }); + } @export(&c.osc_new, .{ .name = "ghostty_osc_new" }); @export(&c.osc_free, .{ .name = "ghostty_osc_free" }); @export(&c.osc_next, .{ .name = "ghostty_osc_next" }); @@ -226,10 +232,7 @@ comptime { @export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" }); @export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" }); @export(&c.color_scheme_report_encode, .{ .name = "ghostty_color_scheme_report_encode" }); - @export(&c.focus_encode, .{ .name = "ghostty_focus_encode" }); @export(&c.mode_report_encode, .{ .name = "ghostty_mode_report_encode" }); - @export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" }); - @export(&c.paste_encode, .{ .name = "ghostty_paste_encode" }); @export(&c.unicode_codepoint_width, .{ .name = "ghostty_unicode_codepoint_width" }); @export(&c.unicode_grapheme_width, .{ .name = "ghostty_unicode_grapheme_width" }); @export(&c.size_report_encode, .{ .name = "ghostty_size_report_encode" }); @@ -237,21 +240,25 @@ comptime { @export(&c.style_is_default, .{ .name = "ghostty_style_is_default" }); @export(&c.sys_log_stderr, .{ .name = "ghostty_sys_log_stderr" }); @export(&c.sys_set, .{ .name = "ghostty_sys_set" }); - @export(&c.cell_get, .{ .name = "ghostty_cell_get" }); - @export(&c.cell_get_multi, .{ .name = "ghostty_cell_get_multi" }); - @export(&c.row_get, .{ .name = "ghostty_row_get" }); - @export(&c.row_get_multi, .{ .name = "ghostty_row_get_multi" }); - @export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" }); - @export(&c.color_contrast, .{ .name = "ghostty_color_contrast" }); - @export(&c.color_luminance, .{ .name = "ghostty_color_luminance" }); - @export(&c.color_parse, .{ .name = "ghostty_color_parse" }); - @export(&c.color_parse_palette_entry, .{ .name = "ghostty_color_parse_palette_entry" }); - @export(&c.color_parse_x11, .{ .name = "ghostty_color_parse_x11" }); - @export(&c.color_palette_default, .{ .name = "ghostty_color_palette_default" }); - @export(&c.color_palette_generate, .{ .name = "ghostty_color_palette_generate" }); - @export(&c.color_perceived_luminance, .{ .name = "ghostty_color_perceived_luminance" }); - @export(&c.color_x11_name_count, .{ .name = "ghostty_color_x11_name_count" }); - @export(&c.color_x11_names, .{ .name = "ghostty_color_x11_names" }); + if (features.grid_introspection) { + @export(&c.cell_get, .{ .name = "ghostty_cell_get" }); + @export(&c.cell_get_multi, .{ .name = "ghostty_cell_get_multi" }); + @export(&c.row_get, .{ .name = "ghostty_row_get" }); + @export(&c.row_get_multi, .{ .name = "ghostty_row_get_multi" }); + } + if (features.color) { + @export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" }); + @export(&c.color_contrast, .{ .name = "ghostty_color_contrast" }); + @export(&c.color_luminance, .{ .name = "ghostty_color_luminance" }); + @export(&c.color_parse, .{ .name = "ghostty_color_parse" }); + @export(&c.color_parse_palette_entry, .{ .name = "ghostty_color_parse_palette_entry" }); + @export(&c.color_parse_x11, .{ .name = "ghostty_color_parse_x11" }); + @export(&c.color_palette_default, .{ .name = "ghostty_color_palette_default" }); + @export(&c.color_palette_generate, .{ .name = "ghostty_color_palette_generate" }); + @export(&c.color_perceived_luminance, .{ .name = "ghostty_color_perceived_luminance" }); + @export(&c.color_x11_name_count, .{ .name = "ghostty_color_x11_name_count" }); + @export(&c.color_x11_names, .{ .name = "ghostty_color_x11_names" }); + } @export(&c.sgr_new, .{ .name = "ghostty_sgr_new" }); @export(&c.sgr_free, .{ .name = "ghostty_sgr_free" }); @export(&c.sgr_reset, .{ .name = "ghostty_sgr_reset" }); @@ -261,33 +268,39 @@ comptime { @export(&c.sgr_unknown_partial, .{ .name = "ghostty_sgr_unknown_partial" }); @export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" }); @export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" }); - @export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" }); - @export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" }); - @export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" }); - @export(&c.formatter_free, .{ .name = "ghostty_formatter_free" }); - @export(&c.terminal_selection_format_buf, .{ .name = "ghostty_terminal_selection_format_buf" }); - @export(&c.terminal_selection_format_alloc, .{ .name = "ghostty_terminal_selection_format_alloc" }); - @export(&c.render_state_new, .{ .name = "ghostty_render_state_new" }); - @export(&c.render_state_update, .{ .name = "ghostty_render_state_update" }); - @export(&c.render_state_begin_update, .{ .name = "ghostty_render_state_begin_update" }); - @export(&c.render_state_end_update, .{ .name = "ghostty_render_state_end_update" }); - @export(&c.render_state_get, .{ .name = "ghostty_render_state_get" }); - @export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" }); - @export(&c.render_state_set, .{ .name = "ghostty_render_state_set" }); - @export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" }); - @export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" }); - @export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" }); - @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); - @export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" }); - @export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" }); - @export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" }); - @export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" }); - @export(&c.render_state_row_cells_next, .{ .name = "ghostty_render_state_row_cells_next" }); - @export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" }); - @export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" }); - @export(&c.render_state_row_cells_get_multi, .{ .name = "ghostty_render_state_row_cells_get_multi" }); - @export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" }); - @export(&c.render_state_free, .{ .name = "ghostty_render_state_free" }); + if (features.formatter) { + @export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" }); + @export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" }); + @export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" }); + @export(&c.formatter_free, .{ .name = "ghostty_formatter_free" }); + } + if (features.formatter and features.selection) { + @export(&c.terminal_selection_format_buf, .{ .name = "ghostty_terminal_selection_format_buf" }); + @export(&c.terminal_selection_format_alloc, .{ .name = "ghostty_terminal_selection_format_alloc" }); + } + if (features.render_state) { + @export(&c.render_state_new, .{ .name = "ghostty_render_state_new" }); + @export(&c.render_state_update, .{ .name = "ghostty_render_state_update" }); + @export(&c.render_state_begin_update, .{ .name = "ghostty_render_state_begin_update" }); + @export(&c.render_state_end_update, .{ .name = "ghostty_render_state_end_update" }); + @export(&c.render_state_get, .{ .name = "ghostty_render_state_get" }); + @export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" }); + @export(&c.render_state_set, .{ .name = "ghostty_render_state_set" }); + @export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" }); + @export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" }); + @export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" }); + @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); + @export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" }); + @export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" }); + @export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" }); + @export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" }); + @export(&c.render_state_row_cells_next, .{ .name = "ghostty_render_state_row_cells_next" }); + @export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" }); + @export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" }); + @export(&c.render_state_row_cells_get_multi, .{ .name = "ghostty_render_state_row_cells_get_multi" }); + @export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" }); + @export(&c.render_state_free, .{ .name = "ghostty_render_state_free" }); + } @export(&c.terminal_new, .{ .name = "ghostty_terminal_new" }); @export(&c.terminal_free, .{ .name = "ghostty_terminal_free" }); @export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" }); @@ -303,66 +316,82 @@ comptime { @export(&c.terminal_continuation_write, .{ .name = "ghostty_terminal_continuation_write" }); @export(&c.terminal_continuation_buf, .{ .name = "ghostty_terminal_continuation_buf" }); @export(&c.terminal_continuation_alloc, .{ .name = "ghostty_terminal_continuation_alloc" }); - @export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" }); - @export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" }); - @export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" }); - @export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" }); - @export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" }); - @export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" }); - @export(&c.terminal_selection_order, .{ .name = "ghostty_terminal_selection_order" }); - @export(&c.terminal_selection_ordered, .{ .name = "ghostty_terminal_selection_ordered" }); - @export(&c.terminal_selection_contains, .{ .name = "ghostty_terminal_selection_contains" }); - @export(&c.terminal_selection_equal, .{ .name = "ghostty_terminal_selection_equal" }); - @export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" }); - @export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" }); - @export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" }); - @export(&c.selection_gesture_event, .{ .name = "ghostty_selection_gesture_event" }); - @export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" }); - @export(&c.selection_gesture_get_multi, .{ .name = "ghostty_selection_gesture_get_multi" }); - @export(&c.selection_gesture_event_new, .{ .name = "ghostty_selection_gesture_event_new" }); - @export(&c.selection_gesture_event_free, .{ .name = "ghostty_selection_gesture_event_free" }); - @export(&c.selection_gesture_event_set, .{ .name = "ghostty_selection_gesture_event_set" }); - @export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" }); - @export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" }); - @export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" }); - @export(&c.snapshot_encode, .{ .name = "ghostty_snapshot_encode" }); - @export(&c.snapshot_encode_buf, .{ .name = "ghostty_snapshot_encode_buf" }); - @export(&c.snapshot_encode_alloc, .{ .name = "ghostty_snapshot_encode_alloc" }); - @export(&c.snapshot_decoder_new, .{ .name = "ghostty_snapshot_decoder_new" }); - @export(&c.snapshot_decoder_new_buf, .{ .name = "ghostty_snapshot_decoder_new_buf" }); - @export(&c.snapshot_decoder_free, .{ .name = "ghostty_snapshot_decoder_free" }); - @export(&c.snapshot_decoder_set, .{ .name = "ghostty_snapshot_decoder_set" }); - @export(&c.snapshot_decoder_get, .{ .name = "ghostty_snapshot_decoder_get" }); - @export(&c.snapshot_decoder_get_multi, .{ .name = "ghostty_snapshot_decoder_get_multi" }); - @export(&c.snapshot_decoder_ready, .{ .name = "ghostty_snapshot_decoder_ready" }); - @export(&c.snapshot_decoder_next, .{ .name = "ghostty_snapshot_decoder_next" }); - @export(&c.snapshot_decoder_decode, .{ .name = "ghostty_snapshot_decoder_decode" }); - @export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" }); - @export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" }); - @export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" }); - @export(&c.kitty_graphics_image_get_multi, .{ .name = "ghostty_kitty_graphics_image_get_multi" }); - @export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" }); - @export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" }); - @export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" }); - @export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" }); - @export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" }); - @export(&c.kitty_graphics_placement_get_multi, .{ .name = "ghostty_kitty_graphics_placement_get_multi" }); - @export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" }); - @export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" }); - @export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" }); - @export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" }); - @export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" }); - @export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" }); - @export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" }); - @export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" }); - @export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" }); - @export(&c.grid_ref_hyperlink_uri, .{ .name = "ghostty_grid_ref_hyperlink_uri" }); - @export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" }); - @export(&c.tracked_grid_ref_free, .{ .name = "ghostty_tracked_grid_ref_free" }); - @export(&c.tracked_grid_ref_has_value, .{ .name = "ghostty_tracked_grid_ref_has_value" }); - @export(&c.tracked_grid_ref_point, .{ .name = "ghostty_tracked_grid_ref_point" }); - @export(&c.tracked_grid_ref_set, .{ .name = "ghostty_tracked_grid_ref_set" }); - @export(&c.tracked_grid_ref_snapshot, .{ .name = "ghostty_tracked_grid_ref_snapshot" }); + if (features.selection) { + @export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" }); + @export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" }); + @export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" }); + @export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" }); + @export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" }); + @export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" }); + @export(&c.terminal_selection_order, .{ .name = "ghostty_terminal_selection_order" }); + @export(&c.terminal_selection_ordered, .{ .name = "ghostty_terminal_selection_ordered" }); + @export(&c.terminal_selection_contains, .{ .name = "ghostty_terminal_selection_contains" }); + @export(&c.terminal_selection_equal, .{ .name = "ghostty_terminal_selection_equal" }); + @export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" }); + @export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" }); + @export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" }); + @export(&c.selection_gesture_event, .{ .name = "ghostty_selection_gesture_event" }); + @export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" }); + @export(&c.selection_gesture_get_multi, .{ .name = "ghostty_selection_gesture_get_multi" }); + @export(&c.selection_gesture_event_new, .{ .name = "ghostty_selection_gesture_event_new" }); + @export(&c.selection_gesture_event_free, .{ .name = "ghostty_selection_gesture_event_free" }); + @export(&c.selection_gesture_event_set, .{ .name = "ghostty_selection_gesture_event_set" }); + } + // Selections are expressed in grid references, so the untracked + // reference constructors are required by both features. + if (features.grid_introspection or features.selection) { + @export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" }); + @export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" }); + } + if (features.grid_introspection) { + @export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" }); + } + if (features.snapshot) { + @export(&c.snapshot_encode, .{ .name = "ghostty_snapshot_encode" }); + @export(&c.snapshot_encode_buf, .{ .name = "ghostty_snapshot_encode_buf" }); + @export(&c.snapshot_encode_alloc, .{ .name = "ghostty_snapshot_encode_alloc" }); + @export(&c.snapshot_decoder_new, .{ .name = "ghostty_snapshot_decoder_new" }); + @export(&c.snapshot_decoder_new_buf, .{ .name = "ghostty_snapshot_decoder_new_buf" }); + @export(&c.snapshot_decoder_free, .{ .name = "ghostty_snapshot_decoder_free" }); + @export(&c.snapshot_decoder_set, .{ .name = "ghostty_snapshot_decoder_set" }); + @export(&c.snapshot_decoder_get, .{ .name = "ghostty_snapshot_decoder_get" }); + @export(&c.snapshot_decoder_get_multi, .{ .name = "ghostty_snapshot_decoder_get_multi" }); + @export(&c.snapshot_decoder_ready, .{ .name = "ghostty_snapshot_decoder_ready" }); + @export(&c.snapshot_decoder_next, .{ .name = "ghostty_snapshot_decoder_next" }); + @export(&c.snapshot_decoder_decode, .{ .name = "ghostty_snapshot_decoder_decode" }); + } + if (features.kitty_graphics) { + @export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" }); + @export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" }); + @export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" }); + @export(&c.kitty_graphics_image_get_multi, .{ .name = "ghostty_kitty_graphics_image_get_multi" }); + @export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" }); + @export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" }); + @export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" }); + @export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" }); + @export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" }); + @export(&c.kitty_graphics_placement_get_multi, .{ .name = "ghostty_kitty_graphics_placement_get_multi" }); + @export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" }); + @export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" }); + @export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" }); + @export(&c.kitty_graphics_placement_viewport_pos, .{ .name = "ghostty_kitty_graphics_placement_viewport_pos" }); + @export(&c.kitty_graphics_placement_source_rect, .{ .name = "ghostty_kitty_graphics_placement_source_rect" }); + @export(&c.kitty_graphics_placement_render_info, .{ .name = "ghostty_kitty_graphics_placement_render_info" }); + } + if (features.grid_introspection) { + @export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" }); + @export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" }); + @export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" }); + @export(&c.grid_ref_hyperlink_uri, .{ .name = "ghostty_grid_ref_hyperlink_uri" }); + @export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" }); + @export(&c.tracked_grid_ref_free, .{ .name = "ghostty_tracked_grid_ref_free" }); + @export(&c.tracked_grid_ref_has_value, .{ .name = "ghostty_tracked_grid_ref_has_value" }); + @export(&c.tracked_grid_ref_point, .{ .name = "ghostty_tracked_grid_ref_point" }); + @export(&c.tracked_grid_ref_set, .{ .name = "ghostty_tracked_grid_ref_set" }); + } + if (features.grid_introspection and features.snapshot) { + @export(&c.tracked_grid_ref_snapshot, .{ .name = "ghostty_tracked_grid_ref_snapshot" }); + } @export(&c.build_info, .{ .name = "ghostty_build_info" }); @export(&c.type_json, .{ .name = "ghostty_type_json" }); @export(&c.alloc_alloc, .{ .name = "ghostty_alloc" }); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index e1729b461..1e974cdd5 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -15873,6 +15873,8 @@ test "Terminal: deleteLines wide char at right margin with full clear" { } test "Terminal: glyph APC stores session glossary entries" { + if (comptime !build_options.glyph_protocol) return error.SkipZigTest; + const alloc = testing.allocator; const io_impl = testing.io; var t = try init(io_impl, alloc, .{ .cols = 80, .rows = 24 }); diff --git a/src/terminal/apc.zig b/src/terminal/apc.zig index 0da13b3b3..1ae70f5f1 100644 --- a/src/terminal/apc.zig +++ b/src/terminal/apc.zig @@ -82,12 +82,16 @@ pub const Handler = struct { if (byte == ';') { const str = id.buf[0..id.len]; if (std.mem.eql(u8, str, glyph.identifier)) { - if (self.enabled.contains(.glyph)) { - self.state = .{ .glyph = .init( - alloc, - self.max_bytes.get(.glyph) orelse - Protocol.defaultMaxBytes(.glyph), - ) }; + if (comptime build_options.glyph_protocol) { + if (self.enabled.contains(.glyph)) { + self.state = .{ .glyph = .init( + alloc, + self.max_bytes.get(.glyph) orelse + Protocol.defaultMaxBytes(.glyph), + ) }; + } else { + self.state = .ignore; + } } else { self.state = .ignore; } @@ -127,11 +131,13 @@ pub const Handler = struct { }; } else unreachable, - .glyph => |*p| p.feed(byte) catch |err| { - log.warn("glyph protocol error: {}", .{err}); - p.deinit(); - self.state = .ignore; - }, + .glyph => |*p| if (comptime build_options.glyph_protocol) { + p.feed(byte) catch |err| { + log.warn("glyph protocol error: {}", .{err}); + p.deinit(); + self.state = .ignore; + }; + } else unreachable, } } @@ -191,14 +197,14 @@ pub const Handler = struct { return; } else unreachable, - .glyph => |*p| { + .glyph => |*p| if (comptime build_options.glyph_protocol) { p.feedSlice(rem) catch |err| { log.warn("glyph protocol error: {}", .{err}); p.deinit(); self.state = .ignore; }; return; - }, + } else unreachable, } } } @@ -229,6 +235,8 @@ pub const Handler = struct { }, .glyph => |*p| glyph_cmd: { + if (comptime !build_options.glyph_protocol) unreachable; + const command = p.complete(p.alloc) catch |err| { log.warn("glyph protocol error: {}", .{err}); break :glyph_cmd null; @@ -269,7 +277,10 @@ pub const State = union(enum) { void, /// Glyph protocol - glyph: glyph.CommandParser, + glyph: if (build_options.glyph_protocol) + glyph.CommandParser + else + void, /// An unsupported APC retained for the optional unknown callback. /// Keep this after recognized protocol states so their tag values and @@ -280,7 +291,10 @@ pub const State = union(enum) { switch (self.*) { .inactive, .ignore, .identify => {}, .unknown => |*v| v.deinit(), - .glyph => |*v| v.deinit(), + .glyph => |*v| if (comptime build_options.glyph_protocol) + v.deinit() + else + unreachable, .kitty => |*v| if (comptime build_options.kitty_graphics) v.deinit() else @@ -415,7 +429,11 @@ pub const Command = union(enum) { else void, - glyph: glyph.Request, + glyph: if (build_options.glyph_protocol) + glyph.Request + else + void, + unknown: Unknown, pub fn deinit(self: *Command, alloc: Allocator) void { @@ -425,7 +443,11 @@ pub const Command = union(enum) { else unreachable, - .glyph => |*v| v.deinit(alloc), + .glyph => |*v| if (comptime build_options.glyph_protocol) + v.deinit(alloc) + else + unreachable, + .unknown => |*v| v.deinit(alloc), } } @@ -636,6 +658,8 @@ test "garbage glyph command" { } test "valid glyph command" { + if (comptime !build_options.glyph_protocol) return error.SkipZigTest; + const testing = std.testing; const alloc = testing.allocator; @@ -700,6 +724,8 @@ test "feedSlice unknown APC command is ignored" { } test "feedSlice valid glyph command" { + if (comptime !build_options.glyph_protocol) return error.SkipZigTest; + const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/build_options.zig b/src/terminal/build_options.zig index 136e0f101..124929d28 100644 --- a/src/terminal/build_options.zig +++ b/src/terminal/build_options.zig @@ -36,9 +36,227 @@ pub const Options = struct { /// Options. c_abi: bool, + /// Optional feature gates, all enabled by default. See Features. + features: Features = .{}, + /// The version of the application. version: std.SemanticVersion, + /// Optional features for lib artifacts. Disabling a feature removes + /// its C API exports and any stream-integrated handling from the + /// build, primarily so size-conscious embedders (e.g. wasm) can trim + /// the binary. The Ghostty application itself always builds with + /// every feature enabled. + /// + /// Field names are the accepted tokens for `-Dvt-features`, which + /// acts like `-Dcpu`: comma-separated modifications applied on top + /// of the default (all enabled) set. `+feature` and `feature` + /// enable, `-feature` disables. The special name `all` refers to + /// every feature, so `-Dvt-features=-all,+render-state` builds only + /// the render state API. Hyphens in names are interchangeable with + /// underscores. Fields are emitted verbatim as bool options on the + /// `terminal_options` module. + pub const Features = packed struct { + /// Terminal state serialization: encode a full terminal (screen + /// contents, scrollback, cursor, styles, modes, etc.) to a + /// compact binary format and decode it back into a live + /// terminal. Used for session persistence, window restoration, + /// and moving terminal state between processes. + /// + /// C API: `ghostty_snapshot_*` and, together with + /// `grid_introspection`, `ghostty_tracked_grid_ref_snapshot`. + snapshot: bool = true, + + /// Textual export of terminal contents as plain text, VT + /// (replayable escape sequences), or HTML, with optional extras + /// such as cursor position, palette, modes, and tabstops. Used + /// for copy-to-clipboard, dump-to-file, and debugging. + /// + /// C API: `ghostty_formatter_*` and, together with `selection`, + /// `ghostty_terminal_selection_format_*`. + formatter: bool = true, + + /// Text selection: select word/line/output/all, selection + /// ordering, adjustment, and containment math, and the + /// click/drag gesture state machine (single/double/triple + /// click behaviors). Selections are expressed in grid + /// references, so enabling this also keeps the untracked grid + /// reference constructors even if `grid_introspection` is + /// disabled. + /// + /// C API: `ghostty_terminal_select_*`, + /// `ghostty_terminal_selection_*`, `ghostty_selection_gesture_*`. + selection: bool = true, + + /// The render state API: a coherent, update-in-place view of + /// the visible screen (rows, cells, styles, cursor, colors, + /// palette) designed to drive a renderer at frame rates. This + /// is the primary read path for embedders that draw the + /// terminal. + /// + /// C API: `ghostty_render_state_*`. + render_state: bool = true, + + /// Encoding of host input events into the byte sequences a + /// terminal application expects: keyboard input (including the + /// Kitty keyboard protocol), mouse reporting, focus reporting, + /// and (bracketed) paste. Any interactive embedder needs this; + /// a read-only viewer does not. + /// + /// C API: `ghostty_key_event_*`, `ghostty_key_encoder_*`, + /// `ghostty_mouse_event_*`, `ghostty_mouse_encoder_*`, + /// `ghostty_focus_encode`, `ghostty_paste_*`. + input_encode: bool = true, + + /// Color utilities: luminance, perceived luminance, and + /// contrast math (e.g. for minimum-contrast rendering), color + /// string parsing (`#rrggbb`, X11 color names, XParseColor + /// `rgb:`/`rgbi:` device specs), the default 256-color palette + /// and palette generation from configurable bases, and the X11 + /// color name table itself. + /// + /// C API: `ghostty_color_*`. + color: bool = true, + + /// Direct inspection of grid contents at a position via grid + /// references: cell and row data getters (codepoints, + /// graphemes, style, hyperlink URI, wrap state, ...) plus + /// tracked grid references that stay valid across terminal + /// updates. Used for hyperlink hover/open, accessibility, and + /// tests; deliberately not built for render loops (use + /// `render_state` for that). + /// + /// C API: `ghostty_grid_ref_*`, `ghostty_tracked_grid_ref_*`, + /// `ghostty_cell_get*`, `ghostty_row_get*`, and (shared with + /// `selection`) `ghostty_terminal_grid_ref` / + /// `ghostty_terminal_point_from_grid_ref`. + grid_introspection: bool = true, + + /// The APC glyph protocol: a Ghostty extension that lets + /// terminal applications register custom font glyphs (glyf + /// outlines) for private-use-area codepoints. Embedders that + /// don't render registered glyphs can disable this; the + /// sequences are still consumed and safely ignored. + /// + /// No C API surface of its own; this gates the stream handling + /// and glossary storage. + glyph_protocol: bool = true, + + /// The Kitty graphics protocol: APC command parsing, image + /// transmission and storage (PNG decoding via the sys + /// interface), and placement tracking, plus the read APIs a + /// renderer uses to draw placed images. Disabled sequences are + /// still consumed and safely ignored. + /// + /// This requires the ability to get timestamps from the OS, so + /// it is always disabled on freestanding targets (e.g. + /// wasm32-freestanding) regardless of this setting. + /// + /// C API: `ghostty_kitty_graphics_*`. + kitty_graphics: bool = true, + + pub fn parse(list: []const u8) error{UnknownFeature}!Features { + // Modifications apply on top of the default set. + var result: Features = .{}; + + var it = std.mem.splitAny(u8, list, ", "); + while (it.next()) |raw| { + if (raw.len == 0) continue; + + var name = raw; + var enable = true; + switch (name[0]) { + '+' => name = name[1..], + '-' => { + enable = false; + name = name[1..]; + }, + else => {}, + } + + // A dangling sign with no name is malformed. + if (name.len == 0) return error.UnknownFeature; + + // The special name "all" refers to every feature. + if (std.mem.eql(u8, name, "all")) { + inline for (@typeInfo(Features).@"struct".fields) |field| { + @field(result, field.name) = enable; + } + continue; + } + + var found = false; + inline for (@typeInfo(Features).@"struct".fields) |field| { + if (eqlName(name, field.name)) { + @field(result, field.name) = enable; + found = true; + } + } + if (!found) return error.UnknownFeature; + } + + return result; + } + + /// Feature name equality where '-' in the input matches '_' in + /// the field name. + fn eqlName(input: []const u8, name: []const u8) bool { + if (input.len != name.len) return false; + for (input, name) |a, b| { + const norm: u8 = if (a == '-') '_' else a; + if (norm != b) return false; + } + return true; + } + + test "parse" { + const testing = std.testing; + + const none: Features = @bitCast( + @as(@typeInfo(Features).@"struct".backing_integer.?, 0), + ); + + // Empty input keeps the defaults. + try testing.expectEqual(Features{}, try Features.parse("")); + + // All three token forms, comma separated. + try testing.expectEqual( + Features{ .snapshot = false }, + try Features.parse("-snapshot"), + ); + try testing.expectEqual( + Features{ .snapshot = false, .formatter = false }, + try Features.parse("-snapshot,+render_state,-formatter,color"), + ); + + // The special "all" name refers to every feature. + try testing.expectEqual(none, try Features.parse("-all")); + try testing.expectEqual(Features{}, try Features.parse("-all,+all")); + var render_only = none; + render_only.render_state = true; + try testing.expectEqual( + render_only, + try Features.parse("-all,+render-state"), + ); + + // Hyphens are interchangeable with underscores. + try testing.expectEqual( + Features{ .input_encode = false, .grid_introspection = false }, + try Features.parse("-input-encode,-grid_introspection"), + ); + + // Later tokens win. + try testing.expectEqual( + Features{}, + try Features.parse("-snapshot,+snapshot"), + ); + + // Unknown names and dangling signs are errors. + try testing.expectError(error.UnknownFeature, Features.parse("bogus")); + try testing.expectError(error.UnknownFeature, Features.parse("-snapshot,-")); + } + }; + /// Add the required build options for the terminal module. /// /// The memory referenced by self is expected to stick around (it isn't @@ -55,25 +273,25 @@ pub const Options = struct { opts.addOption(bool, "simd", self.simd); opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety); - // Kitty graphics is almost always true. This used to be conditional on - // some other factors but we've since generalized the implementation - // to support optional PNG decoding, OS capabilities like filesystems, - // etc. So its safe to always enable it and just have the - // implementation deal with unsupported features as needed. - // - // We disable it on wasm32-freestanding because we at the least - // require the ability to get timestamps and there is no way to - // do that with freestanding targets. - const target = m.resolved_target.?.result; - opts.addOption( - bool, - "kitty_graphics", - !(target.cpu.arch == .wasm32 and target.os.tag == .freestanding), - ); - // These are synthesized based on other options. opts.addOption(bool, "tmux_control_mode", self.oniguruma); + // Feature gates, emitted as flat bools (e.g. `options.snapshot`). + const target = m.resolved_target.?.result; + inline for (@typeInfo(Features).@"struct".fields) |field| { + var value = @field(self.features, field.name); + + // Kitty graphics requires the ability to get timestamps and + // there is no way to do that on freestanding targets, so it + // is always disabled there regardless of the feature setting. + if (comptime std.mem.eql(u8, field.name, "kitty_graphics")) { + if (target.cpu.arch == .wasm32 and target.os.tag == .freestanding) + value = false; + } + + opts.addOption(bool, field.name, value); + } + // Version information. opts.addOption( []const u8, @@ -92,3 +310,7 @@ pub const Options = struct { m.addOptions("terminal_options", opts); } }; + +test { + _ = Options.Features; +} diff --git a/src/terminal/c/kitty_graphics.zig b/src/terminal/c/kitty_graphics.zig index 396b463fb..e27dd5db2 100644 --- a/src/terminal/c/kitty_graphics.zig +++ b/src/terminal/c/kitty_graphics.zig @@ -639,6 +639,8 @@ fn computeViewportPos( } test "placement_iterator new/free" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + var iter: PlacementIterator = null; try testing.expectEqual(Result.success, placement_iterator_new( &lib.alloc.test_allocator, diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 24c5edb39..7dcca5f02 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -5405,6 +5405,8 @@ test "set color sets dirty flag" { } test "set glyph protocol disables APC handling and clears glossary" { + if (comptime !build_options.glyph_protocol) return error.SkipZigTest; + var t: Terminal = null; try testing.expectEqual(Result.success, new( &lib.alloc.test_allocator, diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index 2599097af..91ad59be4 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -1054,7 +1054,7 @@ pub const Handler = struct { } }, - .glyph => |*glyph_req| { + .glyph => |*glyph_req| if (comptime build_options.glyph_protocol) { const resp = self.terminal.glyphProtocol(alloc, glyph_req); if (resp) |r| resp_block: { // Don't waste time encoding if we can't write responses @@ -1824,6 +1824,8 @@ test "full reset" { } test "glyph protocol APC with write_pty callback" { + if (comptime !build_options.glyph_protocol) return error.SkipZigTest; + var t: Terminal = try .init(testing.io, testing.allocator, .{ .cols = 80, .rows = 24 }); defer t.deinit(testing.allocator); From 53c6fdbe7d53eb8c61f7af5e311d04956c4fe283 Mon Sep 17 00:00:00 2001 From: "dolzhenko.e4" Date: Sat, 15 Aug 2026 12:33:30 +0300 Subject: [PATCH 060/392] apprt: own desktop notification truncation Make the fixed-size desktop notification payload a named Message type and initialize it through a constructor. Keeping UTF-8 boundary truncation with the payload owns the buffer capacities and sentinel termination at the message boundary, while stream_handler only forwards the title and body. --- src/apprt/surface.zig | 124 ++++++++++++++++++++++++++++++++-- src/termio/stream_handler.zig | 88 +----------------------- 2 files changed, 120 insertions(+), 92 deletions(-) diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index 3cb0016fa..4a0b8b377 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -16,6 +16,47 @@ pub const Message = union(enum) { /// we want this union to be. pub const WriteReq = MessageData(u8, 255); + /// A fixed-size desktop notification payload sent to the app thread. + pub const DesktopNotification = struct { + /// Desktop notification title. + title: [63:0]u8, + + /// Desktop notification body. + body: [255:0]u8, + + pub fn init(title: []const u8, body: []const u8) DesktopNotification { + var result: DesktopNotification = undefined; + copyUtf8Z(result.title.len, &result.title, title); + copyUtf8Z(result.body.len, &result.body, body); + return result; + } + + /// UTF-8 continuation bytes occupy the range 0x80 through 0xBF. + fn isUtf8ContinuationByte(byte: u8) bool { + return switch (byte) { + 0x80...0xBF => true, + else => false, + }; + } + + /// Copy as much of `src` as fits, backing up from a UTF-8 continuation + /// byte so valid input is never truncated in the middle of a codepoint. + fn copyUtf8Z( + comptime capacity: usize, + dst: *[capacity:0]u8, + src: []const u8, + ) void { + var len = @min(src.len, capacity); + while (len > 0 and + len < src.len and + isUtf8ContinuationByte(src[len])) : (len -= 1) + {} + + @memcpy(dst[0..len], src[0..len]); + dst[len] = 0; + } + }; + /// Set the title of the surface. /// TODO: we should change this to a "WriteReq" style structure in /// the termio message so that we can more efficiently send strings @@ -52,13 +93,7 @@ pub const Message = union(enum) { child_exited: ChildExited, /// Show a desktop notification. - desktop_notification: struct { - /// Desktop notification title. - title: [63:0]u8, - - /// Desktop notification body. - body: [255:0]u8, - }, + desktop_notification: DesktopNotification, /// Health status change for the renderer. renderer_health: renderer.Health, @@ -195,3 +230,78 @@ pub fn newConfig( return copy; } + +test "DesktopNotification init" { + const notification = Message.DesktopNotification.init("Title", "Body"); + + try std.testing.expectEqualStrings("Title", std.mem.sliceTo(¬ification.title, 0)); + try std.testing.expectEqualStrings("Body", std.mem.sliceTo(¬ification.body, 0)); +} + +test "copyUtf8Z handles len at the final byte of every UTF-8 sequence length" { + const DesktopNotification = Message.DesktopNotification; + + var dst_1_byte: [1:0]u8 = undefined; + const src_ending_in_1_byte_codepoint = "ab"; + try std.testing.expect(!DesktopNotification.isUtf8ContinuationByte( + src_ending_in_1_byte_codepoint[dst_1_byte.len], + )); + DesktopNotification.copyUtf8Z( + dst_1_byte.len, + &dst_1_byte, + src_ending_in_1_byte_codepoint, + ); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_1_byte, 0)); + + var dst_2_bytes: [2:0]u8 = undefined; + const src_ending_in_2_byte_codepoint = "aЯ"; + try std.testing.expect(DesktopNotification.isUtf8ContinuationByte( + src_ending_in_2_byte_codepoint[dst_2_bytes.len], + )); + DesktopNotification.copyUtf8Z( + dst_2_bytes.len, + &dst_2_bytes, + src_ending_in_2_byte_codepoint, + ); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_2_bytes, 0)); + + var dst_3_bytes: [3:0]u8 = undefined; + const src_ending_in_3_byte_codepoint = "a€"; + try std.testing.expect(DesktopNotification.isUtf8ContinuationByte( + src_ending_in_3_byte_codepoint[dst_3_bytes.len], + )); + DesktopNotification.copyUtf8Z( + dst_3_bytes.len, + &dst_3_bytes, + src_ending_in_3_byte_codepoint, + ); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_3_bytes, 0)); + + var dst_4_bytes: [4:0]u8 = undefined; + const src_ending_in_4_byte_codepoint = "a😀"; + try std.testing.expect(DesktopNotification.isUtf8ContinuationByte( + src_ending_in_4_byte_codepoint[dst_4_bytes.len], + )); + DesktopNotification.copyUtf8Z( + dst_4_bytes.len, + &dst_4_bytes, + src_ending_in_4_byte_codepoint, + ); + try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_4_bytes, 0)); +} + +test "copyUtf8Z keeps a complete codepoint at the truncation boundary" { + var dst: [5:0]u8 = undefined; + + Message.DesktopNotification.copyUtf8Z(dst.len, &dst, "abcЯz"); + + try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); +} + +test "copyUtf8Z preserves UTF-8 that fits" { + var dst: [5:0]u8 = undefined; + + Message.DesktopNotification.copyUtf8Z(dst.len, &dst, "abcЯ"); + + try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); +} diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 1267649fc..cf6bca909 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -16,27 +16,6 @@ const posix = std.posix; const log = std.log.scoped(.io_handler); -/// UTF-8 continuation bytes occupy the range 0x80 through 0xBF. -fn isUtf8ContinuationByte(byte: u8) bool { - return switch (byte) { - 0x80...0xBF => true, - else => false, - }; -} - -/// Copy as much of `src` as fits, backing up from a UTF-8 continuation byte -/// so valid input is never truncated in the middle of a codepoint. -fn copyUtf8Z(comptime capacity: usize, dst: *[capacity:0]u8, src: []const u8) void { - var len = @min(src.len, capacity); - while (len > 0 and - len < src.len and - isUtf8ContinuationByte(src[len])) : (len -= 1) - {} - - @memcpy(dst[0..len], src[0..len]); - dst[len] = 0; -} - /// This is used as the handler for the terminal.Stream type. This is /// stateful and is expected to live for the entire lifetime of the terminal. /// It is NOT VALID to stop a stream handler, create a new one, and use that @@ -1367,20 +1346,9 @@ pub const StreamHandler = struct { title: []const u8, body: []const u8, ) !void { - var message = apprt.surface.Message{ .desktop_notification = undefined }; - - copyUtf8Z( - message.desktop_notification.title.len, - &message.desktop_notification.title, - title, - ); - copyUtf8Z( - message.desktop_notification.body.len, - &message.desktop_notification.body, - body, - ); - - self.surfaceMessageWriter(message); + self.surfaceMessageWriter(.{ + .desktop_notification = .init(title, body), + }); } /// Send a report to the pty. @@ -1490,53 +1458,3 @@ pub const StreamHandler = struct { self.surfaceMessageWriter(.{ .progress_report = report }); } }; - -test "copyUtf8Z handles len at the final byte of every UTF-8 sequence length" { - var dst_1_byte: [1:0]u8 = undefined; - const src_ending_in_1_byte_codepoint = "ab"; - try std.testing.expect(!isUtf8ContinuationByte( - src_ending_in_1_byte_codepoint[dst_1_byte.len], - )); - copyUtf8Z(dst_1_byte.len, &dst_1_byte, src_ending_in_1_byte_codepoint); - try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_1_byte, 0)); - - var dst_2_bytes: [2:0]u8 = undefined; - const src_ending_in_2_byte_codepoint = "aЯ"; - try std.testing.expect(isUtf8ContinuationByte( - src_ending_in_2_byte_codepoint[dst_2_bytes.len], - )); - copyUtf8Z(dst_2_bytes.len, &dst_2_bytes, src_ending_in_2_byte_codepoint); - try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_2_bytes, 0)); - - var dst_3_bytes: [3:0]u8 = undefined; - const src_ending_in_3_byte_codepoint = "a€"; - try std.testing.expect(isUtf8ContinuationByte( - src_ending_in_3_byte_codepoint[dst_3_bytes.len], - )); - copyUtf8Z(dst_3_bytes.len, &dst_3_bytes, src_ending_in_3_byte_codepoint); - try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_3_bytes, 0)); - - var dst_4_bytes: [4:0]u8 = undefined; - const src_ending_in_4_byte_codepoint = "a😀"; - try std.testing.expect(isUtf8ContinuationByte( - src_ending_in_4_byte_codepoint[dst_4_bytes.len], - )); - copyUtf8Z(dst_4_bytes.len, &dst_4_bytes, src_ending_in_4_byte_codepoint); - try std.testing.expectEqualStrings("a", std.mem.sliceTo(&dst_4_bytes, 0)); -} - -test "copyUtf8Z keeps a complete codepoint at the truncation boundary" { - var dst: [5:0]u8 = undefined; - - copyUtf8Z(dst.len, &dst, "abcЯz"); - - try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); -} - -test "copyUtf8Z preserves UTF-8 that fits" { - var dst: [5:0]u8 = undefined; - - copyUtf8Z(dst.len, &dst, "abcЯ"); - - try std.testing.expectEqualStrings("abcЯ", std.mem.sliceTo(&dst, 0)); -} From 946422dbc5832bb1d8e3298e339c89577da86bca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 06:43:17 -0700 Subject: [PATCH 061/392] terminal: replace std.fmt.parseFloat with custom fraction parsing We have exactly two callers of `parseFloat` and they both have very limited expect input shapes: values 0-1, simple decimals. parseFloat brings in ~26KB of binary size, so replace it with a custom parser for our exact shape. --- src/terminal/apc/glyph/request.zig | 5 +- src/terminal/color.zig | 7 +- src/terminal/fraction.zig | 142 +++++++++++++++++++++++++++++ src/terminal/main.zig | 1 + 4 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 src/terminal/fraction.zig diff --git a/src/terminal/apc/glyph/request.zig b/src/terminal/apc/glyph/request.zig index a45feca28..7bf4b6ba9 100644 --- a/src/terminal/apc/glyph/request.zig +++ b/src/terminal/apc/glyph/request.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Glyf = @import("../../../font/opentype/glyf.zig").Glyf; +const fraction = @import("../../fraction.zig"); /// Maximum decoded glyph payload size accepted by the protocol. /// This is documented in the spec. @@ -563,9 +564,7 @@ pub const Pad = struct { /// Top/bottom fractions are relative to cell height; left/right fractions /// are relative to render span width. fn parseFraction(value: []const u8) ?f64 { - const result = std.fmt.parseFloat(f64, value) catch return null; - if (!(result >= 0 and result <= 1)) return null; - return result; + return fraction.parse(value); } }; diff --git a/src/terminal/color.zig b/src/terminal/color.zig index 3df9a0e6a..ce4433bbc 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -3,6 +3,7 @@ const colorpkg = @This(); const std = @import("std"); const builtin = @import("builtin"); const assert = @import("../quirks.zig").inlineAssert; +const fraction = @import("fraction.zig"); const x11_color = @import("x11_color.zig"); /// The default palette. @@ -641,14 +642,10 @@ pub const RGB = packed struct(u24) { /// /// The value should be between 0.0 and 1.0, inclusive. fn fromIntensity(value: []const u8) error{InvalidFormat}!u8 { - const i = std.fmt.parseFloat(f64, value) catch { + const i = fraction.parse(value) orelse { @branchHint(.cold); return error.InvalidFormat; }; - if (i < 0.0 or i > 1.0) { - @branchHint(.cold); - return error.InvalidFormat; - } return @intFromFloat(i * std.math.maxInt(u8)); } diff --git a/src/terminal/fraction.zig b/src/terminal/fraction.zig new file mode 100644 index 000000000..671344195 --- /dev/null +++ b/src/terminal/fraction.zig @@ -0,0 +1,142 @@ +//! Parsing of decimal fractions in the unit interval [0, 1]. +//! +//! Escape sequences that carry fractional parameters (XTerm `rgbi:` +//! color specifications, glyph protocol padding) only ever need plain +//! decimal values such as "0.5". Using `std.fmt.parseFloat` for these +//! pulls the full correctly-rounded float parser and its lookup tables +//! into the binary (~26KB). This implements only the restricted subset +//! we need. +const std = @import("std"); +const assert = std.debug.assert; + +/// Parse a decimal fraction in the range [0, 1] inclusive. +/// +/// Accepts an optional leading `+` or `-` sign followed by decimal +/// digits with an optional decimal point ("0.5", ".5", "1.", "1"); at +/// least one digit is required. Exponent and hexadecimal float syntax +/// are not supported. Returns null for invalid syntax and for any value +/// outside [0, 1] (so negative values other than -0 are rejected). +/// +/// Results are correctly rounded for inputs of up to 15 fractional +/// digits; further digits are validated but ignored, which can move the +/// result by less than 1e-15. +pub fn parse(value: []const u8) ?f64 { + var v = value; + var negative = false; + if (v.len > 0) switch (v[0]) { + '+' => v = v[1..], + '-' => { + negative = true; + v = v[1..]; + }, + else => {}, + }; + + var i: usize = 0; + const int_part: f64 = int_part: { + var int_part: f64 = 0; + while (i < v.len and v[i] != '.') : (i += 1) { + const d = v[i]; + if (d < '0' or d > '9') return null; + int_part = int_part * 10 + @as(f64, @floatFromInt(d - '0')); + } + break :int_part int_part; + }; + + var digits: usize = i; + var frac: u64 = 0; + var scale: u64 = 1; + if (i < v.len) { + assert(v[i] == '.'); + i += 1; + while (i < v.len) : (i += 1) { + const d = v[i]; + if (d < '0' or d > '9') return null; + // Stop accumulating after 15 digits so that frac and scale + // both stay exactly representable in f64 (and cannot + // overflow), making the division below round only once. + if (scale < 1_000_000_000_000_000) { + frac = frac * 10 + (d - '0'); + scale *= 10; + } + digits += 1; + } + } + if (digits == 0) return null; + + // frac and scale are both exact in f64 (below 2^53 and a power of + // ten no greater than 1e15), so this division rounds only once. + const magnitude = int_part + + @as(f64, @floatFromInt(frac)) / @as(f64, @floatFromInt(scale)); + const result = if (negative) -magnitude else magnitude; + + // Range check. Written so that -0 passes and any out-of-range or + // non-finite value fails. + if (!(result >= 0 and result <= 1)) return null; + return result; +} + +test parse { + const testing = std.testing; + + // Plain values + try testing.expectEqual(0, parse("0").?); + try testing.expectEqual(1, parse("1").?); + try testing.expectEqual(0.5, parse("0.5").?); + try testing.expectEqual(0.25, parse("0.25").?); + try testing.expectEqual(1, parse("1.0").?); + try testing.expectEqual(1, parse("1.000000").?); + try testing.expectEqual(0, parse("0.0").?); + + // Partial forms + try testing.expectEqual(0.5, parse(".5").?); + try testing.expectEqual(1, parse("1.").?); + try testing.expectEqual(0, parse("00.00").?); + + // Signs + try testing.expectEqual(0.5, parse("+0.5").?); + try testing.expectEqual(0, parse("-0").?); + try testing.expectEqual(0, parse("-0.000").?); + try testing.expectEqual(null, parse("-0.5")); + try testing.expectEqual(null, parse("-1")); + + // Matches std.fmt.parseFloat rounding for typical inputs. + try testing.expectEqual( + std.fmt.parseFloat(f64, "0.3") catch unreachable, + parse("0.3").?, + ); + try testing.expectEqual( + std.fmt.parseFloat(f64, "0.123456789012345") catch unreachable, + parse("0.123456789012345").?, + ); + + // Digits past the 15th are ignored: long fractions stay finite and + // equal their 15-digit truncation. + try testing.expectEqual( + parse("0.333333333333333").?, + parse("0." ++ "3" ** 400).?, + ); + + // Out of range + try testing.expectEqual(null, parse("1.0000001")); + try testing.expectEqual(null, parse("2")); + try testing.expectEqual(null, parse("255")); + try testing.expectEqual(null, parse("1" ** 400)); + + // Invalid syntax + try testing.expectEqual(null, parse("")); + try testing.expectEqual(null, parse(".")); + try testing.expectEqual(null, parse("+")); + try testing.expectEqual(null, parse("-")); + try testing.expectEqual(null, parse("+.")); + try testing.expectEqual(null, parse("abc")); + try testing.expectEqual(null, parse("0.5x")); + try testing.expectEqual(null, parse("0x0.8")); + try testing.expectEqual(null, parse("0..5")); + try testing.expectEqual(null, parse("0.5.5")); + try testing.expectEqual(null, parse(" 0.5")); + try testing.expectEqual(null, parse("0.5 ")); + try testing.expectEqual(null, parse("1e-1")); + try testing.expectEqual(null, parse("nan")); + try testing.expectEqual(null, parse("inf")); +} diff --git a/src/terminal/main.zig b/src/terminal/main.zig index fb846aeff..55438dde4 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -90,6 +90,7 @@ test { // Internals _ = @import("bitmap_allocator.zig"); _ = @import("compress.zig"); + _ = @import("fraction.zig"); _ = @import("hash_map.zig"); _ = @import("mem.zig"); _ = @import("ref_counted_set.zig"); From f748b17e27f5ee089494044179dea1c493ce63cc Mon Sep 17 00:00:00 2001 From: phl Date: Sat, 15 Aug 2026 15:54:19 +0200 Subject: [PATCH 062/392] feat: expand tildes in config theme path to HOME When loading a theme from a path that includes a tilde: ``` theme="~/.cache/wal/colors-ghostty" ``` currently fails with the following error: ``` cannot include path separators unless it is an absolute path ``` This PR tries to expand the ~ of the path. If there is no ~ or expansion fails, it falls back to the unexpanded value. --- src/config/theme.zig | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/config/theme.zig b/src/config/theme.zig index a8c39cad9..0a3a8ecee 100644 --- a/src/config/theme.zig +++ b/src/config/theme.zig @@ -5,6 +5,8 @@ const internal_os = @import("../os/main.zig"); const cli = @import("../cli.zig"); const global = @import("../global.zig"); +const log = std.log.scoped(.config); + /// Location of possible themes. The order of this enum matters because it /// defines the priority of theme search (from top to bottom). pub const Location = enum { @@ -113,12 +115,40 @@ pub const LocationIterator = struct { /// will be added to the list and null will be returned. pub fn open( arena_alloc: Allocator, - theme: []const u8, + theme_: []const u8, diags: *cli.DiagnosticList, ) error{ OutOfMemory, Unexpected }!?struct { path: []const u8, file: std.Io.File, } { + var buf: [std.fs.max_path_bytes]u8 = undefined; + const theme = expanded: { + if (!std.mem.startsWith(u8, theme_, "~/")) + break :expanded theme_; + + var environ_map = global.environMap() catch |err| { + log.warn( + "error getting environment map when expanding theme path={s}: {}", + .{ theme_, err }, + ); + break :expanded theme_; + }; + defer environ_map.deinit(); + + break :expanded internal_os.expandHome( + global.io(), + &environ_map, + theme_, + &buf, + ) catch |err| { + log.warn( + "error expanding home directory for theme path={s}: {}", + .{ theme_, err }, + ); + break :expanded theme_; + }; + }; + // Absolute themes are loaded a different path. if (std.fs.path.isAbsolute(theme)) { const file: std.Io.File = try openAbsolute( From cecf81678e47f967b0354acada67e69d229f436b Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:07:28 +0000 Subject: [PATCH 063/392] Update VOUCHED list (#13843) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13841#discussioncomment-18031969) from @jcollie. Vouch: @preiter93 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 2582ddff0..a6a3ae4b8 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -256,6 +256,7 @@ pluiedev pouwerkerk poweruser64 prakhar54-byte +preiter93 prihlop priyans-hu pssalman From fff9f62f38cfef56ada5239ea22555b35c1e063c Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 15 Aug 2026 10:48:08 -0400 Subject: [PATCH 064/392] cli: always install embedded SSH terminfo Remove the remote infocmp short-circuit from +ssh setup. The command already sends Ghostty's embedded terminfo source to tic, so accepting any existing entry leaves the payload unused and can preserve stale data. --- src/cli/ssh.zig | 11 ++++------- src/config/Config.zig | 7 +++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/cli/ssh.zig b/src/cli/ssh.zig index f20188d91..b3044766f 100644 --- a/src/cli/ssh.zig +++ b/src/cli/ssh.zig @@ -128,9 +128,9 @@ pub const Options = struct { /// forwarding to succeed. /// /// 2. **Terminfo install** (`--terminfo`). On the first connection to a -/// given destination, installs Ghostty's terminfo entry on the remote -/// host using `infocmp -x xterm-ghostty | ssh tic -x -` over a -/// shared `ControlMaster` connection. Successful installs are cached +/// given destination, installs Ghostty's embedded terminfo entry on the +/// remote host using `ssh tic -x -` over a shared `ControlMaster` +/// connection. Successful installs are cached /// (see `ghostty +ssh-cache`) so subsequent connections skip this /// step. When terminfo is successfully installed or already cached, /// `TERM` is set to `xterm-ghostty` instead of `xterm-256color`. @@ -491,20 +491,17 @@ fn installRemoteTerminfo( // the most common failure source) and inherit ssh's stderr so it // reaches the user's terminal. Other steps stay quiet either way. const remote_script = if (opts.verbose) - \\infocmp xterm-ghostty >/dev/null 2>&1 && exit 0 \\command -v tic >/dev/null 2>&1 || exit 1 \\mkdir -p ~/.terminfo 2>/dev/null && tic -x - && exit 0 \\exit 1 else - \\infocmp xterm-ghostty >/dev/null 2>&1 && exit 0 \\command -v tic >/dev/null 2>&1 || exit 1 \\mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0 \\exit 1 ; // Set up an SSH ControlMaster scoped to this single install: - // - ControlMaster=yes makes our client also act as the master, - // so `infocmp | ssh tic` runs over a single connection. + // - ControlMaster=yes makes our client also act as the master. // - ControlPersist=no tears the master down when our client // exits; no socket lingers on the remote side. const argv = try std.mem.concat(alloc, []const u8, &.{ diff --git a/src/config/Config.zig b/src/config/Config.zig index 814532b5d..993a4f6e1 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2931,10 +2931,9 @@ keybind: Keybinds = .{}, /// (Available since: 1.2.0) /// /// * `ssh-terminfo` - Enable automatic terminfo installation on remote hosts. -/// Attempts to install Ghostty's terminfo entry using `infocmp` and `tic` when -/// connecting to hosts that lack it. Requires `infocmp` to be available locally -/// and `tic` to be available on remote hosts. Once terminfo is installed on a -/// remote host, it will be automatically "cached" to avoid repeat installations. +/// Attempts to install Ghostty's embedded terminfo entry using `tic` on local +/// cache misses. Requires `tic` to be available on remote hosts. Successful +/// installations are cached locally to avoid repeat installations. /// If desired, the `+ssh-cache` CLI action can be used to manage the installation /// cache manually using various arguments. /// (Available since: 1.2.0) From 06b63c48594ea04d2a68dd8249250d1477722296 Mon Sep 17 00:00:00 2001 From: "Sl (Shahaf Levi)" Date: Sat, 15 Aug 2026 19:13:27 +0300 Subject: [PATCH 065/392] i18n: update Hebrew translations for v1.4 --- po/he.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/he.po b/po/he.po index 3d9b792fb..bd1c3399b 100644 --- a/po/he.po +++ b/po/he.po @@ -405,7 +405,7 @@ msgstr "העתקה ללוח ההעתקה" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." -msgstr "העתק/י את הטקסט הנבחר ללוח ההעתקה בפורמט טקסט פשוט ומעוצב." +msgstr "העתק/י את הטקסט הנבחר ללוח ההעתקה בפורמטים של טקסט פשוט או מעוצב." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" From 69b9abf09ebad2b11a6850a28271676f6bfeb108 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 15 Aug 2026 11:04:31 -0400 Subject: [PATCH 066/392] cli: version SSH terminfo cache entries Derive a version from the encoded Ghostty terminfo and require callers to pass it explicitly when reading or writing the SSH cache. Cache entries created for older or different payloads no longer suppress a required installation. --- src/cli/ssh-cache/DiskCache.zig | 51 ++++++++++++++++++++------------- src/cli/ssh.zig | 20 ++++++++----- src/cli/ssh_cache.zig | 2 ++ src/terminfo/ghostty.zig | 9 ++++++ src/terminfo/main.zig | 1 + 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/cli/ssh-cache/DiskCache.zig b/src/cli/ssh-cache/DiskCache.zig index bc5619d71..ea64dfe83 100644 --- a/src/cli/ssh-cache/DiskCache.zig +++ b/src/cli/ssh-cache/DiskCache.zig @@ -58,6 +58,7 @@ pub fn add( self: DiskCache, alloc: Allocator, key: []const u8, + version: []const u8, timestamp: i64, ) !void { if (!isValidCacheKey(key)) return error.InvalidCacheKey; @@ -111,16 +112,19 @@ pub fn add( // `deinitEntries` defer to walk. if (entries.getPtr(key)) |existing| { existing.timestamp = timestamp; + const version_copy = try alloc.dupe(u8, version); + alloc.free(existing.terminfo_version); + existing.terminfo_version = version_copy; } else { const key_copy = try alloc.dupe(u8, key); errdefer alloc.free(key_copy); - const terminfo_copy = try alloc.dupe(u8, "xterm-ghostty"); - errdefer alloc.free(terminfo_copy); + const version_copy = try alloc.dupe(u8, version); + errdefer alloc.free(version_copy); try entries.put(key_copy, .{ .hostname = key_copy, .timestamp = timestamp, - .terminfo_version = terminfo_copy, + .terminfo_version = version_copy, }); } @@ -225,12 +229,13 @@ pub fn prune( return expired.items.len; } -/// Check if a key exists in the cache. -/// Returns false if the cache file doesn't exist. +/// Check if a key with `version` exists in the cache. +/// Returns false if the cache file doesn't exist or the version doesn't match. pub fn contains( self: DiskCache, alloc: Allocator, key: []const u8, + version: []const u8, ) !bool { if (!isValidCacheKey(key)) return error.InvalidCacheKey; @@ -249,7 +254,8 @@ pub fn contains( var entries = try readEntries(alloc, file); defer deinitEntries(alloc, &entries); - return entries.contains(key); + const entry = entries.get(key) orelse return false; + return std.mem.eql(u8, entry.terminfo_version, version); } fn fixupPermissions(file: std.Io.File) !void { @@ -506,20 +512,25 @@ test "disk cache operations" { // Setup our cache. Adding the same key twice exercises both the new // and existing-entry paths. const cache: DiskCache = .{ .path = path }; - try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds()); - try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds()); - try testing.expect(try cache.contains(alloc, "example.com")); + try cache.add(alloc, "example.com", "v1", std.Io.Timestamp.now(testing.io, .real).toSeconds()); + try testing.expect(!try cache.contains(alloc, "example.com", "v2")); + try cache.add(alloc, "example.com", "v2", std.Io.Timestamp.now(testing.io, .real).toSeconds()); + try testing.expect(try cache.contains(alloc, "example.com", "v2")); // List var entries = try cache.list(alloc); - deinitEntries(alloc, &entries); + defer deinitEntries(alloc, &entries); + try testing.expectEqualStrings( + "v2", + entries.get("example.com").?.terminfo_version, + ); // Remove reports that it removed the entry, and a second remove of the // same key reports nothing to remove. try testing.expect(try cache.remove(alloc, "example.com")); try testing.expect(!try cache.remove(alloc, "example.com")); - try testing.expect(!(try cache.contains(alloc, "example.com"))); - try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds()); + try testing.expect(!(try cache.contains(alloc, "example.com", "v2"))); + try cache.add(alloc, "example.com", "v2", std.Io.Timestamp.now(testing.io, .real).toSeconds()); } test "disk cache cleans up temp files" { @@ -535,8 +546,8 @@ test "disk cache cleans up temp files" { defer alloc.free(cache_path); const cache: DiskCache = .{ .path = cache_path }; - try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds()); - try cache.add(alloc, "example.org", std.Io.Timestamp.now(testing.io, .real).toSeconds()); + try cache.add(alloc, "example.com", "v1", std.Io.Timestamp.now(testing.io, .real).toSeconds()); + try cache.add(alloc, "example.org", "v1", std.Io.Timestamp.now(testing.io, .real).toSeconds()); // Verify only the cache file exists and no temp files left behind var count: usize = 0; @@ -566,20 +577,20 @@ test "disk cache prune" { const day = std.time.s_per_day; const hour = std.time.s_per_hour; const now = std.Io.Timestamp.now(testing.io, .real).toSeconds(); - try cache.add(alloc, "recent.com", now - hour); - try cache.add(alloc, "old.com", now - 100 * day); + try cache.add(alloc, "recent.com", "v1", now - hour); + try cache.add(alloc, "old.com", "v1", now - 100 * day); // Prune entries older than 90 days: only old.com goes. try testing.expectEqual(@as(usize, 1), try cache.prune(alloc, 90 * day)); - try testing.expect(try cache.contains(alloc, "recent.com")); - try testing.expect(!try cache.contains(alloc, "old.com")); + try testing.expect(try cache.contains(alloc, "recent.com", "v1")); + try testing.expect(!try cache.contains(alloc, "old.com", "v1")); // Pruning again removes nothing. try testing.expectEqual(@as(usize, 0), try cache.prune(alloc, 90 * day)); // Sub-day granularity: a 30-minute max age prunes the hour-old entry. try testing.expectEqual(@as(usize, 1), try cache.prune(alloc, 30 * std.time.s_per_min)); - try testing.expect(!try cache.contains(alloc, "recent.com")); + try testing.expect(!try cache.contains(alloc, "recent.com", "v1")); } test "disk cache prune missing file" { @@ -707,7 +718,7 @@ test "disk cache add survives allocation failure" { ); const alloc = failing.allocator(); - if (cache.add(alloc, "user@example.com", 100)) |_| { + if (cache.add(alloc, "user@example.com", "v1", 100)) |_| { if (!failing.has_induced_failure) break; } else |err| { try testing.expectEqual(error.OutOfMemory, err); diff --git a/src/cli/ssh.zig b/src/cli/ssh.zig index b3044766f..ed4a79755 100644 --- a/src/cli/ssh.zig +++ b/src/cli/ssh.zig @@ -7,7 +7,7 @@ const diagnostics = @import("diagnostics.zig"); const Action = @import("ghostty.zig").Action; const DiskCache = @import("ssh_cache.zig").DiskCache; const internal_os = @import("../os/main.zig"); -const ghostty_terminfo = @import("../terminfo/main.zig").ghostty; +const terminfopkg = @import("../terminfo/main.zig"); const global = @import("../global.zig"); const log = std.log.scoped(.ssh); @@ -252,7 +252,11 @@ fn runInner( } else null; if (cache) |c| { - const cached = c.contains(alloc, dest) catch |err| cached: { + const cached = c.contains( + alloc, + dest, + terminfopkg.version, + ) catch |err| cached: { if (DiskCache.isFailure(err)) warnPrint( stderr, "unable to read the cache '{s}': {t}", @@ -313,10 +317,12 @@ fn runInner( // Attempt to cache (if needed) on a successful ssh execution. if (exit_code == 0) if (session.to_cache) |entry| { - if (entry.cache.add(alloc, entry.dest, std.Io.Timestamp.now( - global.io(), - .real, - ).toSeconds())) |_| { + if (entry.cache.add( + alloc, + entry.dest, + terminfopkg.version, + std.Io.Timestamp.now(global.io(), .real).toSeconds(), + )) |_| { verbosePrint(opts, stderr, "cache: wrote {s}", .{entry.dest}); } else |err| { if (DiskCache.isFailure(err)) { @@ -473,7 +479,7 @@ fn installRemoteTerminfo( ) !void { var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); - try ghostty_terminfo.encode(&buf.writer); + try terminfopkg.ghostty.encode(&buf.writer); const terminfo = buf.written(); // ControlPath is in TMPDIR with a short, random basename. ssh uses diff --git a/src/cli/ssh_cache.zig b/src/cli/ssh_cache.zig index f509f69d4..4142e1600 100644 --- a/src/cli/ssh_cache.zig +++ b/src/cli/ssh_cache.zig @@ -5,6 +5,7 @@ const args = @import("args.zig"); const global = @import("../global.zig"); const Action = @import("ghostty.zig").Action; const Duration = @import("../config.zig").Config.Duration; +const terminfopkg = @import("../terminfo/main.zig"); pub const Entry = @import("ssh-cache/Entry.zig"); pub const DiskCache = @import("ssh-cache/DiskCache.zig"); @@ -198,6 +199,7 @@ pub fn runInner( cache.add( alloc, dest, + terminfopkg.version, std.Io.Timestamp.now(global.io(), .real).toSeconds(), ) catch |err| switch (err) { error.InvalidCacheKey => { diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 2b7434cbf..ea64ffcf3 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -392,6 +392,15 @@ pub const ghostty: Source = .{ }, }; +/// A content-derived version of the encoded terminfo source. +pub const version = version: { + @setEvalBranchQuota(100_000); + var hashing: std.Io.Writer.Hashing(std.hash.Wyhash) = + .initHasher(.init(0), &.{}); + ghostty.encode(&hashing.writer) catch unreachable; + break :version std.fmt.comptimePrint("{x}", .{hashing.hasher.final()}); +}; + test "encode" { // Encode var buf: [1024 * 16]u8 = undefined; diff --git a/src/terminfo/main.zig b/src/terminfo/main.zig index c76c328cc..f6c709b91 100644 --- a/src/terminfo/main.zig +++ b/src/terminfo/main.zig @@ -6,6 +6,7 @@ //! extract this into a more full-featured library on its own. pub const ghostty = @import("ghostty.zig").ghostty; +pub const version = @import("ghostty.zig").version; pub const Source = @import("Source.zig"); test { From c1217342958b90ed3a25413c1616dfd2dd8cd1bf Mon Sep 17 00:00:00 2001 From: phl Date: Sat, 15 Aug 2026 20:04:12 +0200 Subject: [PATCH 067/392] move shell expand of theme from theme.zig to config.zig --- src/config/Config.zig | 52 ++++++++++++++++++++++++++++++++++++++++++- src/config/theme.zig | 32 +------------------------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 814532b5d..bb4e7a38d 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -9973,9 +9973,32 @@ pub const Theme = struct { // Trim our value const trimmed = std.mem.trim(u8, input, cli.args.whitespace); + // Expand our value + var buf: [std.fs.max_path_bytes]u8 = undefined; + const expanded = expanded: { + if (!std.mem.startsWith(u8, trimmed, "~/")) + break :expanded trimmed; + + var environ_map = global.environMap() catch |err| { + log.warn("error getting environment map for theme path {s}: {}", .{ trimmed, err }); + break :expanded trimmed; + }; + defer environ_map.deinit(); + + break :expanded internal_os.expandHome( + global.io(), + &environ_map, + trimmed, + &buf, + ) catch |err| { + log.warn("error expanding home directory in theme path {s}: {}", .{ trimmed, err }); + break :expanded trimmed; + }; + }; + // Set the value to the specified value directly. self.* = .{ - .light = try alloc.dupeZ(u8, trimmed), + .light = try alloc.dupeZ(u8, expanded), .dark = self.light, }; } @@ -10028,6 +10051,33 @@ pub const Theme = struct { try testing.expectEqualStrings("foo", v.dark); } + // Expand home + { + var environ_map = try testing.environ.createMap(alloc); + defer environ_map.deinit(); + + var home_buf: [std.fs.max_path_bytes]u8 = undefined; + const home = try internal_os.expandHome( + testing.io, + &environ_map, + "~/theme", + &home_buf, + ); + + var v: Theme = undefined; + try v.parseCLI(alloc, "~/theme/foo"); + + var expected_buf: [std.fs.max_path_bytes]u8 = undefined; + const expected = try std.fmt.bufPrint( + &expected_buf, + "{s}/theme/foo", + .{home}, + ); + + try testing.expectEqualStrings(expected, v.light); + try testing.expectEqualStrings(expected, v.dark); + } + // Light/dark { var v: Theme = undefined; diff --git a/src/config/theme.zig b/src/config/theme.zig index 0a3a8ecee..a8c39cad9 100644 --- a/src/config/theme.zig +++ b/src/config/theme.zig @@ -5,8 +5,6 @@ const internal_os = @import("../os/main.zig"); const cli = @import("../cli.zig"); const global = @import("../global.zig"); -const log = std.log.scoped(.config); - /// Location of possible themes. The order of this enum matters because it /// defines the priority of theme search (from top to bottom). pub const Location = enum { @@ -115,40 +113,12 @@ pub const LocationIterator = struct { /// will be added to the list and null will be returned. pub fn open( arena_alloc: Allocator, - theme_: []const u8, + theme: []const u8, diags: *cli.DiagnosticList, ) error{ OutOfMemory, Unexpected }!?struct { path: []const u8, file: std.Io.File, } { - var buf: [std.fs.max_path_bytes]u8 = undefined; - const theme = expanded: { - if (!std.mem.startsWith(u8, theme_, "~/")) - break :expanded theme_; - - var environ_map = global.environMap() catch |err| { - log.warn( - "error getting environment map when expanding theme path={s}: {}", - .{ theme_, err }, - ); - break :expanded theme_; - }; - defer environ_map.deinit(); - - break :expanded internal_os.expandHome( - global.io(), - &environ_map, - theme_, - &buf, - ) catch |err| { - log.warn( - "error expanding home directory for theme path={s}: {}", - .{ theme_, err }, - ); - break :expanded theme_; - }; - }; - // Absolute themes are loaded a different path. if (std.fs.path.isAbsolute(theme)) { const file: std.Io.File = try openAbsolute( From 36d8e3f77779939a4413ddcd72c05ab08aeae57d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 08:50:19 -0700 Subject: [PATCH 068/392] terminal/snapshot: slicing-by-16 software CRC32C The software CRC32C fallback (WebAssembly and any other target without a dedicated instruction) was the std byte-at-a-time table walk, which profiled at ~65% of snapshot encode and ~70% of decode self-time in V8. Replace it with slicing-by-16: sixteen bytes fold per iteration through comptime per-position tables, so the serial dependency advances one block at a time instead of one byte. The hardware backends (aarch64 CRC, x86_64 SSE4.2) are unchanged, so native is expected to be unaffected; its deltas below are run-to-run noise. Benchmarks: wasm is V8 (node 25), ReleaseFast + wasm-opt -O3, 80x24 terminal, 2 MiB VT corpus per workload, complete snapshot including scrollback, best-of-5. Native is aarch64 macOS, hyperfine mean, ghostty-bench +terminal-snapshot --loops=20. "base" is the parent commit. | wasm | encode base | encode | decode base | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 7.07 ms | 3.43 ms | 6.45 ms | 2.81 ms | | styled | 10.54 ms | 3.14 ms | 14.37 ms | 7.12 ms | | truecolor | 15.23 ms | 5.33 ms | 21.76 ms | 12.11 ms | | cjk | 25.41 ms | 5.95 ms | 30.83 ms | 12.04 ms | | grapheme | 27.67 ms | 14.18 ms | 27.00 ms | 13.16 ms | | native | mode | base | this | |--------|--------|--------:|--------:| | ascii | encode | 40.6 ms | 41.7 ms | | ascii | decode | 51.2 ms | 53.2 ms | | utf8 | encode | 45.2 ms | 47.2 ms | | utf8 | decode | 59.8 ms | 61.6 ms | --- src/crc32c.zig | 118 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/src/crc32c.zig b/src/crc32c.zig index 8b8ebd1b2..dbfca7ef2 100644 --- a/src/crc32c.zig +++ b/src/crc32c.zig @@ -4,8 +4,11 @@ //! lookup (as of Zig 0.16), which is more than an order of magnitude slower //! than the dedicated CRC32C instructions available on aarch64 (CRC //! extension) and x86_64 (SSE4.2). This module selects the best backend at -//! compile time and falls back to the standard library elsewhere, including -//! WebAssembly. +//! compile time. +//! +//! Targets without a dedicated instruction, such as WebAssembly, use a +//! slicing-by-16 table implementation that processes sixteen bytes per +//! iteration instead of one. //! //! The resulting value is identical across all backends: this is the //! iSCSI CRC32C parameter set (reflected, initial and final XOR @@ -14,10 +17,6 @@ const std = @import("std"); const builtin = @import("builtin"); -/// The standard-library implementation of the same parameter set. This is -/// both the portable fallback and the reference the tests compare against. -const Software = std.hash.crc.Crc32Iscsi; - const Backend = enum { aarch64_crc, x86_64_sse42, @@ -58,11 +57,7 @@ pub const Crc32c = struct { pub fn update(self: *Crc32c, bytes: []const u8) void { self.crc = switch (comptime backend) { .aarch64_crc, .x86_64_sse42 => updateHardware(self.crc, bytes), - .software => software: { - var crc: Software = .{ .crc = self.crc }; - crc.update(bytes); - break :software crc.crc; - }, + .software => Software.update(self.crc, bytes), }; } @@ -148,6 +143,101 @@ inline fn step(comptime T: type, crc: u32, value: T) u32 { }; } +/// The portable software backend, used by targets without a dedicated +/// CRC32C instruction. +const Software = struct { + /// The reflected CRC32C (Castagnoli) polynomial. + const reflected_poly: u32 = 0x82F63B78; + + /// Slicing tables: `tables[i][b]` is the CRC of byte `b` followed + /// by `i` zero bytes. Table zero is the classic one-byte-per-step table; + /// the higher tables let one iteration fold sixteen input bytes with + /// sixteen independent lookups instead of a sixteen-step dependency chain. + const tables: [16][256]u32 = tables: { + @setEvalBranchQuota(100_000); + var result: [16][256]u32 = undefined; + for (0..256) |n| { + var crc: u32 = n; + for (0..8) |_| { + crc = (crc >> 1) ^ (reflected_poly * (crc & 1)); + } + result[0][n] = crc; + } + for (1..16) |i| { + for (0..256) |n| { + const prev = result[i - 1][n]; + result[i][n] = (prev >> 8) ^ result[0][prev & 0xFF]; + } + } + break :tables result; + }; + + /// One update pass using slicing-by-16: each iteration XORs the running + /// CRC into the first of four little-endian words and folds all sixteen + /// bytes through per-position tables. The remainder finishes one byte per + /// step through table zero. + fn update(initial: u32, bytes: []const u8) u32 { + const t = &tables; + var crc = initial; + var remaining = bytes; + + while (remaining.len >= 16) : (remaining = remaining[16..]) { + const a = std.mem.readInt(u32, remaining[0..4], .little) ^ crc; + const b = std.mem.readInt(u32, remaining[4..8], .little); + const c = std.mem.readInt(u32, remaining[8..12], .little); + const d = std.mem.readInt(u32, remaining[12..16], .little); + crc = t[15][a & 0xFF] ^ t[14][(a >> 8) & 0xFF] ^ + t[13][(a >> 16) & 0xFF] ^ t[12][a >> 24] ^ + t[11][b & 0xFF] ^ t[10][(b >> 8) & 0xFF] ^ + t[9][(b >> 16) & 0xFF] ^ t[8][b >> 24] ^ + t[7][c & 0xFF] ^ t[6][(c >> 8) & 0xFF] ^ + t[5][(c >> 16) & 0xFF] ^ t[4][c >> 24] ^ + t[3][d & 0xFF] ^ t[2][(d >> 8) & 0xFF] ^ + t[1][(d >> 16) & 0xFF] ^ t[0][d >> 24]; + } + for (remaining) |byte| { + crc = (crc >> 8) ^ t[0][(crc ^ byte) & 0xFF]; + } + return crc; + } +}; + +/// The standard-library implementation of the same parameter set. This is +/// the reference the tests compare against. +const Reference = std.hash.crc.Crc32Iscsi; + +test "software slicing matches the standard library" { + // The selected backend may be hardware, so cover the sliced software + // path directly: every length around the sixteen-byte boundary, several + // alignments, and continuation across arbitrary split points. + var bytes: [512 + 19]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0x511C); + prng.random().bytes(&bytes); + + for (0..64 + 1) |len| { + for (0..4) |offset| { + const input = bytes[offset..][0..len]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(input); + try std.testing.expectEqual( + reference.crc, + Software.update(0xFFFF_FFFF, input), + ); + } + } + + const long = bytes[0..512]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(long); + for ([_]usize{ 0, 1, 15, 16, 17, 100, 511, 512 }) |split| { + const first = Software.update(0xFFFF_FFFF, long[0..split]); + try std.testing.expectEqual( + reference.crc, + Software.update(first, long[split..]), + ); + } +} + test "matches the check value" { // The catalog check value for CRC-32/ISCSI. try std.testing.expectEqual( @@ -164,7 +254,7 @@ test "matches the standard library at every length and split" { for (0..bytes.len + 1) |len| { const input = bytes[0..len]; try std.testing.expectEqual( - Software.hash(input), + Reference.hash(input), Crc32c.hash(input), ); @@ -174,7 +264,7 @@ test "matches the standard library at every length and split" { split.update(input[0 .. len / 3]); split.update(input[len / 3 .. len - len / 3]); split.update(input[len - len / 3 ..]); - try std.testing.expectEqual(Software.hash(input), split.final()); + try std.testing.expectEqual(Reference.hash(input), split.final()); } } @@ -186,7 +276,7 @@ test "matches the standard library at every alignment" { for (0..16) |offset| { const input = bytes[offset..][0..64]; try std.testing.expectEqual( - Software.hash(input), + Reference.hash(input), Crc32c.hash(input), ); } From c1a61fddda00e907c7e66bd3609d6c558cccd26d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 08:54:25 -0700 Subject: [PATCH 069/392] terminal/snapshot: borrow fully buffered record payloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the record source already has the complete payload buffered — always the case for in-memory snapshots such as ghostty_snapshot_decoder_new_buf — the record reader now borrows the payload straight out of the source buffer instead of streaming it through the limited and hashing reader adapters. Payload decoders parse a fixed reader over the borrowed bytes, `finish` validates the CRC with a single bulk update, and the source advances only after validation. The page decoder takes a matching fast path: a fully buffered payload is parsed in place, skipping the staging allocation and copy it previously made per PAGE record. Streaming sources are unchanged. This is a modest win on its own; it is also the foundation for later commits whose buffered fast paths rely on the payload being contiguous. Benchmarks (see the first commit in this series for methodology; "prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 3.43 ms | 3.50 ms | 2.81 ms | 2.68 ms | | styled | 3.14 ms | 3.15 ms | 7.12 ms | 7.04 ms | | truecolor | 5.33 ms | 5.27 ms | 12.11 ms | 12.04 ms | | cjk | 5.95 ms | 5.92 ms | 12.04 ms | 11.89 ms | | grapheme | 14.18 ms | 14.33 ms | 13.16 ms | 13.14 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 41.7 ms | 41.8 ms | | ascii | decode | 53.2 ms | 52.4 ms | | utf8 | encode | 47.2 ms | 47.4 ms | | utf8 | decode | 61.6 ms | 61.2 ms | --- src/terminal/snapshot/page.zig | 12 +++++++- src/terminal/snapshot/record.zig | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/terminal/snapshot/page.zig b/src/terminal/snapshot/page.zig index 0d166e389..bd64e483e 100644 --- a/src/terminal/snapshot/page.zig +++ b/src/terminal/snapshot/page.zig @@ -233,7 +233,17 @@ pub const Decoder = struct { // payload, so this is the byte count of the tables and grid. const remaining = self.record_reader.header.payload_len - Header.len; - if (remaining <= max_staged_payload) { + if (self.record_reader.payloadReader().bufferedLen() >= remaining) { + // The complete payload is already buffered, e.g. borrowed from + // an in-memory snapshot. Parse it in place with no staging + // copy; `finish` still enforces the CRC and exact exhaustion. + try decodePayloadBody( + self.record_reader.payloadReader(), + alloc, + destination, + self.header, + ); + } else if (remaining <= max_staged_payload) { // Stage the payload with one bulk read. The whole payload passes // through the checksum hasher as one update and the payload // decoders then parse a flat buffer, which keeps per-row work free diff --git a/src/terminal/snapshot/record.zig b/src/terminal/snapshot/record.zig index e8dada911..397d00cbe 100644 --- a/src/terminal/snapshot/record.zig +++ b/src/terminal/snapshot/record.zig @@ -257,6 +257,18 @@ pub const Reader = struct { limited: std.Io.Reader.Limited, hashing: std.Io.Reader.Hashed(Crc32c), + /// When the source already has the complete payload buffered, for + /// example an in-memory snapshot, the payload is borrowed straight + /// from the source buffer instead of streaming through the limited + /// and hashing adapters. The checksum is then verified with one bulk + /// update in `finish`, and the source is not advanced until `finish`. + borrowed: ?Borrowed, + + const Borrowed = struct { + source: *std.Io.Reader, + payload: std.Io.Reader, + }; + pub const InitError = Header.DecodeError; /// Errors detected after a payload decoder returns. @@ -278,6 +290,21 @@ pub const Reader = struct { ) InitError!void { self.* = undefined; self.header = try Header.decode(source); + + // The complete payload is already sitting in the source buffer: + // borrow it in place. Payload decoders read from a fixed reader + // over the borrowed bytes and `finish` checksums them in one pass. + if (source.bufferedLen() >= self.header.payload_len) { + self.borrowed = .{ + .source = source, + .payload = .fixed( + source.buffered()[0..self.header.payload_len], + ), + }; + return; + } + + self.borrowed = null; self.limited = .init( source, .limited(self.header.payload_len), @@ -297,11 +324,34 @@ pub const Reader = struct { /// Return the length-limited, checksum-updating payload reader. pub fn payloadReader(self: *Reader) *std.Io.Reader { + if (self.borrowed) |*borrowed| return &borrowed.payload; return &self.hashing.reader; } /// Require exact payload exhaustion and validate its CRC32C. pub fn finish(self: *Reader) FinishError!void { + if (self.borrowed) |*borrowed| { + if (borrowed.payload.bufferedLen() != 0) { + return error.PayloadNotExhausted; + } + + var checksum: Checksum = .init( + self.header.tag, + self.header.payload_len, + ); + checksum.writer().writeAll( + borrowed.payload.buffer[0..borrowed.payload.end], + ) catch unreachable; + if (checksum.final() != self.header.crc32c) { + return error.InvalidChecksum; + } + + // The borrowed bytes validated, so consume them from the + // source only now, leaving it positioned at the next record. + borrowed.source.toss(self.header.payload_len); + return; + } + if (self.hashing.reader.bufferedLen() != 0 or self.limited.remaining != .nothing) { From 973f619a2371c50fffb2062bd59a0c5134c783b1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 08:57:29 -0700 Subject: [PATCH 070/392] terminal/snapshot: vectorize grid row encoding Row encoding previously made two scalar passes over every row (a backward scan for the encoded cell count and a validation pass accumulating the width-selection OR), then wrote a 3-byte header and per-width chunked cells through separate writer calls. Three changes, all bulk-codec only with the portable path unchanged: - scanRow computes the count and word-OR in @Vector(4, u64) strides. Trailing default cells are all-zero words, so the OR over the whole row equals the OR over the encoded prefix. - The per-cell wide-pair validation loop is skipped entirely when the OR carries no wide bits, which is every row of plain text. - Rows are emitted with a single reservation in the destination's spare buffer capacity (header plus cells, no writer calls), using explicit i8x16.shuffle truncation for the 1/2/4-byte cell widths. Zig 0.16 disables loop auto-vectorization, so the previous "vectorizable" truncating loop was actually scalar. Destinations without buffered capacity (counting writers, a still-growing scratch) fall through to the streaming path. Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 3.50 ms | 2.09 ms | 2.68 ms | 2.67 ms | | styled | 3.15 ms | 2.23 ms | 7.04 ms | 7.00 ms | | truecolor | 5.27 ms | 4.95 ms | 12.04 ms | 11.85 ms | | cjk | 5.92 ms | 6.03 ms | 11.89 ms | 11.67 ms | | grapheme | 14.33 ms | 13.08 ms | 13.14 ms | 13.28 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 41.8 ms | 24.5 ms | | ascii | decode | 52.4 ms | 53.4 ms | | utf8 | encode | 47.4 ms | 47.6 ms | | utf8 | decode | 61.2 ms | 60.9 ms | --- src/terminal/snapshot/grid.zig | 219 +++++++++++++++++++++++++++------ 1 file changed, 178 insertions(+), 41 deletions(-) diff --git a/src/terminal/snapshot/grid.zig b/src/terminal/snapshot/grid.zig index fe5d05528..0b5402330 100644 --- a/src/terminal/snapshot/grid.zig +++ b/src/terminal/snapshot/grid.zig @@ -468,25 +468,21 @@ pub fn encode( const row = page.getRow(y); const cells = page.getCells(row); - // Trailing default cells decode implicitly. Wide/spacer pairs and + // Trailing default cells decode implicitly: wide/spacer pairs and // hyperlinked or styled cells are always nonzero, so eliding the - // zero suffix never drops encoded state. - const count: usize = count: { - var i: usize = cells.len; - while (i > 0) : (i -= 1) { - if (!cells[i - 1].isZero()) break :count i; - } - break :count 0; - }; + // zero suffix never drops encoded state. The scan also accumulates + // the OR of the row's cell words to select its encoded cell width. + const count: usize, const word_or: u64 = scanRow(cells); // Validate the wide state of every encoded cell so we don't encode - // corrupt data, and accumulate the OR of the row's cell words to - // select its encoded cell width. Trailing default cells are narrow, - // so checking the encoded prefix against the full row width covers - // every pair. - var word_or: u64 = 0; - for (cells[0..count], 0..) |*cell, x| { - switch (cell.wide) { + // corrupt data. The width bits of the OR word witness whether any + // encoded cell is non-narrow at all; rows without them, the common + // case, satisfy every pair rule vacuously. Trailing default cells + // are narrow, so checking the encoded prefix against the full row + // width covers every pair. + const wide_mask: u64 = comptime @bitCast(Cell{ .width = 3 }); + if (word_or & wide_mask != 0) { + for (cells[0..count], 0..) |*cell, x| switch (cell.wide) { .narrow => {}, .wide => if (x + 1 == cells.len or cells[x + 1].wide != .spacer_tail) @@ -501,21 +497,66 @@ pub fn encode( .spacer_head => if (x + 1 != cells.len or !row.wrap) { return error.InvalidWideCell; }, - } - word_or |= classifyWord(cell); + }; } // Canonical rows use the smallest admissible width. const cell_width: Cell.EncodedWidth = .select(word_or); + const row_header: Row = .{ + .wrap = row.wrap, + .wrap_continuation = row.wrap_continuation, + .semantic_prompt = @intFromEnum(row.semantic_prompt), + .cell_width = cell_width, + }; + + // The bulk codec emits the row header and its encoded cells directly + // into the destination's spare buffer capacity, so a row costs no + // writer call at all instead of one for the header and one per cell + // chunk. + if (comptime bulk_codec) emit: { + const words: [*]const u64 = @ptrCast(cells.ptr); + switch (cell_width) { + inline .one, .two, .four => |width| { + const size = comptime width.size(); + const needed = 3 + count * size; + if (writer.unusedCapacityLen() < needed) break :emit; + const out = writer.unusedCapacitySlice()[0..needed]; + out[0] = @bitCast(row_header); + std.mem.writeInt(u16, out[1..3], @intCast(count), .little); + encodeNarrowInto(width, words, count, out[3..]); + writer.advance(needed); + continue; + }, + .eight => { + // Hyperlink IDs live in a native side table, but we + // embed them in ours, so if we have any hyperlinks we + // need to fall back to the loop below. + const witness: Cell = @bitCast(word_or); + if (!witness.hyperlink and witness.hyperlink_id == 0) { + const needed = 3 + count * 8; + if (writer.unusedCapacityLen() < needed) break :emit; + const out = writer.unusedCapacitySlice()[0..needed]; + out[0] = @bitCast(row_header); + std.mem.writeInt( + u16, + out[1..3], + @intCast(count), + .little, + ); + @memcpy( + out[3..], + std.mem.sliceAsBytes(cells[0..count]), + ); + writer.advance(needed); + continue; + } + }, + } + } + // Row header: flags then the encoded cell count. { - const row_header: Row = .{ - .wrap = row.wrap, - .wrap_continuation = row.wrap_continuation, - .semantic_prompt = @intFromEnum(row.semantic_prompt), - .cell_width = cell_width, - }; var header_bytes: [3]u8 = undefined; header_bytes[0] = @bitCast(row_header); std.mem.writeInt(u16, header_bytes[1..3], @intCast(count), .little); @@ -559,6 +600,47 @@ pub fn encode( try encodeGraphemes(page, writer); } +/// The encoded cell count (through the last nonzero cell) and the bitwise +/// OR of every encoded cell word for one row. +fn scanRow(cells: []const TerminalCell) struct { usize, u64 } { + if (comptime bulk_codec) { + const words: [*]const u64 = @ptrCast(cells.ptr); + const V = @Vector(4, u64); + const VPtr = *align(@alignOf(u64)) const V; + + // Count the zero cells using vectorized instructions + var count = cells.len; + while (count >= 4) { + const tail = @as(VPtr, @ptrCast(words + count - 4)).*; + if (@reduce(.Or, tail) != 0) break; + count -= 4; + } + while (count > 0 and words[count - 1] == 0) count -= 1; + + // Accumulate the OR of the classifaction vectorized, with the + // scalar tail continuing from where the vector loop stopped. + const word_or: u64 = word_or: { + var acc: V = @splat(0); + var i: usize = 0; + while (i + 4 <= count) : (i += 4) { + acc |= @as(VPtr, @ptrCast(words + i)).*; + } + var word_or: u64 = @reduce(.Or, acc); + while (i < count) : (i += 1) word_or |= words[i]; + break :word_or word_or; + }; + + return .{ count, word_or }; + } + + // Scalar path, count backwards + var count: usize = cells.len; + while (count > 0 and cells[count - 1].isZero()) count -= 1; + var word_or: u64 = 0; + for (cells[0..count]) |*cell| word_or |= classifyWord(cell); + return .{ count, word_or }; +} + /// The word used to select a row's encoded cell width. This is the cell's /// wire word with the hyperlink flag reflecting the native cell, so linked /// cells and nonzero native padding disqualify every narrow width. @@ -584,35 +666,90 @@ fn encodeNarrowCells( var i: usize = 0; while (i < cells.len) { const n = @min(cells.len - i, chunk.len / size); + for (cells[i..][0..n], 0..) |*cell, j| { + std.mem.writeInt( + width.Int(), + chunk[j * size ..][0..size], + width.truncate(classifyWord(cell)), + .little, + ); + } + try writer.writeAll(chunk[0 .. n * size]); + i += n; + } +} - if (comptime native_matches_wire) { - // A pure truncating loop over integers that the compiler can - // vectorize. - const words: [*]const u64 = @ptrCast(cells.ptr); - for (0..n) |j| { - std.mem.writeInt( - width.Int(), - chunk[j * size ..][0..size], - width.truncate(words[i + j]), - .little, - ); +/// Truncate one row's cell words into `out` at the given encoded width. +fn encodeNarrowInto( + comptime width: Cell.EncodedWidth, + words: [*]const u64, + count: usize, + out: []u8, +) void { + comptime assert(bulk_codec); + const size = comptime width.size(); + const V = @Vector(2, u64); + const shift: V = @splat(comptime switch (width) { + .one, .two => @bitOffsetOf(Cell, "content"), + .four => 0, + .eight => unreachable, + }); + const mask: @Vector(size * 4, i32) = comptime mask: { + var mask: [size * 4]i32 = undefined; + for (0..2) |lane| { + for (0..size) |byte| { + mask[lane * size + byte] = @intCast(lane * 8 + byte); + mask[(lane + 2) * size + byte] = + ~@as(i32, @intCast(lane * 8 + byte)); } + } + break :mask mask; + }; + + var j: usize = 0; + while (j + 4 <= count) : (j += 4) encodeNarrowStep( + width, + words, + j, + out, + shift, + mask, + ); + if (j < count) { + if (count >= 4) { + encodeNarrowStep(width, words, count - 4, out, shift, mask); } else { - for (cells[i..][0..n], 0..) |*cell, j| { + while (j < count) : (j += 1) { std.mem.writeInt( width.Int(), - chunk[j * size ..][0..size], - width.truncate(classifyWord(cell)), + out[j * size ..][0..size], + width.truncate(words[j]), .little, ); } } - - try writer.writeAll(chunk[0 .. n * size]); - i += n; } } +/// Emit four truncated cell words starting at cell index `j`. +inline fn encodeNarrowStep( + comptime width: Cell.EncodedWidth, + words: [*]const u64, + j: usize, + out: []u8, + shift: @Vector(2, u64), + mask: @Vector(width.size() * 4, i32), +) void { + const size = comptime width.size(); + const VPtr = *align(@alignOf(u64)) const @Vector(2, u64); + const lo: @Vector(16, u8) = @bitCast(@as(VPtr, @ptrCast(words + j)).* >> shift); + const hi: @Vector(16, u8) = @bitCast(@as(VPtr, @ptrCast(words + j + 2)).* >> shift); + @as( + *align(1) @Vector(size * 4, u8), + @ptrCast(out[j * size ..].ptr), + ).* = @shuffle(u8, lo, hi, mask); +} + /// Encode the grapheme suffix section for every kind 1 cell in the grid. fn encodeGraphemes( page: *const TerminalPage, From 593762cfa11afdd3eaefc9e0bdd9d979d85ab39f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 08:59:48 -0700 Subject: [PATCH 071/392] terminal/snapshot: batch grapheme suffix codec Grapheme suffix encoding made two full passes over the grid (a counting pass, then an emit pass) and issued three writer calls per entry plus one per codepoint. Every grapheme cell owns exactly one entry in the page's grapheme map, so the section count now comes straight from page.graphemeCount() with no counting pass, and entries are batched through a 4 KB buffer with one writer call per flush. The per-entry size check moved into the emit loop; an error still cancels the whole record before any of it is emitted, so error behavior is unchanged. Decoding similarly parsed entry headers and codepoints with one reader call per integer. Fully buffered payloads (the common case after the borrowed-payload commit) now parse entry headers and codepoint runs directly from the buffered bytes, and entries whose target cell cannot carry a suffix discard their codepoints in bulk. Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.09 ms | 2.07 ms | 2.67 ms | 2.69 ms | | styled | 2.23 ms | 2.29 ms | 7.00 ms | 7.06 ms | | truecolor | 4.95 ms | 4.91 ms | 11.85 ms | 11.99 ms | | cjk | 6.03 ms | 6.14 ms | 11.67 ms | 11.83 ms | | grapheme | 13.08 ms | 8.26 ms | 13.28 ms | 11.18 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 24.5 ms | 24.3 ms | | ascii | decode | 53.4 ms | 50.9 ms | | utf8 | encode | 47.6 ms | 41.2 ms | | utf8 | decode | 60.9 ms | 59.5 ms | --- src/terminal/snapshot/grid.zig | 157 ++++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 43 deletions(-) diff --git a/src/terminal/snapshot/grid.zig b/src/terminal/snapshot/grid.zig index 0b5402330..2f383ba93 100644 --- a/src/terminal/snapshot/grid.zig +++ b/src/terminal/snapshot/grid.zig @@ -755,36 +755,57 @@ fn encodeGraphemes( page: *const TerminalPage, writer: *std.Io.Writer, ) EncodeError!void { - // Count and validate entries before the section header so the count is - // always exact. Rows without the native grapheme hint contain no - // grapheme cells in any intact page. - var entries: u32 = 0; - for (0..page.size.rows) |y| { - const row = page.getRow(y); - if (!row.grapheme) continue; - for (page.getCells(row)) |*cell| { - if (!cell.hasGrapheme()) continue; - const cps = page.lookupGrapheme(cell) orelse unreachable; - if (cps.len > std.math.maxInt(u16)) return error.TooManyGraphemes; - entries += 1; - } - } - - try io.writeInt(writer, u32, entries); + // Every grapheme cell owns exactly one entry in the page's grapheme + // map, so the section header comes straight from the page without a + // counting pass over the grid. Rows without the native grapheme hint + // contain no grapheme cells in any intact page. + const entries = page.graphemeCount(); + try io.writeInt(writer, u32, @intCast(entries)); if (entries == 0) return; + // Entries are batched into a local buffer so hot pages perform one + // writer call per flush instead of several per entry. + var buffer: [4096]u8 = undefined; + var used: usize = 0; + var emitted: usize = 0; for (0..page.size.rows) |y| { const row = page.getRow(y); if (!row.grapheme) continue; for (page.getCells(row), 0..) |*cell, x| { if (!cell.hasGrapheme()) continue; const cps = page.lookupGrapheme(cell) orelse unreachable; - try io.writeInt(writer, u16, @intCast(y)); - try io.writeInt(writer, u16, @intCast(x)); - try io.writeInt(writer, u16, @intCast(cps.len)); - for (cps) |cp| try io.writeInt(writer, u32, cp); + if (cps.len > std.math.maxInt(u16)) return error.TooManyGraphemes; + emitted += 1; + + const needed = 6 + cps.len * 4; + if (buffer.len - used < needed) { + try writer.writeAll(buffer[0..used]); + used = 0; + } + if (needed > buffer.len) { + // An entry larger than the whole buffer streams directly. + try io.writeInt(writer, u16, @intCast(y)); + try io.writeInt(writer, u16, @intCast(x)); + try io.writeInt(writer, u16, @intCast(cps.len)); + for (cps) |cp| try io.writeInt(writer, u32, cp); + continue; + } + + std.mem.writeInt(u16, buffer[used..][0..2], @intCast(y), .little); + std.mem.writeInt(u16, buffer[used + 2 ..][0..2], @intCast(x), .little); + std.mem.writeInt(u16, buffer[used + 4 ..][0..2], @intCast(cps.len), .little); + used += 6; + for (cps) |cp| { + std.mem.writeInt(u32, buffer[used..][0..4], cp, .little); + used += 4; + } } } + try writer.writeAll(buffer[0..used]); + + // The declared count is trusted by decoders for framing, so the grid + // must have produced exactly that many entries. + assert(emitted == entries); } pub const DecodeError = std.Io.Reader.Error || error{ @@ -1183,9 +1204,23 @@ fn decodeGraphemes( ) DecodeError!void { const entries = try io.readInt(reader, u32); for (0..entries) |_| { - const y = try io.readInt(reader, u16); - const x = try io.readInt(reader, u16); - const cp_count = try io.readInt(reader, u16); + // The staged and borrowed payload paths have every header buffered. + const y: u16, const x: u16, const cp_count: u16 = header: { + if (reader.bufferedLen() >= 6) { + const bytes = reader.buffered()[0..6]; + defer reader.toss(6); + break :header .{ + std.mem.readInt(u16, bytes[0..2], .little), + std.mem.readInt(u16, bytes[2..4], .little), + std.mem.readInt(u16, bytes[4..6], .little), + }; + } + break :header .{ + try io.readInt(reader, u16), + try io.readInt(reader, u16), + try io.readInt(reader, u16), + }; + }; // Resolve the target cell. Entries whose target cannot carry a // suffix are optional detail: their codepoints are consumed to @@ -1207,31 +1242,67 @@ fn decodeGraphemes( break :target .{ .row = row, .cell = cell }; }; - // Always consume every declared codepoint. Invalid scalars and NUL are - // not meaningful grapheme suffix components and are ignored. If native - // capacity is exhausted, remove any prefix already attached so the - // cell never exposes a truncated cluster. - var accept = target != null; - for (0..cp_count) |_| { - const cp = try io.readInt(reader, u32); - if (!accept) continue; - if (cp == 0 or !validScalar(cp)) continue; + // Always consume every declared codepoint. Invalid scalars and NUL + // are not meaningful grapheme suffix components and are ignored. A + // dropped entry's codepoints are discarded in bulk. + const resolved = target orelse { + try reader.discardAll(@as(usize, cp_count) * 4); + continue; + }; - page.appendGrapheme( - target.?.row, - target.?.cell, - @intCast(cp), - ) catch { - if (target.?.cell.hasGrapheme()) { - page.clearGrapheme(target.?.cell); - page.updateRowGraphemeFlag(target.?.row); - } - accept = false; - }; + var accept = true; + var index: usize = 0; + while (index < cp_count) { + const buffered = reader.buffered(); + if (buffered.len >= 4) { + const n = @min(cp_count - index, buffered.len / 4); + for (0..n) |i| applyGraphemeSuffix( + page, + resolved.row, + resolved.cell, + &accept, + std.mem.readInt(u32, buffered[i * 4 ..][0..4], .little), + ); + reader.toss(n * 4); + index += n; + } else { + applyGraphemeSuffix( + page, + resolved.row, + resolved.cell, + &accept, + try io.readInt(reader, u32), + ); + index += 1; + } } } } +/// Attach one decoded suffix codepoint to its resolved target cell. +/// +/// If native capacity is exhausted, any prefix already attached is removed +/// so the cell never exposes a truncated cluster, and `accept` latches +/// false so the entry's remaining codepoints are consumed but dropped. +fn applyGraphemeSuffix( + page: *TerminalPage, + row: *TerminalRow, + cell: *TerminalCell, + accept: *bool, + cp: u32, +) void { + if (!accept.*) return; + if (cp == 0 or !validScalar(cp)) return; + + page.appendGrapheme(row, cell, @intCast(cp)) catch { + if (cell.hasGrapheme()) { + page.clearGrapheme(cell); + page.updateRowGraphemeFlag(row); + } + accept.* = false; + }; +} + /// The encoded word for one native cell and its hyperlink ID. fn cellBits(cell: TerminalCell, link_id: TerminalHyperlinkId) u64 { if (comptime native_matches_wire) { From 2aaad3ca99a27d36eff57d6e59e2044005ef2ba4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 09:01:59 -0700 Subject: [PATCH 072/392] terminal/snapshot: vectorize grid cell decoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decoding one- and two-byte cells widened them to their 8-byte words one scalar store at a time. The bulk codec now widens sixteen transported bytes per step with byte shuffles against a zero vector, degrading width-two surrogate lanes to U+FFFD with a vector select, exactly matching the scalar path. Short row tails reprocess the final full window with overlapping stores that rewrite identical bytes. This is a native win: the shuffles lower to NEON and take ascii decode from 38.9 ms to 34.0 ms (measured by toggling this path at the tip of this series). On wasm, V8 runs the scalar fallback at the same speed as the shuffle version — the loop is store-bound either way — so the wasm deltas below are flat. Row decoding also drops per-field packed-struct read-modify-writes in favor of one load and one store per row header, and rows that are fully default (zero header byte, zero encoded cells) skip all work: decoded pages start zeroed, which is exactly the default row and cell state. Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.07 ms | 2.18 ms | 2.69 ms | 2.68 ms | | styled | 2.29 ms | 2.28 ms | 7.06 ms | 6.93 ms | | truecolor | 4.91 ms | 4.93 ms | 11.99 ms | 11.91 ms | | cjk | 6.14 ms | 6.11 ms | 11.83 ms | 11.72 ms | | grapheme | 8.26 ms | 8.28 ms | 11.18 ms | 11.38 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 24.3 ms | 24.3 ms | | ascii | decode | 50.9 ms | 46.7 ms | | utf8 | encode | 41.2 ms | 41.0 ms | | utf8 | decode | 59.5 ms | 59.5 ms | --- src/terminal/snapshot/grid.zig | 94 ++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/src/terminal/snapshot/grid.zig b/src/terminal/snapshot/grid.zig index 2f383ba93..6ec084e61 100644 --- a/src/terminal/snapshot/grid.zig +++ b/src/terminal/snapshot/grid.zig @@ -865,13 +865,21 @@ pub fn decode( break :header .{ row_header, count }; }; + // A fully default row needs no work at all: decoded pages start + // zeroed, which is exactly the default row and cell state. + if (@as(u8, @bitCast(row_header)) == 0 and count == 0) continue; + + // Update the row fields through one load and store instead of a + // read-modify-write per packed field. const row = page.getRow(y); - row.wrap = row_header.wrap; - row.wrap_continuation = row_header.wrap_continuation; - row.semantic_prompt = std.enums.fromInt( + var row_value = row.*; + row_value.wrap = row_header.wrap; + row_value.wrap_continuation = row_header.wrap_continuation; + row_value.semantic_prompt = std.enums.fromInt( TerminalRow.SemanticPrompt, row_header.semantic_prompt, ) orelse .none; + row.* = row_value; const cells = page.getCells(row); if (count > cells.len) return error.InvalidRowCellCount; @@ -983,8 +991,34 @@ fn widenCells( ) void { const size = comptime width.size(); + // With the bulk codec layout this is a pure widening store: sixteen + // transported bytes per step, shuffling each pair of encoded values + // into u64 lane position against a zero vector and shifting them into + // the content field. Zig 0.16 disables loop auto-vectorization, so the + // scalar loop would issue one widening store per cell. + if (comptime bulk_codec) { + const words: [*]u64 = @ptrCast(cells.ptr); + const step = 16 / size; + var i: usize = 0; + while (i + step <= cells.len) : (i += step) { + widenStep(width, bytes, words, i); + } + if (i < cells.len) { + if (cells.len >= step) { + // Reprocess the final full window with overlapping stores, + // which rewrite the same widened values. + widenStep(width, bytes, words, cells.len - step); + } else { + while (i < cells.len) : (i += 1) { + words[i] = width.extend(widenValue(width, bytes[i * size ..])); + } + } + } + return; + } + // When the native cell matches the wire word, this is a pure widening - // loop over integers that the compiler can vectorize. + // loop over integers. if (comptime native_matches_wire) { const words: [*]u64 = @ptrCast(cells.ptr); for (0..cells.len) |i| { @@ -998,6 +1032,58 @@ fn widenCells( } } +/// Widen sixteen transported bytes into their cell words at cell index `i`: +/// shuffle each pair of encoded values into u64 lane position against a +/// zero vector, then shift the value into the content field. Width two +/// first degrades surrogate lanes to U+FFFD, matching `widenValue`. +inline fn widenStep( + comptime width: Cell.EncodedWidth, + bytes: []const u8, + words: [*]u64, + i: usize, +) void { + const size = comptime width.size(); + const step = 16 / size; + + var in: @Vector(16, u8) = @as( + *align(1) const @Vector(16, u8), + @ptrCast(bytes[i * size ..].ptr), + ).*; + + // Width two admits surrogates, which degrade to U+FFFD exactly + // like `widenValue`. Width one cannot encode an invalid scalar. + if (comptime width == .two) { + const values: @Vector(8, u16) = @bitCast(in); + const invalid = (values & @as(@Vector(8, u16), @splat(0xF800))) == + @as(@Vector(8, u16), @splat(0xD800)); + in = @bitCast(@select( + u16, + invalid, + @as(@Vector(8, u16), @splat(0xFFFD)), + values, + )); + } + + const zero: @Vector(16, u8) = @splat(0); + inline for (0..step / 2) |pair| { + const mask: @Vector(16, i32) = comptime mask: { + var mask: [16]i32 = @splat(~@as(i32, 0)); + for (0..size) |byte| { + mask[byte] = @intCast((2 * pair) * size + byte); + mask[8 + byte] = @intCast((2 * pair + 1) * size + byte); + } + break :mask mask; + }; + const lanes: @Vector(2, u64) = @bitCast( + @shuffle(u8, in, zero, mask), + ); + @as( + *align(@alignOf(u64)) @Vector(2, u64), + @ptrCast(words + i + 2 * pair), + ).* = lanes << @splat(@bitOffsetOf(Cell, "content")); + } +} + /// Read and validate one narrow transported value. inline fn widenValue( comptime width: Cell.EncodedWidth, From 7c1014ef662cd4e9b968b5bac6ca062fa17a3b56 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 09:10:48 -0700 Subject: [PATCH 073/392] terminal/snapshot: resolve wide pairs in a per-row pass Cell decoding ran wide-pair normalization inline for every decoded cell: two neighbor loads and a switch per cell, even though the overwhelming majority of rows contain no wide cells at all. Normalization is defined against already-stored predecessors, so running it as an ordered pass over the stored row afterward is exactly equivalent to interleaving it. The word-cell decoders now accumulate the bitwise OR of the row's wire words as they apply cells, and the pass is gated on it: rows without wide bits are already normalized (every cell narrow), and width-four and narrower transports cannot encode wide bits at all, so their rows skip the check at comptime. That removes the per-cell neighbor traffic from all styled text, which decodes through the four-byte width. Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.18 ms | 2.06 ms | 2.68 ms | 2.70 ms | | styled | 2.28 ms | 2.41 ms | 6.93 ms | 6.56 ms | | truecolor | 4.93 ms | 5.02 ms | 11.91 ms | 11.86 ms | | cjk | 6.11 ms | 6.03 ms | 11.72 ms | 11.60 ms | | grapheme | 8.28 ms | 8.36 ms | 11.38 ms | 11.39 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 24.3 ms | 25.6 ms | | ascii | decode | 46.7 ms | 48.1 ms | | utf8 | encode | 41.0 ms | 41.7 ms | | utf8 | decode | 59.5 ms | 58.5 ms | --- src/terminal/snapshot/grid.zig | 51 ++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/terminal/snapshot/grid.zig b/src/terminal/snapshot/grid.zig index 6ec084e61..1fc946e31 100644 --- a/src/terminal/snapshot/grid.zig +++ b/src/terminal/snapshot/grid.zig @@ -893,7 +893,7 @@ pub fn decode( reader, cells[0..count], ), - .four => try decodeWordCells( + .four => _ = try decodeWordCells( .four, page, row, @@ -913,7 +913,9 @@ pub fn decode( try reader.readSliceAll( std.mem.sliceAsBytes(cells[0..count]), ); + var row_or: u64 = 0; for (0..count) |x| { + row_or |= words[x]; applyCell( page, row, @@ -924,8 +926,9 @@ pub fn decode( hyperlink_remap, ); } + normalizeWideRow(.eight, row, cells, count, row_or); } else { - try decodeWordCells( + _ = try decodeWordCells( .eight, page, row, @@ -1102,6 +1105,9 @@ inline fn widenValue( /// Decode one row of width-four or fallback full-width cells through the /// complete per-cell normalization path. +/// +/// Returns the bitwise OR of every decoded wire word so callers can gate +/// the trailing wide-pair resolution pass without a second scan. fn decodeWordCells( comptime width: Cell.EncodedWidth, page: *TerminalPage, @@ -1111,8 +1117,9 @@ fn decodeWordCells( reader: *std.Io.Reader, style_remap: *const StyleRemap, hyperlink_remap: *const HyperlinkRemap, -) DecodeError!void { +) DecodeError!u64 { const size = comptime width.size(); + var row_or: u64 = 0; // The staged payload path has the complete row buffered. const total = count * size; @@ -1124,6 +1131,7 @@ fn decodeWordCells( bytes[x * size ..][0..size], .little, )); + row_or |= bits; applyCell( page, row, @@ -1135,12 +1143,14 @@ fn decodeWordCells( ); } reader.toss(total); - return; + normalizeWideRow(width, row, cells, count, row_or); + return row_or; } // Streaming sources fall back to per-cell reads. for (0..count) |x| { const bits = width.extend(try io.readInt(reader, width.Int())); + row_or |= bits; applyCell( page, row, @@ -1151,6 +1161,28 @@ fn decodeWordCells( hyperlink_remap, ); } + normalizeWideRow(width, row, cells, count, row_or); + return row_or; +} + +/// Resolve wide-pair relationships for one decoded row. +/// +/// Cell decoding stores every cell unresolved, so this pass applies +/// `normalizeWide` in order, which is equivalent to interleaving it with +/// the stores. Rows whose word OR carries no wide bits are already +/// normalized: every cell is narrow. Width four and narrower transports +/// cannot encode wide bits at all, so those rows skip the check entirely. +inline fn normalizeWideRow( + comptime width: Cell.EncodedWidth, + row: *const TerminalRow, + cells: []TerminalCell, + count: usize, + row_or: u64, +) void { + if (comptime width != .eight) return; + const wide_mask: u64 = comptime @bitCast(Cell{ .width = 3 }); + if (row_or & wide_mask == 0) return; + for (0..count) |x| normalizeWide(row, cells, x); } /// Whether the value is a valid Unicode scalar value. @@ -1160,10 +1192,10 @@ inline fn validScalar(cp: u32) bool { /// Normalize one encoded cell word and store it at `cells[x]`. /// -/// This owns every per-cell decode rule except grapheme suffixes: content -/// validation, reserved-value degradation, style and hyperlink remapping -/// with reference counting, and wide-pair normalization against already -/// decoded neighbors. +/// This owns every per-cell decode rule except grapheme suffixes and +/// wide-pair resolution: content validation, reserved-value degradation, +/// and style and hyperlink remapping with reference counting. Callers run +/// `normalizeWideRow` over the stored row afterward. fn applyCell( page: *TerminalPage, row: *TerminalRow, @@ -1179,7 +1211,6 @@ fn applyCell( // reference counting, or table lookups. if (bits_wire == 0) { storeCell(cell, 0); - normalizeWide(row, cells, x); return; } @@ -1247,8 +1278,6 @@ fn applyCell( page.hyperlink_set.release(page.memory, link_native); }; } - - normalizeWide(row, cells, x); } /// Resolve wide-pair relationships for the cell at `x` against its already From 47a5182621e324f3351683ca2cc422562b3ff792 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 09:14:31 -0700 Subject: [PATCH 074/392] terminal/snapshot: skip remap tables for pages without styles Decoding a page allocated and zeroed two full remap tables (a 128 KB entries array plus an 8 KB seen bitmap each for styles and hyperlinks) even when the page declared no table entries at all, which is every page of plain scrollback. Empty tables now use a shared `.empty` remap that allocates nothing; `get` reads it as all-unmapped through a length check. Pages that do declare entries are unchanged. (Leaving the entries array unzeroed behind a seen-bitmap-gated `get` was also tried and measured no better than the plain memset, so the table keeps its simple zero-means-unmapped representation.) Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.06 ms | 2.13 ms | 2.70 ms | 2.54 ms | | styled | 2.41 ms | 2.29 ms | 6.56 ms | 6.75 ms | | truecolor | 5.02 ms | 4.95 ms | 11.86 ms | 12.05 ms | | cjk | 6.03 ms | 6.23 ms | 11.60 ms | 11.39 ms | | grapheme | 8.36 ms | 8.72 ms | 11.39 ms | 11.27 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 25.6 ms | 24.4 ms | | ascii | decode | 48.1 ms | 45.1 ms | | utf8 | encode | 41.7 ms | 41.9 ms | | utf8 | decode | 58.5 ms | 58.6 ms | --- src/terminal/snapshot/grid.zig | 17 +++++++++++++---- src/terminal/snapshot/page.zig | 15 +++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/terminal/snapshot/grid.zig b/src/terminal/snapshot/grid.zig index 1fc946e31..3a890913b 100644 --- a/src/terminal/snapshot/grid.zig +++ b/src/terminal/snapshot/grid.zig @@ -1515,6 +1515,11 @@ fn Remap(comptime Id: type) type { /// semantics. seen: std.DynamicBitSetUnmanaged, + /// A remap with no entries at all: every lookup is unmapped. Use + /// this instead of `init` when the encoded table is empty so pages + /// without styles or hyperlinks allocate nothing. + pub const empty: Self = .{ .entries = &.{}, .seen = .{} }; + pub fn init(alloc: Allocator) Allocator.Error!Self { const entries = try alloc.alloc(Id, capacity); errdefer alloc.free(entries); @@ -1527,25 +1532,29 @@ fn Remap(comptime Id: type) type { } pub fn deinit(self: *Self, alloc: Allocator) void { - alloc.free(self.entries); - self.seen.deinit(alloc); + if (self.entries.len != 0) { + alloc.free(self.entries); + self.seen.deinit(alloc); + } self.* = undefined; } - /// Record one encoded-to-native mapping. + /// Record one encoded-to-native mapping. Illegal on `empty`. pub fn put(self: *Self, encoded: Id, native: Id) void { assert(!self.seen.isSet(encoded)); self.entries[encoded] = native; self.seen.set(encoded); } - /// Whether the encoded ID already has an entry, even a default one. + /// Whether the encoded ID already has an entry, even a default + /// one. Illegal on `empty`. pub fn contains(self: *const Self, encoded: Id) bool { return self.seen.isSet(encoded); } /// The native ID for an encoded ID, or zero when unmapped. pub inline fn get(self: *const Self, encoded: Id) Id { + if (self.entries.len == 0) return 0; return self.entries[encoded]; } }; diff --git a/src/terminal/snapshot/page.zig b/src/terminal/snapshot/page.zig index bd64e483e..0acd51718 100644 --- a/src/terminal/snapshot/page.zig +++ b/src/terminal/snapshot/page.zig @@ -367,12 +367,19 @@ fn decodePayloadBody( page.pauseIntegrityChecks(true); defer page.pauseIntegrityChecks(false); - var style_remap = grid.StyleRemap.init(alloc) catch - return error.OutOfMemory; + // Pages without styles or hyperlinks, the common case for plain + // scrollback, skip the remap tables entirely: every encoded cell ID + // resolves to the default through the empty remap. + var style_remap: grid.StyleRemap = if (header.style_count > 0) + grid.StyleRemap.init(alloc) catch return error.OutOfMemory + else + .empty; defer style_remap.deinit(alloc); - var hyperlink_remap = grid.HyperlinkRemap.init(alloc) catch - return error.OutOfMemory; + var hyperlink_remap: grid.HyperlinkRemap = if (header.hyperlink_count > 0) + grid.HyperlinkRemap.init(alloc) catch return error.OutOfMemory + else + .empty; defer hyperlink_remap.deinit(alloc); // Styles From 1359973aefb37a9beaa2ec3e8f79df78290ea6f5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 09:18:20 -0700 Subject: [PATCH 075/392] terminal/snapshot: single-pass style entry codec Style entries went through roughly five writer or reader vtable calls each: encode wrote three 4-byte colors and two u16s separately, and decode read 16 bytes into a stack buffer only to re-parse it through a nested fixed reader, one small read per field. Style-heavy pages carry hundreds of entries per page, so encode now assembles each entry in a 16-byte buffer with a single write, and decode parses the fixed-size entry directly from a byte array. Entries additionally parse straight from the buffered payload (ID and value together) when it is contiguous. Inserting a decoded style also hashed twice: an explicit `lookup` before `add`, even though `add` already returns the existing entry for repeated values. Insert with `add` alone, taking one reference per accepted table entry, and surrender those references through the encoded-ID remap after grid decoding, the same scheme hyperlink entries already use. Refcount outcomes are identical: each distinct style nets its cell references. Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.13 ms | 2.12 ms | 2.54 ms | 2.59 ms | | styled | 2.29 ms | 2.23 ms | 6.75 ms | 6.43 ms | | truecolor | 4.95 ms | 3.44 ms | 12.05 ms | 8.35 ms | | cjk | 6.23 ms | 5.99 ms | 11.39 ms | 11.39 ms | | grapheme | 8.72 ms | 8.33 ms | 11.27 ms | 10.89 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 24.4 ms | 24.7 ms | | ascii | decode | 45.1 ms | 47.5 ms | | utf8 | encode | 41.9 ms | 41.9 ms | | utf8 | decode | 58.6 ms | 58.8 ms | --- src/terminal/snapshot/page.zig | 52 +++++++++++------- src/terminal/snapshot/style.zig | 94 ++++++++++++++++----------------- 2 files changed, 80 insertions(+), 66 deletions(-) diff --git a/src/terminal/snapshot/page.zig b/src/terminal/snapshot/page.zig index 0acd51718..6c8bb5ce8 100644 --- a/src/terminal/snapshot/page.zig +++ b/src/terminal/snapshot/page.zig @@ -382,23 +382,36 @@ fn decodePayloadBody( .empty; defer hyperlink_remap.deinit(alloc); - // Styles + // Styles. The complete fixed-size entry is parsed from the buffered + // payload when possible so each entry costs no reader calls. + const style_entry_len = @sizeOf(TerminalStyleId) + style.len; for (0..header.style_count) |_| { - const native_id = try io.readInt(reader, TerminalStyleId); - const value = try style.decodeOrNull(reader); + const native_id: TerminalStyleId, const value = entry: { + if (reader.bufferedLen() >= style_entry_len) { + const bytes = reader.buffered()[0..style_entry_len]; + defer reader.toss(style_entry_len); + break :entry .{ + std.mem.readInt(TerminalStyleId, bytes[0..2], .little), + style.parseOrNull(bytes[2..][0..style.len]), + }; + } + break :entry .{ + try io.readInt(reader, TerminalStyleId), + try style.decodeOrNull(reader), + }; + }; // Zero is reserved for the implicit default. For a duplicate encoded // ID, the first entry wins and this complete entry is simply ignored. if (native_id == 0 or style_remap.contains(native_id)) continue; - // Invalid/default styles map to the native default. Repeated concrete - // values share the existing native entry, while capacity failure also - // degrades only this style. + // Invalid/default styles map to the native default. `add` returns + // the existing entry for a repeated concrete value, taking one + // reference either way, while capacity failure degrades only this + // style. The references are surrendered through the remap below + // once every cell reference is installed. const decoded_id: TerminalStyleId = if (value) |valid| decoded: { if (valid.default()) break :decoded 0; - if (page.styles.lookup(page.memory, valid)) |existing| { - break :decoded existing; - } break :decoded page.styles.add( page.memory, valid, @@ -434,16 +447,17 @@ fn decodePayloadBody( &hyperlink_remap, ); - // A newly inserted table value starts with one reference so grid decoding - // can safely attach it to any number of cells. Unlike organically built - // pages, that initial reference does not itself represent a cell. Release - // it once per distinct live style after every cell reference is installed; - // unused entries then become dead and disappear from canonical re-encoding. - for (1..@as(usize, page.styles.next_id)) |raw_id| { - const id: TerminalStyleId = @intCast(raw_id); - if (page.styles.refCount(page.memory, id) > 0) { - page.styles.release(page.memory, id); - } + // Every accepted table entry took one reference through `add` so grid + // decoding can safely attach its style to any number of cells. Unlike + // organically built pages, those references do not themselves represent + // cells. Release through the encoded-ID remap, so duplicate values + // which deduplicated to the same native ID each surrender their own + // reference; unused entries then become dead and disappear from + // canonical re-encoding. + var style_it = style_remap.seen.iterator(.{}); + while (style_it.next()) |encoded_id| { + const id = style_remap.entries[encoded_id]; + if (id != 0) page.styles.release(page.memory, id); } // Hyperlink insertion likewise creates one temporary reference for every diff --git a/src/terminal/snapshot/style.zig b/src/terminal/snapshot/style.zig index 176190b75..bd0d445ca 100644 --- a/src/terminal/snapshot/style.zig +++ b/src/terminal/snapshot/style.zig @@ -47,19 +47,14 @@ const std = @import("std"); const test_fixture = @import("fixture.zig"); -const io = @import("io.zig"); const sgr = @import("../sgr.zig"); const terminal_style = @import("../style.zig"); -/// Number of bytes written by `encode`, calculated using the encoder itself -/// so this remains synchronized with the field-by-field wire format. -pub const len = computeLen(); - -comptime { - // This size is part of the wire format. If it changes, the snapshot - // version and golden fixtures must also change. - std.debug.assert(len == 16); -} +/// Number of bytes in one encoded style entry. This size is part of the +/// wire format: the codec reads and writes fixed offsets within an entry +/// of exactly this size, so if the layout changes, the snapshot version +/// and golden fixtures must also change. +pub const len = 16; const Flags = packed struct(u16) { bold: bool = false, @@ -80,8 +75,8 @@ const ColorKind = enum(u8) { rgb = 2, }; -/// Errors possible while decoding one style entry. -pub const DecodeError = std.Io.Reader.Error || error{ +/// Semantic validation errors for one complete style entry buffer. +const ParseError = error{ /// A color kind is not defined by snapshot version 1. InvalidColorKind, @@ -98,14 +93,21 @@ pub const DecodeError = std.Io.Reader.Error || error{ InvalidReserved, }; +/// Errors possible while decoding one style entry. +pub const DecodeError = std.Io.Reader.Error || ParseError; + /// Encode one terminal style as a fixed-size snapshot style entry. +/// +/// The entry is assembled in a fixed buffer and written once, so hot +/// encoders perform a single writer call per style. pub fn encode( value: terminal_style.Style, writer: *std.Io.Writer, ) std.Io.Writer.Error!void { - try encodeColor(value.fg_color, writer); - try encodeColor(value.bg_color, writer); - try encodeColor(value.underline_color, writer); + var encoded: [len]u8 = @splat(0); + encodeColorBuf(encoded[0..4], value.fg_color); + encodeColorBuf(encoded[4..8], value.bg_color); + encodeColorBuf(encoded[8..12], value.underline_color); const flags: Flags = .{ .bold = value.flags.bold, @@ -118,17 +120,26 @@ pub fn encode( .overline = value.flags.overline, .underline = @intFromEnum(value.flags.underline), }; - try io.writeInt(writer, u16, @bitCast(flags)); - try io.writeInt(writer, u16, 0); + std.mem.writeInt(u16, encoded[12..14], @bitCast(flags), .little); + try writer.writeAll(&encoded); } /// Decode and validate one fixed-size snapshot style entry. pub fn decode(reader: *std.Io.Reader) DecodeError!terminal_style.Style { - const fg_color = try decodeColor(reader); - const bg_color = try decodeColor(reader); - const underline_color = try decodeColor(reader); + var encoded: [len]u8 = undefined; + try reader.readSliceAll(&encoded); + return parse(&encoded); +} - const flags: Flags = @bitCast(try io.readInt(reader, u16)); +/// Decode and validate one complete fixed-size style entry buffer. +fn parse(encoded: *const [len]u8) ParseError!terminal_style.Style { + const fg_color = try parseColor(encoded[0..4]); + const bg_color = try parseColor(encoded[4..8]); + const underline_color = try parseColor(encoded[8..12]); + + const flags: Flags = @bitCast( + std.mem.readInt(u16, encoded[12..14], .little), + ); if (flags.reserved != 0) return error.InvalidFlags; const underline = std.enums.fromInt( @@ -136,7 +147,7 @@ pub fn decode(reader: *std.Io.Reader) DecodeError!terminal_style.Style { flags.underline, ) orelse return error.InvalidUnderline; - const reserved = try io.readInt(reader, u16); + const reserved = std.mem.readInt(u16, encoded[14..16], .little); if (reserved != 0) return error.InvalidReserved; return .{ @@ -167,9 +178,7 @@ pub fn decodeOrDiscard( ) DecodeError!terminal_style.Style { var encoded: [len]u8 = undefined; try reader.readSliceAll(&encoded); - - var source: std.Io.Reader = .fixed(&encoded); - return decode(&source); + return parse(&encoded); } /// Decode one complete entry, returning null for invalid semantic contents. @@ -193,11 +202,16 @@ pub fn decodeOrNull( }; } -fn encodeColor( +/// `decodeOrNull` over one already-buffered entry, for enclosing codecs +/// that parse many entries from a flat payload without reader calls. +pub fn parseOrNull(encoded: *const [len]u8) ?terminal_style.Style { + return parse(encoded) catch null; +} + +fn encodeColorBuf( + encoded: *[4]u8, value: terminal_style.Style.Color, - writer: *std.Io.Writer, -) std.Io.Writer.Error!void { - var encoded: [4]u8 = @splat(0); +) void { switch (value) { .none => encoded[0] = @intFromEnum(ColorKind.none), .palette => |index| { @@ -211,23 +225,18 @@ fn encodeColor( encoded[3] = rgb.b; }, } - try writer.writeAll(&encoded); } -fn decodeColor( - reader: *std.Io.Reader, -) DecodeError!terminal_style.Style.Color { - // Colors are always 4 bytes - var encoded: [4]u8 = undefined; - try reader.readSliceAll(&encoded); - +fn parseColor( + encoded: *const [4]u8, +) ParseError!terminal_style.Style.Color { // Kind must be something we know about. const kind = std.enums.fromInt(ColorKind, encoded[0]) orelse { return error.InvalidColorKind; }; return switch (kind) { - .none => if (std.mem.eql(u8, encoded[1..], &.{ 0, 0, 0 })) + .none => if (encoded[1] == 0 and encoded[2] == 0 and encoded[3] == 0) .none else error.InvalidColor, @@ -243,15 +252,6 @@ fn decodeColor( }; } -fn computeLen() usize { - comptime { - var buf: [128]u8 = undefined; - var writer: std.Io.Writer = .fixed(&buf); - encode(.{}, &writer) catch unreachable; - return writer.end; - } -} - const test_golden_fixture = test_fixture.parse(@embedFile("testdata/style-v1.hex")); test "golden encoding and decoding" { From eb09bf82918de51f22b805dc705ed67b2968b984 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 09:21:32 -0700 Subject: [PATCH 076/392] terminal/snapshot: interleave software CRC32C streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slicing tables removed the byte-at-a-time dependency chain, but each 16-byte fold still depends serially on the previous one, leaving the software CRC latency-bound at roughly 2.5-3 GB/s in V8 while snapshot payloads run through it once per direction. wasm has no carry-less multiply, so wider tables are the only classic escape — and measuring slicing-by-32 against interleaving showed the extra 16 KB of tables buys nothing once the chain is hidden. Instead, inputs of 4 KiB and up split into thirds processed as three independent fold chains in one loop, then merge with the GF(2) zero-shift operator: crc(A ++ B, s) = crc(B, 0) XOR zeroShift(crc(A, s), |B|). The shift matrices are comptime, storing only even powers of two (an odd power applies the preceding matrix twice), 4 KB total. Software CRC throughput roughly doubles; hardware backends are untouched, so native is unaffected (tables below are noise). Benchmarks ("prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 2.12 ms | 1.73 ms | 2.59 ms | 2.17 ms | | styled | 2.23 ms | 1.47 ms | 6.43 ms | 5.38 ms | | truecolor | 3.44 ms | 2.30 ms | 8.35 ms | 7.17 ms | | cjk | 5.99 ms | 3.89 ms | 11.39 ms | 9.50 ms | | grapheme | 8.33 ms | 6.94 ms | 10.89 ms | 9.24 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 24.7 ms | 24.8 ms | | ascii | decode | 47.5 ms | 49.2 ms | | utf8 | encode | 41.9 ms | 42.4 ms | | utf8 | decode | 58.8 ms | 59.5 ms | --- src/crc32c.zig | 201 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 171 insertions(+), 30 deletions(-) diff --git a/src/crc32c.zig b/src/crc32c.zig index dbfca7ef2..3d8aaca10 100644 --- a/src/crc32c.zig +++ b/src/crc32c.zig @@ -7,8 +7,7 @@ //! compile time. //! //! Targets without a dedicated instruction, such as WebAssembly, use a -//! slicing-by-16 table implementation that processes sixteen bytes per -//! iteration instead of one. +//! custom implementation that is faster than Zig's stdlib. //! //! The resulting value is identical across all backends: this is the //! iSCSI CRC32C parameter set (reflected, initial and final XOR @@ -149,13 +148,21 @@ const Software = struct { /// The reflected CRC32C (Castagnoli) polynomial. const reflected_poly: u32 = 0x82F63B78; - /// Slicing tables: `tables[i][b]` is the CRC of byte `b` followed - /// by `i` zero bytes. Table zero is the classic one-byte-per-step table; - /// the higher tables let one iteration fold sixteen input bytes with - /// sixteen independent lookups instead of a sixteen-step dependency chain. - const tables: [16][256]u32 = tables: { - @setEvalBranchQuota(100_000); - var result: [16][256]u32 = undefined; + /// Number of slicing tables, which is also the bytes folded per + /// iteration. + const slices = 16; + + /// Inputs below this length use the single-stream pass: the + /// stream-combine matrix work would not pay for itself. + const multi_stream_threshold = 4096; + + /// Slicing tables: `tables[i][b]` is the CRC of byte `b` followed by + /// `i` zero bytes. Table zero is the classic one-byte-per-step table; + /// the higher tables let one iteration fold a whole block with + /// independent lookups instead of a byte-by-byte dependency chain. + const tables: [slices][256]u32 = tables: { + @setEvalBranchQuota(200_000); + var result: [slices][256]u32 = undefined; for (0..256) |n| { var crc: u32 = n; for (0..8) |_| { @@ -163,7 +170,7 @@ const Software = struct { } result[0][n] = crc; } - for (1..16) |i| { + for (1..slices) |i| { for (0..256) |n| { const prev = result[i - 1][n]; result[i][n] = (prev >> 8) ^ result[0][prev & 0xFF]; @@ -172,34 +179,135 @@ const Software = struct { break :tables result; }; - /// One update pass using slicing-by-16: each iteration XORs the running - /// CRC into the first of four little-endian words and folds all sixteen - /// bytes through per-position tables. The remainder finishes one byte per - /// step through table zero. fn update(initial: u32, bytes: []const u8) u32 { + if (bytes.len >= multi_stream_threshold) return updateMulti( + initial, + bytes, + ); + + return updateSingle(initial, bytes); + } + + /// One single-stream update pass using slicing. + fn updateSingle(initial: u32, bytes: []const u8) u32 { const t = &tables; var crc = initial; - var remaining = bytes; - - while (remaining.len >= 16) : (remaining = remaining[16..]) { - const a = std.mem.readInt(u32, remaining[0..4], .little) ^ crc; - const b = std.mem.readInt(u32, remaining[4..8], .little); - const c = std.mem.readInt(u32, remaining[8..12], .little); - const d = std.mem.readInt(u32, remaining[12..16], .little); - crc = t[15][a & 0xFF] ^ t[14][(a >> 8) & 0xFF] ^ - t[13][(a >> 16) & 0xFF] ^ t[12][a >> 24] ^ - t[11][b & 0xFF] ^ t[10][(b >> 8) & 0xFF] ^ - t[9][(b >> 16) & 0xFF] ^ t[8][b >> 24] ^ - t[7][c & 0xFF] ^ t[6][(c >> 8) & 0xFF] ^ - t[5][(c >> 16) & 0xFF] ^ t[4][c >> 24] ^ - t[3][d & 0xFF] ^ t[2][(d >> 8) & 0xFF] ^ - t[1][(d >> 16) & 0xFF] ^ t[0][d >> 24]; + var i: usize = 0; + while (i + slices <= bytes.len) : (i += slices) { + crc = foldChunk(bytes, i, crc); } - for (remaining) |byte| { + for (bytes[i..]) |byte| { crc = (crc >> 8) ^ t[0][(crc ^ byte) & 0xFF]; } return crc; } + + /// One update pass as three independent interleaved streams. Faster + /// for large enough inputs. + fn updateMulti(initial: u32, bytes: []const u8) u32 { + // Both leading parts are block multiples so the interleaved loop + // needs no tail handling; the third part absorbs the remainder. + const part = (bytes.len / 3) & ~@as(usize, slices - 1); + const p0 = bytes[0..part]; + const p1 = bytes[part..][0..part]; + const p2 = bytes[2 * part ..]; + + var s0 = initial; + var s1: u32 = 0; + var s2: u32 = 0; + var i: usize = 0; + while (i + slices <= part) : (i += slices) { + s0 = foldChunk(p0, i, s0); + s1 = foldChunk(p1, i, s1); + s2 = foldChunk(p2, i, s2); + } + s2 = updateSingle(s2, p2[part..]); + + const s01 = s1 ^ zeroShift(s0, p1.len); + return s2 ^ zeroShift(s01, p2.len); + } + + /// Fold one aligned block through the per-position slicing tables. + /// The running CRC must already be XORed into the block's first word. + inline fn foldBlock(comptime len: usize, words: *const [len / 4]u32) u32 { + const t = &tables; + var crc: u32 = 0; + inline for (0..len / 4) |w| { + const word = words[w]; + const base = len - 1 - w * 4; + crc ^= t[base][word & 0xFF] ^ + t[base - 1][(word >> 8) & 0xFF] ^ + t[base - 2][(word >> 16) & 0xFF] ^ + t[base - 3][word >> 24]; + } + return crc; + } + + /// Fold the block starting at `offset`, chaining the running CRC state. + inline fn foldChunk(bytes: []const u8, offset: usize, crc: u32) u32 { + var words: [slices / 4]u32 = undefined; + inline for (&words, 0..) |*word, w| { + word.* = std.mem.readInt( + u32, + bytes[offset + w * 4 ..][0..4], + .little, + ); + } + words[0] ^= crc; + return foldBlock(slices, &words); + } + + /// Advance a CRC state as if `len` zero bytes had been processed. + fn zeroShift(state: u32, len: usize) u32 { + var s = state; + var remaining = len; + var k: usize = 0; + while (remaining != 0) : ({ + remaining >>= 1; + k += 1; + }) { + if (remaining & 1 != 0) { + const mat = &zero_shift_matrices[k / 2]; + s = matTimesVec(mat, s); + if (k % 2 != 0) s = matTimesVec(mat, s); + } + } + return s; + } + + /// Multiply the GF(2) matrix by a CRC state column vector. + inline fn matTimesVec(mat: *const [32]u32, vec: u32) u32 { + var sum: u32 = 0; + var v = vec; + var i: usize = 0; + while (v != 0) : ({ + v >>= 1; + i += 1; + }) { + if (v & 1 != 0) sum ^= mat[i]; + } + return sum; + } + + const zero_shift_matrices: [32][32]u32 = matrices: { + @setEvalBranchQuota(500_000); + var matrices: [32][32]u32 = undefined; + var previous: [32]u32 = undefined; + for (0..32) |i| { + const unit: u32 = 1 << i; + previous[i] = (unit >> 8) ^ tables[0][unit & 0xFF]; + } + matrices[0] = previous; + for (1..64) |k| { + var squared: [32]u32 = undefined; + for (0..32) |i| { + squared[i] = matTimesVec(&previous, previous[i]); + } + previous = squared; + if (k % 2 == 0) matrices[k / 2] = squared; + } + break :matrices matrices; + }; }; /// The standard-library implementation of the same parameter set. This is @@ -238,6 +346,39 @@ test "software slicing matches the standard library" { } } +test "software multi-stream matches the standard library" { + // Lengths around and far above the interleaving threshold, plus odd + // remainders, so all three streams and both combine steps are covered. + var bytes: [96 * 1024]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0x3517_1A3B); + prng.random().bytes(&bytes); + + for ([_]usize{ + Software.multi_stream_threshold - 1, + Software.multi_stream_threshold, + Software.multi_stream_threshold + 1, + Software.multi_stream_threshold + 97, + 12 * 1024, + 64 * 1024 + 31, + bytes.len, + }) |len| { + const input = bytes[0..len]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(input); + try std.testing.expectEqual( + reference.crc, + Software.update(0xFFFF_FFFF, input), + ); + + // Continuation across a split inside the multi-stream range. + const first = Software.update(0xFFFF_FFFF, input[0 .. len / 2]); + try std.testing.expectEqual( + reference.crc, + Software.update(first, input[len / 2 ..]), + ); + } +} + test "matches the check value" { // The catalog check value for CRC-32/ISCSI. try std.testing.expectEqual( From 433b16bcb1226a5d840318f88a1fb59e089b0649 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 14:05:14 -0700 Subject: [PATCH 077/392] terminal/apc: limit glyf decode allocations Limit individual allocations made while decoding registered glyf outlines to 64 KB. Carefully crafted glyf outlines could expand into ~768KB of memory per glossary entry, which adds up to hundreds of MB per terminal surface. Across many terminals this could cause issues. The 64KB number was chosen by inspecting every glyph across Apple symbols and Noto emoji and the largest single glyph found was 40KB. So, 64KB is generous while limiting each terminal to ~68MB of RAM for max glyph glossaries. --- src/terminal/apc/glyph.zig | 4 +- src/terminal/apc/glyph/Glossary.zig | 73 ++++++++++++++++++++++++++++- src/terminal/apc/glyph/request.zig | 29 ++++++++++-- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/terminal/apc/glyph.zig b/src/terminal/apc/glyph.zig index 13454a3f3..e86ee9f8a 100644 --- a/src/terminal/apc/glyph.zig +++ b/src/terminal/apc/glyph.zig @@ -133,9 +133,7 @@ //! ## Glossary Capacity //! //! Each session holds at most 1024 registrations keyed by codepoint. -//! Registrations live for the session duration. A 1025th registration -//! evicts the oldest entry (FIFO). Sessions are isolated: two tabs may -//! independently register the same codepoint. +//! Each allocation made by the `glyf` decoder is limited to 64 KiB. //! //! ## Security: PUA-Only Restriction //! diff --git a/src/terminal/apc/glyph/Glossary.zig b/src/terminal/apc/glyph/Glossary.zig index 28458dbb2..7200d162a 100644 --- a/src/terminal/apc/glyph/Glossary.zig +++ b/src/terminal/apc/glyph/Glossary.zig @@ -20,6 +20,11 @@ const Constraint = FontGlyph.RenderOptions.Constraint; /// Defined by the specification. pub const max_entries = 1024; +/// Maximum size of any allocation made while decoding a glyf registration. +/// This is limited by the dynamic allocations not the struct size so the +/// actual size allowed is slightly larger, but worst case only by 10KB. +pub const max_glyf_allocation = 64 * 1024; // 64 KiB + /// An empty glossary with no registered glyphs. pub const empty: Glossary = .{ .entries = .empty }; @@ -170,7 +175,7 @@ pub const Entry = struct { // Decode the payload into some usable glyph format for // future rasterization. const glyph: Glyph = switch (fmt) { - .glyf => .{ .glyf = try req.decodeGlyfPayload(alloc) }, + .glyf => .{ .glyf = try req.decodeGlyfPayload(alloc, max_glyf_allocation) }, .colrv0, .colrv1 => return error.UnsupportedFormat, }; @@ -273,6 +278,47 @@ fn testParseRegister(alloc: Allocator, data: []const u8) !RegisterReq { // and three on-curve points. const test_triangle_glyf_payload = "AAEAZABkA4QDhAACAAABAQEB9P5wAyADhPzgAAA="; +fn testRepeatedPointRegisterReq( + alloc: Allocator, + end_points: []const u16, +) !RegisterReq { + assert(end_points.len > 0); + + var decoded: std.ArrayList(u8) = .empty; + defer decoded.deinit(alloc); + + // Simple glyph header with zero bounds. + const contour_count: u16 = @intCast(end_points.len); + try decoded.appendSlice(alloc, &.{ @truncate(contour_count >> 8), @truncate(contour_count) }); + try decoded.appendNTimes(alloc, 0, 8); + for (end_points) |end_point| try decoded.appendSlice( + alloc, + &.{ @truncate(end_point >> 8), @truncate(end_point) }, + ); + try decoded.appendSlice(alloc, &.{ 0x00, 0x00 }); // No instructions. + + // Maximum-length runs describe on-curve, zero-delta points compactly. + var remaining: usize = @as(usize, end_points[end_points.len - 1]) + 1; + while (remaining > 0) { + const run = @min(remaining, 256); + if (run == 1) { + try decoded.append(alloc, 0x31); + } else { + try decoded.appendSlice(alloc, &.{ 0x39, @intCast(run - 1) }); + } + remaining -= run; + } + + const Encoder = std.base64.standard.Encoder; + const payload = try alloc.alloc(u8, Encoder.calcSize(decoded.items.len)); + defer alloc.free(payload); + const encoded = Encoder.encode(payload, decoded.items); + + const command = try std.fmt.allocPrint(alloc, "r;cp=e000;{s}", .{encoded}); + defer alloc.free(command); + return try testParseRegister(alloc, command); +} + fn testRegisterReq(alloc: Allocator, cp: u21) !RegisterReq { const data = try std.fmt.allocPrint( alloc, @@ -330,6 +376,31 @@ test "Entry init rejects invalid register payload" { try testing.expectError(error.MalformedPayload, Entry.init(alloc, req)); } +test "Entry init rejects glyf allocation over limit" { + const testing = std.testing; + const alloc = testing.allocator; + + // 5,462 points require a 65,544-byte points allocation. + const req = try testRepeatedPointRegisterReq(alloc, &.{5461}); + defer alloc.free(req.raw); + + try testing.expectError(error.PayloadTooLarge, Entry.init(alloc, req)); +} + +test "Entry init accepts largest glyf points allocation under limit" { + const testing = std.testing; + const alloc = testing.allocator; + + // 5,461 points require a 65,532-byte points allocation. + const req = try testRepeatedPointRegisterReq(alloc, &.{ 0, 5460 }); + defer alloc.free(req.raw); + + var entry = try Entry.init(alloc, req); + defer entry.deinit(alloc); + try testing.expectEqual(@as(usize, 5461), entry.glyph.glyf.points.len); + try testing.expectEqual(@as(usize, 2), entry.glyph.glyf.contours.len); +} + test "Glossary register overwrites and moves entry to newest position" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/apc/glyph/request.zig b/src/terminal/apc/glyph/request.zig index 7bf4b6ba9..6b6d7ccd2 100644 --- a/src/terminal/apc/glyph/request.zig +++ b/src/terminal/apc/glyph/request.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const LimitedAllocator = @import("../../../datastruct/main.zig").LimitedAllocator; const Glyf = @import("../../../font/opentype/glyf.zig").Glyf; const fraction = @import("../../fraction.zig"); @@ -272,7 +273,11 @@ pub const Request = union(enum) { }; /// Decode this request's base64 glyf payload into an owned outline. - pub fn decodeGlyfPayload(self: Register, alloc: Allocator) DecodeError!Glyf.Outline { + pub fn decodeGlyfPayload( + self: Register, + alloc: Allocator, + max_allocation_bytes: usize, + ) DecodeError!Glyf.Outline { // Prep base64 decoding, initial validation. const Decoder = std.base64.standard.Decoder; const payload_bytes = self.payload(); @@ -300,8 +305,16 @@ pub const Request = union(enum) { // decode call below. Glyf.Entry.decode returns an owned Outline, so // it is safe to free `data` before returning that outline. const glyf_entry = Glyf.Entry.init(data) catch return error.MalformedPayload; - return glyf_entry.decode(alloc) catch |err| switch (err) { - error.OutOfMemory => error.OutOfMemory, + + // The points slice is the dominant decoded allocation. Limiting + // every allocation made by the decoder prevents a compact record + // from expanding into an unbounded persistent outline. + var limited: LimitedAllocator = .init(alloc, max_allocation_bytes); + return glyf_entry.decode(limited.allocator()) catch |err| switch (err) { + error.OutOfMemory => if (limited.limit_exceeded) + error.PayloadTooLarge + else + error.OutOfMemory, // Unsupported fields error.CompositeNotSupported => error.CompositeUnsupported, error.InstructionsNotSupported => error.HintingUnsupported, @@ -789,7 +802,10 @@ test "register decodes glyf payload" { var cmd = try testParse(testing.allocator, "r;cp=e0a0;AAAAAAAAAAAAAA=="); defer cmd.deinit(testing.allocator); - var outline = try cmd.register.decodeGlyfPayload(testing.allocator); + var outline = try cmd.register.decodeGlyfPayload( + testing.allocator, + std.math.maxInt(usize), + ); defer outline.deinit(testing.allocator); try testing.expectEqual(@as(usize, 0), outline.points.len); @@ -802,7 +818,10 @@ test "register rejects malformed glyf payload" { var cmd = try testParse(testing.allocator, "r;cp=e0a0;%%%not-base64%%%"); defer cmd.deinit(testing.allocator); - try testing.expectError(error.MalformedPayload, cmd.register.decodeGlyfPayload(testing.allocator)); + try testing.expectError( + error.MalformedPayload, + cmd.register.decodeGlyfPayload(testing.allocator, std.math.maxInt(usize)), + ); } test "register response without payload" { From f2897f31dec839352302369b3cb8009c4cec180b Mon Sep 17 00:00:00 2001 From: mitchellh <1299+mitchellh@users.noreply.github.com> Date: Sun, 16 Aug 2026 00:25:02 +0000 Subject: [PATCH 078/392] deps: Update iTerm2 color schemes --- build.zig.zon | 2 +- build.zig.zon.json | 4 ++-- build.zig.zon.nix | 4 ++-- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index f97fddc23..c737ea9f1 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -125,7 +125,7 @@ .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .android_ndk = .{ .path = "./pkg/android-ndk" }, .iterm2_themes = .{ - .url = "https://deps.files.ghostty.org/ghostty-themes-release-20260803-155300-875a82f.tgz", + .url = "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz", .hash = "N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl", .lazy = true, }, diff --git a/build.zig.zon.json b/build.zig.zon.json index 9c6e316bf..a64ea3eea 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -61,8 +61,8 @@ }, "N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl": { "name": "iterm2_themes", - "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260803-155300-875a82f.tgz", - "hash": "sha256-/qxLa9S8bSg2NAbJzwEtZhGvOxaPTAVFNWnBCs/MFBs=" + "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz", + "hash": "sha256-6ph4RxQg7lsS5/L/SACZyVTqUOVzob34P0PhBcm+Y/A=" }, "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": { "name": "jetbrains_mono", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 01bc76f67..0fb3b081c 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -209,8 +209,8 @@ in name = "N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl"; path = fetchZigArtifact { name = "iterm2_themes"; - url = "https://deps.files.ghostty.org/ghostty-themes-release-20260803-155300-875a82f.tgz"; - hash = "sha256-/qxLa9S8bSg2NAbJzwEtZhGvOxaPTAVFNWnBCs/MFBs="; + url = "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz"; + hash = "sha256-6ph4RxQg7lsS5/L/SACZyVTqUOVzob34P0PhBcm+Y/A="; unpack = false; }; } diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 9c726c1fc..58d99dfc3 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -9,7 +9,7 @@ https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918 https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz https://deps.files.ghostty.org/gettext-0.24.tar.gz -https://deps.files.ghostty.org/ghostty-themes-release-20260803-155300-875a82f.tgz +https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index d03df3d72..b69d9931b 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -73,9 +73,9 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260803-155300-875a82f.tgz", + "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz", "dest": "vendor/p/N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl", - "sha256": "feac4b6bd4bc6d28363406c9cf012d6611af3b168f4c05453569c10acfcc141b" + "sha256": "ea9878471420ee5b12e7f2ff480099c954ea50e573a1bdf83f43e105c9be63f0" }, { "type": "archive", From b4079f00c8946207e4db8571e3209f2de7ac4a27 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 20:52:38 -0700 Subject: [PATCH 079/392] libghostty: add render state structured cursor read A normal renderer would have to call `ghostty_render_state_get` _eight times_ to reconstruct the cursor. In languages where FFI is expensive (Go, wasm, etc.), this showed up in profiles of every frame. Add a sized cursor snapshot and expose it. Also expose the existing color snapshot through ghostty_render_state_get and remove the older dedicated color getter. --- example/c-vt-render/src/main.c | 37 ++--- include/ghostty/vt/render.h | 79 +++++++--- src/lib_vt.zig | 1 - src/terminal/c/main.zig | 1 - src/terminal/c/render.zig | 257 +++++++++++++++++++++++++++++++-- src/terminal/c/types.zig | 14 ++ 6 files changed, 324 insertions(+), 65 deletions(-) diff --git a/example/c-vt-render/src/main.c b/example/c-vt-render/src/main.c index cb9e7e8c2..ede168033 100644 --- a/example/c-vt-render/src/main.c +++ b/example/c-vt-render/src/main.c @@ -97,7 +97,8 @@ int main(void) { // state. These are needed to resolve palette-indexed cell colors. GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors); - result = ghostty_render_state_colors_get(render_state, &colors); + result = ghostty_render_state_get( + render_state, GHOSTTY_RENDER_STATE_DATA_COLORS, &colors); assert(result == GHOSTTY_SUCCESS); printf("Background: #%02x%02x%02x\n", @@ -107,31 +108,16 @@ int main(void) { //! [render-colors] //! [render-cursor] - // Read cursor position and visual style from the render state. - bool cursor_visible = false; - ghostty_render_state_get( - render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE, - &cursor_visible); - - bool cursor_in_viewport = false; - ghostty_render_state_get( - render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE, - &cursor_in_viewport); - - if (cursor_visible && cursor_in_viewport) { - uint16_t cx, cy; - ghostty_render_state_get( - render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X, &cx); - ghostty_render_state_get( - render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y, &cy); - - GhosttyRenderStateCursorVisualStyle style; - ghostty_render_state_get( - render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE, - &style); + // Read all cursor state in one call. + GhosttyRenderStateCursor cursor = + GHOSTTY_INIT_SIZED(GhosttyRenderStateCursor); + result = ghostty_render_state_get( + render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR, &cursor); + assert(result == GHOSTTY_SUCCESS); + if (cursor.visible && cursor.viewport_has_value) { const char* style_name = "unknown"; - switch (style) { + switch (cursor.visual_style) { case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR: style_name = "bar"; break; @@ -145,7 +131,8 @@ int main(void) { style_name = "hollow"; break; } - printf("Cursor at (%u, %u), style: %s\n", cx, cy, style_name); + printf("Cursor at (%u, %u), style: %s\n", + cursor.viewport_x, cursor.viewport_y, style_name); } //! [render-cursor] diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 044d1734b..5329a1d9a 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -199,6 +199,14 @@ typedef enum GHOSTTY_ENUM_TYPED { /** Whether the cursor is on the tail of a wide character (bool). * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL = 17, + + /** All cursor state in one sized struct (GhosttyRenderStateCursor). + * Initialize the output with GHOSTTY_INIT_SIZED before querying. */ + GHOSTTY_RENDER_STATE_DATA_CURSOR = 18, + + /** All render-state colors in one sized struct (GhosttyRenderStateColors). + * Initialize the output with GHOSTTY_INIT_SIZED before querying. */ + GHOSTTY_RENDER_STATE_DATA_COLORS = 19, GHOSTTY_RENDER_STATE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyRenderStateData; @@ -286,17 +294,59 @@ typedef struct { uint16_t end_x; } GhosttyRenderStateRowSelection; +/** + * Render-state cursor information. + * + * This struct uses the sized-struct ABI pattern. Initialize with + * GHOSTTY_INIT_SIZED(GhosttyRenderStateCursor) before querying + * GHOSTTY_RENDER_STATE_DATA_CURSOR. + * + * When viewport_has_value is false, viewport_x, viewport_y, and wide_tail + * contain undefined data and must not be read. + * + * @ingroup render + */ +typedef struct { + /** Size of this struct in bytes. Must be set to sizeof(GhosttyRenderStateCursor). */ + size_t size; + + /** Whether the cursor is visible within the viewport. */ + bool viewport_has_value; + + /** Cursor viewport x position in cells. */ + uint16_t viewport_x; + + /** Cursor viewport y position in cells. */ + uint16_t viewport_y; + + /** Whether the cursor is on the tail of a wide character. */ + bool wide_tail; + + /** Whether the cursor is visible based on terminal modes. */ + bool visible; + + /** Whether the cursor should blink based on terminal modes. */ + bool blinking; + + /** Whether the cursor is at a password input field. */ + bool password_input; + + /** The visual style of the cursor. */ + GhosttyRenderStateCursorVisualStyle visual_style; +} GhosttyRenderStateCursor; + /** * Render-state color information. * * This struct uses the sized-struct ABI pattern. Initialize with - * GHOSTTY_INIT_SIZED(GhosttyRenderStateColors) before calling - * ghostty_render_state_colors_get(). + * GHOSTTY_INIT_SIZED(GhosttyRenderStateColors) before querying + * GHOSTTY_RENDER_STATE_DATA_COLORS. * * Example: * @code * GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors); - * GhosttyResult result = ghostty_render_state_colors_get(state, &colors); + * GhosttyResult result = ghostty_render_state_get( + * state, GHOSTTY_RENDER_STATE_DATA_COLORS, &colors); * @endcode * * @ingroup render @@ -430,8 +480,9 @@ GHOSTTY_API GhosttyResult ghostty_render_state_end_update(GhosttyRenderState sta * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) * @param data The data kind to query * @param[out] out Pointer to receive the queried value - * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is - * NULL or `data` is not a recognized enum value + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or + * `out` is NULL, `data` is not a recognized enum value, or a sized + * output struct is smaller than `sizeof(size_t)` * * @ingroup render */ @@ -486,24 +537,6 @@ GHOSTTY_API GhosttyResult ghostty_render_state_set(GhosttyRenderState state, GhosttyRenderStateOption option, const void* value); -/** - * Get the current color information from a render state. - * - * This writes as many fields as fit in the caller-provided sized struct. - * `out_colors->size` must be set by the caller (typically via - * GHOSTTY_INIT_SIZED(GhosttyRenderStateColors)). - * - * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) - * @param[out] out_colors Sized output struct to receive render-state colors - * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or - * `out_colors` is NULL, or if `out_colors->size` is smaller than - * `sizeof(size_t)` - * - * @ingroup render - */ -GHOSTTY_API GhosttyResult ghostty_render_state_colors_get(GhosttyRenderState state, - GhosttyRenderStateColors* out_colors); - /** * Create a new row iterator instance. * diff --git a/src/lib_vt.zig b/src/lib_vt.zig index f7ce5b386..8faf86471 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -286,7 +286,6 @@ comptime { @export(&c.render_state_get, .{ .name = "ghostty_render_state_get" }); @export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" }); @export(&c.render_state_set, .{ .name = "ghostty_render_state_set" }); - @export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" }); @export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" }); @export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" }); @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index 3615984cc..d45bbfe26 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -90,7 +90,6 @@ pub const render_state_end_update = render.end_update; pub const render_state_get = render.get; pub const render_state_get_multi = render.get_multi; pub const render_state_set = render.set; -pub const render_state_colors_get = render.colors_get; pub const render_state_row_iterator_new = render.row_iterator_new; pub const render_state_row_iterator_next = render.row_iterator_next; pub const render_state_row_get = render.row_get; diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index c5383558c..377767532 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -100,6 +100,19 @@ pub const CursorVisualStyle = enum(c_int) { } }; +/// C: GhosttyRenderStateCursor +pub const Cursor = extern struct { + size: usize = @sizeOf(Cursor), + viewport_has_value: bool, + viewport_x: u16, + viewport_y: u16, + wide_tail: bool, + visible: bool, + blinking: bool, + password_input: bool, + visual_style: CursorVisualStyle, +}; + /// C: GhosttyRenderStateData pub const Data = enum(c_int) { invalid = 0, @@ -120,6 +133,8 @@ pub const Data = enum(c_int) { cursor_viewport_x = 15, cursor_viewport_y = 16, cursor_viewport_wide_tail = 17, + cursor = 18, + colors = 19, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: Data) type { @@ -135,6 +150,8 @@ pub const Data = enum(c_int) { .cursor_visible, .cursor_blinking, .cursor_password_input => bool, .cursor_viewport_has_value, .cursor_viewport_wide_tail => bool, .cursor_viewport_x, .cursor_viewport_y => size.CellCountInt, + .cursor => Cursor, + .colors => Colors, }; } }; @@ -270,12 +287,13 @@ inline fn getDispatch( }; } + const out_ptr = out orelse return .invalid_value; return switch (data) { .invalid => .invalid_value, inline else => |comptime_data| getTyped( state, comptime_data, - @ptrCast(@alignCast(out)), + @ptrCast(@alignCast(out_ptr)), ), }; } @@ -328,6 +346,8 @@ fn getTyped( const vp = state.state.cursor.viewport orelse return .invalid_value; out.* = vp.wide_tail; }, + .cursor => return writeCursor(state, out), + .colors => return writeColors(state, out), } return .success; @@ -367,12 +387,87 @@ fn setTyped( return .success; } -pub fn colors_get( - state_: RenderState, - out_colors_: ?*Colors, -) callconv(lib.calling_conv) Result { - const state = state_ orelse return .invalid_value; - const out_colors = out_colors_ orelse return .invalid_value; +fn writeCursor( + state: *RenderStateWrapper, + out_cursor: *Cursor, +) Result { + const out_size = out_cursor.size; + if (out_size < @sizeOf(usize)) return .invalid_value; + + const cursor = &state.state.cursor; + if (lib.structSizedFieldFits( + Cursor, + out_size, + "viewport_has_value", + )) { + out_cursor.viewport_has_value = cursor.viewport != null; + } + + if (cursor.viewport) |viewport| { + if (lib.structSizedFieldFits( + Cursor, + out_size, + "viewport_x", + )) { + out_cursor.viewport_x = viewport.x; + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "viewport_y", + )) { + out_cursor.viewport_y = viewport.y; + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "wide_tail", + )) { + out_cursor.wide_tail = viewport.wide_tail; + } + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "visible", + )) { + out_cursor.visible = cursor.visible; + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "blinking", + )) { + out_cursor.blinking = cursor.blinking; + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "password_input", + )) { + out_cursor.password_input = cursor.password_input; + } + + if (lib.structSizedFieldFits( + Cursor, + out_size, + "visual_style", + )) { + out_cursor.visual_style = CursorVisualStyle.fromCursorStyle(cursor.visual_style); + } + + return .success; +} + +fn writeColors( + state: *RenderStateWrapper, + out_colors: *Colors, +) Result { const out_size = out_colors.size; if (out_size < @sizeOf(usize)) return .invalid_value; @@ -997,6 +1092,14 @@ test "render: begin/end update" { test "render: get invalid value" { var cols: size.CellCountInt = 0; try testing.expectEqual(Result.invalid_value, get(null, .cols, @ptrCast(&cols))); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + try testing.expectEqual(Result.invalid_value, get(state, .cols, null)); } test "render: get invalid data" { @@ -1010,7 +1113,7 @@ test "render: get invalid data" { try testing.expectEqual(Result.invalid_value, get(state, .invalid, null)); } -test "render: colors get invalid value" { +test "render: aggregate get invalid value" { var state: RenderState = null; try testing.expectEqual(Result.success, new( &lib.alloc.test_allocator, @@ -1021,11 +1124,20 @@ test "render: colors get invalid value" { var colors: Colors = std.mem.zeroes(Colors); colors.size = @sizeOf(Colors); - try testing.expectEqual(Result.invalid_value, colors_get(null, &colors)); - try testing.expectEqual(Result.invalid_value, colors_get(state, null)); + try testing.expectEqual(Result.invalid_value, get(null, .colors, &colors)); + try testing.expectEqual(Result.invalid_value, get(state, .colors, null)); colors.size = @sizeOf(usize) - 1; - try testing.expectEqual(Result.invalid_value, colors_get(state, &colors)); + try testing.expectEqual(Result.invalid_value, get(state, .colors, &colors)); + + var cursor: Cursor = std.mem.zeroes(Cursor); + cursor.size = @sizeOf(Cursor); + + try testing.expectEqual(Result.invalid_value, get(null, .cursor, &cursor)); + try testing.expectEqual(Result.invalid_value, get(state, .cursor, null)); + + cursor.size = @sizeOf(usize) - 1; + try testing.expectEqual(Result.invalid_value, get(state, .cursor, &cursor)); } test "render: get/set dirty invalid value" { @@ -1738,7 +1850,7 @@ test "render: update" { try testing.expectEqual(@as(size.CellCountInt, 24), rows_val); } -test "render: colors get" { +test "render: colors data get" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, @@ -1759,7 +1871,7 @@ test "render: colors get" { var colors: Colors = std.mem.zeroes(Colors); colors.size = @sizeOf(Colors); - try testing.expectEqual(Result.success, colors_get(state, &colors)); + try testing.expectEqual(Result.success, get(state, .colors, &colors)); const state_colors = &state.?.state.colors; try testing.expectEqual(state_colors.background.cval(), colors.background); @@ -1777,6 +1889,121 @@ test "render: colors get" { } } +test "render: cursor data get matches scalar getters" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + 80, + 24, + )); + defer terminal_c.free(terminal); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + + try testing.expectEqual(Result.success, update(state, terminal)); + + var cursor: Cursor = std.mem.zeroes(Cursor); + cursor.size = @sizeOf(Cursor); + try testing.expectEqual(Result.success, get(state, .cursor, &cursor)); + + var viewport_has_value: bool = undefined; + var viewport_x: u16 = undefined; + var viewport_y: u16 = undefined; + var wide_tail: bool = undefined; + var visible: bool = undefined; + var blinking: bool = undefined; + var password_input: bool = undefined; + var visual_style: CursorVisualStyle = undefined; + try testing.expectEqual(Result.success, get(state, .cursor_viewport_has_value, &viewport_has_value)); + try testing.expectEqual(Result.success, get(state, .cursor_viewport_x, &viewport_x)); + try testing.expectEqual(Result.success, get(state, .cursor_viewport_y, &viewport_y)); + try testing.expectEqual(Result.success, get(state, .cursor_viewport_wide_tail, &wide_tail)); + try testing.expectEqual(Result.success, get(state, .cursor_visible, &visible)); + try testing.expectEqual(Result.success, get(state, .cursor_blinking, &blinking)); + try testing.expectEqual(Result.success, get(state, .cursor_password_input, &password_input)); + try testing.expectEqual(Result.success, get(state, .cursor_visual_style, &visual_style)); + + try testing.expectEqual(viewport_has_value, cursor.viewport_has_value); + try testing.expectEqual(viewport_x, cursor.viewport_x); + try testing.expectEqual(viewport_y, cursor.viewport_y); + try testing.expectEqual(wide_tail, cursor.wide_tail); + try testing.expectEqual(visible, cursor.visible); + try testing.expectEqual(blinking, cursor.blinking); + try testing.expectEqual(password_input, cursor.password_input); + try testing.expectEqual(visual_style, cursor.visual_style); +} + +test "render: cursor data get without viewport" { + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + + state.?.state.cursor.viewport = null; + state.?.state.cursor.visible = true; + state.?.state.cursor.blinking = true; + state.?.state.cursor.password_input = true; + state.?.state.cursor.visual_style = .underline; + + var cursor: Cursor = std.mem.zeroes(Cursor); + cursor.size = @sizeOf(Cursor); + cursor.viewport_x = 0xAAAA; + cursor.viewport_y = 0xBBBB; + cursor.wide_tail = true; + try testing.expectEqual(Result.success, get(state, .cursor, &cursor)); + + try testing.expect(!cursor.viewport_has_value); + try testing.expectEqual(@as(u16, 0xAAAA), cursor.viewport_x); + try testing.expectEqual(@as(u16, 0xBBBB), cursor.viewport_y); + try testing.expect(cursor.wide_tail); + try testing.expect(cursor.visible); + try testing.expect(cursor.blinking); + try testing.expect(cursor.password_input); + try testing.expectEqual(CursorVisualStyle.underline, cursor.visual_style); +} + +test "render: cursor data get supports truncated sized struct" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + 80, + 24, + )); + defer terminal_c.free(terminal); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + + try testing.expectEqual(Result.success, update(state, terminal)); + const expected = state.?.state.cursor; + const viewport = expected.viewport.?; + + var cursor: Cursor = std.mem.zeroes(Cursor); + cursor.size = @offsetOf(Cursor, "viewport_y") + @sizeOf(u16); + cursor.wide_tail = !viewport.wide_tail; + cursor.visible = !expected.visible; + try testing.expectEqual(Result.success, get(state, .cursor, &cursor)); + + try testing.expect(cursor.viewport_has_value); + try testing.expectEqual(viewport.x, cursor.viewport_x); + try testing.expectEqual(viewport.y, cursor.viewport_y); + try testing.expectEqual(!viewport.wide_tail, cursor.wide_tail); + try testing.expectEqual(!expected.visible, cursor.visible); +} + test "render: row cells bg_color no background" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( @@ -2020,7 +2247,7 @@ test "render: row cells fg_color from style" { try testing.expectEqual(@as(u8, 30), fg.b); } -test "render: colors get supports truncated sized struct" { +test "render: colors data get supports truncated sized struct" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, @@ -2044,7 +2271,7 @@ test "render: colors get supports truncated sized struct" { for (&colors.palette) |*entry| entry.* = sentinel; colors.size = @offsetOf(Colors, "palette") + @sizeOf(colorpkg.RGB.C) * 2; - try testing.expectEqual(Result.success, colors_get(state, &colors)); + try testing.expectEqual(Result.success, get(state, .colors, &colors)); const state_colors = &state.?.state.colors; try testing.expectEqual(state_colors.palette[0].cval(), colors.palette[0]); diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig index 2f8a011cf..681556666 100644 --- a/src/terminal/c/types.zig +++ b/src/terminal/c/types.zig @@ -70,6 +70,7 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: { .{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) }, .{ "GhosttyReader", StructInfo.init(io.Reader) }, .{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) }, + .{ "GhosttyRenderStateCursor", StructInfo.init(render.Cursor) }, .{ "GhosttyRenderStateRowSelection", StructInfo.init(render.RowSelection) }, .{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) }, .{ "GhosttySelectionGestureGeometry", StructInfo.init(selection_gesture.Geometry) }, @@ -257,6 +258,7 @@ test "json parses" { "GhosttyPointCoordinate", "GhosttyReader", "GhosttyRenderStateColors", + "GhosttyRenderStateCursor", "GhosttyRenderStateRowSelection", "GhosttySelection", "GhosttySelectionGestureBehaviors", @@ -309,6 +311,18 @@ test "json parses" { try std.testing.expect(unknown_string_fields.contains("truncated")); try std.testing.expect(unknown_string_fields.contains("content")); + const render_cursor_fields = root.get("GhosttyRenderStateCursor").?.object + .get("fields").?.object; + try std.testing.expect(render_cursor_fields.contains("size")); + try std.testing.expect(render_cursor_fields.contains("viewport_has_value")); + try std.testing.expect(render_cursor_fields.contains("viewport_x")); + try std.testing.expect(render_cursor_fields.contains("viewport_y")); + try std.testing.expect(render_cursor_fields.contains("wide_tail")); + try std.testing.expect(render_cursor_fields.contains("visible")); + try std.testing.expect(render_cursor_fields.contains("blinking")); + try std.testing.expect(render_cursor_fields.contains("password_input")); + try std.testing.expect(render_cursor_fields.contains("visual_style")); + const reader_fields = root.get("GhosttyReader").?.object .get("fields").?.object; try std.testing.expect(reader_fields.contains("read")); From 0d37f2d34dabd6bc6f7cf094e0d6466186b8c783 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 20:56:36 -0700 Subject: [PATCH 080/392] libghostty: add dedicated dirty row iteration + clear functions Add render state C APIs for iterating only rows that require a redraw and for marking a completed frame clean in one call. A one-row update in a 24-row viewport reduces dirty-row discovery from 50 calls to two, while cleanup becomes one call instead of O(N) of rows. This lower call count is massive for environments where FFI is expensive (Go, wasm). The dirty next API outputs the viewport y because it jumps, unlike the normal sequential next where its trivial for a caller to keep track. --- example/c-vt-render/src/main.c | 27 ++---- include/ghostty/vt/render.h | 54 +++++++++++- src/lib_vt.zig | 2 + src/terminal/c/main.zig | 2 + src/terminal/c/render.zig | 153 +++++++++++++++++++++++++++++++++ src/terminal/render.zig | 10 +++ 6 files changed, 223 insertions(+), 25 deletions(-) diff --git a/example/c-vt-render/src/main.c b/example/c-vt-render/src/main.c index cb9e7e8c2..ad4df8293 100644 --- a/example/c-vt-render/src/main.c +++ b/example/c-vt-render/src/main.c @@ -165,15 +165,9 @@ int main(void) { result = ghostty_render_state_row_cells_new(NULL, &cells); assert(result == GHOSTTY_SUCCESS); - int row_index = 0; - while (ghostty_render_state_row_iterator_next(row_iter)) { - // Check per-row dirty state; a real renderer would skip clean rows. - bool row_dirty = false; - ghostty_render_state_row_get( - row_iter, GHOSTTY_RENDER_STATE_ROW_DATA_DIRTY, &row_dirty); - - printf("Row %2d [%s]: ", row_index, - row_dirty ? "dirty" : "clean"); + uint16_t row_y = 0; + while (ghostty_render_state_row_iterator_next_dirty(row_iter, &row_y)) { + printf("Row %2u [dirty]: ", row_y); // Query the row-local selection range. Rows without a selection return // GHOSTTY_NO_VALUE; selected rows return inclusive start/end columns. @@ -239,22 +233,13 @@ int main(void) { } printf("\n"); - - // Clear per-row dirty flag after "rendering" it. - bool clean = false; - ghostty_render_state_row_set( - row_iter, GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY, &clean); - - row_index++; } //! [render-row-iterate] //! [render-dirty-reset] - // After finishing the frame, reset the global dirty state so the next - // update can report changes accurately. - GhosttyRenderStateDirty clean_state = GHOSTTY_RENDER_STATE_DIRTY_FALSE; - result = ghostty_render_state_set( - render_state, GHOSTTY_RENDER_STATE_OPTION_DIRTY, &clean_state); + // After successfully rendering the complete frame, clear both the global + // and per-row dirty state in one call. + result = ghostty_render_state_clean(render_state); assert(result == GHOSTTY_SUCCESS); //! [render-dirty-reset] diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 044d1734b..6dcf97147 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -63,7 +63,10 @@ extern "C" { * tracking which rows in a partially dirty frame have changed. * * The user of the render state API is expected to unset both of these. - * The `update` call does not unset dirty state, it only updates it. + * The `update` call does not unset dirty state, it only updates it. After + * successfully rendering a complete frame, use ghostty_render_state_clean() + * to unset both layers in one call. The granular setters remain available + * for callers that only consume part of a frame. * * An extremely important detail: setting one dirty state doesn't unset * the other. For example, setting the global dirty state to false does not @@ -421,6 +424,23 @@ GHOSTTY_API GhosttyResult ghostty_render_state_begin_update(GhosttyRenderState s */ GHOSTTY_API GhosttyResult ghostty_render_state_end_update(GhosttyRenderState state); +/** + * Mark all dirty render-state data as consumed. + * + * This sets the global dirty state to GHOSTTY_RENDER_STATE_DIRTY_FALSE and + * clears every per-row dirty flag. It is idempotent and does not modify cell + * contents or dirty state owned by the terminal. Call this only after a + * complete frame has been rendered successfully; partial consumers should + * use ghostty_render_state_set() and ghostty_render_state_row_set() instead. + * + * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is + * NULL + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_clean(GhosttyRenderState state); + /** * Get a value from a render state. * @@ -534,7 +554,8 @@ GHOSTTY_API void ghostty_render_state_row_iterator_free(GhosttyRenderStateRowIte /** * Move a render-state row iterator to the next row. * - * Returns true if the iterator moved successfully and row data is + * Rows are visited contiguously in ascending viewport order, starting at + * y = 0. Returns true if the iterator moved successfully and row data is * available to read at the new position. * * @param iterator The iterator handle to advance (may be NULL) @@ -545,12 +566,36 @@ GHOSTTY_API void ghostty_render_state_row_iterator_free(GhosttyRenderStateRowIte */ GHOSTTY_API bool ghostty_render_state_row_iterator_next(GhosttyRenderStateRowIterator iterator); +/** + * Move a render-state row iterator to the next row requiring a redraw. + * + * If the global dirty state is GHOSTTY_RENDER_STATE_DIRTY_FALSE, this returns + * false. If it is GHOSTTY_RENDER_STATE_DIRTY_PARTIAL, clean rows are skipped. + * If it is GHOSTTY_RENDER_STATE_DIRTY_FULL, every remaining row is returned + * regardless of its per-row dirty flag. Rows are returned in ascending + * viewport order. This function does not clear any dirty state. + * + * @param iterator The iterator handle to advance (NULL returns false) + * @param[out] out_y Receives the viewport y coordinate when true is returned + * (NULL returns false); it is not modified when false is + * returned + * @return true if advanced to a row requiring a redraw, false if an argument + * is NULL or the iterator has reached the end of the effective dirty + * rows + * + * @ingroup render + */ +GHOSTTY_API bool ghostty_render_state_row_iterator_next_dirty( + GhosttyRenderStateRowIterator iterator, + uint16_t* out_y); + /** * Get a value from the current row in a render-state row iterator. * * The `out` pointer must point to a value of the type corresponding to the * requested data kind (see GhosttyRenderStateRowData). - * Call ghostty_render_state_row_iterator_next() at least once before + * Call ghostty_render_state_row_iterator_next() or + * ghostty_render_state_row_iterator_next_dirty() at least once before * calling this function. * * @param iterator The iterator handle to query (NULL returns GHOSTTY_INVALID_VALUE) @@ -599,7 +644,8 @@ GHOSTTY_API GhosttyResult ghostty_render_state_row_get_multi( * * The `value` pointer must point to a value of the type corresponding to the * requested option kind (see GhosttyRenderStateRowOption). - * Call ghostty_render_state_row_iterator_next() at least once before + * Call ghostty_render_state_row_iterator_next() or + * ghostty_render_state_row_iterator_next_dirty() at least once before * calling this function. * * @param iterator The iterator handle to update (NULL returns GHOSTTY_INVALID_VALUE) diff --git a/src/lib_vt.zig b/src/lib_vt.zig index f7ce5b386..ea7d0544d 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -283,12 +283,14 @@ comptime { @export(&c.render_state_update, .{ .name = "ghostty_render_state_update" }); @export(&c.render_state_begin_update, .{ .name = "ghostty_render_state_begin_update" }); @export(&c.render_state_end_update, .{ .name = "ghostty_render_state_end_update" }); + @export(&c.render_state_clean, .{ .name = "ghostty_render_state_clean" }); @export(&c.render_state_get, .{ .name = "ghostty_render_state_get" }); @export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" }); @export(&c.render_state_set, .{ .name = "ghostty_render_state_set" }); @export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" }); @export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" }); @export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" }); + @export(&c.render_state_row_iterator_next_dirty, .{ .name = "ghostty_render_state_row_iterator_next_dirty" }); @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); @export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" }); @export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" }); diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index 3615984cc..6eb31a875 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -87,12 +87,14 @@ pub const render_state_free = render.free; pub const render_state_update = render.update; pub const render_state_begin_update = render.begin_update; pub const render_state_end_update = render.end_update; +pub const render_state_clean = render.clean; pub const render_state_get = render.get; pub const render_state_get_multi = render.get_multi; pub const render_state_set = render.set; pub const render_state_colors_get = render.colors_get; pub const render_state_row_iterator_new = render.row_iterator_new; pub const render_state_row_iterator_next = render.row_iterator_next; +pub const render_state_row_iterator_next_dirty = render.row_iterator_next_dirty; pub const render_state_row_get = render.row_get; pub const render_state_row_get_multi = render.row_get_multi; pub const render_state_row_set = render.row_set; diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index c5383558c..e40ac24cd 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -42,6 +42,10 @@ const RowIteratorWrapper = struct { selection: []const ?[2]size.CellCountInt, dirty: []bool, + /// The global dirty state from the render state that populated this + /// iterator. This has the same borrowed lifetime as the row slices. + state_dirty: *const Dirty, + /// The color palette from the render state, needed to resolve /// palette-indexed background colors on cells. palette: *const colorpkg.Palette, @@ -220,6 +224,14 @@ pub fn end_update( return .success; } +pub fn clean( + state_: RenderState, +) callconv(lib.calling_conv) Result { + const state = state_ orelse return .invalid_value; + state.state.clean(); + return .success; +} + pub fn get( state_: RenderState, data: Data, @@ -300,6 +312,7 @@ fn getTyped( .cells = row_data.items(.cells), .selection = row_data.items(.selection), .dirty = row_data.items(.dirty), + .state_dirty = &state.state.dirty, .palette = &state.state.colors.palette, }; }, @@ -442,6 +455,7 @@ pub fn row_iterator_new( .cells = undefined, .selection = undefined, .dirty = undefined, + .state_dirty = undefined, .palette = undefined, }; result.* = ptr; @@ -463,6 +477,36 @@ pub fn row_iterator_next(iterator_: RowIterator) callconv(lib.calling_conv) bool return true; } +pub fn row_iterator_next_dirty( + iterator_: RowIterator, + out_y: ?*size.CellCountInt, +) callconv(lib.calling_conv) bool { + const it = iterator_ orelse return false; + const y_out = out_y orelse return false; + + switch (it.state_dirty.*) { + .false => return false, + .full => { + // The none sentinel wraps to zero. + const next_y = it.y +% 1; + if (next_y >= it.raws.len) return false; + it.y = next_y; + y_out.* = @intCast(next_y); + return true; + }, + .partial => { + var next_y = it.y +% 1; + while (next_y < it.raws.len) : (next_y += 1) { + if (!it.dirty[next_y]) continue; + it.y = next_y; + y_out.* = @intCast(next_y); + return true; + } + return false; + }, + } +} + pub fn row_cells_new( alloc_: ?*const CAllocator, result: *RowCells, @@ -1058,6 +1102,42 @@ test "render: get/set dirty" { try testing.expectEqual(Dirty.full, dirty); } +test "render: clean" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + 10, + 3, + )); + defer terminal_c.free(terminal); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + + try testing.expectEqual(Result.invalid_value, clean(null)); + try testing.expectEqual(Result.success, update(state, terminal)); + try testing.expectEqual(Dirty.full, state.?.state.dirty); + + try testing.expectEqual(Result.success, clean(state)); + try testing.expectEqual(Dirty.false, state.?.state.dirty); + for (state.?.state.row_data.items(.dirty)) |dirty| { + try testing.expect(!dirty); + } + + // Cleaning is idempotent, and an unchanged update remains clean. + try testing.expectEqual(Result.success, clean(state)); + try testing.expectEqual(Result.success, update(state, terminal)); + try testing.expectEqual(Dirty.false, state.?.state.dirty); + for (state.?.state.row_data.items(.dirty)) |dirty| { + try testing.expect(!dirty); + } +} + test "render: set null value" { var state: RenderState = null; try testing.expectEqual(Result.success, new( @@ -1708,6 +1788,79 @@ test "render: row iterator next" { try testing.expectEqual(@as(usize, rows - 1), iterator.?.y); } +test "render: row iterator next dirty" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + 10, + 4, + )); + defer terminal_c.free(terminal); + + var state: RenderState = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &state, + )); + defer free(state); + try testing.expectEqual(Result.success, update(state, terminal)); + + var iterator: RowIterator = null; + try testing.expectEqual(Result.success, row_iterator_new( + &lib.alloc.test_allocator, + &iterator, + )); + defer row_iterator_free(iterator); + + // Invalid arguments neither write the output nor advance the iterator. + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator))); + var out_y: size.CellCountInt = 0xCAFE; + try testing.expect(!row_iterator_next_dirty(null, &out_y)); + try testing.expectEqual(@as(size.CellCountInt, 0xCAFE), out_y); + try testing.expect(!row_iterator_next_dirty(iterator, null)); + try testing.expectEqual(position_none, iterator.?.y); + + // A full redraw returns every row, even if its row flag was cleared. + @memset(state.?.state.row_data.items(.dirty), false); + var expected_y: size.CellCountInt = 0; + while (row_iterator_next_dirty(iterator, &out_y)) : (expected_y += 1) { + try testing.expectEqual(expected_y, out_y); + } + try testing.expectEqual(@as(size.CellCountInt, 4), expected_y); + try testing.expectEqual(@as(size.CellCountInt, 3), out_y); + + // A partial redraw skips clean rows without consuming dirty flags. + const dirty = state.?.state.row_data.items(.dirty); + state.?.state.dirty = .partial; + @memset(dirty, false); + dirty[1] = true; + dirty[3] = true; + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator))); + try testing.expect(row_iterator_next_dirty(iterator, &out_y)); + try testing.expectEqual(@as(size.CellCountInt, 1), out_y); + try testing.expect(row_iterator_next_dirty(iterator, &out_y)); + try testing.expectEqual(@as(size.CellCountInt, 3), out_y); + try testing.expect(!row_iterator_next_dirty(iterator, &out_y)); + try testing.expectEqual(@as(size.CellCountInt, 3), out_y); + try testing.expect(dirty[1]); + try testing.expect(dirty[3]); + + // The global clean state is authoritative, even if stale row flags exist. + state.?.state.dirty = .false; + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator))); + out_y = 0xCAFE; + try testing.expect(!row_iterator_next_dirty(iterator, &out_y)); + try testing.expectEqual(@as(size.CellCountInt, 0xCAFE), out_y); + + // Existing iterators observe cleaning through their borrowed state. + state.?.state.dirty = .partial; + dirty[1] = true; + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator))); + try testing.expectEqual(Result.success, clean(state)); + try testing.expect(!row_iterator_next_dirty(iterator, &out_y)); +} + test "render: update" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( diff --git a/src/terminal/render.zig b/src/terminal/render.zig index 0c9bca318..6bcddb2c2 100644 --- a/src/terminal/render.zig +++ b/src/terminal/render.zig @@ -810,6 +810,16 @@ pub const RenderState = struct { self.pending_styles.clearRetainingCapacity(); } + /// Mark all render-state data as consumed by the renderer. + /// + /// This clears both the global dirty state and every per-row dirty flag. + /// Callers that only consume part of a frame should clear the two layers + /// individually instead. + pub fn clean(self: *RenderState) void { + self.dirty = .false; + @memset(self.row_data.items(.dirty), false); + } + /// Fill a slice of styles with one value. /// /// This is equivalent to `@memset(dst, value)` but manually vectorized: From 9673a22b01317ac6493240694a2451c8287253d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 20:06:51 -0700 Subject: [PATCH 081/392] libghostty: expand ABI type metadata The type metadata export only described extern struct layouts, leaving embedders to mirror enum values and tagged union relationships. Describe every public C type in a versioned manifest with target and build metadata. Keep union field renames alongside their source tagged unions so the manifest uses public C names without changing Zig value layouts. --- example/wasm-vt/index.html | 28 +- include/ghostty/vt/osc.h | 3 + include/ghostty/vt/types.h | 47 +- src/lib/union.zig | 94 ++- src/terminal/Terminal.zig | 2 +- src/terminal/c/osc.zig | 5 +- src/terminal/c/terminal.zig | 2 +- src/terminal/c/types.zig | 1066 ++++++++++++++++++++++++++--------- src/terminal/point.zig | 10 +- src/terminal/sgr.zig | 13 +- src/terminal/stream.zig | 2 +- 11 files changed, 945 insertions(+), 327 deletions(-) diff --git a/example/wasm-vt/index.html b/example/wasm-vt/index.html index 81a033737..76c9b9bbd 100644 --- a/example/wasm-vt/index.html +++ b/example/wasm-vt/index.html @@ -156,7 +156,7 @@ // Look up a field's offset and DataView setter from the type layout JSON. function fieldInfo(structName, fieldName) { - const field = typeLayout[structName].fields[fieldName]; + const field = typeLayout.types[structName].fields[fieldName]; return field; } @@ -166,9 +166,16 @@ switch (field.type) { case 'u8': case 'bool': view.setUint8(field.offset, value); break; case 'u16': view.setUint16(field.offset, value, true); break; - case 'u32': case 'enum': view.setUint32(field.offset, value, true); break; + case 'u32': view.setUint32(field.offset, value, true); break; case 'u64': view.setBigUint64(field.offset, BigInt(value), true); break; - default: throw new Error(`Unsupported field type: ${field.type}`); + default: { + const type = typeLayout.types[field.type]; + if (type?.kind === 'enum' && type.underlying === 'i32') { + view.setInt32(field.offset, value, true); + break; + } + throw new Error(`Unsupported field type: ${field.type}`); + } } } @@ -186,15 +193,14 @@ .replace(/\\\\/g, '\\'); } - // GHOSTTY_FORMATTER_FORMAT_PLAIN = 0 - const GHOSTTY_FORMATTER_FORMAT_PLAIN = 0; - // GHOSTTY_SUCCESS = 0 - const GHOSTTY_SUCCESS = 0; - function run() { const outputDiv = document.getElementById('output'); try { + const GHOSTTY_FORMATTER_FORMAT_PLAIN = + typeLayout.types.GhosttyFormatterFormat.values.PLAIN; + const GHOSTTY_SUCCESS = + typeLayout.types.GhosttyResult.values.SUCCESS; const cols = parseInt(document.getElementById('cols').value, 10); const rows = parseInt(document.getElementById('rows').value, 10); const vtText = parseEscapes(document.getElementById('vtInput').value); @@ -224,7 +230,7 @@ wasmInstance.exports.ghostty_wasm_free_u8_array(dataPtr, vtBytes.length); // Create a plain-text formatter - const FMT_OPTS_SIZE = typeLayout['GhosttyFormatterTerminalOptions'].size; + const FMT_OPTS_SIZE = typeLayout.types.GhosttyFormatterTerminalOptions.size; const fmtOptsPtr = wasmInstance.exports.ghostty_wasm_alloc_u8_array(FMT_OPTS_SIZE); new Uint8Array(getBuffer(), fmtOptsPtr, FMT_OPTS_SIZE).fill(0); const fmtOptsView = new DataView(getBuffer(), fmtOptsPtr, FMT_OPTS_SIZE); @@ -235,12 +241,12 @@ // Set the nested sized-struct `size` fields for extra and extra.screen const extraOffset = fieldInfo('GhosttyFormatterTerminalOptions', 'extra').offset; - const extraSize = typeLayout['GhosttyFormatterTerminalExtra'].size; + const extraSize = typeLayout.types.GhosttyFormatterTerminalExtra.size; const extraSizeField = fieldInfo('GhosttyFormatterTerminalExtra', 'size'); fmtOptsView.setUint32(extraOffset + extraSizeField.offset, extraSize, true); const screenOffset = fieldInfo('GhosttyFormatterTerminalExtra', 'screen').offset; - const screenSize = typeLayout['GhosttyFormatterScreenExtra'].size; + const screenSize = typeLayout.types.GhosttyFormatterScreenExtra.size; const screenSizeField = fieldInfo('GhosttyFormatterScreenExtra', 'size'); fmtOptsView.setUint32(extraOffset + screenOffset + screenSizeField.offset, screenSize, true); diff --git a/include/ghostty/vt/osc.h b/include/ghostty/vt/osc.h index 9409ebc73..f7626383c 100644 --- a/include/ghostty/vt/osc.h +++ b/include/ghostty/vt/osc.h @@ -63,6 +63,9 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_OSC_COMMAND_CONEMU_XTERM_EMULATION = 20, GHOSTTY_OSC_COMMAND_CONEMU_COMMENT = 21, GHOSTTY_OSC_COMMAND_KITTY_TEXT_SIZING = 22, + GHOSTTY_OSC_COMMAND_KITTY_CLIPBOARD_PROTOCOL = 23, + GHOSTTY_OSC_COMMAND_KITTY_DND_PROTOCOL = 24, + GHOSTTY_OSC_COMMAND_CONTEXT_SIGNAL = 25, GHOSTTY_OSC_COMMAND_TYPE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyOscCommandType; diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index c66ee89d2..bc1c6a69b 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -332,29 +332,40 @@ typedef struct { #endif /** - * Return a pointer to a null-terminated JSON string describing the - * layout of every C API struct for the current target. + * Return the versioned libghostty-vt C type manifest for the current target. * - * This is primarily useful for language bindings that can't easily - * set C struct fields and need to do so via byte offsets. For example, - * WebAssembly modules can't share struct definitions with the host. + * The manifest defines all the public types available in the linked + * build. The types contain their layouts, enum values, union fields, and more. + * + * Language bindings, such as WebAssembly hosts, should obtain offsets, + * sizes, alignments, array shapes, enum constants, and tagged-union arms from + * this manifest rather than hardcoding them. Consumers should reject unknown + * schema versions and verify the descriptors they require at initialization. * * Example (abbreviated): * @code{.json} * { - * "GhosttyMouseEncoderSize": { - * "size": 40, - * "align": 8, - * "fields": { - * "size": { "offset": 0, "size": 8, "type": "u64" }, - * "screen_width": { "offset": 8, "size": 4, "type": "u32" }, - * "screen_height": { "offset": 12, "size": 4, "type": "u32" }, - * "cell_width": { "offset": 16, "size": 4, "type": "u32" }, - * "cell_height": { "offset": 20, "size": 4, "type": "u32" }, - * "padding_top": { "offset": 24, "size": 4, "type": "u32" }, - * "padding_bottom": { "offset": 28, "size": 4, "type": "u32" }, - * "padding_right": { "offset": 32, "size": 4, "type": "u32" }, - * "padding_left": { "offset": 36, "size": 4, "type": "u32" } + * "schema": 1, + * "abi": { + * "target": "wasm32", "os": "freestanding", "environment": "none", + * "pointer_size": 4, "usize_size": 4, "endian": "little" + * }, + * "types": { + * "GhosttyRenderStateData": { + * "kind": "enum", "size": 4, "align": 4, + * "underlying": "i32", "prefix": "GHOSTTY_RENDER_STATE_DATA_", + * "values": { "INVALID": 0, "DIRTY": 3, "MAX_VALUE": 2147483647 } + * }, + * "GhosttyStyleColor": { + * "kind": "struct", "size": 16, "align": 8, + * "fields": { + * "tag": { "offset": 0, "size": 4, + * "type": "GhosttyStyleColorTag" }, + * "value": { "offset": 8, "size": 8, + * "type": "GhosttyStyleColorValue", "tag": "tag", + * "arms": { "NONE": null, "PALETTE": "palette", + * "RGB": "rgb" } } + * } * } * } * } diff --git a/src/lib/union.zig b/src/lib/union.zig index 63a34a8cb..728b13304 100644 --- a/src/lib/union.zig +++ b/src/lib/union.zig @@ -15,14 +15,18 @@ const Target = @import("target.zig").Target; /// be an enum created with the `Enum` function in this library, so that /// automatic C ABI compatibility is ensured. /// -/// The `Padding` type is a type that is always added to the C union -/// with the key `_padding`. This should be set to a type that has the size -/// and alignment needed to pad the C union to the expected size. This -/// should never change to ensure ABI compatibility. +/// `options.padding` is a type that is always added to the C union with the key +/// `_padding`. It should have the size and alignment needed to pad the C union +/// to the expected size and should never change to ensure ABI compatibility. +/// +/// Each tag has an optional field in `options.field_renames` that may rename its +/// C value field. Multiple tags may map to the same field when their C value +/// types are identical. Tags with no payload are omitted. This is used +/// for metadata and should match the C headers directly. pub fn TaggedUnion( comptime target: Target, comptime Union: type, - comptime Padding: type, + comptime options: TaggedUnionOptions(Union), ) type { return struct { comptime { @@ -30,7 +34,7 @@ pub fn TaggedUnion( .zig => {}, // For ABI compatibility, we expect that this is our union size. - .c => if (@sizeOf(CValue) != @sizeOf(Padding)) { + .c => if (@sizeOf(CValue) != @sizeOf(options.padding)) { @compileLog(@sizeOf(CValue)); @compileError("TaggedUnion CValue size does not match expected fixed size"); }, @@ -49,11 +53,25 @@ pub fn TaggedUnion( .c => extern struct { tag: Tag, value: CValue, + + /// Returns the public C value-union field name for `tag`, or + /// null when the tag has no value field. This is metadata only; + /// it does not rename fields in `CValue`. + pub fn cFieldRename(comptime tag: Tag) ?[]const u8 { + const tag_name = @tagName(tag); + const value = @field(@unionInit(Union, tag_name, undefined), tag_name); + + if (@field(options.field_renames, tag_name)) |name| return name; + if (@sizeOf(@TypeOf(value)) == 0) return null; + + return tag_name; + } }, }; /// The C ABI compatible union value type. pub const CValue = cvalue: { + @setEvalBranchQuota(10_000); switch (target) { .zig => break :cvalue extern struct {}, .c => {}, @@ -83,8 +101,8 @@ pub fn TaggedUnion( } names[tag_fields.len] = "_padding"; - types[tag_fields.len] = Padding; - attrs[tag_fields.len] = .{ .@"align" = @alignOf(Padding) }; + types[tag_fields.len] = options.padding; + attrs[tag_fields.len] = .{ .@"align" = @alignOf(options.padding) }; break :cvalue @Union(.@"extern", null, &names, &types, &attrs); }; @@ -125,6 +143,30 @@ pub fn TaggedUnion( }; } +/// Options for generating the C representation of a tagged union. +pub fn TaggedUnionOptions(comptime Union: type) type { + const Tag = @typeInfo(Union).@"union".tag_type.?; + const tag_fields = @typeInfo(Tag).@"enum".fields; + const FieldRenames: type = field_renames: { + const default_rename: ?[]const u8 = null; + + var names: [tag_fields.len][]const u8 = undefined; + var types: [tag_fields.len]type = undefined; + var attrs: [tag_fields.len]std.builtin.Type.StructField.Attributes = undefined; + + for (tag_fields, 0..) |field, i| { + names[i] = field.name; + types[i] = ?[]const u8; + attrs[i] = .{ .default_value_ptr = &default_rename }; + } + + break :field_renames @Struct(.auto, null, &names, &types, &attrs); + }; + return struct { + padding: type, + field_renames: FieldRenames = .{}, + }; +} test "TaggedUnion: matching size" { const Tag = enum(c_int) { a, b }; const U = TaggedUnion( @@ -133,7 +175,7 @@ test "TaggedUnion: matching size" { a: u32, b: u64, }, - u64, + .{ .padding = u64 }, ); try testing.expectEqual(8, @sizeOf(U.CValue)); @@ -146,7 +188,7 @@ test "TaggedUnion: padded size" { union(Tag) { a: u32, }, - u64, + .{ .padding = u64 }, ); try testing.expectEqual(8, @sizeOf(U.CValue)); @@ -157,9 +199,39 @@ test "TaggedUnion: c conversion" { const U = TaggedUnion(.c, union(Tag) { a: u32, b: u64, - }, u64); + }, .{ .padding = u64 }); const c = U.cval(.{ .a = 42 }); try testing.expectEqual(Tag.a, c.tag); try testing.expectEqual(42, c.value.a); } + +test "TaggedUnion: custom C value fields" { + const Tag = enum(c_int) { a, b, none }; + const Union = union(Tag) { + a: u32, + b: u32, + none, + }; + const U = TaggedUnion(.c, Union, .{ + .padding = u64, + .field_renames = .{ + .a = "value", + .b = "value", + }, + }); + + try testing.expect(!@hasField(U.CValue, "value")); + try testing.expect(@hasField(U.CValue, "a")); + try testing.expect(@hasField(U.CValue, "b")); + try testing.expect(@hasField(U.CValue, "none")); + try testing.expectEqualStrings("value", U.C.cFieldRename(.a).?); + try testing.expect(U.C.cFieldRename(.none) == null); + + const a = U.cval(.{ .a = 42 }); + try testing.expectEqual(Tag.a, a.tag); + try testing.expectEqual(42, a.value.a); + + const none = U.cval(.none); + try testing.expectEqual(Tag.none, none.tag); +} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 1e974cdd5..b935b505f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -2590,7 +2590,7 @@ pub const ScrollViewport = union(Tag) { @This(), // Padding: largest variant is isize (8 bytes on 64-bit). // Use [2]u64 (16 bytes) for future expansion. - [2]u64, + .{ .padding = [2]u64 }, ); pub const C = c_union.C; pub const CValue = c_union.CValue; diff --git a/src/terminal/c/osc.zig b/src/terminal/c/osc.zig index 8eca343c2..2ecc9c975 100644 --- a/src/terminal/c/osc.zig +++ b/src/terminal/c/osc.zig @@ -12,6 +12,9 @@ pub const Parser = ?*osc.Parser; /// C: GhosttyOscCommand pub const Command = ?*osc.Command; +/// C: GhosttyOscCommandType +pub const CommandType = osc.Command.Key; + pub fn new( alloc_: ?*const CAllocator, result: *Parser, @@ -44,7 +47,7 @@ pub fn end(parser_: Parser, terminator: u8) callconv(lib.calling_conv) Command { return parser_.?.end(terminator); } -pub fn commandType(command_: Command) callconv(lib.calling_conv) osc.Command.Key { +pub fn commandType(command_: Command) callconv(lib.calling_conv) CommandType { const command = command_ orelse return .invalid; return command.*; } diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 7dcca5f02..55d488158 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -182,7 +182,7 @@ pub const UnknownSequence = union(Tag) { // A future borrowed CSI payload may need parameter, separator, and // intermediate arrays. Reserve 128 bytes so that representation and // other structured sequence types can be added without an ABI break. - [16]u64, + .{ .padding = [16]u64 }, ); pub const C = c_union.C; pub const CValue = c_union.CValue; diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig index 681556666..88759ef1c 100644 --- a/src/terminal/c/types.zig +++ b/src/terminal/c/types.zig @@ -1,29 +1,49 @@ -//! Comptime-generated metadata describing the layout of all C API -//! extern structs for the current target. +//! Comptime-generated ABI metadata for the public libghostty-vt C types. //! -//! This is embedded in the binary as a const string and exposed via -//! `ghostty_type_json` so that WASM (and other FFI) consumers can -//! build structs without hardcoding byte offsets. +//! The manifest is embedded in the library and returned by +//! `ghostty_type_json`. It is intended for FFI consumers that cannot use the +//! C headers directly, most notably WebAssembly hosts. const std = @import("std"); +const builtin = @import("builtin"); +const build_options = @import("terminal_options"); const lib = @import("../lib.zig"); -const color = @import("../color.zig"); -const sgr = @import("../sgr.zig"); -const color_c = @import("color.zig"); -const mouse_event = @import("mouse_event.zig"); -const point = @import("../point.zig"); -const size_report = @import("size_report.zig"); -const terminal = @import("terminal.zig"); +const color = @import("../color.zig"); +const clipboard = @import("../clipboard.zig"); +const device_status = @import("../device_status.zig"); +const focus_pkg = @import("../focus.zig"); +const formatter_pkg = @import("../formatter.zig"); +const modes_pkg = @import("../modes.zig"); +const mouse_pkg = @import("../mouse.zig"); +const point = @import("../point.zig"); +const Selection = @import("../Selection.zig"); +const sgr = @import("../sgr.zig"); + +const input_config = @import("../../input/config.zig"); +const input_key = @import("../../input/key.zig"); +const input_mouse = @import("../../input/mouse.zig"); + +const build_info = @import("build_info.zig"); +const cell = @import("cell.zig"); +const color_c = @import("color.zig"); const formatter = @import("formatter.zig"); -const selection = @import("selection.zig"); -const selection_gesture = @import("selection_gesture.zig"); -const render = @import("render.zig"); -const style_c = @import("style.zig"); -const kitty_graphics = @import("kitty_graphics.zig"); -const mouse_encode = @import("mouse_encode.zig"); const grid_ref = @import("grid_ref.zig"); const io = @import("io.zig"); +const key_encode = @import("key_encode.zig"); +const kitty_graphics = @import("kitty_graphics.zig"); +const mouse_encode = @import("mouse_encode.zig"); +const mouse_event = @import("mouse_event.zig"); +const osc = @import("osc.zig"); +const render = @import("render.zig"); +const result = @import("result.zig"); +const row = @import("row.zig"); +const selection = @import("selection.zig"); +const selection_gesture = @import("selection_gesture.zig"); +const size_report = @import("size_report.zig"); +const snapshot = @import("snapshot.zig"); +const style = @import("style.zig"); const sys = @import("sys.zig"); +const terminal = @import("terminal.zig"); /// C: GhosttySurfacePosition pub const SurfacePosition = extern struct { @@ -37,318 +57,802 @@ pub const Codepoints = extern struct { len: usize = 0, }; -/// All C API structs and their Ghostty C names. -pub const structs: std.StaticStringMap(StructInfo) = structs: { - @setEvalBranchQuota(10_000); - break :structs .initComptime(.{ - .{ "GhosttyAllocator", StructInfo.init(lib.alloc.Allocator) }, - .{ "GhosttyAllocatorVtable", StructInfo.init(lib.alloc.VTable) }, - .{ "GhosttyBuffer", StructInfo.init(lib.Buffer) }, - .{ "GhosttyClipboardContent", StructInfo.init(terminal.ClipboardContent) }, - .{ "GhosttyClipboardWrite", StructInfo.init(terminal.ClipboardWrite) }, - .{ "GhosttyCodepoints", StructInfo.init(Codepoints) }, - .{ "GhosttyColorPaletteMask", StructInfo.init(color_c.PaletteMask) }, - .{ "GhosttyColorRgb", StructInfo.init(color.RGB.C) }, - .{ "GhosttyColorX11Entry", StructInfo.init(color_c.X11Entry) }, - .{ "GhosttyDeviceAttributes", StructInfo.init(terminal.DeviceAttributes) }, - .{ "GhosttyDeviceAttributesPrimary", StructInfo.init(terminal.DeviceAttributes.Primary) }, - .{ "GhosttyDeviceAttributesSecondary", StructInfo.init(terminal.DeviceAttributes.Secondary) }, - .{ "GhosttyDeviceAttributesTertiary", StructInfo.init(terminal.DeviceAttributes.Tertiary) }, - .{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) }, - .{ "GhosttySelection", StructInfo.init(selection.CSelection) }, - .{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) }, - .{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) }, - .{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) }, - .{ "GhosttyTerminalSelectionFormatOptions", StructInfo.init(selection.FormatOptions) }, - .{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) }, - .{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) }, - .{ "GhosttyGridRef", StructInfo.init(grid_ref.CGridRef) }, - .{ "GhosttyKittyGraphicsPlacementRenderInfo", StructInfo.init(kitty_graphics.PlacementRenderInfo) }, - .{ "GhosttyMouseEncoderSize", StructInfo.init(mouse_encode.Size) }, - .{ "GhosttyMousePosition", StructInfo.init(mouse_event.Position) }, - .{ "GhosttyPoint", StructInfo.init(point.Point.C) }, - .{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) }, - .{ "GhosttyReader", StructInfo.init(io.Reader) }, - .{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) }, - .{ "GhosttyRenderStateCursor", StructInfo.init(render.Cursor) }, - .{ "GhosttyRenderStateRowSelection", StructInfo.init(render.RowSelection) }, - .{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) }, - .{ "GhosttySelectionGestureGeometry", StructInfo.init(selection_gesture.Geometry) }, - .{ "GhosttySgrAttribute", StructInfo.init(sgr.Attribute.C) }, - .{ "GhosttySgrUnknown", StructInfo.init(sgr.Attribute.Unknown.C) }, - .{ "GhosttySizeReportSize", StructInfo.init(size_report.Size) }, - .{ "GhosttyString", StructInfo.init(lib.String) }, - .{ "GhosttySurfacePosition", StructInfo.init(SurfacePosition) }, - .{ "GhosttyStyle", StructInfo.init(style_c.Style) }, - .{ "GhosttyStyleColor", StructInfo.init(style_c.Color) }, - .{ "GhosttySysImage", StructInfo.init(sys.Image) }, - .{ "GhosttyTerminalDesktopNotification", StructInfo.init(terminal.DesktopNotification) }, - .{ "GhosttyTerminalModeConfig", StructInfo.init(terminal.ModeConfig) }, - .{ "GhosttyTerminalProgressReport", StructInfo.init(terminal.ProgressReport) }, - .{ "GhosttyTerminalScrollbar", StructInfo.init(terminal.TerminalScrollbar) }, - .{ "GhosttyTerminalScrollViewport", StructInfo.init(terminal.ScrollViewport) }, - .{ "GhosttyTerminalUnknownSequence", StructInfo.init(terminal.UnknownSequence.C) }, - .{ "GhosttyTerminalUnknownStringSequence", StructInfo.init(terminal.UnknownStringSequence) }, - .{ "GhosttyWriter", StructInfo.init(io.Writer) }, - }); +const TypeDecl = struct { + name: []const u8, + T: type, + kind: Kind, + prefix: []const u8 = "", + sentinel_suffix: []const u8 = "MAX_VALUE", + alias_type: []const u8 = "", + tagged_union: ?TaggedUnion = null, + union_field_names_T: ?type = null, + + const Kind = enum { + @"struct", + @"union", + @"enum", + alias, + @"opaque", + }; + + const TaggedUnion = struct { + tag_field: []const u8, + value_field: []const u8, + arm_source: ArmSource, + + const ArmSource = enum { + fields, + generated, + }; + }; + + fn initStruct(comptime name: []const u8, comptime T: type) TypeDecl { + return .{ .name = name, .T = T, .kind = .@"struct" }; + } + + fn initTaggedStruct( + comptime name: []const u8, + comptime T: type, + comptime tag_field: []const u8, + comptime value_field: []const u8, + comptime arm_source: TaggedUnion.ArmSource, + ) TypeDecl { + return .{ + .name = name, + .T = T, + .kind = .@"struct", + .tagged_union = .{ + .tag_field = tag_field, + .value_field = value_field, + .arm_source = arm_source, + }, + }; + } + + fn initUnion( + comptime name: []const u8, + comptime T: type, + comptime field_names_T: ?type, + ) TypeDecl { + return .{ + .name = name, + .T = T, + .kind = .@"union", + .union_field_names_T = field_names_T, + }; + } + + fn initEnum( + comptime name: []const u8, + comptime T: type, + comptime prefix: []const u8, + ) TypeDecl { + return .{ .name = name, .T = T, .kind = .@"enum", .prefix = prefix }; + } + + fn initEnumSentinel( + comptime name: []const u8, + comptime T: type, + comptime prefix: []const u8, + comptime sentinel_suffix: []const u8, + ) TypeDecl { + return .{ + .name = name, + .T = T, + .kind = .@"enum", + .prefix = prefix, + .sentinel_suffix = sentinel_suffix, + }; + } + + fn initAlias( + comptime name: []const u8, + comptime T: type, + comptime alias_type: []const u8, + ) TypeDecl { + return .{ .name = name, .T = T, .kind = .alias, .alias_type = alias_type }; + } + + fn initOpaque(comptime name: []const u8) TypeDecl { + return .{ .name = name, .T = *anyopaque, .kind = .@"opaque" }; + } }; -/// The comptime-generated JSON string of all structs. +/// Public C types. Names and enum prefixes are intentionally explicit because +/// Zig identifiers are not the C API contract. +const type_decls = [_]TypeDecl{ + .initStruct("GhosttyAllocator", lib.alloc.Allocator), + .initStruct("GhosttyAllocatorVtable", lib.alloc.VTable), + .initStruct("GhosttyBuffer", lib.Buffer), + .initStruct("GhosttyCellsView", cell.CellsView), + .initStruct("GhosttyClipboardContent", terminal.ClipboardContent), + .initStruct("GhosttyClipboardWrite", terminal.ClipboardWrite), + .initStruct("GhosttyCodepoints", Codepoints), + .initStruct("GhosttyColorPaletteMask", color_c.PaletteMask), + .initStruct("GhosttyColorRgb", color.RGB.C), + .initStruct("GhosttyColorX11Entry", color_c.X11Entry), + .initStruct("GhosttyDeviceAttributes", terminal.DeviceAttributes), + .initStruct("GhosttyDeviceAttributesPrimary", terminal.DeviceAttributes.Primary), + .initStruct("GhosttyDeviceAttributesSecondary", terminal.DeviceAttributes.Secondary), + .initStruct("GhosttyDeviceAttributesTertiary", terminal.DeviceAttributes.Tertiary), + .initStruct("GhosttyFormatterScreenExtra", formatter.ScreenOptions.Extra), + .initStruct("GhosttyFormatterTerminalExtra", formatter.TerminalOptions.Extra), + .initStruct("GhosttyFormatterTerminalOptions", formatter.TerminalOptions), + .initStruct("GhosttyGridRef", grid_ref.CGridRef), + .initStruct("GhosttyKittyGraphicsPlacementRenderInfo", kitty_graphics.PlacementRenderInfo), + .initStruct("GhosttyMouseEncoderSize", mouse_encode.Size), + .initStruct("GhosttyMousePosition", mouse_event.Position), + .initTaggedStruct("GhosttyPoint", point.Point.C, "tag", "value", .generated), + .initStruct("GhosttyPointCoordinate", point.Coordinate), + .initUnion("GhosttyPointValue", point.Point.CValue, point.Point.C), + .initStruct("GhosttyReader", io.Reader), + .initStruct("GhosttyRenderStateColors", render.Colors), + .initStruct("GhosttyRenderStateCursor", render.Cursor), + .initStruct("GhosttyRenderStateRowSelection", render.RowSelection), + .initStruct("GhosttySelection", selection.CSelection), + .initStruct("GhosttySelectionGestureBehaviors", selection_gesture.Behaviors), + .initStruct("GhosttySelectionGestureGeometry", selection_gesture.Geometry), + .initTaggedStruct("GhosttySgrAttribute", sgr.Attribute.C, "tag", "value", .generated), + .initStruct("GhosttySgrUnknown", sgr.Attribute.Unknown.C), + .initUnion("GhosttySgrAttributeValue", sgr.Attribute.CValue, sgr.Attribute.C), + .initStruct("GhosttySizeReportSize", size_report.Size), + .initStruct("GhosttyString", lib.String), + .initStruct("GhosttySurfacePosition", SurfacePosition), + .initStruct("GhosttyStyle", style.Style), + .initTaggedStruct("GhosttyStyleColor", style.Color, "tag", "value", .fields), + .initUnion("GhosttyStyleColorValue", style.ColorValue, null), + .initStruct("GhosttySysImage", sys.Image), + .initStruct("GhosttyTerminalDesktopNotification", terminal.DesktopNotification), + .initStruct("GhosttyTerminalModeConfig", terminal.ModeConfig), + .initStruct("GhosttyTerminalProgressReport", terminal.ProgressReport), + .initStruct("GhosttyTerminalScrollbar", terminal.TerminalScrollbar), + .initTaggedStruct("GhosttyTerminalScrollViewport", terminal.ScrollViewport, "tag", "value", .generated), + .initUnion( + "GhosttyTerminalScrollViewportValue", + terminal.ZigTerminal.ScrollViewport.CValue, + terminal.ZigTerminal.ScrollViewport.C, + ), + .initStruct("GhosttyTerminalSelectLineOptions", selection.SelectLineOptions), + .initStruct("GhosttyTerminalSelectWordBetweenOptions", selection.SelectWordBetweenOptions), + .initStruct("GhosttyTerminalSelectWordOptions", selection.SelectWordOptions), + .initStruct("GhosttyTerminalSelectionFormatOptions", selection.FormatOptions), + .initTaggedStruct("GhosttyTerminalUnknownSequence", terminal.UnknownSequence.C, "tag", "value", .generated), + .initStruct("GhosttyTerminalUnknownStringSequence", terminal.UnknownStringSequence), + .initUnion( + "GhosttyTerminalUnknownSequenceValue", + terminal.UnknownSequence.CValue, + terminal.UnknownSequence.C, + ), + .initStruct("GhosttyWriter", io.Writer), + + .initEnumSentinel("GhosttyResult", result.Result, "GHOSTTY_", "RESULT_MAX_VALUE"), + .initEnum("GhosttyBuildInfo", build_info.BuildInfo, "GHOSTTY_BUILD_INFO_"), + .initEnumSentinel("GhosttyOptimizeMode", build_info.OptimizeMode, "GHOSTTY_OPTIMIZE_", "MODE_MAX_VALUE"), + .initEnumSentinel("GhosttyCellContentTag", cell.ContentTag, "GHOSTTY_CELL_CONTENT_", "TAG_MAX_VALUE"), + .initEnum("GhosttyCellData", cell.CellData, "GHOSTTY_CELL_DATA_"), + .initEnum("GhosttyCellSemanticContent", cell.SemanticContent, "GHOSTTY_CELL_SEMANTIC_"), + .initEnum("GhosttyCellWide", cell.Wide, "GHOSTTY_CELL_WIDE_"), + .initEnum("GhosttyClipboardLocation", clipboard.Location, "GHOSTTY_CLIPBOARD_LOCATION_"), + .initEnum("GhosttyClipboardWriteResult", clipboard.WriteResult, "GHOSTTY_CLIPBOARD_WRITE_RESULT_"), + .initEnum("GhosttyColorScheme", device_status.ColorScheme, "GHOSTTY_COLOR_SCHEME_"), + .initEnum("GhosttyFocusEvent", focus_pkg.Event, "GHOSTTY_FOCUS_"), + .initEnum("GhosttyFormatterFormat", formatter_pkg.Format, "GHOSTTY_FORMATTER_FORMAT_"), + .initEnum("GhosttyKey", input_key.Key, "GHOSTTY_KEY_"), + .initEnum("GhosttyKeyAction", input_key.Action, "GHOSTTY_KEY_ACTION_"), + .initEnum("GhosttyKeyEncoderOption", key_encode.Option, "GHOSTTY_KEY_ENCODER_OPT_"), + .initEnum("GhosttyKittyGraphicsData", kitty_graphics.Data, "GHOSTTY_KITTY_GRAPHICS_DATA_"), + .initEnum("GhosttyKittyGraphicsImageData", kitty_graphics.ImageData, "GHOSTTY_KITTY_IMAGE_DATA_"), + .initEnum("GhosttyKittyGraphicsPlacementData", kitty_graphics.PlacementData, "GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_"), + .initEnum("GhosttyKittyGraphicsPlacementIteratorOption", kitty_graphics.PlacementIteratorOption, "GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_"), + .initEnum("GhosttyKittyImageCompression", kitty_graphics.ImageCompression, "GHOSTTY_KITTY_IMAGE_COMPRESSION_"), + .initEnum("GhosttyKittyImageFormat", kitty_graphics.ImageFormat, "GHOSTTY_KITTY_IMAGE_FORMAT_"), + .initEnum("GhosttyKittyPlacementLayer", kitty_graphics.PlacementLayer, "GHOSTTY_KITTY_PLACEMENT_LAYER_"), + .initEnum("GhosttyModeReportState", modes_pkg.Report.State, "GHOSTTY_MODE_REPORT_"), + .initEnum("GhosttyMouseAction", input_mouse.Action, "GHOSTTY_MOUSE_ACTION_"), + .initEnum("GhosttyMouseButton", input_mouse.Button, "GHOSTTY_MOUSE_BUTTON_"), + .initEnum("GhosttyMouseEncoderOption", mouse_encode.Option, "GHOSTTY_MOUSE_ENCODER_OPT_"), + .initEnum("GhosttyMouseFormat", mouse_pkg.Format, "GHOSTTY_MOUSE_FORMAT_"), + .initEnum("GhosttyMouseTrackingMode", mouse_pkg.Event, "GHOSTTY_MOUSE_TRACKING_"), + .initEnum("GhosttyOptionAsAlt", input_config.OptionAsAlt, "GHOSTTY_OPTION_AS_ALT_"), + .initEnum("GhosttyOscCommandData", osc.CommandData, "GHOSTTY_OSC_DATA_"), + .initEnumSentinel( + "GhosttyOscCommandType", + osc.CommandType, + "GHOSTTY_OSC_COMMAND_", + "TYPE_MAX_VALUE", + ), + .initEnum("GhosttyPointTag", point.Tag, "GHOSTTY_POINT_TAG_"), + .initEnum("GhosttyRenderStateCursorVisualStyle", render.CursorVisualStyle, "GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_"), + .initEnum("GhosttyRenderStateData", render.Data, "GHOSTTY_RENDER_STATE_DATA_"), + .initEnum("GhosttyRenderStateDirty", render.Dirty, "GHOSTTY_RENDER_STATE_DIRTY_"), + .initEnum("GhosttyRenderStateOption", render.SetOption, "GHOSTTY_RENDER_STATE_OPTION_"), + .initEnum("GhosttyRenderStateRowCellsData", render.RowCellsData, "GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_"), + .initEnum("GhosttyRenderStateRowData", render.RowData, "GHOSTTY_RENDER_STATE_ROW_DATA_"), + .initEnum("GhosttyRenderStateRowOption", render.RowOption, "GHOSTTY_RENDER_STATE_ROW_OPTION_"), + .initEnum("GhosttyRowData", row.RowData, "GHOSTTY_ROW_DATA_"), + .initEnum("GhosttyRowSemanticPrompt", row.SemanticPrompt, "GHOSTTY_ROW_SEMANTIC_"), + .initEnum("GhosttySelectionAdjust", Selection.Adjustment, "GHOSTTY_SELECTION_ADJUST_"), + .initEnum("GhosttySelectionGestureAutoscroll", selection_gesture.Autoscroll, "GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_"), + .initEnum("GhosttySelectionGestureBehavior", selection_gesture.Behavior, "GHOSTTY_SELECTION_GESTURE_BEHAVIOR_"), + .initEnum("GhosttySelectionGestureData", selection_gesture.Data, "GHOSTTY_SELECTION_GESTURE_DATA_"), + .initEnum("GhosttySelectionGestureEventOption", selection_gesture.EventOption, "GHOSTTY_SELECTION_GESTURE_EVENT_OPT_"), + .initEnum("GhosttySelectionGestureEventType", selection_gesture.EventType, "GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_"), + .initEnum("GhosttySelectionOrder", Selection.Order, "GHOSTTY_SELECTION_ORDER_"), + .initEnum("GhosttySgrAttributeTag", sgr.Attribute.Tag, "GHOSTTY_SGR_ATTR_"), + .initEnum("GhosttySgrUnderline", sgr.Attribute.Underline, "GHOSTTY_SGR_UNDERLINE_"), + .initEnumSentinel("GhosttySizeReportStyle", size_report.Style, "GHOSTTY_SIZE_REPORT_", "STYLE_MAX_VALUE"), + .initEnum("GhosttySnapshotDecoderData", snapshot.DecoderData, "GHOSTTY_SNAPSHOT_DECODER_DATA_"), + .initEnum("GhosttySnapshotDecoderOption", snapshot.DecoderOption, "GHOSTTY_SNAPSHOT_DECODER_OPT_"), + .initEnumSentinel("GhosttyStyleColorTag", style.ColorTag, "GHOSTTY_STYLE_COLOR_", "TAG_MAX_VALUE"), + .initEnum("GhosttySysLogLevel", sys.LogLevel, "GHOSTTY_SYS_LOG_LEVEL_"), + .initEnum("GhosttySysOption", sys.Option, "GHOSTTY_SYS_OPT_"), + .initEnum("GhosttyTerminalCompressionMode", terminal.CompressionMode, "GHOSTTY_TERMINAL_COMPRESSION_MODE_"), + .initEnum("GhosttyTerminalCompressionResult", terminal.CompressionResult, "GHOSTTY_TERMINAL_COMPRESSION_RESULT_"), + .initEnum("GhosttyTerminalCursorStyle", terminal.TerminalCursorStyle, "GHOSTTY_TERMINAL_CURSOR_STYLE_"), + .initEnum("GhosttyTerminalData", terminal.TerminalData, "GHOSTTY_TERMINAL_DATA_"), + .initEnum("GhosttyTerminalOption", terminal.Option, "GHOSTTY_TERMINAL_OPT_"), + .initEnum("GhosttyTerminalProgressState", terminal.ProgressState, "GHOSTTY_TERMINAL_PROGRESS_STATE_"), + .initEnum("GhosttyTerminalScreen", terminal.TerminalScreen, "GHOSTTY_TERMINAL_SCREEN_"), + .initEnum("GhosttyTerminalScrollViewportTag", terminal.ZigTerminal.ScrollViewport.Tag, "GHOSTTY_SCROLL_VIEWPORT_"), + .initEnum("GhosttyTerminalUnknownSequenceTag", terminal.UnknownSequence.Tag, "GHOSTTY_TERMINAL_UNKNOWN_SEQUENCE_"), + + .initAlias("GhosttyCell", u64, "u64"), + .initAlias("GhosttyColorPaletteIndex", u8, "u8"), + .initAlias("GhosttyKittyKeyFlags", u8, "u8"), + .initAlias("GhosttyMode", u16, "u16"), + .initAlias("GhosttyMods", u16, "u16"), + .initAlias("GhosttyRow", u64, "u64"), + .initAlias("GhosttyStyleId", u16, "u16"), + + .initOpaque("GhosttyFormatter"), + .initOpaque("GhosttyKeyEncoder"), + .initOpaque("GhosttyKeyEvent"), + .initOpaque("GhosttyKittyGraphics"), + .initOpaque("GhosttyKittyGraphicsImage"), + .initOpaque("GhosttyKittyGraphicsPlacementIterator"), + .initOpaque("GhosttyMouseEncoder"), + .initOpaque("GhosttyMouseEvent"), + .initOpaque("GhosttyOscCommand"), + .initOpaque("GhosttyOscParser"), + .initOpaque("GhosttyRenderState"), + .initOpaque("GhosttyRenderStateRowCells"), + .initOpaque("GhosttyRenderStateRowIterator"), + .initOpaque("GhosttySelectionGesture"), + .initOpaque("GhosttySelectionGestureEvent"), + .initOpaque("GhosttySgrParser"), + .initOpaque("GhosttySnapshotDecoder"), + .initOpaque("GhosttyTerminal"), + .initOpaque("GhosttyTrackedGridRef"), +}; + +comptime { + @setEvalBranchQuota(100_000); + for (type_decls, 0..) |decl, i| { + for (type_decls[0..i]) |previous| { + if (std.mem.eql(u8, decl.name, previous.name)) + @compileError("duplicate public ABI type: " ++ decl.name); + } + } +} + pub const json: [:0]const u8 = json: { - @setEvalBranchQuota(200_000); + @setEvalBranchQuota(1_000_000); var counter: std.Io.Writer.Discarding = .init(&.{}); - jsonWriteAll(&counter.writer) catch unreachable; + Json.writeAll(&counter.writer) catch unreachable; var buf: [counter.count:0]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); - jsonWriteAll(&writer) catch unreachable; + Json.writeAll(&writer) catch unreachable; const final = buf; break :json final[0..writer.end :0]; }; -/// Returns a pointer to the comptime-generated JSON string describing -/// the layout of all C API extern structs, and writes its length to `len`. -/// Exported as `ghostty_type_json` for FFI consumers. pub fn get_json() callconv(lib.calling_conv) [*:0]const u8 { return json.ptr; } -/// Meta information about a struct that we expose to ease writing -/// bindings in some languages, particularly WASM where we can't -/// easily share struct definitions and need to hardcode byte offsets. -pub const StructInfo = struct { - name: []const u8, - size: usize, - @"align": usize, - fields: []const FieldInfo, +const Json = struct { + fn writeAll(writer: *std.Io.Writer) std.Io.Writer.Error!void { + var jws: std.json.Stringify = .{ .writer = writer }; + try jws.beginObject(); + try jws.objectField("schema"); + try jws.write(1); - pub const FieldInfo = struct { - name: []const u8, - offset: usize, - size: usize, - type: []const u8, - }; + try jws.objectField("abi"); + try jws.beginObject(); + try jws.objectField("target"); + try jws.write(@tagName(builtin.target.cpu.arch)); + try jws.objectField("os"); + try jws.write(@tagName(builtin.target.os.tag)); + try jws.objectField("environment"); + try jws.write(@tagName(builtin.target.abi)); + try jws.objectField("pointer_size"); + try jws.write(@sizeOf(*anyopaque)); + try jws.objectField("usize_size"); + try jws.write(@sizeOf(usize)); + try jws.objectField("endian"); + try jws.write(@tagName(builtin.target.cpu.arch.endian())); + try jws.endObject(); - pub fn init(comptime T: type) StructInfo { - comptime { - const fields = @typeInfo(T).@"struct".fields; - const field_infos: [fields.len]FieldInfo = blk: { - var infos: [fields.len]FieldInfo = undefined; - for (fields, 0..) |field, i| infos[i] = .{ - .name = field.name, - .offset = @offsetOf(T, field.name), - .size = @sizeOf(field.type), - .type = typeName(field.type), - }; - break :blk infos; - }; + try jws.objectField("library_version"); + try jws.write(build_options.version_string); + try jws.objectField("commit"); + if (build_options.version_build) |commit| try jws.write(commit) else try jws.write(null); + try jws.objectField("dirty"); + try jws.write(null); - return .{ - .name = @typeName(T), - .size = @sizeOf(T), - .@"align" = @alignOf(T), - .fields = &field_infos, - }; + try jws.objectField("types"); + try jws.beginObject(); + inline for (type_decls) |decl| { + try jws.objectField(decl.name); + try writeType(decl, &jws); } + try jws.endObject(); + try jws.endObject(); } - pub fn jsonStringify( - self: *const StructInfo, - jws: anytype, + fn writeType(comptime decl: TypeDecl, jws: *std.json.Stringify) std.Io.Writer.Error!void { + try jws.beginObject(); + try jws.objectField("kind"); + try jws.write(@tagName(decl.kind)); + + switch (decl.kind) { + .@"struct", .@"union" => { + try writeSizeAlign(decl.T, jws); + try jws.objectField("fields"); + try jws.beginObject(); + if (decl.union_field_names_T != null) { + try writeMappedUnionFields(decl, jws); + } else { + const fields = switch (decl.kind) { + .@"struct" => @typeInfo(decl.T).@"struct".fields, + .@"union" => @typeInfo(decl.T).@"union".fields, + else => unreachable, + }; + inline for (fields) |field| { + try writeField( + decl, + field.name, + field.type, + if (decl.kind == .@"union") 0 else @offsetOf(decl.T, field.name), + jws, + ); + } + } + try jws.endObject(); + }, + .@"enum" => { + try writeSizeAlign(c_int, jws); + try jws.objectField("underlying"); + try jws.write("i32"); + try jws.objectField("prefix"); + try jws.write(decl.prefix); + try jws.objectField("values"); + try jws.beginObject(); + inline for (@typeInfo(decl.T).@"enum".fields) |field| { + try writeEnumObjectField(decl.name, field.name, jws); + try jws.write(field.value); + } + try jws.objectField(decl.sentinel_suffix); + try jws.write(std.math.maxInt(c_int)); + try jws.endObject(); + }, + .alias => { + try writeSizeAlign(decl.T, jws); + try jws.objectField("type"); + try jws.write(decl.alias_type); + }, + .@"opaque" => try writeSizeAlign(*anyopaque, jws), + } + try jws.endObject(); + } + + fn writeField( + comptime decl: TypeDecl, + comptime name: []const u8, + comptime T: type, + comptime offset: usize, + jws: *std.json.Stringify, ) std.Io.Writer.Error!void { + try jws.objectField(name); try jws.beginObject(); + try jws.objectField("offset"); + try jws.write(offset); try jws.objectField("size"); - try jws.write(self.size); - try jws.objectField("align"); - try jws.write(self.@"align"); - try jws.objectField("fields"); - try jws.beginObject(); - for (self.fields) |field| { - try jws.objectField(field.name); - try jws.beginObject(); - try jws.objectField("offset"); - try jws.write(field.offset); - try jws.objectField("size"); - try jws.write(field.size); - try jws.objectField("type"); - try jws.write(field.type); - try jws.endObject(); + try jws.write(@sizeOf(T)); + try writeFieldType(decl.name, name, T, jws); + if (decl.tagged_union) |tagged| { + if (std.mem.eql(u8, name, tagged.value_field)) { + try jws.objectField("tag"); + try jws.write(tagged.tag_field); + try writeTaggedArms(decl, tagged, jws); + } } try jws.endObject(); + } + + fn writeMappedUnionFields( + comptime decl: TypeDecl, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + const FieldNames = decl.union_field_names_T.?; + const Tag = @FieldType(FieldNames, "tag"); + const tag_fields = @typeInfo(Tag).@"enum".fields; + + inline for (tag_fields, 0..) |field, i| { + const name = comptime FieldNames.cFieldRename(@field(Tag, field.name)) orelse continue; + if (comptime mappedUnionFieldIsDuplicate(decl, i, name)) continue; + try writeField(decl, name, @FieldType(decl.T, field.name), 0, jws); + } + + if (@hasField(decl.T, "_padding")) + try writeField(decl, "_padding", @FieldType(decl.T, "_padding"), 0, jws); + } + + fn mappedUnionFieldIsDuplicate( + comptime decl: TypeDecl, + comptime index: usize, + comptime name: []const u8, + ) bool { + const FieldNames = decl.union_field_names_T.?; + const Tag = @FieldType(FieldNames, "tag"); + const tag_fields = @typeInfo(Tag).@"enum".fields; + const T = @FieldType(decl.T, tag_fields[index].name); + + inline for (tag_fields[0..index]) |previous| { + const previous_name = FieldNames.cFieldRename(@field(Tag, previous.name)) orelse continue; + if (std.mem.eql(u8, previous_name, name)) { + if (@FieldType(decl.T, previous.name) != T) + @compileError("tagged union metadata maps different field types to " ++ name); + return true; + } + } + + return false; + } + + fn writeSizeAlign(comptime T: type, jws: *std.json.Stringify) std.Io.Writer.Error!void { + try jws.objectField("size"); + try jws.write(@sizeOf(T)); + try jws.objectField("align"); + try jws.write(@alignOf(T)); + } + + fn writeFieldType( + comptime owner: []const u8, + comptime field_name: []const u8, + comptime T: type, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + if (comptime std.mem.eql(u8, owner, "GhosttyCellsView") and + std.mem.eql(u8, field_name, "ptr")) + { + try writePointerTypeNamed(T, true, "GhosttyCell", jws); + return; + } + if (comptime std.mem.eql(u8, owner, "GhosttyGridRef") and + std.mem.eql(u8, field_name, "node")) + { + try writePointerTypeNamed(T, true, "opaque", jws); + return; + } + + if (comptime fieldTypeOverride(owner, field_name)) |name| { + try jws.objectField("type"); + try jws.write(name); + return; + } + + switch (@typeInfo(T)) { + .array => |info| { + try jws.objectField("type"); + try jws.write("array"); + try jws.objectField("elem"); + try jws.write(publicTypeName(info.child)); + try jws.objectField("count"); + try jws.write(info.len); + }, + .optional => |info| try writePointerType(info.child, true, jws), + .pointer => try writePointerType(T, false, jws), + else => { + try jws.objectField("type"); + try jws.write(publicTypeName(T)); + }, + } + } + + fn writePointerType( + comptime T: type, + comptime nullable: bool, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + const info = @typeInfo(T).pointer; + try jws.objectField("type"); + try jws.write("pointer"); + try jws.objectField("elem"); + try jws.write(publicTypeName(info.child)); + try jws.objectField("const"); + try jws.write(info.is_const); + if (nullable) { + try jws.objectField("nullable"); + try jws.write(true); + } + } + + fn writePointerTypeNamed( + comptime T: type, + comptime nullable: bool, + comptime elem: []const u8, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + const pointer_type = switch (@typeInfo(T)) { + .optional => |info| info.child, + .pointer => T, + else => unreachable, + }; + const info = @typeInfo(pointer_type).pointer; + try jws.objectField("type"); + try jws.write("pointer"); + try jws.objectField("elem"); + try jws.write(elem); + try jws.objectField("const"); + try jws.write(info.is_const); + if (nullable) { + try jws.objectField("nullable"); + try jws.write(true); + } + } + + fn fieldTypeOverride(comptime owner: []const u8, comptime field_name: []const u8) ?[]const u8 { + if (std.mem.eql(u8, field_name, "value")) { + if (std.mem.eql(u8, owner, "GhosttyPoint")) return "GhosttyPointValue"; + if (std.mem.eql(u8, owner, "GhosttySgrAttribute")) return "GhosttySgrAttributeValue"; + if (std.mem.eql(u8, owner, "GhosttyStyleColor")) return "GhosttyStyleColorValue"; + if (std.mem.eql(u8, owner, "GhosttyTerminalScrollViewport")) return "GhosttyTerminalScrollViewportValue"; + if (std.mem.eql(u8, owner, "GhosttyTerminalUnknownSequence")) return "GhosttyTerminalUnknownSequenceValue"; + } + if (std.mem.eql(u8, owner, "GhosttyStyleColorValue") and std.mem.eql(u8, field_name, "palette")) + return "GhosttyColorPaletteIndex"; + if (std.mem.eql(u8, owner, "GhosttySgrAttributeValue")) { + if (std.mem.eql(u8, field_name, "underline")) return "GhosttySgrUnderline"; + if (std.mem.endsWith(u8, field_name, "_8") or std.mem.endsWith(u8, field_name, "_256")) + return "GhosttyColorPaletteIndex"; + } + if (std.mem.eql(u8, owner, "GhosttyTerminalModeConfig") and std.mem.eql(u8, field_name, "mode")) + return "GhosttyMode"; + return null; + } + + fn writeTaggedArms( + comptime decl: TypeDecl, + comptime tagged: TypeDecl.TaggedUnion, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + const Tag = @FieldType(decl.T, tagged.tag_field); + try jws.objectField("arms"); + try jws.beginObject(); + inline for (@typeInfo(Tag).@"enum".fields) |field| { + try writeEnumObjectField(publicTypeName(Tag), field.name, jws); + if (comptime taggedArm(decl, field.name)) |arm| try jws.write(arm) else try jws.write(null); + } try jws.endObject(); } -}; -fn jsonWriteAll(writer: *std.Io.Writer) std.Io.Writer.Error!void { - var jws: std.json.Stringify = .{ .writer = writer }; - try jws.beginObject(); - for (structs.keys(), structs.values()) |name, *info| { - try jws.objectField(name); - try info.jsonStringify(&jws); + fn taggedArm(comptime decl: TypeDecl, comptime tag_name: []const u8) ?[]const u8 { + const tagged = decl.tagged_union.?; + const Tag = @FieldType(decl.T, tagged.tag_field); + if (tagged.arm_source == .generated) + return decl.T.cFieldRename(@field(Tag, tag_name)); + + const Value = @FieldType(decl.T, tagged.value_field); + if (!@hasField(Value, tag_name)) return null; + return if (@sizeOf(@FieldType(Value, tag_name)) == 0) null else tag_name; } - try jws.endObject(); -} -fn typeName(comptime T: type) []const u8 { - return switch (@typeInfo(T)) { - .bool => "bool", - .float => |info| switch (info.bits) { - 32 => "f32", - 64 => "f64", - else => @compileError("unsupported float size"), - }, - .int => |info| switch (info.signedness) { - .signed => switch (info.bits) { + fn publicTypeName(comptime T: type) []const u8 { + inline for (type_decls) |decl| switch (decl.kind) { + .@"struct", .@"union", .@"enum" => if (T == decl.T) return decl.name, + .alias, .@"opaque" => {}, + }; + return switch (@typeInfo(T)) { + .bool => "bool", + .float => |info| switch (info.bits) { + 32 => "f32", + 64 => "f64", + else => "opaque", + }, + .int => |info| intName(info.signedness, info.bits), + .comptime_int => "comptime_int", + .void => "void", + .@"opaque" => "opaque", + .@"fn" => "function", + .@"enum" => "enum", + .@"struct" => "struct", + .@"union" => "union", + else => "opaque", + }; + } + + fn intName(comptime signedness: std.builtin.Signedness, comptime bits: u16) []const u8 { + return switch (signedness) { + .signed => switch (bits) { 8 => "i8", 16 => "i16", 32 => "i32", 64 => "i64", - else => @compileError("unsupported signed int size"), + else => "opaque", }, - .unsigned => switch (info.bits) { + .unsigned => switch (bits) { 8 => "u8", 16 => "u16", 32 => "u32", 64 => "u64", - else => @compileError("unsupported unsigned int size"), + else => "opaque", }, - }, - .@"enum" => "enum", - .@"struct" => "struct", - .pointer => "pointer", - .array => "array", - else => "opaque", - }; -} + }; + } -test "json parses" { - const parsed = try std.json.parseFromSlice( - std.json.Value, - std.testing.allocator, - json, - .{}, - ); + fn writeUpperObjectField(comptime name: []const u8, jws: *std.json.Stringify) std.Io.Writer.Error!void { + var upper: [name.len]u8 = undefined; + for (name, 0..) |c, i| upper[i] = std.ascii.toUpper(c); + try jws.objectField(&upper); + } + + fn writeEnumObjectField( + comptime enum_name: []const u8, + comptime zig_name: []const u8, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + if (std.mem.eql(u8, enum_name, "GhosttyKey") and std.mem.startsWith(u8, zig_name, "key_")) + return writeUpperObjectField(zig_name["key_".len..], jws); + if (std.mem.eql(u8, enum_name, "GhosttySgrAttributeTag")) { + if (std.mem.eql(u8, zig_name, "256_underline_color")) return jws.objectField("UNDERLINE_COLOR_256"); + if (std.mem.eql(u8, zig_name, "8_bg")) return jws.objectField("BG_8"); + if (std.mem.eql(u8, zig_name, "8_fg")) return jws.objectField("FG_8"); + if (std.mem.eql(u8, zig_name, "8_bright_bg")) return jws.objectField("BRIGHT_BG_8"); + if (std.mem.eql(u8, zig_name, "8_bright_fg")) return jws.objectField("BRIGHT_FG_8"); + if (std.mem.eql(u8, zig_name, "256_bg")) return jws.objectField("BG_256"); + if (std.mem.eql(u8, zig_name, "256_fg")) return jws.objectField("FG_256"); + } + if (std.mem.eql(u8, enum_name, "GhosttyTerminalOption") and std.mem.eql(u8, zig_name, "size_cb")) + return jws.objectField("SIZE"); + return writeUpperObjectField(zig_name, jws); + } + + fn isBuiltinType(name: []const u8) bool { + const builtins = [_][]const u8{ + "array", "bool", "f32", "f64", "function", "i8", "i16", "i32", + "i64", "opaque", "pointer", "u8", "u16", "u32", "u64", "void", + }; + for (builtins) |builtin_name| { + if (std.mem.eql(u8, name, builtin_name)) return true; + } + return false; + } +}; + +test "manifest parses and is versioned" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); defer parsed.deinit(); - const root = parsed.value.object; + try std.testing.expectEqual(@as(i64, 1), root.get("schema").?.integer); + try std.testing.expect(root.contains("abi")); + try std.testing.expect(root.contains("library_version")); + const manifest_types = root.get("types").?.object; + try std.testing.expectEqual(type_decls.len, manifest_types.count()); + inline for (type_decls) |decl| try std.testing.expect(manifest_types.contains(decl.name)); - // Verify we have every public struct declared by the C API headers. - const expected_structs = [_][]const u8{ - "GhosttyAllocator", - "GhosttyAllocatorVtable", - "GhosttyBuffer", - "GhosttyClipboardContent", - "GhosttyClipboardWrite", - "GhosttyCodepoints", - "GhosttyColorPaletteMask", - "GhosttyColorRgb", - "GhosttyColorX11Entry", - "GhosttyDeviceAttributes", - "GhosttyDeviceAttributesPrimary", - "GhosttyDeviceAttributesSecondary", - "GhosttyDeviceAttributesTertiary", - "GhosttyFormatterScreenExtra", - "GhosttyFormatterTerminalExtra", - "GhosttyFormatterTerminalOptions", - "GhosttyGridRef", - "GhosttyKittyGraphicsPlacementRenderInfo", - "GhosttyMouseEncoderSize", - "GhosttyMousePosition", - "GhosttyPoint", - "GhosttyPointCoordinate", - "GhosttyReader", - "GhosttyRenderStateColors", - "GhosttyRenderStateCursor", - "GhosttyRenderStateRowSelection", - "GhosttySelection", - "GhosttySelectionGestureBehaviors", - "GhosttySelectionGestureGeometry", - "GhosttySgrAttribute", - "GhosttySgrUnknown", - "GhosttySizeReportSize", - "GhosttyString", - "GhosttyStyle", - "GhosttyStyleColor", - "GhosttySurfacePosition", - "GhosttySysImage", - "GhosttyTerminalDesktopNotification", - "GhosttyTerminalModeConfig", - "GhosttyTerminalProgressReport", - "GhosttyTerminalScrollbar", - "GhosttyTerminalScrollViewport", - "GhosttyTerminalSelectLineOptions", - "GhosttyTerminalSelectWordBetweenOptions", - "GhosttyTerminalSelectWordOptions", - "GhosttyTerminalSelectionFormatOptions", - "GhosttyTerminalUnknownSequence", - "GhosttyTerminalUnknownStringSequence", - "GhosttyWriter", - }; - try std.testing.expectEqual(expected_structs.len, root.count()); - for (expected_structs) |name| { - try std.testing.expect(root.contains(name)); - } - - const clipboard_content = root.get("GhosttyClipboardContent").?.object; - const clipboard_content_fields = clipboard_content.get("fields").?.object; - try std.testing.expect(clipboard_content_fields.contains("mime")); - try std.testing.expect(clipboard_content_fields.contains("data")); - - const clipboard_write = root.get("GhosttyClipboardWrite").?.object; - const clipboard_write_fields = clipboard_write.get("fields").?.object; - try std.testing.expect(clipboard_write_fields.contains("size")); - try std.testing.expect(clipboard_write_fields.contains("location")); - try std.testing.expect(clipboard_write_fields.contains("contents")); - try std.testing.expect(clipboard_write_fields.contains("contents_len")); - - const unknown_sequence = root.get("GhosttyTerminalUnknownSequence").?.object; - const unknown_sequence_fields = unknown_sequence.get("fields").?.object; - try std.testing.expect(unknown_sequence_fields.contains("tag")); - try std.testing.expect(unknown_sequence_fields.contains("value")); - - const unknown_string = root.get("GhosttyTerminalUnknownStringSequence").?.object; - const unknown_string_fields = unknown_string.get("fields").?.object; - try std.testing.expect(unknown_string_fields.contains("truncated")); - try std.testing.expect(unknown_string_fields.contains("content")); - - const render_cursor_fields = root.get("GhosttyRenderStateCursor").?.object + const cursor_fields = manifest_types.get("GhosttyRenderStateCursor").?.object .get("fields").?.object; - try std.testing.expect(render_cursor_fields.contains("size")); - try std.testing.expect(render_cursor_fields.contains("viewport_has_value")); - try std.testing.expect(render_cursor_fields.contains("viewport_x")); - try std.testing.expect(render_cursor_fields.contains("viewport_y")); - try std.testing.expect(render_cursor_fields.contains("wide_tail")); - try std.testing.expect(render_cursor_fields.contains("visible")); - try std.testing.expect(render_cursor_fields.contains("blinking")); - try std.testing.expect(render_cursor_fields.contains("password_input")); - try std.testing.expect(render_cursor_fields.contains("visual_style")); - - const reader_fields = root.get("GhosttyReader").?.object - .get("fields").?.object; - try std.testing.expect(reader_fields.contains("read")); - try std.testing.expect(reader_fields.contains("userdata")); - - const writer_fields = root.get("GhosttyWriter").?.object - .get("fields").?.object; - try std.testing.expect(writer_fields.contains("write")); - try std.testing.expect(writer_fields.contains("userdata")); - - try std.testing.expect(!root.contains("GhosttyTerminalOptions")); + try std.testing.expect(cursor_fields.contains("size")); + try std.testing.expect(cursor_fields.contains("viewport_has_value")); + try std.testing.expect(cursor_fields.contains("viewport_x")); + try std.testing.expect(cursor_fields.contains("viewport_y")); + try std.testing.expect(cursor_fields.contains("wide_tail")); + try std.testing.expect(cursor_fields.contains("visible")); + try std.testing.expect(cursor_fields.contains("blinking")); + try std.testing.expect(cursor_fields.contains("password_input")); + try std.testing.expect(cursor_fields.contains("visual_style")); } -test "struct sizes are non-zero" { - const parsed = try std.json.parseFromSlice( - std.json.Value, - std.testing.allocator, - json, - .{}, - ); +test "manifest describes enums, arrays, and tagged unions" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); defer parsed.deinit(); + const manifest_types = parsed.value.object.get("types").?.object; - var it = parsed.value.object.iterator(); - while (it.next()) |entry| { - const struct_info = entry.value_ptr.object; - const size = struct_info.get("size").?.integer; - try std.testing.expect(size > 0); + const data = manifest_types.get("GhosttyRenderStateData").?.object; + try std.testing.expectEqualStrings("enum", data.get("kind").?.string); + try std.testing.expectEqual(@as(i64, @intFromEnum(render.Data.dirty)), data.get("values").?.object.get("DIRTY").?.integer); + + const colors = manifest_types.get("GhosttyRenderStateColors").?.object; + const palette = colors.get("fields").?.object.get("palette").?.object; + try std.testing.expectEqualStrings("array", palette.get("type").?.string); + try std.testing.expectEqualStrings("GhosttyColorRgb", palette.get("elem").?.string); + try std.testing.expectEqual(@as(i64, 256), palette.get("count").?.integer); + + const style_color = manifest_types.get("GhosttyStyleColor").?.object; + const value = style_color.get("fields").?.object.get("value").?.object; + try std.testing.expectEqualStrings("GhosttyStyleColorValue", value.get("type").?.string); + try std.testing.expectEqualStrings("tag", value.get("tag").?.string); + try std.testing.expectEqualStrings("palette", value.get("arms").?.object.get("PALETTE").?.string); + try std.testing.expect(value.get("arms").?.object.get("NONE").? == .null); +} + +test "manifest uses public enum names" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); + defer parsed.deinit(); + const manifest_types = parsed.value.object.get("types").?.object; + + const cell_content = manifest_types.get("GhosttyCellContentTag").?.object.get("values").?.object; + try std.testing.expect(cell_content.contains("TAG_MAX_VALUE")); + try std.testing.expect(!cell_content.contains("MAX_VALUE")); + + const sgr_values = manifest_types.get("GhosttySgrAttributeTag").?.object.get("values").?.object; + try std.testing.expectEqual(@as(i64, 9), sgr_values.get("UNDERLINE_COLOR_256").?.integer); + try std.testing.expectEqual(@as(i64, 23), sgr_values.get("BG_8").?.integer); + try std.testing.expect(!sgr_values.contains("256_UNDERLINE_COLOR")); + + const sgr_arms = manifest_types.get("GhosttySgrAttribute").?.object + .get("fields").?.object.get("value").?.object.get("arms").?.object; + try std.testing.expectEqualStrings("underline_color_256", sgr_arms.get("UNDERLINE_COLOR_256").?.string); + try std.testing.expectEqualStrings("bg_8", sgr_arms.get("BG_8").?.string); + + const terminal_values = manifest_types.get("GhosttyTerminalOption").?.object.get("values").?.object; + try std.testing.expectEqual(@as(i64, 6), terminal_values.get("SIZE").?.integer); + try std.testing.expect(!terminal_values.contains("SIZE_CB")); + + const osc_values = manifest_types.get("GhosttyOscCommandType").?.object.get("values").?.object; + try std.testing.expectEqual(@as(i64, 22), osc_values.get("KITTY_TEXT_SIZING").?.integer); + try std.testing.expectEqual(@as(i64, 23), osc_values.get("KITTY_CLIPBOARD_PROTOCOL").?.integer); + try std.testing.expectEqual(@as(i64, 24), osc_values.get("KITTY_DND_PROTOCOL").?.integer); + try std.testing.expectEqual(@as(i64, 25), osc_values.get("CONTEXT_SIGNAL").?.integer); + try std.testing.expect(osc_values.contains("TYPE_MAX_VALUE")); + try std.testing.expect(!osc_values.contains("MAX_VALUE")); + + const key_values = manifest_types.get("GhosttyKey").?.object.get("values").?.object; + try std.testing.expect(key_values.contains("A")); + try std.testing.expect(!key_values.contains("KEY_A")); + + const mode_values = manifest_types.get("GhosttyModeReportState").?.object.get("values").?.object; + try std.testing.expectEqual(@as(i64, 0), mode_values.get("NOT_RECOGNIZED").?.integer); + try std.testing.expectEqual(@as(i64, 4), mode_values.get("PERMANENTLY_RESET").?.integer); +} + +test "manifest named references resolve" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); + defer parsed.deinit(); + const manifest_types = parsed.value.object.get("types").?.object; + + var type_iterator = manifest_types.iterator(); + while (type_iterator.next()) |type_entry| { + const descriptor = type_entry.value_ptr.object; + const fields_value = descriptor.get("fields") orelse continue; + var field_iterator = fields_value.object.iterator(); + while (field_iterator.next()) |field_entry| { + const field = field_entry.value_ptr.object; + const field_type = field.get("type").?.string; + if (!Json.isBuiltinType(field_type)) + try std.testing.expect(manifest_types.contains(field_type)); + + if (field.get("elem")) |elem_value| { + const elem = elem_value.string; + if (!Json.isBuiltinType(elem)) + try std.testing.expect(manifest_types.contains(elem)); + } + + if (field.get("arms")) |arms_value| { + const union_descriptor = manifest_types.get(field_type).?.object; + const union_fields = union_descriptor.get("fields").?.object; + const tag_field_name = field.get("tag").?.string; + const tag_type_name = fields_value.object.get(tag_field_name).?.object.get("type").?.string; + const tag_values = manifest_types.get(tag_type_name).?.object.get("values").?.object; + var arm_iterator = arms_value.object.iterator(); + while (arm_iterator.next()) |arm_entry| { + try std.testing.expect(tag_values.contains(arm_entry.key_ptr.*)); + if (arm_entry.value_ptr.* == .null) continue; + try std.testing.expect(union_fields.contains(arm_entry.value_ptr.string)); + } + } + } } } diff --git a/src/terminal/point.zig b/src/terminal/point.zig index f6bf25c39..5937576cf 100644 --- a/src/terminal/point.zig +++ b/src/terminal/point.zig @@ -71,7 +71,15 @@ pub const Point = union(Tag) { @This(), // Padding: largest variant is Coordinate (u16 + u32 = 6 bytes). // Use [2]u64 (16 bytes) for future expansion. - [2]u64, + .{ + .padding = [2]u64, + .field_renames = .{ + .active = "coordinate", + .viewport = "coordinate", + .screen = "coordinate", + .history = "coordinate", + }, + }, ); pub const C = c_union.C; pub const CValue = c_union.CValue; diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index e9b9354c3..3fecd4469 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -161,7 +161,18 @@ pub const Attribute = union(Tag) { // Largest variant is Unknown.C: 2 pointers + 2 usize = 32 bytes on 64-bit. // We use [8]u64 (64 bytes) to allow room for future expansion while // maintaining ABI compatibility. - [8]u64, + .{ + .padding = [8]u64, + .field_renames = .{ + .@"256_underline_color" = "underline_color_256", + .@"8_bg" = "bg_8", + .@"8_fg" = "fg_8", + .@"8_bright_bg" = "bright_bg_8", + .@"8_bright_fg" = "bright_fg_8", + .@"256_bg" = "bg_256", + .@"256_fg" = "fg_256", + }, + }, ); pub const Value = c_union.Value; pub const C = c_union.C; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index afbfa719c..105cd1e4f 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -236,7 +236,7 @@ pub const Action = union(Key) { @This(), // TODO: Before shipping an ABI-compatible libghostty, verify this. // This was just arbitrarily chosen for now. - [16]u64, + .{ .padding = [16]u64 }, ); pub const Tag = c_union.Tag; pub const Value = c_union.Value; From c75559589e41721ab5a3de8e336d8476f4290535 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 20:21:59 -0700 Subject: [PATCH 082/392] libghostty: add ABI manifest schema The ABI manifest previously had no machine-readable grammar or test that the public export conformed to it. Define a Draft 2020-12 schema and add a build check that executes ghostty_type_json for native and wasm libraries before validation. Run both forms in CI and publish the schema with the generated API docs. --- .github/workflows/test.yml | 9 + Doxyfile | 3 +- build.zig | 10 + include/ghostty/vt/types.h | 3 + nix/devShell.nix | 2 + src/build/docker/lib-c-docs/Dockerfile | 1 + src/terminal/c/types-schema-verify.py | 114 ++++++++ src/terminal/c/types.schema.json | 348 +++++++++++++++++++++++++ src/terminal/c/types.zig | 3 +- 9 files changed, 491 insertions(+), 2 deletions(-) create mode 100644 src/terminal/c/types-schema-verify.py create mode 100644 src/terminal/c/types.schema.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e45793f22..a5216072c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -701,6 +701,12 @@ jobs: -Dtarget=wasm32-freestanding \ -Doptimize=ReleaseSmall + - name: Validate WASM ABI manifest + run: | + nix develop -c zig build test-lib-vt-schema \ + -Dtarget=wasm32-freestanding \ + -Doptimize=ReleaseSmall + - name: Optimize ReleaseSmall WASM run: | nix develop -c wasm-opt -O3 \ @@ -1470,6 +1476,9 @@ jobs: - name: Test run: nix develop -c zig build test-lib-vt + - name: Validate native ABI manifest + run: nix develop -c zig build test-lib-vt-schema + test-kaitai: if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' needs: skip diff --git a/Doxyfile b/Doxyfile index 1703e6fac..77eeb3d3c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -57,7 +57,8 @@ GENERATE_HTML = YES HTML_OUTPUT = zig-out/share/ghostty/doc/libghostty HTML_EXTRA_STYLESHEET = dist/doxygen/ghostty.css HTML_EXTRA_FILES = dist/doxygen/favicon.png \ - dist/doxygen/mobile-nav.js + dist/doxygen/mobile-nav.js \ + src/terminal/c/types.schema.json HTML_COLORSTYLE = DARK HTML_CODE_FOLDING = NO HTML_HEADER = dist/doxygen/header.html diff --git a/build.zig b/build.zig index 1c56cadf8..5ce7e816b 100644 --- a/build.zig +++ b/build.zig @@ -73,6 +73,10 @@ pub fn build(b: *std.Build) !void { "test-lib-vt-build", "Build libghostty-vt tests without running them (compile check)", ); + const test_lib_vt_schema_step = b.step( + "test-lib-vt-schema", + "Validate the libghostty-vt ABI type manifest", + ); const test_valgrind_step = b.step( "test-valgrind", "Run tests under valgrind", @@ -136,6 +140,12 @@ pub fn build(b: *std.Build) !void { }; libghostty_vt_shared.install(b.getInstallStep()); + const type_schema_test = b.addSystemCommand(&.{"python3"}); + type_schema_test.addFileArg(b.path("src/terminal/c/types-schema-verify.py")); + type_schema_test.addFileArg(b.path("src/terminal/c/types.schema.json")); + type_schema_test.addFileArg(libghostty_vt_shared.output); + test_lib_vt_schema_step.dependOn(&type_schema_test.step); + // libghostty-vt static lib const libghostty_vt_static = try buildpkg.GhosttyLibVt.initStatic( b, diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index bc1c6a69b..787cc0148 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -342,6 +342,9 @@ typedef struct { * this manifest rather than hardcoding them. Consumers should reject unknown * schema versions and verify the descriptors they require at initialization. * + * The formal format is defined by the + * libghostty-vt ABI manifest JSON Schema. + * * Example (abbreviated): * @code{.json} * { diff --git a/nix/devShell.nix b/nix/devShell.nix index c94892414..fba128a21 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -91,8 +91,10 @@ inherit pkgs lib stdenv; }; python = python3.withPackages (python-pkgs: [ + python-pkgs.jsonschema python-pkgs.kaitaistruct python-pkgs.ucs-detect + python-pkgs.wasmtime ]); in mkShell { diff --git a/src/build/docker/lib-c-docs/Dockerfile b/src/build/docker/lib-c-docs/Dockerfile index a3cfdcc98..b5d176160 100644 --- a/src/build/docker/lib-c-docs/Dockerfile +++ b/src/build/docker/lib-c-docs/Dockerfile @@ -14,6 +14,7 @@ WORKDIR /ghostty COPY include/ ./include/ COPY images/ ./images/ COPY dist/doxygen/ ./dist/doxygen/ +COPY src/terminal/c/types.schema.json ./src/terminal/c/types.schema.json COPY example/ ./example/ COPY Doxyfile ./ COPY DoxygenLayout.xml ./ diff --git a/src/terminal/c/types-schema-verify.py b/src/terminal/c/types-schema-verify.py new file mode 100644 index 000000000..1a8e064dc --- /dev/null +++ b/src/terminal/c/types-schema-verify.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Validate the ABI manifest exported by a native or WebAssembly library.""" + +from __future__ import annotations + +import argparse +import ctypes +import json +import sys +from pathlib import Path +from typing import Any + +try: + import jsonschema +except ImportError as error: + raise SystemExit( + "missing ABI schema verifier dependencies; run this inside " + "`nix develop`" + ) from error + + +def load_native_manifest(path: Path) -> bytes: + """Call ghostty_type_json in a native shared library.""" + library = ctypes.CDLL(str(path.resolve())) + type_json = library.ghostty_type_json + type_json.argtypes = () + type_json.restype = ctypes.c_char_p + + result = type_json() + if result is None: + raise RuntimeError("ghostty_type_json returned NULL") + return result + + +def load_wasm_manifest(path: Path) -> bytes: + """Call ghostty_type_json and read its result from WebAssembly memory.""" + try: + import wasmtime + except ImportError as error: + raise SystemExit( + "missing WebAssembly verifier dependencies; run this inside " + "`nix develop`" + ) from error + + engine = wasmtime.Engine() + module = wasmtime.Module.from_file(engine, str(path)) + store = wasmtime.Store(engine) + instance = wasmtime.Instance(store, module, []) + exports = instance.exports(store) + memory = exports["memory"] + type_json = exports["ghostty_type_json"] + + pointer = type_json(store) + data = memory.read(store, pointer, memory.data_len(store)) + terminator = data.find(b"\0") + if terminator < 0: + raise RuntimeError("ghostty_type_json result is not NUL terminated") + return bytes(data[:terminator]) + + +def load_manifest(path: Path) -> dict[str, Any]: + """Execute the public export and decode its JSON result.""" + encoded = ( + load_wasm_manifest(path) + if path.suffix.lower() == ".wasm" + else load_native_manifest(path) + ) + value = json.loads(encoded) + if not isinstance(value, dict): + raise ValueError("ghostty_type_json did not return a JSON object") + return value + + +def format_path(parts: list[object]) -> str: + """Format a jsonschema error path for command-line output.""" + return "/" + "/".join(str(part) for part in parts) + + +def validate(schema_path: Path, library_path: Path) -> None: + """Validate the schema itself and the manifest returned by the library.""" + schema = json.loads(schema_path.read_bytes()) + validator_type = jsonschema.validators.validator_for(schema) + validator_type.check_schema(schema) + + manifest = load_manifest(library_path) + errors = sorted( + validator_type(schema).iter_errors(manifest), + key=lambda error: list(error.absolute_path), + ) + if errors: + for error in errors: + print( + f"{format_path(list(error.absolute_path))}: {error.message}", + file=sys.stderr, + ) + raise SystemExit(f"ABI manifest failed {schema_path}") + + abi = manifest["abi"] + print( + f"validated {abi['target']}-{abi['os']} ABI manifest " + f"({len(manifest['types'])} types)" + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("schema", type=Path) + parser.add_argument("library", type=Path) + args = parser.parse_args() + validate(args.schema, args.library) + + +if __name__ == "__main__": + main() diff --git a/src/terminal/c/types.schema.json b/src/terminal/c/types.schema.json new file mode 100644 index 000000000..7020cb172 --- /dev/null +++ b/src/terminal/c/types.schema.json @@ -0,0 +1,348 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "libghostty-vt ABI type manifest", + "description": "Schema for the JSON returned by ghostty_type_json.", + "type": "object", + "additionalProperties": false, + "required": ["schema", "abi", "library_version", "commit", "dirty", "types"], + "properties": { + "schema": { + "const": 1 + }, + "abi": { + "$ref": "#/$defs/abi" + }, + "library_version": { + "type": "string", + "minLength": 1 + }, + "commit": { + "type": ["string", "null"], + "minLength": 1 + }, + "dirty": { + "type": ["boolean", "null"] + }, + "types": { + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^Ghostty[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "$ref": "#/$defs/typeDescriptor" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveInteger": { + "type": "integer", + "minimum": 1 + }, + "typeReference": { + "type": "string", + "pattern": "^(Ghostty[A-Za-z0-9_]+|bool|f32|f64|function|i8|i16|i32|i64|opaque|u8|u16|u32|u64|void)$" + }, + "abi": { + "type": "object", + "additionalProperties": false, + "required": [ + "target", + "os", + "environment", + "pointer_size", + "usize_size", + "endian" + ], + "properties": { + "target": { + "type": "string", + "minLength": 1 + }, + "os": { + "type": "string", + "minLength": 1 + }, + "environment": { + "type": "string", + "minLength": 1 + }, + "pointer_size": { + "$ref": "#/$defs/positiveInteger" + }, + "usize_size": { + "$ref": "#/$defs/positiveInteger" + }, + "endian": { + "enum": ["little", "big"] + } + } + }, + "fieldBase": { + "type": "object", + "required": ["offset", "size", "type"], + "properties": { + "offset": { + "$ref": "#/$defs/nonNegativeInteger" + }, + "size": { + "$ref": "#/$defs/nonNegativeInteger" + } + } + }, + "plainField": { + "allOf": [ + { + "$ref": "#/$defs/fieldBase" + }, + { + "properties": { + "type": { + "$ref": "#/$defs/typeReference" + } + } + } + ], + "unevaluatedProperties": false + }, + "arrayField": { + "allOf": [ + { + "$ref": "#/$defs/fieldBase" + }, + { + "required": ["elem", "count"], + "properties": { + "type": { + "const": "array" + }, + "elem": { + "$ref": "#/$defs/typeReference" + }, + "count": { + "$ref": "#/$defs/nonNegativeInteger" + } + } + } + ], + "unevaluatedProperties": false + }, + "pointerField": { + "allOf": [ + { + "$ref": "#/$defs/fieldBase" + }, + { + "required": ["elem", "const"], + "properties": { + "type": { + "const": "pointer" + }, + "elem": { + "$ref": "#/$defs/typeReference" + }, + "const": { + "type": "boolean" + }, + "nullable": { + "const": true + } + } + } + ], + "unevaluatedProperties": false + }, + "taggedUnionField": { + "allOf": [ + { + "$ref": "#/$defs/fieldBase" + }, + { + "required": ["tag", "arms"], + "properties": { + "type": { + "$ref": "#/$defs/typeReference" + }, + "tag": { + "type": "string", + "minLength": 1 + }, + "arms": { + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "additionalProperties": { + "type": ["string", "null"] + } + } + } + } + ], + "unevaluatedProperties": false + }, + "field": { + "oneOf": [ + { + "$ref": "#/$defs/plainField" + }, + { + "$ref": "#/$defs/arrayField" + }, + { + "$ref": "#/$defs/pointerField" + }, + { + "$ref": "#/$defs/taggedUnionField" + } + ] + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/field" + } + }, + "descriptorBase": { + "type": "object", + "required": ["kind", "size", "align"], + "properties": { + "size": { + "$ref": "#/$defs/nonNegativeInteger" + }, + "align": { + "$ref": "#/$defs/positiveInteger" + } + } + }, + "structDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "required": ["fields"], + "properties": { + "kind": { + "const": "struct" + }, + "fields": { + "$ref": "#/$defs/fields" + } + } + } + ], + "unevaluatedProperties": false + }, + "unionDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "required": ["fields"], + "properties": { + "kind": { + "const": "union" + }, + "fields": { + "$ref": "#/$defs/fields" + } + } + } + ], + "unevaluatedProperties": false + }, + "enumDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "required": ["underlying", "prefix", "values"], + "properties": { + "kind": { + "const": "enum" + }, + "underlying": { + "const": "i32" + }, + "prefix": { + "type": "string", + "minLength": 1 + }, + "values": { + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "additionalProperties": { + "type": "integer" + } + } + } + } + ], + "unevaluatedProperties": false + }, + "aliasDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "required": ["type"], + "properties": { + "kind": { + "const": "alias" + }, + "type": { + "$ref": "#/$defs/typeReference" + } + } + } + ], + "unevaluatedProperties": false + }, + "opaqueDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "properties": { + "kind": { + "const": "opaque" + } + } + } + ], + "unevaluatedProperties": false + }, + "typeDescriptor": { + "oneOf": [ + { + "$ref": "#/$defs/structDescriptor" + }, + { + "$ref": "#/$defs/unionDescriptor" + }, + { + "$ref": "#/$defs/enumDescriptor" + }, + { + "$ref": "#/$defs/aliasDescriptor" + }, + { + "$ref": "#/$defs/opaqueDescriptor" + } + ] + } + } +} diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig index 88759ef1c..2242b1d50 100644 --- a/src/terminal/c/types.zig +++ b/src/terminal/c/types.zig @@ -2,7 +2,8 @@ //! //! The manifest is embedded in the library and returned by //! `ghostty_type_json`. It is intended for FFI consumers that cannot use the -//! C headers directly, most notably WebAssembly hosts. +//! C headers directly, most notably WebAssembly hosts. Its format is defined +//! by `types.schema.json`. const std = @import("std"); const builtin = @import("builtin"); const build_options = @import("terminal_options"); From 0e8b7bea63ccf8b89d15a82bdaf6a9bb3fc8c43d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 15 Aug 2026 21:04:03 -0700 Subject: [PATCH 083/392] vt: expose packed cell layout GhosttyCell was exposed as a raw integer while its manifest entry was only an alias, forcing bulk-read consumers to duplicate the internal cell bit layout.\n\nAdd reflection helpers for packed structs and tagged unions, and keep the C-facing layout metadata next to Cell itself. Extend the ABI manifest and schema with recursive bit descriptors so every content arm, including palette and RGB backgrounds, can be decoded without hardcoded masks.\n\nDocument manifest-driven cell decoding and test the metadata against Zig reflection and real cell values. --- include/ghostty/vt/render.h | 9 +- include/ghostty/vt/screen.h | 19 +- include/ghostty/vt/types.h | 7 + src/lib/main.zig | 3 + src/lib/packed.zig | 290 ++++++++++++++++++++++++++++++ src/terminal/c/types.schema.json | 155 +++++++++++++++- src/terminal/c/types.zig | 300 ++++++++++++++++++++++++++++++- src/terminal/page.zig | 46 +++++ 8 files changed, 813 insertions(+), 16 deletions(-) create mode 100644 src/lib/packed.zig diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 728966f48..f6b5e2222 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -256,9 +256,12 @@ typedef enum GHOSTTY_ENUM_TYPED { * * This is the bulk alternative to iterating cells one at a time. * It lets callers with expensive call boundaries (e.g. WebAssembly - * embedders) read an entire row with a single call, then drill - * into the cells iterator only for cells that need managed data - * (styles, graphemes). */ + * embedders) read an entire row with a single call. + * + * Bit positions aren't protected by ABI, so callers should parse them + * out of the manifest from `ghostty_type_json`. Callers with access + * to the C header or without high FFI costs should use `ghostty_cell_get`. + */ GHOSTTY_RENDER_STATE_ROW_DATA_CELLS_RAW = 5, GHOSTTY_RENDER_STATE_ROW_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyRenderStateRowData; diff --git a/include/ghostty/vt/screen.h b/include/ghostty/vt/screen.h index 0d009a781..2899989dd 100644 --- a/include/ghostty/vt/screen.h +++ b/include/ghostty/vt/screen.h @@ -21,19 +21,21 @@ extern "C" { * Terminal screen cell and row types. * * These types represent the contents of a terminal screen. A GhosttyCell - * is a single grid cell and a GhosttyRow is a single row. Both are opaque - * values whose fields are accessed via ghostty_cell_get() and - * ghostty_row_get() respectively. + * is a single grid cell and a GhosttyRow is a single row. Cell fields can + * be accessed via ghostty_cell_get() or decoded from the packed layout in + * ghostty_type_json(). Rows are opaque and accessed via ghostty_row_get(). * * @{ */ /** - * Opaque cell value. + * Packed cell value. * - * Represents a single terminal cell. The internal layout is opaque and - * must be queried via ghostty_cell_get(). Obtain cell values from - * terminal query APIs. + * Represents a single terminal cell. Portable callers can query fields via + * ghostty_cell_get(). Boundary-sensitive callers can decode the packed value + * using the GhosttyCell descriptor returned by ghostty_type_json(). The + * manifest is authoritative for the linked build; hardcoding bit positions + * is unsupported. * * @ingroup screen */ @@ -55,7 +57,8 @@ typedef uint64_t GhosttyRow; * * The memory is not owned by this struct. The pointer is only valid * for the lifetime documented by the API that produces it. Each value - * is queried via ghostty_cell_get() like any other GhosttyCell. + * can be queried via ghostty_cell_get() or decoded using the GhosttyCell + * packed descriptor returned by ghostty_type_json(). * * @ingroup screen */ diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h index 787cc0148..a0621945e 100644 --- a/include/ghostty/vt/types.h +++ b/include/ghostty/vt/types.h @@ -342,6 +342,13 @@ typedef struct { * this manifest rather than hardcoding them. Consumers should reject unknown * schema versions and verify the descriptors they require at initialization. * + * Packed type descriptors define fields using `lsb` and `width`. `lsb` is + * relative to bit zero of the containing numerical value; for nested packed + * layouts it is relative to the immediate containing field. Tagged packed + * unions select an inline arm layout using the named tag field. These layouts + * describe the current linked build and are not a cross-version stability + * promise. + * * The formal format is defined by the * libghostty-vt ABI manifest JSON Schema. * diff --git a/src/lib/main.zig b/src/lib/main.zig index 45cdb71d4..573007bd9 100644 --- a/src/lib/main.zig +++ b/src/lib/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const enumpkg = @import("enum.zig"); +const packedpkg = @import("packed.zig"); const structpkg = @import("struct.zig"); const types = @import("types.zig"); const unionpkg = @import("union.zig"); @@ -8,6 +9,8 @@ pub const allocator = @import("allocator.zig"); pub const TinyIo = @import("TinyIo.zig"); pub const Buffer = types.Buffer; pub const Enum = enumpkg.Enum; +pub const Packed = packedpkg.Packed; +pub const PackedTaggedUnion = packedpkg.PackedTaggedUnion; pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum; pub const String = types.String; pub const Struct = structpkg.Struct; diff --git a/src/lib/packed.zig b/src/lib/packed.zig new file mode 100644 index 000000000..b210242d8 --- /dev/null +++ b/src/lib/packed.zig @@ -0,0 +1,290 @@ +const std = @import("std"); +const testing = std.testing; + +/// Metadata for a field in a packed layout. +pub const FieldOptions = struct { + /// Public name for this field. The Zig field name is used by default. + name: ?[]const u8 = null, + + /// Public type name for scalar fields. The Zig type is used by default. + type_name: ?[]const u8 = null, + + /// Omit this field from public metadata. This is intended for padding. + omit: bool = false, + + /// How the field is represented in public metadata. + encoding: Encoding = .scalar, + + pub const Encoding = union(enum) { + scalar, + @"packed": type, + tagged_union: type, + }; +}; + +/// Create metadata for a packed struct. Physical layout information is always +/// reflected from `T`; options only provide public names and relationships. +pub fn Packed( + comptime T: type, + comptime options: PackedOptions(T), +) type { + const info = packedStructInfo(T); + const FieldT = std.meta.FieldEnum(T); + + return struct { + pub const Zig = T; + + // I know this is an insane amount of validation but getting + // this correct is critical to C APIs working so we go overboard. + comptime { + for (info.fields, 0..) |field, i| { + const field_options = @field(options.fields, field.name); + if (field_options.omit) { + if (field_options.name != null or + field_options.type_name != null or + field_options.encoding != .scalar) + { + @compileError("omitted packed field has other options: " ++ field.name); + } + continue; + } + + if (field_options.name) |name| { + if (name.len == 0) + @compileError("packed field name cannot be empty: " ++ field.name); + } + + switch (field_options.encoding) { + .scalar => switch (@typeInfo(field.type)) { + .bool, .int, .@"enum" => {}, + else => @compileError("packed field requires an explicit encoding: " ++ field.name), + }, + .@"packed" => |Layout| { + if (Layout.Zig != field.type) + @compileError("nested packed layout has the wrong Zig type: " ++ field.name); + if (field_options.type_name != null) + @compileError("nested packed field cannot have a scalar type name: " ++ field.name); + }, + .tagged_union => |Layout| { + if (Layout.Owner != T or Layout.Union != field.type or + Layout.union_field != @field(FieldT, field.name)) + { + @compileError("tagged union layout does not match packed field: " ++ field.name); + } + if (field_options.type_name != null) + @compileError("tagged union field cannot have a scalar type name: " ++ field.name); + }, + } + + const public_name = field_options.name orelse field.name; + for (info.fields[0..i]) |previous| { + const previous_options = @field(options.fields, previous.name); + if (previous_options.omit) continue; + const previous_name = previous_options.name orelse previous.name; + if (std.mem.eql(u8, public_name, previous_name)) + @compileError("duplicate public packed field name: " ++ public_name); + } + } + } + + pub const Backing = info.backing_integer.?; + pub const Field = std.meta.FieldEnum(T); + + pub fn fieldOptions(comptime field: Field) FieldOptions { + return @field(options.fields, @tagName(field)); + } + + pub fn fieldName(comptime field: Field) ?[]const u8 { + const field_options = fieldOptions(field); + if (field_options.omit) return null; + return field_options.name orelse @tagName(field); + } + + pub fn bitOffset(comptime field: Field) usize { + return @bitOffsetOf(T, @tagName(field)); + } + + pub fn bitWidth(comptime field: Field) usize { + return @bitSizeOf(@FieldType(T, @tagName(field))); + } + }; +} + +/// Options for a packed struct. A field is generated for every field in `T` +/// so unknown field names are rejected by normal Zig type checking. +pub fn PackedOptions(comptime T: type) type { + const fields = packedStructInfo(T).fields; + const default_options: FieldOptions = .{}; + + var names: [fields.len][]const u8 = undefined; + var types: [fields.len]type = undefined; + var attrs: [fields.len]std.builtin.Type.StructField.Attributes = undefined; + + for (fields, 0..) |field, i| { + names[i] = field.name; + types[i] = FieldOptions; + attrs[i] = .{ .default_value_ptr = &default_options }; + } + + const Fields = @Struct(.auto, null, &names, &types, &attrs); + return struct { + fields: Fields = .{}, + }; +} + +/// Describe a packed union field whose active arm is selected by another +/// field in the containing packed struct. Each tag maps to a reflected packed +/// arm layout. Multiple tags may map to the same source union field. +pub fn PackedTaggedUnion( + comptime OwnerT: type, + comptime union_field_value: std.meta.FieldEnum(OwnerT), + comptime tag_field_value: std.meta.FieldEnum(OwnerT), + comptime options: PackedTaggedUnionOptions( + @FieldType(OwnerT, @tagName(union_field_value)), + @FieldType(OwnerT, @tagName(tag_field_value)), + ), +) type { + _ = packedStructInfo(OwnerT); + const UnionT = @FieldType(OwnerT, @tagName(union_field_value)); + const union_info = @typeInfo(UnionT).@"union"; + if (union_info.layout != .@"packed") + @compileError("packed tagged union value must be a packed union"); + + const TagT = @FieldType(OwnerT, @tagName(tag_field_value)); + const tag_info = @typeInfo(TagT).@"enum"; + const ArmT = PackedTaggedUnionArm(UnionT); + + return struct { + pub const Owner = OwnerT; + pub const Union = UnionT; + pub const Tag = TagT; + pub const Arm = ArmT; + pub const union_field = union_field_value; + pub const tag_field = tag_field_value; + + comptime { + for (tag_info.fields) |tag| { + const arm_value = @field(options.arms, tag.name) orelse continue; + switch (arm_value) { + inline else => |Layout, source| { + const source_name = @tagName(source); + if (Layout.Zig != @FieldType(UnionT, source_name)) + @compileError("packed union arm layout has the wrong Zig type for tag: " ++ tag.name); + if (@bitSizeOf(Layout.Zig) > @bitSizeOf(UnionT)) + @compileError("packed union arm is wider than its union: " ++ tag.name); + }, + } + } + } + + pub fn arm(comptime tag: Tag) ?Arm { + return @field(options.arms, @tagName(tag)); + } + }; +} + +/// Options for mapping tag values to packed union arms. +pub fn PackedTaggedUnionOptions(comptime Union: type, comptime Tag: type) type { + const union_info = @typeInfo(Union).@"union"; + if (union_info.layout != .@"packed") + @compileError("packed tagged union value must be a packed union"); + const tag_fields = @typeInfo(Tag).@"enum".fields; + const Arm = PackedTaggedUnionArm(Union); + const default_arm: ?Arm = null; + + var names: [tag_fields.len][]const u8 = undefined; + var types: [tag_fields.len]type = undefined; + var attrs: [tag_fields.len]std.builtin.Type.StructField.Attributes = undefined; + + for (tag_fields, 0..) |field, i| { + names[i] = field.name; + types[i] = ?Arm; + attrs[i] = .{ .default_value_ptr = &default_arm }; + } + + const Arms = @Struct(.auto, null, &names, &types, &attrs); + return struct { + arms: Arms = .{}, + }; +} + +fn PackedTaggedUnionArm(comptime Union: type) type { + const fields = @typeInfo(Union).@"union".fields; + const Tag = std.meta.FieldEnum(Union); + + var names: [fields.len][]const u8 = undefined; + var types: [fields.len]type = undefined; + var attrs: [fields.len]std.builtin.Type.UnionField.Attributes = undefined; + + for (fields, 0..) |field, i| { + names[i] = field.name; + types[i] = type; + attrs[i] = .{ .@"align" = 1 }; + } + + return @Union(.auto, Tag, &names, &types, &attrs); +} + +fn packedStructInfo(comptime T: type) std.builtin.Type.Struct { + const info = @typeInfo(T).@"struct"; + if (info.layout != .@"packed") + @compileError("Packed requires a packed struct"); + if (info.backing_integer == null) + @compileError("Packed requires an integer-backed packed struct"); + return info; +} + +test "Packed: reflected fields and nested tagged union" { + const Tag = enum(u2) { first, second, third }; + const Value = packed union { + number: packed struct(u4) { + data: u3, + _pad: u1, + }, + flags: packed struct(u4) { + a: bool, + b: bool, + _pad: u2, + }, + }; + const Nested = packed struct(u1) { enabled: bool }; + const Subject = packed struct(u8) { + tag: Tag, + value: Value, + nested: Nested, + _padding: u1, + }; + + const Number = Packed(@FieldType(Value, "number"), .{ .fields = .{ + .data = .{ .name = "number" }, + ._pad = .{ .omit = true }, + } }); + const Flags = Packed(@FieldType(Value, "flags"), .{ .fields = .{ + ._pad = .{ .omit = true }, + } }); + const NestedLayout = Packed(Nested, .{}); + const ValueLayout = PackedTaggedUnion(Subject, .value, .tag, .{ .arms = .{ + .first = .{ .number = Number }, + .second = .{ .number = Number }, + .third = .{ .flags = Flags }, + } }); + const Layout = Packed(Subject, .{ .fields = .{ + .value = .{ .encoding = .{ .tagged_union = ValueLayout } }, + .nested = .{ .encoding = .{ .@"packed" = NestedLayout } }, + ._padding = .{ .omit = true }, + } }); + + try testing.expectEqual(@bitOffsetOf(Subject, "value"), Layout.bitOffset(.value)); + try testing.expectEqual(@bitSizeOf(Value), Layout.bitWidth(.value)); + try testing.expectEqualStrings("number", Number.fieldName(.data).?); + try testing.expect(Number.fieldName(._pad) == null); + switch (ValueLayout.arm(.first).?) { + .number => |ArmLayout| try testing.expect(ArmLayout == Number), + else => return error.TestUnexpectedResult, + } + switch (ValueLayout.arm(.second).?) { + .number => |ArmLayout| try testing.expect(ArmLayout == Number), + else => return error.TestUnexpectedResult, + } + try testing.expectEqual(@as(usize, 1), NestedLayout.bitWidth(.enabled)); +} diff --git a/src/terminal/c/types.schema.json b/src/terminal/c/types.schema.json index 7020cb172..7d784153b 100644 --- a/src/terminal/c/types.schema.json +++ b/src/terminal/c/types.schema.json @@ -45,7 +45,11 @@ }, "typeReference": { "type": "string", - "pattern": "^(Ghostty[A-Za-z0-9_]+|bool|f32|f64|function|i8|i16|i32|i64|opaque|u8|u16|u32|u64|void)$" + "pattern": "^(Ghostty[A-Za-z0-9_]+|bool|f32|f64|function|[iu][1-9][0-9]*|opaque|void)$" + }, + "integerTypeReference": { + "type": "string", + "pattern": "^[iu][1-9][0-9]*$" }, "abi": { "type": "object", @@ -208,6 +212,129 @@ "$ref": "#/$defs/field" } }, + "bitBase": { + "type": "object", + "required": ["lsb", "width"], + "properties": { + "lsb": { + "description": "Least-significant bit relative to the containing numerical value.", + "$ref": "#/$defs/nonNegativeInteger" + }, + "width": { + "description": "Physical width of this field in bits.", + "$ref": "#/$defs/positiveInteger" + } + } + }, + "scalarBit": { + "allOf": [ + { + "$ref": "#/$defs/bitBase" + }, + { + "required": ["type"], + "properties": { + "type": { + "$ref": "#/$defs/typeReference" + } + } + } + ], + "unevaluatedProperties": false + }, + "packedBit": { + "allOf": [ + { + "$ref": "#/$defs/bitBase" + }, + { + "required": ["kind", "bits"], + "properties": { + "kind": { + "const": "packed" + }, + "bits": { + "$ref": "#/$defs/bits" + } + } + } + ], + "unevaluatedProperties": false + }, + "packedArm": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "width", "bits"], + "properties": { + "kind": { + "const": "packed" + }, + "width": { + "description": "Physical width of this inline arm in bits.", + "$ref": "#/$defs/positiveInteger" + }, + "bits": { + "$ref": "#/$defs/bits" + } + } + }, + "taggedUnionBit": { + "allOf": [ + { + "$ref": "#/$defs/bitBase" + }, + { + "required": ["kind", "tag", "arms"], + "properties": { + "kind": { + "const": "union" + }, + "tag": { + "type": "string", + "minLength": 1 + }, + "arms": { + "type": "object", + "minProperties": 1, + "propertyNames": { + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/packedArm" + }, + { + "type": "null" + } + ] + } + } + } + } + ], + "unevaluatedProperties": false + }, + "bit": { + "oneOf": [ + { + "$ref": "#/$defs/scalarBit" + }, + { + "$ref": "#/$defs/packedBit" + }, + { + "$ref": "#/$defs/taggedUnionBit" + } + ] + }, + "bits": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/bit" + } + }, "descriptorBase": { "type": "object", "required": ["kind", "size", "align"], @@ -291,6 +418,29 @@ ], "unevaluatedProperties": false }, + "packedDescriptor": { + "allOf": [ + { + "$ref": "#/$defs/descriptorBase" + }, + { + "required": ["underlying", "bits"], + "properties": { + "kind": { + "const": "packed" + }, + "underlying": { + "description": "Integer backing type used to interpret the packed value.", + "$ref": "#/$defs/integerTypeReference" + }, + "bits": { + "$ref": "#/$defs/bits" + } + } + } + ], + "unevaluatedProperties": false + }, "aliasDescriptor": { "allOf": [ { @@ -336,6 +486,9 @@ { "$ref": "#/$defs/enumDescriptor" }, + { + "$ref": "#/$defs/packedDescriptor" + }, { "$ref": "#/$defs/aliasDescriptor" }, diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig index 2242b1d50..578900a2b 100644 --- a/src/terminal/c/types.zig +++ b/src/terminal/c/types.zig @@ -16,6 +16,7 @@ const focus_pkg = @import("../focus.zig"); const formatter_pkg = @import("../formatter.zig"); const modes_pkg = @import("../modes.zig"); const mouse_pkg = @import("../mouse.zig"); +const page = @import("../page.zig"); const point = @import("../point.zig"); const Selection = @import("../Selection.zig"); const sgr = @import("../sgr.zig"); @@ -72,6 +73,7 @@ const TypeDecl = struct { @"struct", @"union", @"enum", + @"packed", alias, @"opaque", }; @@ -146,6 +148,10 @@ const TypeDecl = struct { }; } + fn initPacked(comptime name: []const u8, comptime T: type) TypeDecl { + return .{ .name = name, .T = T, .kind = .@"packed" }; + } + fn initAlias( comptime name: []const u8, comptime T: type, @@ -297,7 +303,7 @@ const type_decls = [_]TypeDecl{ .initEnum("GhosttyTerminalScrollViewportTag", terminal.ZigTerminal.ScrollViewport.Tag, "GHOSTTY_SCROLL_VIEWPORT_"), .initEnum("GhosttyTerminalUnknownSequenceTag", terminal.UnknownSequence.Tag, "GHOSTTY_TERMINAL_UNKNOWN_SEQUENCE_"), - .initAlias("GhosttyCell", u64, "u64"), + .initPacked("GhosttyCell", page.Cell.CLayout), .initAlias("GhosttyColorPaletteIndex", u8, "u8"), .initAlias("GhosttyKittyKeyFlags", u8, "u8"), .initAlias("GhosttyMode", u16, "u16"), @@ -438,6 +444,7 @@ const Json = struct { try jws.write(std.math.maxInt(c_int)); try jws.endObject(); }, + .@"packed" => try writePackedType(decl.T, jws), .alias => { try writeSizeAlign(decl.T, jws); try jws.objectField("type"); @@ -448,6 +455,98 @@ const Json = struct { try jws.endObject(); } + fn writePackedType( + comptime Layout: type, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + try writeSizeAlign(Layout.Zig, jws); + try jws.objectField("underlying"); + try jws.write(publicTypeName(Layout.Backing)); + try jws.objectField("bits"); + try jws.beginObject(); + try writePackedBits(Layout, jws); + try jws.endObject(); + } + + fn writePackedBits( + comptime Layout: type, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + inline for (@typeInfo(Layout.Zig).@"struct".fields) |field| { + const field_tag = @field(Layout.Field, field.name); + const name = comptime Layout.fieldName(field_tag) orelse continue; + const options = Layout.fieldOptions(field_tag); + + try jws.objectField(name); + try jws.beginObject(); + try jws.objectField("lsb"); + try jws.write(Layout.bitOffset(field_tag)); + try jws.objectField("width"); + try jws.write(Layout.bitWidth(field_tag)); + + switch (options.encoding) { + .scalar => { + try jws.objectField("type"); + try jws.write(options.type_name orelse publicTypeName(field.type)); + }, + .@"packed" => |Nested| { + try jws.objectField("kind"); + try jws.write("packed"); + try jws.objectField("bits"); + try jws.beginObject(); + try writePackedBits(Nested, jws); + try jws.endObject(); + }, + .tagged_union => |UnionLayout| try writePackedTaggedUnion(Layout, UnionLayout, jws), + } + try jws.endObject(); + } + } + + fn writePackedTaggedUnion( + comptime Layout: type, + comptime UnionLayout: type, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + try jws.objectField("kind"); + try jws.write("union"); + try jws.objectField("tag"); + try jws.write(Layout.fieldName(UnionLayout.tag_field).?); + try jws.objectField("arms"); + try jws.beginObject(); + + const tag_options = Layout.fieldOptions(UnionLayout.tag_field); + const tag_type_name = tag_options.type_name orelse publicTypeName(UnionLayout.Tag); + inline for (@typeInfo(UnionLayout.Tag).@"enum".fields) |tag| { + try writeEnumObjectField(tag_type_name, tag.name, jws); + const arm = UnionLayout.arm(@field(UnionLayout.Tag, tag.name)) orelse { + try jws.write(null); + continue; + }; + switch (arm) { + inline else => |ArmLayout| try writePackedArm(ArmLayout, jws), + } + } + + try jws.endObject(); + } + + fn writePackedArm( + comptime Layout: type, + jws: *std.json.Stringify, + ) std.Io.Writer.Error!void { + try jws.beginObject(); + try jws.objectField("kind"); + try jws.write("packed"); + try jws.objectField("width"); + try jws.write(@bitSizeOf(Layout.Zig)); + try jws.objectField("bits"); + try jws.beginObject(); + try writePackedBits(Layout, jws); + try jws.endObject(); + try jws.endObject(); + } + fn writeField( comptime decl: TypeDecl, comptime name: []const u8, @@ -653,7 +752,7 @@ const Json = struct { fn publicTypeName(comptime T: type) []const u8 { inline for (type_decls) |decl| switch (decl.kind) { .@"struct", .@"union", .@"enum" => if (T == decl.T) return decl.name, - .alias, .@"opaque" => {}, + .@"packed", .alias, .@"opaque" => {}, }; return switch (@typeInfo(T)) { .bool => "bool", @@ -681,14 +780,14 @@ const Json = struct { 16 => "i16", 32 => "i32", 64 => "i64", - else => "opaque", + else => std.fmt.comptimePrint("i{d}", .{bits}), }, .unsigned => switch (bits) { 8 => "u8", 16 => "u16", 32 => "u32", 64 => "u64", - else => "opaque", + else => std.fmt.comptimePrint("u{d}", .{bits}), }, }; } @@ -728,6 +827,12 @@ const Json = struct { for (builtins) |builtin_name| { if (std.mem.eql(u8, name, builtin_name)) return true; } + if (name.len > 1 and (name[0] == 'i' or name[0] == 'u') and + name[1] >= '1' and name[1] <= '9') + { + for (name[2..]) |c| if (!std.ascii.isDigit(c)) return false; + return true; + } return false; } }; @@ -779,6 +884,87 @@ test "manifest describes enums, arrays, and tagged unions" { try std.testing.expect(value.get("arms").?.object.get("NONE").? == .null); } +test "manifest describes the complete packed cell layout" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); + defer parsed.deinit(); + const manifest_types = parsed.value.object.get("types").?.object; + const descriptor = manifest_types.get("GhosttyCell").?.object; + + try std.testing.expectEqualStrings("packed", descriptor.get("kind").?.string); + try std.testing.expectEqual(@as(i64, @sizeOf(page.Cell.CLayout.Zig)), descriptor.get("size").?.integer); + try std.testing.expectEqualStrings("u64", descriptor.get("underlying").?.string); + + const bits = descriptor.get("bits").?.object; + inline for (@typeInfo(page.Cell.CLayout.Zig).@"struct".fields) |field| { + const field_tag = @field(page.Cell.CLayout.Field, field.name); + const name = comptime page.Cell.CLayout.fieldName(field_tag); + if (name) |public_name| { + const bit = bits.get(public_name).?.object; + try std.testing.expectEqual( + @as(i64, @intCast(@bitOffsetOf(page.Cell.CLayout.Zig, field.name))), + bit.get("lsb").?.integer, + ); + try std.testing.expectEqual( + @as(i64, @intCast(@bitSizeOf(field.type))), + bit.get("width").?.integer, + ); + } else { + try std.testing.expect(!bits.contains(field.name)); + } + } + + const content = bits.get("content").?.object; + try std.testing.expectEqualStrings("union", content.get("kind").?.string); + try std.testing.expectEqualStrings("content_tag", content.get("tag").?.string); + const arms = content.get("arms").?.object; + + const Content = @FieldType(page.Cell.CLayout.Zig, "content"); + const Codepoint = @FieldType(Content, "codepoint"); + const codepoint = arms.get("CODEPOINT").?.object; + const grapheme = arms.get("CODEPOINT_GRAPHEME").?.object; + try expectPackedArmField(codepoint, "codepoint", Codepoint, "data", "u21"); + try expectPackedArmField(grapheme, "codepoint", Codepoint, "data", "u21"); + try expectPackedArmField( + arms.get("BG_COLOR_PALETTE").?.object, + "index", + @FieldType(Content, "color_palette"), + "data", + "GhosttyColorPaletteIndex", + ); + + const rgb = arms.get("BG_COLOR_RGB").?.object; + const Rgb = @FieldType(Content, "color_rgb"); + inline for (@typeInfo(Rgb).@"struct".fields) |field| + try expectPackedArmField(rgb, field.name, Rgb, field.name, "u8"); +} + +test "manifest packed cell layouts decode real values" { + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); + defer parsed.deinit(); + const manifest_types = parsed.value.object.get("types").?.object; + const cell_bits = manifest_types.get("GhosttyCell").?.object.get("bits").?.object; + + var codepoint = page.Cell.CLayout.Zig.init('A'); + try expectDecodedCellContent(manifest_types, cell_bits, @bitCast(codepoint), "codepoint", 'A'); + + codepoint.content_tag = .codepoint_grapheme; + try expectDecodedCellContent(manifest_types, cell_bits, @bitCast(codepoint), "codepoint", 'A'); + + var palette: page.Cell.CLayout.Zig = @bitCast(@as(u64, 0)); + palette.content_tag = .bg_color_palette; + palette.content = .{ .color_palette = .{ .data = 173 } }; + try expectDecodedCellContent(manifest_types, cell_bits, @bitCast(palette), "index", 173); + + var rgb: page.Cell.CLayout.Zig = @bitCast(@as(u64, 0)); + rgb.content_tag = .bg_color_rgb; + rgb.content = .{ .color_rgb = .{ .r = 0x12, .g = 0x34, .b = 0x56 } }; + const rgb_arm = activeCellContentArm(manifest_types, cell_bits, @bitCast(rgb)); + const content = extractManifestBits(@bitCast(rgb), cell_bits.get("content").?.object); + try std.testing.expectEqual(@as(u64, 0x12), extractManifestBits(content, rgb_arm.get("bits").?.object.get("r").?.object)); + try std.testing.expectEqual(@as(u64, 0x34), extractManifestBits(content, rgb_arm.get("bits").?.object.get("g").?.object)); + try std.testing.expectEqual(@as(u64, 0x56), extractManifestBits(content, rgb_arm.get("bits").?.object.get("b").?.object)); +} + test "manifest uses public enum names" { const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{}); defer parsed.deinit(); @@ -827,6 +1013,13 @@ test "manifest named references resolve" { var type_iterator = manifest_types.iterator(); while (type_iterator.next()) |type_entry| { const descriptor = type_entry.value_ptr.object; + if (descriptor.get("bits")) |bits_value| { + try expectManifestBitsValid( + manifest_types, + bits_value.object, + @intCast(descriptor.get("size").?.integer * 8), + ); + } const fields_value = descriptor.get("fields") orelse continue; var field_iterator = fields_value.object.iterator(); while (field_iterator.next()) |field_entry| { @@ -857,3 +1050,102 @@ test "manifest named references resolve" { } } } + +fn expectPackedArmField( + arm: std.json.ObjectMap, + manifest_name: []const u8, + comptime T: type, + comptime zig_name: []const u8, + expected_type: []const u8, +) !void { + try std.testing.expectEqualStrings("packed", arm.get("kind").?.string); + try std.testing.expectEqual(@as(i64, @bitSizeOf(T)), arm.get("width").?.integer); + const bit = arm.get("bits").?.object.get(manifest_name).?.object; + try std.testing.expectEqual(@as(i64, @bitOffsetOf(T, zig_name)), bit.get("lsb").?.integer); + try std.testing.expectEqual(@as(i64, @bitSizeOf(@FieldType(T, zig_name))), bit.get("width").?.integer); + try std.testing.expectEqualStrings(expected_type, bit.get("type").?.string); +} + +fn extractManifestBits(value: u64, bit: std.json.ObjectMap) u64 { + const lsb: u6 = @intCast(bit.get("lsb").?.integer); + const width: u7 = @intCast(bit.get("width").?.integer); + const mask = if (width == 64) + std.math.maxInt(u64) + else + (@as(u64, 1) << @intCast(width)) - 1; + return (value >> lsb) & mask; +} + +fn activeCellContentArm( + manifest_types: std.json.ObjectMap, + cell_bits: std.json.ObjectMap, + raw: u64, +) std.json.ObjectMap { + const content_tag = cell_bits.get("content_tag").?.object; + const value = extractManifestBits(raw, content_tag); + const enum_values = manifest_types.get(content_tag.get("type").?.string).?.object + .get("values").?.object; + var iterator = enum_values.iterator(); + while (iterator.next()) |entry| { + if (entry.value_ptr.integer == value) + return cell_bits.get("content").?.object.get("arms").?.object + .get(entry.key_ptr.*).?.object; + } + unreachable; +} + +fn expectDecodedCellContent( + manifest_types: std.json.ObjectMap, + cell_bits: std.json.ObjectMap, + raw: u64, + field_name: []const u8, + expected: u64, +) !void { + const arm = activeCellContentArm(manifest_types, cell_bits, raw); + const content = extractManifestBits(raw, cell_bits.get("content").?.object); + try std.testing.expectEqual( + expected, + extractManifestBits(content, arm.get("bits").?.object.get(field_name).?.object), + ); +} + +fn expectManifestBitsValid( + manifest_types: std.json.ObjectMap, + bits: std.json.ObjectMap, + container_width: usize, +) !void { + var iterator = bits.iterator(); + while (iterator.next()) |entry| { + const bit = entry.value_ptr.object; + const lsb: usize = @intCast(bit.get("lsb").?.integer); + const width: usize = @intCast(bit.get("width").?.integer); + try std.testing.expect(lsb + width <= container_width); + + if (bit.get("type")) |type_value| { + const type_name = type_value.string; + try std.testing.expect(Json.isBuiltinType(type_name) or manifest_types.contains(type_name)); + continue; + } + + const kind = bit.get("kind").?.string; + if (std.mem.eql(u8, kind, "packed")) { + try expectManifestBitsValid(manifest_types, bit.get("bits").?.object, width); + continue; + } + + try std.testing.expectEqualStrings("union", kind); + const tag_name = bit.get("tag").?.string; + const tag = bits.get(tag_name).?.object; + const enum_values = manifest_types.get(tag.get("type").?.string).?.object + .get("values").?.object; + var arm_iterator = bit.get("arms").?.object.iterator(); + while (arm_iterator.next()) |arm_entry| { + try std.testing.expect(enum_values.contains(arm_entry.key_ptr.*)); + if (arm_entry.value_ptr.* == .null) continue; + const arm = arm_entry.value_ptr.object; + const arm_width: usize = @intCast(arm.get("width").?.integer); + try std.testing.expect(arm_width <= width); + try expectManifestBitsValid(manifest_types, arm.get("bits").?.object, arm_width); + } + } +} diff --git a/src/terminal/page.zig b/src/terminal/page.zig index c2814ae98..d19860697 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const build_options = @import("terminal_options"); +const lib = @import("../lib/main.zig"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const assert = @import("../quirks.zig").inlineAssert; @@ -2144,6 +2145,51 @@ pub const Cell = packed struct(u64) { prompt = 2, }; + /// Metadata for the C representation. All physical bit offsets and + /// widths are reflected from Cell. + pub const CLayout = lib.Packed(Cell, .{ .fields = .{ + .content_tag = .{ .type_name = "GhosttyCellContentTag" }, + .content = .{ .encoding = .{ .tagged_union = lib.PackedTaggedUnion( + Cell, + .content, + .content_tag, + .{ .arms = .{ + .codepoint = .{ .codepoint = lib.Packed( + @FieldType(@FieldType(Cell, "content"), "codepoint"), + .{ .fields = .{ + .data = .{ .name = "codepoint" }, + ._pad = .{ .omit = true }, + } }, + ) }, + .codepoint_grapheme = .{ .codepoint = lib.Packed( + @FieldType(@FieldType(Cell, "content"), "codepoint"), + .{ .fields = .{ + .data = .{ .name = "codepoint" }, + ._pad = .{ .omit = true }, + } }, + ) }, + .bg_color_palette = .{ .color_palette = lib.Packed( + @FieldType(@FieldType(Cell, "content"), "color_palette"), + .{ .fields = .{ + .data = .{ + .name = "index", + .type_name = "GhosttyColorPaletteIndex", + }, + ._pad = .{ .omit = true }, + } }, + ) }, + .bg_color_rgb = .{ .color_rgb = lib.Packed( + @FieldType(@FieldType(Cell, "content"), "color_rgb"), + .{}, + ) }, + } }, + ) } }, + .style_id = .{ .type_name = "GhosttyStyleId" }, + .wide = .{ .type_name = "GhosttyCellWide" }, + .semantic_content = .{ .type_name = "GhosttyCellSemanticContent" }, + ._padding = .{ .omit = true }, + } }); + /// The backing integer of this packed struct. Prefer this over /// hardcoding the integer type so that code is resilient to the /// size changing. From 0525c18f60ade456a484d9dc26e751d3c1b3993d Mon Sep 17 00:00:00 2001 From: Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com> Date: Sun, 16 Aug 2026 09:02:05 +0200 Subject: [PATCH 084/392] i18n: adjust and extend Ukrainian translation --- po/uk.po | 384 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 208 insertions(+), 176 deletions(-) diff --git a/po/uk.po b/po/uk.po index 12372e266..901a02104 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2026-08-10 10:06-0500\n" -"PO-Revision-Date: 2026-02-18 13:14+0100\n" +"PO-Revision-Date: 2026-08-16 09:02+0100\n" "Last-Translator: Volodymyr Chernetskyi " "<19735328+chernetskyi@users.noreply.github.com>\n" "Language-Team: Ukrainian \n" @@ -42,7 +42,7 @@ msgstr "Дозволити" #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92 msgid "Remember choice for this split" -msgstr "Запамʼятати для цієї панелі" +msgstr "Запамʼятати вибір для цієї панелі" #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 msgid "Reload configuration to show this prompt again" @@ -69,8 +69,8 @@ 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.2/config-errors-dialog.blp:12 msgid "Ignore" @@ -86,7 +86,7 @@ msgstr "Перезавантажити налаштування" msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ Ви використовуєте відладочну збірку Ghostty! Продуктивність буде погіршено." +"⚠️ Ви використовуєте збірку Ghostty для налагодження! Швидкодія буде нижчою." #: src/apprt/gtk/ui/1.5/inspector-window.blp:5 msgid "Ghostty: Terminal Inspector" @@ -118,12 +118,13 @@ msgid "" "through the content, but no input events will be sent to the running " "application." msgstr "" -"Цей термінал працює в режимі читання. Можна переглядати, виділяти і гортати " -"вміст, але ввід не буде передано до запущеної програми." +"Цей термінал працює в режимі «лише читання». Можна переглядати, виділяти і " +"прокручувати вміст, але події введення не передаватимуться до запущеної " +"програми." #: src/apprt/gtk/ui/1.2/surface.blp:112 msgid "Read-only" -msgstr "Тільки для читання" +msgstr "Лише читання" #: src/apprt/gtk/ui/1.2/surface.blp:316 src/apprt/gtk/ui/1.5/window.blp:203 msgid "Copy" @@ -175,7 +176,7 @@ msgstr "Нова панель праворуч" #: src/apprt/gtk/ui/1.2/surface.blp:377 msgid "Close Split" -msgstr "" +msgstr "Закрити панель" #: src/apprt/gtk/ui/1.2/surface.blp:383 msgid "Tab" @@ -203,7 +204,7 @@ msgstr "Вікно" #: src/apprt/gtk/ui/1.2/surface.blp:406 src/apprt/gtk/ui/1.5/window.blp:215 msgid "Change Window Title…" -msgstr "" +msgstr "Змінити заголовок вікна…" #: src/apprt/gtk/ui/1.2/surface.blp:411 src/apprt/gtk/ui/1.5/window.blp:220 #: src/input/command.zig:421 @@ -221,11 +222,11 @@ msgstr "Налаштування" #: src/apprt/gtk/ui/1.2/surface.blp:427 src/apprt/gtk/ui/1.5/window.blp:306 msgid "Open Configuration in OS Editor" -msgstr "" +msgstr "Відкрити налаштування в системному редакторі" #: src/apprt/gtk/ui/1.2/surface.blp:433 src/apprt/gtk/ui/1.5/window.blp:312 msgid "Open Configuration in New Window" -msgstr "" +msgstr "Відкрити налаштування в новому вікні" #: src/apprt/gtk/ui/1.5/title-dialog.blp:5 msgid "Leave blank to restore the default title." @@ -261,7 +262,7 @@ msgstr "Про Ghostty" #: src/apprt/gtk/ui/1.5/window.blp:331 src/input/command.zig:702 msgid "Quit" -msgstr "Завершити" +msgstr "Вийти" #: src/apprt/gtk/ui/1.5/command-palette.blp:17 msgid "Execute a command…" @@ -269,27 +270,27 @@ msgstr "Виконати команду…" #: src/apprt/gtk/class/application.zig:1767 msgid "The keybind was revoked by the system." -msgstr "" +msgstr "Привʼязка клавіш відкликана системою." #: src/apprt/gtk/class/application.zig:1769 msgid "The keybind was denied by the system." -msgstr "" +msgstr "Привʼязка клавіш відхилена системою." #: src/apprt/gtk/class/application.zig:1780 msgid "Global keybind unavailable" -msgstr "" +msgstr "Глобальна привʼязка клавіш недоступна" #: src/apprt/gtk/class/application.zig:2259 msgid "Export Terminal IO Events" -msgstr "" +msgstr "Експортувати події введення/виведення терміналу" #: src/apprt/gtk/class/application.zig:2262 msgid "Export" -msgstr "" +msgstr "Експортувати" #: src/apprt/gtk/class/application.zig:2783 msgid "Editing configuration file" -msgstr "" +msgstr "Редагування файлу налаштувань" #: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 msgid "" @@ -316,12 +317,12 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Вставка цього тексту в термінал може бути небезпечною, бо схоже, що деякі " -"команди можуть бути виконані." +"Вставка цього тексту в термінал може бути небезпечною, оскільки, схоже, " +"деякі команди можуть бути виконані." #: src/apprt/gtk/class/close_confirmation_dialog.zig:184 msgid "Quit Ghostty?" -msgstr "Завершити Ghostty?" +msgstr "Вийти з Ghostty?" #: src/apprt/gtk/class/close_confirmation_dialog.zig:185 msgid "Close Tab?" @@ -375,7 +376,7 @@ msgstr "Змінити заголовок вкладки" #: src/apprt/gtk/class/title_dialog.zig:229 msgid "Change Window Title" -msgstr "" +msgstr "Змінити заголовок вікна" #: src/apprt/gtk/class/window.zig:1153 msgid "Reloaded the configuration" @@ -383,7 +384,7 @@ msgstr "Налаштування перезавантажено" #: src/apprt/gtk/class/window.zig:1830 msgid "Copied to clipboard" -msgstr "Скопійовано до буферa обміну" +msgstr "Скопійовано до буфера обміну" #: src/apprt/gtk/class/window.zig:1832 msgid "Cleared clipboard" @@ -395,710 +396,741 @@ msgstr "Розробники Ghostty" #: src/input/command.zig:149 msgid "Reset Terminal" -msgstr "" +msgstr "Скинути термінал" #: src/input/command.zig:150 msgid "Reset the terminal to a clean state." -msgstr "" +msgstr "Скинути стан терміналу." #: src/input/command.zig:155 msgid "Copy to Clipboard" -msgstr "" +msgstr "Скопіювати до буфера обміну" #: src/input/command.zig:156 msgid "" "Copy the selected text to the clipboard in both plain and styled formats." msgstr "" +"Скопіювати виділений текст до буфера обміну в звичайному і форматованому " +"виглядах одночасно." #: src/input/command.zig:159 msgid "Copy Selection as Plain Text to Clipboard" -msgstr "" +msgstr "Скопіювати виділене до буфера обміну як звичайний текст" #: src/input/command.zig:160 msgid "Copy the selected text as plain text to the clipboard." -msgstr "" +msgstr "Скопіювати виділений текст до буфера обміну як звичайний текст." #: src/input/command.zig:163 msgid "Copy Selection as ANSI Sequences to Clipboard" -msgstr "" +msgstr "Скопіювати виділене до буфера обміну як ANSI-послідовності" #: src/input/command.zig:164 msgid "Copy the selected text as ANSI escape sequences to the clipboard." -msgstr "" +msgstr "Скопіювати виділений текст до буфера обміну як ANSI-послідовності." #: src/input/command.zig:167 msgid "Copy Selection as HTML to Clipboard" -msgstr "" +msgstr "Скопіювати виділене до буфера обміну у форматі HTML" #: src/input/command.zig:168 msgid "Copy the selected text as HTML to the clipboard." -msgstr "" +msgstr "Скопіювати виділений текст до буфера обміну у форматі HTML." #: src/input/command.zig:173 msgid "Copy URL to Clipboard" -msgstr "" +msgstr "Скопіювати URL до буфера обміну" #: src/input/command.zig:174 msgid "Copy the URL under the cursor to the clipboard." -msgstr "" +msgstr "Скопіювати URL під вказівником до буфера обміну." #: src/input/command.zig:179 msgid "Copy Terminal Title to Clipboard" -msgstr "" +msgstr "Скопіювати заголовок терміналу до буфера обміну" #: src/input/command.zig:180 msgid "" "Copy the terminal title to the clipboard. If the terminal title is not set " "this has no effect." msgstr "" +"Скопіювати заголовок терміналу до буфера обміну. Якщо заголовку немає, то " +"нічого не відбудеться." #: src/input/command.zig:185 msgid "Paste from Clipboard" -msgstr "" +msgstr "Вставити з буфера обміну" #: src/input/command.zig:186 msgid "Paste the contents of the main clipboard." -msgstr "" +msgstr "Вставити вміст головного буфера обміну." #: src/input/command.zig:191 msgid "Paste from Selection" -msgstr "" +msgstr "Вставити виділене" #: src/input/command.zig:192 msgid "Paste the contents of the selection clipboard." -msgstr "" +msgstr "Вставити вміст буфера виділення." #: src/input/command.zig:197 msgid "Start Search" -msgstr "" +msgstr "Шукати" #: src/input/command.zig:198 msgid "Start a search if one isn't already active." -msgstr "" +msgstr "Почати пошук, якщо активного пошуку ще немає." #: src/input/command.zig:203 msgid "Search Selection" -msgstr "" +msgstr "Шукати виділене" #: src/input/command.zig:204 msgid "Start a search for the current text selection." -msgstr "" +msgstr "Почати пошук виділеного тексту." #: src/input/command.zig:209 msgid "End Search" -msgstr "" +msgstr "Завершити пошук" #: src/input/command.zig:210 msgid "End the current search if any and hide any GUI elements." -msgstr "" +msgstr "Завершити поточний пошук і сховати елементи інтерфейсу." #: src/input/command.zig:215 msgid "Next Search Result" -msgstr "" +msgstr "Наступна знахідка" #: src/input/command.zig:216 msgid "Navigate to the next search result, if any." -msgstr "" +msgstr "Перейти до наступної знахідки, якщо така є." #: src/input/command.zig:219 msgid "Previous Search Result" -msgstr "" +msgstr "Попередня знахідка" #: src/input/command.zig:220 msgid "Navigate to the previous search result, if any." -msgstr "" +msgstr "Перейти до попередньої знахідки, якщо така є." #: src/input/command.zig:225 msgid "Increase Font Size" -msgstr "" +msgstr "Збільшити розмір шрифту" #: src/input/command.zig:226 msgid "Increase the font size by 1 point." -msgstr "" +msgstr "Збільшити розмір шрифту на 1 пункт." #: src/input/command.zig:231 msgid "Decrease Font Size" -msgstr "" +msgstr "Зменшити розмір шрифту" #: src/input/command.zig:232 msgid "Decrease the font size by 1 point." -msgstr "" +msgstr "Зменшити розмір шрифту на 1 пункт." #: src/input/command.zig:237 msgid "Reset Font Size" -msgstr "" +msgstr "Скинути розмір шрифту" #: src/input/command.zig:238 msgid "Reset the font size to the default." -msgstr "" +msgstr "Скинути розмір шрифту до значення за замовчуванням." #: src/input/command.zig:243 msgid "Clear Screen" -msgstr "" +msgstr "Очистити екран" #: src/input/command.zig:244 msgid "Clear the screen and scrollback." -msgstr "" +msgstr "Очистити екран і буфер прокручування." #: src/input/command.zig:249 msgid "Select All" -msgstr "" +msgstr "Вибрати все" #: src/input/command.zig:250 msgid "Select all text on the screen." -msgstr "" +msgstr "Вибрати весь текст на екрані." #: src/input/command.zig:255 msgid "Scroll to Top" -msgstr "" +msgstr "Прокрутити до початку" #: src/input/command.zig:256 msgid "Scroll to the top of the screen." -msgstr "" +msgstr "Прокрутити до початку екрана." #: src/input/command.zig:261 msgid "Scroll to Bottom" -msgstr "" +msgstr "Прокрутити до кінця" #: src/input/command.zig:262 msgid "Scroll to the bottom of the screen." -msgstr "" +msgstr "Прокрутити до кінця екрана." #: src/input/command.zig:267 msgid "Scroll to Selection" -msgstr "" +msgstr "Прокрутити до виділеного" #: src/input/command.zig:268 msgid "Scroll to the selected text." -msgstr "" +msgstr "Прокрутити до виділеного тексту." #: src/input/command.zig:273 msgid "Scroll Page Up" -msgstr "" +msgstr "Прокрутити сторінку догори" #: src/input/command.zig:274 msgid "Scroll the screen up by a page." -msgstr "" +msgstr "Прокрутити екран на сторінку догори." #: src/input/command.zig:279 msgid "Scroll Page Down" -msgstr "" +msgstr "Прокрутити сторінку донизу" #: src/input/command.zig:280 msgid "Scroll the screen down by a page." -msgstr "" +msgstr "Прокрутити екран на сторінку донизу." #: src/input/command.zig:286 msgid "Copy Screen to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти екран до файлу і скопіювати шлях" #: src/input/command.zig:287 msgid "" "Copy the screen contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Зберегти вміст екрана до тимчасового файлу і скопіювати шлях до файлу до " +"буфера обміну." #: src/input/command.zig:291 msgid "Copy Screen to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти екран до файлу і вставити шлях" #: src/input/command.zig:292 msgid "" "Copy the screen contents to a temporary file and paste the path to the file." msgstr "" +"Зберегти вміст екрана до тимчасового файлу і вставити шлях до файлу." #: src/input/command.zig:296 msgid "Copy Screen to Temporary File and Open" -msgstr "" +msgstr "Зберегти екран до файлу і відкрити файл" #: src/input/command.zig:297 msgid "Copy the screen contents to a temporary file and open it." -msgstr "" +msgstr "Зберегти вміст екрана до тимчасового файлу і відкрити файл." #: src/input/command.zig:305 msgid "Copy Screen as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти екран у форматі HTML до файлу і скопіювати шлях" #: src/input/command.zig:306 msgid "" "Copy the screen contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Зберегти вміст екрана у форматі HTML до тимчасового файлу і скопіювати шлях " +"до файлу до буфера обміну." #: src/input/command.zig:313 msgid "Copy Screen as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти екран у форматі HTML до файлу і вставити шлях" #: src/input/command.zig:314 msgid "" "Copy the screen contents as HTML to a temporary file and paste the path to " "the file." msgstr "" +"Зберегти вміст екрана у форматі HTML до тимчасового файлу і вставити шлях до " +"файлу." #: src/input/command.zig:321 msgid "Copy Screen as HTML to Temporary File and Open" -msgstr "" +msgstr "Зберегти екран у форматі HTML до файлу і відкрити файл" #: src/input/command.zig:322 msgid "Copy the screen contents as HTML to a temporary file and open it." msgstr "" +"Зберегти вміст екрана у форматі HTML до тимчасового файлу і відкрити файл." #: src/input/command.zig:330 msgid "Copy Screen as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти екран як ANSI-послідовності до файлу і скопіювати шлях" #: src/input/command.zig:331 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Зберегти вміст екрана як ANSI-послідовності до тимчасового файлу і " +"скопіювати шлях до файлу до буфера обміну." #: src/input/command.zig:338 msgid "Copy Screen as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти екран як ANSI-послідовності до файлу і вставити шлях" #: src/input/command.zig:339 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Зберегти вміст екрана як ANSI-послідовності до тимчасового файлу і вставити " +"шлях до файлу." #: src/input/command.zig:346 msgid "Copy Screen as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Зберегти екран як ANSI-послідовності до файлу і відкрити файл" #: src/input/command.zig:347 msgid "" "Copy the screen contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Зберегти вміст екрана як ANSI-послідовності до тимчасового файлу і відкрити " +"файл." #: src/input/command.zig:354 msgid "Copy Selection to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти виділене до файлу і скопіювати шлях" #: src/input/command.zig:355 msgid "" "Copy the selection contents to a temporary file and copy the path to the " "clipboard." msgstr "" +"Зберегти виділене до тимчасового файлу і скопіювати шлях до файлу до буфера " +"обміну." #: src/input/command.zig:359 msgid "Copy Selection to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти виділене до файлу і вставити шлях" #: src/input/command.zig:360 msgid "" "Copy the selection contents to a temporary file and paste the path to the " "file." -msgstr "" +msgstr "Зберегти виділене до тимчасового файлу і вставити шлях до файлу." #: src/input/command.zig:364 msgid "Copy Selection to Temporary File and Open" -msgstr "" +msgstr "Зберегти виділене до файлу і відкрити файл" #: src/input/command.zig:365 msgid "Copy the selection contents to a temporary file and open it." -msgstr "" +msgstr "Зберегти виділене до тимчасового файлу і відкрити файл." #: src/input/command.zig:373 msgid "Copy Selection as HTML to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти виділене у форматі HTML до файлу і скопіювати шлях" #: src/input/command.zig:374 msgid "" "Copy the selection contents as HTML to a temporary file and copy the path to " "the clipboard." msgstr "" +"Зберегти виділене у форматі HTML до тимчасового файлу і скопіювати шлях до " +"файлу до буфера обміну." #: src/input/command.zig:381 msgid "Copy Selection as HTML to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти виділене у форматі HTML до файлу і вставити шлях" #: src/input/command.zig:382 msgid "" "Copy the selection contents as HTML to a temporary file and paste the path " "to the file." msgstr "" +"Зберегти виділене у форматі HTML до тимчасового файлу і вставити шлях до " +"файлу." #: src/input/command.zig:389 msgid "Copy Selection as HTML to Temporary File and Open" -msgstr "" +msgstr "Зберегти виділене у форматі HTML до файлу і відкрити файл" #: src/input/command.zig:390 msgid "Copy the selection contents as HTML to a temporary file and open it." -msgstr "" +msgstr "Зберегти виділене у форматі HTML до тимчасового файлу і відкрити файл." #: src/input/command.zig:398 msgid "Copy Selection as ANSI Sequences to Temporary File and Copy Path" -msgstr "" +msgstr "Зберегти виділене як ANSI-послідовності до файлу і скопіювати шлях" #: src/input/command.zig:399 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "copy the path to the clipboard." msgstr "" +"Зберегти виділене як ANSI-послідовності до тимчасового файлу і скопіювати " +"шлях до файлу до буфера обміну." #: src/input/command.zig:406 msgid "Copy Selection as ANSI Sequences to Temporary File and Paste Path" -msgstr "" +msgstr "Зберегти виділене як ANSI-послідовності до файлу і вставити шлях" #: src/input/command.zig:407 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "paste the path to the file." msgstr "" +"Зберегти виділене як ANSI-послідовності до тимчасового файлу і вставити шлях " +"до файлу." #: src/input/command.zig:414 msgid "Copy Selection as ANSI Sequences to Temporary File and Open" -msgstr "" +msgstr "Зберегти виділене як ANSI-послідовності до файлу і відкрити файл" #: src/input/command.zig:415 msgid "" "Copy the selection contents as ANSI escape sequences to a temporary file and " "open it." msgstr "" +"Зберегти виділене як ANSI-послідовності до тимчасового файлу і відкрити файл." #: src/input/command.zig:422 msgid "Open a new window." -msgstr "" +msgstr "Створити нове вікно." #: src/input/command.zig:428 msgid "Open a new tab." -msgstr "" +msgstr "Створити нову вкладку." #: src/input/command.zig:434 msgid "Move Tab Left" -msgstr "" +msgstr "Пересунути вкладку ліворуч" #: src/input/command.zig:435 msgid "Move the current tab to the left." -msgstr "" +msgstr "Пересунути поточну вкладку ліворуч." #: src/input/command.zig:439 msgid "Move Tab Right" -msgstr "" +msgstr "Пересунути вкладку праворуч" #: src/input/command.zig:440 msgid "Move the current tab to the right." -msgstr "" +msgstr "Пересунути поточну вкладку праворуч." #: src/input/command.zig:446 msgid "Move Tab to New Window" -msgstr "" +msgstr "Винести вкладку до нового вікна" #: src/input/command.zig:447 msgid "Move the current tab to a new window." -msgstr "" +msgstr "Винести поточну вкладку до нового вікна." #: src/input/command.zig:452 msgid "Toggle Tab Overview" -msgstr "" +msgstr "Перемкнути огляд вкладок" #: src/input/command.zig:453 msgid "Toggle the tab overview." -msgstr "" +msgstr "Перемкнути огляд вкладок." #: src/input/command.zig:458 msgid "Change Terminal Title…" -msgstr "" +msgstr "Змінити заголовок терміналу…" #: src/input/command.zig:459 msgid "Prompt for a new title for the current terminal." -msgstr "" +msgstr "Спитати новий заголовок для поточного терміналу." #: src/input/command.zig:465 msgid "Prompt for a new title for the current tab." -msgstr "" +msgstr "Спитати новий заголовок для поточної вкладки." #: src/input/command.zig:478 msgid "Split the terminal to the left." -msgstr "" +msgstr "Створити нову панель ліворуч." #: src/input/command.zig:483 msgid "Split the terminal to the right." -msgstr "" +msgstr "Створити нову панель праворуч." #: src/input/command.zig:488 msgid "Split the terminal up." -msgstr "" +msgstr "Створити нову панель зверху." #: src/input/command.zig:493 msgid "Split the terminal down." -msgstr "" +msgstr "Створити нову панель знизу." #: src/input/command.zig:500 msgid "Focus Split: Previous" -msgstr "" +msgstr "Перейти до попередньої панелі" #: src/input/command.zig:501 msgid "Focus the previous split, if any." -msgstr "" +msgstr "Перейти до попередньої панелі, якщо така є." #: src/input/command.zig:505 msgid "Focus Split: Next" -msgstr "" +msgstr "Перейти до наступної панелі" #: src/input/command.zig:506 msgid "Focus the next split, if any." -msgstr "" +msgstr "Перейти до наступної панелі, якщо така є." #: src/input/command.zig:510 msgid "Focus Split: Left" -msgstr "" +msgstr "Перейти до панелі ліворуч" #: src/input/command.zig:511 msgid "Focus the split to the left, if it exists." -msgstr "" +msgstr "Перейти до панелі ліворуч, якщо така є." #: src/input/command.zig:515 msgid "Focus Split: Right" -msgstr "" +msgstr "Перейти до панелі праворуч" #: src/input/command.zig:516 msgid "Focus the split to the right, if it exists." -msgstr "" +msgstr "Перейти до панелі праворуч, якщо така є." #: src/input/command.zig:520 msgid "Focus Split: Up" -msgstr "" +msgstr "Перейти до панелі зверху" #: src/input/command.zig:521 msgid "Focus the split above, if it exists." -msgstr "" +msgstr "Перейти до панелі зверху, якщо така є." #: src/input/command.zig:525 msgid "Focus Split: Down" -msgstr "" +msgstr "Перейти до панелі знизу" #: src/input/command.zig:526 msgid "Focus the split below, if it exists." -msgstr "" +msgstr "Перейти до панелі знизу, якщо така є." #: src/input/command.zig:533 msgid "Focus Window: Previous" -msgstr "" +msgstr "Перейти до попереднього вікна" #: src/input/command.zig:534 msgid "Focus the previous window, if any." -msgstr "" +msgstr "Перейти до попереднього вікна, якщо таке є." #: src/input/command.zig:538 msgid "Focus Window: Next" -msgstr "" +msgstr "Перейти до наступного вікна" #: src/input/command.zig:539 msgid "Focus the next window, if any." -msgstr "" +msgstr "Перейти до наступного вікна, якщо таке є." #: src/input/command.zig:545 msgid "Toggle Split Zoom" -msgstr "" +msgstr "Перемкнути масштабування панелі" #: src/input/command.zig:546 msgid "Toggle the zoom state of the current split." -msgstr "" +msgstr "Перемкнути режим масштабування поточної панелі." #: src/input/command.zig:551 msgid "Toggle Read-Only Mode" -msgstr "" +msgstr "Перемкнути режим «лише читання»" #: src/input/command.zig:552 msgid "Toggle read-only mode for the current surface." -msgstr "" +msgstr "Перемкнути режим «лише читання» для поточної панелі." #: src/input/command.zig:557 msgid "Equalize Splits" -msgstr "" +msgstr "Вирівняти панелі" #: src/input/command.zig:558 msgid "Equalize the size of all splits." -msgstr "" +msgstr "Вирівняти розміри всіх панелей." #: src/input/command.zig:563 msgid "Reset Window Size" -msgstr "" +msgstr "Скинути розмір вікна" #: src/input/command.zig:564 msgid "Reset the window size to the default." -msgstr "" +msgstr "Скинути розмір вікна до значення за замовчуванням." #: src/input/command.zig:569 msgid "Toggle Inspector" -msgstr "" +msgstr "Перемкнути термінальний інспектор" #: src/input/command.zig:570 msgid "Toggle the inspector." -msgstr "" +msgstr "Перемкнути термінальний інспектор." #: src/input/command.zig:575 msgid "Show the GTK Inspector" -msgstr "" +msgstr "Показати інспектор GTK" #: src/input/command.zig:576 msgid "Show the GTK inspector." -msgstr "" +msgstr "Показати інспектор GTK." #: src/input/command.zig:581 msgid "Show On-Screen Keyboard" -msgstr "" +msgstr "Показати екранну клавіатуру" #: src/input/command.zig:582 msgid "Show the on-screen keyboard if present." -msgstr "" +msgstr "Показати екранну клавіатуру, якщо така є." #: src/input/command.zig:588 msgid "Open Config Using OS editor" -msgstr "" +msgstr "Відкрити налаштування в системному редакторі" #: src/input/command.zig:589 msgid "Open the config file with the OS's default editor." -msgstr "" +msgstr "Відкрити файл налаштувань у системному редакторі за замовчуванням." #: src/input/command.zig:593 msgid "Open Config in New Terminal Window" -msgstr "" +msgstr "Відкрити налаштування в новому вікні" #: src/input/command.zig:594 msgid "Open the config file in a new window using $EDITOR or $VISUAL." msgstr "" +"Відкрити файл налаштувань в новому вікні за допомогою $EDITOR або $VISUAL." #: src/input/command.zig:600 msgid "Reload Config" -msgstr "" +msgstr "Перезавантажити налаштування" #: src/input/command.zig:601 msgid "Reload the config file." -msgstr "" +msgstr "Перезавантажити файл налаштувань." #: src/input/command.zig:606 msgid "Close Terminal" -msgstr "" +msgstr "Закрити термінал" #: src/input/command.zig:607 msgid "Close the current terminal." -msgstr "" +msgstr "Закрити поточний термінал." #: src/input/command.zig:614 msgid "Close the current tab." -msgstr "" +msgstr "Закрити поточну вкладку." #: src/input/command.zig:618 msgid "Close Other Tabs" -msgstr "" +msgstr "Закрити інші вкладки" #: src/input/command.zig:619 msgid "Close all tabs in this window except the current one." -msgstr "" +msgstr "Закрити всі вкладки в цьому вікні, окрім поточної." #: src/input/command.zig:623 msgid "Close Tabs to the Right" -msgstr "" +msgstr "Закрити вкладки праворуч" #: src/input/command.zig:624 msgid "Close all tabs to the right of the current one." -msgstr "" +msgstr "Закрити всі вкладки праворуч поточної." #: src/input/command.zig:631 msgid "Close the current window." -msgstr "" +msgstr "Закрити поточне вікно." #: src/input/command.zig:636 msgid "Close All Windows" -msgstr "" +msgstr "Закрити всі вікна" #: src/input/command.zig:637 msgid "Close all windows." -msgstr "" +msgstr "Закрити всі вікна." #: src/input/command.zig:642 msgid "Toggle Maximize" -msgstr "" +msgstr "Перемкнути розгортання" #: src/input/command.zig:643 msgid "Toggle the maximized state of the current window." -msgstr "" +msgstr "Перемкнути розгортання поточного вікна." #: src/input/command.zig:648 msgid "Toggle Fullscreen" -msgstr "" +msgstr "Перемкнути повноекранний режим" #: src/input/command.zig:649 msgid "Toggle the fullscreen state of the current window." -msgstr "" +msgstr "Перемкнути повноекранний режим поточного вікна." #: src/input/command.zig:654 msgid "Toggle Window Decorations" -msgstr "" +msgstr "Перемкнути оформлення вікон" #: src/input/command.zig:655 msgid "Toggle the window decorations." -msgstr "" +msgstr "Перемкнути оформлення вікон." #: src/input/command.zig:660 msgid "Toggle Float on Top" -msgstr "" +msgstr "Перемкнути режим «поверх інших вікон»" #: src/input/command.zig:661 msgid "Toggle the float on top state of the current window." -msgstr "" +msgstr "Перемкнути режим «поверх інших вікон» для поточного вікна." #: src/input/command.zig:666 msgid "Toggle Secure Input" -msgstr "" +msgstr "Перемкнути безпечне введення" #: src/input/command.zig:667 msgid "Toggle secure input mode." -msgstr "" +msgstr "Перемкнути режим безпечного введення." #: src/input/command.zig:672 msgid "Toggle Mouse Reporting" -msgstr "" +msgstr "Перемкнути передавання подій миші" #: src/input/command.zig:673 msgid "Toggle whether mouse events are reported to terminal applications." -msgstr "" +msgstr "Перемкнути передавання подій миші до термінальних застосунків." #: src/input/command.zig:678 msgid "Toggle Background Opacity" -msgstr "" +msgstr "Перемкнути непрозорість фону" #: src/input/command.zig:679 msgid "Toggle the background opacity of a window that started transparent." msgstr "" +"Перемкнути непрозорість фону вікна, фон якого спочатку був прозорим." #: src/input/command.zig:684 msgid "Check for Updates" -msgstr "" +msgstr "Перевірити наявність оновлень" #: src/input/command.zig:685 msgid "Check for updates to the application." -msgstr "" +msgstr "Перевірити наявність оновлень застосунку." #: src/input/command.zig:690 msgid "Undo" -msgstr "" +msgstr "Скасувати" #: src/input/command.zig:691 msgid "Undo the last action." -msgstr "" +msgstr "Скасувати останню дію." #: src/input/command.zig:696 msgid "Redo" -msgstr "" +msgstr "Повторити" #: src/input/command.zig:697 msgid "Redo the last undone action." -msgstr "" +msgstr "Повторити останню скасовану дію." #: src/input/command.zig:703 msgid "Quit the application." -msgstr "" +msgstr "Вийти із застосунку." #: src/input/command.zig:708 msgid "Ghostty" -msgstr "" +msgstr "Привиденя" #: src/input/command.zig:709 msgid "Put a little Ghostty in your terminal." -msgstr "" +msgstr "Вставити маленького привида у ваш термінал." From 37174c73b0600cc3b2d8579fa7a210086ee3bbed Mon Sep 17 00:00:00 2001 From: phl Date: Sun, 16 Aug 2026 09:50:35 +0200 Subject: [PATCH 085/392] add helper and expand also if light/dark is specified --- src/config/Config.zig | 121 ++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index bb4e7a38d..0e1f44b30 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4518,6 +4518,29 @@ fn expandPaths(self: *Config, base: []const u8) !void { } } +/// Expand tilde paths to absolute paths to the user's home directory. +/// If expansion fails, an error is logged and the original path is returned. +fn expandHome(path: []const u8, buf: []u8) []const u8 { + if (!std.mem.startsWith(u8, path, "~/")) + return path; + + var environ_map = global.environMap() catch |err| { + log.warn("failed to get environment map for path \"{s}\": {}", .{ path, err }); + return path; + }; + defer environ_map.deinit(); + + return internal_os.expandHome( + global.io(), + &environ_map, + path, + buf, + ) catch |err| { + log.warn("failed to expand home directory in path \"{s}\": {}", .{ path, err }); + return path; + }; +} + fn loadTheme(self: *Config, theme: Theme) !void { // Load the correct theme depending on the conditional state. // Dark/light themes were programmed prior to conditional configuration @@ -4617,14 +4640,17 @@ fn loadTheme(self: *Config, theme: Theme) !void { /// Call this once after you are done setting configuration. This /// is idempotent but will waste memory if called multiple times. pub fn finalize(self: *Config) !void { + const alloc = self._arena.?.allocator(); + // We always load the theme first because it may set other fields // in our config. - if (self.theme) |theme| { + if (self.theme) |*theme| { + try theme.finalize(alloc); const different = !std.mem.eql(u8, theme.light, theme.dark); // Warning: loadTheme will deinit our existing config and replace // it so all memory from self prior to this point will be freed. - try self.loadTheme(theme); + try self.loadTheme(theme.*); // If we have different light vs dark mode themes, disable // window-theme = auto since that breaks it. @@ -4638,8 +4664,6 @@ pub fn finalize(self: *Config) !void { } } - const alloc = self._arena.?.allocator(); - // Used for a variety of defaults. See the function docs as well the // specific variable use sites for more details. const probable_cli = probableCliEnvironment(); @@ -5444,20 +5468,8 @@ pub const WorkingDirectory = union(enum) { else => return, }; - if (!std.mem.startsWith(u8, path, "~/")) return; - var buf: [std.fs.max_path_bytes]u8 = undefined; - const expanded = expanded: { - var environ_map = global.environMap() catch |err| break :expanded err; - defer environ_map.deinit(); - break :expanded internal_os.expandHome(global.io(), &environ_map, path, &buf); - } catch |err| { - log.warn( - "error expanding home directory for working-directory path={s}: {}", - .{ path, err }, - ); - return; - }; + const expanded = expandHome(path, &buf); if (std.mem.eql(u8, expanded, path)) return; self.* = .{ .path = try alloc.dupe(u8, expanded) }; @@ -9973,36 +9985,26 @@ pub const Theme = struct { // Trim our value const trimmed = std.mem.trim(u8, input, cli.args.whitespace); - // Expand our value - var buf: [std.fs.max_path_bytes]u8 = undefined; - const expanded = expanded: { - if (!std.mem.startsWith(u8, trimmed, "~/")) - break :expanded trimmed; - - var environ_map = global.environMap() catch |err| { - log.warn("error getting environment map for theme path {s}: {}", .{ trimmed, err }); - break :expanded trimmed; - }; - defer environ_map.deinit(); - - break :expanded internal_os.expandHome( - global.io(), - &environ_map, - trimmed, - &buf, - ) catch |err| { - log.warn("error expanding home directory in theme path {s}: {}", .{ trimmed, err }); - break :expanded trimmed; - }; - }; - // Set the value to the specified value directly. self.* = .{ - .light = try alloc.dupeZ(u8, expanded), + .light = try alloc.dupeZ(u8, trimmed), .dark = self.light, }; } + /// Expand tilde paths in light/dark theme values. + pub fn finalize(self: *Theme, alloc: Allocator) Allocator.Error!void { + var buf: [std.fs.max_path_bytes]u8 = undefined; + + const light = expandHome(self.light, &buf); + if (!std.mem.eql(u8, light, self.light)) + self.light = try alloc.dupeZ(u8, light); + + const dark = expandHome(self.dark, &buf); + if (!std.mem.eql(u8, dark, self.dark)) + self.dark = try alloc.dupeZ(u8, dark); + } + /// Deep copy of the struct. Required by Config. pub fn clone(self: *const Theme, alloc: Allocator) Allocator.Error!Theme { return .{ @@ -10051,7 +10053,15 @@ pub const Theme = struct { try testing.expectEqualStrings("foo", v.dark); } - // Expand home + // Light/dark + { + var v: Theme = undefined; + try v.parseCLI(alloc, " light:foo, dark : bar "); + try testing.expectEqualStrings("foo", v.light); + try testing.expectEqualStrings("bar", v.dark); + } + + // Expand tilde to home { var environ_map = try testing.environ.createMap(alloc); defer environ_map.deinit(); @@ -10060,30 +10070,23 @@ pub const Theme = struct { const home = try internal_os.expandHome( testing.io, &environ_map, - "~/theme", + "~/", &home_buf, ); var v: Theme = undefined; - try v.parseCLI(alloc, "~/theme/foo"); + try v.parseCLI(alloc, "light:~/foo, dark:~/bar"); + try v.finalize(alloc); var expected_buf: [std.fs.max_path_bytes]u8 = undefined; - const expected = try std.fmt.bufPrint( - &expected_buf, - "{s}/theme/foo", - .{home}, + try testing.expectEqualStrings( + try std.fmt.bufPrint(&expected_buf, "{s}foo", .{home}), + v.light, + ); + try testing.expectEqualStrings( + try std.fmt.bufPrint(&expected_buf, "{s}bar", .{home}), + v.dark, ); - - try testing.expectEqualStrings(expected, v.light); - try testing.expectEqualStrings(expected, v.dark); - } - - // Light/dark - { - var v: Theme = undefined; - try v.parseCLI(alloc, " light:foo, dark : bar "); - try testing.expectEqualStrings("foo", v.light); - try testing.expectEqualStrings("bar", v.dark); } var v: Theme = undefined; From 68a2008b340959b2a94f27f37ca98257109f6396 Mon Sep 17 00:00:00 2001 From: phl Date: Sun, 16 Aug 2026 20:16:31 +0200 Subject: [PATCH 086/392] fix: format --- src/config/Config.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 0e1f44b30..ac854616c 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -9995,11 +9995,11 @@ pub const Theme = struct { /// Expand tilde paths in light/dark theme values. pub fn finalize(self: *Theme, alloc: Allocator) Allocator.Error!void { var buf: [std.fs.max_path_bytes]u8 = undefined; - + const light = expandHome(self.light, &buf); if (!std.mem.eql(u8, light, self.light)) self.light = try alloc.dupeZ(u8, light); - + const dark = expandHome(self.dark, &buf); if (!std.mem.eql(u8, dark, self.dark)) self.dark = try alloc.dupeZ(u8, dark); From bd647035e97da4aadfe1003877ecf64a3a655059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sacha=20Tr=C3=A9moureux?= Date: Sat, 15 Aug 2026 12:48:37 +0200 Subject: [PATCH 087/392] input: don't emit fallback text on key release Key events without a kitty entry fall back to writing their UTF-8 text directly. On GTK, keys whose unshifted keysym is a dead key or level 5 latch have no unshifted codepoint and take this path. With event type reporting enabled, releases therefore emitted the same text as presses and duplicated characters in applications such as Neovim. Skip the raw text fallback for release events while retaining it for presses and repeats. Keep the guard in the shared encoder so release events for identified keys still retain the UTF-8 data used to derive alternate keys. Cover releases with and without report-all mode, and verify that repeat events continue to emit fallback text. --- src/input/key_encode.zig | 52 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/input/key_encode.zig b/src/input/key_encode.zig index 6ab5a4cc8..fd388452d 100644 --- a/src/input/key_encode.zig +++ b/src/input/key_encode.zig @@ -224,9 +224,11 @@ fn kitty( const entry = entry_ orelse { // No entry found. If we have UTF-8 text this is a pure text event // (e.g. composed/IME text), so send it as-is so programs can - // still receive it. - if (event.utf8.len > 0) return try writer.writeAll(event.utf8); - return; + // still receive it. Release events never insert text, same as the + // plain-text path above. + if (event.action == .release) return; + if (event.utf8.len == 0) return; + return try writer.writeAll(event.utf8); }; // If this is just a modifier we require "report all" to send the sequence. @@ -1492,6 +1494,50 @@ test "kitty: composed text with report all" { try testing.expectEqualStrings("\xc3\xbb", writer.buffered()); } +// A key whose base (unshifted) keysym is a dead key has no unshifted +// codepoint, so it has no kitty entry and falls back to writing the text +// directly. That must not happen on release or the text is inserted twice. +test "kitty: text fallback on release" { + for ([_]bool{ false, true }) |report_all| { + var buf: [128]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + try kitty(&writer, .{ + .action = .release, + .key = .unidentified, + .mods = .{ .shift = true }, + .utf8 = "!", + }, .{ + .kitty_flags = .{ + .disambiguate = true, + .report_events = true, + .report_alternates = true, + .report_all = report_all, + .report_associated = true, + }, + }); + try testing.expectEqualStrings("", writer.buffered()); + } +} + +test "kitty: text fallback on repeat" { + var buf: [128]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + try kitty(&writer, .{ + .action = .repeat, + .key = .unidentified, + .mods = .{ .shift = true }, + .utf8 = "!", + }, .{ + .kitty_flags = .{ + .disambiguate = true, + .report_events = true, + .report_alternates = true, + .report_associated = true, + }, + }); + try testing.expectEqualStrings("!", writer.buffered()); +} + test "kitty: shift+a on US keyboard" { var buf: [128]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); From 7b57c0a02942beb3217a751e6d26955e3c3c1d72 Mon Sep 17 00:00:00 2001 From: "ghostty-vouch[bot]" <262049992+ghostty-vouch[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:25:55 +0000 Subject: [PATCH 088/392] Update VOUCHED list (#13859) Triggered by [discussion comment](https://github.com/ghostty-org/ghostty/discussions/13858#discussioncomment-18043290) from @jcollie. Vouch: @tsacha Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index a6a3ae4b8..330a05c28 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -305,6 +305,7 @@ ticclick tnagatomi trag1c tristan957 +tsacha turbolent tweedbeetle uhojin From a8e9b413f13cfeec77efd95c3ba9b4750fcbfada Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 16 Aug 2026 12:37:16 -0700 Subject: [PATCH 089/392] libghostty: simplify Wasm allocation API Replace a bunch of type-specific Wasm allocation functions with a generic byte allocator and reusable opaque out-parameters for pointers. This makes it a lot more ergonomic (relatively) to use the Wasm interface and removes a dozen or so exports. This also updates the `ghostty_type_json` `abi` field with a maximum alignment value that host sides can use to keep every allocation aligned properly, easily, without hardcoding numbers. This adds a test to verify this all works as intended and runs in CI. --- .github/workflows/test.yml | 5 + example/wasm-key-encode/index.html | 60 ++++++++-- example/wasm-sgr/index.html | 62 ++++++---- example/wasm-vt/index.html | 30 +++-- include/ghostty/vt/sgr.h | 25 ----- include/ghostty/vt/types.h | 3 +- include/ghostty/vt/wasm.h | 174 ++++++++++++++--------------- src/lib/allocator.zig | 4 +- src/lib/allocator/convenience.zig | 59 ---------- src/lib/allocator/wasm.zig | 98 ++++++++++++++++ src/lib/c_abi.zig | 28 +++++ src/lib_vt.zig | 15 +-- src/terminal/c/main.zig | 2 - src/terminal/c/sgr.zig | 11 -- src/terminal/c/types.schema.json | 5 + src/terminal/c/types.zig | 7 ++ test/wasm-alloc.mjs | 136 ++++++++++++++++++++++ 17 files changed, 481 insertions(+), 243 deletions(-) delete mode 100644 src/lib/allocator/convenience.zig create mode 100644 src/lib/allocator/wasm.zig create mode 100644 src/lib/c_abi.zig create mode 100644 test/wasm-alloc.mjs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5216072c..3470a8c33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -733,6 +733,11 @@ jobs: echo "Verified ${artifact} requires simd128" done + - name: Test WASM allocator + run: | + nix develop -c node test/wasm-alloc.mjs ghostty-vt.wasm + nix develop -c node test/wasm-alloc.mjs ghostty-vt-small.wasm + # Compile-only checks for the -Dvt-features flags so that future changes # don't regress any feature combination. build-libghostty-vt-features: diff --git a/example/wasm-key-encode/index.html b/example/wasm-key-encode/index.html index 9f4d8bebb..2578bdad7 100644 --- a/example/wasm-key-encode/index.html +++ b/example/wasm-key-encode/index.html @@ -163,6 +163,7 @@ let wasmMemory = null; let encoderPtr = null; let lastKeyEvent = null; + let typeLayout = null; async function loadWasm() { try { @@ -184,6 +185,12 @@ wasmInstance = wasmModule.instance; wasmMemory = wasmInstance.exports.memory; + + const jsonPtr = wasmInstance.exports.ghostty_type_json(); + const jsonStr = new TextDecoder().decode( + new Uint8Array(wasmMemory.buffer, jsonPtr, wasmMemory.buffer.byteLength - jsonPtr) + ).split('\0')[0]; + typeLayout = JSON.parse(jsonStr); return true; } catch (e) { @@ -199,6 +206,15 @@ return wasmMemory.buffer; } + function readUsize(ptr) { + const view = new DataView(getBuffer()); + switch (typeLayout.abi.usize_size) { + case 4: return view.getUint32(ptr, true); + case 8: return Number(view.getBigUint64(ptr, true)); + default: throw new Error('unsupported size_t width'); + } + } + function formatHex(bytes) { return Array.from(bytes) .map(b => b.toString(16).padStart(2, '0')) @@ -415,16 +431,28 @@ function encodeKeyEvent(event) { if (!encoderPtr) return null; + const usizeSize = typeLayout.abi.usize_size; + let eventPtrPtr = 0; + let eventPtr = 0; + let utf8Ptr = 0; + let utf8Length = 0; + let requiredPtr = 0; + let required = 0; + let bufPtr = 0; + let writtenPtr = 0; + try { // Create key event - const eventPtrPtr = wasmInstance.exports.ghostty_wasm_alloc_opaque(); + eventPtrPtr = wasmInstance.exports.ghostty_wasm_alloc_opaque(); const result = wasmInstance.exports.ghostty_key_event_new(0, eventPtrPtr); if (result !== 0) { throw new Error(`ghostty_key_event_new failed with result ${result}`); } - const eventPtr = new DataView(getBuffer()).getUint32(eventPtrPtr, true); + eventPtr = wasmInstance.exports.ghostty_wasm_take_opaque(eventPtrPtr); + wasmInstance.exports.ghostty_wasm_free_opaque(eventPtrPtr); + eventPtrPtr = 0; // Get action from radio buttons const actionRadio = document.querySelector('input[name="action"]:checked'); @@ -458,9 +486,10 @@ // Set UTF-8 text from the key event (the actual character produced) if (event.key.length === 1) { const utf8Bytes = new TextEncoder().encode(event.key); - const utf8Ptr = wasmInstance.exports.ghostty_wasm_alloc_u8_array(utf8Bytes.length); + utf8Length = utf8Bytes.length; + utf8Ptr = wasmInstance.exports.ghostty_wasm_alloc(utf8Length); new Uint8Array(getBuffer()).set(utf8Bytes, utf8Ptr); - wasmInstance.exports.ghostty_key_event_set_utf8(eventPtr, utf8Ptr, utf8Bytes.length); + wasmInstance.exports.ghostty_key_event_set_utf8(eventPtr, utf8Ptr, utf8Length); } // Set unshifted codepoint @@ -470,15 +499,15 @@ } // Encode the key event - const requiredPtr = wasmInstance.exports.ghostty_wasm_alloc_usize(); + requiredPtr = wasmInstance.exports.ghostty_wasm_alloc(usizeSize); wasmInstance.exports.ghostty_key_encoder_encode( encoderPtr, eventPtr, 0, 0, requiredPtr ); - const required = new DataView(getBuffer()).getUint32(requiredPtr, true); + required = readUsize(requiredPtr); - const bufPtr = wasmInstance.exports.ghostty_wasm_alloc_u8_array(required); - const writtenPtr = wasmInstance.exports.ghostty_wasm_alloc_usize(); + bufPtr = wasmInstance.exports.ghostty_wasm_alloc(required); + writtenPtr = wasmInstance.exports.ghostty_wasm_alloc(usizeSize); const encodeResult = wasmInstance.exports.ghostty_key_encoder_encode( encoderPtr, eventPtr, bufPtr, required, writtenPtr ); @@ -487,7 +516,7 @@ return null; // No encoding for this key } - const written = new DataView(getBuffer()).getUint32(writtenPtr, true); + const written = readUsize(writtenPtr); const encoded = new Uint8Array(getBuffer()).slice(bufPtr, bufPtr + written); return { @@ -498,6 +527,13 @@ } catch (e) { console.error('Encoding error:', e); return null; + } finally { + wasmInstance.exports.ghostty_wasm_free(writtenPtr, usizeSize); + wasmInstance.exports.ghostty_wasm_free(bufPtr, required); + wasmInstance.exports.ghostty_wasm_free(requiredPtr, usizeSize); + wasmInstance.exports.ghostty_wasm_free(utf8Ptr, utf8Length); + wasmInstance.exports.ghostty_key_event_free(eventPtr); + wasmInstance.exports.ghostty_wasm_free_opaque(eventPtrPtr); } } @@ -555,13 +591,14 @@ if (!encoderPtr) return; const flags = getKittyFlags(); - const flagsPtr = wasmInstance.exports.ghostty_wasm_alloc_u8(); + const flagsPtr = wasmInstance.exports.ghostty_wasm_alloc(1); new DataView(getBuffer()).setUint8(flagsPtr, flags); wasmInstance.exports.ghostty_key_encoder_setopt( encoderPtr, 5, // GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS flagsPtr ); + wasmInstance.exports.ghostty_wasm_free(flagsPtr, 1); // Re-encode last key with new flags reencodeLastKey(); @@ -649,7 +686,8 @@ throw new Error(`ghostty_key_encoder_new failed with result ${result}`); } - encoderPtr = new DataView(getBuffer()).getUint32(encoderPtrPtr, true); + encoderPtr = wasmInstance.exports.ghostty_wasm_take_opaque(encoderPtrPtr); + wasmInstance.exports.ghostty_wasm_free_opaque(encoderPtrPtr); // Set kitty flags based on checkboxes updateEncoderFlags(); diff --git a/example/wasm-sgr/index.html b/example/wasm-sgr/index.html index e62b26c7e..24f318b9b 100644 --- a/example/wasm-sgr/index.html +++ b/example/wasm-sgr/index.html @@ -106,6 +106,7 @@