vt: add style and grapheme accessors
Add ghostty_grid_ref_style and ghostty_grid_ref_graphemes to the grid ref C API, allowing callers to extract the full style and grapheme cluster directly from a grid reference without manually resolving the page internals.pull/11676/head
parent
df8813bf1b
commit
549824842d
|
|
@ -12,6 +12,7 @@
|
|||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/screen.h>
|
||||
#include <ghostty/vt/style.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
@ -77,6 +78,46 @@ GhosttyResult ghostty_grid_ref_cell(const GhosttyGridRef *ref,
|
|||
GhosttyResult ghostty_grid_ref_row(const GhosttyGridRef *ref,
|
||||
GhosttyRow *out_row);
|
||||
|
||||
/**
|
||||
* Get the grapheme cluster codepoints for the cell at the grid reference's
|
||||
* position.
|
||||
*
|
||||
* Writes the full grapheme cluster (the cell's primary codepoint followed by
|
||||
* any combining codepoints) into the provided buffer. If the cell has no text,
|
||||
* out_len is set to 0 and GHOSTTY_SUCCESS is returned.
|
||||
*
|
||||
* If the buffer is too small (or NULL), the function returns
|
||||
* GHOSTTY_OUT_OF_SPACE and writes the required number of codepoints to
|
||||
* out_len. The caller can then retry with a sufficiently sized buffer.
|
||||
*
|
||||
* @param ref Pointer to the grid reference
|
||||
* @param buf Output buffer of uint32_t codepoints (may be NULL)
|
||||
* @param buf_len Number of uint32_t elements in the buffer
|
||||
* @param[out] out_len On success, the number of codepoints written. On
|
||||
* GHOSTTY_OUT_OF_SPACE, the required buffer size in codepoints.
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
|
||||
* node is NULL, GHOSTTY_OUT_OF_SPACE if the buffer is too small
|
||||
*
|
||||
* @ingroup grid_ref
|
||||
*/
|
||||
GhosttyResult ghostty_grid_ref_graphemes(const GhosttyGridRef *ref,
|
||||
uint32_t *buf,
|
||||
size_t buf_len,
|
||||
size_t *out_len);
|
||||
|
||||
/**
|
||||
* Get the style of the cell at the grid reference's position.
|
||||
*
|
||||
* @param ref Pointer to the grid reference
|
||||
* @param[out] out_style On success, set to the cell's style (may be NULL)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
|
||||
* node is NULL
|
||||
*
|
||||
* @ingroup grid_ref
|
||||
*/
|
||||
GhosttyResult ghostty_grid_ref_style(const GhosttyGridRef *ref,
|
||||
GhosttyStyle *out_style);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -28,6 +28,16 @@ extern "C" {
|
|||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Style identifier type.
|
||||
*
|
||||
* Used to look up the full style from a grid reference.
|
||||
* Obtain this from a cell via GHOSTTY_CELL_DATA_STYLE_ID.
|
||||
*
|
||||
* @ingroup style
|
||||
*/
|
||||
typedef uint16_t GhosttyStyleId;
|
||||
|
||||
/**
|
||||
* Style color tags.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -199,6 +199,8 @@ comptime {
|
|||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
|
||||
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
|
||||
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });
|
||||
@export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ const testing = std.testing;
|
|||
const page = @import("../page.zig");
|
||||
const PageList = @import("../PageList.zig");
|
||||
const size = @import("../size.zig");
|
||||
const stylepkg = @import("../style.zig");
|
||||
const cell_c = @import("cell.zig");
|
||||
const row_c = @import("row.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyGridRef
|
||||
|
|
@ -53,6 +55,58 @@ pub fn grid_ref_row(
|
|||
return .success;
|
||||
}
|
||||
|
||||
pub fn grid_ref_graphemes(
|
||||
ref: *const CGridRef,
|
||||
out_buf: ?[*]u32,
|
||||
buf_len: usize,
|
||||
out_len: *usize,
|
||||
) callconv(.c) Result {
|
||||
const p = ref.toPin() orelse return .invalid_value;
|
||||
const cell = p.rowAndCell().cell;
|
||||
|
||||
if (!cell.hasText()) {
|
||||
out_len.* = 0;
|
||||
return .success;
|
||||
}
|
||||
|
||||
const cp = cell.codepoint();
|
||||
const extra = if (cell.hasGrapheme()) p.grapheme(cell) else null;
|
||||
const total = 1 + if (extra) |e| e.len else 0;
|
||||
|
||||
if (out_buf == null or buf_len < total) {
|
||||
out_len.* = total;
|
||||
return .out_of_space;
|
||||
}
|
||||
|
||||
const buf = out_buf.?[0..buf_len];
|
||||
buf[0] = cp;
|
||||
if (extra) |e| for (e, 1..) |c, i| {
|
||||
buf[i] = c;
|
||||
};
|
||||
|
||||
out_len.* = total;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn grid_ref_style(
|
||||
ref: *const CGridRef,
|
||||
out: ?*style_c.Style,
|
||||
) callconv(.c) Result {
|
||||
const p = ref.toPin() orelse return .invalid_value;
|
||||
if (out) |o| {
|
||||
const cell = p.rowAndCell().cell;
|
||||
if (cell.style_id == stylepkg.default_id) {
|
||||
o.* = .fromStyle(.{});
|
||||
} else {
|
||||
o.* = .fromStyle(p.node.data.styles.get(
|
||||
p.node.data.memory,
|
||||
cell.style_id,
|
||||
).*);
|
||||
}
|
||||
}
|
||||
return .success;
|
||||
}
|
||||
|
||||
test "grid_ref_cell null node" {
|
||||
const ref = CGridRef{};
|
||||
var out: cell_c.CCell = undefined;
|
||||
|
|
@ -74,3 +128,28 @@ test "grid_ref_row null out" {
|
|||
const ref = CGridRef{};
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_row(&ref, null));
|
||||
}
|
||||
|
||||
test "grid_ref_graphemes null node" {
|
||||
const ref = CGridRef{};
|
||||
var len: usize = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_graphemes(&ref, null, 0, &len));
|
||||
}
|
||||
|
||||
test "grid_ref_graphemes null buf returns out_of_space" {
|
||||
const ref = CGridRef{};
|
||||
var len: usize = undefined;
|
||||
// With null node this returns invalid_value before checking the buffer,
|
||||
// so we can only test null node here. Full buffer tests require a real page.
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_graphemes(&ref, null, 0, &len));
|
||||
}
|
||||
|
||||
test "grid_ref_style null node" {
|
||||
const ref = CGridRef{};
|
||||
var out: style_c.Style = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_style(&ref, &out));
|
||||
}
|
||||
|
||||
test "grid_ref_style null out" {
|
||||
const ref = CGridRef{};
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_style(&ref, null));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ pub const terminal_grid_ref = terminal.grid_ref;
|
|||
const grid_ref = @import("grid_ref.zig");
|
||||
pub const grid_ref_cell = grid_ref.grid_ref_cell;
|
||||
pub const grid_ref_row = grid_ref.grid_ref_row;
|
||||
pub const grid_ref_graphemes = grid_ref.grid_ref_graphemes;
|
||||
pub const grid_ref_style = grid_ref.grid_ref_style;
|
||||
|
||||
test {
|
||||
_ = cell;
|
||||
|
|
|
|||
Loading…
Reference in New Issue