vt: add render_row_iterator_next

This commit is contained in:
Mitchell Hashimoto
2026-03-19 10:06:19 -07:00
parent ad0e47ebac
commit f610d7e00f
4 changed files with 85 additions and 8 deletions

View File

@@ -227,6 +227,20 @@ GhosttyResult ghostty_render_state_row_iterator_new(
GhosttyRenderState state,
GhosttyRenderStateRowIterator* out_iterator);
/**
* Move a render-state row iterator to the next row.
*
* Returns true if the iterator moved successfully and row data is
* available to read at the new position.
*
* @param iterator The iterator handle to advance (may be NULL)
* @return true if advanced to the next row, false if `iterator` is
* NULL or if the iterator has reached the end
*
* @ingroup render
*/
bool ghostty_render_state_row_iterator_next(GhosttyRenderStateRowIterator iterator);
/**
* Free a render-state row iterator.
*

View File

@@ -194,6 +194,7 @@ comptime {
@export(&c.render_state_dirty_get, .{ .name = "ghostty_render_state_dirty_get" });
@export(&c.render_state_dirty_set, .{ .name = "ghostty_render_state_dirty_set" });
@export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" });
@export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" });
@export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" });
@export(&c.render_state_free, .{ .name = "ghostty_render_state_free" });
@export(&c.terminal_new, .{ .name = "ghostty_terminal_new" });

View File

@@ -44,6 +44,7 @@ pub const render_state_colors_get = render.colors_get;
pub const render_state_dirty_get = render.dirty_get;
pub const render_state_dirty_set = render.dirty_set;
pub const render_state_row_iterator_new = render.row_iterator_new;
pub const render_state_row_iterator_next = render.row_iterator_next;
pub const render_state_row_iterator_free = render.row_iterator_free;
pub const sgr_new = sgr.new;

View File

@@ -20,7 +20,7 @@ const RowIteratorWrapper = struct {
alloc: std.mem.Allocator,
/// The current index (also y value) into the row list.
y: size.CellCountInt,
y: ?size.CellCountInt,
/// These are the raw pointers into the render state data.
raws: []const page.Row,
@@ -69,6 +69,13 @@ fn new_(alloc_: ?*const CAllocator) error{OutOfMemory}!*RenderStateWrapper {
return ptr;
}
pub fn free(state_: RenderState) callconv(.c) void {
const state = state_ orelse return;
const alloc = state.alloc;
state.state.deinit(alloc);
alloc.destroy(state);
}
pub fn update(
state_: RenderState,
terminal_: terminal_c.Terminal,
@@ -208,7 +215,7 @@ fn row_iterator_new_(
const row_data = state.state.row_data.slice();
it.* = .{
.alloc = alloc,
.y = 0,
.y = null,
.raws = row_data.items(.raw),
.cells = row_data.items(.cells),
.dirty = row_data.items(.dirty),
@@ -223,11 +230,12 @@ pub fn row_iterator_free(iterator_: RowIterator) callconv(.c) void {
alloc.destroy(iterator);
}
pub fn free(state_: RenderState) callconv(.c) void {
const state = state_ orelse return;
const alloc = state.alloc;
state.state.deinit(alloc);
alloc.destroy(state);
pub fn row_iterator_next(iterator_: RowIterator) callconv(.c) bool {
const it = iterator_ orelse return false;
const next_y: size.CellCountInt = if (it.y) |y| y + 1 else 0;
if (next_y >= it.raws.len) return false;
it.y = next_y;
return true;
}
test "render: new/free" {
@@ -410,7 +418,7 @@ test "render: row iterator new/free" {
const iterator_ptr = iterator.?;
const row_data = state.?.state.row_data.slice();
try testing.expectEqual(@as(size.CellCountInt, 0), iterator_ptr.y);
try testing.expectEqual(@as(?size.CellCountInt, null), iterator_ptr.y);
try testing.expectEqual(row_data.items(.raw).len, iterator_ptr.raws.len);
try testing.expectEqual(row_data.items(.cells).len, iterator_ptr.cells.len);
try testing.expectEqual(row_data.items(.dirty).len, iterator_ptr.dirty.len);
@@ -420,6 +428,59 @@ test "render: row iterator free null" {
row_iterator_free(null);
}
test "render: row iterator next null" {
try testing.expect(!row_iterator_next(null));
}
test "render: row iterator next" {
var terminal: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib_alloc.test_allocator,
&terminal,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 10_000,
},
));
defer terminal_c.free(terminal);
var state: RenderState = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&state,
));
defer free(state);
try testing.expectEqual(Result.success, update(state, terminal));
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
const rows = state.?.state.rows;
if (rows == 0) {
try testing.expect(!row_iterator_next(iterator));
return;
}
try testing.expect(row_iterator_next(iterator));
try testing.expectEqual(@as(?size.CellCountInt, 0), iterator.?.y);
var i: size.CellCountInt = 1;
while (i < rows) : (i += 1) {
try testing.expect(row_iterator_next(iterator));
try testing.expectEqual(@as(?size.CellCountInt, i), iterator.?.y);
}
try testing.expect(!row_iterator_next(iterator));
try testing.expectEqual(@as(?size.CellCountInt, rows - 1), iterator.?.y);
}
test "render: update" {
var terminal: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(