From 2c1dad790b750b64adf0f2c4128604c2eba91dab Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 11 Apr 2026 07:14:52 -0700 Subject: [PATCH] libghostty: add _get_multi to all _get APIs Replace the ImageInfo and PlacementInfo sized structs and their associated .info enum variants with a new _get_multi pattern that batches multiple enum+pointer pairs into a single call. This avoids struct ABI concerns (field order, padding, alignment, GHOSTTY_INIT_SIZED) while preserving the single-call-crossing performance benefit for FFI and Cgo callers. Each _get_multi function takes an array of enum keys, an array of output pointers, and an optional out_written parameter that reports how many values were successfully written before any error. This applies uniformly to all _get APIs: terminal_get, cell_get, row_get, render_state_get, render_state_row_get, render_state_row_cells_get, kitty_graphics_image_get, and kitty_graphics_placement_get. The C example is updated to use compound-literal _get_multi calls, and tests cover both success and error paths for every new function. --- example/c-vt-kitty-graphics/src/main.c | 32 +-- include/ghostty/vt/kitty_graphics.h | 166 ++++++-------- include/ghostty/vt/render.h | 88 +++++++- include/ghostty/vt/screen.h | 62 +++++- include/ghostty/vt/terminal.h | 33 +++ src/lib_vt.zig | 8 + src/terminal/c/cell.zig | 60 +++++ src/terminal/c/kitty_graphics.zig | 295 +++++++++++++------------ src/terminal/c/main.zig | 8 + src/terminal/c/render.zig | 197 +++++++++++++++++ src/terminal/c/row.zig | 61 +++++ src/terminal/c/terminal.zig | 67 ++++++ 12 files changed, 823 insertions(+), 254 deletions(-) diff --git a/example/c-vt-kitty-graphics/src/main.c b/example/c-vt-kitty-graphics/src/main.c index 5001c3707..d88ee1952 100644 --- a/example/c-vt-kitty-graphics/src/main.c +++ b/example/c-vt-kitty-graphics/src/main.c @@ -145,14 +145,15 @@ int main() { bool is_virtual = false; int32_t z = 0; - ghostty_kitty_graphics_placement_get(iter, - GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID, &image_id); - ghostty_kitty_graphics_placement_get(iter, - GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID, &placement_id); - ghostty_kitty_graphics_placement_get(iter, - GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL, &is_virtual); - ghostty_kitty_graphics_placement_get(iter, - GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z, &z); + ghostty_kitty_graphics_placement_get_multi(iter, 4, + (GhosttyKittyGraphicsPlacementData[]){ + GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID, + GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID, + GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL, + GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z, + }, + (void*[]){ &image_id, &placement_id, &is_virtual, &z }, + NULL); printf(" placement #%d: image_id=%u placement_id=%u virtual=%s z=%d\n", placement_count, image_id, placement_id, @@ -170,11 +171,16 @@ int main() { GhosttyKittyImageFormat format = 0; size_t data_len = 0; - ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_NUMBER, &number); - ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_WIDTH, &width); - ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_HEIGHT, &height); - ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_FORMAT, &format); - ghostty_kitty_graphics_image_get(image, GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN, &data_len); + ghostty_kitty_graphics_image_get_multi(image, 5, + (GhosttyKittyGraphicsImageData[]){ + GHOSTTY_KITTY_IMAGE_DATA_NUMBER, + GHOSTTY_KITTY_IMAGE_DATA_WIDTH, + GHOSTTY_KITTY_IMAGE_DATA_HEIGHT, + GHOSTTY_KITTY_IMAGE_DATA_FORMAT, + GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN, + }, + (void*[]){ &number, &width, &height, &format, &data_len }, + NULL); printf(" image: number=%u size=%ux%u format=%d data_len=%zu\n", number, width, height, format, data_len); diff --git a/include/ghostty/vt/kitty_graphics.h b/include/ghostty/vt/kitty_graphics.h index 062c8a237..9bace3a3c 100644 --- a/include/ghostty/vt/kitty_graphics.h +++ b/include/ghostty/vt/kitty_graphics.h @@ -215,17 +215,6 @@ typedef enum GHOSTTY_ENUM_TYPED { */ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12, - /** - * All placement metadata as a sized struct. - * - * This is an optimization over querying each field individually, - * particularly useful in environments with high per-call overhead - * such as FFI or Cgo. - * - * Output type: GhosttyKittyGraphicsPlacementInfo * - * (initialize with GHOSTTY_INIT_SIZED) - */ - GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO = 13, GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyKittyGraphicsPlacementData; @@ -354,96 +343,9 @@ typedef enum GHOSTTY_ENUM_TYPED { */ GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8, - /** - * All image metadata as a sized struct. - * - * This is an optimization over querying each field individually, - * particularly useful in environments with high per-call overhead - * such as FFI or Cgo. - * - * Output type: GhosttyKittyGraphicsImageInfo * - * (initialize with GHOSTTY_INIT_SIZED) - */ - GHOSTTY_KITTY_IMAGE_DATA_INFO = 9, GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyKittyGraphicsImageData; -/** - * All image metadata in a single sized struct. - * - * Returned by ghostty_kitty_graphics_image_get() with - * GHOSTTY_KITTY_IMAGE_DATA_INFO. This is an optimization over - * querying each field individually, particularly useful in - * environments with high per-call overhead such as FFI or Cgo. - * - * This struct uses the sized-struct ABI pattern. Initialize with - * GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsImageInfo). - * - * @ingroup kitty_graphics - */ -typedef struct { - /** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsImageInfo). */ - size_t size; - /** The image ID. */ - uint32_t id; - /** The image number. */ - uint32_t number; - /** Image width in pixels. */ - uint32_t width; - /** Image height in pixels. */ - uint32_t height; - /** Pixel format of the image. */ - GhosttyKittyImageFormat format; - /** Compression of the image. */ - GhosttyKittyImageCompression compression; - /** Borrowed pointer to the raw pixel data. */ - const uint8_t* data_ptr; - /** Length of the raw pixel data in bytes. */ - size_t data_len; -} GhosttyKittyGraphicsImageInfo; - -/** - * All placement metadata in a single sized struct. - * - * Returned by ghostty_kitty_graphics_placement_get() with - * GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO. This is an optimization - * over querying each field individually, particularly useful in - * environments with high per-call overhead such as FFI or Cgo. - * - * This struct uses the sized-struct ABI pattern. Initialize with - * GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementInfo). - * - * @ingroup kitty_graphics - */ -typedef struct { - /** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementInfo). */ - size_t size; - /** The image ID this placement belongs to. */ - uint32_t image_id; - /** The placement ID. */ - uint32_t placement_id; - /** Whether this is a virtual placement (unicode placeholder). */ - bool is_virtual; - /** Pixel offset from the left edge of the cell. */ - uint32_t x_offset; - /** Pixel offset from the top edge of the cell. */ - uint32_t y_offset; - /** Source rectangle x origin in pixels. */ - uint32_t source_x; - /** Source rectangle y origin in pixels. */ - uint32_t source_y; - /** Source rectangle width in pixels (0 = full image width). */ - uint32_t source_width; - /** Source rectangle height in pixels (0 = full image height). */ - uint32_t source_height; - /** Number of columns this placement occupies. */ - uint32_t columns; - /** Number of rows this placement occupies. */ - uint32_t rows; - /** Z-index for this placement. */ - int32_t z; -} GhosttyKittyGraphicsPlacementInfo; - /** * Combined rendering geometry for a placement in a single sized struct. * @@ -542,6 +444,40 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_image_get( GhosttyKittyGraphicsImageData data, void* out); +/** + * Get multiple data fields from a Kitty graphics image in a single call. + * + * This is an optimization over calling ghostty_kitty_graphics_image_get() + * repeatedly, particularly useful in environments with high per-call + * overhead such as FFI or Cgo. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * The type of each values[i] pointer must match the output type + * documented for keys[i]. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param image The image handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup kitty_graphics + */ +GHOSTTY_API GhosttyResult ghostty_kitty_graphics_image_get_multi( + GhosttyKittyGraphicsImage image, + size_t count, + const GhosttyKittyGraphicsImageData* keys, + void** values, + size_t* out_written); + /** * Create a new placement iterator instance. * @@ -627,6 +563,40 @@ GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get( GhosttyKittyGraphicsPlacementData data, void* out); +/** + * Get multiple data fields from the current placement in a single call. + * + * This is an optimization over calling ghostty_kitty_graphics_placement_get() + * repeatedly, particularly useful in environments with high per-call + * overhead such as FFI or Cgo. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * The type of each values[i] pointer must match the output type + * documented for keys[i]. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup kitty_graphics + */ +GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get_multi( + GhosttyKittyGraphicsPlacementIterator iterator, + size_t count, + const GhosttyKittyGraphicsPlacementData* keys, + void** values, + size_t* out_written); + /** * Compute the grid rectangle occupied by the current placement. * diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 3c2ea619e..d1a3687d9 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -331,8 +331,36 @@ GHOSTTY_API GhosttyResult ghostty_render_state_update(GhosttyRenderState state, * @ingroup render */ GHOSTTY_API GhosttyResult ghostty_render_state_get(GhosttyRenderState state, - GhosttyRenderStateData data, - void* out); + GhosttyRenderStateData data, + void* out); + +/** + * Get multiple data fields from a render state in a single call. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_get_multi( + GhosttyRenderState state, + size_t count, + const GhosttyRenderStateData* keys, + void** values, + size_t* out_written); /** * Set an option on a render state. @@ -433,6 +461,34 @@ GHOSTTY_API GhosttyResult ghostty_render_state_row_get( GhosttyRenderStateRowData data, void* out); +/** + * Get multiple data fields from the current row in a single call. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_row_get_multi( + GhosttyRenderStateRowIterator iterator, + size_t count, + const GhosttyRenderStateRowData* keys, + void** values, + size_t* out_written); + /** * Set an option on the current row in a render-state row iterator. * @@ -571,6 +627,34 @@ GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_get( GhosttyRenderStateRowCellsData data, void* out); +/** + * Get multiple data fields from the current cell in a single call. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param cells The row cells handle (NULL returns GHOSTTY_INVALID_VALUE) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup render + */ +GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_get_multi( + GhosttyRenderStateRowCells cells, + size_t count, + const GhosttyRenderStateRowCellsData* keys, + void** values, + size_t* out_written); + /** * Free a row cells instance. * diff --git a/include/ghostty/vt/screen.h b/include/ghostty/vt/screen.h index a8f73abad..9f639b583 100644 --- a/include/ghostty/vt/screen.h +++ b/include/ghostty/vt/screen.h @@ -314,8 +314,35 @@ typedef enum GHOSTTY_ENUM_TYPED { * @ingroup screen */ GHOSTTY_API GhosttyResult ghostty_cell_get(GhosttyCell cell, - GhosttyCellData data, - void *out); + GhosttyCellData data, + void *out); + +/** + * Get multiple data fields from a cell in a single call. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param cell The cell value + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup screen + */ +GHOSTTY_API GhosttyResult ghostty_cell_get_multi(GhosttyCell cell, + size_t count, + const GhosttyCellData* keys, + void** values, + size_t* out_written); /** * Get data from a row. @@ -334,8 +361,35 @@ GHOSTTY_API GhosttyResult ghostty_cell_get(GhosttyCell cell, * @ingroup screen */ GHOSTTY_API GhosttyResult ghostty_row_get(GhosttyRow row, - GhosttyRowData data, - void *out); + GhosttyRowData data, + void *out); + +/** + * Get multiple data fields from a row in a single call. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param row The row value + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup screen + */ +GHOSTTY_API GhosttyResult ghostty_row_get_multi(GhosttyRow row, + size_t count, + const GhosttyRowData* keys, + void** values, + size_t* out_written); /** @} */ diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 637bebbfb..433308c84 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -1038,6 +1038,39 @@ GHOSTTY_API GhosttyResult ghostty_terminal_get(GhosttyTerminal terminal, GhosttyTerminalData data, void *out); +/** + * Get multiple data fields from a terminal in a single call. + * + * This is an optimization over calling ghostty_terminal_get() + * repeatedly, particularly useful in environments with high per-call + * overhead such as FFI or Cgo. + * + * Each element in the keys array specifies a data kind, and the + * corresponding element in the values array receives the result. + * The type of each values[i] pointer must match the output type + * documented for keys[i]. + * + * Processing stops at the first error; on success out_written + * is set to count, on error it is set to the index of the + * failing key (i.e. the number of values successfully written). + * + * @param terminal The terminal handle (may be NULL) + * @param count Number of key/value pairs + * @param keys Array of data kinds to query + * @param values Array of output pointers (types must match each key's + * documented output type) + * @param[out] out_written On return, receives the number of values + * successfully written (may be NULL) + * @return GHOSTTY_SUCCESS if all queries succeed + * + * @ingroup terminal + */ +GHOSTTY_API GhosttyResult ghostty_terminal_get_multi(GhosttyTerminal terminal, + size_t count, + const GhosttyTerminalData* keys, + void** values, + size_t* out_written); + /** * Resolve a point in the terminal grid to a grid reference. * diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 0fce98ab3..ae0c87b1e 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -192,7 +192,9 @@ comptime { @export(&c.sys_log_stderr, .{ .name = "ghostty_sys_log_stderr" }); @export(&c.sys_set, .{ .name = "ghostty_sys_set" }); @export(&c.cell_get, .{ .name = "ghostty_cell_get" }); + @export(&c.cell_get_multi, .{ .name = "ghostty_cell_get_multi" }); @export(&c.row_get, .{ .name = "ghostty_row_get" }); + @export(&c.row_get_multi, .{ .name = "ghostty_row_get_multi" }); @export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" }); @export(&c.sgr_new, .{ .name = "ghostty_sgr_new" }); @export(&c.sgr_free, .{ .name = "ghostty_sgr_free" }); @@ -210,17 +212,20 @@ comptime { @export(&c.render_state_new, .{ .name = "ghostty_render_state_new" }); @export(&c.render_state_update, .{ .name = "ghostty_render_state_update" }); @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" }); + @export(&c.render_state_row_get_multi, .{ .name = "ghostty_render_state_row_get_multi" }); @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_get_multi, .{ .name = "ghostty_render_state_row_cells_get_multi" }); @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" }); @@ -233,16 +238,19 @@ comptime { @export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" }); @export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" }); @export(&c.terminal_get, .{ .name = "ghostty_terminal_get" }); + @export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" }); @export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" }); @export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" }); @export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" }); @export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" }); @export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" }); + @export(&c.kitty_graphics_image_get_multi, .{ .name = "ghostty_kitty_graphics_image_get_multi" }); @export(&c.kitty_graphics_placement_iterator_new, .{ .name = "ghostty_kitty_graphics_placement_iterator_new" }); @export(&c.kitty_graphics_placement_iterator_free, .{ .name = "ghostty_kitty_graphics_placement_iterator_free" }); @export(&c.kitty_graphics_placement_iterator_set, .{ .name = "ghostty_kitty_graphics_placement_iterator_set" }); @export(&c.kitty_graphics_placement_next, .{ .name = "ghostty_kitty_graphics_placement_next" }); @export(&c.kitty_graphics_placement_get, .{ .name = "ghostty_kitty_graphics_placement_get" }); + @export(&c.kitty_graphics_placement_get_multi, .{ .name = "ghostty_kitty_graphics_placement_get_multi" }); @export(&c.kitty_graphics_placement_rect, .{ .name = "ghostty_kitty_graphics_placement_rect" }); @export(&c.kitty_graphics_placement_pixel_size, .{ .name = "ghostty_kitty_graphics_placement_pixel_size" }); @export(&c.kitty_graphics_placement_grid_size, .{ .name = "ghostty_kitty_graphics_placement_grid_size" }); diff --git a/src/terminal/c/cell.zig b/src/terminal/c/cell.zig index c9746da72..b59567dff 100644 --- a/src/terminal/c/cell.zig +++ b/src/terminal/c/cell.zig @@ -120,6 +120,27 @@ pub fn get( }; } +pub fn get_multi( + cell_: CCell, + count: usize, + keys: ?[*]const CellData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = get(cell_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn getTyped( cell_: CCell, comptime data: CellData, @@ -176,3 +197,42 @@ test "get wide" { try testing.expectEqual(Result.success, get(cell, .wide, @ptrCast(&w))); try testing.expectEqual(Wide.wide, w); } + +test "get_multi success" { + const cell: CCell = @bitCast(Cell.init('B')); + var cp: u32 = 0; + var has_text: bool = false; + var written: usize = 0; + + const keys = [_]CellData{ .codepoint, .has_text }; + var values = [_]?*anyopaque{ @ptrCast(&cp), @ptrCast(&has_text) }; + try testing.expectEqual(Result.success, get_multi(cell, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expectEqual(@as(u32, 'B'), cp); + try testing.expect(has_text); +} + +test "get_multi error sets out_written" { + const cell: CCell = @bitCast(Cell.init('C')); + var cp: u32 = 0; + var written: usize = 99; + + const keys = [_]CellData{ .codepoint, .invalid }; + var values = [_]?*anyopaque{ @ptrCast(&cp), @ptrCast(&cp) }; + try testing.expectEqual(Result.invalid_value, get_multi(cell, keys.len, &keys, &values, &written)); + try testing.expectEqual(1, written); + try testing.expectEqual(@as(u32, 'C'), cp); +} + +test "get_multi null keys returns invalid_value" { + const cell: CCell = @bitCast(Cell.init('A')); + var cp: u32 = 0; + var values = [_]?*anyopaque{@ptrCast(&cp)}; + try testing.expectEqual(Result.invalid_value, get_multi(cell, 1, null, &values, null)); +} + +test "get_multi null values returns invalid_value" { + const cell: CCell = @bitCast(Cell.init('A')); + const keys = [_]CellData{.codepoint}; + try testing.expectEqual(Result.invalid_value, get_multi(cell, 1, &keys, null, null)); +} diff --git a/src/terminal/c/kitty_graphics.zig b/src/terminal/c/kitty_graphics.zig index 97143fd1e..d50b8c4c2 100644 --- a/src/terminal/c/kitty_graphics.zig +++ b/src/terminal/c/kitty_graphics.zig @@ -76,7 +76,6 @@ pub const PlacementData = enum(c_int) { columns = 10, rows = 11, z = 12, - info = 13, pub fn OutType(comptime self: PlacementData) type { return switch (self) { @@ -93,7 +92,6 @@ pub const PlacementData = enum(c_int) { .rows, => u32, .z => i32, - .info => PlacementInfo, }; } }; @@ -180,7 +178,6 @@ pub const ImageData = enum(c_int) { compression = 6, data_ptr = 7, data_len = 8, - info = 9, pub fn OutType(comptime self: ImageData) type { return switch (self) { @@ -190,7 +187,6 @@ pub const ImageData = enum(c_int) { .compression => ImageCompression, .data_ptr => [*]const u8, .data_len => usize, - .info => ImageInfo, }; } }; @@ -222,6 +218,29 @@ pub fn image_get( }; } +pub fn image_get_multi( + image_: ImageHandle, + count: usize, + keys: ?[*]const ImageData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + if (comptime !build_options.kitty_graphics) return .no_value; + + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = image_get(image_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn imageGetTyped( image_: ImageHandle, comptime data: ImageData, @@ -239,17 +258,6 @@ fn imageGetTyped( .compression => out.* = image.compression, .data_ptr => out.* = image.data.ptr, .data_len => out.* = image.data.len, - .info => { - if (out.size < @sizeOf(ImageInfo)) return .invalid_value; - out.id = image.id; - out.number = image.number; - out.width = image.width; - out.height = image.height; - out.format = image.format; - out.compression = image.compression; - out.data_ptr = image.data.ptr; - out.data_len = image.data.len; - }, } return .success; @@ -343,6 +351,29 @@ pub fn placement_get( }; } +pub fn placement_get_multi( + iter_: PlacementIterator, + count: usize, + keys: ?[*]const PlacementData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + if (comptime !build_options.kitty_graphics) return .no_value; + + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = placement_get(iter_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn placementGetTyped( iter_: PlacementIterator, comptime data: PlacementData, @@ -368,21 +399,6 @@ fn placementGetTyped( .columns => out.* = val.columns, .rows => out.* = val.rows, .z => out.* = val.z, - .info => { - if (out.size < @sizeOf(PlacementInfo)) return .invalid_value; - out.image_id = key.image_id; - out.placement_id = key.placement_id.id; - out.is_virtual = val.location == .virtual; - out.x_offset = val.x_offset; - out.y_offset = val.y_offset; - out.source_x = val.source_x; - out.source_y = val.source_y; - out.source_width = val.source_width; - out.source_height = val.source_height; - out.columns = val.columns; - out.rows = val.rows; - out.z = val.z; - }, } return .success; @@ -508,36 +524,6 @@ pub fn placement_source_rect( return .success; } -/// C: GhosttyKittyGraphicsImageInfo -pub const ImageInfo = extern struct { - size: usize = @sizeOf(ImageInfo), - id: u32 = 0, - number: u32 = 0, - width: u32 = 0, - height: u32 = 0, - format: ImageFormat = .rgb, - compression: ImageCompression = .none, - data_ptr: [*]const u8 = @as([*]const u8, @ptrFromInt(1)), - data_len: usize = 0, -}; - -/// C: GhosttyKittyGraphicsPlacementInfo -pub const PlacementInfo = extern struct { - size: usize = @sizeOf(PlacementInfo), - image_id: u32 = 0, - placement_id: u32 = 0, - is_virtual: bool = false, - x_offset: u32 = 0, - y_offset: u32 = 0, - source_x: u32 = 0, - source_y: u32 = 0, - source_width: u32 = 0, - source_height: u32 = 0, - columns: u32 = 0, - rows: u32 = 0, - z: i32 = 0, -}; - /// C: GhosttyKittyGraphicsPlacementRenderInfo pub const PlacementRenderInfo = extern struct { size: usize = @sizeOf(PlacementRenderInfo), @@ -1523,83 +1509,6 @@ test "image_get on null returns invalid_value" { try testing.expectEqual(Result.invalid_value, image_get(null, .id, @ptrCast(&id))); } -test "image_get info returns all fields" { - if (comptime !build_options.kitty_graphics) return error.SkipZigTest; - - var t: terminal_c.Terminal = null; - try testing.expectEqual(Result.success, terminal_c.new( - &lib.alloc.test_allocator, - &t, - .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, - )); - defer terminal_c.free(t); - try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20)); - - const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\"; - terminal_c.vt_write(t, cmd.ptr, cmd.len); - - var graphics: KittyGraphics = undefined; - try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics))); - const img = image_get_handle(graphics, 1); - try testing.expect(img != null); - - var info: ImageInfo = .{}; - try testing.expectEqual(Result.success, image_get(img, .info, @ptrCast(&info))); - try testing.expectEqual(1, info.id); - try testing.expectEqual(1, info.width); - try testing.expectEqual(2, info.height); - try testing.expectEqual(ImageFormat.rgb, info.format); - try testing.expectEqual(ImageCompression.none, info.compression); - try testing.expectEqual(6, info.data_len); -} - -test "image_get info null returns invalid_value" { - if (comptime !build_options.kitty_graphics) return error.SkipZigTest; - - var info: ImageInfo = .{}; - try testing.expectEqual(Result.invalid_value, image_get(null, .info, @ptrCast(&info))); -} - -test "placement_get info returns all fields" { - if (comptime !build_options.kitty_graphics) return error.SkipZigTest; - - var t: terminal_c.Terminal = null; - try testing.expectEqual(Result.success, terminal_c.new( - &lib.alloc.test_allocator, - &t, - .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, - )); - defer terminal_c.free(t); - try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20)); - - const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\"; - terminal_c.vt_write(t, cmd.ptr, cmd.len); - - var graphics: KittyGraphics = undefined; - try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics))); - - var iter: PlacementIterator = null; - try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter)); - defer placement_iterator_free(iter); - try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter))); - try testing.expect(placement_iterator_next(iter)); - - var info: PlacementInfo = .{}; - try testing.expectEqual(Result.success, placement_get(iter, .info, @ptrCast(&info))); - try testing.expectEqual(1, info.image_id); - try testing.expectEqual(1, info.placement_id); - try testing.expect(!info.is_virtual); - try testing.expectEqual(10, info.columns); - try testing.expectEqual(1, info.rows); -} - -test "placement_get info null returns invalid_value" { - if (comptime !build_options.kitty_graphics) return error.SkipZigTest; - - var info: PlacementInfo = .{}; - try testing.expectEqual(Result.invalid_value, placement_get(null, .info, @ptrCast(&info))); -} - test "placement_render_info returns all fields" { if (comptime !build_options.kitty_graphics) return error.SkipZigTest; @@ -1687,3 +1596,115 @@ test "placement_render_info null returns invalid_value" { var ri: PlacementRenderInfo = .{}; try testing.expectEqual(Result.invalid_value, placement_render_info(null, null, null, &ri)); } + +test "image_get_multi success" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var t: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &t, + .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, + )); + defer terminal_c.free(t); + try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20)); + + const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\"; + terminal_c.vt_write(t, cmd.ptr, cmd.len); + + var graphics: KittyGraphics = undefined; + try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics))); + const img = image_get_handle(graphics, 1); + try testing.expect(img != null); + + var id: u32 = 0; + var width: u32 = 0; + var height: u32 = 0; + var written: usize = 0; + + const keys = [_]ImageData{ .id, .width, .height }; + var values = [_]?*anyopaque{ @ptrCast(&id), @ptrCast(&width), @ptrCast(&height) }; + try testing.expectEqual(Result.success, image_get_multi(img, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expectEqual(1, id); + try testing.expectEqual(1, width); + try testing.expectEqual(2, height); +} + +test "image_get_multi error sets out_written" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var id: u32 = 0; + var written: usize = 99; + + const keys = [_]ImageData{ .id, .invalid }; + var values = [_]?*anyopaque{ @ptrCast(&id), @ptrCast(&id) }; + try testing.expectEqual(Result.invalid_value, image_get_multi(null, keys.len, &keys, &values, &written)); + try testing.expectEqual(0, written); +} + +test "image_get_multi null keys returns invalid_value" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var id: u32 = 0; + var values = [_]?*anyopaque{@ptrCast(&id)}; + try testing.expectEqual(Result.invalid_value, image_get_multi(null, 1, null, &values, null)); +} + +test "placement_get_multi success" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var t: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &t, + .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, + )); + defer terminal_c.free(t); + try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20)); + + const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2,c=10,r=1;////////\x1b\\"; + terminal_c.vt_write(t, cmd.ptr, cmd.len); + + var graphics: KittyGraphics = undefined; + try testing.expectEqual(Result.success, terminal_c.get(t, .kitty_graphics, @ptrCast(&graphics))); + + var iter: PlacementIterator = null; + try testing.expectEqual(Result.success, placement_iterator_new(&lib.alloc.test_allocator, &iter)); + defer placement_iterator_free(iter); + try testing.expectEqual(Result.success, get(graphics, .placement_iterator, @ptrCast(&iter))); + try testing.expect(placement_iterator_next(iter)); + + var image_id: u32 = 0; + var columns: u32 = 0; + var z: i32 = 99; + var written: usize = 0; + + const keys = [_]PlacementData{ .image_id, .columns, .z }; + var values = [_]?*anyopaque{ @ptrCast(&image_id), @ptrCast(&columns), @ptrCast(&z) }; + try testing.expectEqual(Result.success, placement_get_multi(iter, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expectEqual(1, image_id); + try testing.expectEqual(10, columns); + try testing.expectEqual(0, z); +} + +test "placement_get_multi error sets out_written" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var id: u32 = 0; + var written: usize = 99; + + const keys = [_]PlacementData{ .image_id, .invalid }; + var values = [_]?*anyopaque{ @ptrCast(&id), @ptrCast(&id) }; + try testing.expectEqual(Result.invalid_value, placement_get_multi(null, keys.len, &keys, &values, &written)); + try testing.expectEqual(0, written); +} + +test "placement_get_multi null keys returns invalid_value" { + if (comptime !build_options.kitty_graphics) return error.SkipZigTest; + + var id: u32 = 0; + var values = [_]?*anyopaque{@ptrCast(&id)}; + try testing.expectEqual(Result.invalid_value, placement_get_multi(null, 1, null, &values, null)); +} diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index e61b83e4b..126b66401 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -12,11 +12,13 @@ pub const kitty_graphics = @import("kitty_graphics.zig"); pub const kitty_graphics_get = kitty_graphics.get; pub const kitty_graphics_image = kitty_graphics.image_get_handle; pub const kitty_graphics_image_get = kitty_graphics.image_get; +pub const kitty_graphics_image_get_multi = kitty_graphics.image_get_multi; pub const kitty_graphics_placement_iterator_new = kitty_graphics.placement_iterator_new; pub const kitty_graphics_placement_iterator_free = kitty_graphics.placement_iterator_free; pub const kitty_graphics_placement_iterator_set = kitty_graphics.placement_iterator_set; pub const kitty_graphics_placement_next = kitty_graphics.placement_iterator_next; pub const kitty_graphics_placement_get = kitty_graphics.placement_get; +pub const kitty_graphics_placement_get_multi = kitty_graphics.placement_get_multi; pub const kitty_graphics_placement_rect = kitty_graphics.placement_rect; pub const kitty_graphics_placement_pixel_size = kitty_graphics.placement_pixel_size; pub const kitty_graphics_placement_grid_size = kitty_graphics.placement_grid_size; @@ -66,17 +68,20 @@ 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_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; +pub const render_state_row_get_multi = render.row_get_multi; 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_get_multi = render.row_cells_get_multi; pub const render_state_row_cells_free = render.row_cells_free; pub const sgr_new = sgr.new; @@ -142,8 +147,10 @@ pub const alloc_free = allocator.free; pub const size_report_encode = size_report.encode; pub const cell_get = cell.get; +pub const cell_get_multi = cell.get_multi; pub const row_get = row.get; +pub const row_get_multi = row.get_multi; pub const style_default = style.default_style; pub const style_is_default = style.style_is_default; @@ -161,6 +168,7 @@ pub const terminal_scroll_viewport = terminal.scroll_viewport; pub const terminal_mode_get = terminal.mode_get; pub const terminal_mode_set = terminal.mode_set; pub const terminal_get = terminal.get; +pub const terminal_get_multi = terminal.get_multi; pub const terminal_grid_ref = terminal.grid_ref; pub const terminal_point_from_grid_ref = terminal.point_from_grid_ref; diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index b95bedd5f..af82ddfa1 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -201,6 +201,27 @@ pub fn get( }; } +pub fn get_multi( + state_: RenderState, + count: usize, + keys: ?[*]const Data, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = get(state_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn getTyped( state_: RenderState, comptime data: Data, @@ -468,6 +489,27 @@ pub fn row_cells_get( }; } +pub fn row_cells_get_multi( + cells_: RowCells, + count: usize, + keys: ?[*]const RowCellsData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = row_cells_get(cells_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn rowCellsGetTyped( cells_: RowCells, comptime data: RowCellsData, @@ -568,6 +610,27 @@ pub fn row_get( }; } +pub fn row_get_multi( + iterator_: RowIterator, + count: usize, + keys: ?[*]const RowData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = row_get(iterator_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn rowGetTyped( iterator_: RowIterator, comptime data: RowData, @@ -1380,3 +1443,137 @@ test "render: colors get supports truncated sized struct" { try testing.expectEqual(state_colors.palette[1].cval(), colors.palette[1]); try testing.expectEqual(sentinel, colors.palette[2]); } + +test "render: get_multi success" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + .{ .cols = 80, .rows = 24, .max_scrollback = 10_000 }, + )); + 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 cols: u16 = 0; + var rows: u16 = 0; + var written: usize = 0; + + const keys = [_]Data{ .cols, .rows }; + var values = [_]?*anyopaque{ @ptrCast(&cols), @ptrCast(&rows) }; + try testing.expectEqual(Result.success, get_multi(state, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expectEqual(80, cols); + try testing.expectEqual(24, rows); +} + +test "render: get_multi null returns invalid_value" { + var cols: u16 = 0; + var values = [_]?*anyopaque{@ptrCast(&cols)}; + try testing.expectEqual(Result.invalid_value, get_multi(null, 1, null, &values, null)); +} + +test "render: row_get_multi success" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + .{ .cols = 80, .rows = 24, .max_scrollback = 10_000 }, + )); + 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 it: RowIterator = null; + try testing.expectEqual(Result.success, row_iterator_new( + &lib.alloc.test_allocator, + &it, + )); + defer row_iterator_free(it); + + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it))); + try testing.expect(row_iterator_next(it)); + + var dirty: bool = true; + var written: usize = 0; + + const keys = [_]RowData{.dirty}; + var values = [_]?*anyopaque{@ptrCast(&dirty)}; + try testing.expectEqual(Result.success, row_get_multi(it, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); +} + +test "render: row_get_multi null returns invalid_value" { + var dirty: bool = false; + var values = [_]?*anyopaque{@ptrCast(&dirty)}; + try testing.expectEqual(Result.invalid_value, row_get_multi(null, 1, null, &values, null)); +} + +test "render: row_cells_get_multi success" { + var terminal: terminal_c.Terminal = null; + try testing.expectEqual(Result.success, terminal_c.new( + &lib.alloc.test_allocator, + &terminal, + .{ .cols = 80, .rows = 24, .max_scrollback = 10_000 }, + )); + defer terminal_c.free(terminal); + + terminal_c.vt_write(terminal, "A", 1); + + 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); + + try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it))); + try testing.expect(row_iterator_next(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, row_get(it, .cells, @ptrCast(&cells))); + try testing.expect(row_cells_next(cells)); + + var raw: row.CRow = undefined; + var written: usize = 0; + + const keys = [_]RowCellsData{.raw}; + var values = [_]?*anyopaque{@ptrCast(&raw)}; + try testing.expectEqual(Result.success, row_cells_get_multi(cells, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); +} + +test "render: row_cells_get_multi null returns invalid_value" { + var raw: row.CRow = undefined; + var values = [_]?*anyopaque{@ptrCast(&raw)}; + try testing.expectEqual(Result.invalid_value, row_cells_get_multi(null, 1, null, &values, null)); +} diff --git a/src/terminal/c/row.zig b/src/terminal/c/row.zig index d45327066..66c398c8b 100644 --- a/src/terminal/c/row.zig +++ b/src/terminal/c/row.zig @@ -83,6 +83,27 @@ pub fn get( }; } +pub fn get_multi( + row_: CRow, + count: usize, + keys: ?[*]const RowData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = get(row_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn getTyped( row_: CRow, comptime data: RowData, @@ -130,3 +151,43 @@ test "get dirty" { try testing.expectEqual(Result.success, get(row, .dirty, @ptrCast(&dirty))); try testing.expect(dirty); } + +test "get_multi success" { + var zig_row: Row = @bitCast(@as(u64, 0)); + zig_row.wrap = true; + zig_row.dirty = true; + const row_val: CRow = @bitCast(zig_row); + + var wrap: bool = false; + var dirty: bool = false; + var written: usize = 0; + + const keys = [_]RowData{ .wrap, .dirty }; + var values = [_]?*anyopaque{ @ptrCast(&wrap), @ptrCast(&dirty) }; + try testing.expectEqual(Result.success, get_multi(row_val, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expect(wrap); + try testing.expect(dirty); +} + +test "get_multi error sets out_written" { + var zig_row: Row = @bitCast(@as(u64, 0)); + zig_row.wrap = true; + const row_val: CRow = @bitCast(zig_row); + + var wrap: bool = false; + var written: usize = 99; + + const keys = [_]RowData{ .wrap, .invalid }; + var values = [_]?*anyopaque{ @ptrCast(&wrap), @ptrCast(&wrap) }; + try testing.expectEqual(Result.invalid_value, get_multi(row_val, keys.len, &keys, &values, &written)); + try testing.expectEqual(1, written); + try testing.expect(wrap); +} + +test "get_multi null keys returns invalid_value" { + const row_val: CRow = @bitCast(@as(u64, 0)); + var wrap: bool = false; + var values = [_]?*anyopaque{@ptrCast(&wrap)}; + try testing.expectEqual(Result.invalid_value, get_multi(row_val, 1, null, &values, null)); +} diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 8a2a3d40b..0931b1685 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -612,6 +612,27 @@ pub fn get( }; } +pub fn get_multi( + terminal_: Terminal, + count: usize, + keys: ?[*]const TerminalData, + values: ?[*]?*anyopaque, + out_written: ?*usize, +) callconv(lib.calling_conv) Result { + const k = keys orelse return .invalid_value; + const v = values orelse return .invalid_value; + + for (0..count) |i| { + const result = get(terminal_, k[i], v[i]); + if (result != .success) { + if (out_written) |w| w.* = i; + return result; + } + } + if (out_written) |w| w.* = count; + return .success; +} + fn getTyped( terminal_: Terminal, comptime data: TerminalData, @@ -2537,3 +2558,49 @@ test "set color sets dirty flag" { try testing.expectEqual(Result.success, set(t, .color_foreground, @ptrCast(&fg))); try testing.expect(zt.flags.dirty.palette); } + +test "get_multi success" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &t, + .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, + )); + defer free(t); + + var cols: u16 = 0; + var rows: u16 = 0; + var written: usize = 0; + + const keys = [_]TerminalData{ .cols, .rows }; + var values = [_]?*anyopaque{ @ptrCast(&cols), @ptrCast(&rows) }; + try testing.expectEqual(Result.success, get_multi(t, keys.len, &keys, &values, &written)); + try testing.expectEqual(keys.len, written); + try testing.expectEqual(80, cols); + try testing.expectEqual(24, rows); +} + +test "get_multi error sets out_written" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib.alloc.test_allocator, + &t, + .{ .cols = 80, .rows = 24, .max_scrollback = 0 }, + )); + defer free(t); + + var cols: u16 = 0; + var written: usize = 99; + + const keys = [_]TerminalData{ .cols, .invalid }; + var values = [_]?*anyopaque{ @ptrCast(&cols), @ptrCast(&cols) }; + try testing.expectEqual(Result.invalid_value, get_multi(t, keys.len, &keys, &values, &written)); + try testing.expectEqual(1, written); + try testing.expectEqual(80, cols); +} + +test "get_multi null keys returns invalid_value" { + var cols: u16 = 0; + var values = [_]?*anyopaque{@ptrCast(&cols)}; + try testing.expectEqual(Result.invalid_value, get_multi(null, 1, null, &values, null)); +}