Encode a terminal's full state and restore it three ways: in one shot, with scrollback kept compressed, and page by page. The page by page path gives you a terminal you can draw before the old history arrives.
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef struct {
const uint8_t *data;
size_t len;
size_t offset;
} BufferReader;
static bool buffer_read(void *userdata,
uint8_t *buffer,
size_t capacity,
size_t *out_read) {
BufferReader *reader = userdata;
size_t remaining = reader->len - reader->offset;
size_t count = remaining < capacity ? remaining : capacity;
if (count > 64) count = 64;
memcpy(buffer, reader->data + reader->offset, count);
reader->offset += count;
*out_read = count;
return true;
}
int main(void) {
const size_t continuation_limit = 1024;
source,
&continuation_limit);
size_t unlimited = SIZE_MAX;
const char *line = "snapshot history line\r\n";
for (size_t i = 0; i < 1000; i++) {
source, (const uint8_t *)line, strlen(line));
}
const char *unfinished = "\x1b[31";
source, (const uint8_t *)unfinished, strlen(unfinished));
uint8_t *snapshot = NULL;
size_t snapshot_len = 0;
source, NULL, &snapshot, &snapshot_len);
printf("encoded %zu snapshot bytes\n", snapshot_len);
NULL, &full_decoder, snapshot, snapshot_len);
NULL, &compressed_decoder, snapshot, snapshot_len);
const bool compress_history = true;
compressed_decoder,
&compress_history);
compressed_decoder, &compressed_terminal);
BufferReader reader_state = {
.data = snapshot,
.len = snapshot_len,
.offset = 0,
};
.read = buffer_read,
.userdata = &reader_state,
};
NULL, &incremental_decoder, reader);
incremental_decoder, &incremental_terminal);
uint64_t history_rows = 0;
incremental_decoder,
&history_rows);
printf("snapshot advertises %llu primary history rows\n",
(unsigned long long)history_rows);
size_t page_count = 0;
incremental_decoder,
&kind);
uint32_t image_id = 0;
incremental_decoder,
&image_id);
printf("restored the pixels of image %u\n", image_id);
continue;
}
size_t rows = 0;
uint32_t remaining = 0;
};
void *values[] = {&screen, &rows, &remaining};
size_t written = 0;
incremental_decoder,
sizeof(keys) / sizeof(keys[0]),
keys,
values,
&written);
assert(written == sizeof(keys) / sizeof(keys[0]));
printf("restored %zu rows to screen %d (%u pages remain)\n",
rows, (int)screen, remaining);
page_count++;
}
assert(page_count > 0);
return 0;
}
GHOSTTY_API void ghostty_free(const GhosttyAllocator *allocator, uint8_t *ptr, size_t len)
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_new_buf(const GhosttyAllocator *allocator, GhosttySnapshotDecoder *decoder, const uint8_t *ptr, size_t len)
GHOSTTY_API void ghostty_snapshot_decoder_free(GhosttySnapshotDecoder decoder)
GHOSTTY_API GhosttyResult ghostty_snapshot_encode_alloc(GhosttyTerminal terminal, const GhosttyAllocator *allocator, uint8_t **out_ptr, size_t *out_len)
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_get_multi(GhosttySnapshotDecoder decoder, size_t count, const GhosttySnapshotDecoderData *keys, void **values, size_t *out_written)
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_decode(GhosttySnapshotDecoder decoder, GhosttyTerminal *terminal)
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_set(GhosttySnapshotDecoder decoder, GhosttySnapshotDecoderOption option, const void *value)
GhosttySnapshotDecoderProgressKind
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_next(GhosttySnapshotDecoder decoder)
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_get(GhosttySnapshotDecoder decoder, GhosttySnapshotDecoderData data, void *out)
struct GhosttySnapshotDecoderImpl * GhosttySnapshotDecoder
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_new(const GhosttyAllocator *allocator, GhosttySnapshotDecoder *decoder, GhosttyReader reader)
GhosttySnapshotDecoderData
GHOSTTY_API GhosttyResult ghostty_snapshot_decoder_ready(GhosttySnapshotDecoder decoder, GhosttyTerminal *terminal)
@ GHOSTTY_SNAPSHOT_DECODER_OPT_COMPRESS_HISTORY
@ GHOSTTY_SNAPSHOT_DECODER_PROGRESS_KIND_IMAGE
@ GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_IMAGE_ID
@ GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_ROWS
@ GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_KIND
@ GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_SCREEN
@ GHOSTTY_SNAPSHOT_DECODER_DATA_HISTORY_ROWS_PRIMARY
@ GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_REMAINING
GHOSTTY_API GhosttyResult ghostty_terminal_set(GhosttyTerminal terminal, GhosttyTerminalOption option, const void *value)
struct GhosttyTerminalImpl * GhosttyTerminal
GHOSTTY_API void ghostty_terminal_free(GhosttyTerminal terminal)
GHOSTTY_API void ghostty_terminal_vt_write(GhosttyTerminal terminal, const uint8_t *data, size_t len)
GHOSTTY_API GhosttyResult ghostty_terminal_new(const GhosttyAllocator *allocator, GhosttyTerminal *terminal, uint16_t cols, uint16_t rows)
@ GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES
@ GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES