mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-10 03:09:42 +00:00
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:
17
example/c-vt-compression/README.md
Normal file
17
example/c-vt-compression/README.md
Normal 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
|
||||
```
|
||||
32
example/c-vt-compression/build.zig
Normal file
32
example/c-vt-compression/build.zig
Normal 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);
|
||||
}
|
||||
14
example/c-vt-compression/build.zig.zon
Normal file
14
example/c-vt-compression/build.zig.zon
Normal 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",
|
||||
},
|
||||
}
|
||||
72
example/c-vt-compression/src/main.c
Normal file
72
example/c-vt-compression/src/main.c
Normal 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;
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
* - @ref c-vt-formatter/src/main.c - Terminal formatter example
|
||||
* - @ref c-vt-grid-traverse/src/main.c - Grid traversal example using grid refs
|
||||
* - @ref c-vt-grid-ref-tracked/src/main.c - Tracked grid ref example
|
||||
* - @ref c-vt-compression/src/main.c - Idle scrollback compression example
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -105,6 +106,11 @@
|
||||
* detect when it loses its value, and move it to a new point.
|
||||
*/
|
||||
|
||||
/** @example c-vt-compression/src/main.c
|
||||
* This example demonstrates how to schedule incremental scrollback compression
|
||||
* after compression-relevant terminal activity becomes idle.
|
||||
*/
|
||||
|
||||
/** @example c-vt-selection-gesture/src/main.c
|
||||
* This example demonstrates how to use synthetic selection gesture events to
|
||||
* derive drag and deep-press selection snapshots.
|
||||
|
||||
@@ -40,6 +40,17 @@ extern "C" {
|
||||
* @snippet c-vt-stream/src/main.c vt-stream-init
|
||||
* @snippet c-vt-stream/src/main.c vt-stream-write
|
||||
*
|
||||
* ## Scrollback Compression
|
||||
*
|
||||
* Scrollback compression is caller-driven. The terminal exposes an opaque
|
||||
* activity token so an embedding application can restart an idle timer only
|
||||
* when compression-relevant state changes. Once idle, call incremental
|
||||
* compression until it no longer reports pending work. libghostty-vt does not
|
||||
* create a timer or background thread.
|
||||
*
|
||||
* @snippet c-vt-compression/src/main.c compression-activity
|
||||
* @snippet c-vt-compression/src/main.c compression-idle-step
|
||||
*
|
||||
* ## Effects
|
||||
*
|
||||
* By default, the terminal sequence processing with ghostty_terminal_vt_write()
|
||||
@@ -177,6 +188,37 @@ typedef struct {
|
||||
// future options.
|
||||
} GhosttyTerminalOptions;
|
||||
|
||||
/**
|
||||
* Amount of compression work to perform before returning.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Perform one bounded compression step suitable for idle scheduling. */
|
||||
GHOSTTY_TERMINAL_COMPRESSION_MODE_INCREMENTAL = 0,
|
||||
|
||||
/** Synchronously inspect every currently eligible page. */
|
||||
GHOSTTY_TERMINAL_COMPRESSION_MODE_FULL = 1,
|
||||
GHOSTTY_TERMINAL_COMPRESSION_MODE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalCompressionMode;
|
||||
|
||||
/**
|
||||
* Scheduling result from terminal compression.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Retained-mapping reclamation is unavailable on this target. */
|
||||
GHOSTTY_TERMINAL_COMPRESSION_RESULT_UNSUPPORTED = 0,
|
||||
|
||||
/** More incremental compression work remains. */
|
||||
GHOSTTY_TERMINAL_COMPRESSION_RESULT_PENDING = 1,
|
||||
|
||||
/** The pass has no continuation to schedule. */
|
||||
GHOSTTY_TERMINAL_COMPRESSION_RESULT_COMPLETE = 2,
|
||||
GHOSTTY_TERMINAL_COMPRESSION_RESULT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalCompressionResult;
|
||||
|
||||
/**
|
||||
* Scroll viewport behavior tag.
|
||||
*
|
||||
@@ -1158,6 +1200,60 @@ GHOSTTY_API void ghostty_terminal_vt_write(GhosttyTerminal terminal,
|
||||
GHOSTTY_API void ghostty_terminal_scroll_viewport(GhosttyTerminal terminal,
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
|
||||
/**
|
||||
* Return the current compression activity token.
|
||||
*
|
||||
* The token is opaque and only equality comparisons are meaningful. An
|
||||
* embedding application should cache it and restart its compression idle
|
||||
* delay whenever the value changes. The value may wrap and changes in either
|
||||
* direction have the same meaning.
|
||||
*
|
||||
* This function only observes terminal state. It does not perform or schedule
|
||||
* compression.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param[out] out_activity Receives the current activity token
|
||||
* @return GHOSTTY_SUCCESS on success, or GHOSTTY_INVALID_VALUE if an argument
|
||||
* is NULL
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_compression_activity(
|
||||
GhosttyTerminal terminal,
|
||||
uint64_t* out_activity);
|
||||
|
||||
/**
|
||||
* Compress eligible terminal scrollback.
|
||||
*
|
||||
* Incremental mode performs bounded work suitable for an idle callback. A
|
||||
* pending result means the application should invoke another step while the
|
||||
* terminal remains idle. A complete result means no continuation is needed
|
||||
* until ghostty_terminal_compression_activity() changes. Full mode performs
|
||||
* one synchronous scan and can stall on large scrollback buffers.
|
||||
*
|
||||
* Compression is opportunistic. Complete means the pass has finished, not
|
||||
* that every page was compressed: pages may be unprofitable or encounter an
|
||||
* allocation or reclamation failure. Compression changes only the terminal's
|
||||
* storage representation and never its logical contents or scrollback limit.
|
||||
* Accessing compressed history restores it transparently.
|
||||
*
|
||||
* This function is not thread-safe with other operations on the same
|
||||
* terminal. The caller must serialize it with writes, rendering, searches,
|
||||
* and other terminal access.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param mode The amount of compression work to perform
|
||||
* @param[out] out_result Receives the compression scheduling result
|
||||
* @return GHOSTTY_SUCCESS on success, or GHOSTTY_INVALID_VALUE if an argument
|
||||
* or mode is invalid
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_compress(
|
||||
GhosttyTerminal terminal,
|
||||
GhosttyTerminalCompressionMode mode,
|
||||
GhosttyTerminalCompressionResult* out_result);
|
||||
|
||||
/**
|
||||
* Get the current value of a terminal mode.
|
||||
*
|
||||
|
||||
@@ -261,6 +261,8 @@ comptime {
|
||||
@export(&c.terminal_set, .{ .name = "ghostty_terminal_set" });
|
||||
@export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" });
|
||||
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
|
||||
@export(&c.terminal_compression_activity, .{ .name = "ghostty_terminal_compression_activity" });
|
||||
@export(&c.terminal_compress, .{ .name = "ghostty_terminal_compress" });
|
||||
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
|
||||
@@ -4005,9 +4005,9 @@ const compressPage_tw = tripwire.module(
|
||||
/// state.
|
||||
///
|
||||
/// PageList tracks mutations which require a later incremental pass in its
|
||||
/// compression state. Full compression always returns `complete`, indicating
|
||||
/// that it has no continuation to schedule rather than that every page was
|
||||
/// compressed.
|
||||
/// compression state. On supported targets, full compression returns
|
||||
/// `complete`, indicating that it has no continuation to schedule rather than
|
||||
/// that every page was compressed.
|
||||
pub fn compress(
|
||||
self: *PageList,
|
||||
mode: enum { incremental, drain, full },
|
||||
@@ -4020,6 +4020,13 @@ pub fn compress(
|
||||
.unsupported => break .unsupported,
|
||||
},
|
||||
.full => full: {
|
||||
// Match incremental mode's unsupported result. Full compression
|
||||
// has no useful work to perform without strict reclamation.
|
||||
if (!terminal_mem.canReclaim(.strict)) {
|
||||
self.page_compression.reset();
|
||||
break :full .unsupported;
|
||||
}
|
||||
|
||||
self.compressFull();
|
||||
|
||||
// Full compression has no continuation. Discard any partial
|
||||
@@ -4107,10 +4114,6 @@ fn compressIncremental(self: *PageList) IncrementalCompressionResult {
|
||||
|
||||
/// Compress every fully historical resident page which is currently cold.
|
||||
fn compressFull(self: *PageList) void {
|
||||
// Avoid the codec, scratch allocation, and exact encoded allocation on a
|
||||
// target where strict retained-mapping reclamation cannot succeed.
|
||||
if (!terminal_mem.canReclaim(.strict)) return;
|
||||
|
||||
var it: CompressionIterator = .init(self);
|
||||
while (it.next()) |node| {
|
||||
|
||||
|
||||
@@ -2314,6 +2314,25 @@ pub fn compressionActivity(self: *const Terminal) u64 {
|
||||
return @as(u64, state.activity_serial);
|
||||
}
|
||||
|
||||
/// The amount of compression work performed by `compress` before returning.
|
||||
///
|
||||
/// The declaration order is part of the libghostty-vt C ABI. Removed values
|
||||
/// must leave a `null` hole so later values retain their integer values.
|
||||
pub const CompressionMode = lib.Enum(lib.target, &.{
|
||||
"incremental",
|
||||
"full",
|
||||
});
|
||||
|
||||
/// The scheduling result of a `compress` call.
|
||||
///
|
||||
/// The declaration order is part of the libghostty-vt C ABI. Removed values
|
||||
/// must leave a `null` hole so later values retain their integer values.
|
||||
pub const CompressionResult = lib.Enum(lib.target, &.{
|
||||
"unsupported",
|
||||
"pending",
|
||||
"complete",
|
||||
});
|
||||
|
||||
/// Compress cold memory to save resident memory space.
|
||||
///
|
||||
/// Full compression does a full pass compressing everything it can before
|
||||
@@ -2330,13 +2349,19 @@ pub fn compressionActivity(self: *const Terminal) u64 {
|
||||
/// experience, for example during idle times.
|
||||
pub fn compress(
|
||||
self: *Terminal,
|
||||
mode: enum { incremental, full },
|
||||
) PageList.IncrementalCompressionResult {
|
||||
mode: CompressionMode,
|
||||
) CompressionResult {
|
||||
const pages = &self.screens.get(.primary).?.pages;
|
||||
return switch (mode) {
|
||||
const result = switch (mode) {
|
||||
.incremental => pages.compress(.incremental),
|
||||
.full => pages.compress(.full),
|
||||
};
|
||||
|
||||
return switch (result) {
|
||||
.unsupported => .unsupported,
|
||||
.pending => .pending,
|
||||
.complete => .complete,
|
||||
};
|
||||
}
|
||||
|
||||
/// To be called before shifting a row (as in insertLines and deleteLines)
|
||||
|
||||
@@ -183,6 +183,8 @@ pub const terminal_resize = terminal.resize;
|
||||
pub const terminal_set = terminal.set;
|
||||
pub const terminal_vt_write = terminal.vt_write;
|
||||
pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
pub const terminal_compression_activity = terminal.compression_activity;
|
||||
pub const terminal_compress = terminal.compress;
|
||||
pub const terminal_mode_get = terminal.mode_get;
|
||||
pub const terminal_mode_set = terminal.mode_set;
|
||||
pub const terminal_get = terminal.get;
|
||||
|
||||
@@ -223,6 +223,12 @@ const Effects = struct {
|
||||
/// C: GhosttyTerminal
|
||||
pub const Terminal = ?*TerminalWrapper;
|
||||
|
||||
/// C: GhosttyTerminalCompressionMode
|
||||
pub const CompressionMode = ZigTerminal.CompressionMode;
|
||||
|
||||
/// C: GhosttyTerminalCompressionResult
|
||||
pub const CompressionResult = ZigTerminal.CompressionResult;
|
||||
|
||||
pub fn zigTerminal(terminal_: Terminal) ?*ZigTerminal {
|
||||
return (terminal_ orelse return null).terminal;
|
||||
}
|
||||
@@ -315,6 +321,30 @@ pub fn vt_write(
|
||||
wrapper.stream.nextSlice(ptr[0..len]);
|
||||
}
|
||||
|
||||
pub fn compression_activity(
|
||||
terminal_: Terminal,
|
||||
out_activity_: ?*u64,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const out_activity = out_activity_ orelse return .invalid_value;
|
||||
out_activity.* = t.compressionActivity();
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn compress(
|
||||
terminal_: Terminal,
|
||||
mode_: c_int,
|
||||
out_result_: ?*CompressionResult,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const out_result = out_result_ orelse return .invalid_value;
|
||||
const mode = std.meta.intToEnum(CompressionMode, mode_) catch
|
||||
return .invalid_value;
|
||||
|
||||
out_result.* = t.compress(mode);
|
||||
return .success;
|
||||
}
|
||||
|
||||
/// C: GhosttyTerminalOption
|
||||
pub const Option = enum(c_int) {
|
||||
userdata = 0,
|
||||
@@ -1099,6 +1129,112 @@ test "scroll_viewport null" {
|
||||
scroll_viewport(null, .{ .tag = .row, .value = .{ .row = 1 } });
|
||||
}
|
||||
|
||||
test "compression invalid arguments" {
|
||||
var activity: u64 = undefined;
|
||||
var compression_result: CompressionResult = undefined;
|
||||
|
||||
try testing.expectEqual(
|
||||
Result.invalid_value,
|
||||
compression_activity(null, &activity),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Result.invalid_value,
|
||||
compress(null, @intFromEnum(CompressionMode.incremental), &compression_result),
|
||||
);
|
||||
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000_000,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
try testing.expectEqual(
|
||||
Result.invalid_value,
|
||||
compression_activity(t, null),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Result.invalid_value,
|
||||
compress(t, @intFromEnum(CompressionMode.incremental), null),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Result.invalid_value,
|
||||
compress(t, -1, &compression_result),
|
||||
);
|
||||
}
|
||||
|
||||
test "compression activity and incremental scheduling" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000_000,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
var initial_activity: u64 = undefined;
|
||||
try testing.expectEqual(
|
||||
Result.success,
|
||||
compression_activity(t, &initial_activity),
|
||||
);
|
||||
|
||||
const line = "repeated and compressible terminal history\r\n";
|
||||
const repeat = 4_000;
|
||||
const input = try testing.allocator.alloc(u8, line.len * repeat);
|
||||
defer testing.allocator.free(input);
|
||||
for (0..repeat) |i|
|
||||
@memcpy(input[i * line.len ..][0..line.len], line);
|
||||
vt_write(t, input.ptr, input.len);
|
||||
|
||||
var activity: u64 = undefined;
|
||||
try testing.expectEqual(
|
||||
Result.success,
|
||||
compression_activity(t, &activity),
|
||||
);
|
||||
try testing.expect(activity != initial_activity);
|
||||
|
||||
var compression_result: CompressionResult = undefined;
|
||||
for (0..1_000) |_| {
|
||||
try testing.expectEqual(
|
||||
Result.success,
|
||||
compress(
|
||||
t,
|
||||
@intFromEnum(CompressionMode.incremental),
|
||||
&compression_result,
|
||||
),
|
||||
);
|
||||
|
||||
switch (compression_result) {
|
||||
.pending => continue,
|
||||
.complete => break,
|
||||
.unsupported => unreachable,
|
||||
}
|
||||
} else return error.TestUnexpectedResult;
|
||||
|
||||
// Compression changes storage representation, not the activity token.
|
||||
var final_activity: u64 = undefined;
|
||||
try testing.expectEqual(
|
||||
Result.success,
|
||||
compression_activity(t, &final_activity),
|
||||
);
|
||||
try testing.expectEqual(activity, final_activity);
|
||||
|
||||
try testing.expectEqual(
|
||||
Result.success,
|
||||
compress(t, @intFromEnum(CompressionMode.full), &compression_result),
|
||||
);
|
||||
try testing.expectEqual(CompressionResult.complete, compression_result);
|
||||
}
|
||||
|
||||
test "reset" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user