Commit 92f2425
Eric Bower
·
2026-08-11 11:49:22 -0400 EDT
parent 347fbf4
fix(pty): preserve terminal width and prevent readline truncation in run tasks - Fetch terminal size in daemonize() before stdio redirection to /dev/null - Send .Resize before .Run in zmx run to match client terminal size - Default TERM to xterm-256color when unset/dumb to prevent readline horizontal scroll artifacts
4 files changed,
+41,
-3
+13,
-3
1@@ -63,6 +63,14 @@ fn exec(sesh_name: []const u8, cmd: Cmd) !noreturn {
2 );
3 _ = cross.c.putenv(session_env.ptr);
4
5+ if (cross.c.getenv("TERM")) |term_env| {
6+ if (std.mem.eql(u8, std.mem.span(term_env), "dumb")) {
7+ _ = cross.c.putenv(@constCast("TERM=xterm-256color"));
8+ }
9+ } else {
10+ _ = cross.c.putenv(@constCast("TERM=xterm-256color"));
11+ }
12+
13 const err = lib_posix.execvpeZ(cmd.file, cmd.argv_ptr, std.c.environ);
14 std.log.err("execvpe failed: cmd={s} err={s}", .{ cmd.file, @errorName(err) });
15 lib_posix.exit(1);
16@@ -78,8 +86,7 @@ pub const PtyInfo = struct {
17 ///
18 /// This is the second fork in the double-fork technique explained in the
19 /// daemonize() comment.
20-pub fn spawnPty(sesh_name: []const u8, cmd: Cmd) !PtyInfo {
21- const size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
22+pub fn spawnPty(sesh_name: []const u8, cmd: Cmd, size: ipc.Resize) !PtyInfo {
23 var ws: cross.c.struct_winsize = .{
24 .ws_row = size.rows,
25 .ws_col = size.cols,
26@@ -179,6 +186,9 @@ pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo
27 // becomes the session leader and detaches process from its controlling terminal
28 _ = try lib_posix.setsid();
29
30+ // Fetch terminal size before redirecting stdio FDs to /dev/null.
31+ const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
32+
33 // Redirect stdin/stdout/stderr to /dev/null. The daemon
34 // communicates via its unix socket, not stdio. Without
35 // this, any pipe on FDs 0-2 (e.g. from bats' `run`
36@@ -225,5 +235,5 @@ pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo
37 }
38 }
39
40- return spawnPty(sesh_name, cmd);
41+ return spawnPty(sesh_name, cmd, term_size);
42 }
+13,
-0
1@@ -52,6 +52,19 @@ pub fn getTerminalSize(fd: i32) Resize {
2 if (cross.c.ioctl(fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
3 return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
4 }
5+ inline for (.{ lib_posix.STDOUT_FILENO, lib_posix.STDIN_FILENO, lib_posix.STDERR_FILENO }) |fallback_fd| {
6+ if (fallback_fd != fd) {
7+ if (cross.c.ioctl(fallback_fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
8+ return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
9+ }
10+ }
11+ }
12+ if (lib_posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0)) |tty_fd| {
13+ defer lib_posix.close(tty_fd);
14+ if (cross.c.ioctl(tty_fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
15+ return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
16+ }
17+ } else |_| {}
18 return .{ .rows = 24, .cols = 120 };
19 }
20
+3,
-0
1@@ -1599,6 +1599,9 @@ fn run(gpa: std.mem.Allocator, io: std.Io, daemon: *Daemon, detached: bool, comm
2 };
3 defer lib_posix.close(client_sock);
4
5+ const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
6+ ipc.send(client_sock, .Resize, std.mem.asBytes(&term_size)) catch {};
7+
8 var fds = try std.ArrayList(i32).initCapacity(gpa, 1);
9 defer fds.deinit(gpa);
10 try fds.append(gpa, client_sock);
+12,
-0
1@@ -295,3 +295,15 @@ load test_helper
2 run "$ZMX" print
3 [ "$status" -ne 0 ]
4 }
5+
6+@test "run: long command line does not truncate history when creating session" {
7+ local longcmd="echo 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
8+ run "$ZMX" run test-long-cmd -d "$longcmd"
9+ [ "$status" -eq 0 ]
10+ wait_for_session test-long-cmd
11+ wait_for_output test-long-cmd 12345678901234567890
12+ run "$ZMX" history test-long-cmd
13+ [[ "$output" != *"<1234567890"* ]]
14+ [[ "$output" == *"12345678901234567890"* ]]
15+}
16+