From 3ec8ce8063f48b63b4888a3382d4836f867446ff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Jun 2023 19:22:16 -0700 Subject: [PATCH 01/19] terminfo: basic Source structure and can encode --- src/main.zig | 1 + src/terminfo/Source.zig | 98 +++++++++++++++++++++++++++++++++++++++++ src/terminfo/main.zig | 12 +++++ 3 files changed, 111 insertions(+) create mode 100644 src/terminfo/Source.zig create mode 100644 src/terminfo/main.zig diff --git a/src/main.zig b/src/main.zig index 9600a4a66..f9160a282 100644 --- a/src/main.zig +++ b/src/main.zig @@ -190,6 +190,7 @@ test { // Libraries _ = @import("segmented_pool.zig"); _ = @import("terminal/main.zig"); + _ = @import("terminfo/main.zig"); // TODO _ = @import("blocking_queue.zig"); diff --git a/src/terminfo/Source.zig b/src/terminfo/Source.zig new file mode 100644 index 000000000..46ea8bd97 --- /dev/null +++ b/src/terminfo/Source.zig @@ -0,0 +1,98 @@ +//! Terminfo source format. This can be used to encode terminfo files. +//! This cannot parse terminfo source files yet because it isn't something +//! I need to do but this can be added later. +//! +//! Background: https://invisible-island.net/ncurses/man/terminfo.5.html + +const Source = @This(); + +const std = @import("std"); + +/// The set of names for the terminal. These match the TERM environment variable +/// and are used to look up this terminal. Historically, the final name in the +/// list was the most common name for the terminal and contains spaces and +/// other characters. See terminfo(5) for details. +names: []const []const u8, + +/// The set of capabilities in this terminfo file. +capabilities: []const Capability, + +/// A capability in a terminfo file. This also includes any "use" capabilities +/// since they behave just like other capabilities as documented in terminfo(5). +pub const Capability = struct { + /// The name of capability. This is the "Cap-name" value in terminfo(5). + name: []const u8, + value: Value, + + pub const Value = union(enum) { + /// Canceled value, i.e. suffixed with @ + canceled: void, + + /// Boolean values are always true if they exist so there is no value. + boolean: void, + + /// Numeric values are always "unsigned decimal integers". The size + /// of the integer is unspecified in terminfo(5). I chose 32-bits + /// because it is a common integer size but this may be wrong. + numeric: u32, + + string: []const u8, + }; +}; + +/// Encode as a terminfo source file. The encoding is always done in a +/// human-readable format with whitespace. Fields are always written in the +/// order of the slices on this struct; this will not do any reordering. +pub fn encode(self: Source, writer: anytype) !void { + // Encode the names in the order specified + for (self.names, 0..) |name, i| { + if (i != 0) try writer.writeAll("|"); + try writer.writeAll(name); + } + try writer.writeAll(",\n"); + + // Encode each of the capabilities in the order specified + for (self.capabilities) |cap| { + try writer.writeAll("\t"); + try writer.writeAll(cap.name); + switch (cap.value) { + .canceled => try writer.writeAll("@"), + .boolean => {}, + .numeric => |v| try writer.print("#{d}", .{v}), + .string => |v| try writer.print("={s}", .{v}), + } + try writer.writeAll(",\n"); + } +} + +test "encode" { + const src: Source = .{ + .names = &.{ + "ghostty", + "xterm-ghostty", + "Ghostty", + }, + + .capabilities = &.{ + .{ .name = "am", .value = .{ .boolean = {} } }, + .{ .name = "ccc", .value = .{ .canceled = {} } }, + .{ .name = "colors", .value = .{ .numeric = 256 } }, + .{ .name = "bel", .value = .{ .string = "^G" } }, + }, + }; + + // Encode + var buf: [1024]u8 = undefined; + var buf_stream = std.io.fixedBufferStream(&buf); + try src.encode(buf_stream.writer()); + + const expected = + \\ghostty|xterm-ghostty|Ghostty, + \\ am, + \\ ccc@, + \\ colors#256, + \\ bel=^G, + \\ + ; + try std.testing.expectEqualStrings(@as([]const u8, expected), buf_stream.getWritten()); +} diff --git a/src/terminfo/main.zig b/src/terminfo/main.zig new file mode 100644 index 000000000..0c1947b1c --- /dev/null +++ b/src/terminfo/main.zig @@ -0,0 +1,12 @@ +//! Package terminfo provides functionality related to terminfo/termcap files. +//! +//! At the time of writing this comment, the focus is on generating terminfo +//! files so that we can maintain our terminfo in Zig instead of hand-writing +//! the archaic (imo) terminfo format by hand. But eventually we may want to +//! extract this into a more full-featured library on its own. + +pub const Source = @import("Source.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} From ea67b4aa48fc083ee7d8ad1f8e236989c6244cbc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 08:59:05 -0700 Subject: [PATCH 02/19] terminfo: working on Ghostty's terminfo --- src/terminfo/ghostty.zig | 92 ++++++++++++++++++++++++++++++++++++++++ src/terminfo/main.zig | 1 + 2 files changed, 93 insertions(+) create mode 100644 src/terminfo/ghostty.zig diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig new file mode 100644 index 000000000..55a9d320a --- /dev/null +++ b/src/terminfo/ghostty.zig @@ -0,0 +1,92 @@ +const std = @import("std"); +const Source = @import("Source.zig"); + +/// Ghostty's terminfo entry. +pub const ghostty: Source = .{ + .names = &.{ + "ghostty", + "xterm-ghostty", + "Ghostty", + }, + + // NOTE: These capabilities are super underdocumented and I'm not 100% + // I've got the list or my understanding of any in this list fully correct. + // As we learn more, please update the comments to better explain what + // anything means. + // + // I've marked some capabilities as "???" if I don't understand what they + // mean but I just assume I support since other modern terminals do. In + // this case, I'd love if anyone could help explain what this means and + // verify that Ghostty does indeed support it and if not we can fix it. + .capabilities = &.{ + // automatic right margin -- when reaching the end of a line, text is + // wrapped to the next line. + .{ .name = "am", .value = .{ .boolean = {} } }, + + // background color erase -- screen is erased with the background color + .{ .name = "bce", .value = .{ .boolean = {} } }, + + // terminal can change color definitions, i.e. we can change the color + // palette. TODO: this may require implementing CSI 4 which we don't + // at the time of writing this comment. + .{ .name = "ccc", .value = .{ .boolean = {} } }, + + // supports changing the window title. + .{ .name = "hs", .value = .{ .boolean = {} } }, + + // terminal has a meta key + .{ .name = "km", .value = .{ .boolean = {} } }, + + // terminal will not echo input on the screen on its own + .{ .name = "mc5i", .value = .{ .boolean = {} } }, + + // safe to move (move what?) while in insert/standout mode. (???) + .{ .name = "mir", .value = .{ .boolean = {} } }, + .{ .name = "msgr", .value = .{ .boolean = {} } }, + + // no pad character (???) + .{ .name = "npc", .value = .{ .boolean = {} } }, + + // newline ignored after 80 cols (???) + .{ .name = "xenl", .value = .{ .boolean = {} } }, + + // Tmux "truecolor" mode. Other programs also use this to detect + // if the terminal supports "truecolor". This means that the terminal + // can display 24-bit RGB colors. + .{ .name = "Tc", .value = .{ .boolean = {} } }, + + // Colored underlines. https://sw.kovidgoyal.net/kitty/underlines/ + .{ .name = "Su", .value = .{ .boolean = {} } }, + + // Full keyboard support using Kitty's keyboard protocol: + // https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + // Commented out because we don't yet support this. + // .{ .name = "fullkbd", .value = .{ .boolean = {} } }, + + // Number of colors in the color palette. + .{ .name = "colors", .value = .{ .numeric = 256 } }, + + // Number of columns in a line. Our terminal is variable width on + // Window resize but this appears to just be the value set by most + // terminals. + .{ .name = "cols", .value = .{ .numeric = 80 } }, + + // Initial tabstop interval. + .{ .name = "it", .value = .{ .numeric = 8 } }, + + // Number of lines on a page. Similar to cols this is variable width + // but this appears to be the value set by most terminals. + .{ .name = "lines", .value = .{ .numeric = 24 } }, + + // Number of color pairs on the screen. + .{ .name = "pairs", .value = .{ .numeric = 32767 } }, + }, +}; + +test "encode" { + // Encode + var buf: [1024]u8 = undefined; + var buf_stream = std.io.fixedBufferStream(&buf); + try ghostty.encode(buf_stream.writer()); + try std.testing.expect(buf_stream.getWritten().len > 0); +} diff --git a/src/terminfo/main.zig b/src/terminfo/main.zig index 0c1947b1c..8803da9ef 100644 --- a/src/terminfo/main.zig +++ b/src/terminfo/main.zig @@ -5,6 +5,7 @@ //! the archaic (imo) terminfo format by hand. But eventually we may want to //! extract this into a more full-featured library on its own. +pub const ghostty = @import("ghostty.zig"); pub const Source = @import("Source.zig"); test { From 21d922304bf39823d832556a482399668e599adf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 09:12:42 -0700 Subject: [PATCH 03/19] build: build.zig encodes and writes the terminfo source --- build.zig | 29 +++++++++++++++++++++++------ src/terminfo/main.zig | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index 8f5d075ab..ce6141be2 100644 --- a/build.zig +++ b/build.zig @@ -3,7 +3,16 @@ const builtin = @import("builtin"); const fs = std.fs; const LibExeObjStep = std.build.LibExeObjStep; const RunStep = std.build.RunStep; + const apprt = @import("src/apprt.zig"); +const font = @import("src/font/main.zig"); +const terminfo = @import("src/terminfo/main.zig"); +const WasmTarget = @import("src/os/wasm/target.zig").Target; +const LibtoolStep = @import("src/build/LibtoolStep.zig"); +const LipoStep = @import("src/build/LipoStep.zig"); +const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig"); +const Version = @import("src/build/Version.zig"); + const glfw = @import("vendor/mach-glfw/build.zig"); const fontconfig = @import("pkg/fontconfig/build.zig"); const freetype = @import("pkg/freetype/build.zig"); @@ -21,12 +30,6 @@ const utf8proc = @import("pkg/utf8proc/build.zig"); const zlib = @import("pkg/zlib/build.zig"); const tracylib = @import("pkg/tracy/build.zig"); const system_sdk = @import("vendor/mach-glfw/system_sdk.zig"); -const font = @import("src/font/main.zig"); -const WasmTarget = @import("src/os/wasm/target.zig").Target; -const LibtoolStep = @import("src/build/LibtoolStep.zig"); -const LipoStep = @import("src/build/LipoStep.zig"); -const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig"); -const Version = @import("src/build/Version.zig"); // Do a comptime Zig version requirement. The required Zig version is // somewhat arbitrary: it is meant to be a version that we feel works well, @@ -271,6 +274,20 @@ pub fn build(b: *std.Build) !void { } } + // Terminfo + { + // Encode our terminfo + var str = std.ArrayList(u8).init(b.allocator); + defer str.deinit(); + try terminfo.ghostty.encode(str.writer()); + + // Write it + var wf = b.addWriteFiles(); + const src_source = wf.add("share/terminfo/ghostty.terminfo", str.items); + const src_install = b.addInstallFile(src_source, "share/terminfo/ghostty.terminfo"); + b.getInstallStep().dependOn(&src_install.step); + } + // App (Linux) if (target.isLinux()) { // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html diff --git a/src/terminfo/main.zig b/src/terminfo/main.zig index 8803da9ef..c76c328cc 100644 --- a/src/terminfo/main.zig +++ b/src/terminfo/main.zig @@ -5,7 +5,7 @@ //! the archaic (imo) terminfo format by hand. But eventually we may want to //! extract this into a more full-featured library on its own. -pub const ghostty = @import("ghostty.zig"); +pub const ghostty = @import("ghostty.zig").ghostty; pub const Source = @import("Source.zig"); test { From aad84833239f996ecf0bc5a23b973b07f78dfb3d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 09:45:26 -0700 Subject: [PATCH 04/19] build: use tic to compile terminfo into database format --- build.zig | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/build.zig b/build.zig index ce6141be2..cd62d5a64 100644 --- a/build.zig +++ b/build.zig @@ -286,6 +286,42 @@ pub fn build(b: *std.Build) !void { const src_source = wf.add("share/terminfo/ghostty.terminfo", str.items); const src_install = b.addInstallFile(src_source, "share/terminfo/ghostty.terminfo"); b.getInstallStep().dependOn(&src_install.step); + + // Convert to termcap source format if thats helpful to people and + // install it. The resulting value here is the termcap source in case + // that is used for other commands. + const cap_source = cap_source: { + const run_step = RunStep.create(b, "infotocap"); + run_step.addArg("infotocap"); + run_step.addFileSourceArg(src_source); + const out_source = run_step.captureStdOut(); + _ = run_step.captureStdErr(); // so we don't see stderr + + const cap_install = b.addInstallFile(out_source, "share/terminfo/ghostty.termcap"); + b.getInstallStep().dependOn(&cap_install.step); + + break :cap_source out_source; + }; + _ = cap_source; + + // Compile the terminfo source into a terminfo database + { + // Hardcoded until: https://github.com/ziglang/zig/issues/16187 + const path = "zig-cache/tmp/tic"; + + const run_step = RunStep.create(b, "tic"); + run_step.addArgs(&.{ "tic", "-x", "-o", path }); + run_step.addFileSourceArg(src_source); + _ = run_step.captureStdErr(); // so we don't see stderr + + const install = b.addInstallDirectory(.{ + .source_dir = path, + .install_dir = .prefix, + .install_subdir = "share/terminfo", + }); + install.step.dependOn(&run_step.step); + b.getInstallStep().dependOn(&install.step); + } } // App (Linux) From 3a28ab1e8a633df9b0f29d4ed13d8f841f6bcff1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 09:52:32 -0700 Subject: [PATCH 05/19] nix: add ncurses so we have access to tic, infocmp, etc. --- nix/devshell.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/devshell.nix b/nix/devshell.nix index e606c091d..1f627c5ac 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -4,6 +4,7 @@ , flatpak-builder , gdb , glxinfo +, ncurses , nodejs , parallel , pkg-config @@ -67,6 +68,7 @@ in mkShell rec { nativeBuildInputs = [ # For builds llvmPackages_latest.llvm + ncurses pkg-config scdoc zig From 0f43e79bda0851f97f137dc0a5a3a4eb132b3b4d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 10:29:19 -0700 Subject: [PATCH 06/19] terminfo: a bunch more capabilities --- src/terminfo/ghostty.zig | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 55a9d320a..923c7e549 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -80,6 +80,56 @@ pub const ghostty: Source = .{ // Number of color pairs on the screen. .{ .name = "pairs", .value = .{ .numeric = 32767 } }, + + // Alternate character set. This is the VT100 alternate character set. + // I don't know what the value means, I copied this from Kitty and + // verified with some other terminals (looks similar). + .{ .name = "acsc", .value = .{ .string = "++\\,\\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~" } }, + + // These are all capabilities that should be pretty straightforward + // and map to input sequences. + .{ .name = "bel", .value = .{ .string = "^G" } }, + .{ .name = "blink", .value = .{ .string = "\\E[5m" } }, + .{ .name = "bold", .value = .{ .string = "\\E[1m" } }, + .{ .name = "cbt", .value = .{ .string = "\\E[Z" } }, + .{ .name = "civis", .value = .{ .string = "\\E[?25l" } }, + .{ .name = "clear", .value = .{ .string = "\\E[H\\E[2J" } }, + .{ .name = "cnorm", .value = .{ .string = "\\E[?25h" } }, + .{ .name = "cr", .value = .{ .string = "\\r" } }, + .{ .name = "csr", .value = .{ .string = "\\E[%i%p1%d;%p2%dr" } }, + .{ .name = "cub", .value = .{ .string = "\\E[%p1%dD" } }, + .{ .name = "cub1", .value = .{ .string = "^H" } }, + .{ .name = "cud", .value = .{ .string = "\\E[%p1%dB" } }, + .{ .name = "cud1", .value = .{ .string = "^J" } }, + .{ .name = "cuf", .value = .{ .string = "\\E[%p1%dC" } }, + .{ .name = "cuf1", .value = .{ .string = "\\E[C" } }, + .{ .name = "cup", .value = .{ .string = "\\E[%i%p1%d;%p2%dH" } }, + .{ .name = "cuu", .value = .{ .string = "\\E[%p1%dA" } }, + .{ .name = "cuu1", .value = .{ .string = "\\E[A" } }, + .{ .name = "cvvis", .value = .{ .string = "\\E[?12;25h" } }, + .{ .name = "dch", .value = .{ .string = "\\E[%p1%dP" } }, + .{ .name = "dch1", .value = .{ .string = "\\E[P" } }, + .{ .name = "dim", .value = .{ .string = "\\E[2m" } }, + .{ .name = "dl", .value = .{ .string = "\\E[%p1%dM" } }, + .{ .name = "dl1", .value = .{ .string = "\\E[M" } }, + .{ .name = "dsl", .value = .{ .string = "\\E]2;\\007" } }, + .{ .name = "ech", .value = .{ .string = "\\E[%p1%dX" } }, + .{ .name = "ed", .value = .{ .string = "\\E[J" } }, + .{ .name = "el", .value = .{ .string = "\\E[K" } }, + .{ .name = "el1", .value = .{ .string = "\\E[1K" } }, + .{ .name = "flash", .value = .{ .string = "\\E[?5h$<100/>\\E[?5l" } }, + .{ .name = "fsl", .value = .{ .string = "^G" } }, + .{ .name = "home", .value = .{ .string = "\\E[H" } }, + .{ .name = "hpa", .value = .{ .string = "\\E[%i%p1%dG" } }, + .{ .name = "ht", .value = .{ .string = "^I" } }, + .{ .name = "hts", .value = .{ .string = "\\EH" } }, + .{ .name = "ich", .value = .{ .string = "\\E[%p1%d@" } }, + .{ .name = "il", .value = .{ .string = "\\E[%p1%dL" } }, + .{ .name = "il1", .value = .{ .string = "\\E[L" } }, + .{ .name = "ind", .value = .{ .string = "\\n" } }, + .{ .name = "indn", .value = .{ .string = "\\E[%p1%dS" } }, + .{ .name = "initc", .value = .{ .string = "\\E]4;%p1%d;rgb\\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\" } }, + .{ .name = "invis", .value = .{ .string = "\\E[8m" } }, }, }; From 629fddcf5fe3ce74f9365065d3281a72675c566a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 11:04:02 -0700 Subject: [PATCH 07/19] terminfo: more capabilties, I think this is all of them --- src/terminfo/ghostty.zig | 185 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 923c7e549..05316b868 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -130,6 +130,191 @@ pub const ghostty: Source = .{ .{ .name = "indn", .value = .{ .string = "\\E[%p1%dS" } }, .{ .name = "initc", .value = .{ .string = "\\E]4;%p1%d;rgb\\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\" } }, .{ .name = "invis", .value = .{ .string = "\\E[8m" } }, + .{ .name = "oc", .value = .{ .string = "\\E]104\\007" } }, + .{ .name = "op", .value = .{ .string = "\\E[39;49m" } }, + .{ .name = "rc", .value = .{ .string = "\\E8" } }, + .{ .name = "rep", .value = .{ .string = "%p1%c\\E[%p2%{1}%-%db" } }, + .{ .name = "rev", .value = .{ .string = "\\E[7m" } }, + .{ .name = "ri", .value = .{ .string = "\\EM" } }, + .{ .name = "rin", .value = .{ .string = "\\E[%p1%dT" } }, + .{ .name = "ritm", .value = .{ .string = "\\E[23m" } }, + .{ .name = "rmacs", .value = .{ .string = "\\E(B" } }, + .{ .name = "rmam", .value = .{ .string = "\\E[?7l" } }, + .{ .name = "rmcup", .value = .{ .string = "\\E[?1049l" } }, + .{ .name = "rmir", .value = .{ .string = "\\E[4l" } }, + .{ .name = "rmkx", .value = .{ .string = "\\E[?1l" } }, + .{ .name = "rmso", .value = .{ .string = "\\E[27m" } }, + .{ .name = "rmul", .value = .{ .string = "\\E[24m" } }, + .{ .name = "rmxx", .value = .{ .string = "\\E[29m" } }, + .{ .name = "setab", .value = .{ .string = "\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" } }, + .{ .name = "setaf", .value = .{ .string = "\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" } }, + .{ .name = "setrgbb", .value = .{ .string = "\\E[48:2:%p1%d:%p2%d:%p3%dm" } }, + .{ .name = "setrgbf", .value = .{ .string = "\\E[38:2:%p1%d:%p2%d:%p3%dm" } }, + .{ .name = "sgr", .value = .{ .string = "%?%p9%t\\E(0%e\\E(B%;\\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m" } }, + .{ .name = "sgr0", .value = .{ .string = "\\E(B\\E[m" } }, + .{ .name = "sitm", .value = .{ .string = "\\E[3m" } }, + .{ .name = "smacs", .value = .{ .string = "\\E(0" } }, + .{ .name = "smam", .value = .{ .string = "\\E[?7h" } }, + .{ .name = "smcup", .value = .{ .string = "\\E[?1049h" } }, + .{ .name = "smir", .value = .{ .string = "\\E[4h" } }, + .{ .name = "smkx", .value = .{ .string = "\\E[?1h" } }, + .{ .name = "smso", .value = .{ .string = "\\E[7m" } }, + .{ .name = "smul", .value = .{ .string = "\\E[4m" } }, + .{ .name = "smxx", .value = .{ .string = "\\E[9m" } }, + .{ .name = "tbc", .value = .{ .string = "\\E[3g" } }, + .{ .name = "tsl", .value = .{ .string = "\\E]2;" } }, + .{ .name = "u6", .value = .{ .string = "\\E[%i%d;%dR" } }, + .{ .name = "u7", .value = .{ .string = "\\E[6n" } }, + .{ .name = "u8", .value = .{ .string = "\\E[?%[;0123456789]c" } }, + .{ .name = "u9", .value = .{ .string = "\\E[c" } }, + .{ .name = "vpa", .value = .{ .string = "\\E[%i%p1%dd" } }, + + //----------------------------------------------------------- + // Completely unvalidated entries that are blindly copied from + // other terminals (Kitty, Wezterm, Alacritty) and may or may not + // actually work with Ghostty. todo is to validate these! + + .{ .name = "kDC", .value = .{ .string = "\\E[3;2~" } }, + .{ .name = "kDC3", .value = .{ .string = "\\E[3;3~" } }, + .{ .name = "kDC4", .value = .{ .string = "\\E[3;4~" } }, + .{ .name = "kDC5", .value = .{ .string = "\\E[3;5~" } }, + .{ .name = "kDC6", .value = .{ .string = "\\E[3;6~" } }, + .{ .name = "kDC7", .value = .{ .string = "\\E[3;7~" } }, + .{ .name = "kDN", .value = .{ .string = "\\E[1;2B" } }, + .{ .name = "kDN3", .value = .{ .string = "\\E[1;3B" } }, + .{ .name = "kDN4", .value = .{ .string = "\\E[1;4B" } }, + .{ .name = "kDN5", .value = .{ .string = "\\E[1;5B" } }, + .{ .name = "kDN6", .value = .{ .string = "\\E[1;6B" } }, + .{ .name = "kDN7", .value = .{ .string = "\\E[1;7B" } }, + .{ .name = "kEND", .value = .{ .string = "\\E[1;2F" } }, + .{ .name = "kEND3", .value = .{ .string = "\\E[1;3F" } }, + .{ .name = "kEND4", .value = .{ .string = "\\E[1;4F" } }, + .{ .name = "kEND5", .value = .{ .string = "\\E[1;5F" } }, + .{ .name = "kEND6", .value = .{ .string = "\\E[1;6F" } }, + .{ .name = "kEND7", .value = .{ .string = "\\E[1;7F" } }, + .{ .name = "kHOM", .value = .{ .string = "\\E[1;2H" } }, + .{ .name = "kHOM3", .value = .{ .string = "\\E[1;3H" } }, + .{ .name = "kHOM4", .value = .{ .string = "\\E[1;4H" } }, + .{ .name = "kHOM5", .value = .{ .string = "\\E[1;5H" } }, + .{ .name = "kHOM6", .value = .{ .string = "\\E[1;6H" } }, + .{ .name = "kHOM7", .value = .{ .string = "\\E[1;7H" } }, + .{ .name = "kIC", .value = .{ .string = "\\E[2;2~" } }, + .{ .name = "kIC3", .value = .{ .string = "\\E[2;3~" } }, + .{ .name = "kIC4", .value = .{ .string = "\\E[2;4~" } }, + .{ .name = "kIC5", .value = .{ .string = "\\E[2;5~" } }, + .{ .name = "kIC6", .value = .{ .string = "\\E[2;6~" } }, + .{ .name = "kIC7", .value = .{ .string = "\\E[2;7~" } }, + .{ .name = "kLFT", .value = .{ .string = "\\E[1;2D" } }, + .{ .name = "kLFT3", .value = .{ .string = "\\E[1;3D" } }, + .{ .name = "kLFT4", .value = .{ .string = "\\E[1;4D" } }, + .{ .name = "kLFT5", .value = .{ .string = "\\E[1;5D" } }, + .{ .name = "kLFT6", .value = .{ .string = "\\E[1;6D" } }, + .{ .name = "kLFT7", .value = .{ .string = "\\E[1;7D" } }, + .{ .name = "kNXT", .value = .{ .string = "\\E[6;2~" } }, + .{ .name = "kNXT3", .value = .{ .string = "\\E[6;3~" } }, + .{ .name = "kNXT4", .value = .{ .string = "\\E[6;4~" } }, + .{ .name = "kNXT5", .value = .{ .string = "\\E[6;5~" } }, + .{ .name = "kNXT6", .value = .{ .string = "\\E[6;6~" } }, + .{ .name = "kNXT7", .value = .{ .string = "\\E[6;7~" } }, + .{ .name = "kPRV", .value = .{ .string = "\\E[5;2~" } }, + .{ .name = "kPRV3", .value = .{ .string = "\\E[5;3~" } }, + .{ .name = "kPRV4", .value = .{ .string = "\\E[5;4~" } }, + .{ .name = "kPRV5", .value = .{ .string = "\\E[5;5~" } }, + .{ .name = "kPRV6", .value = .{ .string = "\\E[5;6~" } }, + .{ .name = "kPRV7", .value = .{ .string = "\\E[5;7~" } }, + .{ .name = "kRIT", .value = .{ .string = "\\E[1;2C" } }, + .{ .name = "kRIT3", .value = .{ .string = "\\E[1;3C" } }, + .{ .name = "kRIT4", .value = .{ .string = "\\E[1;4C" } }, + .{ .name = "kRIT5", .value = .{ .string = "\\E[1;5C" } }, + .{ .name = "kRIT6", .value = .{ .string = "\\E[1;6C" } }, + .{ .name = "kRIT7", .value = .{ .string = "\\E[1;7C" } }, + .{ .name = "kUP", .value = .{ .string = "\\E[1;2A" } }, + .{ .name = "kUP3", .value = .{ .string = "\\E[1;3A" } }, + .{ .name = "kUP4", .value = .{ .string = "\\E[1;4A" } }, + .{ .name = "kUP5", .value = .{ .string = "\\E[1;5A" } }, + .{ .name = "kUP6", .value = .{ .string = "\\E[1;6A" } }, + .{ .name = "kUP7", .value = .{ .string = "\\E[1;7A" } }, + .{ .name = "kbs", .value = .{ .string = "^?" } }, + .{ .name = "kcbt", .value = .{ .string = "\\E[Z" } }, + .{ .name = "kcub1", .value = .{ .string = "\\E0D" } }, + .{ .name = "kcud1", .value = .{ .string = "\\E0B" } }, + .{ .name = "kcuf1", .value = .{ .string = "\\E0C" } }, + .{ .name = "kcuu1", .value = .{ .string = "\\E0A" } }, + .{ .name = "kdch1", .value = .{ .string = "\\E[3~" } }, + .{ .name = "kend", .value = .{ .string = "\\E0F" } }, + .{ .name = "kent", .value = .{ .string = "\\E0M" } }, + .{ .name = "kf1", .value = .{ .string = "\\E0P" } }, + .{ .name = "kf10", .value = .{ .string = "\\E[21~" } }, + .{ .name = "kf11", .value = .{ .string = "\\E[23~" } }, + .{ .name = "kf12", .value = .{ .string = "\\E[24~" } }, + .{ .name = "kf13", .value = .{ .string = "\\E[1;2P" } }, + .{ .name = "kf14", .value = .{ .string = "\\E[1;2Q" } }, + .{ .name = "kf15", .value = .{ .string = "\\E[1;2R" } }, + .{ .name = "kf16", .value = .{ .string = "\\E[1;2S" } }, + .{ .name = "kf17", .value = .{ .string = "\\E[15;2~" } }, + .{ .name = "kf18", .value = .{ .string = "\\E[17;2~" } }, + .{ .name = "kf19", .value = .{ .string = "\\E[18;2~" } }, + .{ .name = "kf2", .value = .{ .string = "\\E0Q" } }, + .{ .name = "kf20", .value = .{ .string = "\\E[19;2~" } }, + .{ .name = "kf21", .value = .{ .string = "\\E[20;2~" } }, + .{ .name = "kf22", .value = .{ .string = "\\E[21;2~" } }, + .{ .name = "kf23", .value = .{ .string = "\\E[23;2~" } }, + .{ .name = "kf24", .value = .{ .string = "\\E[24;2~" } }, + .{ .name = "kf25", .value = .{ .string = "\\E[1;5P" } }, + .{ .name = "kf26", .value = .{ .string = "\\E[1;5Q" } }, + .{ .name = "kf27", .value = .{ .string = "\\E[13;5~" } }, + .{ .name = "kf28", .value = .{ .string = "\\E[1:5S" } }, + .{ .name = "kf29", .value = .{ .string = "\\E[15;5~" } }, + .{ .name = "kf3", .value = .{ .string = "\\E0R" } }, + .{ .name = "kf30", .value = .{ .string = "\\E[17;5~" } }, + .{ .name = "kf31", .value = .{ .string = "\\E[18;5~" } }, + .{ .name = "kf32", .value = .{ .string = "\\E[19;5~" } }, + .{ .name = "kf33", .value = .{ .string = "\\E[20;5~" } }, + .{ .name = "kf34", .value = .{ .string = "\\E[21;5~" } }, + .{ .name = "kf35", .value = .{ .string = "\\E[23;5~" } }, + .{ .name = "kf36", .value = .{ .string = "\\E[24;5~" } }, + .{ .name = "kf37", .value = .{ .string = "\\E[1;6P" } }, + .{ .name = "kf38", .value = .{ .string = "\\E[1;6Q" } }, + .{ .name = "kf39", .value = .{ .string = "\\E[13;6~" } }, + .{ .name = "kf4", .value = .{ .string = "\\E0S" } }, + .{ .name = "kf40", .value = .{ .string = "\\E[1;6S" } }, + .{ .name = "kf41", .value = .{ .string = "\\E[15;6~" } }, + .{ .name = "kf42", .value = .{ .string = "\\E[17;6~" } }, + .{ .name = "kf43", .value = .{ .string = "\\E[18;6~" } }, + .{ .name = "kf44", .value = .{ .string = "\\E[19;6~" } }, + .{ .name = "kf45", .value = .{ .string = "\\E[20;6~" } }, + .{ .name = "kf46", .value = .{ .string = "\\E[21;6~" } }, + .{ .name = "kf47", .value = .{ .string = "\\E[23;6~" } }, + .{ .name = "kf48", .value = .{ .string = "\\E[24;6~" } }, + .{ .name = "kf49", .value = .{ .string = "\\E[1;3P" } }, + .{ .name = "kf5", .value = .{ .string = "\\E[15~" } }, + .{ .name = "kf50", .value = .{ .string = "\\E[1;3Q" } }, + .{ .name = "kf51", .value = .{ .string = "\\E[13;3~" } }, + .{ .name = "kf52", .value = .{ .string = "\\E[1;3S" } }, + .{ .name = "kf53", .value = .{ .string = "\\E[15;3~" } }, + .{ .name = "kf54", .value = .{ .string = "\\E[17;3~" } }, + .{ .name = "kf55", .value = .{ .string = "\\E[18;3~" } }, + .{ .name = "kf56", .value = .{ .string = "\\E[19;3~" } }, + .{ .name = "kf57", .value = .{ .string = "\\E[20;3~" } }, + .{ .name = "kf58", .value = .{ .string = "\\E[21;3~" } }, + .{ .name = "kf59", .value = .{ .string = "\\E[23;3~" } }, + .{ .name = "kf6", .value = .{ .string = "\\E[17~" } }, + .{ .name = "kf60", .value = .{ .string = "\\E[24;3~" } }, + .{ .name = "kf61", .value = .{ .string = "\\E[1;4P" } }, + .{ .name = "kf62", .value = .{ .string = "\\E[1;4Q" } }, + .{ .name = "kf63", .value = .{ .string = "\\E[13;4~" } }, + .{ .name = "kf7", .value = .{ .string = "\\E[18~" } }, + .{ .name = "kf8", .value = .{ .string = "\\E[19~" } }, + .{ .name = "kf9", .value = .{ .string = "\\E[20~" } }, + .{ .name = "khome", .value = .{ .string = "\\E[H" } }, + .{ .name = "kich1", .value = .{ .string = "\\E[2~" } }, + .{ .name = "kind", .value = .{ .string = "\\E[1;2B" } }, + .{ .name = "kmous", .value = .{ .string = "\\E[M" } }, + .{ .name = "knp", .value = .{ .string = "\\E[6~" } }, + .{ .name = "kpp", .value = .{ .string = "\\E[5~" } }, + .{ .name = "kri", .value = .{ .string = "\\E[1;2A" } }, + .{ .name = "rs1", .value = .{ .string = "\\E]\\E\\\\\\Ec" } }, + .{ .name = "sc", .value = .{ .string = "\\E7" } }, }, }; From 3ae2a3e1fa94e6e8d0856ee6fe22b1e47590ad04 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 11:11:08 -0700 Subject: [PATCH 08/19] ghostty encoding test needs more buffer --- src/terminfo/ghostty.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 05316b868..79c20680d 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -4,8 +4,15 @@ const Source = @import("Source.zig"); /// Ghostty's terminfo entry. pub const ghostty: Source = .{ .names = &.{ + // The preferred name "ghostty", + + // We support the "xterm-" prefix because some poorly behaved programs + // use this to detect if the terminal supports 256 colors and other + // features. "xterm-ghostty", + + // Our "formal" name "Ghostty", }, @@ -320,7 +327,7 @@ pub const ghostty: Source = .{ test "encode" { // Encode - var buf: [1024]u8 = undefined; + var buf: [4096]u8 = undefined; var buf_stream = std.io.fixedBufferStream(&buf); try ghostty.encode(buf_stream.writer()); try std.testing.expect(buf_stream.getWritten().len > 0); From e6bc3c8067ca3d80998abb9e7a33ae1e867e8485 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 11:35:03 -0700 Subject: [PATCH 09/19] os: exePath to get path to running executable We're going to use this first for macOS in order to find the TERMINFO directory (if it exists). --- src/os/exe.zig | 40 ++++++++++++++++++++++++++++++++++++++++ src/os/main.zig | 1 + 2 files changed, 41 insertions(+) create mode 100644 src/os/exe.zig diff --git a/src/os/exe.zig b/src/os/exe.zig new file mode 100644 index 000000000..55c8a6fe2 --- /dev/null +++ b/src/os/exe.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +/// Returns the path to the currently executing executable. This may return +/// null if the path cannot be determined. This function is not thread-safe. +/// +/// This function can be very slow. The caller can choose to cache the value +/// if they want but this function itself doesn't handle caching. +pub fn exePath(buf: []u8) !?[]const u8 { + if (comptime builtin.target.isDarwin()) { + // We put the path into a temporary buffer first because we need + // to call realpath on it to resolve symlinks and expand all ".." + // and such. + var size: u32 = std.math.cast(u32, buf.len) orelse return error.OutOfMemory; + const result = _NSGetExecutablePath(buf.ptr, &size); + if (result == -1) return error.OutOfMemory; + if (result != 0) return error.Unknown; + const path = std.mem.sliceTo(buf, 0); + + // Expand. + var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const realpath = try std.os.realpath(path, &realpath_buf); + if (realpath.len > buf.len) return error.OutOfMemory; + + @memcpy(buf[0..realpath.len], realpath); + return buf[0..realpath.len]; + } + + return null; +} + +// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html +extern "c" fn _NSGetExecutablePath(buf: [*]u8, size: *u32) c_int; + +test exePath { + // This just ensures it compiles and runs without crashing. The result + // is allowed to be null for non-supported platforms. + var buf: [4096]u8 = undefined; + _ = try exePath(&buf); +} diff --git a/src/os/main.zig b/src/os/main.zig index 070e7be1d..8d0c286af 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -1,6 +1,7 @@ //! The "os" package contains utilities for interfacing with the operating //! system. +pub usingnamespace @import("exe.zig"); pub usingnamespace @import("file.zig"); pub usingnamespace @import("flatpak.zig"); pub usingnamespace @import("locale.zig"); From da1248d9738f02d0c35d2a18677a19cadbf556ad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 11:41:16 -0700 Subject: [PATCH 10/19] build: copy terminfo data into Mac app bundle --- build.zig | 30 ++++++++++++++++++++++++++---- src/os/exe.zig | 2 ++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index cd62d5a64..85f6fbb90 100644 --- a/build.zig +++ b/build.zig @@ -286,11 +286,18 @@ pub fn build(b: *std.Build) !void { const src_source = wf.add("share/terminfo/ghostty.terminfo", str.items); const src_install = b.addInstallFile(src_source, "share/terminfo/ghostty.terminfo"); b.getInstallStep().dependOn(&src_install.step); + if (target.isDarwin()) { + const mac_src_install = b.addInstallFile( + src_source, + "Ghostty.app/Contents/Resources/terminfo/ghostty.terminfo", + ); + b.getInstallStep().dependOn(&mac_src_install.step); + } // Convert to termcap source format if thats helpful to people and // install it. The resulting value here is the termcap source in case // that is used for other commands. - const cap_source = cap_source: { + { const run_step = RunStep.create(b, "infotocap"); run_step.addArg("infotocap"); run_step.addFileSourceArg(src_source); @@ -300,9 +307,14 @@ pub fn build(b: *std.Build) !void { const cap_install = b.addInstallFile(out_source, "share/terminfo/ghostty.termcap"); b.getInstallStep().dependOn(&cap_install.step); - break :cap_source out_source; - }; - _ = cap_source; + if (target.isDarwin()) { + const mac_cap_install = b.addInstallFile( + out_source, + "Ghostty.app/Contents/Resources/terminfo/ghostty.termcap", + ); + b.getInstallStep().dependOn(&mac_cap_install.step); + } + } // Compile the terminfo source into a terminfo database { @@ -321,6 +333,16 @@ pub fn build(b: *std.Build) !void { }); install.step.dependOn(&run_step.step); b.getInstallStep().dependOn(&install.step); + + if (target.isDarwin()) { + const mac_install = b.addInstallDirectory(.{ + .source_dir = path, + .install_dir = .prefix, + .install_subdir = "Ghostty.app/Contents/Resources/terminfo", + }); + mac_install.step.dependOn(&run_step.step); + b.getInstallStep().dependOn(&mac_install.step); + } } } diff --git a/src/os/exe.zig b/src/os/exe.zig index 55c8a6fe2..a2889a3ef 100644 --- a/src/os/exe.zig +++ b/src/os/exe.zig @@ -6,6 +6,8 @@ const builtin = @import("builtin"); /// /// This function can be very slow. The caller can choose to cache the value /// if they want but this function itself doesn't handle caching. +/// +/// From: https://unix.stackexchange.com/questions/317211/absolute-path-to-currently-executing-program pub fn exePath(buf: []u8) !?[]const u8 { if (comptime builtin.target.isDarwin()) { // We put the path into a temporary buffer first because we need From 629f8f93d68953b0b26abd8580075fa839225af9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 11:50:17 -0700 Subject: [PATCH 11/19] ci: during release builds, copy terminfo db into mac app bundle --- .github/workflows/release-tip.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 2c971a301..10cbdacfe 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -96,6 +96,12 @@ jobs: - name: Build Ghostty.app run: cd macos && xcodebuild -configuration Release + # Copy the resources we build during zig build into the final Ghostty.app + - name: Copy Resources + run: | + # Terminfo + cp -R zig-out/Ghostty.app/Contents/Resources/terminfo macos/build/Release/Ghostty.app/Contents/Resources/terminfo + # We inject the "build number" as simply the number of commits since HEAD. # This will be a monotonically always increasing build number that we use. - name: Inject Build Number From 9c8f78438664334480f1aa0b9501962ca7be7528 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 12:10:10 -0700 Subject: [PATCH 12/19] termio: sets TERMINFO if we can find the terminfo dir --- src/termio/Exec.zig | 58 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 80ea35348..38944132a 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -491,8 +491,27 @@ const Subprocess = struct { break :env try std.process.getEnvMap(alloc); }; errdefer env.deinit(); - try env.put("TERM", "xterm-256color"); - try env.put("COLORTERM", "truecolor"); + + // Set our TERM var. This is a bit complicated because we want to use + // the ghostty TERM value but we want to only do that if we have + // ghostty in the TERMINFO database. + // + // For now, we just look up a bundled dir but in the future we should + // also load the terminfo database and look for it. + if (try terminfoDir(alloc)) |dir| { + try env.put("TERM", "xterm-ghostty"); + try env.put("COLORTERM", "truecolor"); + try env.put("TERMINFO", dir); + } else { + if (comptime builtin.target.isDarwin()) { + log.warn("ghostty terminfo not found, using xterm-256color", .{}); + log.warn("the terminfo SHOULD exist on macos, please ensure", .{}); + log.warn("you're using a valid app bundle.", .{}); + } + + try env.put("TERM", "xterm-256color"); + try env.put("COLORTERM", "truecolor"); + } // When embedding in macOS and running via XCode, XCode injects // a bunch of things that break our shell process. We remove those. @@ -745,6 +764,41 @@ const Subprocess = struct { fn killCommandFlatpak(command: *FlatpakHostCommand) !void { try command.signal(c.SIGHUP, true); } + + /// Gets the directory to the terminfo database, if it can be detected. + /// The memory returned can't be easily freed so the alloc should be + /// an arena or something similar. + fn terminfoDir(alloc: Allocator) !?[]const u8 { + // We only support Mac lookups right now because the terminfo + // DB can be embedded directly in the App bundle. + if (comptime !builtin.target.isDarwin()) return null; + + // Get the path to our running binary + var exe_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var exe = (try internal_os.exePath(&exe_buf)) orelse return null; + + // We have an exe path! Climb the tree looking for the terminfo + // bundle as we expect it. + while (std.fs.path.dirname(exe)) |dir| { + exe = dir; + + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const path = try std.fmt.bufPrint( + &buf, + "{s}/Contents/Resources/terminfo", + .{dir}, + ); + + if (std.fs.accessAbsolute(path, .{})) { + return try alloc.dupe(u8, path); + } else |_| { + // Folder doesn't exist. If a different error happens its okay + // we just ignore it and move on. + } + } + + return null; + } }; /// The read thread sits in a loop doing the following pseudo code: From b2cd2e06de18994c972eca35f540164a360e05c2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 12:12:49 -0700 Subject: [PATCH 13/19] use stdlib selfExePath --- src/os/exe.zig | 42 ------------------------------------------ src/os/main.zig | 1 - src/termio/Exec.zig | 2 +- 3 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 src/os/exe.zig diff --git a/src/os/exe.zig b/src/os/exe.zig deleted file mode 100644 index a2889a3ef..000000000 --- a/src/os/exe.zig +++ /dev/null @@ -1,42 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -/// Returns the path to the currently executing executable. This may return -/// null if the path cannot be determined. This function is not thread-safe. -/// -/// This function can be very slow. The caller can choose to cache the value -/// if they want but this function itself doesn't handle caching. -/// -/// From: https://unix.stackexchange.com/questions/317211/absolute-path-to-currently-executing-program -pub fn exePath(buf: []u8) !?[]const u8 { - if (comptime builtin.target.isDarwin()) { - // We put the path into a temporary buffer first because we need - // to call realpath on it to resolve symlinks and expand all ".." - // and such. - var size: u32 = std.math.cast(u32, buf.len) orelse return error.OutOfMemory; - const result = _NSGetExecutablePath(buf.ptr, &size); - if (result == -1) return error.OutOfMemory; - if (result != 0) return error.Unknown; - const path = std.mem.sliceTo(buf, 0); - - // Expand. - var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const realpath = try std.os.realpath(path, &realpath_buf); - if (realpath.len > buf.len) return error.OutOfMemory; - - @memcpy(buf[0..realpath.len], realpath); - return buf[0..realpath.len]; - } - - return null; -} - -// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html -extern "c" fn _NSGetExecutablePath(buf: [*]u8, size: *u32) c_int; - -test exePath { - // This just ensures it compiles and runs without crashing. The result - // is allowed to be null for non-supported platforms. - var buf: [4096]u8 = undefined; - _ = try exePath(&buf); -} diff --git a/src/os/main.zig b/src/os/main.zig index 8d0c286af..070e7be1d 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -1,7 +1,6 @@ //! The "os" package contains utilities for interfacing with the operating //! system. -pub usingnamespace @import("exe.zig"); pub usingnamespace @import("file.zig"); pub usingnamespace @import("flatpak.zig"); pub usingnamespace @import("locale.zig"); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 38944132a..1345d8f0d 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -775,7 +775,7 @@ const Subprocess = struct { // Get the path to our running binary var exe_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var exe = (try internal_os.exePath(&exe_buf)) orelse return null; + var exe: []const u8 = std.fs.selfExePath(&exe_buf) catch return null; // We have an exe path! Climb the tree looking for the terminfo // bundle as we expect it. From d9421b87b053c02b57cb81f50d4cc17317894890 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 12:40:12 -0700 Subject: [PATCH 14/19] build: copy the terminfo db using cp so we get symlinks --- build.zig | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/build.zig b/build.zig index 85f6fbb90..0d3487643 100644 --- a/build.zig +++ b/build.zig @@ -319,29 +319,33 @@ pub fn build(b: *std.Build) !void { // Compile the terminfo source into a terminfo database { // Hardcoded until: https://github.com/ziglang/zig/issues/16187 - const path = "zig-cache/tmp/tic"; + const path = "zig-cache/tmp/terminfo"; const run_step = RunStep.create(b, "tic"); run_step.addArgs(&.{ "tic", "-x", "-o", path }); run_step.addFileSourceArg(src_source); _ = run_step.captureStdErr(); // so we don't see stderr - const install = b.addInstallDirectory(.{ - .source_dir = path, - .install_dir = .prefix, - .install_subdir = "share/terminfo", + const copy_step = RunStep.create(b, "copy terminfo db"); + copy_step.step.dependOn(&run_step.step); + copy_step.addArgs(&.{ + "cp", + "-R", + path, + b.fmt("{s}/share", .{b.install_prefix}), }); - install.step.dependOn(&run_step.step); - b.getInstallStep().dependOn(&install.step); + b.getInstallStep().dependOn(©_step.step); if (target.isDarwin()) { - const mac_install = b.addInstallDirectory(.{ - .source_dir = path, - .install_dir = .prefix, - .install_subdir = "Ghostty.app/Contents/Resources/terminfo", + const mac_copy_step = RunStep.create(b, "copy terminfo db"); + mac_copy_step.step.dependOn(&run_step.step); + mac_copy_step.addArgs(&.{ + "cp", + "-R", + path, + b.fmt("{s}/Ghostty.app/Contents/Resources", .{b.install_prefix}), }); - mac_install.step.dependOn(&run_step.step); - b.getInstallStep().dependOn(&mac_install.step); + b.getInstallStep().dependOn(&mac_copy_step.step); } } } From 7e51dbb7e5cde8e7df59db637ded932add256c42 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 14:24:58 -0700 Subject: [PATCH 15/19] build: fix race conditions, use actual filesource --- build.zig | 36 +++++++++++++++--------------------- src/terminfo/ghostty.zig | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/build.zig b/build.zig index 0d3487643..36b047756 100644 --- a/build.zig +++ b/build.zig @@ -318,34 +318,28 @@ pub fn build(b: *std.Build) !void { // Compile the terminfo source into a terminfo database { - // Hardcoded until: https://github.com/ziglang/zig/issues/16187 - const path = "zig-cache/tmp/terminfo"; - const run_step = RunStep.create(b, "tic"); - run_step.addArgs(&.{ "tic", "-x", "-o", path }); + run_step.addArgs(&.{ "tic", "-x", "-o" }); + const path = run_step.addOutputFileArg("terminfo"); run_step.addFileSourceArg(src_source); _ = run_step.captureStdErr(); // so we don't see stderr - const copy_step = RunStep.create(b, "copy terminfo db"); - copy_step.step.dependOn(&run_step.step); - copy_step.addArgs(&.{ - "cp", - "-R", - path, - b.fmt("{s}/share", .{b.install_prefix}), - }); - b.getInstallStep().dependOn(©_step.step); + { + const copy_step = RunStep.create(b, "copy terminfo db"); + copy_step.addArgs(&.{ "cp", "-R" }); + copy_step.addFileSourceArg(path); + copy_step.addArg(b.fmt("{s}/share", .{b.install_prefix})); + b.getInstallStep().dependOn(©_step.step); + } if (target.isDarwin()) { - const mac_copy_step = RunStep.create(b, "copy terminfo db"); - mac_copy_step.step.dependOn(&run_step.step); - mac_copy_step.addArgs(&.{ - "cp", - "-R", - path, + const copy_step = RunStep.create(b, "copy terminfo db"); + copy_step.addArgs(&.{ "cp", "-R" }); + copy_step.addFileSourceArg(path); + copy_step.addArg( b.fmt("{s}/Ghostty.app/Contents/Resources", .{b.install_prefix}), - }); - b.getInstallStep().dependOn(&mac_copy_step.step); + ); + b.getInstallStep().dependOn(©_step.step); } } } diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 79c20680d..fc0d2fc54 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -101,7 +101,7 @@ pub const ghostty: Source = .{ .{ .name = "cbt", .value = .{ .string = "\\E[Z" } }, .{ .name = "civis", .value = .{ .string = "\\E[?25l" } }, .{ .name = "clear", .value = .{ .string = "\\E[H\\E[2J" } }, - .{ .name = "cnorm", .value = .{ .string = "\\E[?25h" } }, + .{ .name = "cnorm", .value = .{ .string = "\\E[?12l\\E[?25h" } }, .{ .name = "cr", .value = .{ .string = "\\r" } }, .{ .name = "csr", .value = .{ .string = "\\E[%i%p1%d;%p2%dr" } }, .{ .name = "cub", .value = .{ .string = "\\E[%p1%dD" } }, From 7092078585f6e69d51c26fbccbe964f5f5952c45 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 14:43:11 -0700 Subject: [PATCH 16/19] terminfo: fix 0 => O --- src/terminfo/ghostty.zig | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index fc0d2fc54..0dbaf7d31 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -39,7 +39,7 @@ pub const ghostty: Source = .{ .{ .name = "ccc", .value = .{ .boolean = {} } }, // supports changing the window title. - .{ .name = "hs", .value = .{ .boolean = {} } }, + //.{ .name = "hs", .value = .{ .boolean = {} } }, // terminal has a meta key .{ .name = "km", .value = .{ .boolean = {} } }, @@ -243,14 +243,14 @@ pub const ghostty: Source = .{ .{ .name = "kUP7", .value = .{ .string = "\\E[1;7A" } }, .{ .name = "kbs", .value = .{ .string = "^?" } }, .{ .name = "kcbt", .value = .{ .string = "\\E[Z" } }, - .{ .name = "kcub1", .value = .{ .string = "\\E0D" } }, - .{ .name = "kcud1", .value = .{ .string = "\\E0B" } }, - .{ .name = "kcuf1", .value = .{ .string = "\\E0C" } }, - .{ .name = "kcuu1", .value = .{ .string = "\\E0A" } }, + .{ .name = "kcub1", .value = .{ .string = "\\EOD" } }, + .{ .name = "kcud1", .value = .{ .string = "\\EOB" } }, + .{ .name = "kcuf1", .value = .{ .string = "\\EOC" } }, + .{ .name = "kcuu1", .value = .{ .string = "\\EOA" } }, .{ .name = "kdch1", .value = .{ .string = "\\E[3~" } }, - .{ .name = "kend", .value = .{ .string = "\\E0F" } }, - .{ .name = "kent", .value = .{ .string = "\\E0M" } }, - .{ .name = "kf1", .value = .{ .string = "\\E0P" } }, + .{ .name = "kend", .value = .{ .string = "\\EOF" } }, + .{ .name = "kent", .value = .{ .string = "\\EOM" } }, + .{ .name = "kf1", .value = .{ .string = "\\EOP" } }, .{ .name = "kf10", .value = .{ .string = "\\E[21~" } }, .{ .name = "kf11", .value = .{ .string = "\\E[23~" } }, .{ .name = "kf12", .value = .{ .string = "\\E[24~" } }, @@ -261,7 +261,7 @@ pub const ghostty: Source = .{ .{ .name = "kf17", .value = .{ .string = "\\E[15;2~" } }, .{ .name = "kf18", .value = .{ .string = "\\E[17;2~" } }, .{ .name = "kf19", .value = .{ .string = "\\E[18;2~" } }, - .{ .name = "kf2", .value = .{ .string = "\\E0Q" } }, + .{ .name = "kf2", .value = .{ .string = "\\EOQ" } }, .{ .name = "kf20", .value = .{ .string = "\\E[19;2~" } }, .{ .name = "kf21", .value = .{ .string = "\\E[20;2~" } }, .{ .name = "kf22", .value = .{ .string = "\\E[21;2~" } }, @@ -269,10 +269,10 @@ pub const ghostty: Source = .{ .{ .name = "kf24", .value = .{ .string = "\\E[24;2~" } }, .{ .name = "kf25", .value = .{ .string = "\\E[1;5P" } }, .{ .name = "kf26", .value = .{ .string = "\\E[1;5Q" } }, - .{ .name = "kf27", .value = .{ .string = "\\E[13;5~" } }, - .{ .name = "kf28", .value = .{ .string = "\\E[1:5S" } }, + .{ .name = "kf27", .value = .{ .string = "\\E[1;5R" } }, + .{ .name = "kf28", .value = .{ .string = "\\E[1;5S" } }, .{ .name = "kf29", .value = .{ .string = "\\E[15;5~" } }, - .{ .name = "kf3", .value = .{ .string = "\\E0R" } }, + .{ .name = "kf3", .value = .{ .string = "\\EOR" } }, .{ .name = "kf30", .value = .{ .string = "\\E[17;5~" } }, .{ .name = "kf31", .value = .{ .string = "\\E[18;5~" } }, .{ .name = "kf32", .value = .{ .string = "\\E[19;5~" } }, @@ -282,8 +282,8 @@ pub const ghostty: Source = .{ .{ .name = "kf36", .value = .{ .string = "\\E[24;5~" } }, .{ .name = "kf37", .value = .{ .string = "\\E[1;6P" } }, .{ .name = "kf38", .value = .{ .string = "\\E[1;6Q" } }, - .{ .name = "kf39", .value = .{ .string = "\\E[13;6~" } }, - .{ .name = "kf4", .value = .{ .string = "\\E0S" } }, + .{ .name = "kf39", .value = .{ .string = "\\E[1;6R" } }, + .{ .name = "kf4", .value = .{ .string = "\\EOS" } }, .{ .name = "kf40", .value = .{ .string = "\\E[1;6S" } }, .{ .name = "kf41", .value = .{ .string = "\\E[15;6~" } }, .{ .name = "kf42", .value = .{ .string = "\\E[17;6~" } }, @@ -296,7 +296,7 @@ pub const ghostty: Source = .{ .{ .name = "kf49", .value = .{ .string = "\\E[1;3P" } }, .{ .name = "kf5", .value = .{ .string = "\\E[15~" } }, .{ .name = "kf50", .value = .{ .string = "\\E[1;3Q" } }, - .{ .name = "kf51", .value = .{ .string = "\\E[13;3~" } }, + .{ .name = "kf51", .value = .{ .string = "\\E[1;3R" } }, .{ .name = "kf52", .value = .{ .string = "\\E[1;3S" } }, .{ .name = "kf53", .value = .{ .string = "\\E[15;3~" } }, .{ .name = "kf54", .value = .{ .string = "\\E[17;3~" } }, @@ -309,11 +309,11 @@ pub const ghostty: Source = .{ .{ .name = "kf60", .value = .{ .string = "\\E[24;3~" } }, .{ .name = "kf61", .value = .{ .string = "\\E[1;4P" } }, .{ .name = "kf62", .value = .{ .string = "\\E[1;4Q" } }, - .{ .name = "kf63", .value = .{ .string = "\\E[13;4~" } }, + .{ .name = "kf63", .value = .{ .string = "\\E[1;4R" } }, .{ .name = "kf7", .value = .{ .string = "\\E[18~" } }, .{ .name = "kf8", .value = .{ .string = "\\E[19~" } }, .{ .name = "kf9", .value = .{ .string = "\\E[20~" } }, - .{ .name = "khome", .value = .{ .string = "\\E[H" } }, + .{ .name = "khome", .value = .{ .string = "\\EOH" } }, .{ .name = "kich1", .value = .{ .string = "\\E[2~" } }, .{ .name = "kind", .value = .{ .string = "\\E[1;2B" } }, .{ .name = "kmous", .value = .{ .string = "\\E[M" } }, From 97df179b0466eba8adf46d45d37b3b20dbd85c29 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 15:04:33 -0700 Subject: [PATCH 17/19] terminfo: switch to semicolon SGR 48 to prevent render issues --- src/terminal/Parser.zig | 29 +++++++++++++++++++++++++++++ src/terminal/sgr.zig | 10 ++++++++++ src/terminal/stream.zig | 2 +- src/terminfo/ghostty.zig | 6 ++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 0c3d8ca73..abfbc0efb 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -531,6 +531,35 @@ test "csi: SGR ESC [ 38 : 2 m" { } } +test "csi: SGR ESC [ 48 : 2 m" { + var p = init(); + _ = p.next(0x1B); + for ("[48:2:240:143:104") |c| { + const a = p.next(c); + try testing.expect(a[0] == null); + try testing.expect(a[1] == null); + try testing.expect(a[2] == null); + } + + { + const a = p.next('m'); + try testing.expect(p.state == .ground); + try testing.expect(a[0] == null); + try testing.expect(a[1].? == .csi_dispatch); + try testing.expect(a[2] == null); + + const d = a[1].?.csi_dispatch; + try testing.expect(d.final == 'm'); + try testing.expect(d.sep == .colon); + try testing.expect(d.params.len == 5); + try testing.expectEqual(@as(u16, 48), d.params[0]); + try testing.expectEqual(@as(u16, 2), d.params[1]); + try testing.expectEqual(@as(u16, 240), d.params[2]); + try testing.expectEqual(@as(u16, 143), d.params[3]); + try testing.expectEqual(@as(u16, 104), d.params[4]); + } +} + test "csi: SGR ESC [4:3m colon" { var p = init(); _ = p.next(0x1B); diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index c7fcda035..865518b8e 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -467,6 +467,16 @@ test "sgr: 256 color" { try testing.expect(p.next().? == .@"256_bg"); } +test "sgr: 24-bit bg color" { + { + const v = testParseColon(&[_]u16{ 48, 2, 1, 2, 3 }); + try testing.expect(v == .direct_color_bg); + try testing.expectEqual(@as(u8, 1), v.direct_color_bg.r); + try testing.expectEqual(@as(u8, 2), v.direct_color_bg.g); + try testing.expectEqual(@as(u8, 3), v.direct_color_bg.b); + } +} + test "sgr: underline color" { { const v = testParseColon(&[_]u16{ 58, 2, 1, 2, 3 }); diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index d681992e5..e78b3f457 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -48,7 +48,7 @@ pub fn Stream(comptime Handler: type) type { tracy.value(@intCast(u64, c)); defer tracy.end(); - //log.debug("char: {x}", .{c}); + // log.debug("char: {c}", .{c}); const actions = self.parser.next(c); for (actions) |action_opt| { // if (action_opt) |action| { diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 0dbaf7d31..865210155 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -39,7 +39,7 @@ pub const ghostty: Source = .{ .{ .name = "ccc", .value = .{ .boolean = {} } }, // supports changing the window title. - //.{ .name = "hs", .value = .{ .boolean = {} } }, + .{ .name = "hs", .value = .{ .boolean = {} } }, // terminal has a meta key .{ .name = "km", .value = .{ .boolean = {} } }, @@ -155,7 +155,9 @@ pub const ghostty: Source = .{ .{ .name = "rmxx", .value = .{ .string = "\\E[29m" } }, .{ .name = "setab", .value = .{ .string = "\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" } }, .{ .name = "setaf", .value = .{ .string = "\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" } }, - .{ .name = "setrgbb", .value = .{ .string = "\\E[48:2:%p1%d:%p2%d:%p3%dm" } }, + .{ .name = "setrgbb", .value = .{ .string = "\\E[48;2;%p1%d;%p2%d;%p3%dm" } }, + // This causes weird rendering issues, why? + //.{ .name = "setrgbb", .value = .{ .string = "\\E[48:2:%p1%d:%p2%d:%p3%dm" } }, .{ .name = "setrgbf", .value = .{ .string = "\\E[38:2:%p1%d:%p2%d:%p3%dm" } }, .{ .name = "sgr", .value = .{ .string = "%?%p9%t\\E(0%e\\E(B%;\\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m" } }, .{ .name = "sgr0", .value = .{ .string = "\\E(B\\E[m" } }, From 60d4024d6448fb53ea34c7f7e24d001871a8d157 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 15:16:54 -0700 Subject: [PATCH 18/19] terminal: reset CSI param separator in parser on clear --- src/terminal/Parser.zig | 67 ++++++++++++++++++++++++++++++++++------ src/terminfo/ghostty.zig | 4 +-- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index abfbc0efb..07466e19e 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -382,15 +382,7 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { self.params_idx += 1; } - // We only allow the colon separator for the 'm' command. - switch (self.params_sep) { - .none => {}, - .semicolon => {}, - .colon => if (c != 'm') break :csi_dispatch null, - .mixed => break :csi_dispatch null, - } - - break :csi_dispatch Action{ + const result: Action = .{ .csi_dispatch = .{ .intermediates = self.intermediates[0..self.intermediates_idx], .params = self.params[0..self.params_idx], @@ -398,10 +390,35 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { .sep = switch (self.params_sep) { .none, .semicolon => .semicolon, .colon => .colon, - .mixed => unreachable, + + // This should never happen because of the checks below + // but we have to exhaustively handle the switch. + .mixed => .semicolon, }, }, }; + + // We only allow the colon separator for the 'm' command. + switch (self.params_sep) { + .none => {}, + .semicolon => {}, + .colon => if (c != 'm') { + log.warn( + "CSI colon separator only allowed for 'm' command, got: {}", + .{result}, + ); + break :csi_dispatch null; + }, + .mixed => { + log.warn( + "CSI command had mixed colons and semicolons, got: {}", + .{result}, + ); + break :csi_dispatch null; + }, + } + + break :csi_dispatch result; }, .esc_dispatch => Action{ .esc_dispatch = .{ @@ -418,6 +435,7 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { fn clear(self: *Parser) void { self.intermediates_idx = 0; self.params_idx = 0; + self.params_sep = .none; self.param_acc = 0; self.param_acc_idx = 0; } @@ -531,6 +549,35 @@ test "csi: SGR ESC [ 38 : 2 m" { } } +test "csi: SGR colon followed by semicolon" { + var p = init(); + _ = p.next(0x1B); + for ("[48:2") |c| { + const a = p.next(c); + try testing.expect(a[0] == null); + try testing.expect(a[1] == null); + try testing.expect(a[2] == null); + } + + { + const a = p.next('m'); + try testing.expect(p.state == .ground); + try testing.expect(a[0] == null); + try testing.expect(a[1].? == .csi_dispatch); + try testing.expect(a[2] == null); + } + + _ = p.next(0x1B); + _ = p.next('['); + { + const a = p.next('H'); + try testing.expect(p.state == .ground); + try testing.expect(a[0] == null); + try testing.expect(a[1].? == .csi_dispatch); + try testing.expect(a[2] == null); + } +} + test "csi: SGR ESC [ 48 : 2 m" { var p = init(); _ = p.next(0x1B); diff --git a/src/terminfo/ghostty.zig b/src/terminfo/ghostty.zig index 865210155..2e37bfc64 100644 --- a/src/terminfo/ghostty.zig +++ b/src/terminfo/ghostty.zig @@ -155,9 +155,7 @@ pub const ghostty: Source = .{ .{ .name = "rmxx", .value = .{ .string = "\\E[29m" } }, .{ .name = "setab", .value = .{ .string = "\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" } }, .{ .name = "setaf", .value = .{ .string = "\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" } }, - .{ .name = "setrgbb", .value = .{ .string = "\\E[48;2;%p1%d;%p2%d;%p3%dm" } }, - // This causes weird rendering issues, why? - //.{ .name = "setrgbb", .value = .{ .string = "\\E[48:2:%p1%d:%p2%d:%p3%dm" } }, + .{ .name = "setrgbb", .value = .{ .string = "\\E[48:2:%p1%d:%p2%d:%p3%dm" } }, .{ .name = "setrgbf", .value = .{ .string = "\\E[38:2:%p1%d:%p2%d:%p3%dm" } }, .{ .name = "sgr", .value = .{ .string = "%?%p9%t\\E(0%e\\E(B%;\\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m" } }, .{ .name = "sgr0", .value = .{ .string = "\\E(B\\E[m" } }, From 701cb9f2f5b757c28a193ceaa6bdc388386b05c3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 24 Jun 2023 15:34:17 -0700 Subject: [PATCH 19/19] build: fix race condition in build on creating 'share' dir --- build.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.zig b/build.zig index 36b047756..b846894b7 100644 --- a/build.zig +++ b/build.zig @@ -324,6 +324,10 @@ pub fn build(b: *std.Build) !void { run_step.addFileSourceArg(src_source); _ = run_step.captureStdErr(); // so we don't see stderr + // Depend on the terminfo source install step so that Zig build + // creates the "share" directory for us. + run_step.step.dependOn(&src_install.step); + { const copy_step = RunStep.create(b, "copy terminfo db"); copy_step.addArgs(&.{ "cp", "-R" });