mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
vt: add cell-level iteration and data access to render state row cells
Add next, select, and get functions to the render state row cells API, mirroring the row iterator pattern. row_cells_next advances to the next cell sequentially, row_cells_select jumps to a specific column index with bounds validation, and row_cells_get queries data for the current cell position. The get function supports querying raw cell values (GhosttyCell), resolved styles (GhosttyStyle), grapheme codepoint counts, and writing grapheme codepoints into a caller-provided buffer. Also add Cell.C and Cell.cval() to page.zig, matching the existing Row.C/Row.cval() pattern, so the render state can convert cells to the C ABI type without a raw bitCast.
This commit is contained in:
@@ -411,6 +411,84 @@ GhosttyResult ghostty_render_state_row_cells_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttyRenderStateRowCells* out_cells);
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_render_state_row_cells_get().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_INVALID = 0,
|
||||
|
||||
/** The raw cell value (GhosttyCell). */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW = 1,
|
||||
|
||||
/** The style for the current cell (GhosttyStyle). */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE = 2,
|
||||
|
||||
/** The total number of grapheme codepoints including the base codepoint
|
||||
* (uint32_t). Returns 0 if the cell has no text. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3,
|
||||
|
||||
/** Write grapheme codepoints into a caller-provided buffer (uint32_t*).
|
||||
* The buffer must be at least graphemes_len elements. The base codepoint
|
||||
* is written first, followed by any extra codepoints. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4,
|
||||
} GhosttyRenderStateRowCellsData;
|
||||
|
||||
/**
|
||||
* Move a render-state row cells iterator to the next cell.
|
||||
*
|
||||
* Returns true if the iterator moved successfully and cell data is
|
||||
* available to read at the new position.
|
||||
*
|
||||
* @param cells The row cells handle to advance (may be NULL)
|
||||
* @return true if advanced to the next cell, false if `cells` is
|
||||
* NULL or if the iterator has reached the end
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
bool ghostty_render_state_row_cells_next(GhosttyRenderStateRowCells cells);
|
||||
|
||||
/**
|
||||
* Move a render-state row cells iterator to a specific column.
|
||||
*
|
||||
* Positions the iterator at the given x (column) index so that
|
||||
* subsequent reads return data for that cell.
|
||||
*
|
||||
* @param cells The row cells handle to reposition (NULL returns
|
||||
* GHOSTTY_INVALID_VALUE)
|
||||
* @param x The zero-based column index to select
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `cells`
|
||||
* is NULL or `x` is out of range
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_cells_select(
|
||||
GhosttyRenderStateRowCells cells, uint16_t x);
|
||||
|
||||
/**
|
||||
* Get a value from the current cell in a render-state row cells iterator.
|
||||
*
|
||||
* The `out` pointer must point to a value of the type corresponding to the
|
||||
* requested data kind (see GhosttyRenderStateRowCellsData).
|
||||
* Call ghostty_render_state_row_cells_next() or
|
||||
* ghostty_render_state_row_cells_select() at least once before
|
||||
* calling this function.
|
||||
*
|
||||
* @param cells The row cells handle to query (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param data The data kind to query
|
||||
* @param[out] out Pointer to receive the queried value
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if
|
||||
* `cells` is NULL or the iterator is not positioned on a cell
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_cells_get(
|
||||
GhosttyRenderStateRowCells cells,
|
||||
GhosttyRenderStateRowCellsData data,
|
||||
void* out);
|
||||
|
||||
/**
|
||||
* Free a row cells instance.
|
||||
*
|
||||
|
||||
@@ -198,6 +198,9 @@ comptime {
|
||||
@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_next, .{ .name = "ghostty_render_state_row_cells_next" });
|
||||
@export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" });
|
||||
@export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" });
|
||||
@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" });
|
||||
|
||||
@@ -48,6 +48,9 @@ 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_next = render.row_cells_next;
|
||||
pub const render_state_row_cells_select = render.row_cells_select;
|
||||
pub const render_state_row_cells_get = render.row_cells_get;
|
||||
pub const render_state_row_cells_free = render.row_cells_free;
|
||||
|
||||
pub const sgr_new = sgr.new;
|
||||
|
||||
@@ -12,6 +12,7 @@ const terminal_c = @import("terminal.zig");
|
||||
const renderpkg = @import("../render.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
const row = @import("row.zig");
|
||||
const style_c = @import("style.zig");
|
||||
|
||||
const log = std.log.scoped(.render_state_c);
|
||||
|
||||
@@ -331,12 +332,103 @@ pub fn row_cells_new(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn row_cells_next(cells_: RowCells) callconv(.c) bool {
|
||||
const cells = cells_ orelse return false;
|
||||
const next_x: size.CellCountInt = if (cells.x) |x| x + 1 else 0;
|
||||
if (next_x >= cells.raws.len) return false;
|
||||
cells.x = next_x;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn row_cells_select(cells_: RowCells, x: size.CellCountInt) callconv(.c) Result {
|
||||
const cells = cells_ orelse return .invalid_value;
|
||||
if (x >= cells.raws.len) return .invalid_value;
|
||||
cells.x = x;
|
||||
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: GhosttyRenderStateRowCellsData
|
||||
pub const RowCellsData = enum(c_int) {
|
||||
invalid = 0,
|
||||
raw = 1,
|
||||
style = 2,
|
||||
graphemes_len = 3,
|
||||
graphemes_buf = 4,
|
||||
|
||||
/// Output type expected for querying the data of the given kind.
|
||||
pub fn OutType(comptime self: RowCellsData) type {
|
||||
return switch (self) {
|
||||
.invalid => void,
|
||||
.raw => page.Cell.C,
|
||||
.style => style_c.Style,
|
||||
.graphemes_len => u32,
|
||||
.graphemes_buf => [*]u32,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn row_cells_get(
|
||||
cells_: RowCells,
|
||||
data: RowCellsData,
|
||||
out: ?*anyopaque,
|
||||
) callconv(.c) Result {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(RowCellsData, @intFromEnum(data)) catch {
|
||||
log.warn("render_state_row_cells_get invalid data value={d}", .{@intFromEnum(data)});
|
||||
return .invalid_value;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
inline else => |comptime_data| rowCellsGetTyped(
|
||||
cells_,
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn rowCellsGetTyped(
|
||||
cells_: RowCells,
|
||||
comptime data: RowCellsData,
|
||||
out: *data.OutType(),
|
||||
) Result {
|
||||
const cells = cells_ orelse return .invalid_value;
|
||||
const x = cells.x orelse return .invalid_value;
|
||||
const cell = cells.raws[x];
|
||||
switch (data) {
|
||||
.invalid => return .invalid_value,
|
||||
.raw => out.* = cell.cval(),
|
||||
.style => out.* = style_c.Style.fromStyle(cells.styles[x]),
|
||||
.graphemes_len => {
|
||||
if (!cell.hasText()) {
|
||||
out.* = 0;
|
||||
return .success;
|
||||
}
|
||||
const extra = if (cell.hasGrapheme()) cells.graphemes[x] else &[_]u21{};
|
||||
out.* = @intCast(1 + extra.len);
|
||||
},
|
||||
.graphemes_buf => {
|
||||
if (!cell.hasText()) return .success;
|
||||
const extra = if (cell.hasGrapheme()) cells.graphemes[x] else &[_]u21{};
|
||||
const total = 1 + extra.len;
|
||||
const out_slice = out.*[0..total];
|
||||
out_slice[0] = cell.codepoint();
|
||||
for (extra, 1..) |cp, i| {
|
||||
out_slice[i] = cp;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// C: GhosttyRenderStateRowData
|
||||
pub const RowData = enum(c_int) {
|
||||
invalid = 0,
|
||||
|
||||
@@ -2059,6 +2059,14 @@ pub const Cell = packed struct(u64) {
|
||||
prompt = 2,
|
||||
};
|
||||
|
||||
/// C ABI type.
|
||||
pub const C = u64;
|
||||
|
||||
/// Returns this cell as a C ABI value.
|
||||
pub fn cval(self: Cell) C {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
/// Helper to make a cell that just has a codepoint.
|
||||
pub fn init(cp: u21) Cell {
|
||||
// We have to use this bitCast here to ensure that our memory is
|
||||
|
||||
Reference in New Issue
Block a user