terminal: updating render state with tests

pull/9662/head
Mitchell Hashimoto 2025-11-18 06:18:51 -10:00
parent 3f7cee1e99
commit a860801323
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 43 additions and 7 deletions

View File

@ -42,7 +42,7 @@ pub const RenderState = struct {
/// area and scrolling with new output.
viewport_is_bottom: bool,
/// The rows (y=0 is top) of the viewport.
/// The rows (y=0 is top) of the viewport. Guaranteed to be `rows` length.
///
/// This is a MultiArrayList because only the update cares about
/// the allocators. Callers care about all the other properties, and
@ -75,7 +75,7 @@ pub const RenderState = struct {
/// Arena used for any heap allocations for this row,
arena: ArenaAllocator.State,
/// The cells in this row, always `cols` length.
/// The cells in this row. Guaranteed to be `cols` length.
cells: std.MultiArrayList(Cell),
/// A dirty flag that can be used by the renderer to track
@ -113,7 +113,8 @@ pub const RenderState = struct {
alloc: Allocator,
t: *Terminal,
) Allocator.Error!void {
self.redraw = redraw: {
const s: *Screen = t.screens.active;
const redraw = redraw: {
// If our screen key changed, we need to do a full rebuild
// because our render state is viewport-specific.
if (t.screens.active_key != self.screen) break :redraw true;
@ -134,17 +135,23 @@ pub const RenderState = struct {
if (v > 0) break :redraw true;
}
// If our dimensions changed, we do a full rebuild.
if (self.rows != s.pages.rows or
self.cols != s.pages.cols)
{
break :redraw true;
}
break :redraw false;
};
// Full redraw resets our state completely.
if (self.redraw) {
if (redraw) {
self.* = .empty;
self.screen = t.screens.active_key;
self.redraw = true;
}
const s: *Screen = t.screens.active;
// Always set our cheap fields, its more expensive to compare
self.rows = s.pages.rows;
self.cols = s.pages.cols;
@ -186,7 +193,7 @@ pub const RenderState = struct {
var y: size.CellCountInt = 0;
while (row_it.next()) |row_pin| : (y = y + 1) {
// If the row isn't dirty then we assume it is unchanged.
if (!self.redraw and !row_pin.isDirty()) continue;
if (!redraw and !row_pin.isDirty()) continue;
// Promote our arena. State is copied by value so we need to
// restore it on all exit paths so we don't leak memory.
@ -306,3 +313,32 @@ test {
defer state.deinit(alloc);
try state.update(alloc, &t);
}
test "basic text" {
const testing = std.testing;
const alloc = testing.allocator;
var t = try Terminal.init(alloc, .{
.cols = 10,
.rows = 3,
});
defer t.deinit(alloc);
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD");
var state: RenderState = .empty;
defer state.deinit(alloc);
try state.update(alloc, &t);
// Verify we have the right number of rows
const row_data = state.row_data.slice();
try testing.expectEqual(3, row_data.len);
// All rows should have cols cells
const cells = row_data.items(.cells);
try testing.expectEqual(10, cells[0].len);
try testing.expectEqual(10, cells[1].len);
try testing.expectEqual(10, cells[2].len);
}