From 75b49051a3bb2cd8da564e0a72ef8b0abacf8dc2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Mar 2026 07:21:36 -0700 Subject: [PATCH] vt: add GhosttyRenderStateRowCells opaque type Add a new opaque RowCells type that wraps per-row cell data (raw cells, graphemes, styles) for the C API. The caller allocates a RowCells handle via row_cells_new, then populates it by passing it to row_get with the new .cells data kind. This queries the current row from the iterator and slices the underlying MultiArrayList into the RowCellsWrapper fields. The new type and functions are wired through main.zig, lib_vt.zig, and the render.h C header. --- include/ghostty/vt/render.h | 41 +++++++++++++++++++++++++++++ src/lib_vt.zig | 2 ++ src/terminal/c/main.zig | 2 ++ src/terminal/c/render.zig | 52 +++++++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 2 deletions(-) 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;