Commit 0f50c39

chamard  ·  2026-09-05 09:01:37 -0400 EDT
parent 5afa7bc
fix: let line limit control scrollback retention (#254)
1 files changed,  +35, -5
+35, -5
......@@ -214,6 +214,15 @@ pub fn clientLoop(client_sock_fd: i32, env_str: []const u8) !ClientResult {
214214 }
215215 }
216216
217+fn initTerminal(gpa: std.mem.Allocator, io: std.Io, size: ipc.Resize, cfg: *const Cfg) !ghostty_vt.Terminal {
218+ return ghostty_vt.Terminal.init(io, gpa, .{
219+ .cols = size.cols,
220+ .rows = size.rows,
221+ .max_scrollback_lines = cfg.max_scrollback_lines,
222+ .max_scrollback_bytes = null, // Let the line limit control scrollback.
223+ });
224+}
225+
217226 /// dameonLoop is what the daemon runs to send and receive ipc commands from its corresponding
218227 /// clients. It uses poll() as its non-blocking mechanism.
219228 fn daemonLoop(daemon: *Daemon, gpa: std.mem.Allocator, io: std.Io, server_sock_fd: lib_posix.socket_t, pty_fd: i32) !void {
......@@ -225,11 +234,7 @@ fn daemonLoop(daemon: *Daemon, gpa: std.mem.Allocator, io: std.Io, server_sock_f
225234 defer poll_fds.deinit(gpa);
226235
227236 const init_size = ipc.getTerminalSize(pty_fd);
228- var term = try ghostty_vt.Terminal.init(io, gpa, .{
229- .cols = init_size.cols,
230- .rows = init_size.rows,
231- .max_scrollback_lines = daemon.cfg.max_scrollback_lines,
232- });
237+ var term = try initTerminal(gpa, io, init_size, daemon.cfg);
233238 defer term.deinit(gpa);
234239 var vt_stream = term.vtStream();
235240 defer vt_stream.deinit();
......@@ -1354,6 +1359,31 @@ pub const Daemon = struct {
13541359 }
13551360 };
13561361
1362+test "terminal retains the configured scrollback without the default byte cap" {
1363+ const alloc = std.testing.allocator;
1364+ const cfg = Cfg{ .socket_dir = "", .log_dir = "" };
1365+ var term = try initTerminal(alloc, std.testing.io, .{ .cols = 80, .rows = 24 }, &cfg);
1366+ defer term.deinit(alloc);
1367+ var stream = term.vtStream();
1368+ defer stream.deinit();
1369+
1370+ stream.nextSlice("first line\r\n");
1371+ for (1..cfg.max_scrollback_lines) |_| stream.nextSlice("more output\r\n");
1372+
1373+ const history = util.serializeTerminal(alloc, &term, .plain) orelse return error.TestUnexpectedNull;
1374+ defer alloc.free(history);
1375+ try std.testing.expect(std.mem.startsWith(u8, history, "first line\n"));
1376+
1377+ // Exceed the limit comfortably because Ghostty prunes whole pages.
1378+ for (0..cfg.max_scrollback_lines) |_| stream.nextSlice("more output\r\n");
1379+ stream.nextSlice("latest line\r\n");
1380+
1381+ const pruned_history = util.serializeTerminal(alloc, &term, .plain) orelse return error.TestUnexpectedNull;
1382+ defer alloc.free(pruned_history);
1383+ try std.testing.expect(std.mem.indexOf(u8, pruned_history, "first line") == null);
1384+ try std.testing.expect(std.mem.indexOf(u8, pruned_history, "latest line") != null);
1385+}
1386+
13571387 fn testDaemon() Daemon {
13581388 return .{
13591389 .cfg = undefined,