mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
libghostty: add render state structured cursor read
A normal renderer would have to call `ghostty_render_state_get` _eight times_ to reconstruct the cursor. In languages where FFI is expensive (Go, wasm, etc.), this showed up in profiles of every frame. Add a sized cursor snapshot and expose it. Also expose the existing color snapshot through ghostty_render_state_get and remove the older dedicated color getter.
This commit is contained in:
@@ -97,7 +97,8 @@ int main(void) {
|
||||
// state. These are needed to resolve palette-indexed cell colors.
|
||||
GhosttyRenderStateColors colors =
|
||||
GHOSTTY_INIT_SIZED(GhosttyRenderStateColors);
|
||||
result = ghostty_render_state_colors_get(render_state, &colors);
|
||||
result = ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_COLORS, &colors);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
printf("Background: #%02x%02x%02x\n",
|
||||
@@ -107,31 +108,16 @@ int main(void) {
|
||||
//! [render-colors]
|
||||
|
||||
//! [render-cursor]
|
||||
// Read cursor position and visual style from the render state.
|
||||
bool cursor_visible = false;
|
||||
ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE,
|
||||
&cursor_visible);
|
||||
|
||||
bool cursor_in_viewport = false;
|
||||
ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE,
|
||||
&cursor_in_viewport);
|
||||
|
||||
if (cursor_visible && cursor_in_viewport) {
|
||||
uint16_t cx, cy;
|
||||
ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X, &cx);
|
||||
ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y, &cy);
|
||||
|
||||
GhosttyRenderStateCursorVisualStyle style;
|
||||
ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE,
|
||||
&style);
|
||||
// Read all cursor state in one call.
|
||||
GhosttyRenderStateCursor cursor =
|
||||
GHOSTTY_INIT_SIZED(GhosttyRenderStateCursor);
|
||||
result = ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_CURSOR, &cursor);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
if (cursor.visible && cursor.viewport_has_value) {
|
||||
const char* style_name = "unknown";
|
||||
switch (style) {
|
||||
switch (cursor.visual_style) {
|
||||
case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR:
|
||||
style_name = "bar";
|
||||
break;
|
||||
@@ -145,7 +131,8 @@ int main(void) {
|
||||
style_name = "hollow";
|
||||
break;
|
||||
}
|
||||
printf("Cursor at (%u, %u), style: %s\n", cx, cy, style_name);
|
||||
printf("Cursor at (%u, %u), style: %s\n",
|
||||
cursor.viewport_x, cursor.viewport_y, style_name);
|
||||
}
|
||||
//! [render-cursor]
|
||||
|
||||
|
||||
@@ -199,6 +199,14 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Whether the cursor is on the tail of a wide character (bool).
|
||||
* Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL = 17,
|
||||
|
||||
/** All cursor state in one sized struct (GhosttyRenderStateCursor).
|
||||
* Initialize the output with GHOSTTY_INIT_SIZED before querying. */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR = 18,
|
||||
|
||||
/** All render-state colors in one sized struct (GhosttyRenderStateColors).
|
||||
* Initialize the output with GHOSTTY_INIT_SIZED before querying. */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLORS = 19,
|
||||
GHOSTTY_RENDER_STATE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyRenderStateData;
|
||||
|
||||
@@ -286,17 +294,59 @@ typedef struct {
|
||||
uint16_t end_x;
|
||||
} GhosttyRenderStateRowSelection;
|
||||
|
||||
/**
|
||||
* Render-state cursor information.
|
||||
*
|
||||
* This struct uses the sized-struct ABI pattern. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyRenderStateCursor) before querying
|
||||
* GHOSTTY_RENDER_STATE_DATA_CURSOR.
|
||||
*
|
||||
* When viewport_has_value is false, viewport_x, viewport_y, and wide_tail
|
||||
* contain undefined data and must not be read.
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyRenderStateCursor). */
|
||||
size_t size;
|
||||
|
||||
/** Whether the cursor is visible within the viewport. */
|
||||
bool viewport_has_value;
|
||||
|
||||
/** Cursor viewport x position in cells. */
|
||||
uint16_t viewport_x;
|
||||
|
||||
/** Cursor viewport y position in cells. */
|
||||
uint16_t viewport_y;
|
||||
|
||||
/** Whether the cursor is on the tail of a wide character. */
|
||||
bool wide_tail;
|
||||
|
||||
/** Whether the cursor is visible based on terminal modes. */
|
||||
bool visible;
|
||||
|
||||
/** Whether the cursor should blink based on terminal modes. */
|
||||
bool blinking;
|
||||
|
||||
/** Whether the cursor is at a password input field. */
|
||||
bool password_input;
|
||||
|
||||
/** The visual style of the cursor. */
|
||||
GhosttyRenderStateCursorVisualStyle visual_style;
|
||||
} GhosttyRenderStateCursor;
|
||||
|
||||
/**
|
||||
* Render-state color information.
|
||||
*
|
||||
* This struct uses the sized-struct ABI pattern. Initialize with
|
||||
* GHOSTTY_INIT_SIZED(GhosttyRenderStateColors) before calling
|
||||
* ghostty_render_state_colors_get().
|
||||
* GHOSTTY_INIT_SIZED(GhosttyRenderStateColors) before querying
|
||||
* GHOSTTY_RENDER_STATE_DATA_COLORS.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors);
|
||||
* GhosttyResult result = ghostty_render_state_colors_get(state, &colors);
|
||||
* GhosttyResult result = ghostty_render_state_get(
|
||||
* state, GHOSTTY_RENDER_STATE_DATA_COLORS, &colors);
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup render
|
||||
@@ -430,8 +480,9 @@ GHOSTTY_API GhosttyResult ghostty_render_state_end_update(GhosttyRenderState sta
|
||||
* @param state The render state handle (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 `state` is
|
||||
* NULL or `data` is not a recognized enum value
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
|
||||
* `out` is NULL, `data` is not a recognized enum value, or a sized
|
||||
* output struct is smaller than `sizeof(size_t)`
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
@@ -486,24 +537,6 @@ GHOSTTY_API GhosttyResult ghostty_render_state_set(GhosttyRenderState state,
|
||||
GhosttyRenderStateOption option,
|
||||
const void* value);
|
||||
|
||||
/**
|
||||
* Get the current color information from a render state.
|
||||
*
|
||||
* This writes as many fields as fit in the caller-provided sized struct.
|
||||
* `out_colors->size` must be set by the caller (typically via
|
||||
* GHOSTTY_INIT_SIZED(GhosttyRenderStateColors)).
|
||||
*
|
||||
* @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param[out] out_colors Sized output struct to receive render-state colors
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
|
||||
* `out_colors` is NULL, or if `out_colors->size` is smaller than
|
||||
* `sizeof(size_t)`
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_render_state_colors_get(GhosttyRenderState state,
|
||||
GhosttyRenderStateColors* out_colors);
|
||||
|
||||
/**
|
||||
* Create a new row iterator instance.
|
||||
*
|
||||
|
||||
@@ -286,7 +286,6 @@ comptime {
|
||||
@export(&c.render_state_get, .{ .name = "ghostty_render_state_get" });
|
||||
@export(&c.render_state_get_multi, .{ .name = "ghostty_render_state_get_multi" });
|
||||
@export(&c.render_state_set, .{ .name = "ghostty_render_state_set" });
|
||||
@export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" });
|
||||
@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_get, .{ .name = "ghostty_render_state_row_get" });
|
||||
|
||||
@@ -90,7 +90,6 @@ pub const render_state_end_update = render.end_update;
|
||||
pub const render_state_get = render.get;
|
||||
pub const render_state_get_multi = render.get_multi;
|
||||
pub const render_state_set = render.set;
|
||||
pub const render_state_colors_get = render.colors_get;
|
||||
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_get = render.row_get;
|
||||
|
||||
@@ -100,6 +100,19 @@ pub const CursorVisualStyle = enum(c_int) {
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyRenderStateCursor
|
||||
pub const Cursor = extern struct {
|
||||
size: usize = @sizeOf(Cursor),
|
||||
viewport_has_value: bool,
|
||||
viewport_x: u16,
|
||||
viewport_y: u16,
|
||||
wide_tail: bool,
|
||||
visible: bool,
|
||||
blinking: bool,
|
||||
password_input: bool,
|
||||
visual_style: CursorVisualStyle,
|
||||
};
|
||||
|
||||
/// C: GhosttyRenderStateData
|
||||
pub const Data = enum(c_int) {
|
||||
invalid = 0,
|
||||
@@ -120,6 +133,8 @@ pub const Data = enum(c_int) {
|
||||
cursor_viewport_x = 15,
|
||||
cursor_viewport_y = 16,
|
||||
cursor_viewport_wide_tail = 17,
|
||||
cursor = 18,
|
||||
colors = 19,
|
||||
|
||||
/// Output type expected for querying the data of the given kind.
|
||||
pub fn OutType(comptime self: Data) type {
|
||||
@@ -135,6 +150,8 @@ pub const Data = enum(c_int) {
|
||||
.cursor_visible, .cursor_blinking, .cursor_password_input => bool,
|
||||
.cursor_viewport_has_value, .cursor_viewport_wide_tail => bool,
|
||||
.cursor_viewport_x, .cursor_viewport_y => size.CellCountInt,
|
||||
.cursor => Cursor,
|
||||
.colors => Colors,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -270,12 +287,13 @@ inline fn getDispatch(
|
||||
};
|
||||
}
|
||||
|
||||
const out_ptr = out orelse return .invalid_value;
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
state,
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
@ptrCast(@alignCast(out_ptr)),
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -328,6 +346,8 @@ fn getTyped(
|
||||
const vp = state.state.cursor.viewport orelse return .invalid_value;
|
||||
out.* = vp.wide_tail;
|
||||
},
|
||||
.cursor => return writeCursor(state, out),
|
||||
.colors => return writeColors(state, out),
|
||||
}
|
||||
|
||||
return .success;
|
||||
@@ -367,12 +387,87 @@ fn setTyped(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn colors_get(
|
||||
state_: RenderState,
|
||||
out_colors_: ?*Colors,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const state = state_ orelse return .invalid_value;
|
||||
const out_colors = out_colors_ orelse return .invalid_value;
|
||||
fn writeCursor(
|
||||
state: *RenderStateWrapper,
|
||||
out_cursor: *Cursor,
|
||||
) Result {
|
||||
const out_size = out_cursor.size;
|
||||
if (out_size < @sizeOf(usize)) return .invalid_value;
|
||||
|
||||
const cursor = &state.state.cursor;
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"viewport_has_value",
|
||||
)) {
|
||||
out_cursor.viewport_has_value = cursor.viewport != null;
|
||||
}
|
||||
|
||||
if (cursor.viewport) |viewport| {
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"viewport_x",
|
||||
)) {
|
||||
out_cursor.viewport_x = viewport.x;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"viewport_y",
|
||||
)) {
|
||||
out_cursor.viewport_y = viewport.y;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"wide_tail",
|
||||
)) {
|
||||
out_cursor.wide_tail = viewport.wide_tail;
|
||||
}
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"visible",
|
||||
)) {
|
||||
out_cursor.visible = cursor.visible;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"blinking",
|
||||
)) {
|
||||
out_cursor.blinking = cursor.blinking;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"password_input",
|
||||
)) {
|
||||
out_cursor.password_input = cursor.password_input;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Cursor,
|
||||
out_size,
|
||||
"visual_style",
|
||||
)) {
|
||||
out_cursor.visual_style = CursorVisualStyle.fromCursorStyle(cursor.visual_style);
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn writeColors(
|
||||
state: *RenderStateWrapper,
|
||||
out_colors: *Colors,
|
||||
) Result {
|
||||
const out_size = out_colors.size;
|
||||
if (out_size < @sizeOf(usize)) return .invalid_value;
|
||||
|
||||
@@ -997,6 +1092,14 @@ test "render: begin/end update" {
|
||||
test "render: get invalid value" {
|
||||
var cols: size.CellCountInt = 0;
|
||||
try testing.expectEqual(Result.invalid_value, get(null, .cols, @ptrCast(&cols)));
|
||||
|
||||
var state: RenderState = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&state,
|
||||
));
|
||||
defer free(state);
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .cols, null));
|
||||
}
|
||||
|
||||
test "render: get invalid data" {
|
||||
@@ -1010,7 +1113,7 @@ test "render: get invalid data" {
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .invalid, null));
|
||||
}
|
||||
|
||||
test "render: colors get invalid value" {
|
||||
test "render: aggregate get invalid value" {
|
||||
var state: RenderState = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
@@ -1021,11 +1124,20 @@ test "render: colors get invalid value" {
|
||||
var colors: Colors = std.mem.zeroes(Colors);
|
||||
colors.size = @sizeOf(Colors);
|
||||
|
||||
try testing.expectEqual(Result.invalid_value, colors_get(null, &colors));
|
||||
try testing.expectEqual(Result.invalid_value, colors_get(state, null));
|
||||
try testing.expectEqual(Result.invalid_value, get(null, .colors, &colors));
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .colors, null));
|
||||
|
||||
colors.size = @sizeOf(usize) - 1;
|
||||
try testing.expectEqual(Result.invalid_value, colors_get(state, &colors));
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .colors, &colors));
|
||||
|
||||
var cursor: Cursor = std.mem.zeroes(Cursor);
|
||||
cursor.size = @sizeOf(Cursor);
|
||||
|
||||
try testing.expectEqual(Result.invalid_value, get(null, .cursor, &cursor));
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .cursor, null));
|
||||
|
||||
cursor.size = @sizeOf(usize) - 1;
|
||||
try testing.expectEqual(Result.invalid_value, get(state, .cursor, &cursor));
|
||||
}
|
||||
|
||||
test "render: get/set dirty invalid value" {
|
||||
@@ -1738,7 +1850,7 @@ test "render: update" {
|
||||
try testing.expectEqual(@as(size.CellCountInt, 24), rows_val);
|
||||
}
|
||||
|
||||
test "render: colors get" {
|
||||
test "render: colors data get" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
@@ -1759,7 +1871,7 @@ test "render: colors get" {
|
||||
|
||||
var colors: Colors = std.mem.zeroes(Colors);
|
||||
colors.size = @sizeOf(Colors);
|
||||
try testing.expectEqual(Result.success, colors_get(state, &colors));
|
||||
try testing.expectEqual(Result.success, get(state, .colors, &colors));
|
||||
|
||||
const state_colors = &state.?.state.colors;
|
||||
try testing.expectEqual(state_colors.background.cval(), colors.background);
|
||||
@@ -1777,6 +1889,121 @@ test "render: colors get" {
|
||||
}
|
||||
}
|
||||
|
||||
test "render: cursor data get matches scalar getters" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
80,
|
||||
24,
|
||||
));
|
||||
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 cursor: Cursor = std.mem.zeroes(Cursor);
|
||||
cursor.size = @sizeOf(Cursor);
|
||||
try testing.expectEqual(Result.success, get(state, .cursor, &cursor));
|
||||
|
||||
var viewport_has_value: bool = undefined;
|
||||
var viewport_x: u16 = undefined;
|
||||
var viewport_y: u16 = undefined;
|
||||
var wide_tail: bool = undefined;
|
||||
var visible: bool = undefined;
|
||||
var blinking: bool = undefined;
|
||||
var password_input: bool = undefined;
|
||||
var visual_style: CursorVisualStyle = undefined;
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_viewport_has_value, &viewport_has_value));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_viewport_x, &viewport_x));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_viewport_y, &viewport_y));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_viewport_wide_tail, &wide_tail));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_visible, &visible));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_blinking, &blinking));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_password_input, &password_input));
|
||||
try testing.expectEqual(Result.success, get(state, .cursor_visual_style, &visual_style));
|
||||
|
||||
try testing.expectEqual(viewport_has_value, cursor.viewport_has_value);
|
||||
try testing.expectEqual(viewport_x, cursor.viewport_x);
|
||||
try testing.expectEqual(viewport_y, cursor.viewport_y);
|
||||
try testing.expectEqual(wide_tail, cursor.wide_tail);
|
||||
try testing.expectEqual(visible, cursor.visible);
|
||||
try testing.expectEqual(blinking, cursor.blinking);
|
||||
try testing.expectEqual(password_input, cursor.password_input);
|
||||
try testing.expectEqual(visual_style, cursor.visual_style);
|
||||
}
|
||||
|
||||
test "render: cursor data get without viewport" {
|
||||
var state: RenderState = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&state,
|
||||
));
|
||||
defer free(state);
|
||||
|
||||
state.?.state.cursor.viewport = null;
|
||||
state.?.state.cursor.visible = true;
|
||||
state.?.state.cursor.blinking = true;
|
||||
state.?.state.cursor.password_input = true;
|
||||
state.?.state.cursor.visual_style = .underline;
|
||||
|
||||
var cursor: Cursor = std.mem.zeroes(Cursor);
|
||||
cursor.size = @sizeOf(Cursor);
|
||||
cursor.viewport_x = 0xAAAA;
|
||||
cursor.viewport_y = 0xBBBB;
|
||||
cursor.wide_tail = true;
|
||||
try testing.expectEqual(Result.success, get(state, .cursor, &cursor));
|
||||
|
||||
try testing.expect(!cursor.viewport_has_value);
|
||||
try testing.expectEqual(@as(u16, 0xAAAA), cursor.viewport_x);
|
||||
try testing.expectEqual(@as(u16, 0xBBBB), cursor.viewport_y);
|
||||
try testing.expect(cursor.wide_tail);
|
||||
try testing.expect(cursor.visible);
|
||||
try testing.expect(cursor.blinking);
|
||||
try testing.expect(cursor.password_input);
|
||||
try testing.expectEqual(CursorVisualStyle.underline, cursor.visual_style);
|
||||
}
|
||||
|
||||
test "render: cursor data get supports truncated sized struct" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
80,
|
||||
24,
|
||||
));
|
||||
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));
|
||||
const expected = state.?.state.cursor;
|
||||
const viewport = expected.viewport.?;
|
||||
|
||||
var cursor: Cursor = std.mem.zeroes(Cursor);
|
||||
cursor.size = @offsetOf(Cursor, "viewport_y") + @sizeOf(u16);
|
||||
cursor.wide_tail = !viewport.wide_tail;
|
||||
cursor.visible = !expected.visible;
|
||||
try testing.expectEqual(Result.success, get(state, .cursor, &cursor));
|
||||
|
||||
try testing.expect(cursor.viewport_has_value);
|
||||
try testing.expectEqual(viewport.x, cursor.viewport_x);
|
||||
try testing.expectEqual(viewport.y, cursor.viewport_y);
|
||||
try testing.expectEqual(!viewport.wide_tail, cursor.wide_tail);
|
||||
try testing.expectEqual(!expected.visible, cursor.visible);
|
||||
}
|
||||
|
||||
test "render: row cells bg_color no background" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
@@ -2020,7 +2247,7 @@ test "render: row cells fg_color from style" {
|
||||
try testing.expectEqual(@as(u8, 30), fg.b);
|
||||
}
|
||||
|
||||
test "render: colors get supports truncated sized struct" {
|
||||
test "render: colors data get supports truncated sized struct" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
@@ -2044,7 +2271,7 @@ test "render: colors get supports truncated sized struct" {
|
||||
for (&colors.palette) |*entry| entry.* = sentinel;
|
||||
|
||||
colors.size = @offsetOf(Colors, "palette") + @sizeOf(colorpkg.RGB.C) * 2;
|
||||
try testing.expectEqual(Result.success, colors_get(state, &colors));
|
||||
try testing.expectEqual(Result.success, get(state, .colors, &colors));
|
||||
|
||||
const state_colors = &state.?.state.colors;
|
||||
try testing.expectEqual(state_colors.palette[0].cval(), colors.palette[0]);
|
||||
|
||||
@@ -70,6 +70,7 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||
.{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) },
|
||||
.{ "GhosttyReader", StructInfo.init(io.Reader) },
|
||||
.{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) },
|
||||
.{ "GhosttyRenderStateCursor", StructInfo.init(render.Cursor) },
|
||||
.{ "GhosttyRenderStateRowSelection", StructInfo.init(render.RowSelection) },
|
||||
.{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) },
|
||||
.{ "GhosttySelectionGestureGeometry", StructInfo.init(selection_gesture.Geometry) },
|
||||
@@ -257,6 +258,7 @@ test "json parses" {
|
||||
"GhosttyPointCoordinate",
|
||||
"GhosttyReader",
|
||||
"GhosttyRenderStateColors",
|
||||
"GhosttyRenderStateCursor",
|
||||
"GhosttyRenderStateRowSelection",
|
||||
"GhosttySelection",
|
||||
"GhosttySelectionGestureBehaviors",
|
||||
@@ -309,6 +311,18 @@ test "json parses" {
|
||||
try std.testing.expect(unknown_string_fields.contains("truncated"));
|
||||
try std.testing.expect(unknown_string_fields.contains("content"));
|
||||
|
||||
const render_cursor_fields = root.get("GhosttyRenderStateCursor").?.object
|
||||
.get("fields").?.object;
|
||||
try std.testing.expect(render_cursor_fields.contains("size"));
|
||||
try std.testing.expect(render_cursor_fields.contains("viewport_has_value"));
|
||||
try std.testing.expect(render_cursor_fields.contains("viewport_x"));
|
||||
try std.testing.expect(render_cursor_fields.contains("viewport_y"));
|
||||
try std.testing.expect(render_cursor_fields.contains("wide_tail"));
|
||||
try std.testing.expect(render_cursor_fields.contains("visible"));
|
||||
try std.testing.expect(render_cursor_fields.contains("blinking"));
|
||||
try std.testing.expect(render_cursor_fields.contains("password_input"));
|
||||
try std.testing.expect(render_cursor_fields.contains("visual_style"));
|
||||
|
||||
const reader_fields = root.get("GhosttyReader").?.object
|
||||
.get("fields").?.object;
|
||||
try std.testing.expect(reader_fields.contains("read"));
|
||||
|
||||
Reference in New Issue
Block a user