libghostty-vt: add C API for snapshotting functions

Expose terminal snapshot through the libghostty-vt C API and add
a new C example that runs in CI to verify this stuff works!

## Example

```c
size_t continuation_limit = 1024;
assert(ghostty_terminal_set(
    terminal,
    GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES,
    &continuation_limit) == GHOSTTY_SUCCESS);

uint8_t *bytes = NULL;
size_t len = 0;
assert(ghostty_snapshot_encode_alloc(
    terminal, NULL, &bytes, &len) == GHOSTTY_SUCCESS);

GhosttySnapshotDecoder decoder = NULL;
assert(ghostty_snapshot_decoder_new_buf(
    NULL, &decoder, bytes, len) == GHOSTTY_SUCCESS);

GhosttyTerminal restored = NULL;
assert(ghostty_snapshot_decoder_decode(
    decoder, &restored) == GHOSTTY_SUCCESS);

ghostty_snapshot_decoder_free(decoder);
ghostty_free(NULL, bytes, len);
```

Streaming decode:

```c
GhosttyReader reader = {
    .read = read_snapshot,
    .userdata = source,
};
GhosttySnapshotDecoder decoder = NULL;
assert(ghostty_snapshot_decoder_new(
    NULL, &decoder, reader) == GHOSTTY_SUCCESS);

GhosttyTerminal terminal = NULL;
assert(ghostty_snapshot_decoder_ready(
    decoder, &terminal) == GHOSTTY_SUCCESS);

GhosttyResult result;
while ((result = ghostty_snapshot_decoder_next(decoder)) ==
       GHOSTTY_SUCCESS) {
  size_t rows = 0;
  assert(ghostty_snapshot_decoder_get(
      decoder,
      GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_ROWS,
      &rows) == GHOSTTY_SUCCESS);
  render(terminal);
}
assert(result == GHOSTTY_NO_VALUE);
```
This commit is contained in:
Mitchell Hashimoto
2026-08-03 12:38:08 -07:00
parent 7d748097a0
commit d7bb4b8639
19 changed files with 3652 additions and 62 deletions

View File

@@ -31,12 +31,14 @@
* - @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 snapshot "Terminal Snapshot" - Encode and incrementally restore terminal state
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
* - @ref paste "Paste Utilities" - Validate paste data safety
* - @ref unicode "Unicode Utilities" - Codepoint properties for text layout
* - @ref build_info "Build Info" - Query compile-time build configuration
* - @ref allocator "Memory Management" - Memory management and custom allocators
* - @ref io "Byte-stream I/O" - Reusable synchronous reader and writer callbacks
* - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
*
* Encoding related APIs:
@@ -140,6 +142,7 @@ extern "C" {
#include <ghostty/vt/terminal.h>
#include <ghostty/vt/grid_ref.h>
#include <ghostty/vt/grid_ref_tracked.h>
#include <ghostty/vt/io.h>
#include <ghostty/vt/osc.h>
#include <ghostty/vt/sgr.h>
#include <ghostty/vt/style.h>
@@ -153,6 +156,7 @@ extern "C" {
#include <ghostty/vt/screen.h>
#include <ghostty/vt/selection.h>
#include <ghostty/vt/size_report.h>
#include <ghostty/vt/snapshot.h>
#include <ghostty/vt/unicode.h>
#include <ghostty/vt/wasm.h>

107
include/ghostty/vt/io.h Normal file
View File

@@ -0,0 +1,107 @@
/**
* @file io.h
*
* Generic IO callbacks for libghostty-vt.
*/
#ifndef GHOSTTY_VT_IO_H
#define GHOSTTY_VT_IO_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/** @defgroup io I/O
*
* Synchronous callback interfaces used by APIs that consume or produce byte
* streams. The callback and userdata pointers must remain valid for the full
* lifetime documented by the API receiving a GhosttyReader or GhosttyWriter.
*
* @{
*/
#ifdef __cplusplus
extern "C" {
#endif
/**
* Read bytes from a source.
*
* The callback must set @p out_read to a value no greater than @p capacity
* when returning true. A positive value reports progress; it may be less than
* capacity and does not indicate end-of-file. A zero value is definitive
* end-of-file. It must not be used to report temporary input starvation or a
* would-block condition.
*
* Returning false reports a fatal read error and the value of @p out_read is
* ignored. The library does not inspect or modify errno.
*
* All pointer arguments are borrowed and valid only for the duration of the
* callback. The callback is invoked synchronously on the calling thread.
*
* @param userdata Opaque userdata from GhosttyReader
* @param buffer Destination for read bytes; always non-NULL
* @param capacity Writable capacity of @p buffer; always greater than zero
* @param[out] out_read Number of bytes read when returning true; non-NULL
* @return true for a successful read or end-of-file, false for a fatal error
*/
typedef bool (*GhosttyReaderFn)(
void* userdata,
uint8_t* buffer,
size_t capacity,
size_t* out_read);
/**
* Write bytes to a destination.
*
* Returning true means all @p len bytes were accepted. Returning false
* reports a fatal write error. A callback wrapping an interface that permits
* partial writes must retry internally until the full slice is accepted or
* an error occurs.
*
* On failure, the destination may already contain a prefix of the bytes. The
* calling operation fails and must not be resumed from that partial output.
* The library does not inspect or modify errno.
*
* @p data is borrowed and valid only for the duration of the callback. The
* callback is invoked synchronously on the calling thread. Successful return
* means the bytes were handed to the destination; it does not imply that the
* destination was flushed or made durable.
*
* @param userdata Opaque userdata from GhosttyWriter
* @param data Source bytes; always non-NULL
* @param len Number of source bytes; always greater than zero
* @return true if the complete slice was accepted, false on fatal error
*/
typedef bool (*GhosttyWriterFn)(
void* userdata,
const uint8_t* data,
size_t len);
/**
* A byte source callback and its opaque context.
*
* The struct is passed by value. @p read must be non-NULL.
*/
typedef struct {
GhosttyReaderFn read;
void* userdata;
} GhosttyReader;
/**
* A byte destination callback and its opaque context.
*
* The struct is passed by value. @p write must be non-NULL.
*/
typedef struct {
GhosttyWriterFn write;
void* userdata;
} GhosttyWriter;
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* GHOSTTY_VT_IO_H */

View File

@@ -0,0 +1,525 @@
/**
* @file snapshot.h
*
* Encode and restore complete terminal snapshots.
*/
#ifndef GHOSTTY_VT_SNAPSHOT_H
#define GHOSTTY_VT_SNAPSHOT_H
#include <stddef.h>
#include <stdint.h>
#include <ghostty/vt/allocator.h>
#include <ghostty/vt/io.h>
#include <ghostty/vt/terminal.h>
#include <ghostty/vt/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @defgroup snapshot Terminal Snapshot
*
* Encode and restore the complete state of a terminal via a binary format.
*
* A snapshot is an ordered, authenticated record stream. Its READY checkpoint
* contains enough state to render and resume the terminal, including any
* unfinished VT parser input. Older scrollback pages follow READY and the
* FINISH checkpoint authenticates the complete snapshot.
*
* End-of-file before an operation's required READY or FINISH checkpoint is
* malformed, truncated snapshot data and returns GHOSTTY_INVALID_VALUE.
* GHOSTTY_IO_ERROR is reserved for a reader callback that returns false.
*
* ## Examples
*
* The complete working example is available in `example/c-vt-snapshot`.
*
* ### Encode a terminal and its unfinished VT continuation
* @snippet c-vt-snapshot/src/main.c snapshot-encode
*
* ### Restore a complete snapshot in one call
* @snippet c-vt-snapshot/src/main.c snapshot-decode
*
* ### Adapt a byte source to GhosttyReader
* @snippet c-vt-snapshot/src/main.c snapshot-buffer-reader
*
* ### Restore READY first, then incrementally prepend history
* @snippet c-vt-snapshot/src/main.c snapshot-incremental
*
* ## Format
*
* Every integer is unsigned and little-endian. The stream begins with this
* fixed ten-byte envelope:
*
* @code{.unparsed}
* byte 0 8 10
* +---------------+--------+
* | "GHOSTSNP" | version|
* | 8-byte magic | u16 |
* +---------------+--------+
* @endcode
*
* The envelope is followed by independently checksummed records. A record's
* CRC32C covers its encoded tag and payload length followed by its payload; it
* does not cover the CRC field itself.
*
* @code{.unparsed}
* byte 0 2 6 10 10 + payload_len
* +-------+-------------+-----------+----------------+
* | tag | payload_len | CRC32C | payload |
* | u16 | u32 | u32 | payload_len B |
* +-------+-------------+-----------+----------------+
* \____________________/ \______________/
* CRC prefix CRC suffix
* @endcode
*
* Record groups occur in this strict order. SCREEN and HISTORY groups contain
* one entry for each screen declared by TERMINAL. Each manifest is followed
* by the number of PAGE records it declares. Active SCREEN pages make the
* terminal renderable; HISTORY pages are older scrollback ordered newest to
* oldest so an incremental decoder can prepend them as they arrive.
*
* @code{.unparsed}
* +---------------- TERMINAL ----------------+
* | terminal-wide state and screen count |
* +----------------- SCREEN -----------------+ repeated per screen
* | active-screen manifest |
* +------------------ PAGE ------------------+ repeated per manifest
* | active screen rows |
* +------------- CONTINUATION ---------------+
* | unfinished VT/UTF-8 input, or ground |
* +------------------ READY -----------------+
* | BLAKE3-256 of every preceding byte | ready() returns here
* +----------------- HISTORY ----------------+ repeated per screen
* | scrollback manifest |
* +------------------ PAGE ------------------+ next() consumes one page
* | older screen rows |
* +------------------ FINISH ----------------+
* | BLAKE3-256 of every preceding byte | next() returns NO_VALUE
* +------------------------------------------+
* | trailing transport bytes (not consumed) |
* +------------------------------------------+
* @endcode
*
* READY authenticates the renderable prefix through CONTINUATION. FINISH
* authenticates READY and every history record as well as the earlier prefix.
* Thus record CRC32C detects local corruption while the BLAKE3 checkpoints
* also bind the ordering and completeness of the record stream.
*
* Snapshot format version 1 is a work in progress and does not yet carry a
* binary-compatibility guarantee.
*
* @see <a href="https://github.com/ghostty-org/ghostty/blob/main/src/terminal/snapshot/main.zig">Snapshot format and Zig codec documentation</a>
*
* @{
*/
/**
* Configurable snapshot decoder options.
*
* Options may only be changed before decoding starts. Calling
* ghostty_snapshot_decoder_set() after ghostty_snapshot_decoder_ready() or
* ghostty_snapshot_decoder_decode() returns GHOSTTY_INVALID_VALUE.
*/
typedef enum GHOSTTY_ENUM_TYPED {
/**
* Largest non-ground continuation the decoder will accept.
*
* A value of zero accepts only snapshots whose VT parser is in the ground
* state. The decoder default matches the largest built-in APC protocol
* buffer limit, currently 65 MiB.
*
* This is an input validation limit only. It does not configure continuation
* tracking on a terminal returned by the decoder.
*
* Input type: size_t *
*/
GHOSTTY_SNAPSHOT_DECODER_OPT_MAX_CONTINUATION_BYTES = 0,
GHOSTTY_SNAPSHOT_DECODER_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttySnapshotDecoderOption;
/**
* Queryable snapshot decoder data.
*
* Each variant documents the output pointer type expected by
* ghostty_snapshot_decoder_get().
*/
typedef enum GHOSTTY_ENUM_TYPED {
/** Invalid data type. Never results in data extraction. */
GHOSTTY_SNAPSHOT_DECODER_DATA_INVALID = 0,
/**
* Current maximum accepted continuation size.
*
* This value is available in every non-failed decoder state.
*
* Output type: size_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_MAX_CONTINUATION_BYTES = 1,
/**
* Number of snapshot source bytes consumed so far.
*
* At FINISH this identifies the first byte after the snapshot. Trailing
* bytes are not consumed. This value is unavailable after a decoding error,
* because the decoder can no longer guarantee its source position.
*
* Output type: size_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_SOURCE_OFFSET = 2,
/**
* Advisory complete logical history extent for the primary screen.
*
* The value counts rows before the active area, including any resident
* overlap carried before READY. It becomes available after READY validates.
*
* Output type: uint64_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_HISTORY_ROWS_PRIMARY = 3,
/**
* Advisory complete logical history extent for the alternate screen.
*
* The value has the same semantics and lifetime as
* GHOSTTY_SNAPSHOT_DECODER_DATA_HISTORY_ROWS_PRIMARY. Querying it returns
* GHOSTTY_NO_VALUE when the snapshot does not declare an alternate screen.
*
* Output type: uint64_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_HISTORY_ROWS_ALTERNATE = 4,
/**
* Screen associated with the most recently decoded history page.
*
* This value is available only after ghostty_snapshot_decoder_next()
* returns GHOSTTY_SUCCESS. A later call to next replaces it or clears it
* when FINISH is reached or an error occurs.
*
* Output type: GhosttyTerminalScreen *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_SCREEN = 5,
/**
* Rows prepended by the most recently decoded history page.
*
* Zero means the page was consumed and authenticated but could not be
* applied to the live terminal.
*
* Output type: size_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_ROWS = 6,
/**
* Page records remaining in the same screen's HISTORY sequence.
*
* This is not a count of all pages remaining in the snapshot.
*
* Output type: uint32_t *
*/
GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_REMAINING = 7,
GHOSTTY_SNAPSHOT_DECODER_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttySnapshotDecoderData;
/**
* Encode a complete terminal snapshot to a writer.
*
* The terminal's persistent VT stream supplies the continuation bytes needed
* to reconstruct unfinished parser state. The caller must prevent concurrent
* writes or other terminal mutation for the duration of this call. The writer
* callback must not call terminal APIs with the same terminal handle.
* A terminal can be encoded with tracking disabled when its VT parser and
* UTF-8 decoder are both at ground. If either is unfinished, tracking must
* have been enabled before the input that produced that state was written;
* otherwise this returns GHOSTTY_INVALID_VALUE.
*
* Encoding begins at the writer's current position. If an error occurs, the
* writer may contain a partial snapshot without a valid FINISH checkpoint.
* Calls to the writer are synchronous; this function does not flush or make
* the caller's destination durable.
*
* @param terminal Terminal to encode (must not be NULL)
* @param writer Destination writer whose write callback must not be NULL
* @return GHOSTTY_SUCCESS on success, GHOSTTY_IO_ERROR if the writer rejects
* output, GHOSTTY_LIMIT_EXCEEDED if output accounting overflows, or
* another error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_encode(GhosttyTerminal terminal,
GhosttyWriter writer);
/**
* Encode a complete terminal snapshot to a caller-provided buffer.
*
* Pass NULL for buf with buf_len zero to query the required size. If the
* buffer is too small, this returns GHOSTTY_OUT_OF_SPACE and stores the
* required capacity in out_written. A non-NULL undersized buffer may contain
* a partial snapshot prefix. On success, out_written receives the number of
* bytes encoded.
*
* A terminal can be encoded with tracking disabled when its VT parser and
* UTF-8 decoder are both at ground. If either is unfinished, tracking must
* have been enabled before the input that produced that state was written;
* otherwise this returns GHOSTTY_INVALID_VALUE.
*
* @param terminal Terminal to encode (must not be NULL)
* @param buf Destination buffer, or NULL when buf_len is zero
* @param buf_len Destination buffer capacity in bytes
* @param[out] out_written Bytes written, or required capacity on
* GHOSTTY_OUT_OF_SPACE (must not be NULL)
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_encode_buf(
GhosttyTerminal terminal,
uint8_t* buf,
size_t buf_len,
size_t* out_written);
/**
* Encode a complete terminal snapshot to an allocated buffer.
*
* The returned buffer is allocated with allocator, or the default allocator
* when allocator is NULL. The caller must release it with ghostty_free(),
* passing the same allocator used here.
*
* A terminal can be encoded with tracking disabled when its VT parser and
* UTF-8 decoder are both at ground. If either is unfinished, tracking must
* have been enabled before the input that produced that state was written;
* otherwise this returns GHOSTTY_INVALID_VALUE.
*
* @param terminal Terminal to encode (must not be NULL)
* @param allocator Allocator for the output, or NULL for the default allocator
* @param[out] out_ptr Allocated snapshot bytes (must not be NULL)
* @param[out] out_len Number of allocated snapshot bytes (must not be NULL)
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_encode_alloc(
GhosttyTerminal terminal,
const GhosttyAllocator* allocator,
uint8_t** out_ptr,
size_t* out_len);
/**
* Create a snapshot decoder that reads from a caller-provided reader.
*
* The decoder stores a copy of reader. Its read callback must not be NULL, and
* both the callback and its caller-owned context must remain valid until
* FINISH is reached or the decoder is freed. Reads are synchronous and occur
* only during ready, next, or decode calls. A zero-byte successful read is
* permanent end-of-file, not temporary starvation; nonblocking sources must
* wait outside the decoder or block in their callback. The read callback must
* not call APIs, including ghostty_snapshot_decoder_free(), on the decoder
* that owns it. Returning false reports GHOSTTY_IO_ERROR; returning true with
* zero bytes before a required checkpoint reports truncated snapshot data as
* GHOSTTY_INVALID_VALUE.
*
* @param allocator Allocator for decoder and decoded terminal state, or NULL
* for the default allocator
* @param decoder Pointer to receive the decoder handle (must not be NULL)
* @param reader Snapshot source reader
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_new(
const GhosttyAllocator* allocator,
GhosttySnapshotDecoder* decoder,
GhosttyReader reader);
/**
* Create a snapshot decoder over a borrowed byte buffer.
*
* The bytes are not copied. ptr must remain valid and immutable until FINISH
* is reached or the decoder is freed. Bytes after FINISH are not consumed;
* query GHOSTTY_SNAPSHOT_DECODER_DATA_SOURCE_OFFSET to locate them.
*
* @param allocator Allocator for decoder and decoded terminal state, or NULL
* for the default allocator
* @param decoder Pointer to receive the decoder handle (must not be NULL)
* @param ptr Snapshot source bytes
* @param len Number of source bytes
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_new_buf(
const GhosttyAllocator* allocator,
GhosttySnapshotDecoder* decoder,
const uint8_t* ptr,
size_t len);
/**
* Free a snapshot decoder.
*
* This does not release the caller's ownership of a terminal returned by
* ready or decode. Abandoning an incremental decode leaves that terminal
* usable with whatever history had already been restored.
*
* @param decoder Decoder to free (may be NULL)
*
* @ingroup snapshot
*/
GHOSTTY_API void ghostty_snapshot_decoder_free(GhosttySnapshotDecoder decoder);
/**
* Set a snapshot decoder option.
*
* The value pointer must have the type documented by option. Options may only
* be changed before decoding starts.
*
* @param decoder Decoder handle (must not be NULL)
* @param option Option to change
* @param value Pointer to the option value (must not be NULL)
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if decoding has
* started or an argument is invalid, or another error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_set(
GhosttySnapshotDecoder decoder,
GhosttySnapshotDecoderOption option,
const void* value);
/**
* Decode and authenticate the renderable snapshot prefix through READY.
*
* On success, terminal receives a caller-owned terminal with its persistent
* VT stream already restored from the snapshot continuation. The terminal is
* immediately usable for rendering and live input. Older scrollback remains
* to be restored with ghostty_snapshot_decoder_next().
*
* The restored parser state may be unfinished, but terminal continuation
* tracking is disabled; GHOSTTY_TERMINAL_DATA_CONTINUATION_MAX_BYTES returns
* zero. The decoder's continuation option is an input limit, not terminal
* runtime policy.
*
* The caller must keep the returned terminal alive until FINISH validates or
* the decoder is freed. The decoder borrows this terminal handle while it
* restores history; ghostty_snapshot_decoder_next() uses it automatically.
*
* This operation may only be called once and only before decoding starts.
* terminal is set to NULL on every error. A decoding, I/O, or allocation
* error after input consumption begins poisons the decoder, after which it
* must be freed. An invalid argument or lifecycle error detected before the
* operation consumes input does not poison it.
*
* @param decoder Decoder handle (must not be NULL)
* @param[out] terminal Pointer to receive the terminal (must not be NULL)
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_ready(
GhosttySnapshotDecoder decoder,
GhosttyTerminal* terminal);
/**
* Decode one history page into the terminal returned by READY.
*
* Each GHOSTTY_SUCCESS consumes and authenticates one PAGE record. Query the
* GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_* values before calling next again.
* GHOSTTY_NO_VALUE means FINISH was validated; repeated calls after FINISH
* also return GHOSTTY_NO_VALUE.
*
* The terminal may be rendered, resized, and fed live PTY input between calls.
* If a history page can no longer be applied safely, it is still consumed and
* authenticated and progress reports zero rows. The decoder applies history
* to the caller-owned terminal produced by its READY operation.
*
* A decoding error invalidates the decoder's source position. The terminal
* remains caller-owned and usable with its already-restored history, but only
* ghostty_snapshot_decoder_free() may subsequently be called on the decoder.
*
* @param decoder Decoder handle (must not be NULL)
* @return GHOSTTY_SUCCESS for one page, GHOSTTY_NO_VALUE after FINISH, or an
* error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_next(
GhosttySnapshotDecoder decoder);
/**
* Decode and authenticate one complete snapshot.
*
* This is the one-shot form of READY followed by all history pages through
* FINISH. It may only be called before decoding starts. Bytes following FINISH
* are left unread. On success terminal receives a caller-owned terminal with
* its persistent VT stream restored. Continuation tracking on the returned
* terminal is disabled and GHOSTTY_TERMINAL_DATA_CONTINUATION_MAX_BYTES
* returns zero. terminal is set to NULL on every error.
* A decoding, I/O, or allocation error after input consumption begins poisons
* the decoder, after which it must be freed. An invalid argument or
* lifecycle error detected before the operation consumes input does not
* poison it.
*
* @param decoder Decoder handle (must not be NULL)
* @param[out] terminal Pointer to receive the terminal (must not be NULL)
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_decode(
GhosttySnapshotDecoder decoder,
GhosttyTerminal* terminal);
/**
* Get typed data from a snapshot decoder.
*
* The output pointer must have the type documented by data. A phase-dependent
* value that is not currently available returns GHOSTTY_NO_VALUE.
*
* @param decoder Decoder handle (must not be NULL)
* @param data Data kind to query
* @param[out] out Pointer to receive the value (must not be NULL)
* @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the requested data
* is unavailable, or another error code on failure
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_get(
GhosttySnapshotDecoder decoder,
GhosttySnapshotDecoderData data,
void* out);
/**
* Get multiple snapshot decoder data fields in a single call.
*
* Each keys element selects a data kind and the corresponding values element
* points to storage of the documented output type. Processing stops at the
* first error. On success out_written is set to count; on error it is set to
* the number of values written before the failing key. Invalid array arguments
* report zero values written.
*
* @param decoder Decoder handle (must not be NULL)
* @param count Number of key/value pairs
* @param keys Array of data kinds to query
* @param values Array of output pointers corresponding to keys
* @param[out] out_written Number of successfully written values (may be NULL)
* @return GHOSTTY_SUCCESS if every query succeeds, or the first error
*
* @ingroup snapshot
*/
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_get_multi(
GhosttySnapshotDecoder decoder,
size_t count,
const GhosttySnapshotDecoderData* keys,
void** values,
size_t* out_written);
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* GHOSTTY_VT_SNAPSHOT_H */

View File

@@ -16,6 +16,7 @@
#include <ghostty/vt/modes.h>
#include <ghostty/vt/size_report.h>
#include <ghostty/vt/grid_ref.h>
#include <ghostty/vt/io.h>
#include <ghostty/vt/kitty_graphics.h>
#include <ghostty/vt/screen.h>
#include <ghostty/vt/point.h>
@@ -1027,6 +1028,26 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Input type: GhosttyTerminalProgressReportFn
*/
GHOSTTY_TERMINAL_OPT_PROGRESS_REPORT = 30,
/**
* Set the maximum number of replay-safe VT continuation bytes retained.
*
* Continuation bytes reconstruct an escape sequence or UTF-8 codepoint
* which was unfinished at the end of the most recent
* ghostty_terminal_vt_write() call. They are used automatically by terminal
* snapshots and may also be exported directly with the continuation APIs.
*
* Tracking is disabled by default. A nonzero value enables tracking and
* sets its byte limit. Passing NULL or a pointer to zero disables tracking.
* Lowering the limit below an already-retained
* continuation, or enabling tracking while the parser is already
* unfinished, makes the current continuation unavailable because earlier
* bytes cannot be reconstructed. Tracking recovers automatically after a
* later write reaches the ground state or contains a fresh replay start.
*
* Input type: size_t*
*/
GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES = 31,
GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyTerminalOption;
@@ -1376,6 +1397,17 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Output type: size_t *
*/
GHOSTTY_TERMINAL_DATA_SCROLLBACK_MAX_LINES = 35,
/**
* The configured maximum retained VT continuation size in bytes.
*
* A value of zero means continuation tracking is disabled. This reports the
* configured limit even when a current unfinished continuation is
* temporarily unavailable.
*
* Output type: size_t *
*/
GHOSTTY_TERMINAL_DATA_CONTINUATION_MAX_BYTES = 36,
GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyTerminalData;
@@ -1501,6 +1533,97 @@ GHOSTTY_API void ghostty_terminal_vt_write(GhosttyTerminal terminal,
const uint8_t* data,
size_t len);
/**
* Write the terminal's replay-safe VT continuation to a callback writer.
*
* The continuation is the exact byte suffix needed to reconstruct unfinished
* VT parser or UTF-8 decoder state in an equivalent terminal. It is empty
* when the stream is at ground. The callback is invoked synchronously and
* may be called more than once. It must not call terminal APIs with the same
* terminal handle.
*
* Continuation tracking must have been enabled by setting
* GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES to a nonzero value before the
* input that produced the continuation was written.
*
* The caller must serialize this operation with ghostty_terminal_vt_write()
* and all other access to the same terminal.
*
* @param terminal Terminal to read from (must not be NULL)
* @param writer Destination writer whose write callback must not be NULL
* @return GHOSTTY_SUCCESS on success, GHOSTTY_IO_ERROR if the callback rejects
* a write, GHOSTTY_LIMIT_EXCEEDED if output accounting overflows, or
* GHOSTTY_INVALID_VALUE if an argument is invalid, tracking is
* disabled, or the current continuation is unavailable
*
* @ingroup terminal
*/
GHOSTTY_API GhosttyResult ghostty_terminal_continuation_write(
GhosttyTerminal terminal,
GhosttyWriter writer);
/**
* Copy the terminal's replay-safe VT continuation into a caller buffer.
*
* Pass NULL for buf with buf_len zero to query the required size. A size query
* returns GHOSTTY_OUT_OF_SPACE and stores the required size in out_written,
* including zero when the stream is at ground. If a non-NULL buffer is too
* small, the function has the same result and reports the full required size.
* Continuation tracking must have been enabled by setting
* GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES to a nonzero value before the
* input that produced the continuation was written.
*
* The caller must serialize this operation with all other access to the same
* terminal.
*
* @param terminal Terminal to read from (must not be NULL)
* @param buf Destination buffer, or NULL when buf_len is zero
* @param buf_len Destination buffer capacity in bytes
* @param[out] out_written Bytes written, or required size on
* GHOSTTY_OUT_OF_SPACE (must not be NULL)
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE for a size query or
* insufficient buffer, or GHOSTTY_INVALID_VALUE if an argument is
* invalid, tracking is disabled, or the current continuation is
* unavailable
*
* @ingroup terminal
*/
GHOSTTY_API GhosttyResult ghostty_terminal_continuation_buf(
GhosttyTerminal terminal,
uint8_t* buf,
size_t buf_len,
size_t* out_written);
/**
* Return an allocated copy of the terminal's replay-safe VT continuation.
*
* The returned bytes are allocated with allocator, or the default allocator
* when allocator is NULL. The caller must release them with ghostty_free(),
* passing the same allocator and returned length. An empty continuation is a
* successful zero-length allocation.
* Continuation tracking must have been enabled by setting
* GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES to a nonzero value before the
* input that produced the continuation was written.
*
* The caller must serialize this operation with all other access to the same
* terminal.
*
* @param terminal Terminal to read from (must not be NULL)
* @param allocator Allocator for the output, or NULL for the default allocator
* @param[out] out_ptr Allocated continuation bytes (must not be NULL)
* @param[out] out_len Number of continuation bytes (must not be NULL)
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
* failure, or GHOSTTY_INVALID_VALUE if an argument is invalid,
* tracking is disabled, or the current continuation is unavailable
*
* @ingroup terminal
*/
GHOSTTY_API GhosttyResult ghostty_terminal_continuation_alloc(
GhosttyTerminal terminal,
const GhosttyAllocator* allocator,
uint8_t** out_ptr,
size_t* out_len);
/**
* Scroll the terminal viewport.
*

View File

@@ -82,6 +82,10 @@ typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_OUT_OF_SPACE = -3,
/** The requested value has no value */
GHOSTTY_NO_VALUE = -4,
/** Operation failed while reading from or writing to external I/O */
GHOSTTY_IO_ERROR = -5,
/** Operation failed because encoded input exceeded a configured limit */
GHOSTTY_LIMIT_EXCEEDED = -6,
GHOSTTY_RESULT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyResult;
@@ -94,6 +98,13 @@ typedef enum GHOSTTY_ENUM_TYPED {
*/
typedef struct GhosttyTerminalImpl* GhosttyTerminal;
/**
* Opaque handle to an incremental terminal snapshot decoder.
*
* @ingroup snapshot
*/
typedef struct GhosttySnapshotDecoderImpl* GhosttySnapshotDecoder;
/**
* Opaque handle to a tracked grid reference.
*