vt: expose dirty state in C API

Switch RenderState.Dirty to lib.Enum so it uses C-compatible enum
backing when building the C ABI target. Add GhosttyRenderStateDirty and
new ghostty_render_state_dirty_get/set declarations to the render header,
then wire both functions through src/terminal/c/main.zig and the lib_vt
export table.
This commit is contained in:
Mitchell Hashimoto
2026-03-18 09:54:03 -07:00
parent a0d738697e
commit 2876fb7a55
5 changed files with 140 additions and 12 deletions

View File

@@ -49,6 +49,22 @@ extern "C" {
*/
typedef struct GhosttyRenderState* GhosttyRenderState;
/**
* Dirty state of a render state after update.
*
* @ingroup render
*/
typedef enum {
/** Not dirty at all; rendering can be skipped. */
GHOSTTY_RENDER_STATE_DIRTY_FALSE = 0,
/** Some rows changed; renderer can redraw incrementally. */
GHOSTTY_RENDER_STATE_DIRTY_PARTIAL = 1,
/** Global state changed; renderer should redraw everything. */
GHOSTTY_RENDER_STATE_DIRTY_FULL = 2,
} GhosttyRenderStateDirty;
/**
* Create a new render state instance.
*
@@ -79,6 +95,34 @@ GhosttyResult ghostty_render_state_new(const GhosttyAllocator* allocator,
GhosttyResult ghostty_render_state_update(GhosttyRenderState state,
GhosttyTerminal terminal);
/**
* Get the current dirty state of a render state.
*
* @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
* @param[out] out_dirty On success, receives the current dirty state
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is
* NULL
*
* @ingroup render
*/
GhosttyResult ghostty_render_state_dirty_get(GhosttyRenderState state,
GhosttyRenderStateDirty* out_dirty);
/**
* Set the dirty state of a render state.
*
* This can be used by callers to clear dirty state after handling updates.
*
* @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
* @param dirty The dirty state to set
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is
* NULL or `dirty` is not a recognized enum value
*
* @ingroup render
*/
GhosttyResult ghostty_render_state_dirty_set(GhosttyRenderState state,
GhosttyRenderStateDirty dirty);
/**
* Free a render state instance.
*

View File

@@ -189,6 +189,8 @@ comptime {
@export(&c.formatter_free, .{ .name = "ghostty_formatter_free" });
@export(&c.render_state_new, .{ .name = "ghostty_render_state_new" });
@export(&c.render_state_update, .{ .name = "ghostty_render_state_update" });
@export(&c.render_state_dirty_get, .{ .name = "ghostty_render_state_dirty_get" });
@export(&c.render_state_dirty_set, .{ .name = "ghostty_render_state_dirty_set" });
@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" });

View File

@@ -39,6 +39,8 @@ pub const formatter_free = formatter.free;
pub const render_state_new = render.new;
pub const render_state_free = render.free;
pub const render_state_update = render.update;
pub const render_state_dirty_get = render.dirty_get;
pub const render_state_dirty_set = render.dirty_set;
pub const sgr_new = sgr.new;
pub const sgr_free = sgr.free;

View File

@@ -14,6 +14,9 @@ const RenderStateWrapper = struct {
/// C: GhosttyRenderState
pub const RenderState = ?*RenderStateWrapper;
/// C: GhosttyRenderStateDirty
pub const Dirty = renderpkg.RenderState.Dirty;
pub fn new(
alloc_: ?*const CAllocator,
result: *RenderState,
@@ -47,6 +50,26 @@ pub fn update(
return .success;
}
pub fn dirty_get(
state_: RenderState,
out_dirty: *Dirty,
) callconv(.c) Result {
const state = state_ orelse return .invalid_value;
out_dirty.* = state.state.dirty;
return .success;
}
pub fn dirty_set(
state_: RenderState,
dirty_: c_int,
) callconv(.c) Result {
const state = state_ orelse return .invalid_value;
const dirty = std.meta.intToEnum(Dirty, dirty_) catch
return .invalid_value;
state.state.dirty = dirty;
return .success;
}
pub fn free(state_: RenderState) callconv(.c) void {
const state = state_ orelse return;
const alloc = state.alloc;
@@ -80,6 +103,60 @@ test "render: update invalid value" {
try testing.expectEqual(Result.invalid_value, update(state, null));
}
test "render: dirty get/set invalid value" {
var state: RenderState = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&state,
));
defer free(state);
var dirty: Dirty = .false;
try testing.expectEqual(Result.invalid_value, dirty_get(null, &dirty));
try testing.expectEqual(Result.invalid_value, dirty_set(
null,
@intFromEnum(Dirty.full),
));
}
test "render: dirty get/set" {
var state: RenderState = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&state,
));
defer free(state);
var dirty: Dirty = undefined;
try testing.expectEqual(Result.success, dirty_get(state, &dirty));
try testing.expectEqual(Dirty.false, dirty);
try testing.expectEqual(Result.success, dirty_set(
state,
@intFromEnum(Dirty.partial),
));
try testing.expectEqual(Result.success, dirty_get(state, &dirty));
try testing.expectEqual(Dirty.partial, dirty);
try testing.expectEqual(Result.success, dirty_set(
state,
@intFromEnum(Dirty.full),
));
try testing.expectEqual(Result.success, dirty_get(state, &dirty));
try testing.expectEqual(Dirty.full, dirty);
}
test "render: dirty set invalid enum value" {
var state: RenderState = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&state,
));
defer free(state);
try testing.expectEqual(Result.invalid_value, dirty_set(state, 99));
}
test "render: update" {
var terminal: terminal_c.Terminal = null;
try testing.expectEqual(Result.success, terminal_c.new(

View File

@@ -1,8 +1,11 @@
const std = @import("std");
const build_options = @import("terminal_options");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const fastmem = @import("../fastmem.zig");
const lib = @import("../lib/main.zig");
const lib_target: lib.Target = if (build_options.c_abi) .c else .zig;
const color = @import("color.zig");
const cursor = @import("cursor.zig");
const highlight = @import("highlight.zig");
@@ -222,20 +225,20 @@ pub const RenderState = struct {
style: Style,
};
// Dirty state
pub const Dirty = enum {
/// Not dirty at all. Can skip rendering if prior state was
/// already rendered.
false,
// Dirty state.
pub const Dirty = lib.Enum(lib_target, &.{
// Not dirty at all. Can skip rendering if prior state was
// already rendered.
"false",
/// Partially dirty. Some rows changed but not all. None of the
/// global state changed such as colors.
partial,
// Some rows changed but not all. None of the global state
// changed such as colors.
"partial",
/// Fully dirty. Global state changed or dimensions changed. All rows
/// should be redrawn.
full,
};
// Global state changed or dimensions changed. All rows should
// be redrawn.
"full",
});
const SelectionCache = struct {
selection: Selection,