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
......@@ -63,6 +63,14 @@ fn exec(sesh_name: []const u8, cmd: Cmd) !noreturn {
6363 );
6464 _ = cross.c.putenv(session_env.ptr);
6565
66+ if (cross.c.getenv("TERM")) |term_env| {
67+ if (std.mem.eql(u8, std.mem.span(term_env), "dumb")) {
68+ _ = cross.c.putenv(@constCast("TERM=xterm-256color"));
69+ }
70+ } else {
71+ _ = cross.c.putenv(@constCast("TERM=xterm-256color"));
72+ }
73+
6674 const err = lib_posix.execvpeZ(cmd.file, cmd.argv_ptr, std.c.environ);
6775 std.log.err("execvpe failed: cmd={s} err={s}", .{ cmd.file, @errorName(err) });
6876 lib_posix.exit(1);
......@@ -78,8 +86,7 @@ pub const PtyInfo = struct {
7886 ///
7987 /// This is the second fork in the double-fork technique explained in the
8088 /// daemonize() comment.
81-pub fn spawnPty(sesh_name: []const u8, cmd: Cmd) !PtyInfo {
82- const size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
89+pub fn spawnPty(sesh_name: []const u8, cmd: Cmd, size: ipc.Resize) !PtyInfo {
8390 var ws: cross.c.struct_winsize = .{
8491 .ws_row = size.rows,
8592 .ws_col = size.cols,
......@@ -179,6 +186,9 @@ pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo
179186 // becomes the session leader and detaches process from its controlling terminal
180187 _ = try lib_posix.setsid();
181188
189+ // Fetch terminal size before redirecting stdio FDs to /dev/null.
190+ const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
191+
182192 // Redirect stdin/stdout/stderr to /dev/null. The daemon
183193 // communicates via its unix socket, not stdio. Without
184194 // this, any pipe on FDs 0-2 (e.g. from bats' `run`
......@@ -225,5 +235,5 @@ pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo
225235 }
226236 }
227237
228- return spawnPty(sesh_name, cmd);
238+ return spawnPty(sesh_name, cmd, term_size);
229239 }
+13, -0
......@@ -52,6 +52,19 @@ pub fn getTerminalSize(fd: i32) Resize {
5252 if (cross.c.ioctl(fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
5353 return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
5454 }
55+ inline for (.{ lib_posix.STDOUT_FILENO, lib_posix.STDIN_FILENO, lib_posix.STDERR_FILENO }) |fallback_fd| {
56+ if (fallback_fd != fd) {
57+ if (cross.c.ioctl(fallback_fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
58+ return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
59+ }
60+ }
61+ }
62+ if (lib_posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0)) |tty_fd| {
63+ defer lib_posix.close(tty_fd);
64+ if (cross.c.ioctl(tty_fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {
65+ return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel };
66+ }
67+ } else |_| {}
5568 return .{ .rows = 24, .cols = 120 };
5669 }
5770
+3, -0
......@@ -1599,6 +1599,9 @@ fn run(gpa: std.mem.Allocator, io: std.Io, daemon: *Daemon, detached: bool, comm
15991599 };
16001600 defer lib_posix.close(client_sock);
16011601
1602+ const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO);
1603+ ipc.send(client_sock, .Resize, std.mem.asBytes(&term_size)) catch {};
1604+
16021605 var fds = try std.ArrayList(i32).initCapacity(gpa, 1);
16031606 defer fds.deinit(gpa);
16041607 try fds.append(gpa, client_sock);
+12, -0
......@@ -295,3 +295,15 @@ load test_helper
295295 run "$ZMX" print
296296 [ "$status" -ne 0 ]
297297 }
298+
299+@test "run: long command line does not truncate history when creating session" {
300+ local longcmd="echo 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
301+ run "$ZMX" run test-long-cmd -d "$longcmd"
302+ [ "$status" -eq 0 ]
303+ wait_for_session test-long-cmd
304+ wait_for_output test-long-cmd 12345678901234567890
305+ run "$ZMX" history test-long-cmd
306+ [[ "$output" != *"<1234567890"* ]]
307+ [[ "$output" == *"12345678901234567890"* ]]
308+}
309+