diff --git a/src/benchmark/ScrollbackCompression.zig b/src/benchmark/ScrollbackCompression.zig index 3968427b4..d971c551d 100644 --- a/src/benchmark/ScrollbackCompression.zig +++ b/src/benchmark/ScrollbackCompression.zig @@ -138,7 +138,7 @@ pub fn create( .terminal = try .init(global.io(), alloc, .{ .rows = opts.@"terminal-rows", .cols = opts.@"terminal-cols", - .max_scrollback = opts.@"max-scrollback", + .max_scrollback_bytes = opts.@"max-scrollback", }), }; return ptr; diff --git a/src/config/Config.zig b/src/config/Config.zig index 90ccfc319..b47d287dc 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -41,6 +41,7 @@ const ClipboardCodepointMap = @import("ClipboardCodepointMap.zig"); const KeyRemapSet = @import("../input/key_mods.zig").RemapSet; pub const WindowPaddingBalance = @import("../renderer/size.zig").PaddingBalance; const string = @import("string.zig"); +const Limit = @import("limit.zig").Limit; // We do this instead of importing all of terminal/main.zig to // limit the dependency graph. This is important because some things @@ -95,6 +96,10 @@ pub const compatibility = std.StaticStringMap( // Ghostty 1.3 rename the "window" option to "new-window". // See: https://github.com/ghostty-org/ghostty/pull/9764 .{ "macos-dock-drop-behavior", compatMacOSDockDropBehavior }, + + // Ghostty 1.4 renamed `scrollback-limit` to `scrollback-limit-bytes` + // when `scrollback-limit-lines` was added so the units are explicit. + .{ "scrollback-limit", cli.compatibilityRenamed(Config, "scrollback-limit-bytes") }, }); /// Set Ghostty's graphical user interface language to a language other than the @@ -1375,11 +1380,32 @@ input: RepeatableReadableIO = .{}, /// /// This size is per terminal surface, not for the entire application. /// -/// It is not currently possible to set an unlimited scrollback buffer. -/// This is a future planned feature. +/// A separate maximum can be set with `scrollback-limit-lines`; if both limits +/// are set, then the first one reached will determine when scrollback is +/// removed. +/// +/// The default is 50 MB. Set this to `unlimited` to remove the byte limit. /// /// This can be changed at runtime but will only affect new terminal surfaces. -@"scrollback-limit": usize = 50_000_000, // 50MB +@"scrollback-limit-bytes": Limit(usize, 50_000_000) = .default, + +/// The maximum number of lines of scrollback to retain. This excludes +/// the active screen. Soft-wrapped lines count as multiple lines. +/// +/// This limit is an estimate. Internally, Ghostty will only trim lines +/// up to the minimum allocation unit that is used internally (called a +/// "page"). The size of a page depends on how many styles, graphemes, etc. +/// take up the screen. In practice, this can be anywhere from a handful to +/// a couple hundred lines. Importantly, memory is capped either way. +/// This means that the actual limited lines will likely be slightly +/// higher in practice. +/// +/// The default is `unlimited`. A separate maximum can be set with +/// `scrollback-limit-bytes`; if both limits are set, then the first one reached +/// will determine when scrollback is removed. +/// +/// This can be changed at runtime but will only affect new terminal surfaces. +@"scrollback-limit-lines": Limit(usize, std.math.maxInt(usize)) = .default, /// Whether to compress scrollback pages while the terminal is idle. /// @@ -10825,6 +10851,94 @@ test "theme specifying light/dark sets theme usage in conditional state" { } } +test "scrollback limits" { + const testing = std.testing; + const alloc = testing.allocator; + + var cfg = try Config.default(alloc); + defer cfg.deinit(); + try testing.expectEqual( + @as(usize, 50_000_000), + cfg.@"scrollback-limit-bytes".value, + ); + try testing.expectEqual( + std.math.maxInt(usize), + cfg.@"scrollback-limit-lines".value, + ); + + var it: TestIterator = .{ .data = &.{ + "--scrollback-limit-bytes=1234", + "--scrollback-limit-lines=567", + } }; + try cfg.loadIter(alloc, &it); + + try testing.expectEqual( + @as(usize, 1234), + cfg.@"scrollback-limit-bytes".value, + ); + try testing.expectEqual( + @as(usize, 567), + cfg.@"scrollback-limit-lines".value, + ); + + var unlimited_it: TestIterator = .{ .data = &.{ + "--scrollback-limit-bytes=unlimited", + "--scrollback-limit-lines=unlimited", + } }; + try cfg.loadIter(alloc, &unlimited_it); + + try testing.expectEqual( + std.math.maxInt(usize), + cfg.@"scrollback-limit-bytes".value, + ); + try testing.expectEqual( + std.math.maxInt(usize), + cfg.@"scrollback-limit-lines".value, + ); + + var reset_it: TestIterator = .{ .data = &.{ + "--scrollback-limit-bytes=", + "--scrollback-limit-lines=", + } }; + try cfg.loadIter(alloc, &reset_it); + + try testing.expectEqual( + @as(usize, 50_000_000), + cfg.@"scrollback-limit-bytes".value, + ); + try testing.expectEqual( + std.math.maxInt(usize), + cfg.@"scrollback-limit-lines".value, + ); +} + +test "compatibility: scrollback-limit renamed to bytes" { + const testing = std.testing; + const alloc = testing.allocator; + + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--scrollback-limit=1234", + } }; + try cfg.loadIter(alloc, &it); + + try testing.expectEqual( + @as(usize, 1234), + cfg.@"scrollback-limit-bytes".value, + ); + + var unlimited_it: TestIterator = .{ .data = &.{ + "--scrollback-limit=unlimited", + } }; + try cfg.loadIter(alloc, &unlimited_it); + + try testing.expectEqual( + std.math.maxInt(usize), + cfg.@"scrollback-limit-bytes".value, + ); +} + test "compatibility: gtk-single-instance desktop" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/config/limit.zig b/src/config/limit.zig new file mode 100644 index 000000000..ace78676a --- /dev/null +++ b/src/config/limit.zig @@ -0,0 +1,115 @@ +const std = @import("std"); +const formatterpkg = @import("formatter.zig"); + +/// A configurable integer limit with a declared default and support for the +/// special value `unlimited`. +/// +/// Unlimited is stored as the maximum value of the integer type so the value +/// remains directly comparable without optional unwrapping. +pub fn Limit(comptime T: type, comptime default_value: T) type { + comptime std.debug.assert(@typeInfo(T) == .int); + + return struct { + const Self = @This(); + + value: T = default_value, + + /// The limit initialized with its declared default value. + pub const default: Self = .{}; + + /// Parses an integer limit or the special value "unlimited". + pub fn parseCLI(input_: ?[]const u8) !Self { + const input = input_ orelse return error.ValueRequired; + if (std.mem.eql(u8, input, "unlimited")) { + return .{ .value = std.math.maxInt(T) }; + } + + return .{ + .value = std.fmt.parseInt(T, input, 0) catch + return error.InvalidValue, + }; + } + + /// Formats the maximum integer value as "unlimited" and all other + /// values as integers. + pub fn formatEntry( + self: Self, + formatter: formatterpkg.EntryFormatter, + ) !void { + if (self.value == std.math.maxInt(T)) { + try formatter.formatEntry([]const u8, "unlimited"); + } else { + try formatter.formatEntry(T, self.value); + } + } + + /// Returns the configured value, or null for the unlimited sentinel. + pub fn optional(self: Self) ?T { + if (self.value == std.math.maxInt(T)) return null; + return self.value; + } + + /// Returns an independent copy of this value for Config cloning. + pub fn clone( + self: Self, + _: std.mem.Allocator, + ) std.mem.Allocator.Error!Self { + return self; + } + }; +} + +test "Limit default and parsing" { + const testing = std.testing; + const TestLimit = Limit(usize, 42); + + try testing.expectEqual(@as(usize, 42), TestLimit.default.value); + try testing.expectEqual( + @as(usize, 123), + (try TestLimit.parseCLI("123")).value, + ); + try testing.expectEqual( + std.math.maxInt(usize), + (try TestLimit.parseCLI("unlimited")).value, + ); + try testing.expectError(error.InvalidValue, TestLimit.parseCLI("invalid")); +} + +test "Limit optional" { + const testing = std.testing; + const TestLimit = Limit(u16, 42); + + try testing.expectEqual(@as(?u16, 42), (TestLimit{}).optional()); + try testing.expectEqual( + @as(?u16, null), + (TestLimit{ .value = std.math.maxInt(u16) }).optional(), + ); +} + +test "Limit formatting" { + const testing = std.testing; + const TestLimit = Limit(usize, 42); + + { + var buf: std.Io.Writer.Allocating = .init(testing.allocator); + defer buf.deinit(); + + try (TestLimit{ .value = 123 }).formatEntry( + formatterpkg.entryFormatter("limit", &buf.writer), + ); + try testing.expectEqualStrings("limit = 123\n", buf.written()); + } + + { + var buf: std.Io.Writer.Allocating = .init(testing.allocator); + defer buf.deinit(); + + try (TestLimit{ .value = std.math.maxInt(usize) }).formatEntry( + formatterpkg.entryFormatter("limit", &buf.writer), + ); + try testing.expectEqualStrings( + "limit = unlimited\n", + buf.written(), + ); + } +} diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 79e46e5c8..db5104c16 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -411,6 +411,14 @@ explicit_max_size: usize, /// and at least two pages for our algorithms. min_max_size: usize, +/// Maximum number of physical rows retained as scrollback. This excludes the +/// active area and is raised to `min_max_lines` when necessary. +explicit_max_lines: usize, + +/// Minimum line limit required to retain one standard page worth of +/// scrollback at the current column count. +min_max_lines: usize, + /// The total number of rows represented by this PageList. This is used /// specifically for scrollbar information so we can have the total size. total_rows: usize, @@ -531,6 +539,13 @@ fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) usize { return PagePool.item_size * pages; } +/// Returns the minimum line limit for a given column count. Line limits are +/// page-granular, so we always permit at least one standard page worth of +/// scrollback rows. +fn minMaxLines(cols: size.CellCountInt) usize { + return initialCapacity(cols).rows; +} + /// Calculates the initial capacity for a new page for a given column /// count. This will attempt to fit within std_size at all times so we /// can use our memory pool, but if cols is too big, this will return a @@ -597,29 +612,53 @@ const init_tw = tripwire.module(enum { viewport_pin_track, }, init); +pub const Options = struct { + /// The initial active-area size. This can be resized with `resize`. + cols: size.CellCountInt, + rows: size.CellCountInt, + + /// The maximum number of bytes allocated for pages. The effective limit + /// is raised when necessary to hold the active area. Null is unlimited. + max_size: ?usize = null, + + /// The maximum number of scrollback rows, excluding the active + /// area. Rows are physical (viewed) rows, so a wrapped row counts as + /// multiple rows. Max line pruning only happens at page-boundaries + /// (the minimum internal allocation size) so in practice the max lines + /// is always slightly larger than configured. + /// + /// Null is unlimited. + max_lines: ?usize = null, +}; + /// Initialize the page. The top of the first page in the list is always the /// top of the active area of the screen (important knowledge for quickly /// setting up cursors in Screen). /// -/// max_size is the maximum number of bytes that will be allocated for +/// `max_size` is the maximum number of bytes that will be allocated for /// pages. If this is smaller than the bytes required to show the viewport /// then max_size will be ignored and the viewport will be shown, but no /// scrollback will be created. max_size is always rounded down to the nearest /// terminal page size (not virtual memory page), otherwise we would always /// slightly exceed max_size in the limits. /// -/// If max_size is null then there is no defined limit and the screen will -/// grow forever. In reality, the limit is set to the byte limit that your -/// computer can address in memory. If you somehow require more than that -/// (due to disk paging) then please contribute that yourself and perhaps -/// search deep within yourself to find out why you need that. +/// `max_lines` is the maximum number of physical rows retained as scrollback, +/// excluding the active area. It is a page-granular heuristic: at least one +/// standard page worth of rows is permitted and only complete historical +/// pages are removed. +/// +/// If either limit is null then that dimension has no defined limit and the +/// screen will grow forever. In reality, the limit is set to the amount your +/// computer can address in memory. If you somehow require more than that (due +/// to disk paging) then please contribute that yourself and perhaps search +/// deep within yourself to find out why you need that. pub fn init( alloc: Allocator, - cols: size.CellCountInt, - rows: size.CellCountInt, - max_size: ?usize, + opts: Options, ) Allocator.Error!PageList { const tw = init_tw; + const cols = opts.cols; + const rows = opts.rows; // The screen starts with a single page that is the entire viewport, // and we'll split it thereafter if it gets too large and add more as @@ -663,8 +702,10 @@ pub fn init( .page_serial = page_serial, .page_serial_epoch = 0, .page_size = page_size, - .explicit_max_size = max_size orelse std.math.maxInt(usize), + .explicit_max_size = opts.max_size orelse std.math.maxInt(usize), .min_max_size = min_max_size, + .explicit_max_lines = opts.max_lines orelse std.math.maxInt(usize), + .min_max_lines = minMaxLines(cols), .total_rows = rows, .tracked_pins = tracked_pins, .viewport = .{ .active = {} }, @@ -794,6 +835,7 @@ pub inline fn pauseIntegrityChecks(self: *PageList, pause: bool) void { } const IntegrityError = error{ + MaxLinesExceeded, PageSerialInvalid, TotalRowsMismatch, TrackedPinInvalid, @@ -838,6 +880,21 @@ fn verifyIntegrity(self: *const PageList) IntegrityError!void { return IntegrityError.TotalRowsMismatch; } + // A line limit may only be exceeded when the oldest page also contains + // active rows. Complete historical pages are always eligible for pruning. + if (self.total_rows > self.rows) { + const history_rows = self.total_rows - self.rows; + if (history_rows > self.maxLines() and + self.pages.first.? != self.getTopLeft(.active).node) + { + log.warn( + "PageList integrity violation: max lines exceeded history={} max={}", + .{ history_rows, self.maxLines() }, + ); + return IntegrityError.MaxLinesExceeded; + } + } + // Verify that all our tracked pins point to valid pages. for (self.tracked_pins.keys()) |p| { if (!self.pinIsValid(p.*)) return error.TrackedPinInvalid; @@ -1181,6 +1238,8 @@ pub fn clone( .page_size = page_size, .explicit_max_size = self.explicit_max_size, .min_max_size = self.min_max_size, + .explicit_max_lines = self.explicit_max_lines, + .min_max_lines = self.min_max_lines, .cols = self.cols, .rows = self.rows, .total_rows = total_rows, @@ -1210,6 +1269,9 @@ pub fn clone( result.total_rows = result.rows; } + // A clone can copy more history than its inherited line limit. + result.enforceMaxLines(); + result.assertIntegrity(); return result; } @@ -1262,7 +1324,13 @@ pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void { // its too easy to get the logic wrong in here. self.viewport_pin_row_offset = null; - if (!opts.reflow) return try self.resizeWithoutReflow(opts); + if (!opts.reflow) { + try self.resizeWithoutReflow(opts); + // Shrinking the active row count turns former active rows into + // scrollback even without reflow, which can cross the line limit. + self.enforceMaxLines(); + return; + } // Recalculate our minimum max size. This allows grow to work properly // when increasing beyond our initial minimum max size or explicit max @@ -1274,6 +1342,11 @@ pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void { ); errdefer self.min_max_size = old_min_max_size; + // Recalculate our minimum max lines for the same reasons as above. + const old_min_max_lines = self.min_max_lines; + self.min_max_lines = minMaxLines(opts.cols orelse self.cols); + errdefer self.min_max_lines = old_min_max_lines; + // On reflow, the main thing that causes reflow is column changes. If // only rows change, reflow is impossible. So we change our behavior based // on the change of columns. @@ -1309,6 +1382,11 @@ pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void { }, .active, .top => {}, } + + // Column reflow can change the physical history row count, and a row + // resize can move the active boundary. Both may expose whole old pages + // that are now eligible for line-limit pruning. + self.enforceMaxLines(); } /// Resize the pagelist with reflow by adding or removing columns. @@ -2343,6 +2421,12 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) Allocator.Error!void { opts.rows orelse self.rows, ) else old_min_max_size; errdefer self.min_max_size = old_min_max_size; + const old_min_max_lines = self.min_max_lines; + self.min_max_lines = if (!opts.reflow) + minMaxLines(opts.cols orelse self.cols) + else + old_min_max_lines; + errdefer self.min_max_lines = old_min_max_lines; // Important! We have to do cols first because cols may cause us to // destroy pages if we're increasing cols which will free up page_size @@ -3470,6 +3554,11 @@ fn fixupViewport( self: *PageList, removed: usize, ) void { + // Page removal can mark every pin on the removed page as garbage. The + // viewport pin is an internal navigation anchor that is always remapped, + // so it remains valid after the removal. + self.viewport_pin.garbage = false; + switch (self.viewport) { .active => {}, @@ -3506,11 +3595,70 @@ pub fn maxSize(self: *const PageList) usize { return @max(self.explicit_max_size, self.min_max_size); } +/// Returns the effective scrollback row limit. At least one standard page +/// worth of rows is permitted so enforcement never has to split a page. +/// +/// Like `maxSize`, this is a heuristic. An unusually large page which contains +/// both history and active rows can temporarily exceed this value because only +/// complete historical pages are pruned. +fn maxLines(self: *const PageList) usize { + return @max(self.explicit_max_lines, self.min_max_lines); +} + +/// Prune complete historical pages until the line limit is satisfied or the +/// oldest remaining page overlaps the active area. +fn enforceMaxLines(self: *PageList) void { + if (self.explicit_max_lines == std.math.maxInt(usize)) return; + + // A partially constructed clone can temporarily contain fewer rows than + // its active area. It has no scrollback to prune. + if (self.total_rows <= self.rows) return; + + // If we're lower then the limit we're good. + const max_lines = self.maxLines(); + if (self.total_rows - self.rows <= max_lines) return; + + // Removing pages changes compression. + self.page_compression.markActivity(); + + // Accumulate the row delta so viewport offsets are fixed up once + // after all eligible pages have been removed. + var removed: usize = 0; + while (self.total_rows - self.rows > max_lines) { + const first = self.pages.first.?; + + // The page containing the active top may also contain history. Keep + // that boundary page whole even if its history exceeds the heuristic. + if (first == self.getTopLeft(.active).node) break; + + const first_rows = first.rows(); + + // Automatic pruning invalidates the content represented by pins in + // the removed page. erasePage remaps them to the next page below but + // only the line-limit caller knows that their original content is + // gone, so mark them as garbage here. + for (self.tracked_pins.keys()) |p| { + if (p.node == first) p.garbage = true; + } + + // erasePage updates the list, pin targets, and byte accounting. + // Row accounting belongs to the caller because erasePage is also used + // by paths that already adjusted total_rows. + self.erasePage(first); + self.total_rows -= first_rows; + removed += first_rows; + } + + // Reconcile viewport mode and cached row offsets with the combined prefix + // removal only after every page and pin points into the final list. + if (removed > 0) self.fixupViewport(removed); +} + /// Grow the active area by exactly one row. /// /// This may allocate, but also may not if our current page has more /// capacity we can use. This will prune scrollback if necessary to -/// adhere to max_size. +/// adhere to max_size and max_lines. /// /// This returns the newly allocated page node if there is one. pub fn grow(self: *PageList) Allocator.Error!?*List.Node { @@ -3529,6 +3677,9 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node { // Increase our total rows by one self.total_rows += 1; + // Growing inside the last page moves the active boundary without + // allocating; that alone can make the first page wholly historical. + self.enforceMaxLines(); return null; } @@ -3630,6 +3781,10 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node { // we're reusing an existing page so nothing has changed. page.assertIntegrity(); + + // Byte-limit recycling may leave history above the independent line + // limit, so enforce it after the recycled page becomes the new tail. + self.enforceMaxLines(); return first; } @@ -3648,6 +3803,9 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node { // Record the increased row count self.total_rows += 1; + // Appending a page can cross the line limit and can make the oldest + // active-boundary page wholly historical. + self.enforceMaxLines(); return next_node; } @@ -6600,7 +6758,7 @@ pub const Pin = struct { }; fn mixedWidthPinListForTest(alloc: Allocator) !PageList { - var result = try init(alloc, 2, 1, null); + var result = try init(alloc, .{ .cols = 2, .rows = 1 }); errdefer result.deinit(); // This deliberately constructs a layout that normal PageList operations @@ -6776,7 +6934,7 @@ fn expectLivePageSerialsValidForTest(self: *const PageList) !void { test "PageList Pin rightWrap exact row multiple" { const testing = std.testing; - var s = try init(testing.allocator, 10, 3, null); + var s = try init(testing.allocator, .{ .cols = 10, .rows = 3 }); defer s.deinit(); const start = s.pin(.{ .active = .{ .x = 5, .y = 0 } }).?; @@ -6792,7 +6950,7 @@ test "PageList Pin rightWrap exact row multiple" { test "PageList Pin leftWrap exact row multiple" { const testing = std.testing; - var s = try init(testing.allocator, 10, 3, null); + var s = try init(testing.allocator, .{ .cols = 10, .rows = 3 }); defer s.deinit(); const start = s.pin(.{ .active = .{ .x = 5, .y = 2 } }).?; @@ -6808,7 +6966,7 @@ test "PageList Pin leftWrap exact row multiple" { test "PageList Pin rightWrap maximum distance" { const testing = std.testing; - var s = try init(testing.allocator, 1, 3, null); + var s = try init(testing.allocator, .{ .cols = 1, .rows = 3 }); defer s.deinit(); const start = s.pin(.{ .active = .{ .y = 0 } }).?; @@ -6818,7 +6976,7 @@ test "PageList Pin rightWrap maximum distance" { test "PageList Pin leftWrap maximum distance" { const testing = std.testing; - var s = try init(testing.allocator, 1, 3, null); + var s = try init(testing.allocator, .{ .cols = 1, .rows = 3 }); defer s.deinit(); const start = s.pin(.{ .active = .{ .y = 2 } }).?; @@ -6828,7 +6986,7 @@ test "PageList Pin leftWrap maximum distance" { test "PageList incremental compression skips visible history" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(3); @@ -6881,7 +7039,7 @@ test "PageList incremental compression skips visible history" { test "PageList owns incremental compression state" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const state: IncrementalCompressionState = .{ @@ -6955,7 +7113,7 @@ test "PageList owns incremental compression state" { test "PageList replacements preserve compression continuation and mark activity" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const state: IncrementalCompressionState = .{ @@ -6983,7 +7141,7 @@ test "PageList replacements preserve compression continuation and mark activity" test "PageList incremental compression bounds inspected pages" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(incremental_compression_max_inspected + 1); @@ -7027,7 +7185,7 @@ test "PageList incremental compression bounds inspected pages" { test "PageList incremental compression advances after failure" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(2); @@ -7052,7 +7210,7 @@ test "PageList incremental compression advances after allocation failure" { var failing = testing.FailingAllocator.init(testing.allocator, .{}); const alloc = failing.allocator(); - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(2); const first = s.pages.first.?; @@ -7080,7 +7238,7 @@ test "PageList incremental compression advances after decommit failure" { const tw = compressPage_tw; defer tw.end(.reset) catch unreachable; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(2); @@ -7100,7 +7258,7 @@ test "PageList incremental compression advances after decommit failure" { test "PageList incremental compression restarts after replacement" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(1); @@ -7131,7 +7289,7 @@ test "PageList incremental compression restarts after replacement" { test "PageList incremental compression restarts after reset" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(1); @@ -7150,7 +7308,7 @@ test "PageList incremental compression restarts after reset" { test "PageList incremental compression restarts after active boundary resize" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(1); @@ -7187,12 +7345,11 @@ test "PageList incremental compression restarts after active boundary resize" { test "PageList incremental compression restarts after prune reuse" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 2 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 2 * PagePool.item_size, + }); defer s.deinit(); try s.growColdPagesForTest(1); @@ -7218,12 +7375,11 @@ test "PageList incremental compression restarts after prune reuse" { test "PageList bounded pruning after partial erase preserves live serials" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 2 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 2 * PagePool.item_size, + }); defer s.deinit(); while (s.totalPages() < 2) _ = try s.grow(); @@ -7244,7 +7400,7 @@ test "PageList bounded pruning after partial erase preserves live serials" { test "PageList partial erase restarts compression before continuation" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(incremental_compression_max_inspected + 1); _ = s.compress(.full); @@ -7277,12 +7433,11 @@ test "PageList partial erase restarts compression before continuation" { test "PageList bounded pruning after split invalidation preserves live serials" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 2 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 2 * PagePool.item_size, + }); defer s.deinit(); while (s.totalPages() < 2) _ = try s.grow(); @@ -7306,12 +7461,11 @@ test "PageList bounded pruning after split invalidation preserves live serials" test "PageList repeated bounded pruning after split preserves live serials" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 3 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 3 * PagePool.item_size, + }); defer s.deinit(); const epoch = s.page_serial_epoch; @@ -7342,12 +7496,11 @@ test "PageList repeated bounded pruning after split preserves live serials" { test "PageList bounded pruning after front replacement preserves live serials" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 2 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 2 * PagePool.item_size, + }); defer s.deinit(); while (s.totalPages() < 2) _ = try s.grow(); @@ -7366,12 +7519,11 @@ test "PageList bounded pruning after front replacement preserves live serials" { test "PageList bounded pruning after middle replacement preserves live serials" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 3 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 3 * PagePool.item_size, + }); defer s.deinit(); while (s.totalPages() < 3) _ = try s.grow(); @@ -7392,7 +7544,7 @@ test "PageList bounded pruning after middle replacement preserves live serials" test "PageList incremental compression restarts after earlier replacement" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(3); @@ -7420,7 +7572,7 @@ test "PageList incremental compression restarts after earlier replacement" { test "PageList incremental compression keeps progress after tail growth" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(incremental_compression_max_inspected + 1); _ = s.compress(.full); @@ -7450,7 +7602,7 @@ test "PageList incremental compression keeps progress after tail growth" { test "PageList memory stats do not restore compressed pages" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(2); @@ -7523,7 +7675,7 @@ test "PageList preserved page keeps compressed storage" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const node = s.pages.first.?; @@ -7603,7 +7755,7 @@ test "PageList preserved page keeps compressed storage" { test "PageList memory stats include unused pool backing" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Pool allocation ownership is based on the requested layout fitting in a @@ -7645,7 +7797,7 @@ test "PageList memory stats include unused pool backing" { test "PageList does not compress the mixed history and active page" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // One additional row creates history, but the history and all active rows @@ -7667,7 +7819,7 @@ test "PageList compresses only complete cold history pages" { // More active rows than one page at these dimensions can hold ensures the // active area spans multiple nodes when the pass chooses its boundary. const active_rows = initialCapacity(80).rows + 1; - var s = try init(alloc, 80, active_rows, null); + var s = try init(alloc, .{ .cols = 80, .rows = active_rows }); defer s.deinit(); try s.growColdPagesForTest(2); @@ -7727,7 +7879,7 @@ test "PageList compresses only complete cold history pages" { test "PageList lazily restores compressed history made active by resize" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(1); @@ -7767,11 +7919,11 @@ test "PageList lazily restores compressed history made active by resize" { test "PageList full and incremental compression skip a spanning viewport" { const testing = std.testing; - var full = try init(testing.allocator, 80, 24, null); + var full = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer full.deinit(); try full.growColdPagesForTest(3); - var incremental = try init(testing.allocator, 80, 24, null); + var incremental = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer incremental.deinit(); try incremental.growColdPagesForTest(3); @@ -7823,7 +7975,7 @@ test "PageList full and incremental compression skip a spanning viewport" { test "PageList cold compression continues after an incompressible page" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growColdPagesForTest(2); @@ -7855,7 +8007,7 @@ test "PageList compression restores through page access" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const node = s.pages.first.?; @@ -7914,7 +8066,7 @@ test "PageList compression uses temporary scratch for oversized pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); var node = s.pages.first.?; @@ -7943,7 +8095,7 @@ test "PageList compression leaves incompressible pages resident" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const node = s.pages.first.?; @@ -7963,7 +8115,7 @@ test "PageList compression leaves incompressible pages resident" { test "PageList reset discards malformed compressed data" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const node = s.pages.first.?; @@ -7978,7 +8130,7 @@ test "PageList reset discards malformed compressed data" { test "PageList deinit discards malformed compressed data" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); const node = s.pages.first.?; try testing.expect(s.compressPage(node)); @memset(node.data.compressed.encoded, 0xFF); @@ -7989,12 +8141,11 @@ test "PageList deinit discards malformed compressed data" { test "PageList prune reuses malformed compressed page memory" { const testing = std.testing; - var s = try init( - testing.allocator, - 80, - 24, - 2 * PagePool.item_size, - ); + var s = try init(testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_size = 2 * PagePool.item_size, + }); defer s.deinit(); // Allocate the second page so the first one can be pruned and reused. @@ -8025,7 +8176,7 @@ test "PageList" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expect(s.viewport == .active); try testing.expect(s.pages.first != null); @@ -8062,12 +8213,10 @@ test "PageList init error" { tw.errorAlways(tag, error.OutOfMemory); try std.testing.expectError( error.OutOfMemory, - init( - std.testing.allocator, - 80, - 24, - null, - ), + init(std.testing.allocator, .{ + .cols = 80, + .rows = 24, + }), ); } @@ -8081,12 +8230,10 @@ test "PageList init error" { const cols: size.CellCountInt = if (tag == .page_buf_std) 80 else std_capacity.maxCols().? + 1; try std.testing.expectError( error.OutOfMemory, - init( - std.testing.allocator, - cols, - 24, - null, - ), + init(std.testing.allocator, .{ + .cols = cols, + .rows = 24, + }), ); } @@ -8099,12 +8246,10 @@ test "PageList init error" { tw.errorAfter(tag, error.OutOfMemory, 1); try std.testing.expectError( error.OutOfMemory, - init( - std.testing.allocator, - std_capacity.maxCols().? + 1, - std_capacity.rows + 1, - null, - ), + init(std.testing.allocator, .{ + .cols = std_capacity.maxCols().? + 1, + .rows = std_capacity.rows + 1, + }), ); } } @@ -8125,7 +8270,7 @@ test "PageList init rows across two pages" { }; // Init - var s = try init(alloc, cap.cols, rows, null); + var s = try init(alloc, .{ .cols = cap.cols, .rows = rows }); defer s.deinit(); try testing.expect(s.viewport == .active); try testing.expect(s.pages.first != null); @@ -8149,12 +8294,10 @@ test "PageList init more than max cols" { // Initialize with more columns than we can fit in our standard // capacity. This is going to force us to go to a non-standard page // immediately. - var s = try init( - alloc, - std_capacity.maxCols().? + 1, - 80, - null, - ); + var s = try init(alloc, .{ + .cols = std_capacity.maxCols().? + 1, + .rows = 80, + }); defer s.deinit(); try testing.expect(s.viewport == .active); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -8178,7 +8321,7 @@ test "PageList pointFromPin active no history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); { @@ -8211,7 +8354,7 @@ test "PageList pointFromPin active with history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(30); @@ -8242,7 +8385,7 @@ test "PageList pointFromPin active from prior page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 5 pages. const page = s.pages.last.?.page(); @@ -8284,7 +8427,7 @@ test "PageList pointFromPin traverse pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 2 pages. @@ -8368,7 +8511,7 @@ test "PageList active after grow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -8412,7 +8555,7 @@ test "PageList grow allows exceeding max size for active area" { // Setup our initial page so that we fully take up one page. const cap = try std_capacity.adjust(.{ .cols = 5 }); - var s = try init(alloc, 5, cap.rows, 0); + var s = try init(alloc, .{ .cols = 5, .rows = cap.rows, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -8445,7 +8588,7 @@ test "PageList grow prune required with a single page" { const alloc = testing.allocator; // Need scrollback > 0 to have a scrollbar to test - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // This block is all test setup. There is nothing required about this @@ -8490,7 +8633,7 @@ test "PageList scrollbar with max_size 0 after grow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); // Grow some rows (simulates normal terminal output) @@ -8509,7 +8652,7 @@ test "PageList scroll with max_size 0 no history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); try s.growRows(10); @@ -8531,7 +8674,7 @@ test "PageList scroll top" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8594,7 +8737,7 @@ test "PageList scroll delta row back" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8650,7 +8793,7 @@ test "PageList scroll delta row back overflow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8697,7 +8840,7 @@ test "PageList scroll delta row back overflow" { test "PageList scroll minimum row delta" { const testing = std.testing; - var s = try init(testing.allocator, 10, 3, null); + var s = try init(testing.allocator, .{ .cols = 10, .rows = 3 }); defer s.deinit(); // Create one row of history so scrolling all the way back has an @@ -8712,7 +8855,7 @@ test "PageList scroll delta row forward" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8761,7 +8904,7 @@ test "PageList scroll delta row forward into active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); s.scroll(.{ .delta_row = 2 }); @@ -8785,7 +8928,7 @@ test "PageList scroll delta row back without space preserves active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); s.scroll(.{ .delta_row = -1 }); @@ -8810,7 +8953,7 @@ test "PageList scroll to pin" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8857,7 +9000,7 @@ test "PageList scroll to pin in active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8885,7 +9028,7 @@ test "PageList scroll to pin at top" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8915,7 +9058,7 @@ test "PageList scroll to row 0" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -8964,7 +9107,7 @@ test "PageList scroll to row in scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(20); @@ -9012,7 +9155,7 @@ test "PageList scroll to row in middle" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(50); @@ -9055,7 +9198,7 @@ test "PageList scroll to row at active boundary" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(20); @@ -9094,7 +9237,7 @@ test "PageList scroll to row beyond active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(10); @@ -9121,7 +9264,7 @@ test "PageList scroll to row without scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); s.scroll(.{ .row = 5 }); @@ -9147,7 +9290,7 @@ test "PageList scroll to row then delta" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(30); @@ -9210,7 +9353,7 @@ test "PageList scroll to row with cache fast path down" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(50); @@ -9273,7 +9416,7 @@ test "PageList scroll to row with cache fast path up" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(50); @@ -9336,7 +9479,7 @@ test "PageList scroll clear" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); { @@ -9369,7 +9512,7 @@ test "PageList: jump zero prompts" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, null); + var s = try init(alloc, .{ .cols = 5, .rows = 3 }); defer s.deinit(); try s.growRows(3); try testing.expect(s.pages.first == s.pages.last); @@ -9396,7 +9539,7 @@ test "PageList: jump zero prompts" { test "PageList: jump minimum prompt delta" { const testing = std.testing; - var s = try init(testing.allocator, 10, 3, null); + var s = try init(testing.allocator, .{ .cols = 10, .rows = 3 }); defer s.deinit(); s.scroll(.{ .delta_prompt = std.math.minInt(isize) }); @@ -9407,7 +9550,7 @@ test "Screen: jump back one prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, null); + var s = try init(alloc, .{ .cols = 5, .rows = 3 }); defer s.deinit(); try s.growRows(3); try testing.expect(s.pages.first == s.pages.last); @@ -9476,7 +9619,7 @@ test "Screen: jump forward prompt skips multiline continuation" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, null); + var s = try init(alloc, .{ .cols = 5, .rows = 3 }); defer s.deinit(); try s.growRows(7); @@ -9525,7 +9668,7 @@ test "PageList grow fit in capacity" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // So we know we're using capacity to grow @@ -9547,7 +9690,7 @@ test "PageList grow allocate" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to capacity @@ -9612,12 +9755,208 @@ test "PageList Cell screenPoint supports long scrollback" { } }, cell.screenPoint()); } +test "PageList max lines uses one-page minimum" { + const testing = std.testing; + const cols: size.CellCountInt = 80; + const page_rows: usize = initialCapacity(cols).rows; + + var s = try init(testing.allocator, .{ + .cols = cols, + .rows = 1, + .max_lines = page_rows / 2, + }); + defer s.deinit(); + + try testing.expectEqual(page_rows, s.maxLines()); + + // The requested limit is below one page, so a complete page of history + // remains valid. + try s.growRows(page_rows); + try testing.expectEqual(page_rows, s.total_rows - s.rows); + try testing.expectEqual(@as(usize, 2), s.totalPages()); + + const first = s.pages.first.?; + const old_page_size = s.page_size; + + // One more row puts us over the effective limit. The now-complete + // historical page is removed rather than partially trimmed. + _ = try s.grow(); + try testing.expectEqual(@as(usize, 1), s.total_rows - s.rows); + try testing.expectEqual(@as(usize, 1), s.totalPages()); + try testing.expect(s.pages.first.? != first); + try testing.expectEqual( + old_page_size - PagePool.item_size, + s.page_size, + ); +} + +test "PageList max lines does not round larger limits" { + const testing = std.testing; + const cols: size.CellCountInt = 80; + const page_rows: usize = initialCapacity(cols).rows; + const max_lines = page_rows + page_rows / 2; + + var s = try init(testing.allocator, .{ + .cols = cols, + .rows = 1, + .max_lines = max_lines, + }); + defer s.deinit(); + + try testing.expectEqual(max_lines, s.maxLines()); + try s.growRows(max_lines); + try testing.expectEqual(max_lines, s.total_rows - s.rows); + + const first = s.pages.first.?; + const retained = first.next.?; + const removed_pin = try s.trackPin(.{ .node = first }); + defer s.untrackPin(removed_pin); + const retained_pin = try s.trackPin(.{ .node = retained }); + defer s.untrackPin(retained_pin); + + s.scroll(.{ .pin = retained_pin.* }); + try testing.expectEqual(page_rows, s.scrollbar().offset); + + const old_page_size = s.page_size; + _ = try s.grow(); + + // Whole-page pruning undershoots the requested limit without rounding it. + try testing.expectEqual( + max_lines + 1 - page_rows, + s.total_rows - s.rows, + ); + try testing.expectEqual(retained, s.pages.first.?); + try testing.expectEqual(retained, removed_pin.node); + try testing.expect(removed_pin.garbage); + try testing.expectEqual(retained, retained_pin.node); + try testing.expect(!retained_pin.garbage); + try testing.expectEqual(@as(usize, 0), s.scrollbar().offset); + try testing.expectEqual( + old_page_size - PagePool.item_size, + s.page_size, + ); +} + +test "PageList max lines and max size enforce the smaller limit" { + const testing = std.testing; + const cols: size.CellCountInt = 80; + const page_rows: usize = initialCapacity(cols).rows; + + // A line limit of one page keeps the logical allocation below a much + // larger byte limit. + { + var s = try init(testing.allocator, .{ + .cols = cols, + .rows = 1, + .max_size = 8 * PagePool.item_size, + .max_lines = page_rows, + }); + defer s.deinit(); + + try s.growRows(4 * page_rows); + try testing.expect(s.total_rows - s.rows <= s.maxLines()); + try testing.expect(s.totalPages() <= 2); + try testing.expect(s.page_size < s.maxSize()); + } + + // A two-page byte limit prunes before the larger line limit is reached. + { + var s = try init(testing.allocator, .{ + .cols = cols, + .rows = 1, + .max_size = PagePool.item_size, + .max_lines = 4 * page_rows, + }); + defer s.deinit(); + + try s.growRows(2 * page_rows); + try testing.expect(s.total_rows - s.rows < s.maxLines()); + try testing.expectEqual(s.maxSize(), s.page_size); + } +} + +test "PageList max lines applies to resize and clone" { + const testing = std.testing; + const cols: size.CellCountInt = 80; + const page_rows: usize = initialCapacity(cols).rows; + + var s = try init(testing.allocator, .{ + .cols = cols, + .rows = 2, + .max_lines = page_rows, + }); + defer s.deinit(); + + try s.growRows(page_rows); + try testing.expectEqual(page_rows, s.total_rows - s.rows); + + // Prevent row shrinking from trimming the trailing active row instead of + // turning it into history. + const cell = s.getCell(.{ .active = .{ .y = 1 } }).?; + cell.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 'A' } }, + }; + + try s.resize(.{ .rows = 1, .reflow = false }); + try testing.expectEqual(@as(usize, 1), s.total_rows - s.rows); + + const new_cols: size.CellCountInt = cols + 1; + try s.resize(.{ .cols = new_cols, .reflow = true }); + try testing.expectEqual(minMaxLines(new_cols), s.min_max_lines); + + // Exercise the same active-row shrink through the reflow path. Reflow + // completes before the newly historical complete page is pruned. + { + var reflowed = try init(testing.allocator, .{ + .cols = cols, + .rows = 2, + .max_lines = page_rows, + }); + defer reflowed.deinit(); + + try reflowed.growRows(page_rows); + const active_cell = reflowed.getCell(.{ .active = .{ .y = 1 } }).?; + active_cell.cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = .{ .data = 'A' } }, + }; + + try reflowed.resize(.{ + .cols = new_cols, + .rows = 1, + .reflow = true, + }); + try testing.expectEqual(minMaxLines(new_cols), reflowed.min_max_lines); + try testing.expect( + reflowed.total_rows - reflowed.rows <= reflowed.maxLines() or + reflowed.pages.first.? == + reflowed.getTopLeft(.active).node, + ); + } + + var cloned = try s.clone(testing.allocator, .{ + .top = .{ .screen = .{} }, + }); + defer cloned.deinit(); + + try testing.expectEqual(s.explicit_max_lines, cloned.explicit_max_lines); + try testing.expectEqual(s.min_max_lines, cloned.min_max_lines); + try testing.expectEqual(s.maxLines(), cloned.maxLines()); + + try cloned.growRows(2 * cloned.maxLines()); + try testing.expect( + cloned.total_rows - cloned.rows <= cloned.maxLines() or + cloned.pages.first.? == cloned.getTopLeft(.active).node, + ); +} + test "PageList grow prune scrollback" { const testing = std.testing; const alloc = testing.allocator; // Use std_size to limit scrollback so pruning is triggered. - var s = try init(alloc, 80, 24, std_size); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = std_size }); defer s.deinit(); // Grow to capacity @@ -9686,7 +10025,7 @@ test "PageList grow prune scrollback with viewport pin not in pruned page" { const alloc = testing.allocator; // Use std_size to limit scrollback so pruning is triggered. - var s = try init(alloc, 80, 24, std_size); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = std_size }); defer s.deinit(); // Grow to capacity of first page @@ -9745,7 +10084,7 @@ test "PageList eraseRows invalidates viewport offset cache" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -9785,7 +10124,7 @@ test "PageList eraseRow invalidates viewport offset cache" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -9824,7 +10163,7 @@ test "PageList eraseRowBounded invalidates viewport offset cache" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -9863,7 +10202,7 @@ test "PageList eraseRowBounded invalidates viewport offset cache" { test "PageList row erasure renews affected page generations" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); while (s.totalPages() < 2) _ = try s.grow(); @@ -9893,7 +10232,7 @@ test "PageList row erasure renews affected page generations" { test "PageList trailing row truncation renews page generation" { const testing = std.testing; - var s = try init(testing.allocator, 80, 24, null); + var s = try init(testing.allocator, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const node = s.pages.last.?; @@ -9911,7 +10250,7 @@ test "PageList eraseRowBounded multi-page invalidates viewport offset cache" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -9952,7 +10291,7 @@ test "PageList eraseRowBounded full page shift invalidates viewport offset cache const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -9995,7 +10334,7 @@ test "PageList eraseRowBounded exhausts pages invalidates viewport offset cache" const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up several pages worth of history @@ -10040,7 +10379,7 @@ test "PageList increaseCapacity to increase styles" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); const original_styles_cap = s.pages.first.?.capacity().styles; @@ -10091,7 +10430,7 @@ test "PageList increaseCapacity to increase graphemes" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); const original_cap = s.pages.first.?.capacity().grapheme_bytes; @@ -10135,7 +10474,7 @@ test "PageList increaseCapacity to increase hyperlinks" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); const original_cap = s.pages.first.?.capacity().hyperlink_bytes; @@ -10179,7 +10518,7 @@ test "PageList increaseCapacity to increase string_bytes" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); const original_cap = s.pages.first.?.capacity().string_bytes; @@ -10223,7 +10562,7 @@ test "PageList increaseCapacity tracked pins" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); // Create a tracked pin on the first page @@ -10246,7 +10585,7 @@ test "PageList increaseCapacity returns OutOfSpace at max capacity" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); // Keep increasing styles capacity until we get OutOfSpace @@ -10268,7 +10607,7 @@ test "PageList increaseCapacity after col shrink" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 2, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 2, .max_size = 0 }); defer s.deinit(); // Shrink columns @@ -10296,7 +10635,7 @@ test "PageList increaseCapacity multi-page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to create a second page @@ -10335,7 +10674,7 @@ test "PageList increaseCapacity preserves dirty flag" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); // Set page dirty flag and mark some rows as dirty @@ -10366,7 +10705,7 @@ test "PageList pageIterator single page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // The viewport should be within a single page @@ -10389,7 +10728,7 @@ test "PageList pageIterator two pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to capacity @@ -10425,7 +10764,7 @@ test "PageList pageIterator history two pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to capacity @@ -10455,7 +10794,7 @@ test "PageList pageIterator reverse single page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // The viewport should be within a single page @@ -10478,7 +10817,7 @@ test "PageList pageIterator reverse two pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to capacity @@ -10518,7 +10857,7 @@ test "PageList pageIterator reverse history two pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow to capacity @@ -10548,7 +10887,7 @@ test "PageList PageIterator reverse count includes row zero" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, null); + var s = try init(alloc, .{ .cols = 2, .rows = 2 }); defer s.deinit(); var it: PageIterator = .{ @@ -10566,7 +10905,7 @@ test "PageList PageIterator count crosses page boundaries" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const first = s.pages.first.?; @@ -10618,7 +10957,7 @@ test "PageList cellIterator" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10668,7 +11007,7 @@ test "PageList cellIterator reverse" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10718,7 +11057,7 @@ test "PageList promptIterator left_up" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10775,7 +11114,7 @@ test "PageList promptIterator right_down" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10832,7 +11171,7 @@ test "PageList promptIterator right_down continuation at start" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10875,7 +11214,7 @@ test "PageList promptIterator right_down with prompt before continuation" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10914,7 +11253,7 @@ test "PageList promptIterator right_down limit inclusive" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10946,7 +11285,7 @@ test "PageList promptIterator left_up limit inclusive" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 20, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -10979,7 +11318,7 @@ test "PageList highlightSemanticContent prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11033,7 +11372,7 @@ test "PageList highlightSemanticContent prompt with output" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11098,7 +11437,7 @@ test "PageList highlightSemanticContent prompt multiline" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11154,7 +11493,7 @@ test "PageList highlightSemanticContent prompt only" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11198,7 +11537,7 @@ test "PageList highlightSemanticContent prompt to end of screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11246,7 +11585,7 @@ test "PageList highlightSemanticContent input basic" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11301,7 +11640,7 @@ test "PageList highlightSemanticContent input with output" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11366,7 +11705,7 @@ test "PageList highlightSemanticContent input multiline with continuation" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11443,7 +11782,7 @@ test "PageList highlightSemanticContent input no input returns null" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11491,7 +11830,7 @@ test "PageList highlightSemanticContent input to end of screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11539,7 +11878,7 @@ test "PageList highlightSemanticContent input prompt only returns null" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11586,7 +11925,7 @@ test "PageList highlightSemanticContent output basic" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11657,7 +11996,7 @@ test "PageList highlightSemanticContent output multiline" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11748,7 +12087,7 @@ test "PageList highlightSemanticContent output stops at next prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11833,7 +12172,7 @@ test "PageList highlightSemanticContent output to end of screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11905,7 +12244,7 @@ test "PageList highlightSemanticContent output no output returns null" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -11965,7 +12304,7 @@ test "PageList highlightSemanticContent output skips empty cells" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 20, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 20, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -12042,7 +12381,7 @@ test "PageList erase" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -12076,7 +12415,7 @@ test "PageList erase reaccounts page size" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); const start_size = s.page_size; @@ -12103,7 +12442,7 @@ test "PageList erase row with tracked pin resets to top-left" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 5 pages. @@ -12140,7 +12479,7 @@ test "PageList erase row with tracked pin shifts" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Put a tracked pin in the history @@ -12161,7 +12500,7 @@ test "PageList erase row with tracked pin is erased" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Put a tracked pin in the history @@ -12182,7 +12521,7 @@ test "PageList erase resets viewport to active if moves within active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 5 pages. @@ -12211,7 +12550,7 @@ test "PageList erase resets viewport if inside erased page but not active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 5 pages. @@ -12240,7 +12579,7 @@ test "PageList erase resets viewport to active if top is inside active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow so we take up at least 5 pages. @@ -12268,7 +12607,7 @@ test "PageList erase active regrows automatically" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expect(s.totalRows() == s.rows); s.eraseActive(10); @@ -12279,7 +12618,7 @@ test "PageList erase a one-row active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 1, null); + var s = try init(alloc, .{ .cols = 10, .rows = 1 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -12307,7 +12646,7 @@ test "PageList eraseRowBounded less than full row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Pins @@ -12344,7 +12683,7 @@ test "PageList eraseRowBounded with pin at top" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Pins @@ -12369,7 +12708,7 @@ test "PageList eraseRowBounded full rows single page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Pins @@ -12402,7 +12741,7 @@ test "PageList eraseRowBounded full rows two pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Grow to two pages so our active area straddles @@ -12489,7 +12828,7 @@ test "PageList eraseRow hyperlink-dense row crosses page boundary" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Grow to two pages so our active area straddles them: the first @@ -12601,7 +12940,7 @@ test "PageList eraseRowBounded hyperlink-dense row crosses page boundary" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 10, null); + var s = try init(alloc, .{ .cols = 80, .rows = 10 }); defer s.deinit(); // Grow to two pages so our active area straddles them: the first @@ -12699,7 +13038,7 @@ test "PageList clone" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -12714,7 +13053,7 @@ test "PageList clone partial trimmed right" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 20, null); + var s = try init(alloc, .{ .cols = 80, .rows = 20 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); try s.growRows(30); @@ -12731,7 +13070,7 @@ test "PageList clone partial trimmed left" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 20, null); + var s = try init(alloc, .{ .cols = 80, .rows = 20 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); try s.growRows(30); @@ -12747,7 +13086,7 @@ test "PageList clone partial trimmed left reclaims styles" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 20, null); + var s = try init(alloc, .{ .cols = 80, .rows = 20 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); try s.growRows(30); @@ -12796,7 +13135,7 @@ test "PageList clone partial trimmed both" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 20, null); + var s = try init(alloc, .{ .cols = 80, .rows = 20 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); try s.growRows(30); @@ -12813,7 +13152,7 @@ test "PageList clone less than active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -12828,7 +13167,7 @@ test "PageList clone remap tracked pin" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -12856,7 +13195,7 @@ test "PageList clone remap tracked pin not in cloned area" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -12880,7 +13219,7 @@ test "PageList clone full dirty" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try testing.expectEqual(@as(usize, s.rows), s.totalRows()); @@ -12907,7 +13246,7 @@ test "PageList resize (no reflow) more rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 3), s.totalRows()); @@ -12940,7 +13279,7 @@ test "PageList resize (no reflow) more rows with history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, null); + var s = try init(alloc, .{ .cols = 10, .rows = 3 }); defer s.deinit(); try s.growRows(50); { @@ -12979,7 +13318,7 @@ test "PageList resize (no reflow) less rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 10), s.totalRows()); @@ -13013,7 +13352,7 @@ test "PageList resize (no reflow) one rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 10), s.totalRows()); @@ -13047,7 +13386,7 @@ test "PageList resize (no reflow) less rows cursor on bottom" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 10), s.totalRows()); @@ -13099,7 +13438,7 @@ test "PageList resize (no reflow) less rows cursor in scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 10), s.totalRows()); @@ -13153,7 +13492,7 @@ test "PageList resize (no reflow) less rows trims blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 5, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 5, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -13212,7 +13551,7 @@ test "PageList resize (no reflow) less rows trims blank lines cursor in blank li const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 5, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 5, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -13255,7 +13594,7 @@ test "PageList resize (no reflow) less rows trims blank lines erases pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 100, 5, 0); + var s = try init(alloc, .{ .cols = 100, .rows = 5, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -13288,7 +13627,7 @@ test "PageList resize (no reflow) more rows extends blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -13331,7 +13670,7 @@ test "PageList resize (no reflow) more rows contains viewport" { // When the rows are increased we need to make sure that the viewport // doesn't end up below the active area if it's currently in pin mode. - var s = try init(alloc, 5, 5, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_size = 1 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); @@ -13360,7 +13699,7 @@ test "PageList resize (no reflow) less cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Resize @@ -13380,7 +13719,7 @@ test "PageList resize (no reflow) less cols pin in trimmed cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Put a tracked pin in the history @@ -13409,7 +13748,7 @@ test "PageList resize (no reflow) less cols clears graphemes" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Add a grapheme. @@ -13439,7 +13778,7 @@ test "PageList resize (no reflow) more cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_size = 0 }); defer s.deinit(); // Resize @@ -13459,7 +13798,7 @@ test "PageList resize (no reflow) more cols with spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 3, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 3, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -13536,7 +13875,7 @@ test "PageList resize (no reflow) grow cols fast path with spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_size = 0 }); defer s.deinit(); // Shrink to 5 cols. The page keeps capacity for 10 cols. @@ -13615,7 +13954,7 @@ test "PageList resize (no reflow) more cols forces less rows per page" { const cols: size.CellCountInt = 5; const rows: size.CellCountInt = 150; try testing.expect((try std_capacity.adjust(.{ .cols = cols })).rows >= rows); - var s = try init(alloc, cols, rows, 0); + var s = try init(alloc, .{ .cols = cols, .rows = rows, .max_size = 0 }); defer s.deinit(); // Then we need to resize our cols so that our rows per page shrinks. @@ -13680,7 +14019,7 @@ test "PageList resize (no reflow) less cols then more cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_size = 0 }); defer s.deinit(); // Resize less @@ -13704,7 +14043,7 @@ test "PageList resize (no reflow) less rows and cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Resize less @@ -13724,7 +14063,7 @@ test "PageList resize less rows and cols cursor at bottom" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); const cursor_pin = try s.trackPin(s.pin(.{ .active = .{ @@ -13755,7 +14094,7 @@ test "PageList resize less rows and cols cursor near top pushed to scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Fill every active row with non-blank content so that shrinking rows @@ -13807,7 +14146,7 @@ test "PageList resize (no reflow) more rows and less cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Resize less @@ -13828,7 +14167,7 @@ test "PageList resize more rows and cols doesn't fit in single std page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Resize to a size that requires more than one page to fit our rows. @@ -13847,7 +14186,7 @@ test "PageList resize (no reflow) empty screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_size = 0 }); defer s.deinit(); // Resize @@ -13875,7 +14214,7 @@ test "PageList resize (no reflow) more cols forces smaller cap" { try testing.expect(cap2.rows < cap.rows); // Create initial cap, fits in one page - var s = try init(alloc, cap.cols, cap.rows, null); + var s = try init(alloc, .{ .cols = cap.cols, .rows = cap.rows }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -13908,7 +14247,7 @@ test "PageList resize (no reflow) more rows adds blank rows if cursor at bottom" const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, null); + var s = try init(alloc, .{ .cols = 5, .rows = 3 }); defer s.deinit(); // Grow to 5 total rows, simulating 3 active + 2 scrollback @@ -13981,7 +14320,7 @@ test "PageList resize reflow more cols no wrapped rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -14013,7 +14352,7 @@ test "PageList resize reflow more cols wrapped rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -14066,7 +14405,7 @@ test "PageList resize reflow invalidates viewport offset cache" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, null); + var s = try init(alloc, .{ .cols = 2, .rows = 4 }); defer s.deinit(); try s.growRows(20); @@ -14126,7 +14465,7 @@ test "PageList resize reflow more cols creates multiple pages" { unreachable; }; - var s = try init(alloc, cap.cols, cap.rows, null); + var s = try init(alloc, .{ .cols = cap.cols, .rows = cap.rows }); defer s.deinit(); // Wrap every other row so every line is wrapped for reflow @@ -14179,7 +14518,7 @@ test "PageList resize reflow more cols wrap across page boundary" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 10, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -14310,7 +14649,7 @@ test "PageList resize reflow more cols wrap across page boundary cursor in secon const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 10, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -14396,7 +14735,7 @@ test "PageList resize reflow less cols wrap across page boundary cursor in secon const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, null); + var s = try init(alloc, .{ .cols = 5, .rows = 10 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -14575,7 +14914,7 @@ test "PageList resize reflow more cols cursor in wrapped row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -14626,7 +14965,7 @@ test "PageList resize reflow more cols cursor in not wrapped row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -14677,7 +15016,7 @@ test "PageList resize reflow more cols cursor in wrapped row that isn't unwrappe const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -14742,7 +15081,7 @@ test "PageList resize reflow more cols no reflow preserves semantic prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -14768,7 +15107,7 @@ test "PageList resize reflow exceeds hyperlink memory forcing capacity increase" const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 10, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -14862,7 +15201,7 @@ test "PageList resize reflow exceeds grapheme memory forcing capacity increase" const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 10, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -14974,7 +15313,7 @@ test "PageList resize reflow exceeds style memory forcing capacity increase" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, pagepkg.std_capacity.styles - 1, 10, 0); + var s = try init(alloc, .{ .cols = pagepkg.std_capacity.styles - 1, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -15058,7 +15397,7 @@ test "PageList resize reflow more cols unwrap wide spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15131,7 +15470,7 @@ test "PageList resize reflow more cols unwrap wide spacer head across two rows" const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 3, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 3, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15235,7 +15574,7 @@ test "PageList resize reflow more cols unwrap still requires wide spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15316,7 +15655,7 @@ test "PageList resize reflow less cols no reflow preserves semantic prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 4, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 4, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15359,7 +15698,7 @@ test "PageList resize reflow less cols no reflow preserves semantic prompt on fi const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 4, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 4, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15385,7 +15724,7 @@ test "PageList resize reflow less cols wrap preserves semantic prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 4, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 4, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15411,7 +15750,7 @@ test "PageList resize reflow less cols no wrapped rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15449,7 +15788,7 @@ test "PageList resize reflow less cols wrapped rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, null); + var s = try init(alloc, .{ .cols = 4, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15518,7 +15857,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, null); + var s = try init(alloc, .{ .cols = 4, .rows = 2 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15604,7 +15943,7 @@ test "PageList resize reflow less cols cursor in wrapped row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, null); + var s = try init(alloc, .{ .cols = 4, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15638,7 +15977,7 @@ test "PageList resize reflow less cols wraps spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 3, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 3, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -15734,7 +16073,7 @@ test "PageList resize reflow less cols cursor goes to scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, null); + var s = try init(alloc, .{ .cols = 4, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15765,7 +16104,7 @@ test "PageList resize reflow less cols cursor in unchanged row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, null); + var s = try init(alloc, .{ .cols = 4, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15799,7 +16138,7 @@ test "PageList resize reflow less cols cursor in blank cell" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 6, 2, null); + var s = try init(alloc, .{ .cols = 6, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15833,7 +16172,7 @@ test "PageList resize reflow less cols cursor in final blank cell" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 6, 2, null); + var s = try init(alloc, .{ .cols = 6, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15867,7 +16206,7 @@ test "PageList resize reflow less cols cursor in wrapped blank cell" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 6, 2, null); + var s = try init(alloc, .{ .cols = 6, .rows = 2 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15901,7 +16240,7 @@ test "PageList resize reflow less cols blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 3, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -15944,7 +16283,7 @@ test "PageList resize reflow less cols blank lines between" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 3, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -16000,7 +16339,7 @@ test "PageList resize reflow less cols blank lines between no scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_size = 0 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -16053,7 +16392,7 @@ test "PageList resize reflow less cols cursor not on last line preserves locatio const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_size = 1 }); defer s.deinit(); try testing.expect(s.pages.first == s.pages.last); const page = s.pages.first.?.page(); @@ -16097,7 +16436,7 @@ test "PageList resize reflow less cols copy style" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16151,7 +16490,7 @@ test "PageList resize reflow less cols to eliminate a wide char" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 1, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 1, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16196,7 +16535,7 @@ test "PageList resize reflow less cols to wrap a wide char" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 3, 1, 0); + var s = try init(alloc, .{ .cols = 3, .rows = 1, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16264,7 +16603,7 @@ test "PageList resize reflow less cols to wrap a multi-codepoint grapheme with a const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16385,7 +16724,7 @@ test "PageList resize reflow less cols copy kitty placeholder" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16426,7 +16765,7 @@ test "PageList resize reflow more cols clears kitty placeholder" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16469,7 +16808,7 @@ test "PageList resize reflow wrap moves kitty placeholder" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 2, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 2, .max_size = 0 }); defer s.deinit(); { try testing.expect(s.pages.first == s.pages.last); @@ -16508,7 +16847,7 @@ test "PageList reset" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); s.reset(); try testing.expect(s.viewport == .active); @@ -16527,7 +16866,7 @@ test "PageList reset invalidates stale untracked refs even if node memory is reu const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); var stale_nodes: [page_preheat * 4]*List.Node = undefined; @@ -16587,7 +16926,7 @@ test "PageList reset across two pages" { }; // Init - var s = try init(alloc, cap.cols, rows, null); + var s = try init(alloc, .{ .cols = cap.cols, .rows = rows }); defer s.deinit(); s.reset(); try testing.expect(s.viewport == .active); @@ -16599,7 +16938,7 @@ test "PageList reset moves tracked pins and marks them as garbage" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Create a tracked pin into the active area @@ -16622,7 +16961,7 @@ test "PageList clears history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); try s.growRows(30); s.reset(); @@ -16644,7 +16983,7 @@ test "PageList resize reflow grapheme map capacity exceeded" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 4, 10, 0); + var s = try init(alloc, .{ .cols = 4, .rows = 10, .max_size = 0 }); defer s.deinit(); try testing.expectEqual(@as(usize, 1), s.totalPages()); @@ -16731,7 +17070,7 @@ test "PageList resize grow cols with unwrap fixes viewport pin" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 10, null); + var s = try init(alloc, .{ .cols = 2, .rows = 10 }); defer s.deinit(); // Make sure we have some history, in this case we have 30 rows of history @@ -16781,7 +17120,7 @@ test "PageList grow reuses non-standard page without leak" { // Create a PageList with 3 * std_size max so we can fit multiple pages // but will still trigger reuse. - var s = try init(alloc, 80, 24, 3 * std_size); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 3 * std_size }); defer s.deinit(); // Increase the first page capacity to make it non-standard (larger than std_size). @@ -16864,7 +17203,7 @@ test "PageList grow non-standard page prune protection" { // This is kind of magic and likely depends on std_size. const rows_count = 600; - var s = try init(alloc, 80, rows_count, std_size); + var s = try init(alloc, .{ .cols = 80, .rows = rows_count, .max_size = std_size }); defer s.deinit(); // Make the first page non-standard @@ -16927,7 +17266,7 @@ test "PageList resize (no reflow) more cols remaps pins in backfill path" { const cols: size.CellCountInt = 5; const cap = try std_capacity.adjust(.{ .cols = cols }); - var s = try init(alloc, cols, cap.rows, null); + var s = try init(alloc, .{ .cols = cols, .rows = cap.rows }); defer s.deinit(); // Grow until we have two pages. @@ -16985,7 +17324,7 @@ test "PageList compact pool page produces exact-size heap page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); // A freshly created page is pool-owned at std_size. @@ -17014,7 +17353,7 @@ test "PageList compact then grow allocates new page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Compact the only page. It now has no spare row capacity. @@ -17033,7 +17372,7 @@ test "PageList compact then reset frees heap pages" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); // Compact the only page so the list contains a sub-std_size @@ -17053,7 +17392,7 @@ test "PageList compact then clone" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Write a marker so we can verify contents survive. @@ -17089,7 +17428,7 @@ test "PageList compact oversized page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Grow until we have multiple pages @@ -17176,7 +17515,7 @@ test "PageList destroyed pool page reuse is zeroed" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, null); + var s = try init(alloc, .{ .cols = 80, .rows = 24 }); defer s.deinit(); // Create a page and scribble over its entire backing memory, @@ -17208,7 +17547,7 @@ test "PageList increaseCapacity from zero-capacity dimensions" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); // Compact the only page. A plain page has no styled, grapheme, @@ -17242,7 +17581,7 @@ test "PageList compact after increaseCapacity" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_size = 0 }); defer s.deinit(); var node = s.pages.first.?; @@ -17262,7 +17601,7 @@ test "PageList split at middle row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17309,7 +17648,7 @@ test "PageList split at row 0 is no-op" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17343,7 +17682,7 @@ test "PageList split at last row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17383,7 +17722,7 @@ test "PageList split single row page returns OutOfSpace" { const alloc = testing.allocator; // Initialize with 1 row - var s = try init(alloc, 10, 1, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 1, .max_size = 0 }); defer s.deinit(); const split_pin: Pin = .{ .node = s.pages.first.?, .y = 0, .x = 0 }; @@ -17396,7 +17735,7 @@ test "PageList split moves tracked pins" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Track a pin at row 7 @@ -17419,7 +17758,7 @@ test "PageList split tracked pin before split point unchanged" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const original_node = s.pages.first.?; @@ -17443,7 +17782,7 @@ test "PageList split tracked pin at split point moves to new page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const original_node = s.pages.first.?; @@ -17468,7 +17807,7 @@ test "PageList split multiple tracked pins across regions" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const original_node = s.pages.first.?; @@ -17514,7 +17853,7 @@ test "PageList split tracked viewport_pin in split region moves correctly" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const original_node = s.pages.first.?; @@ -17541,7 +17880,7 @@ test "PageList split middle page preserves linked list order" { const alloc = testing.allocator; // Create a single page with 12 rows - var s = try init(alloc, 10, 12, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 12, .max_size = 0 }); defer s.deinit(); // Split at row 4 to create: page1 (rows 0-3), page2 (rows 4-11) @@ -17590,7 +17929,7 @@ test "PageList split last page makes new page the last" { const alloc = testing.allocator; // Create a single page with 10 rows - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); // Split to create 2 pages first @@ -17621,7 +17960,7 @@ test "PageList split first page keeps original as first" { const alloc = testing.allocator; // Create 2 pages by splitting - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const original_first = s.pages.first.?; @@ -17654,7 +17993,7 @@ test "PageList split preserves wrap flags" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17708,7 +18047,7 @@ test "PageList split preserves styled cells" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17759,7 +18098,7 @@ test "PageList split preserves grapheme clusters" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); @@ -17807,7 +18146,7 @@ test "PageList split preserves hyperlinks" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_size = 0 }); defer s.deinit(); const page = s.pages.first.?.page(); diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 9ba3a56f4..b098f0923 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -253,9 +253,15 @@ pub const Options = struct { cols: size.CellCountInt, rows: size.CellCountInt, - /// The maximum size of scrollback in bytes. Zero means unlimited. Any - /// other value will be clamped to support a minimum of the active area. - max_scrollback: usize = 0, + /// The maximum size of scrollback in bytes. Null is unlimited, zero + /// disables scrollback, and any other value is clamped to support a + /// minimum of the active area. + max_scrollback_bytes: ?usize = 0, + + /// The maximum number of physical scrollback rows, excluding the active + /// area. Null is unlimited. The effective limit permits at least one + /// standard page and only complete historical pages are pruned. + max_scrollback_lines: ?usize = null, /// The total storage limit for Kitty images in bytes for this /// screen. Kitty image storage is per-screen. @@ -276,13 +282,13 @@ pub const Options = struct { pub const default: Options = .{ .cols = 80, .rows = 24, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }; }; /// Initialize a new screen. /// -/// max_scrollback is the amount of scrollback to keep in bytes. This +/// max_scrollback_bytes is the amount of scrollback to keep in bytes. This /// will be rounded UP to the nearest page size because our minimum allocation /// size is that anyways. /// @@ -293,12 +299,12 @@ pub fn init( opts: Options, ) Allocator.Error!Screen { // Initialize our backing pages. - var pages = try PageList.init( - alloc, - opts.cols, - opts.rows, - opts.max_scrollback, - ); + var pages = try PageList.init(alloc, .{ + .cols = opts.cols, + .rows = opts.rows, + .max_size = opts.max_scrollback_bytes, + .max_lines = opts.max_scrollback_lines, + }); errdefer pages.deinit(); // Create our tracked pin for the cursor. @@ -310,7 +316,7 @@ pub fn init( .io = io, .alloc = alloc, .pages = pages, - .no_scrollback = opts.max_scrollback == 0, + .no_scrollback = opts.max_scrollback_bytes == 0, .cursor = .{ .x = 0, .y = 0, @@ -3777,12 +3783,31 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void { } } +test "Screen forwards optional scrollback limits" { + const testing = std.testing; + const max_lines: usize = 123; + var s = try init(testing.io, testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_scrollback_bytes = null, + .max_scrollback_lines = max_lines, + }); + defer s.deinit(); + + try testing.expectEqual( + std.math.maxInt(usize), + s.pages.explicit_max_size, + ); + try testing.expectEqual(max_lines, s.pages.explicit_max_lines); + try testing.expect(!s.no_scrollback); +} + test "Screen read and write" { const testing = std.testing; const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try testing.expectEqual(@as(style.Id, 0), s.cursor.style_id); @@ -3797,7 +3822,7 @@ test "Screen read and write newline" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try testing.expectEqual(@as(style.Id, 0), s.cursor.style_id); @@ -3812,7 +3837,7 @@ test "Screen read and write scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("hello\nworld\ntest"); @@ -3833,7 +3858,7 @@ test "Screen read and write no scrollback small" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello\nworld\ntest"); @@ -3854,7 +3879,7 @@ test "Screen read and write no scrollback large" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); for (0..1_000) |i| { @@ -3876,13 +3901,13 @@ test "Screen cursorCopy x/y" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); s.cursorAbsolute(2, 3); try testing.expect(s.cursor.x == 2); try testing.expect(s.cursor.y == 3); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); try s2.cursorCopy(s.cursor, .{}); try testing.expect(s2.cursor.x == 2); @@ -3901,10 +3926,10 @@ test "Screen cursorCopy style deref" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); const page = s2.cursor.page_pin.node.page(); @@ -3924,10 +3949,10 @@ test "Screen cursorCopy style deref new page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 2048 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 2048 }); defer s2.deinit(); // We need to get the cursor on a new page. @@ -3998,11 +4023,11 @@ test "Screen cursorCopy style copy" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); const page = s2.cursor.page_pin.node.page(); try s2.cursorCopy(s.cursor, .{}); @@ -4015,10 +4040,10 @@ test "Screen cursorCopy hyperlink deref" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); const page = s2.cursor.page_pin.node.page(); @@ -4041,7 +4066,7 @@ test "Screen write regrows compacted page capacity" { var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -4087,10 +4112,10 @@ test "Screen cursorCopy hyperlink deref new page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 2048 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 2048 }); defer s2.deinit(); // We need to get the cursor on a new page. @@ -4161,7 +4186,7 @@ test "Screen cursorCopy hyperlink copy" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); // Create a hyperlink for the cursor. @@ -4169,7 +4194,7 @@ test "Screen cursorCopy hyperlink copy" { try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.page().hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); const page = s2.cursor.page_pin.node.page(); @@ -4187,7 +4212,7 @@ test "Screen cursorCopy hyperlink copy disabled" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); // Create a hyperlink for the cursor. @@ -4195,7 +4220,7 @@ test "Screen cursorCopy hyperlink copy disabled" { try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.page().hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); - var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s2 = try Screen.init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s2.deinit(); const page = s2.cursor.page_pin.node.page(); @@ -4213,7 +4238,7 @@ test "Screen style basics" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); const page = s.cursor.page_pin.node.page(); try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -4236,7 +4261,7 @@ test "Screen style reset to default" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); const page = s.cursor.page_pin.node.page(); try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -4257,7 +4282,7 @@ test "Screen style reset with unset" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); const page = s.cursor.page_pin.node.page(); try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -4278,7 +4303,7 @@ test "Screen clearRows active one line" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("hello, world"); @@ -4294,7 +4319,7 @@ test "Screen clearRows active multi line" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("hello\nworld"); @@ -4311,7 +4336,7 @@ test "Screen clearRows active styled line" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); @@ -4350,7 +4375,7 @@ test "Screen clearRows uses stored page width" { var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 2, .rows = 1, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -4369,7 +4394,7 @@ test "Screen clearRows protected" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("UNPROTECTED"); @@ -4398,7 +4423,7 @@ test "Screen eraseRows history" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("1\n2\n3\n4\n5\n6"); @@ -4433,7 +4458,7 @@ test "Screen eraseRows history with more lines" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 1000 }); defer s.deinit(); try s.testWriteString("A\nB\nC\n1\n2\n3\n4\n5\n6"); @@ -4468,7 +4493,7 @@ test "Screen eraseRows active partial" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1\n2\n3"); @@ -4498,7 +4523,7 @@ test "Screen: cursorCellEndOfPrev across mixed-width pages" { var s = try init(testing.io, testing.allocator, .{ .cols = 4, .rows = 2, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -4519,7 +4544,7 @@ test "Screen: cursorDown across pages preserves style" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -4572,7 +4597,7 @@ test "Screen: cursorUp across pages preserves style" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -4620,7 +4645,7 @@ test "Screen: cursorAbsolute across pages preserves style" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -4676,7 +4701,7 @@ test "Screen: cursorAbsolute to page with insufficient capacity" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -4744,7 +4769,7 @@ test "Screen: scrolling" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.setAttribute(.{ .direct_color_bg = .{ .r = 155 } }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4787,7 +4812,7 @@ test "Screen: scrolling with a single-row screen no scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD"); @@ -4808,7 +4833,7 @@ test "Screen: scrolling with a single-row screen with scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD"); @@ -4839,7 +4864,7 @@ test "Screen: scrolling across pages preserves style" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4869,7 +4894,7 @@ test "Screen: scroll down from 0" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4889,7 +4914,7 @@ test "Screen: scrollback various cases" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); try s.cursorDownScroll(); @@ -4971,7 +4996,7 @@ test "Screen: scrollback with multi-row delta" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH\n6IJKL"); @@ -4998,7 +5023,7 @@ test "Screen: scrollback empty" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 50 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 50 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); s.scroll(.{ .delta_row = 1 }); @@ -5014,7 +5039,7 @@ test "Screen: scrollback doesn't move viewport if not at bottom" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"); @@ -5050,7 +5075,7 @@ test "Screen: scrolling moves selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5130,7 +5155,7 @@ test "Screen: cursorScrollRegionUp simple" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4MNOP\n5QRST"); @@ -5158,7 +5183,7 @@ test "Screen: cursorScrollRegionUp renews page generation" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4MNOP\n5QRST"); s.cursorAbsolute(0, 2); @@ -5175,7 +5200,7 @@ test "Screen: cursorScrollRegionUp moves selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4MNOP\n5QRST"); @@ -5214,7 +5239,7 @@ test "Screen: cursorScrollRegionUp region spans pages" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -5279,7 +5304,7 @@ test "Screen: cursorScrollRegionUp region spans pages with background SGR" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10 }); defer s.deinit(); // We need to get the cursor to a new page. See the previous test @@ -5323,7 +5348,7 @@ test "Screen: cursorScrollRegionUp with styled erased row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); // Write a styled row at the top so the erased row has managed @@ -5353,7 +5378,7 @@ test "Screen: scrolling moves viewport" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5379,7 +5404,7 @@ test "Screen: scrolling when viewport is pruned" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 215, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 215, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Write some to create scrollback and move back into our scrollback. @@ -5406,7 +5431,7 @@ test "Screen: scroll and clear full screen" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5434,7 +5459,7 @@ test "Screen: scroll and clear partial screen" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); @@ -5462,7 +5487,7 @@ test "Screen: scroll and clear empty screen" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); try s.scrollClear(); { @@ -5482,7 +5507,7 @@ test "Screen: scroll and clear ignore blank lines" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); try s.scrollClear(); @@ -5526,7 +5551,7 @@ test "Screen: scroll above same page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); try s.setAttribute(.{ .direct_color_bg = .{ .r = 155 } }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5589,7 +5614,7 @@ test "Screen: scroll above same page but cursor on previous page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -5677,7 +5702,7 @@ test "Screen: scroll above same page but cursor on previous page last row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -5768,7 +5793,7 @@ test "Screen: scroll above creates new page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -5844,7 +5869,7 @@ test "Screen: scroll above with cursor on non-final row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 4, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 4, .max_scrollback_bytes = 10 }); defer s.deinit(); // Get the cursor to be 2 rows above a new page @@ -5922,7 +5947,7 @@ test "Screen: scroll above no scrollback bottom of page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const first_page_size = s.pages.pages.first.?.capacity().rows; @@ -5996,7 +6021,7 @@ test "Screen: scroll above hyperlink-dense row to fresh page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10000 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10000 }); defer s.deinit(); // Fill the first page so it is exactly full and the cursor is on @@ -6085,7 +6110,7 @@ test "Screen: scroll above hyperlink-dense row to existing page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10000 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10000 }); defer s.deinit(); // Fill the first page so it is exactly full and the cursor is on @@ -6151,7 +6176,7 @@ test "Screen: clone" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -6194,7 +6219,7 @@ test "Screen: clone partial" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -6224,7 +6249,7 @@ test "Screen: clone partial cursor out of bounds" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -6259,7 +6284,7 @@ test "Screen: clone contains full selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6298,7 +6323,7 @@ test "Screen: clone contains none of selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6327,7 +6352,7 @@ test "Screen: clone contains selection start cutoff" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6366,7 +6391,7 @@ test "Screen: clone contains selection end cutoff" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6405,7 +6430,7 @@ test "Screen: clone contains selection end cutoff reversed" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6444,7 +6469,7 @@ test "Screen: clone contains subset of selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 4, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD"); @@ -6483,7 +6508,7 @@ test "Screen: clone clamps clipped selections to mixed-width pages" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 4, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 4, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const first = s.pages.pages.first.?; @@ -6531,7 +6556,7 @@ test "Screen: clone contains subset of rectangle selection" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 4, .max_scrollback_bytes = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD"); @@ -6572,7 +6597,7 @@ test "Screen: clone basic" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -6612,7 +6637,7 @@ test "Screen: clone empty viewport" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); { @@ -6636,7 +6661,7 @@ test "Screen: clone one line viewport" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABC"); @@ -6661,7 +6686,7 @@ test "Screen: clone empty active" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); { @@ -6685,7 +6710,7 @@ test "Screen: clone one line active with extra space" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABC"); @@ -6710,7 +6735,7 @@ test "Screen: clear history with no history" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); try testing.expect(s.pages.viewport == .active); @@ -6735,7 +6760,7 @@ test "Screen: clear history" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH\n6IJKL"); try testing.expect(s.pages.viewport == .active); @@ -6770,7 +6795,7 @@ test "Screen: clear above cursor" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); s.clearRows( @@ -6798,7 +6823,7 @@ test "Screen: clear above cursor with history" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); @@ -6827,7 +6852,7 @@ test "Screen: resize (no reflow) more rows" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6846,7 +6871,7 @@ test "Screen: resize (no reflow) less rows" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6870,7 +6895,7 @@ test "Screen: resize (no reflow) less rows trims blank lines" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -6906,7 +6931,7 @@ test "Screen: resize (no reflow) more rows trims blank lines" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -6942,7 +6967,7 @@ test "Screen: resize (no reflow) more cols" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6960,7 +6985,7 @@ test "Screen: resize (no reflow) less cols" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6979,7 +7004,7 @@ test "Screen: resize (no reflow) more rows with scrollback cursor end" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 7, .rows = 3, .max_scrollback = 2 }); + var s = try init(io, alloc, .{ .cols = 7, .rows = 3, .max_scrollback_bytes = 2 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -6997,7 +7022,7 @@ test "Screen: resize (no reflow) less rows with scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 7, .rows = 3, .max_scrollback = 2 }); + var s = try init(io, alloc, .{ .cols = 7, .rows = 3, .max_scrollback_bytes = 2 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -7017,7 +7042,7 @@ test "Screen: resize (no reflow) less rows with empty trailing" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -7042,7 +7067,7 @@ test "Screen: resize (no reflow) more rows with soft wrapping" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); const str = "1A2B\n3C4E\n5F6G"; try s.testWriteString(str); @@ -7084,7 +7109,7 @@ test "Screen: resize more rows no scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7112,7 +7137,7 @@ test "Screen: resize more rows with empty scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7140,7 +7165,7 @@ test "Screen: resize more rows with populated scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -7186,7 +7211,7 @@ test "Screen: resize more cols no reflow" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7216,7 +7241,7 @@ test "Screen: resize more cols perfect split" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -7235,7 +7260,7 @@ test "Screen: resize (no reflow) more cols with scrollback scrolled up" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -7269,7 +7294,7 @@ test "Screen: resize (no reflow) less cols with scrollback scrolled up" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -7314,7 +7339,7 @@ test "Screen: resize more cols no reflow preserves semantic prompt" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); // Set one of the rows to be a prompt @@ -7359,7 +7384,7 @@ test "Screen: resize more cols with reflow that fits full width" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7400,7 +7425,7 @@ test "Screen: resize more cols with reflow that ends in newline" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 6, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 6, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7446,7 +7471,7 @@ test "Screen: resize more cols with reflow that forces more wrapping" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7488,7 +7513,7 @@ test "Screen: resize more cols with reflow that unwraps multiple times" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -7530,7 +7555,7 @@ test "Screen: resize more cols with populated scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD5EFGH"; try s.testWriteString(str); @@ -7583,7 +7608,7 @@ test "Screen: resize more cols bounded scrollback keeps viewport valid" { var s = try init(io, alloc, .{ .cols = 2, .rows = 10, - .max_scrollback = 10_000, + .max_scrollback_bytes = 10_000, }); defer s.deinit(); @@ -7656,7 +7681,7 @@ test "Screen: resize more cols with reflow" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1ABC\n2DEF\n3ABC\n4DEF"; try s.testWriteString(str); @@ -7705,7 +7730,7 @@ test "Screen: resize errors preserve state" { var s = try init(io, alloc, .{ .cols = 10, .rows = 3, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -7787,7 +7812,7 @@ test "Screen: resize cursor references when node survives" { var s = try init(io, alloc, .{ .cols = 5, .rows = 3, - .max_scrollback = 1000, + .max_scrollback_bytes = 1000, }); defer s.deinit(); @@ -7837,7 +7862,7 @@ test "Screen: resize cursor references when node is replaced" { var s = try init(io, alloc, .{ .cols = 5, .rows = 3, - .max_scrollback = 1000, + .max_scrollback_bytes = 1000, }); defer s.deinit(); @@ -7886,7 +7911,7 @@ test "Screen: resize more rows and cols with wrapping" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 4, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 4, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1A2B\n3C4D"; try s.testWriteString(str); @@ -7920,7 +7945,7 @@ test "Screen: resize less rows no scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7952,7 +7977,7 @@ test "Screen: resize less rows moving cursor" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -7993,7 +8018,7 @@ test "Screen: resize less rows with empty scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8017,7 +8042,7 @@ test "Screen: resize less rows with populated scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -8049,7 +8074,7 @@ test "Screen: resize less rows with full scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); const str = "00000\n1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -8090,7 +8115,7 @@ test "Screen: resize less cols no reflow" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1AB\n2EF\n3IJ"; try s.testWriteString(str); @@ -8120,7 +8145,7 @@ test "Screen: resize less cols with reflow but row space" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -8159,7 +8184,7 @@ test "Screen: resize less cols with reflow with trimmed rows" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -8184,7 +8209,7 @@ test "Screen: resize less cols with reflow with trimmed rows and scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); const str = "3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -8209,7 +8234,7 @@ test "Screen: resize less cols with reflow previously wrapped" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "3IJKL4ABCD5EFGH"; try s.testWriteString(str); @@ -8243,7 +8268,7 @@ test "Screen: resize less cols with reflow and scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1A\n2B\n3C\n4D\n5E"; try s.testWriteString(str); @@ -8277,7 +8302,7 @@ test "Screen: resize less cols with reflow previously wrapped and scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 2 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 2 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL4ABCD5EFGH"; try s.testWriteString(str); @@ -8332,7 +8357,7 @@ test "Screen: resize less cols with scrollback keeps cursor row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); const str = "1A\n2B\n3C\n4D\n5E"; try s.testWriteString(str); @@ -8362,7 +8387,7 @@ test "Screen: resize more rows, less cols with reflow with scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 3 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 3 }); defer s.deinit(); const str = "1ABCD\n2EFGH3IJKL\n4MNOP"; try s.testWriteString(str); @@ -8404,7 +8429,7 @@ test "Screen: resize more rows then shrink again" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 10 }); defer s.deinit(); const str = "1ABC"; try s.testWriteString(str); @@ -8454,7 +8479,7 @@ test "Screen: resize less cols to eliminate wide char" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 1, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 1, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "😀"; try s.testWriteString(str); @@ -8490,7 +8515,7 @@ test "Screen: resize less cols to wrap wide char" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 3, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 3, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "x😀"; try s.testWriteString(str); @@ -8530,7 +8555,7 @@ test "Screen: resize less cols to eliminate wide char with row space" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "😀"; try s.testWriteString(str); @@ -8563,7 +8588,7 @@ test "Screen: resize less cols reflows cursor after wrapped text" { const testing = std.testing; const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 50, .rows = 7, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 50, .rows = 7, .max_scrollback_bytes = 0 }); defer s.deinit(); for (0..30) |_| try s.testWriteString("a"); @@ -8581,7 +8606,7 @@ test "Screen: resize less cols reflows cursor after empty cells" { const testing = std.testing; const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("abc"); @@ -8601,7 +8626,7 @@ test "Screen: resize more cols with wide spacer head" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 3, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 3, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = " 😀"; try s.testWriteString(str); @@ -8655,7 +8680,7 @@ test "Screen: resize more cols with wide spacer head multiple lines" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 3, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 3, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "xxxyy😀"; try s.testWriteString(str); @@ -8707,7 +8732,7 @@ test "Screen: resize more cols requiring a wide spacer head" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "xx😀"; try s.testWriteString(str); @@ -8759,7 +8784,7 @@ test "Screen: resize more cols with cursor at prompt" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); // zig fmt: off @@ -8800,7 +8825,7 @@ test "Screen: resize more cols with cursor not at prompt" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); // zig fmt: off @@ -8842,7 +8867,7 @@ test "Screen: resize with prompt_redraw last clears only one line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 4, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 4, .max_scrollback_bytes = 5 }); defer s.deinit(); // zig fmt: off @@ -8882,7 +8907,7 @@ test "Screen: resize with prompt_redraw last multiline prompt clears only last l const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 5 }); defer s.deinit(); // Create a 3-line prompt: 1 initial + 2 continuation lines @@ -8923,7 +8948,7 @@ test "Screen: select untracked" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -8944,7 +8969,7 @@ test "Screen: select replaces existing pins" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -8971,7 +8996,7 @@ test "Screen: reselecting tracked selection preserves its pins" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.select(Selection.init( @@ -8989,7 +9014,7 @@ test "Screen: selectAll" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); { @@ -9026,7 +9051,7 @@ test "Screen: selectLine" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -9108,7 +9133,7 @@ test "Screen: selectLine across soft-wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -9135,7 +9160,7 @@ test "Screen: selectLine across full soft-wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1ABCD2EFGH\n3IJKL"); @@ -9161,7 +9186,7 @@ test "Screen: selectLine across soft-wrap ignores blank lines" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -9222,7 +9247,7 @@ test "Screen: selectLine disabled whitespace trimming" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -9272,7 +9297,7 @@ test "Screen: selectLine with scrollback" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 5 }); + var s = try init(io, alloc, .{ .cols = 2, .rows = 3, .max_scrollback_bytes = 5 }); defer s.deinit(); try s.testWriteString("1A\n2B\n3C\n4D\n5E"); @@ -9317,7 +9342,7 @@ test "Screen: selectLine semantic prompt boundary" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("ABCDE\n"); s.cursorSetSemanticContent(.{ .prompt = .initial }); @@ -9368,7 +9393,7 @@ test "Screen: selectLine semantic prompt to input boundary" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Write prompt followed by user input on same row: "$>command" @@ -9418,7 +9443,7 @@ test "Screen: selectLine semantic input to output boundary" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Row 0: user input @@ -9464,7 +9489,7 @@ test "Screen: selectLine semantic mid-row boundary" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Single row with output then prompt then input: "out$>cmd" @@ -9533,7 +9558,7 @@ test "Screen: selectLine semantic boundary soft-wrap with mid-row transition" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Row 0: prompt "$ " + input "cmd" (soft-wraps) @@ -9603,7 +9628,7 @@ test "Screen: selectLine semantic boundary disabled" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Write prompt followed by input @@ -9636,7 +9661,7 @@ test "Screen: selectLine semantic boundary first cell of row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Row 0: input that soft-wraps @@ -9693,7 +9718,7 @@ test "Screen: selectLine semantic boundary across mixed-width pages" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 4, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 4, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); s.cursorSetSemanticContent(.{ .input = .clear_explicit }); @@ -9725,7 +9750,7 @@ test "Screen: selectLine semantic all same content" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // All prompt content that soft-wraps @@ -9760,7 +9785,7 @@ test "Screen: selectWord" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -9885,7 +9910,7 @@ test "Screen: selectWord across soft-wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString(" 1234012\n 123"); @@ -9961,7 +9986,7 @@ test "Screen: selectWord whitespace across soft-wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("1 1\n 123"); @@ -10061,7 +10086,7 @@ test "Screen: selectWord with character boundary" { }; for (cases) |case| { - var s = try init(io, alloc, .{ .cols = 20, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString(case); @@ -10142,7 +10167,7 @@ test "Screen: selectOutput" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 15, .max_scrollback_bytes = 0 }); defer s.deinit(); // Build content with cell-level semantic content: @@ -10239,7 +10264,7 @@ test "Screen: selectionString basic" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -10265,7 +10290,7 @@ test "Screen: selectionString start outside of written area" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -10291,7 +10316,7 @@ test "Screen: selectionString end outside of written area" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -10317,7 +10342,7 @@ test "Screen: selectionString trim space" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1AB \n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -10355,7 +10380,7 @@ test "Screen: selectionString trim empty line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1AB \n\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -10393,7 +10418,7 @@ test "Screen: selectionString soft wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -10419,7 +10444,7 @@ test "Screen: selectionString wide char" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1A⚡"; try s.testWriteString(str); @@ -10475,7 +10500,7 @@ test "Screen: selectionString wide char with header" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABC⚡"; try s.testWriteString(str); @@ -10502,7 +10527,7 @@ test "Screen: selectionString empty with soft wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 2, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 2, .max_scrollback_bytes = 0 }); defer s.deinit(); // Let me describe the situation that caused this because this @@ -10536,7 +10561,7 @@ test "Screen: selectionString with zero width joiner" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 1, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "👨‍"; // this has a ZWJ try s.testWriteString(str); @@ -10573,7 +10598,7 @@ test "Screen: selectionString, rectangle, basic" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 30, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 30, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -10607,7 +10632,7 @@ test "Screen: selectionString, rectangle, w/EOL" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 30, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 30, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -10643,7 +10668,7 @@ test "Screen: selectionString, rectangle, more complex w/breaks" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 30, .rows = 8, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 30, .rows = 8, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -10683,7 +10708,7 @@ test "Screen: selectionString multi-page" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 2048 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 2048 }); defer s.deinit(); const first_page_size = s.pages.pages.first.?.capacity().rows; @@ -10718,7 +10743,7 @@ test "Screen: lineIterator" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH"; try s.testWriteString(str); @@ -10750,7 +10775,7 @@ test "Screen: lineIterator soft wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3ABCD"; try s.testWriteString(str); @@ -10783,7 +10808,7 @@ test "Screen: hyperlink start/end" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try testing.expect(s.cursor.hyperlink_id == 0); { @@ -10811,7 +10836,7 @@ test "Screen: hyperlink accepts its current values" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.startHyperlink("http://example.com", "current"); @@ -10827,7 +10852,7 @@ test "Screen: implicit hyperlink ID wraps" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); s.cursor.hyperlink_implicit_id = std.math.maxInt(size.OffsetInt); @@ -10865,7 +10890,7 @@ test "Screen: hyperlink reuse" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try testing.expect(s.cursor.hyperlink_id == 0); @@ -10904,7 +10929,7 @@ test "Screen: hyperlink cursor state on resize" { // it may be invalid one day. It's here to document/verify the // current behavior. - var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 5, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); // Start a hyperlink @@ -10936,7 +10961,7 @@ test "Screen: cursorSetHyperlink OOM + URI too large for string alloc" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); // Start a hyperlink with a URI that just barely fits in the string alloc. @@ -10974,7 +10999,7 @@ test "Screen: increaseCapacity cursor style ref count preserved" { var s = try init(io, alloc, .{ .cols = 5, .rows = 5, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); try s.setAttribute(.bold); @@ -11030,7 +11055,7 @@ test "Screen: increaseCapacity cursor hyperlink ref count preserved" { var s = try init(io, alloc, .{ .cols = 5, .rows = 5, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); try s.startHyperlink("https://example.com/", null); @@ -11074,7 +11099,7 @@ test "Screen: increaseCapacity cursor with both style and hyperlink preserved" { var s = try init(io, alloc, .{ .cols = 5, .rows = 5, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -11140,7 +11165,7 @@ test "Screen: increaseCapacity non-cursor page returns early" { var s = try init(io, alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 10000, + .max_scrollback_bytes = 10000, }); defer s.deinit(); @@ -11224,7 +11249,7 @@ test "Screen: cursorDown to page with insufficient capacity" { const io = testing.io; // Small screen to make page boundary crossing easy to set up - var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 3, .max_scrollback_bytes = 1 }); defer s.deinit(); // Scroll down enough to create a second page @@ -11294,7 +11319,7 @@ test "Screen setAttribute increases capacity when style map is full" { const io = testing.io; // Use a small screen with multiple rows - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 10 }); defer s.deinit(); // Write content to multiple rows @@ -11346,7 +11371,7 @@ test "Screen setAttribute splits page on OutOfSpace at max styles" { var s = try init(io, alloc, .{ .cols = 10, .rows = 10, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -11414,7 +11439,7 @@ test "selectionString map allocation failure cleanup" { const testing = std.testing; const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello"); @@ -11445,7 +11470,7 @@ test "Screen: promptClickMove line right basic" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11473,7 +11498,7 @@ test "Screen: promptClickMove line right cursor not on input" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11501,7 +11526,7 @@ test "Screen: promptClickMove line right click on same position" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11528,7 +11553,7 @@ test "Screen: promptClickMove line right skips non-input cells" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11561,7 +11586,7 @@ test "Screen: promptClickMove line right soft-wrapped line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11600,7 +11625,7 @@ test "Screen: promptClickMove disabled when click is none" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Click mode is .none by default (disabled) @@ -11627,7 +11652,7 @@ test "Screen: promptClickMove line right stops at hard wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11660,7 +11685,7 @@ test "Screen: promptClickMove line right stops at non-continuation row" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11708,7 +11733,7 @@ test "Screen: promptClickMove line left basic" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11736,7 +11761,7 @@ test "Screen: promptClickMove line left skips non-input cells" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11769,7 +11794,7 @@ test "Screen: promptClickMove line left soft-wrapped line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11808,7 +11833,7 @@ test "Screen: promptClickMove line left stops at hard wrap" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11844,7 +11869,7 @@ test "Screen: promptClickMove click right of input same line" { // Set up: "> hello" where "> " is prompt and "hello" is input // Clicking to the right of the 'o' should move cursor past the input - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11872,7 +11897,7 @@ test "Screen: promptClickMove click right of input cursor at end" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11899,7 +11924,7 @@ test "Screen: promptClickMove click right of input on lower line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11929,7 +11954,7 @@ test "Screen: promptClickMove click right of input cursor at end lower line" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode @@ -11955,7 +11980,7 @@ test "Screen: promptClickMove click right of input cursor on last char" { const alloc = testing.allocator; const io = testing.io; - var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback = 0 }); + var s = try init(io, alloc, .{ .cols = 20, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); // Enable line click mode diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index b85e56254..398496da6 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -512,7 +512,7 @@ pub fn adjust( test "Selection: adjust right" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A1234\nB5678\nC1234\nD5678"); @@ -579,7 +579,7 @@ test "Selection: adjust right" { test "Selection: adjust left" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A1234\nB5678\nC1234\nD5678"); @@ -628,7 +628,7 @@ test "Selection: adjust left" { test "Selection: adjust left skips blanks" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A1234\nB5678\nC12\nD56"); @@ -677,7 +677,7 @@ test "Selection: adjust left skips blanks" { test "Selection: adjust up" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A\nB\nC\nD\nE"); @@ -724,7 +724,7 @@ test "Selection: adjust up" { test "Selection: adjust down" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A\nB\nC\nD\nE"); @@ -771,7 +771,7 @@ test "Selection: adjust down" { test "Selection: adjust down with not full screen" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A\nB\nC"); @@ -799,7 +799,7 @@ test "Selection: adjust down with not full screen" { test "Selection: adjust home" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A\nB\nC"); @@ -827,7 +827,7 @@ test "Selection: adjust home" { test "Selection: adjust end with not full screen" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A\nB\nC"); @@ -855,7 +855,7 @@ test "Selection: adjust end with not full screen" { test "Selection: adjust beginning of line" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A12 B34\nC12 D34"); @@ -925,7 +925,7 @@ test "Selection: adjust beginning of line" { test "Selection: adjust end of line" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("A12 B34\nC12 D34"); @@ -996,7 +996,7 @@ test "Selection: order, standard" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 100, .rows = 100, .max_scrollback = 1 }); + var s = try Screen.init(io, alloc, .{ .cols = 100, .rows = 100, .max_scrollback_bytes = 1 }); defer s.deinit(); { @@ -1061,7 +1061,7 @@ test "Selection: rectangle corners clamp across mixed-width pages" { var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 4, .rows = 2, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); @@ -1087,7 +1087,7 @@ test "Selection: order, rectangle" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 100, .rows = 100, .max_scrollback = 1 }); + var s = try Screen.init(io, alloc, .{ .cols = 100, .rows = 100, .max_scrollback_bytes = 1 }); defer s.deinit(); // Conventions: @@ -1199,7 +1199,7 @@ test "Selection: order, rectangle" { test "topLeft" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); { // forward @@ -1262,7 +1262,7 @@ test "topLeft" { test "bottomRight" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); { // forward @@ -1325,7 +1325,7 @@ test "bottomRight" { test "ordered" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); { // forward @@ -1406,7 +1406,7 @@ test "ordered" { test "Selection: contains" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback_bytes = 0 }); defer s.deinit(); { const sel = Selection.init( @@ -1452,7 +1452,7 @@ test "Selection: contains" { test "Selection: contains, rectangle" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 15, .rows = 15, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 15, .rows = 15, .max_scrollback_bytes = 0 }); defer s.deinit(); { const sel = Selection.init( @@ -1514,7 +1514,7 @@ test "Selection: contains, rectangle" { test "Selection: containedRow" { const testing = std.testing; - var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); { @@ -1637,7 +1637,7 @@ test "Selection: containedRow clamps mixed-width pages" { var s = try Screen.init(testing.io, testing.allocator, .{ .cols = 4, .rows = 3, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); diff --git a/src/terminal/SelectionGesture.zig b/src/terminal/SelectionGesture.zig index ddcb05be4..a842b2471 100644 --- a/src/terminal/SelectionGesture.zig +++ b/src/terminal/SelectionGesture.zig @@ -1029,7 +1029,7 @@ fn testDragSelection( .padding_left = 5, .screen_height = 110, }; - var screen = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var screen = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer screen.deinit(); const click_pin = screen.pages.pin(.{ @@ -1090,7 +1090,7 @@ fn testDragSelectionIsNull( .padding_left = 5, .screen_height = 110, }; - var screen = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); + var screen = try Screen.init(testing.io, testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback_bytes = 0 }); defer screen.deinit(); const click_pin = screen.pages.pin(.{ @@ -1724,7 +1724,7 @@ test "SelectionGesture autoscroll tick scrolls and continues drag" { } test "SelectionGesture autoscroll tick resolves drag pin after scrolling" { - var t = try Terminal.init(testing.io, testing.allocator, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); + var t = try Terminal.init(testing.io, testing.allocator, .{ .cols = 5, .rows = 3, .max_scrollback_bytes = 10 }); defer t.deinit(testing.allocator); try t.printString("1111\n2222\n3333\n4444\n5555"); t.scrollViewport(.{ .delta = -2 }); diff --git a/src/terminal/StringMap.zig b/src/terminal/StringMap.zig index d5de87426..9b2fa3ca8 100644 --- a/src/terminal/StringMap.zig +++ b/src/terminal/StringMap.zig @@ -131,7 +131,7 @@ test "StringMap searchIterator" { defer re.deinit(); // Initialize our screen - var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 5, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -190,7 +190,7 @@ test "StringMap searchIterator URL detection" { defer re.deinit(); // Initialize our screen with text containing a URL - var s = try Screen.init(io, alloc, .{ .cols = 40, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 40, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello https://example.com/path world"); @@ -252,7 +252,7 @@ test "StringMap searchIterator URL with click position" { defer re.deinit(); // Initialize our screen with text containing a URL - var s = try Screen.init(io, alloc, .{ .cols = 40, .rows = 5, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 40, .rows = 5, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello https://example.com world"); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 72ebb34a7..d8442335f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -256,7 +256,16 @@ pub const Cursor = struct { pub const Options = struct { cols: size.CellCountInt, rows: size.CellCountInt, - max_scrollback: usize = 10_000, + + /// The maximum size of scrollback in bytes. Null is unlimited and zero + /// disables scrollback. + max_scrollback_bytes: ?usize = 10_000, + + /// The maximum number of physical scrollback rows, excluding the active + /// area. Null is unlimited. The effective limit permits at least one + /// standard page and only complete historical pages are pruned. + max_scrollback_lines: ?usize = null, + colors: Colors = .default, /// The default mode state. When the terminal gets a reset, it @@ -300,7 +309,8 @@ pub fn init( var screen_set: ScreenSet = try .init(io_impl, alloc, .{ .cols = cols, .rows = rows, - .max_scrollback = opts.max_scrollback, + .max_scrollback_bytes = opts.max_scrollback_bytes, + .max_scrollback_lines = opts.max_scrollback_lines, .kitty_image_storage_limit = opts.kitty_image_storage_limit, .kitty_image_loading_limits = opts.kitty_image_loading_limits, }); @@ -3830,7 +3840,7 @@ pub fn resize( .{ .cols = opts.cols, .rows = opts.rows, - .max_scrollback = 0, + .max_scrollback_bytes = 0, .kitty_image_storage_limit = if (comptime build_options.kitty_graphics) primary.kitty_images.total_limit else @@ -3878,6 +3888,26 @@ pub fn resize( }; } +test "Terminal forwards optional scrollback limits" { + const max_lines: usize = 123; + var t = try init(testing.io, testing.allocator, .{ + .cols = 80, + .rows = 24, + .max_scrollback_bytes = null, + .max_scrollback_lines = max_lines, + }); + defer t.deinit(testing.allocator); + + try testing.expectEqual( + std.math.maxInt(usize), + t.screens.active.pages.explicit_max_size, + ); + try testing.expectEqual( + max_lines, + t.screens.active.pages.explicit_max_lines, + ); +} + test "Terminal: resize resets synchronized output" { const alloc = testing.allocator; const io_impl = testing.io; @@ -4294,7 +4324,7 @@ pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen { .{ .cols = self.cols, .rows = self.rows, - .max_scrollback = switch (key) { + .max_scrollback_bytes = switch (key) { .primary => primary.pages.explicit_max_size, .alternate => 0, }, @@ -7663,7 +7693,7 @@ test "Terminal: insertLines top/bottom scroll region" { test "Terminal: insertLines across page boundary marks all shifted rows dirty" { const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback_bytes = 1024 }); defer t.deinit(alloc); const first_page = t.screens.active.pages.pages.first.?; @@ -7718,7 +7748,7 @@ test "Terminal: insertLines hyperlink-dense row crosses page boundary" { // page's capacity and retry rather than corrupting the page list. const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback_bytes = 1024 }); defer t.deinit(alloc); const pages = &t.screens.active.pages; @@ -8410,7 +8440,7 @@ test "Terminal: scrollUp creates scrollback in primary screen" { // scrollUp (CSI S) should push lines into scrollback like xterm. const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 10 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 10 }); defer t.deinit(alloc); // Fill the screen with content @@ -8453,11 +8483,11 @@ test "Terminal: scrollUp creates scrollback in primary screen" { } } -test "Terminal: scrollUp with max_scrollback zero" { - // When max_scrollback is 0, scrollUp should still work but not retain history +test "Terminal: scrollUp with max_scrollback_bytes zero" { + // When max_scrollback_bytes is 0, scrollUp should still work but not retain history const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); try t.printString("AAAAA"); @@ -8486,11 +8516,11 @@ test "Terminal: scrollUp with max_scrollback zero" { } } -test "Terminal: scrollUp with max_scrollback zero and top margin" { - // When max_scrollback is 0 and top margin is set, should use deleteLines path +test "Terminal: scrollUp with max_scrollback_bytes zero and top margin" { + // When max_scrollback_bytes is 0 and top margin is set, should use deleteLines path const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); try t.printString("AAAAA"); @@ -8517,11 +8547,11 @@ test "Terminal: scrollUp with max_scrollback zero and top margin" { } } -test "Terminal: scrollUp with max_scrollback zero and left/right margin" { - // When max_scrollback is 0 with left/right margins, uses deleteLines path +test "Terminal: scrollUp with max_scrollback_bytes zero and left/right margin" { + // When max_scrollback_bytes is 0 with left/right margins, uses deleteLines path const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); try t.printString("AAAAABBBBB"); @@ -9703,7 +9733,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" { test "Terminal: index bottom of scroll region clear hyperlinks" { const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); t.setTopAndBottomMargin(2, 3); @@ -9896,7 +9926,7 @@ test "Terminal: index bottom of scroll region creates scrollback" { test "Terminal: index bottom of scroll region no scrollback" { const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); t.setTopAndBottomMargin(1, 3); @@ -10063,7 +10093,7 @@ test "Terminal: index bottom of alt screen top region" { test "Terminal: scrollUp top region no scrollback" { const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 5, .max_scrollback_bytes = 0 }); defer t.deinit(alloc); try t.printString("A\nB\nC\nD\nE"); @@ -10648,7 +10678,7 @@ test "Terminal: deleteLines colors with bg color" { test "Terminal: deleteLines across page boundary marks all shifted rows dirty" { const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback_bytes = 1024 }); defer t.deinit(alloc); const first_page = t.screens.active.pages.pages.first.?; @@ -10708,7 +10738,7 @@ test "Terminal: deleteLines hyperlink-dense row crosses page boundary" { // this exercises the cursor accounting of the capacity increase. const alloc = testing.allocator; const io_impl = testing.io; - var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 }); + var t = try init(io_impl, alloc, .{ .rows = 5, .cols = 10, .max_scrollback_bytes = 1024 }); defer t.deinit(alloc); const pages = &t.screens.active.pages; diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index e76db9490..6a680c904 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -365,7 +365,7 @@ fn new_( .{ .cols = opts.cols, .rows = opts.rows, - .max_scrollback = opts.max_scrollback, + .max_scrollback_bytes = opts.max_scrollback, }, ); errdefer t.deinit(alloc); diff --git a/src/terminal/render.zig b/src/terminal/render.zig index c70e69627..867cd6224 100644 --- a/src/terminal/render.zig +++ b/src/terminal/render.zig @@ -1427,7 +1427,7 @@ test "incremental updates match full rebuild" { var t = try Terminal.init(io, alloc, .{ .cols = 20, .rows = 8, - .max_scrollback = 500, + .max_scrollback_bytes = 500, }); defer t.deinit(alloc); @@ -2010,7 +2010,7 @@ test "linkCells with scrollback spanning pages" { var t = try Terminal.init(io, alloc, .{ .cols = page.std_capacity.cols, .rows = viewport_rows, - .max_scrollback = 10_000, + .max_scrollback_bytes = 10_000, }); defer t.deinit(alloc); diff --git a/src/terminal/search/pagelist.zig b/src/terminal/search/pagelist.zig index f9fb3c9c1..519d1ce76 100644 --- a/src/terminal/search/pagelist.zig +++ b/src/terminal/search/pagelist.zig @@ -381,7 +381,7 @@ test "compressed history match spanning page boundary remains compressed" { var t: Terminal = try .init(io, alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 10 * 1024 * 1024, + .max_scrollback_bytes = 10 * 1024 * 1024, }); defer t.deinit(alloc); @@ -487,7 +487,11 @@ test "feed with pruned page" { const alloc = testing.allocator; // Zero here forces minimum max size to effectively two pages. - var p: PageList = try .init(alloc, 80, 24, 0); + var p: PageList = try .init(alloc, .{ + .cols = 80, + .rows = 24, + .max_size = 0, + }); defer p.deinit(); // Grow to capacity @@ -530,7 +534,10 @@ test "feed with pruned page" { test "feed keeps its tracked pin within a shorter page" { const alloc = testing.allocator; - var pages: PageList = try .init(alloc, 10, 2, null); + var pages: PageList = try .init(alloc, .{ + .cols = 10, + .rows = 2, + }); defer pages.deinit(); const first = pages.pages.first.?; diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig index fee53e007..81e43ecaf 100644 --- a/src/terminal/search/screen.zig +++ b/src/terminal/search/screen.zig @@ -977,7 +977,7 @@ test "simple search with history" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1019,7 +1019,7 @@ test "reload active with history change" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1207,7 +1207,7 @@ test "select after resize resets stale flattened results" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 3, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); @@ -1307,7 +1307,7 @@ test "select into history" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1485,7 +1485,7 @@ test "select prev with history" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1540,7 +1540,7 @@ test "select prev wraps when all matches are in history" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1605,7 +1605,7 @@ test "select after partial history erase drops a pruned selection" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1638,7 +1638,7 @@ test "select after history compaction ignores replaced results" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1673,7 +1673,7 @@ test "select after partial history page erase ignores shifted results" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1707,7 +1707,7 @@ test "reload defers pruning unselected history results" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1742,7 +1742,7 @@ test "reload after partial history page erase drops shifted selection first" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1781,7 +1781,7 @@ test "select after history page split ignores moved results" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1815,7 +1815,7 @@ test "screen search no scrollback has no history" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer t.deinit(alloc); @@ -1852,7 +1852,7 @@ test "reloadActive partial history cleanup on appendSlice error" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1900,7 +1900,7 @@ test "reloadActive partial history cleanup on loop append error" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; @@ -1947,7 +1947,7 @@ test "select after clearing scrollback" { var t: Terminal = try .init(io, alloc, .{ .cols = 10, .rows = 2, - .max_scrollback = std.math.maxInt(usize), + .max_scrollback_bytes = std.math.maxInt(usize), }); defer t.deinit(alloc); const list: *PageList = &t.screens.active.pages; diff --git a/src/terminal/search/sliding_window.zig b/src/terminal/search/sliding_window.zig index e83a187ae..95b37d730 100644 --- a/src/terminal/search/sliding_window.zig +++ b/src/terminal/search/sliding_window.zig @@ -722,7 +722,7 @@ test "SlidingWindow empty needle has no matches" { var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); try s.testWriteString("hello"); @@ -739,7 +739,7 @@ test "SlidingWindow single append" { var w: SlidingWindow = try .init(alloc, .forward, "boo!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -785,7 +785,7 @@ test "SlidingWindow single append case insensitive ASCII" { var w: SlidingWindow = try .init(alloc, .forward, "Boo!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -831,7 +831,7 @@ test "SlidingWindow single append single char" { var w: SlidingWindow = try .init(alloc, .forward, "b"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -877,7 +877,7 @@ test "SlidingWindow single append no match" { var w: SlidingWindow = try .init(alloc, .forward, "nope!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -902,7 +902,7 @@ test "SlidingWindow two pages" { var w: SlidingWindow = try .init(alloc, .forward, "boo!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -958,7 +958,7 @@ test "SlidingWindow two pages single char" { var w: SlidingWindow = try .init(alloc, .forward, "b"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1014,7 +1014,7 @@ test "SlidingWindow two pages match across boundary" { var w: SlidingWindow = try .init(alloc, .forward, "hello, world"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1060,7 +1060,7 @@ test "SlidingWindow two pages no match across boundary with newline" { var w: SlidingWindow = try .init(alloc, .forward, "hello, world"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1094,7 +1094,7 @@ test "SlidingWindow two pages no match across boundary with newline reverse" { var w: SlidingWindow = try .init(alloc, .reverse, "hello, world"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1125,7 +1125,7 @@ test "SlidingWindow two pages no match prunes first page" { var w: SlidingWindow = try .init(alloc, .forward, "nope!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1158,7 +1158,7 @@ test "SlidingWindow two pages no match keeps both pages" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1202,7 +1202,7 @@ test "SlidingWindow single append across circular buffer boundary" { var w: SlidingWindow = try .init(alloc, .forward, "abc"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("XXXXXXXXXXXXXXXXXXXboo!XXXXX"); @@ -1259,7 +1259,7 @@ test "SlidingWindow single append match on boundary" { var w: SlidingWindow = try .init(alloc, .forward, "abcd"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("o!XXXXXXXXXXXXXXXXXXXbo"); @@ -1319,7 +1319,7 @@ test "SlidingWindow single append reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "boo!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -1365,7 +1365,7 @@ test "SlidingWindow single append no match reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "nope!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("hello. boo! hello. boo!"); @@ -1390,7 +1390,7 @@ test "SlidingWindow two pages reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "boo!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1446,7 +1446,7 @@ test "SlidingWindow two pages match across boundary reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "hello, world"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1502,7 +1502,7 @@ test "SlidingWindow two pages no match prunes first page reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "nope!"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1535,7 +1535,7 @@ test "SlidingWindow two pages no match keeps both pages reversed" { const alloc = testing.allocator; const io = testing.io; - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 1000 }); defer s.deinit(); // Fill up the first page. The final bytes in the first page @@ -1579,7 +1579,7 @@ test "SlidingWindow single append across circular buffer boundary reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "abc"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("XXXXXXXXXXXXXXXXXXXboo!XXXXX"); @@ -1637,7 +1637,7 @@ test "SlidingWindow single append match on boundary reversed" { var w: SlidingWindow = try .init(alloc, .reverse, "abcd"); defer w.deinit(); - var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); + var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, .max_scrollback_bytes = 0 }); defer s.deinit(); try s.testWriteString("o!XXXXXXXXXXXXXXXXXXXbo"); @@ -1779,7 +1779,7 @@ test "SlidingWindow append whitespace only node" { var s = try Screen.init(io, alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 0, + .max_scrollback_bytes = 0, }); defer s.deinit(); diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index 61e402a4f..021e1892c 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -2239,7 +2239,7 @@ test "stream: CSI W with intermediate but no params" { var t: Terminal = try .init(testing.io, testing.allocator, .{ .cols = 80, .rows = 24, - .max_scrollback = 100, + .max_scrollback_bytes = 100, }); defer t.deinit(testing.allocator); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 5d7a47d25..883d8d5de 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -246,7 +246,8 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { break :opts .{ .cols = grid_size.columns, .rows = grid_size.rows, - .max_scrollback = opts.full_config.@"scrollback-limit", + .max_scrollback_bytes = opts.full_config.@"scrollback-limit-bytes".optional(), + .max_scrollback_lines = opts.full_config.@"scrollback-limit-lines".optional(), .default_modes = default_modes, .default_cursor_style = opts.config.cursor_style, .default_cursor_blink = opts.config.cursor_blink, diff --git a/test/fuzz-libghostty/src/fuzz_stream.zig b/test/fuzz-libghostty/src/fuzz_stream.zig index 4c14086f0..6f19b4b19 100644 --- a/test/fuzz-libghostty/src/fuzz_stream.zig +++ b/test/fuzz-libghostty/src/fuzz_stream.zig @@ -44,7 +44,7 @@ pub export fn zig_fuzz_test( var t = Terminal.init(threaded.io(), alloc, .{ .cols = 80, .rows = 24, - .max_scrollback = 100, + .max_scrollback_bytes = 100, }) catch return; defer t.deinit(alloc);