libghostty: expose row cell styling bit

Add a render row-cells data key for querying whether the current cell has
explicit styling. This lets consumers avoid fetching a raw cell or full style
snapshot when all they need is the cell's HasStyling bit.

The new key is appended to the existing enum for ABI safety and is served by
the existing row-cells getter path. Existing data keys and function exports are
unchanged.
This commit is contained in:
Mitchell Hashimoto
2026-05-27 21:09:00 -07:00
parent 15264856f6
commit 8beea5f92d
2 changed files with 71 additions and 1 deletions

View File

@@ -607,6 +607,13 @@ typedef enum GHOSTTY_ENUM_TYPED {
* GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION once per row and applying that
* range directly, avoiding one C API call per cell for selection state. */
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7,
/** Whether the cell has any explicit styling (bool).
* This is equivalent to querying the raw cell's
* GHOSTTY_CELL_DATA_HAS_STYLING value, but avoids materializing the raw
* GhosttyCell for renderers that only need to know whether fetching the
* full style is necessary. */
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING = 8,
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyRenderStateRowCellsData;

View File

@@ -466,6 +466,7 @@ pub const RowCellsData = enum(c_int) {
bg_color = 5,
fg_color = 6,
selected = 7,
has_styling = 8,
/// Output type expected for querying the data of the given kind.
pub fn OutType(comptime self: RowCellsData) type {
@@ -476,7 +477,7 @@ pub const RowCellsData = enum(c_int) {
.graphemes_len => u32,
.graphemes_buf => u32,
.bg_color, .fg_color => colorpkg.RGB.C,
.selected => bool,
.selected, .has_styling => bool,
};
}
};
@@ -571,6 +572,7 @@ fn rowCellsGetTyped(
x >= sel[0] and x <= sel[1]
else
false,
.has_styling => out.* = cell.hasStyling(),
}
return .success;
@@ -1193,6 +1195,67 @@ test "render: row cells get selected" {
try testing.expect(selected);
}
test "render: row cells get has_styling" {
var terminal: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(
&lib.alloc.test_allocator,
&terminal,
.{
.cols = 10,
.rows = 3,
.max_scrollback = 10_000,
},
));
defer terminal_c.free(terminal);
const input = "A\x1b[31mB";
terminal_c.vt_write(terminal, input, input.len);
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 it: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib.alloc.test_allocator,
&it,
));
defer row_iterator_free(it);
var cells: RowCells = null;
try testing.expectEqual(Result.success, row_cells_new(
&lib.alloc.test_allocator,
&cells,
));
defer row_cells_free(cells);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it)));
try testing.expect(row_iterator_next(it));
try testing.expectEqual(Result.success, row_get(it, .cells, @ptrCast(&cells)));
var has_styling = true;
try testing.expectEqual(Result.success, row_cells_select(cells, 0));
try testing.expectEqual(Result.success, row_cells_get(cells, .has_styling, @ptrCast(&has_styling)));
try testing.expect(!has_styling);
try testing.expectEqual(Result.success, row_cells_select(cells, 1));
try testing.expectEqual(Result.success, row_cells_get(cells, .has_styling, @ptrCast(&has_styling)));
try testing.expect(has_styling);
has_styling = false;
var written: usize = 0;
const keys = [_]RowCellsData{.has_styling};
var values = [_]?*anyopaque{@ptrCast(&has_styling)};
try testing.expectEqual(Result.success, row_cells_get_multi(cells, keys.len, &keys, &values, &written));
try testing.expectEqual(keys.len, written);
try testing.expect(has_styling);
}
test "render: row iterator next" {
var terminal: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(