From a27e04e8f938be5a6b4c1831d78fea57fae5813f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Jul 2026 07:14:19 -0700 Subject: [PATCH] lib-vt: readers for configured scrollback limits C API callers could configure runtime scrollback limits but could not read them back. Add terminal data keys for the primary screen byte and line configurations. Return GHOSTTY_NO_VALUE for unlimited limits and keep reads stable while an alternate screen is active. Document the configured-value semantics and add focused coverage for defaults, updates, and unlimited values. --- include/ghostty/vt/terminal.h | 25 +++++++++- src/terminal/c/terminal.zig | 92 ++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 3bda9c9fd..d28ba2b50 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -884,8 +884,7 @@ typedef enum GHOSTTY_ENUM_TYPED { * Set the maximum number of physical lines retained in scrollback. * * Lowering the limit immediately removes eligible complete historical - * pages. The effective limit retains the active-area and page-granularity - * minimums. A NULL value pointer removes the line limit. + * pages. A NULL value pointer removes the line limit. * * Input type: size_t* */ @@ -1217,6 +1216,28 @@ typedef enum GHOSTTY_ENUM_TYPED { * Output type: bool * */ GHOSTTY_TERMINAL_DATA_VT_PROCESSING_ERROR = 33, + + /** + * The configured maximum scrollback allocation in bytes. + * + * This always reports the primary screen's configured value, including + * while an alternate screen is active. Returns GHOSTTY_NO_VALUE when the + * configured byte limit is unlimited. + * + * Output type: size_t * + */ + GHOSTTY_TERMINAL_DATA_SCROLLBACK_MAX_BYTES = 34, + + /** + * The configured maximum number of physical scrollback lines. + * + * This always reports the primary screen's configured value, including + * while an alternate screen is active. Returns GHOSTTY_NO_VALUE when the + * configured line limit is unlimited. + * + * Output type: size_t * + */ + GHOSTTY_TERMINAL_DATA_SCROLLBACK_MAX_LINES = 35, GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyTerminalData; diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 4d6d7fea6..c2a1ae333 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -782,6 +782,8 @@ pub const TerminalData = enum(c_int) { selection = 31, viewport_active = 32, vt_processing_error = 33, + scrollback_max_bytes = 34, + scrollback_max_lines = 35, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: TerminalData) type { @@ -799,7 +801,11 @@ pub const TerminalData = enum(c_int) { .scrollbar => TerminalScrollbar, .cursor_style => style_c.Style, .title, .pwd => lib.String, - .total_rows, .scrollback_rows => usize, + .total_rows, + .scrollback_rows, + .scrollback_max_bytes, + .scrollback_max_lines, + => usize, .width_px, .height_px => u32, .color_foreground, .color_background, @@ -935,6 +941,16 @@ fn getTyped( ), .viewport_active => out.* = t.screens.active.pages.viewport == .active, .vt_processing_error => out.* = wrapper.stream.handler.semantic_failure, + .scrollback_max_bytes => { + const max = t.screens.get(.primary).?.pages.limits.bytes.explicit; + if (max == std.math.maxInt(usize)) return .no_value; + out.* = max; + }, + .scrollback_max_lines => { + const max = t.screens.get(.primary).?.pages.limits.lines.explicit; + if (max == std.math.maxInt(usize)) return .no_value; + out.* = max; + }, } return .success; @@ -1807,6 +1823,80 @@ test "get scrollback_rows" { try testing.expectEqual(@as(usize, 2), scrollback); } +test "get configured scrollback limits" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &t, + 80, + 3, + )); + defer free(t); + + var value: usize = undefined; + try testing.expectEqual( + Result.success, + get(t, .scrollback_max_bytes, @ptrCast(&value)), + ); + try testing.expectEqual(@as(usize, 10_000), value); + try testing.expectEqual( + Result.no_value, + get(t, .scrollback_max_lines, @ptrCast(&value)), + ); + + const max_bytes: usize = 0; + const max_lines: usize = 12; + try testing.expectEqual( + Result.success, + set(t, .scrollback_max_bytes, &max_bytes), + ); + try testing.expectEqual( + Result.success, + set(t, .scrollback_max_lines, &max_lines), + ); + try testing.expectEqual( + Result.success, + get(t, .scrollback_max_bytes, @ptrCast(&value)), + ); + try testing.expectEqual(max_bytes, value); + try testing.expectEqual( + Result.success, + get(t, .scrollback_max_lines, @ptrCast(&value)), + ); + try testing.expectEqual(max_lines, value); + + // The configured limits belong to the primary screen and remain + // readable while an alternate screen is active. + vt_write(t, "\x1b[?1049h", 8); + try testing.expectEqual( + Result.success, + get(t, .scrollback_max_bytes, @ptrCast(&value)), + ); + try testing.expectEqual(max_bytes, value); + try testing.expectEqual( + Result.success, + get(t, .scrollback_max_lines, @ptrCast(&value)), + ); + try testing.expectEqual(max_lines, value); + + try testing.expectEqual( + Result.success, + set(t, .scrollback_max_bytes, null), + ); + try testing.expectEqual( + Result.success, + set(t, .scrollback_max_lines, null), + ); + try testing.expectEqual( + Result.no_value, + get(t, .scrollback_max_bytes, @ptrCast(&value)), + ); + try testing.expectEqual( + Result.no_value, + get(t, .scrollback_max_lines, @ptrCast(&value)), + ); +} + test "get invalid" { var t: Terminal = null; try testing.expectEqual(Result.success, new(