mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
libghostty: starting render state API in C (#11664)
This adds the `terminal.RenderState` API for C.
The render state API is the API that should be used to create a high
performance renderer. It limits access to a terminal instance to a very
optimized `update` call so that terminal IO is blocked for a tiny amount
of time. After that, all read access on the RenderState is safe to build
frame data.
## Example
```c
int main(void) {
GhosttyResult result;
GhosttyTerminal terminal = NULL;
GhosttyTerminalOptions terminal_opts = {
.cols = 80,
.rows = 24,
.max_scrollback = 10000,
};
result = ghostty_terminal_new(NULL, &terminal, terminal_opts);
assert(result == GHOSTTY_SUCCESS);
GhosttyRenderState render_state = NULL;
result = ghostty_render_state_new(NULL, &render_state);
assert(result == GHOSTTY_SUCCESS);
const char* first_frame = "first frame\r\n";
ghostty_terminal_vt_write(
terminal,
(const uint8_t*)first_frame,
strlen(first_frame));
result = ghostty_render_state_update(render_state, terminal);
assert(result == GHOSTTY_SUCCESS);
const char* second_frame = "second frame\r\n";
ghostty_terminal_vt_write(
terminal,
(const uint8_t*)second_frame,
strlen(second_frame));
result = ghostty_render_state_update(render_state, terminal);
assert(result == GHOSTTY_SUCCESS);
printf("Render state was updated successfully.\n");
ghostty_render_state_free(render_state);
ghostty_terminal_free(terminal);
return 0;
}
```
## API Changes
New `GhosttyRenderState` C API (`include/ghostty/vt/render.h`):
| Function | Description |
|---|---|
| `ghostty_render_state_new` | Allocate an empty render state. |
| `ghostty_render_state_free` | Destroy a render state. |
| `ghostty_render_state_update` | Snapshot a terminal instance into the
render state. |
| `ghostty_render_state_get` | Type-tagged read of dimensions, dirty
state, colors, cursor, palette. |
| `ghostty_render_state_set` | Type-tagged write (currently: dirty
state). |
| `ghostty_render_state_colors_get` | Bulk color read via sized-struct
for forward compatibility. |
| `ghostty_render_state_row_iterator_new` | Allocate a reusable row
iterator. |
| `ghostty_render_state_row_iterator_next` | Advance the row iterator. |
| `ghostty_render_state_row_iterator_free` | Destroy a row iterator. |
| `ghostty_render_state_row_get` | Read per-row data (dirty flag, raw
row, cells). |
| `ghostty_render_state_row_set` | Write per-row data (dirty flag). |
| `ghostty_render_state_row_cells_new` | Allocate a reusable cell
iterator. |
| `ghostty_render_state_row_cells_next` | Advance the cell iterator. |
| `ghostty_render_state_row_cells_select` | Jump the cell iterator to a
specific column. |
| `ghostty_render_state_row_cells_get` | Read per-cell data (raw cell,
style, graphemes). |
| `ghostty_render_state_row_cells_free` | Destroy a cell iterator. |
`GhosttyRenderStateData` keys (for `_get`):
| Key | Type | Description |
|---|---|---|
| `COLS` | `uint16_t` | Viewport width in cells. |
| `ROWS` | `uint16_t` | Viewport height in cells. |
| `DIRTY` | `GhosttyRenderStateDirty` | Global dirty state. |
| `ROW_ITERATOR` | `GhosttyRenderStateRowIterator` | Populate a
pre-allocated row iterator. |
| `COLOR_BACKGROUND` | `GhosttyColorRgb` | Default background color. |
| `COLOR_FOREGROUND` | `GhosttyColorRgb` | Default foreground color. |
| `COLOR_CURSOR` | `GhosttyColorRgb` | Explicit cursor color (invalid if
not set). |
| `COLOR_CURSOR_HAS_VALUE` | `bool` | Whether an explicit cursor color
is set. |
| `COLOR_PALETTE` | `GhosttyColorRgb[256]` | Active 256-color palette. |
| `CURSOR_VISUAL_STYLE` | `GhosttyRenderStateCursorVisualStyle` | Bar,
block, underline, or hollow block. |
| `CURSOR_VISIBLE` | `bool` | Cursor visibility from terminal modes. |
| `CURSOR_BLINKING` | `bool` | Cursor blink state from terminal modes. |
| `CURSOR_PASSWORD_INPUT` | `bool` | Whether cursor is at a password
field. |
| `CURSOR_VIEWPORT_HAS_VALUE` | `bool` | Whether cursor is in the
viewport. |
| `CURSOR_VIEWPORT_X` | `uint16_t` | Cursor viewport column. |
| `CURSOR_VIEWPORT_Y` | `uint16_t` | Cursor viewport row. |
| `CURSOR_VIEWPORT_WIDE_TAIL` | `bool` | Cursor on wide-char tail cell.
|
`GhosttyRenderStateOption` keys (for `_set`):
| Key | Type | Description |
|---|---|---|
| `DIRTY` | `GhosttyRenderStateDirty` | Reset global dirty state. |
`GhosttyRenderStateRowData` keys (for `_row_get`):
| Key | Type | Description |
|---|---|---|
| `DIRTY` | `bool` | Whether this row is dirty. |
| `RAW` | `GhosttyRow` | Raw row value. |
| `CELLS` | `GhosttyRenderStateRowCells` | Populate a pre-allocated cell
iterator. |
`GhosttyRenderStateRowOption` keys (for `_row_set`):
| Key | Type | Description |
|---|---|---|
| `DIRTY` | `bool` | Clear/set dirty flag for this row. |
`GhosttyRenderStateRowCellsData` keys (for `_row_cells_get`):
| Key | Type | Description |
|---|---|---|
| `RAW` | `GhosttyCell` | Raw cell value. |
| `STYLE` | `GhosttyStyle` | Resolved style for this cell. |
| `GRAPHEMES_LEN` | `uint32_t` | Total codepoints including base (0 if
empty). |
| `GRAPHEMES_BUF` | `uint32_t*` | Write codepoints into caller buffer. |
This commit is contained in:
19
example/c-vt-render/README.md
Normal file
19
example/c-vt-render/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Example: `ghostty-vt` Render State
|
||||
|
||||
This contains an example of how to use the `ghostty-vt` render-state API
|
||||
to create a render state, update it from terminal content, iterate rows
|
||||
and cells, read styles and colors, inspect cursor state, and manage dirty
|
||||
tracking.
|
||||
|
||||
This uses a `build.zig` and `Zig` to build the C program so that we
|
||||
can reuse a lot of our build logic and depend directly on our source
|
||||
tree, but Ghostty emits a standard C library that can be used with any
|
||||
C tooling.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the program:
|
||||
|
||||
```shell-session
|
||||
zig build run
|
||||
```
|
||||
42
example/c-vt-render/build.zig
Normal file
42
example/c-vt-render/build.zig
Normal file
@@ -0,0 +1,42 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
exe_mod.addCSourceFiles(.{
|
||||
.root = b.path("src"),
|
||||
.files = &.{"main.c"},
|
||||
});
|
||||
|
||||
// You'll want to use a lazy dependency here so that ghostty is only
|
||||
// downloaded if you actually need it.
|
||||
if (b.lazyDependency("ghostty", .{
|
||||
// Setting simd to false will force a pure static build that
|
||||
// doesn't even require libc, but it has a significant performance
|
||||
// penalty. If your embedding app requires libc anyway, you should
|
||||
// always keep simd enabled.
|
||||
// .simd = false,
|
||||
})) |dep| {
|
||||
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
|
||||
}
|
||||
|
||||
// Exe
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "c_vt_render",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
b.installArtifact(exe);
|
||||
|
||||
// Run
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
24
example/c-vt-render/build.zig.zon
Normal file
24
example/c-vt-render/build.zig.zon
Normal file
@@ -0,0 +1,24 @@
|
||||
.{
|
||||
.name = .c_vt_render,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0xb10e18b2fab773c9,
|
||||
.minimum_zig_version = "0.15.1",
|
||||
.dependencies = .{
|
||||
// Ghostty dependency. In reality, you'd probably use a URL-based
|
||||
// dependency like the one showed (and commented out) below this one.
|
||||
// We use a path dependency here for simplicity and to ensure our
|
||||
// examples always test against the source they're bundled with.
|
||||
.ghostty = .{ .path = "../../" },
|
||||
|
||||
// Example of what a URL-based dependency looks like:
|
||||
// .ghostty = .{
|
||||
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
|
||||
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
|
||||
// },
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
234
example/c-vt-render/src/main.c
Normal file
234
example/c-vt-render/src/main.c
Normal file
@@ -0,0 +1,234 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ghostty/vt.h>
|
||||
|
||||
/// Helper: resolve a style color to an RGB value using the palette.
|
||||
static GhosttyColorRgb resolve_color(GhosttyStyleColor color,
|
||||
const GhosttyRenderStateColors* colors,
|
||||
GhosttyColorRgb fallback) {
|
||||
switch (color.tag) {
|
||||
case GHOSTTY_STYLE_COLOR_RGB:
|
||||
return color.value.rgb;
|
||||
case GHOSTTY_STYLE_COLOR_PALETTE:
|
||||
return colors->palette[color.value.palette];
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
GhosttyResult result;
|
||||
|
||||
//! [render-state-update]
|
||||
// Create a terminal and render state, then update the render state
|
||||
// from the terminal. The render state captures a snapshot of everything
|
||||
// needed to draw a frame.
|
||||
GhosttyTerminal terminal = NULL;
|
||||
GhosttyTerminalOptions terminal_opts = {
|
||||
.cols = 40,
|
||||
.rows = 5,
|
||||
.max_scrollback = 10000,
|
||||
};
|
||||
result = ghostty_terminal_new(NULL, &terminal, terminal_opts);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttyRenderState render_state = NULL;
|
||||
result = ghostty_render_state_new(NULL, &render_state);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Feed some styled content into the terminal.
|
||||
const char* content =
|
||||
"Hello, \033[1;32mworld\033[0m!\r\n" // bold green "world"
|
||||
"\033[4munderlined\033[0m text\r\n" // underlined text
|
||||
"\033[38;2;255;128;0morange\033[0m\r\n"; // 24-bit orange fg
|
||||
ghostty_terminal_vt_write(
|
||||
terminal, (const uint8_t*)content, strlen(content));
|
||||
|
||||
result = ghostty_render_state_update(render_state, terminal);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
//! [render-state-update]
|
||||
|
||||
//! [render-dirty-check]
|
||||
// Check the global dirty state to decide how much work the renderer
|
||||
// needs to do. After rendering, reset it to false.
|
||||
GhosttyRenderStateDirty dirty;
|
||||
result = ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_DIRTY, &dirty);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
switch (dirty) {
|
||||
case GHOSTTY_RENDER_STATE_DIRTY_FALSE:
|
||||
printf("Frame is clean, nothing to draw.\n");
|
||||
break;
|
||||
case GHOSTTY_RENDER_STATE_DIRTY_PARTIAL:
|
||||
printf("Partial redraw needed.\n");
|
||||
break;
|
||||
case GHOSTTY_RENDER_STATE_DIRTY_FULL:
|
||||
printf("Full redraw needed.\n");
|
||||
break;
|
||||
}
|
||||
//! [render-dirty-check]
|
||||
|
||||
//! [render-colors]
|
||||
// Retrieve colors (background, foreground, palette) from the render
|
||||
// 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);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
printf("Background: #%02x%02x%02x\n",
|
||||
colors.background.r, colors.background.g, colors.background.b);
|
||||
printf("Foreground: #%02x%02x%02x\n",
|
||||
colors.foreground.r, colors.foreground.g, colors.foreground.b);
|
||||
//! [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);
|
||||
|
||||
const char* style_name = "unknown";
|
||||
switch (style) {
|
||||
case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR:
|
||||
style_name = "bar";
|
||||
break;
|
||||
case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK:
|
||||
style_name = "block";
|
||||
break;
|
||||
case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_UNDERLINE:
|
||||
style_name = "underline";
|
||||
break;
|
||||
case GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK_HOLLOW:
|
||||
style_name = "hollow";
|
||||
break;
|
||||
}
|
||||
printf("Cursor at (%u, %u), style: %s\n", cx, cy, style_name);
|
||||
}
|
||||
//! [render-cursor]
|
||||
|
||||
//! [render-row-iterate]
|
||||
// Iterate rows via the row iterator. For each dirty row, iterate its
|
||||
// cells, read codepoints/graphemes and styles, and emit ANSI-colored
|
||||
// output as a simple "renderer".
|
||||
GhosttyRenderStateRowIterator row_iter = NULL;
|
||||
result = ghostty_render_state_row_iterator_new(NULL, &row_iter);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
result = ghostty_render_state_get(
|
||||
render_state, GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR, &row_iter);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttyRenderStateRowCells cells = NULL;
|
||||
result = ghostty_render_state_row_cells_new(NULL, &cells);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
int row_index = 0;
|
||||
while (ghostty_render_state_row_iterator_next(row_iter)) {
|
||||
// Check per-row dirty state; a real renderer would skip clean rows.
|
||||
bool row_dirty = false;
|
||||
ghostty_render_state_row_get(
|
||||
row_iter, GHOSTTY_RENDER_STATE_ROW_DATA_DIRTY, &row_dirty);
|
||||
|
||||
printf("Row %2d [%s]: ", row_index,
|
||||
row_dirty ? "dirty" : "clean");
|
||||
|
||||
// Get cells for this row (reuses the same cells handle).
|
||||
result = ghostty_render_state_row_get(
|
||||
row_iter, GHOSTTY_RENDER_STATE_ROW_DATA_CELLS, &cells);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
while (ghostty_render_state_row_cells_next(cells)) {
|
||||
// Get the grapheme length; 0 means the cell is empty.
|
||||
uint32_t grapheme_len = 0;
|
||||
ghostty_render_state_row_cells_get(
|
||||
cells, GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN,
|
||||
&grapheme_len);
|
||||
|
||||
if (grapheme_len == 0) {
|
||||
putchar(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the style for this cell. Returns the default style for
|
||||
// cells that have no explicit styling.
|
||||
GhosttyStyle style = GHOSTTY_INIT_SIZED(GhosttyStyle);
|
||||
ghostty_render_state_row_cells_get(
|
||||
cells, GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE, &style);
|
||||
|
||||
// Resolve foreground color for this cell.
|
||||
GhosttyColorRgb fg =
|
||||
resolve_color(style.fg_color, &colors, colors.foreground);
|
||||
|
||||
// Emit ANSI true-color escape for the foreground.
|
||||
printf("\033[38;2;%u;%u;%um", fg.r, fg.g, fg.b);
|
||||
if (style.bold) printf("\033[1m");
|
||||
if (style.underline) printf("\033[4m");
|
||||
|
||||
// Read grapheme codepoints into a buffer and print them.
|
||||
// The buffer must be at least grapheme_len elements.
|
||||
uint32_t codepoints[16];
|
||||
uint32_t len = grapheme_len < 16 ? grapheme_len : 16;
|
||||
ghostty_render_state_row_cells_get(
|
||||
cells, GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF,
|
||||
codepoints);
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
// Simple ASCII print; a real renderer would handle UTF-8.
|
||||
if (codepoints[i] < 128)
|
||||
putchar((char)codepoints[i]);
|
||||
else
|
||||
printf("U+%04X", codepoints[i]);
|
||||
}
|
||||
|
||||
printf("\033[0m"); // Reset style after each cell.
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Clear per-row dirty flag after "rendering" it.
|
||||
bool clean = false;
|
||||
ghostty_render_state_row_set(
|
||||
row_iter, GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY, &clean);
|
||||
|
||||
row_index++;
|
||||
}
|
||||
//! [render-row-iterate]
|
||||
|
||||
//! [render-dirty-reset]
|
||||
// After finishing the frame, reset the global dirty state so the next
|
||||
// update can report changes accurately.
|
||||
GhosttyRenderStateDirty clean_state = GHOSTTY_RENDER_STATE_DIRTY_FALSE;
|
||||
result = ghostty_render_state_set(
|
||||
render_state, GHOSTTY_RENDER_STATE_OPTION_DIRTY, &clean_state);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
//! [render-dirty-reset]
|
||||
|
||||
// Cleanup
|
||||
ghostty_render_state_row_cells_free(cells);
|
||||
ghostty_render_state_row_iterator_free(row_iter);
|
||||
ghostty_render_state_free(render_state);
|
||||
ghostty_terminal_free(terminal);
|
||||
return 0;
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
*
|
||||
* The API is organized into the following groups:
|
||||
* - @ref terminal "Terminal" - Complete terminal emulator state and rendering
|
||||
* - @ref render "Render State" - Incremental render state updates for custom renderers
|
||||
* - @ref formatter "Formatter" - Format terminal content as plain text, VT sequences, or HTML
|
||||
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
|
||||
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
|
||||
@@ -99,8 +100,10 @@ extern "C" {
|
||||
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/color.h>
|
||||
#include <ghostty/vt/focus.h>
|
||||
#include <ghostty/vt/formatter.h>
|
||||
#include <ghostty/vt/render.h>
|
||||
#include <ghostty/vt/terminal.h>
|
||||
#include <ghostty/vt/grid_ref.h>
|
||||
#include <ghostty/vt/osc.h>
|
||||
|
||||
587
include/ghostty/vt/render.h
Normal file
587
include/ghostty/vt/render.h
Normal file
@@ -0,0 +1,587 @@
|
||||
/**
|
||||
* @file render.h
|
||||
*
|
||||
* Render state for creating high performance renderers.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_RENDER_H
|
||||
#define GHOSTTY_VT_RENDER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/color.h>
|
||||
#include <ghostty/vt/terminal.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup render Render State
|
||||
*
|
||||
* Represents the state required to render a visible screen (a viewport)
|
||||
* of a terminal instance. This is stateful and optimized for repeated
|
||||
* updates from a single terminal instance and only updating dirty regions
|
||||
* of the screen.
|
||||
*
|
||||
* The key design principle of this API is that it only needs read/write
|
||||
* access to the terminal instance during the update call. This allows
|
||||
* the render state to minimally impact terminal IO performance and also
|
||||
* allows the renderer to be safely multi-threaded (as long as a lock is
|
||||
* held during the update call to ensure exclusive access to the terminal
|
||||
* instance).
|
||||
*
|
||||
* The basic usage of this API is:
|
||||
*
|
||||
* 1. Create an empty render state
|
||||
* 2. Update it from a terminal instance whenever you need.
|
||||
* 3. Read from the render state to get the data needed to draw your frame.
|
||||
*
|
||||
* ## Dirty Tracking
|
||||
*
|
||||
* Dirty tracking is a key feature of the render state that allows renderers
|
||||
* to efficiently determine what parts of the screen have changed and only
|
||||
* redraw changed regions.
|
||||
*
|
||||
* The render state API keeps track of dirty state at two independent layers:
|
||||
* a global dirty state that indicates whether the entire frame is clean,
|
||||
* partially dirty, or fully dirty, and a per-row dirty state that allows
|
||||
* tracking which rows in a partially dirty frame have changed.
|
||||
*
|
||||
* The user of the render state API is expected to unset both of these.
|
||||
* The `update` call does not unset dirty state, it only updates it.
|
||||
*
|
||||
* An extremely important detail: setting one dirty state doesn't unset
|
||||
* the other. For example, setting the global dirty state to false does not
|
||||
* reset the row-level dirty flags. So, the caller of the render state API must
|
||||
* be careful to manage both layers of dirty state correctly.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* ### Creating and updating render state
|
||||
* @snippet c-vt-render/src/main.c render-state-update
|
||||
*
|
||||
* ### Checking dirty state
|
||||
* @snippet c-vt-render/src/main.c render-dirty-check
|
||||
*
|
||||
* ### Reading colors
|
||||
* @snippet c-vt-render/src/main.c render-colors
|
||||
*
|
||||
* ### Reading cursor state
|
||||
* @snippet c-vt-render/src/main.c render-cursor
|
||||
*
|
||||
* ### Iterating rows and cells
|
||||
* @snippet c-vt-render/src/main.c render-row-iterate
|
||||
*
|
||||
* ### Resetting dirty state after rendering
|
||||
* @snippet c-vt-render/src/main.c render-dirty-reset
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opaque handle to a render state instance.
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef struct GhosttyRenderState* GhosttyRenderState;
|
||||
|
||||
/**
|
||||
* Opaque handle to a render-state row iterator.
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef struct GhosttyRenderStateRowIterator* GhosttyRenderStateRowIterator;
|
||||
|
||||
/**
|
||||
* Opaque handle to render-state row cells.
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef struct GhosttyRenderStateRowCells* GhosttyRenderStateRowCells;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Visual style of the cursor.
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Bar cursor (DECSCUSR 5, 6). */
|
||||
GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR = 0,
|
||||
|
||||
/** Block cursor (DECSCUSR 1, 2). */
|
||||
GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK = 1,
|
||||
|
||||
/** Underline cursor (DECSCUSR 3, 4). */
|
||||
GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_UNDERLINE = 2,
|
||||
|
||||
/** Hollow block cursor. */
|
||||
GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK_HOLLOW = 3,
|
||||
} GhosttyRenderStateCursorVisualStyle;
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_render_state_get().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_RENDER_STATE_DATA_INVALID = 0,
|
||||
|
||||
/** Viewport width in cells (uint16_t). */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLS = 1,
|
||||
|
||||
/** Viewport height in cells (uint16_t). */
|
||||
GHOSTTY_RENDER_STATE_DATA_ROWS = 2,
|
||||
|
||||
/** Current dirty state (GhosttyRenderStateDirty). */
|
||||
GHOSTTY_RENDER_STATE_DATA_DIRTY = 3,
|
||||
|
||||
/** Populate a pre-allocated GhosttyRenderStateRowIterator with row data
|
||||
* from the render state (GhosttyRenderStateRowIterator). Row data is
|
||||
* only valid as long as the underlying render state is not updated.
|
||||
* It is unsafe to use row data after updating the render state.
|
||||
* */
|
||||
GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR = 4,
|
||||
|
||||
/** Default/current background color (GhosttyColorRgb). */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLOR_BACKGROUND = 5,
|
||||
|
||||
/** Default/current foreground color (GhosttyColorRgb). */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLOR_FOREGROUND = 6,
|
||||
|
||||
/** Cursor color when explicitly set by terminal state (GhosttyColorRgb).
|
||||
* Returns GHOSTTY_INVALID_VALUE if no explicit cursor color is set;
|
||||
* use COLOR_CURSOR_HAS_VALUE to check first. */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR = 7,
|
||||
|
||||
/** Whether an explicit cursor color is set (bool). */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR_HAS_VALUE = 8,
|
||||
|
||||
/** The active 256-color palette (GhosttyColorRgb[256]). */
|
||||
GHOSTTY_RENDER_STATE_DATA_COLOR_PALETTE = 9,
|
||||
|
||||
/** The visual style of the cursor (GhosttyRenderStateCursorVisualStyle). */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE = 10,
|
||||
|
||||
/** Whether the cursor is visible based on terminal modes (bool). */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE = 11,
|
||||
|
||||
/** Whether the cursor should blink based on terminal modes (bool). */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING = 12,
|
||||
|
||||
/** Whether the cursor is at a password input field (bool). */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT = 13,
|
||||
|
||||
/** Whether the cursor is visible within the viewport (bool).
|
||||
* If false, the cursor viewport position values are undefined. */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE = 14,
|
||||
|
||||
/** Cursor viewport x position in cells (uint16_t).
|
||||
* Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X = 15,
|
||||
|
||||
/** Cursor viewport y position in cells (uint16_t).
|
||||
* Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
|
||||
GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y = 16,
|
||||
|
||||
/** 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,
|
||||
} GhosttyRenderStateData;
|
||||
|
||||
/**
|
||||
* Settable options for ghostty_render_state_set().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Set dirty state (GhosttyRenderStateDirty). */
|
||||
GHOSTTY_RENDER_STATE_OPTION_DIRTY = 0,
|
||||
} GhosttyRenderStateOption;
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_render_state_row_get().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_RENDER_STATE_ROW_DATA_INVALID = 0,
|
||||
|
||||
/** Whether the current row is dirty (bool). */
|
||||
GHOSTTY_RENDER_STATE_ROW_DATA_DIRTY = 1,
|
||||
|
||||
/** The raw row value (GhosttyRow). */
|
||||
GHOSTTY_RENDER_STATE_ROW_DATA_RAW = 2,
|
||||
|
||||
/** Populate a pre-allocated GhosttyRenderStateRowCells with cell data for
|
||||
* the current row (GhosttyRenderStateRowCells). Cell data is only
|
||||
* valid as long as the underlying render state is not updated.
|
||||
* It is unsafe to use cell data after updating the render state. */
|
||||
GHOSTTY_RENDER_STATE_ROW_DATA_CELLS = 3,
|
||||
} GhosttyRenderStateRowData;
|
||||
|
||||
/**
|
||||
* Settable options for ghostty_render_state_row_set().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Set dirty state for the current row (bool). */
|
||||
GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY = 0,
|
||||
} GhosttyRenderStateRowOption;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors);
|
||||
* GhosttyResult result = ghostty_render_state_colors_get(state, &colors);
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyRenderStateColors). */
|
||||
size_t size;
|
||||
|
||||
/** The default/current background color for the render state. */
|
||||
GhosttyColorRgb background;
|
||||
|
||||
/** The default/current foreground color for the render state. */
|
||||
GhosttyColorRgb foreground;
|
||||
|
||||
/** The cursor color when explicitly set by terminal state. */
|
||||
GhosttyColorRgb cursor;
|
||||
|
||||
/**
|
||||
* True when cursor contains a valid explicit cursor color value.
|
||||
* If this is false, the cursor color should be ignored; it will
|
||||
* contain undefined data.
|
||||
* */
|
||||
bool cursor_has_value;
|
||||
|
||||
/** The active 256-color palette for this render state. */
|
||||
GhosttyColorRgb palette[256];
|
||||
} GhosttyRenderStateColors;
|
||||
|
||||
/**
|
||||
* Create a new render state instance.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param state Pointer to store the created render state handle
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
|
||||
* failure
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_new(const GhosttyAllocator* allocator,
|
||||
GhosttyRenderState* state);
|
||||
|
||||
/**
|
||||
* Free a render state instance.
|
||||
*
|
||||
* Releases all resources associated with the render state. After this call,
|
||||
* the render state handle becomes invalid.
|
||||
*
|
||||
* @param state The render state handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
void ghostty_render_state_free(GhosttyRenderState state);
|
||||
|
||||
/**
|
||||
* Update a render state instance from a terminal.
|
||||
*
|
||||
* This consumes terminal/screen dirty state in the same way as the internal
|
||||
* render state update path.
|
||||
*
|
||||
* @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param terminal The terminal handle to read from (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
|
||||
* `terminal` is NULL, GHOSTTY_OUT_OF_MEMORY if updating the state requires
|
||||
* allocation and that allocation fails
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_update(GhosttyRenderState state,
|
||||
GhosttyTerminal terminal);
|
||||
|
||||
/**
|
||||
* Get a value from a render state.
|
||||
*
|
||||
* The `out` pointer must point to a value of the type corresponding to the
|
||||
* requested data kind (see GhosttyRenderStateData).
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_get(GhosttyRenderState state,
|
||||
GhosttyRenderStateData data,
|
||||
void* out);
|
||||
|
||||
/**
|
||||
* Set an option on a render state.
|
||||
*
|
||||
* The `value` pointer must point to a value of the type corresponding to the
|
||||
* requested option kind (see GhosttyRenderStateOption).
|
||||
*
|
||||
* @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param option The option to set
|
||||
* @param[in] value Pointer to the value to set (NULL returns
|
||||
* GHOSTTY_INVALID_VALUE)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
|
||||
* `value` is NULL
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
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
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_colors_get(GhosttyRenderState state,
|
||||
GhosttyRenderStateColors* out_colors);
|
||||
|
||||
/**
|
||||
* Create a new row iterator instance.
|
||||
*
|
||||
* All fields except the allocator are left undefined until populated
|
||||
* via ghostty_render_state_get() with
|
||||
* GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param[out] out_iterator On success, receives the created iterator handle
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
|
||||
* failure
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_iterator_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttyRenderStateRowIterator* out_iterator);
|
||||
|
||||
/**
|
||||
* Free a render-state row iterator.
|
||||
*
|
||||
* @param iterator The iterator handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
void ghostty_render_state_row_iterator_free(GhosttyRenderStateRowIterator iterator);
|
||||
|
||||
/**
|
||||
* Move a render-state row iterator to the next row.
|
||||
*
|
||||
* Returns true if the iterator moved successfully and row data is
|
||||
* available to read at the new position.
|
||||
*
|
||||
* @param iterator The iterator handle to advance (may be NULL)
|
||||
* @return true if advanced to the next row, false if `iterator` is
|
||||
* NULL or if the iterator has reached the end
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
bool ghostty_render_state_row_iterator_next(GhosttyRenderStateRowIterator iterator);
|
||||
|
||||
/**
|
||||
* Get a value from the current row in a render-state row iterator.
|
||||
*
|
||||
* The `out` pointer must point to a value of the type corresponding to the
|
||||
* requested data kind (see GhosttyRenderStateRowData).
|
||||
* Call ghostty_render_state_row_iterator_next() at least once before
|
||||
* calling this function.
|
||||
*
|
||||
* @param iterator The iterator handle to query (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
|
||||
* `iterator` is NULL or the iterator is not positioned on a row
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_get(
|
||||
GhosttyRenderStateRowIterator iterator,
|
||||
GhosttyRenderStateRowData data,
|
||||
void* out);
|
||||
|
||||
/**
|
||||
* Set an option on the current row in a render-state row iterator.
|
||||
*
|
||||
* The `value` pointer must point to a value of the type corresponding to the
|
||||
* requested option kind (see GhosttyRenderStateRowOption).
|
||||
* Call ghostty_render_state_row_iterator_next() at least once before
|
||||
* calling this function.
|
||||
*
|
||||
* @param iterator The iterator handle to update (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param option The option to set
|
||||
* @param[in] value Pointer to the value to set (NULL returns
|
||||
* GHOSTTY_INVALID_VALUE)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if
|
||||
* `iterator` is NULL or the iterator is not positioned on a row
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_set(
|
||||
GhosttyRenderStateRowIterator iterator,
|
||||
GhosttyRenderStateRowOption option,
|
||||
const void* value);
|
||||
|
||||
/**
|
||||
* Create a new row cells instance.
|
||||
*
|
||||
* All fields except the allocator are left undefined until populated
|
||||
* via ghostty_render_state_row_get() with
|
||||
* GHOSTTY_RENDER_STATE_ROW_DATA_CELLS.
|
||||
*
|
||||
* You can reuse this value repeatedly with ghostty_render_state_row_get() to
|
||||
* avoid allocating a new cells container for every row.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param[out] out_cells On success, receives the created row cells handle
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
|
||||
* failure
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_cells_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttyRenderStateRowCells* out_cells);
|
||||
|
||||
/**
|
||||
* Queryable data kinds for ghostty_render_state_row_cells_get().
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
typedef enum {
|
||||
/** Invalid / sentinel value. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_INVALID = 0,
|
||||
|
||||
/** The raw cell value (GhosttyCell). */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW = 1,
|
||||
|
||||
/** The style for the current cell (GhosttyStyle). */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE = 2,
|
||||
|
||||
/** The total number of grapheme codepoints including the base codepoint
|
||||
* (uint32_t). Returns 0 if the cell has no text. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3,
|
||||
|
||||
/** Write grapheme codepoints into a caller-provided buffer (uint32_t*).
|
||||
* The buffer must be at least graphemes_len elements. The base codepoint
|
||||
* is written first, followed by any extra codepoints. */
|
||||
GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4,
|
||||
} GhosttyRenderStateRowCellsData;
|
||||
|
||||
/**
|
||||
* Move a render-state row cells iterator to the next cell.
|
||||
*
|
||||
* Returns true if the iterator moved successfully and cell data is
|
||||
* available to read at the new position.
|
||||
*
|
||||
* @param cells The row cells handle to advance (may be NULL)
|
||||
* @return true if advanced to the next cell, false if `cells` is
|
||||
* NULL or if the iterator has reached the end
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
bool ghostty_render_state_row_cells_next(GhosttyRenderStateRowCells cells);
|
||||
|
||||
/**
|
||||
* Move a render-state row cells iterator to a specific column.
|
||||
*
|
||||
* Positions the iterator at the given x (column) index so that
|
||||
* subsequent reads return data for that cell.
|
||||
*
|
||||
* @param cells The row cells handle to reposition (NULL returns
|
||||
* GHOSTTY_INVALID_VALUE)
|
||||
* @param x The zero-based column index to select
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `cells`
|
||||
* is NULL or `x` is out of range
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_cells_select(
|
||||
GhosttyRenderStateRowCells cells, uint16_t x);
|
||||
|
||||
/**
|
||||
* Get a value from the current cell in a render-state row cells iterator.
|
||||
*
|
||||
* The `out` pointer must point to a value of the type corresponding to the
|
||||
* requested data kind (see GhosttyRenderStateRowCellsData).
|
||||
* Call ghostty_render_state_row_cells_next() or
|
||||
* ghostty_render_state_row_cells_select() at least once before
|
||||
* calling this function.
|
||||
*
|
||||
* @param cells The row cells handle to query (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
|
||||
* `cells` is NULL or the iterator is not positioned on a cell
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
GhosttyResult ghostty_render_state_row_cells_get(
|
||||
GhosttyRenderStateRowCells cells,
|
||||
GhosttyRenderStateRowCellsData data,
|
||||
void* out);
|
||||
|
||||
/**
|
||||
* Free a row cells instance.
|
||||
*
|
||||
* @param cells The row cells handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup render
|
||||
*/
|
||||
void ghostty_render_state_row_cells_free(GhosttyRenderStateRowCells cells);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GHOSTTY_VT_RENDER_H */
|
||||
@@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const enumpkg = @import("enum.zig");
|
||||
const structpkg = @import("struct.zig");
|
||||
const types = @import("types.zig");
|
||||
const unionpkg = @import("union.zig");
|
||||
|
||||
@@ -7,7 +8,8 @@ pub const allocator = @import("allocator.zig");
|
||||
pub const Enum = enumpkg.Enum;
|
||||
pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum;
|
||||
pub const String = types.String;
|
||||
pub const Struct = @import("struct.zig").Struct;
|
||||
pub const Struct = structpkg.Struct;
|
||||
pub const structSizedFieldFits = structpkg.sizedFieldFits;
|
||||
pub const Target = @import("target.zig").Target;
|
||||
pub const TaggedUnion = unionpkg.TaggedUnion;
|
||||
pub const cutPrefix = @import("string.zig").cutPrefix;
|
||||
|
||||
@@ -41,6 +41,52 @@ pub fn Struct(
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true if a struct of type `T` with size `size` can set
|
||||
/// field `field` (if it fits within the size). This is used for ABI
|
||||
/// compatibility for structs that have an explicit size field.
|
||||
pub fn sizedFieldFits(
|
||||
comptime T: type,
|
||||
size: usize,
|
||||
comptime field: []const u8,
|
||||
) bool {
|
||||
const offset = @offsetOf(T, field);
|
||||
const field_size = @sizeOf(@FieldType(T, field));
|
||||
return size >= offset + field_size;
|
||||
}
|
||||
|
||||
test "sizedFieldFits boundary checks" {
|
||||
const Sized = extern struct {
|
||||
size: usize,
|
||||
a: u8,
|
||||
b: u32,
|
||||
};
|
||||
|
||||
const size_required = @offsetOf(Sized, "size") + @sizeOf(@FieldType(Sized, "size"));
|
||||
const a_required = @offsetOf(Sized, "a") + @sizeOf(@FieldType(Sized, "a"));
|
||||
const b_required = @offsetOf(Sized, "b") + @sizeOf(@FieldType(Sized, "b"));
|
||||
|
||||
try testing.expect(sizedFieldFits(Sized, size_required, "size"));
|
||||
try testing.expect(!sizedFieldFits(Sized, size_required - 1, "size"));
|
||||
|
||||
try testing.expect(sizedFieldFits(Sized, a_required, "a"));
|
||||
try testing.expect(!sizedFieldFits(Sized, a_required - 1, "a"));
|
||||
|
||||
try testing.expect(sizedFieldFits(Sized, b_required, "b"));
|
||||
try testing.expect(!sizedFieldFits(Sized, b_required - 1, "b"));
|
||||
}
|
||||
|
||||
test "sizedFieldFits respects alignment padding" {
|
||||
const Sized = extern struct {
|
||||
size: usize,
|
||||
a: u8,
|
||||
b: u32,
|
||||
};
|
||||
|
||||
const up_to_padding = @offsetOf(Sized, "b");
|
||||
try testing.expect(sizedFieldFits(Sized, up_to_padding, "a"));
|
||||
try testing.expect(!sizedFieldFits(Sized, up_to_padding, "b"));
|
||||
}
|
||||
|
||||
test "packed struct converts to extern with full-size bools" {
|
||||
const Packed = packed struct {
|
||||
flag1: bool,
|
||||
|
||||
@@ -187,6 +187,22 @@ comptime {
|
||||
@export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" });
|
||||
@export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" });
|
||||
@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_get, .{ .name = "ghostty_render_state_get" });
|
||||
@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_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_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" });
|
||||
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });
|
||||
@export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" });
|
||||
|
||||
@@ -4,6 +4,7 @@ pub const focus = @import("focus.zig");
|
||||
pub const formatter = @import("formatter.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const render = @import("render.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
pub const mouse_event = @import("mouse_event.zig");
|
||||
@@ -35,6 +36,23 @@ pub const formatter_format_buf = formatter.format_buf;
|
||||
pub const formatter_format_alloc = formatter.format_alloc;
|
||||
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_get = render.get;
|
||||
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_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_free = render.row_cells_free;
|
||||
|
||||
pub const sgr_new = sgr.new;
|
||||
pub const sgr_free = sgr.free;
|
||||
pub const sgr_reset = sgr.reset;
|
||||
@@ -126,6 +144,7 @@ test {
|
||||
_ = formatter;
|
||||
_ = modes;
|
||||
_ = osc;
|
||||
_ = render;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
_ = mouse_event;
|
||||
|
||||
1104
src/terminal/c/render.zig
Normal file
1104
src/terminal/c/render.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ const Row = page.Row;
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyRow
|
||||
pub const CRow = u64;
|
||||
pub const CRow = Row.C;
|
||||
|
||||
/// C: GhosttyRowSemanticPrompt
|
||||
pub const SemanticPrompt = enum(c_int) {
|
||||
|
||||
@@ -1947,6 +1947,14 @@ pub const Row = packed struct(u64) {
|
||||
prompt_continuation = 2,
|
||||
};
|
||||
|
||||
/// C ABI type.
|
||||
pub const C = u64;
|
||||
|
||||
/// Returns this row as a C ABI value.
|
||||
pub fn cval(self: Row) C {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
/// Returns true if this row has any managed memory outside of the
|
||||
/// row structure (graphemes, styles, etc.)
|
||||
pub inline fn managedMemory(self: Row) bool {
|
||||
@@ -2051,6 +2059,14 @@ pub const Cell = packed struct(u64) {
|
||||
prompt = 2,
|
||||
};
|
||||
|
||||
/// C ABI type.
|
||||
pub const C = u64;
|
||||
|
||||
/// Returns this cell as a C ABI value.
|
||||
pub fn cval(self: Cell) C {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
/// Helper to make a cell that just has a codepoint.
|
||||
pub fn init(cp: u21) Cell {
|
||||
// We have to use this bitCast here to ensure that our memory is
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user