terminal: expose compression through libghostty-vt

Scrollback compression scheduling was only available to Zig callers that
used Terminal directly, leaving C embedders unable to drive the same idle
compression policy.

Define ABI-aware mode and result enums on Terminal and export activity
and compression operations through the C API. Keep scheduling
caller-owned, validate C inputs, and document the incremental contract
with a complete example.

Report unsupported reclamation consistently for full passes so callers
can disable compression on targets that cannot retain decommitted
mappings.
This commit is contained in:
Mitchell Hashimoto
2026-07-09 10:11:07 -07:00
parent 6d5dda40db
commit 172f15da3b
11 changed files with 415 additions and 10 deletions

View File

@@ -0,0 +1,17 @@
# Example: Scrollback Compression in C
This example shows how a libghostty-vt embedding application can track
compression-relevant terminal activity and perform incremental scrollback
compression after its own idle delay.
libghostty-vt does not create a timer or background thread. The embedding
application remains responsible for scheduling compression and serializing it
with other access to the terminal.
## Usage
Run the example:
```shell-session
zig build run
```

View File

@@ -0,0 +1,32 @@
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"},
});
if (b.lazyDependency("ghostty", .{})) |dep| {
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
}
const exe = b.addExecutable(.{
.name = "c_vt_compression",
.root_module = exe_mod,
});
b.installArtifact(exe);
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);
}

View File

@@ -0,0 +1,14 @@
.{
.name = .c_vt_compression,
.version = "0.0.0",
.fingerprint = 0x3d527aa68a4cf8d,
.minimum_zig_version = "0.15.1",
.dependencies = .{
.ghostty = .{ .path = "../../" },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View File

@@ -0,0 +1,72 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ghostty/vt.h>
//! [compression-idle-step]
// Perform one step after the application's idle timer fires. Returning true
// asks the application to schedule another step while the terminal is idle.
static bool compression_idle_step(GhosttyTerminal terminal) {
GhosttyTerminalCompressionResult compression_result;
GhosttyResult result = ghostty_terminal_compress(
terminal,
GHOSTTY_TERMINAL_COMPRESSION_MODE_INCREMENTAL,
&compression_result);
assert(result == GHOSTTY_SUCCESS);
switch (compression_result) {
case GHOSTTY_TERMINAL_COMPRESSION_RESULT_PENDING:
return true;
case GHOSTTY_TERMINAL_COMPRESSION_RESULT_COMPLETE:
case GHOSTTY_TERMINAL_COMPRESSION_RESULT_UNSUPPORTED:
return false;
default:
assert(false);
return false;
}
}
//! [compression-idle-step]
int main(void) {
GhosttyTerminal terminal;
GhosttyTerminalOptions opts = {
.cols = 80,
.rows = 24,
.max_scrollback = 10 * 1024 * 1024,
};
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
assert(result == GHOSTTY_SUCCESS);
//! [compression-activity]
uint64_t compression_activity;
result = ghostty_terminal_compression_activity(
terminal,
&compression_activity);
assert(result == GHOSTTY_SUCCESS);
// Terminal mutations may change the token. When it changes, restart the
// application's idle timer rather than compressing on the output path.
const char *line = "repeated and compressible terminal history\r\n";
for (size_t i = 0; i < 4000; i++) {
ghostty_terminal_vt_write(
terminal,
(const uint8_t *)line,
strlen(line));
}
uint64_t new_activity;
result = ghostty_terminal_compression_activity(terminal, &new_activity);
assert(result == GHOSTTY_SUCCESS);
if (new_activity != compression_activity) {
compression_activity = new_activity;
// Restart the application's compression idle timer here.
}
//! [compression-activity]
// Simulate the idle timer and its short pending-work continuations.
while (compression_idle_step(terminal)) {}
ghostty_terminal_free(terminal);
return 0;
}