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 { | |
| 63 | 63 | ); | |
| 64 | 64 | _ = cross.c.putenv(session_env.ptr); | |
| 65 | 65 | ||
| 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 | + | ||
| 66 | 74 | const err = lib_posix.execvpeZ(cmd.file, cmd.argv_ptr, std.c.environ); | |
| 67 | 75 | std.log.err("execvpe failed: cmd={s} err={s}", .{ cmd.file, @errorName(err) }); | |
| 68 | 76 | lib_posix.exit(1); |
| ... | ... | @@ -78,8 +86,7 @@ pub const PtyInfo = struct { | |
| 78 | 86 | /// | |
| 79 | 87 | /// This is the second fork in the double-fork technique explained in the | |
| 80 | 88 | /// 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 { | |
| 83 | 90 | var ws: cross.c.struct_winsize = .{ | |
| 84 | 91 | .ws_row = size.rows, | |
| 85 | 92 | .ws_col = size.cols, |
| ... | ... | @@ -179,6 +186,9 @@ pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo | |
| 179 | 186 | // becomes the session leader and detaches process from its controlling terminal | |
| 180 | 187 | _ = try lib_posix.setsid(); | |
| 181 | 188 | ||
| 189 | + | // Fetch terminal size before redirecting stdio FDs to /dev/null. | |
| 190 | + | const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO); | |
| 191 | + | ||
| 182 | 192 | // Redirect stdin/stdout/stderr to /dev/null. The daemon | |
| 183 | 193 | // communicates via its unix socket, not stdio. Without | |
| 184 | 194 | // 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 | |
| 225 | 235 | } | |
| 226 | 236 | } | |
| 227 | 237 | ||
| 228 | - | return spawnPty(sesh_name, cmd); | |
| 238 | + | return spawnPty(sesh_name, cmd, term_size); | |
| 229 | 239 | } |
+13,
-0
| ... | ... | @@ -52,6 +52,19 @@ pub fn getTerminalSize(fd: i32) Resize { | |
| 52 | 52 | if (cross.c.ioctl(fd, cross.c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) { | |
| 53 | 53 | return .{ .rows = ws.ws_row, .cols = ws.ws_col, .xpixel = ws.ws_xpixel, .ypixel = ws.ws_ypixel }; | |
| 54 | 54 | } | |
| 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 |_| {} | |
| 55 | 68 | return .{ .rows = 24, .cols = 120 }; | |
| 56 | 69 | } | |
| 57 | 70 |
+3,
-0
| ... | ... | @@ -1599,6 +1599,9 @@ fn run(gpa: std.mem.Allocator, io: std.Io, daemon: *Daemon, detached: bool, comm | |
| 1599 | 1599 | }; | |
| 1600 | 1600 | defer lib_posix.close(client_sock); | |
| 1601 | 1601 | ||
| 1602 | + | const term_size = ipc.getTerminalSize(lib_posix.STDOUT_FILENO); | |
| 1603 | + | ipc.send(client_sock, .Resize, std.mem.asBytes(&term_size)) catch {}; | |
| 1604 | + | ||
| 1602 | 1605 | var fds = try std.ArrayList(i32).initCapacity(gpa, 1); | |
| 1603 | 1606 | defer fds.deinit(gpa); | |
| 1604 | 1607 | try fds.append(gpa, client_sock); |
+12,
-0
| ... | ... | @@ -295,3 +295,15 @@ load test_helper | |
| 295 | 295 | run "$ZMX" print | |
| 296 | 296 | [ "$status" -ne 0 ] | |
| 297 | 297 | } | |
| 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 | + |