diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 1738198ce..23f11525d 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -79,6 +79,13 @@ typedef struct GhosttyRenderState* GhosttyRenderState; */ typedef struct GhosttyRenderStateRowIterator* GhosttyRenderStateRowIterator; +/** + * Opaque handle to render-state row cells. + * + * @ingroup render + */ +typedef struct GhosttyRenderStateRowCells* GhosttyRenderStateRowCells; + /** * Dirty state of a render state after update. * @@ -138,6 +145,10 @@ typedef enum { /** The raw row value (GhosttyRow). */ GHOSTTY_RENDER_STATE_ROW_DATA_RAW = 2, + + /** Populate a pre-allocated GhosttyRenderStateRowCells with cell data for + * the current row (GhosttyRenderStateRowCells). */ + GHOSTTY_RENDER_STATE_ROW_DATA_CELLS = 3, } GhosttyRenderStateRowData; /** @@ -370,6 +381,36 @@ GhosttyResult ghostty_render_state_row_set( GhosttyRenderStateRowOption option, const void* value); +/** + * Create a new row cells instance. + * + * All fields except the allocator are left undefined until populated + * via ghostty_render_state_row_get() with + * GHOSTTY_RENDER_STATE_ROW_DATA_CELLS. + * + * You can reuse this value repeatedly with ghostty_render_state_row_get() to + * avoid allocating a new cells container for every row. + * + * @param allocator Pointer to allocator, or NULL to use the default allocator + * @param[out] out_cells On success, receives the created row cells handle + * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation + * failure + * + * @ingroup render + */ +GhosttyResult ghostty_render_state_row_cells_new( + const GhosttyAllocator* allocator, + GhosttyRenderStateRowCells* out_cells); + +/** + * Free a row cells instance. + * + * @param cells The row cells handle to free (may be NULL) + * + * @ingroup render + */ +void ghostty_render_state_row_cells_free(GhosttyRenderStateRowCells cells); + /** @} */ #ifdef __cplusplus diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 3eb2dbec7..c6f019f76 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -197,6 +197,8 @@ comptime { @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); @export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" }); @export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" }); + @export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" }); + @export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" }); @export(&c.render_state_free, .{ .name = "ghostty_render_state_free" }); @export(&c.terminal_new, .{ .name = "ghostty_terminal_new" }); @export(&c.terminal_free, .{ .name = "ghostty_terminal_free" }); diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index d1771928f..ba456e13d 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -47,6 +47,8 @@ pub const render_state_row_iterator_next = render.row_iterator_next; pub const render_state_row_get = render.row_get; pub const render_state_row_set = render.row_set; pub const render_state_row_iterator_free = render.row_iterator_free; +pub const render_state_row_cells_new = render.row_cells_new; +pub const render_state_row_cells_free = render.row_cells_free; pub const sgr_new = sgr.new; pub const sgr_free = sgr.free; diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index 740fcaa09..a2ecb1292 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -32,12 +32,23 @@ const RowIteratorWrapper = struct { dirty: []bool, }; +const RowCellsWrapper = struct { + alloc: std.mem.Allocator, + x: ?size.CellCountInt, + raws: []const page.Cell, + graphemes: []const []const u21, + styles: []const Style, +}; + /// C: GhosttyRenderState pub const RenderState = ?*RenderStateWrapper; /// C: GhosttyRenderStateRowIterator pub const RowIterator = ?*RowIteratorWrapper; +/// C: GhosttyRenderStateRowCells +pub const RowCells = ?*RowCellsWrapper; + /// C: GhosttyRenderStateDirty pub const Dirty = renderpkg.RenderState.Dirty; @@ -253,8 +264,6 @@ pub fn colors_get( return .success; } - - pub fn row_iterator_new( alloc_: ?*const CAllocator, state_: RenderState, @@ -310,11 +319,38 @@ pub fn row_iterator_next(iterator_: RowIterator) callconv(.c) bool { return true; } +pub fn row_cells_new( + alloc_: ?*const CAllocator, + result: *RowCells, +) callconv(.c) Result { + const alloc = lib_alloc.default(alloc_); + const ptr = alloc.create(RowCellsWrapper) catch { + result.* = null; + return .out_of_memory; + }; + ptr.* = .{ + .alloc = alloc, + .x = undefined, + .raws = undefined, + .graphemes = undefined, + .styles = undefined, + }; + result.* = ptr; + return .success; +} + +pub fn row_cells_free(cells_: RowCells) callconv(.c) void { + const cells = cells_ orelse return; + const alloc = cells.alloc; + alloc.destroy(cells); +} + /// C: GhosttyRenderStateRowData pub const RowData = enum(c_int) { invalid = 0, dirty = 1, raw = 2, + cells = 3, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: RowData) type { @@ -322,6 +358,7 @@ pub const RowData = enum(c_int) { .invalid => void, .dirty => bool, .raw => row.CRow, + .cells => RowCells, }; } }; @@ -370,6 +407,17 @@ fn rowGetTyped( .invalid => return .invalid_value, .dirty => out.* = it.dirty[y], .raw => out.* = it.raws[y].cval(), + .cells => { + const cells = out.* orelse return .invalid_value; + const cell_data = it.cells[y].slice(); + cells.* = .{ + .alloc = cells.alloc, + .x = null, + .raws = cell_data.items(.raw), + .graphemes = cell_data.items(.grapheme), + .styles = cell_data.items(.style), + }; + }, } return .success;