mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
libghostty: scrollback limits can be changed at runtime (#13481)
**This is an ABI breaking change for C libs.** Fixes https://github.com/ghostty-org/ghostty/issues/13268 You can now change scrollback limits (bytes and lines) at runtime. This breaks the ABI but makes for a much more long-term pattern: `ghostty_terminal_new` now only takes viewport size, and you set scrollback configurations with the generic `ghostty_terminal_set`. The `GhosttyTerminalOptions` struct is fully removed. I think this will serve us much better over the long term.
This commit is contained in:
@@ -7,12 +7,7 @@
|
||||
int main() {
|
||||
// Create a terminal with a small grid
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Write some VT-encoded content into the terminal
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
int main() {
|
||||
// Create a terminal with a small grid
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Write some VT-encoded content into the terminal
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
int main() {
|
||||
// Create a terminal with a small grid
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Write some VT-encoded content into the terminal
|
||||
|
||||
@@ -87,12 +87,7 @@ void print_all_colors(GhosttyTerminal terminal, const char* label) {
|
||||
int main() {
|
||||
// Create a terminal
|
||||
GhosttyTerminal terminal = NULL;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
if (ghostty_terminal_new(NULL, &terminal, opts) != GHOSTTY_SUCCESS) {
|
||||
if (ghostty_terminal_new(NULL, &terminal, 80, 24) != GHOSTTY_SUCCESS) {
|
||||
fprintf(stderr, "Failed to create terminal\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -30,12 +30,14 @@ static bool compression_idle_step(GhosttyTerminal terminal) {
|
||||
|
||||
int main(void) {
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10 * 1024 * 1024,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
size_t max_scrollback_bytes = 10 * 1024 * 1024;
|
||||
result = ghostty_terminal_set(
|
||||
terminal,
|
||||
GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES,
|
||||
&max_scrollback_bytes);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
//! [compression-activity]
|
||||
|
||||
@@ -74,12 +74,7 @@ GhosttyClipboardWriteResult on_clipboard_write(
|
||||
int main() {
|
||||
// Create a terminal
|
||||
GhosttyTerminal terminal = NULL;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
if (ghostty_terminal_new(NULL, &terminal, opts) != GHOSTTY_SUCCESS) {
|
||||
if (ghostty_terminal_new(NULL, &terminal, 80, 24) != GHOSTTY_SUCCESS) {
|
||||
fprintf(stderr, "Failed to create terminal\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
int main() {
|
||||
// Create a terminal with a small grid
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Write VT-encoded content into the terminal to exercise various
|
||||
|
||||
@@ -25,12 +25,7 @@ static uint32_t codepoint_at_tracked_ref(GhosttyTrackedGridRef tracked) {
|
||||
|
||||
int main() {
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 8,
|
||||
.rows = 3,
|
||||
.max_scrollback = 100,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 8, 3);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
const char *text = "alpha\r\n"
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
int main() {
|
||||
// Create a small terminal
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 10,
|
||||
.rows = 3,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 10, 3);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Write some content so the grid has interesting data
|
||||
|
||||
@@ -72,12 +72,7 @@ int main() {
|
||||
|
||||
/* Create a terminal with Kitty graphics enabled. */
|
||||
GhosttyTerminal terminal = NULL;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
if (ghostty_terminal_new(NULL, &terminal, opts) != GHOSTTY_SUCCESS) {
|
||||
if (ghostty_terminal_new(NULL, &terminal, 80, 24) != GHOSTTY_SUCCESS) {
|
||||
fprintf(stderr, "Failed to create terminal\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -26,12 +26,7 @@ int main(void) {
|
||||
// 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);
|
||||
result = ghostty_terminal_new(NULL, &terminal, 40, 5);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttyRenderState render_state = NULL;
|
||||
|
||||
@@ -54,12 +54,7 @@ static GhosttySelectionGestureEvent new_event(
|
||||
|
||||
int main() {
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 20,
|
||||
.rows = 4,
|
||||
.max_scrollback = 100,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 20, 4);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
vt_write(terminal, "hello world\r\nsecond line");
|
||||
|
||||
@@ -49,12 +49,7 @@ static void print_selection(
|
||||
|
||||
int main() {
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 8,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 8);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// A realistic shell transcript with OSC 133 semantic prompt markers.
|
||||
|
||||
@@ -7,12 +7,7 @@ int main(void) {
|
||||
//! [vt-stream-init]
|
||||
// Create a terminal
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
//! [vt-stream-init]
|
||||
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
int main() {
|
||||
// Create a terminal
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(nullptr, &terminal, opts);
|
||||
GhosttyResult result = ghostty_terminal_new(nullptr, &terminal, 80, 24);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Feed VT data into the terminal
|
||||
|
||||
@@ -3,12 +3,7 @@ import GhosttyVt
|
||||
|
||||
// Create a terminal with a small grid
|
||||
var terminal: GhosttyTerminal?
|
||||
var opts = GhosttyTerminalOptions(
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
max_scrollback: 0
|
||||
)
|
||||
let result = ghostty_terminal_new(nil, &terminal, opts)
|
||||
let result = ghostty_terminal_new(nil, &terminal, 80, 24)
|
||||
guard result == GHOSTTY_SUCCESS, let terminal else {
|
||||
fatalError("Failed to create terminal")
|
||||
}
|
||||
|
||||
@@ -199,20 +199,15 @@
|
||||
const rows = parseInt(document.getElementById('rows').value, 10);
|
||||
const vtText = parseEscapes(document.getElementById('vtInput').value);
|
||||
|
||||
const TERM_OPTS_SIZE = typeLayout['GhosttyTerminalOptions'].size;
|
||||
const optsPtr = wasmInstance.exports.ghostty_wasm_alloc_u8_array(TERM_OPTS_SIZE);
|
||||
new Uint8Array(getBuffer(), optsPtr, TERM_OPTS_SIZE).fill(0);
|
||||
const optsView = new DataView(getBuffer(), optsPtr, TERM_OPTS_SIZE);
|
||||
setField(optsView, 'GhosttyTerminalOptions', 'cols', cols);
|
||||
setField(optsView, 'GhosttyTerminalOptions', 'rows', rows);
|
||||
setField(optsView, 'GhosttyTerminalOptions', 'max_scrollback', 0);
|
||||
|
||||
// Allocate pointer to receive the terminal handle
|
||||
const termPtrPtr = wasmInstance.exports.ghostty_wasm_alloc_opaque();
|
||||
|
||||
// Create terminal
|
||||
const newResult = wasmInstance.exports.ghostty_terminal_new(0, termPtrPtr, optsPtr);
|
||||
wasmInstance.exports.ghostty_wasm_free_u8_array(optsPtr, TERM_OPTS_SIZE);
|
||||
const newResult = wasmInstance.exports.ghostty_terminal_new(
|
||||
0,
|
||||
termPtrPtr,
|
||||
cols,
|
||||
rows);
|
||||
|
||||
if (newResult !== GHOSTTY_SUCCESS) {
|
||||
throw new Error(`ghostty_terminal_new failed with result ${newResult}`);
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
* @code{.c}
|
||||
* // Create a terminal and feed it some VT data that changes modes
|
||||
* GhosttyTerminal terminal;
|
||||
* ghostty_terminal_new(NULL, &terminal,
|
||||
* (GhosttyTerminalOptions){.cols = 80, .rows = 24, .max_scrollback = 0});
|
||||
* ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
*
|
||||
* // Application might write data that enables Kitty keyboard protocol, etc.
|
||||
* ghostty_terminal_vt_write(terminal, vt_data, vt_len);
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
* @code{.c}
|
||||
* // Create a terminal and feed it some VT data that enables mouse tracking
|
||||
* GhosttyTerminal terminal;
|
||||
* ghostty_terminal_new(NULL, &terminal,
|
||||
* (GhosttyTerminalOptions){.cols = 80, .rows = 24, .max_scrollback = 0});
|
||||
* ghostty_terminal_new(NULL, &terminal, 80, 24);
|
||||
*
|
||||
* // Application might write data that enables mouse reporting, etc.
|
||||
* ghostty_terminal_vt_write(terminal, vt_data, vt_len);
|
||||
|
||||
@@ -172,26 +172,6 @@ extern "C" {
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Terminal initialization options.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
/** Terminal width in cells. Must be greater than zero. */
|
||||
uint16_t cols;
|
||||
|
||||
/** Terminal height in cells. Must be greater than zero. */
|
||||
uint16_t rows;
|
||||
|
||||
/** Maximum number of lines to keep in scrollback history. */
|
||||
size_t max_scrollback;
|
||||
|
||||
// TODO: Consider ABI compatibility implications of this struct.
|
||||
// We may want to artificially pad it significantly to support
|
||||
// future options.
|
||||
} GhosttyTerminalOptions;
|
||||
|
||||
/**
|
||||
* Amount of compression work to perform before returning.
|
||||
*
|
||||
@@ -888,6 +868,52 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Input type: GhosttyTerminalClipboardWriteFn
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE = 26,
|
||||
|
||||
/**
|
||||
* Set the maximum scrollback allocation in bytes.
|
||||
*
|
||||
* This is an estimate. Internally, libghostty only prunes bytes up
|
||||
* to a "page"-granularity. A page is the minimum allocated unit of
|
||||
* grid space within Ghostty. A page at the time of writing these docs
|
||||
* is about 400KB, so the byte limit will be within this delta.
|
||||
*
|
||||
* This works alongside the line limit configuration. If both are set,
|
||||
* the first-reached limit is used first. Both limits are dependent
|
||||
* on external state (byte limit can be reached with less lines if
|
||||
* more styles are used for example, line limit can be reached with
|
||||
* a narrower terminal viewport). So, they are useful together.
|
||||
*
|
||||
* Lowering the limit immediately removes eligible complete historical
|
||||
* pages. A value of zero disables scrollback and erases retained history.
|
||||
* A NULL value pointer removes the byte limit.
|
||||
*
|
||||
* Input type: size_t*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES = 27,
|
||||
|
||||
/**
|
||||
* Set the maximum number of physical lines retained in scrollback.
|
||||
*
|
||||
* This is an estimate. Internally, libghostty only prunes lines up
|
||||
* to a "page"-granularity. A page is the minimum allocated unit of
|
||||
* grid space within Ghostty. As a result, the actual available scrollback
|
||||
* lines will almost always be higher than configured. The magnitude
|
||||
* of the difference depends on the number of used styles, graphemes, etc.
|
||||
* since the row-count in a page is dynamic based on that. In general,
|
||||
* it ranges from dozens to a hundred or so lines.
|
||||
*
|
||||
* This works alongside the line limit configuration. If both are set,
|
||||
* the first-reached limit is used first. Both limits are dependent
|
||||
* on external state (byte limit can be reached with less lines if
|
||||
* more styles are used for example, line limit can be reached with
|
||||
* a narrower terminal viewport). So, they are useful together.
|
||||
*
|
||||
* Lowering the limit immediately removes eligible complete historical
|
||||
* pages. A NULL value pointer removes the line limit.
|
||||
*
|
||||
* Input type: size_t*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_LINES = 28,
|
||||
GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
@@ -1215,22 +1241,50 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Output type: bool *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_VT_PROCESSING_ERROR = 33,
|
||||
|
||||
/**
|
||||
* The configured maximum scrollback allocation in bytes.
|
||||
*
|
||||
* This always reports the primary screen's configured value, including
|
||||
* while an alternate screen is active. Returns GHOSTTY_NO_VALUE when the
|
||||
* configured byte limit is unlimited.
|
||||
*
|
||||
* Output type: size_t *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_SCROLLBACK_MAX_BYTES = 34,
|
||||
|
||||
/**
|
||||
* The configured maximum number of physical scrollback lines.
|
||||
*
|
||||
* This always reports the primary screen's configured value, including
|
||||
* while an alternate screen is active. Returns GHOSTTY_NO_VALUE when the
|
||||
* configured line limit is unlimited.
|
||||
*
|
||||
* Output type: size_t *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_SCROLLBACK_MAX_LINES = 35,
|
||||
GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalData;
|
||||
|
||||
/**
|
||||
* Create a new terminal instance.
|
||||
*
|
||||
* The terminal starts with various reasonable defaults e.g. around
|
||||
* scrollback limits. Use ghostty_terminal_set() to change any options
|
||||
* prior to using the terminal.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param terminal Pointer to store the created terminal handle
|
||||
* @param options Terminal initialization options
|
||||
* @param cols Terminal width in cells (must be greater than zero)
|
||||
* @param rows Terminal height in cells (must be greater than zero)
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_new(const GhosttyAllocator* allocator,
|
||||
GhosttyTerminal* terminal,
|
||||
GhosttyTerminalOptions options);
|
||||
GhosttyTerminal* terminal,
|
||||
uint16_t cols,
|
||||
uint16_t rows);
|
||||
|
||||
/**
|
||||
* Free a terminal instance.
|
||||
@@ -1291,7 +1345,8 @@ GHOSTTY_API GhosttyResult ghostty_terminal_resize(GhosttyTerminal terminal,
|
||||
* write_pty callback and userdata pointer. The value is passed
|
||||
* directly for pointer types (callbacks, userdata) or as a pointer
|
||||
* to the value for non-pointer types (e.g. GhosttyString*).
|
||||
* NULL clears the option to its default.
|
||||
* The behavior of a NULL value is specific to each option and is
|
||||
* documented by the corresponding GhosttyTerminalOption value.
|
||||
*
|
||||
* Callbacks are invoked synchronously during ghostty_terminal_vt_write().
|
||||
* Callbacks must not call ghostty_terminal_vt_write() on the same
|
||||
|
||||
@@ -162,7 +162,7 @@ fn summaryTable(pages: *const PageList) void {
|
||||
);
|
||||
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
|
||||
var limit_buf: [64]u8 = undefined;
|
||||
const limit = formatBytes(&limit_buf, pages.maxSize());
|
||||
const limit = formatBytes(&limit_buf, pages.limits.max(.bytes));
|
||||
cimgui.c.ImGui_TextUnformatted(limit.ptr);
|
||||
|
||||
cimgui.c.ImGui_TableNextRow();
|
||||
|
||||
@@ -353,7 +353,12 @@ pub fn internalStateTable(
|
||||
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
|
||||
cimgui.c.ImGui_Text("Memory Limit");
|
||||
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
|
||||
cimgui.c.ImGui_Text("%d bytes (%d KiB)", pages.maxSize(), units.toKibiBytes(pages.maxSize()));
|
||||
const max_bytes = pages.limits.max(.bytes);
|
||||
cimgui.c.ImGui_Text(
|
||||
"%d bytes (%d KiB)",
|
||||
max_bytes,
|
||||
units.toKibiBytes(max_bytes),
|
||||
);
|
||||
|
||||
cimgui.c.ImGui_TableNextRow();
|
||||
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
|
||||
|
||||
@@ -401,23 +401,8 @@ page_size: usize,
|
||||
/// in the state struct.
|
||||
page_compression: IncrementalCompressionState = .{},
|
||||
|
||||
/// Maximum size of the page allocation in bytes. This only includes pages
|
||||
/// that are used ONLY for scrollback. If the active area is still partially
|
||||
/// in a page that also includes scrollback, then that page is not included.
|
||||
explicit_max_size: usize,
|
||||
|
||||
/// This is the minimum max size that we will respect due to the rows/cols
|
||||
/// of the PageList. We must always be able to fit at least the active area
|
||||
/// and at least two pages for our algorithms.
|
||||
min_max_size: usize,
|
||||
|
||||
/// Maximum number of physical rows retained as scrollback. This excludes the
|
||||
/// active area and is raised to `min_max_lines` when necessary.
|
||||
explicit_max_lines: usize,
|
||||
|
||||
/// Minimum line limit required to retain one standard page worth of
|
||||
/// scrollback at the current column count.
|
||||
min_max_lines: usize,
|
||||
/// Limits for scrollback.
|
||||
limits: Limits,
|
||||
|
||||
/// The total number of rows represented by this PageList. This is used
|
||||
/// specifically for scrollbar information so we can have the total size.
|
||||
@@ -486,66 +471,6 @@ pub const Viewport = union(enum) {
|
||||
pin,
|
||||
};
|
||||
|
||||
/// Returns the minimum valid "max size" for a given number of rows and cols
|
||||
/// such that we can fit the active area AND at least two pages. Note we
|
||||
/// need the two pages for algorithms to work properly (such as grow) but
|
||||
/// we don't need to fit double the active area.
|
||||
///
|
||||
/// This min size may not be totally correct in the case that a large
|
||||
/// number of other dimensions makes our row size in a page very small.
|
||||
/// But this gives us a nice fast heuristic for determining min/max size.
|
||||
/// Therefore, if the page size is violated you should always also verify
|
||||
/// that we have enough space for the active area.
|
||||
fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) usize {
|
||||
// Invariant required to ensure our divCeil below cannot overflow.
|
||||
comptime {
|
||||
const max_rows = std.math.maxInt(size.CellCountInt);
|
||||
_ = std.math.divCeil(usize, max_rows, 1) catch unreachable;
|
||||
}
|
||||
|
||||
// Get our capacity to fit our rows. If the cols are too big, it may
|
||||
// force less rows than we want meaning we need more than one page to
|
||||
// represent a viewport.
|
||||
const cap = initialCapacity(cols);
|
||||
|
||||
// Calculate the number of standard sized pages we need to represent
|
||||
// an active area.
|
||||
const pages_exact = if (cap.rows >= rows) 1 else std.math.divCeil(
|
||||
usize,
|
||||
rows,
|
||||
cap.rows,
|
||||
) catch {
|
||||
// Not possible:
|
||||
// - initialCapacity guarantees at least 1 row
|
||||
// - numerator/denominator can't overflow because of comptime check above
|
||||
unreachable;
|
||||
};
|
||||
|
||||
// We always need at least one page extra so that we
|
||||
// can fit partial pages to spread our active area across two pages.
|
||||
// Even for caps that can't fit all rows in a single page, we add one
|
||||
// because the most extra space we need at any given time is only
|
||||
// the partial amount of one page.
|
||||
const pages = pages_exact + 1;
|
||||
assert(pages >= 2);
|
||||
|
||||
// log.debug("minMaxSize cols={} rows={} cap={} pages={}", .{
|
||||
// cols,
|
||||
// rows,
|
||||
// cap,
|
||||
// pages,
|
||||
// });
|
||||
|
||||
return PagePool.item_size * pages;
|
||||
}
|
||||
|
||||
/// Returns the minimum line limit for a given column count. Line limits are
|
||||
/// page-granular, so we always permit at least one standard page worth of
|
||||
/// scrollback rows.
|
||||
fn minMaxLines(cols: size.CellCountInt) usize {
|
||||
return initialCapacity(cols).rows;
|
||||
}
|
||||
|
||||
/// Calculates the initial capacity for a new page for a given column
|
||||
/// count. This will attempt to fit within std_size at all times so we
|
||||
/// can use our memory pool, but if cols is too big, this will return a
|
||||
@@ -680,8 +605,9 @@ pub fn init(
|
||||
rows,
|
||||
);
|
||||
|
||||
// Get our minimum max size, see doc comments for more details.
|
||||
const min_max_size = minMaxSize(cols, rows);
|
||||
var limits: Limits = .init(cols, rows);
|
||||
limits.set(.bytes, opts.max_size);
|
||||
limits.set(.lines, opts.max_lines);
|
||||
|
||||
// We always track our viewport pin to ensure this is never an allocation
|
||||
try tw.check(.viewport_pin);
|
||||
@@ -702,10 +628,7 @@ pub fn init(
|
||||
.page_serial = page_serial,
|
||||
.page_serial_epoch = 0,
|
||||
.page_size = page_size,
|
||||
.explicit_max_size = opts.max_size orelse std.math.maxInt(usize),
|
||||
.min_max_size = min_max_size,
|
||||
.explicit_max_lines = opts.max_lines orelse std.math.maxInt(usize),
|
||||
.min_max_lines = minMaxLines(cols),
|
||||
.limits = limits,
|
||||
.total_rows = rows,
|
||||
.tracked_pins = tracked_pins,
|
||||
.viewport = .{ .active = {} },
|
||||
@@ -884,12 +807,12 @@ fn verifyIntegrity(self: *const PageList) IntegrityError!void {
|
||||
// active rows. Complete historical pages are always eligible for pruning.
|
||||
if (self.total_rows > self.rows) {
|
||||
const history_rows = self.total_rows - self.rows;
|
||||
if (history_rows > self.maxLines() and
|
||||
if (history_rows > self.limits.max(.lines) and
|
||||
self.pages.first.? != self.getTopLeft(.active).node)
|
||||
{
|
||||
log.warn(
|
||||
"PageList integrity violation: max lines exceeded history={} max={}",
|
||||
.{ history_rows, self.maxLines() },
|
||||
.{ history_rows, self.limits.max(.lines) },
|
||||
);
|
||||
return IntegrityError.MaxLinesExceeded;
|
||||
}
|
||||
@@ -1236,10 +1159,7 @@ pub fn clone(
|
||||
.page_serial = page_serial,
|
||||
.page_serial_epoch = 0,
|
||||
.page_size = page_size,
|
||||
.explicit_max_size = self.explicit_max_size,
|
||||
.min_max_size = self.min_max_size,
|
||||
.explicit_max_lines = self.explicit_max_lines,
|
||||
.min_max_lines = self.min_max_lines,
|
||||
.limits = self.limits,
|
||||
.cols = self.cols,
|
||||
.rows = self.rows,
|
||||
.total_rows = total_rows,
|
||||
@@ -1270,7 +1190,7 @@ pub fn clone(
|
||||
}
|
||||
|
||||
// A clone can copy more history than its inherited line limit.
|
||||
result.enforceMaxLines();
|
||||
result.limits.enforce(&result, .lines);
|
||||
|
||||
result.assertIntegrity();
|
||||
return result;
|
||||
@@ -1328,24 +1248,18 @@ pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void {
|
||||
try self.resizeWithoutReflow(opts);
|
||||
// Shrinking the active row count turns former active rows into
|
||||
// scrollback even without reflow, which can cross the line limit.
|
||||
self.enforceMaxLines();
|
||||
self.limits.enforce(self, .lines);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate our minimum max size. This allows grow to work properly
|
||||
// when increasing beyond our initial minimum max size or explicit max
|
||||
// size to fit the active area.
|
||||
const old_min_max_size = self.min_max_size;
|
||||
self.min_max_size = minMaxSize(
|
||||
// Recalculate our minimum limits. This allows grow to work properly when
|
||||
// increasing beyond the explicit limits to fit the active area.
|
||||
const old_limits = self.limits;
|
||||
self.limits.resize(
|
||||
opts.cols orelse self.cols,
|
||||
opts.rows orelse self.rows,
|
||||
);
|
||||
errdefer self.min_max_size = old_min_max_size;
|
||||
|
||||
// Recalculate our minimum max lines for the same reasons as above.
|
||||
const old_min_max_lines = self.min_max_lines;
|
||||
self.min_max_lines = minMaxLines(opts.cols orelse self.cols);
|
||||
errdefer self.min_max_lines = old_min_max_lines;
|
||||
errdefer self.limits = old_limits;
|
||||
|
||||
// On reflow, the main thing that causes reflow is column changes. If
|
||||
// only rows change, reflow is impossible. So we change our behavior based
|
||||
@@ -1386,7 +1300,7 @@ pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void {
|
||||
// Column reflow can change the physical history row count, and a row
|
||||
// resize can move the active boundary. Both may expose whole old pages
|
||||
// that are now eligible for line-limit pruning.
|
||||
self.enforceMaxLines();
|
||||
self.limits.enforce(self, .lines);
|
||||
}
|
||||
|
||||
/// Resize the pagelist with reflow by adding or removing columns.
|
||||
@@ -2413,20 +2327,14 @@ const ReflowCursor = struct {
|
||||
};
|
||||
|
||||
fn resizeWithoutReflow(self: *PageList, opts: Resize) Allocator.Error!void {
|
||||
// We only set the new min_max_size if we're not reflowing. If we are
|
||||
// reflowing, then resize handles this for us.
|
||||
const old_min_max_size = self.min_max_size;
|
||||
self.min_max_size = if (!opts.reflow) minMaxSize(
|
||||
// We only set the new minimums if we're not reflowing. If we are
|
||||
// reflowing, then the outer resize call handles this for us.
|
||||
const old_limits = self.limits;
|
||||
if (!opts.reflow) self.limits.resize(
|
||||
opts.cols orelse self.cols,
|
||||
opts.rows orelse self.rows,
|
||||
) else old_min_max_size;
|
||||
errdefer self.min_max_size = old_min_max_size;
|
||||
const old_min_max_lines = self.min_max_lines;
|
||||
self.min_max_lines = if (!opts.reflow)
|
||||
minMaxLines(opts.cols orelse self.cols)
|
||||
else
|
||||
old_min_max_lines;
|
||||
errdefer self.min_max_lines = old_min_max_lines;
|
||||
);
|
||||
errdefer self.limits = old_limits;
|
||||
|
||||
// Important! We have to do cols first because cols may cause us to
|
||||
// destroy pages if we're increasing cols which will free up page_size
|
||||
@@ -2913,7 +2821,7 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
|
||||
defer self.assertIntegrity();
|
||||
|
||||
// Special case no-scrollback mode to never allow scrolling.
|
||||
if (self.explicit_max_size == 0) {
|
||||
if (self.limits.bytes.explicit == 0) {
|
||||
self.viewport = .active;
|
||||
return;
|
||||
}
|
||||
@@ -3483,7 +3391,7 @@ pub fn scrollbar(self: *PageList) Scrollbar {
|
||||
// it always has SOME extra space (due to the way we allocate by page).
|
||||
// So even with no scrollback we have some growth. It is architecturally
|
||||
// much simpler to just hide that for no-scrollback cases.
|
||||
if (self.explicit_max_size == 0) return .{
|
||||
if (self.limits.bytes.explicit == 0) return .{
|
||||
.total = self.rows,
|
||||
.offset = 0,
|
||||
.len = self.rows,
|
||||
@@ -3584,74 +3492,31 @@ fn fixupViewport(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the actual max size. This may be greater than the explicit
|
||||
/// value if the explicit value is less than the min_max_size.
|
||||
/// Change the maximum logical page allocation at runtime. Null removes the
|
||||
/// explicit byte limit and zero disables scrollback.
|
||||
///
|
||||
/// This value is a HEURISTIC. You cannot assert on this value. We may
|
||||
/// exceed this value if required to fit the active area. This may be
|
||||
/// required in some cases if the active area has a large number of
|
||||
/// graphemes, styles, etc.
|
||||
pub fn maxSize(self: *const PageList) usize {
|
||||
return @max(self.explicit_max_size, self.min_max_size);
|
||||
/// Lowering the limit immediately removes eligible complete historical pages.
|
||||
/// The effective limit may still be raised to fit the active area, and a page
|
||||
/// which overlaps the active area is never split solely to satisfy this limit.
|
||||
pub fn setMaxBytes(self: *PageList, max: ?usize) void {
|
||||
defer self.assertIntegrity();
|
||||
|
||||
self.limits.set(.bytes, max);
|
||||
self.limits.enforce(self, .bytes);
|
||||
if (self.limits.bytes.explicit == 0) self.viewport = .active;
|
||||
}
|
||||
|
||||
/// Returns the effective scrollback row limit. At least one standard page
|
||||
/// worth of rows is permitted so enforcement never has to split a page.
|
||||
/// Change the maximum number of physical scrollback rows at runtime. Null
|
||||
/// removes the explicit line limit.
|
||||
///
|
||||
/// Like `maxSize`, this is a heuristic. An unusually large page which contains
|
||||
/// both history and active rows can temporarily exceed this value because only
|
||||
/// complete historical pages are pruned.
|
||||
fn maxLines(self: *const PageList) usize {
|
||||
return @max(self.explicit_max_lines, self.min_max_lines);
|
||||
}
|
||||
/// Lowering the limit immediately removes eligible complete historical pages.
|
||||
/// The effective limit always permits at least one standard page of history,
|
||||
/// and a page which overlaps the active area is never split for enforcement.
|
||||
pub fn setMaxLines(self: *PageList, max: ?usize) void {
|
||||
defer self.assertIntegrity();
|
||||
|
||||
/// Prune complete historical pages until the line limit is satisfied or the
|
||||
/// oldest remaining page overlaps the active area.
|
||||
fn enforceMaxLines(self: *PageList) void {
|
||||
if (self.explicit_max_lines == std.math.maxInt(usize)) return;
|
||||
|
||||
// A partially constructed clone can temporarily contain fewer rows than
|
||||
// its active area. It has no scrollback to prune.
|
||||
if (self.total_rows <= self.rows) return;
|
||||
|
||||
// If we're lower then the limit we're good.
|
||||
const max_lines = self.maxLines();
|
||||
if (self.total_rows - self.rows <= max_lines) return;
|
||||
|
||||
// Removing pages changes compression.
|
||||
self.page_compression.markActivity();
|
||||
|
||||
// Accumulate the row delta so viewport offsets are fixed up once
|
||||
// after all eligible pages have been removed.
|
||||
var removed: usize = 0;
|
||||
while (self.total_rows - self.rows > max_lines) {
|
||||
const first = self.pages.first.?;
|
||||
|
||||
// The page containing the active top may also contain history. Keep
|
||||
// that boundary page whole even if its history exceeds the heuristic.
|
||||
if (first == self.getTopLeft(.active).node) break;
|
||||
|
||||
const first_rows = first.rows();
|
||||
|
||||
// Automatic pruning invalidates the content represented by pins in
|
||||
// the removed page. erasePage remaps them to the next page below but
|
||||
// only the line-limit caller knows that their original content is
|
||||
// gone, so mark them as garbage here.
|
||||
for (self.tracked_pins.keys()) |p| {
|
||||
if (p.node == first) p.garbage = true;
|
||||
}
|
||||
|
||||
// erasePage updates the list, pin targets, and byte accounting.
|
||||
// Row accounting belongs to the caller because erasePage is also used
|
||||
// by paths that already adjusted total_rows.
|
||||
self.erasePage(first);
|
||||
self.total_rows -= first_rows;
|
||||
removed += first_rows;
|
||||
}
|
||||
|
||||
// Reconcile viewport mode and cached row offsets with the combined prefix
|
||||
// removal only after every page and pin points into the final list.
|
||||
if (removed > 0) self.fixupViewport(removed);
|
||||
self.limits.set(.lines, max);
|
||||
self.limits.enforce(self, .lines);
|
||||
}
|
||||
|
||||
/// Grow the active area by exactly one row.
|
||||
@@ -3679,7 +3544,7 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node {
|
||||
|
||||
// Growing inside the last page moves the active boundary without
|
||||
// allocating; that alone can make the first page wholly historical.
|
||||
self.enforceMaxLines();
|
||||
self.limits.enforce(self, .lines);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3699,7 +3564,7 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node {
|
||||
// initial allocation.
|
||||
if (self.pages.first != null and
|
||||
self.pages.first != self.pages.last and
|
||||
self.page_size + PagePool.item_size > self.maxSize())
|
||||
self.page_size + PagePool.item_size > self.limits.max(.bytes))
|
||||
prune: {
|
||||
const first = self.pages.popFirst().?;
|
||||
assert(first != last);
|
||||
@@ -3784,7 +3649,7 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node {
|
||||
|
||||
// Byte-limit recycling may leave history above the independent line
|
||||
// limit, so enforce it after the recycled page becomes the new tail.
|
||||
self.enforceMaxLines();
|
||||
self.limits.enforce(self, .lines);
|
||||
return first;
|
||||
}
|
||||
|
||||
@@ -3805,7 +3670,7 @@ pub fn grow(self: *PageList) Allocator.Error!?*List.Node {
|
||||
|
||||
// Appending a page can cross the line limit and can make the oldest
|
||||
// active-boundary page wholly historical.
|
||||
self.enforceMaxLines();
|
||||
self.limits.enforce(self, .lines);
|
||||
return next_node;
|
||||
}
|
||||
|
||||
@@ -6346,6 +6211,190 @@ fn markDirty(self: *PageList, pt: point.Point) void {
|
||||
self.pin(pt).?.markDirty();
|
||||
}
|
||||
|
||||
/// Runtime-configurable byte and line limits for a PageList.
|
||||
const Limits = struct {
|
||||
bytes: Limit,
|
||||
lines: Limit,
|
||||
|
||||
/// The limit keys.
|
||||
pub const Key = std.meta.FieldEnum(Limits);
|
||||
|
||||
pub const Limit = struct {
|
||||
/// Explicit is the specified maximum value for this limit
|
||||
/// by the user or maxInt otherwise.
|
||||
explicit: usize = std.math.maxInt(usize),
|
||||
|
||||
/// Min is the minimum valid maximum for this entry, so if
|
||||
/// explicit is lower than this then min wins.
|
||||
min: usize,
|
||||
};
|
||||
|
||||
/// Return unlimited limits with minimums for the given PageList size.
|
||||
pub fn init(cols: size.CellCountInt, rows: size.CellCountInt) Limits {
|
||||
return .{
|
||||
.bytes = .{ .min = minMaxSize(cols, rows) },
|
||||
.lines = .{ .min = minMaxLines(cols) },
|
||||
};
|
||||
}
|
||||
|
||||
/// Set an explicit limit. Null means unlimited. This does not enforce the
|
||||
/// new value automatically; the caller must still call `enforce`.
|
||||
pub fn set(
|
||||
self: *Limits,
|
||||
comptime key: Key,
|
||||
value: ?usize,
|
||||
) void {
|
||||
switch (key) {
|
||||
.bytes => self.bytes.explicit = value orelse std.math.maxInt(usize),
|
||||
.lines => self.lines.explicit = value orelse std.math.maxInt(usize),
|
||||
}
|
||||
}
|
||||
|
||||
/// Recalculate the effective minimums for a new PageList size.
|
||||
///
|
||||
/// This must be called whenever either PageList dimension changes so the
|
||||
/// effective byte and line limits remain valid for the new size.
|
||||
pub fn resize(
|
||||
self: *Limits,
|
||||
cols: size.CellCountInt,
|
||||
rows: size.CellCountInt,
|
||||
) void {
|
||||
self.bytes.min = minMaxSize(cols, rows);
|
||||
self.lines.min = minMaxLines(cols);
|
||||
}
|
||||
|
||||
/// Return the effective maximum for a limit.
|
||||
pub fn max(self: *const Limits, key: Key) usize {
|
||||
return switch (key) {
|
||||
.bytes => @max(self.bytes.explicit, self.bytes.min),
|
||||
.lines => @max(self.lines.explicit, self.lines.min),
|
||||
};
|
||||
}
|
||||
|
||||
/// Whether the given limit is currently exceeded. Both limits are
|
||||
/// heuristics: complete historical pages are the smallest unit that
|
||||
/// enforcement removes.
|
||||
pub fn exceeded(
|
||||
self: *const Limits,
|
||||
pagelist: *const PageList,
|
||||
limit: Key,
|
||||
) bool {
|
||||
return switch (limit) {
|
||||
.bytes => pagelist.page_size > self.max(.bytes),
|
||||
.lines => pagelist.total_rows > pagelist.rows and
|
||||
pagelist.total_rows - pagelist.rows > self.max(.lines),
|
||||
};
|
||||
}
|
||||
|
||||
/// Prune complete historical pages until the selected limit is satisfied
|
||||
/// or the oldest remaining page overlaps the active area.
|
||||
pub fn enforce(
|
||||
self: *const Limits,
|
||||
pagelist: *PageList,
|
||||
key: Key,
|
||||
) void {
|
||||
if (!self.exceeded(pagelist, key)) return;
|
||||
|
||||
// A partially constructed clone can temporarily contain fewer rows than
|
||||
// its active area. It has no scrollback to prune.
|
||||
if (pagelist.total_rows <= pagelist.rows) return;
|
||||
|
||||
// Accumulate the row delta so viewport offsets are fixed up once
|
||||
// after all eligible pages have been removed.
|
||||
var removed: usize = 0;
|
||||
while (self.exceeded(pagelist, key)) {
|
||||
const first = pagelist.pages.first.?;
|
||||
|
||||
// The page containing the active top may also contain history. Keep
|
||||
// that boundary page whole even if its history exceeds the heuristic.
|
||||
if (first == pagelist.getTopLeft(.active).node) break;
|
||||
|
||||
if (removed == 0) pagelist.page_compression.markActivity();
|
||||
|
||||
const first_rows = first.rows();
|
||||
|
||||
// Automatic pruning invalidates the content represented by pins in
|
||||
// the removed page. erasePage remaps them to the next page below but
|
||||
// only the enforcing caller knows that their original content is gone,
|
||||
// so mark them as garbage here.
|
||||
for (pagelist.tracked_pins.keys()) |p| {
|
||||
if (p.node == first) p.garbage = true;
|
||||
}
|
||||
|
||||
// erasePage updates the list, pin targets, and byte accounting.
|
||||
// Row accounting belongs to the caller because erasePage is also used
|
||||
// by paths that already adjusted total_rows.
|
||||
pagelist.erasePage(first);
|
||||
pagelist.total_rows -= first_rows;
|
||||
removed += first_rows;
|
||||
}
|
||||
|
||||
// Reconcile viewport mode and cached row offsets with the combined prefix
|
||||
// removal only after every page and pin points into the final list.
|
||||
if (removed > 0) pagelist.fixupViewport(removed);
|
||||
}
|
||||
|
||||
/// Returns the minimum valid "max size" for a given number of rows and cols
|
||||
/// such that we can fit the active area AND at least two pages. Note we
|
||||
/// need the two pages for algorithms to work properly (such as grow) but
|
||||
/// we don't need to fit double the active area.
|
||||
///
|
||||
/// This min size may not be totally correct in the case that a large
|
||||
/// number of other dimensions makes our row size in a page very small.
|
||||
/// But this gives us a nice fast heuristic for determining min/max size.
|
||||
/// Therefore, if the page size is violated you should always also verify
|
||||
/// that we have enough space for the active area.
|
||||
fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) usize {
|
||||
// Invariant required to ensure our divCeil below cannot overflow.
|
||||
comptime {
|
||||
const max_rows = std.math.maxInt(size.CellCountInt);
|
||||
_ = std.math.divCeil(usize, max_rows, 1) catch unreachable;
|
||||
}
|
||||
|
||||
// Get our capacity to fit our rows. If the cols are too big, it may
|
||||
// force less rows than we want meaning we need more than one page to
|
||||
// represent a viewport.
|
||||
const cap = initialCapacity(cols);
|
||||
|
||||
// Calculate the number of standard sized pages we need to represent
|
||||
// an active area.
|
||||
const pages_exact = if (cap.rows >= rows) 1 else std.math.divCeil(
|
||||
usize,
|
||||
rows,
|
||||
cap.rows,
|
||||
) catch {
|
||||
// Not possible:
|
||||
// - initialCapacity guarantees at least 1 row
|
||||
// - numerator/denominator can't overflow because of comptime check above
|
||||
unreachable;
|
||||
};
|
||||
|
||||
// We always need at least one page extra so that we
|
||||
// can fit partial pages to spread our active area across two pages.
|
||||
// Even for caps that can't fit all rows in a single page, we add one
|
||||
// because the most extra space we need at any given time is only
|
||||
// the partial amount of one page.
|
||||
const pages = pages_exact + 1;
|
||||
assert(pages >= 2);
|
||||
|
||||
// log.debug("minMaxSize cols={} rows={} cap={} pages={}", .{
|
||||
// cols,
|
||||
// rows,
|
||||
// cap,
|
||||
// pages,
|
||||
// });
|
||||
|
||||
return PagePool.item_size * pages;
|
||||
}
|
||||
|
||||
/// Returns the minimum line limit for a given column count. Line limits are
|
||||
/// page-granular, so we always permit at least one standard page worth of
|
||||
/// scrollback rows.
|
||||
fn minMaxLines(cols: size.CellCountInt) usize {
|
||||
return initialCapacity(cols).rows;
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents an exact x/y coordinate within the screen. This is called
|
||||
/// a "pin" because it is a fixed point within the pagelist direct to
|
||||
/// a specific page pointer and memory offset. The benefit is that this
|
||||
@@ -9755,6 +9804,216 @@ test "PageList Cell screenPoint supports long scrollback" {
|
||||
} }, cell.screenPoint());
|
||||
}
|
||||
|
||||
test "PageList set max bytes prunes immediately and can be raised" {
|
||||
const testing = std.testing;
|
||||
const cols: size.CellCountInt = 80;
|
||||
const page_rows: usize = initialCapacity(cols).rows;
|
||||
|
||||
var s = try init(testing.allocator, .{
|
||||
.cols = cols,
|
||||
.rows = 1,
|
||||
.max_size = null,
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
// Build four complete pages of history followed by the active row.
|
||||
try s.growRows(4 * page_rows);
|
||||
try testing.expectEqual(@as(usize, 5), s.totalPages());
|
||||
|
||||
const removed = s.pages.first.?;
|
||||
const retained = s.pages.last.?.prev.?;
|
||||
const removed_pin = try s.trackPin(.{ .node = removed });
|
||||
defer s.untrackPin(removed_pin);
|
||||
const retained_pin = try s.trackPin(.{ .node = retained });
|
||||
defer s.untrackPin(retained_pin);
|
||||
|
||||
s.scroll(.{ .pin = retained_pin.* });
|
||||
try testing.expectEqual(3 * page_rows, s.scrollbar().offset);
|
||||
|
||||
// The active-area minimum is two pages. Lowering below that immediately
|
||||
// removes all older complete historical pages.
|
||||
s.setMaxBytes(PagePool.item_size);
|
||||
try testing.expectEqual(PagePool.item_size, s.limits.bytes.explicit);
|
||||
try testing.expectEqual(2 * PagePool.item_size, s.limits.max(.bytes));
|
||||
try testing.expectEqual(s.limits.max(.bytes), s.page_size);
|
||||
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||
try testing.expectEqual(page_rows, s.total_rows - s.rows);
|
||||
try testing.expectEqual(retained, s.pages.first.?);
|
||||
try testing.expectEqual(retained, removed_pin.node);
|
||||
try testing.expect(removed_pin.garbage);
|
||||
try testing.expectEqual(retained, retained_pin.node);
|
||||
try testing.expect(!retained_pin.garbage);
|
||||
try testing.expectEqual(@as(usize, 0), s.scrollbar().offset);
|
||||
|
||||
// Raising the limit doesn't allocate or otherwise change retained data,
|
||||
// but subsequent growth can exceed the previous effective limit.
|
||||
const limited_size = s.page_size;
|
||||
const limited_rows = s.total_rows;
|
||||
s.setMaxBytes(8 * PagePool.item_size);
|
||||
try testing.expectEqual(limited_size, s.page_size);
|
||||
try testing.expectEqual(limited_rows, s.total_rows);
|
||||
try s.growRows(2 * page_rows);
|
||||
try testing.expect(s.page_size > limited_size);
|
||||
|
||||
// Null restores unlimited growth and likewise preserves current data.
|
||||
const raised_size = s.page_size;
|
||||
const raised_rows = s.total_rows;
|
||||
s.setMaxBytes(null);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
s.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expectEqual(raised_size, s.page_size);
|
||||
try testing.expectEqual(raised_rows, s.total_rows);
|
||||
try s.growRows(5 * page_rows);
|
||||
try testing.expect(s.page_size > 8 * PagePool.item_size);
|
||||
}
|
||||
|
||||
test "PageList set max bytes zero preserves active boundary" {
|
||||
const testing = std.testing;
|
||||
|
||||
var s = try init(testing.allocator, .{
|
||||
.cols = 80,
|
||||
.rows = 1,
|
||||
.max_size = null,
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
// Make the sole page larger than the effective zero-byte limit. Its first
|
||||
// row will be history, but the same indivisible page also contains active.
|
||||
while (s.page_size <= s.limits.bytes.min) {
|
||||
_ = try s.increaseCapacity(s.pages.first.?, .grapheme_bytes);
|
||||
}
|
||||
_ = try s.grow();
|
||||
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||
try testing.expectEqual(s.pages.first.?, s.getTopLeft(.active).node);
|
||||
try testing.expect(s.getTopLeft(.active).y > 0);
|
||||
|
||||
s.scroll(.top);
|
||||
try testing.expect(s.viewport == .top);
|
||||
|
||||
s.setMaxBytes(0);
|
||||
try testing.expectEqual(@as(usize, 0), s.limits.bytes.explicit);
|
||||
try testing.expect(s.page_size > s.limits.max(.bytes));
|
||||
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||
try testing.expect(s.viewport == .active);
|
||||
try testing.expectEqual(Scrollbar{
|
||||
.total = s.rows,
|
||||
.offset = 0,
|
||||
.len = s.rows,
|
||||
}, s.scrollbar());
|
||||
|
||||
// No-scrollback mode cannot be moved back into the retained boundary row.
|
||||
s.scroll(.top);
|
||||
try testing.expect(s.viewport == .active);
|
||||
}
|
||||
|
||||
test "PageList set max lines prunes immediately and can be raised" {
|
||||
const testing = std.testing;
|
||||
const cols: size.CellCountInt = 80;
|
||||
const page_rows: usize = initialCapacity(cols).rows;
|
||||
const lowered_lines = page_rows + page_rows / 2;
|
||||
|
||||
var s = try init(testing.allocator, .{
|
||||
.cols = cols,
|
||||
.rows = 1,
|
||||
.max_size = null,
|
||||
.max_lines = null,
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
try s.growRows(4 * page_rows);
|
||||
try testing.expectEqual(@as(usize, 5), s.totalPages());
|
||||
|
||||
const removed = s.pages.first.?;
|
||||
const retained = s.pages.last.?.prev.?;
|
||||
const removed_pin = try s.trackPin(.{ .node = removed });
|
||||
defer s.untrackPin(removed_pin);
|
||||
const retained_pin = try s.trackPin(.{ .node = retained });
|
||||
defer s.untrackPin(retained_pin);
|
||||
|
||||
s.scroll(.{ .pin = retained_pin.* });
|
||||
try testing.expectEqual(3 * page_rows, s.scrollbar().offset);
|
||||
|
||||
// Whole-page enforcement undershoots a non-page-aligned line limit.
|
||||
s.setMaxLines(lowered_lines);
|
||||
try testing.expectEqual(lowered_lines, s.limits.lines.explicit);
|
||||
try testing.expectEqual(lowered_lines, s.limits.max(.lines));
|
||||
try testing.expectEqual(page_rows, s.total_rows - s.rows);
|
||||
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||
try testing.expectEqual(retained, s.pages.first.?);
|
||||
try testing.expectEqual(retained, removed_pin.node);
|
||||
try testing.expect(removed_pin.garbage);
|
||||
try testing.expectEqual(retained, retained_pin.node);
|
||||
try testing.expect(!retained_pin.garbage);
|
||||
try testing.expectEqual(@as(usize, 0), s.scrollbar().offset);
|
||||
|
||||
const limited_size = s.page_size;
|
||||
const limited_rows = s.total_rows;
|
||||
s.setMaxLines(4 * page_rows);
|
||||
try testing.expectEqual(limited_size, s.page_size);
|
||||
try testing.expectEqual(limited_rows, s.total_rows);
|
||||
try s.growRows(2 * page_rows);
|
||||
try testing.expect(s.total_rows - s.rows > lowered_lines);
|
||||
|
||||
const raised_size = s.page_size;
|
||||
const raised_rows = s.total_rows;
|
||||
s.setMaxLines(null);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
s.limits.lines.explicit,
|
||||
);
|
||||
try testing.expectEqual(raised_size, s.page_size);
|
||||
try testing.expectEqual(raised_rows, s.total_rows);
|
||||
try s.growRows(3 * page_rows);
|
||||
try testing.expect(s.total_rows - s.rows > 4 * page_rows);
|
||||
}
|
||||
|
||||
test "PageList set max limits remain independent" {
|
||||
const testing = std.testing;
|
||||
const cols: size.CellCountInt = 80;
|
||||
const page_rows: usize = initialCapacity(cols).rows;
|
||||
const byte_limit = 3 * PagePool.item_size;
|
||||
const line_limit = page_rows / 2;
|
||||
|
||||
var s = try init(testing.allocator, .{
|
||||
.cols = cols,
|
||||
.rows = 1,
|
||||
.max_size = null,
|
||||
.max_lines = null,
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
try s.growRows(4 * page_rows);
|
||||
|
||||
// The byte setter leaves the line limit unlimited.
|
||||
s.setMaxBytes(byte_limit);
|
||||
try testing.expectEqual(byte_limit, s.limits.bytes.explicit);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
s.limits.lines.explicit,
|
||||
);
|
||||
try testing.expectEqual(@as(usize, 3), s.totalPages());
|
||||
try testing.expectEqual(2 * page_rows, s.total_rows - s.rows);
|
||||
|
||||
// The smaller runtime line limit prunes one more complete page without
|
||||
// changing the configured byte limit. Its effective value is raised to
|
||||
// the existing one-page minimum.
|
||||
s.setMaxLines(line_limit);
|
||||
try testing.expectEqual(byte_limit, s.limits.bytes.explicit);
|
||||
try testing.expectEqual(line_limit, s.limits.lines.explicit);
|
||||
try testing.expectEqual(page_rows, s.limits.max(.lines));
|
||||
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||
try testing.expectEqual(page_rows, s.total_rows - s.rows);
|
||||
|
||||
// Removing only the line limit leaves byte enforcement in effect.
|
||||
s.setMaxLines(null);
|
||||
try s.growRows(3 * page_rows);
|
||||
try testing.expectEqual(byte_limit, s.page_size);
|
||||
try testing.expectEqual(@as(usize, 3), s.totalPages());
|
||||
try testing.expect(s.total_rows - s.rows > page_rows);
|
||||
}
|
||||
|
||||
test "PageList max lines uses one-page minimum" {
|
||||
const testing = std.testing;
|
||||
const cols: size.CellCountInt = 80;
|
||||
@@ -9767,7 +10026,7 @@ test "PageList max lines uses one-page minimum" {
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
try testing.expectEqual(page_rows, s.maxLines());
|
||||
try testing.expectEqual(page_rows, s.limits.max(.lines));
|
||||
|
||||
// The requested limit is below one page, so a complete page of history
|
||||
// remains valid.
|
||||
@@ -9803,7 +10062,7 @@ test "PageList max lines does not round larger limits" {
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
try testing.expectEqual(max_lines, s.maxLines());
|
||||
try testing.expectEqual(max_lines, s.limits.max(.lines));
|
||||
try s.growRows(max_lines);
|
||||
try testing.expectEqual(max_lines, s.total_rows - s.rows);
|
||||
|
||||
@@ -9854,9 +10113,11 @@ test "PageList max lines and max size enforce the smaller limit" {
|
||||
defer s.deinit();
|
||||
|
||||
try s.growRows(4 * page_rows);
|
||||
try testing.expect(s.total_rows - s.rows <= s.maxLines());
|
||||
try testing.expect(
|
||||
s.total_rows - s.rows <= s.limits.max(.lines),
|
||||
);
|
||||
try testing.expect(s.totalPages() <= 2);
|
||||
try testing.expect(s.page_size < s.maxSize());
|
||||
try testing.expect(s.page_size < s.limits.max(.bytes));
|
||||
}
|
||||
|
||||
// A two-page byte limit prunes before the larger line limit is reached.
|
||||
@@ -9870,8 +10131,10 @@ test "PageList max lines and max size enforce the smaller limit" {
|
||||
defer s.deinit();
|
||||
|
||||
try s.growRows(2 * page_rows);
|
||||
try testing.expect(s.total_rows - s.rows < s.maxLines());
|
||||
try testing.expectEqual(s.maxSize(), s.page_size);
|
||||
try testing.expect(
|
||||
s.total_rows - s.rows < s.limits.max(.lines),
|
||||
);
|
||||
try testing.expectEqual(s.limits.max(.bytes), s.page_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9903,7 +10166,10 @@ test "PageList max lines applies to resize and clone" {
|
||||
|
||||
const new_cols: size.CellCountInt = cols + 1;
|
||||
try s.resize(.{ .cols = new_cols, .reflow = true });
|
||||
try testing.expectEqual(minMaxLines(new_cols), s.min_max_lines);
|
||||
try testing.expectEqual(
|
||||
Limits.minMaxLines(new_cols),
|
||||
s.limits.lines.min,
|
||||
);
|
||||
|
||||
// Exercise the same active-row shrink through the reflow path. Reflow
|
||||
// completes before the newly historical complete page is pruned.
|
||||
@@ -9927,9 +10193,13 @@ test "PageList max lines applies to resize and clone" {
|
||||
.rows = 1,
|
||||
.reflow = true,
|
||||
});
|
||||
try testing.expectEqual(minMaxLines(new_cols), reflowed.min_max_lines);
|
||||
try testing.expectEqual(
|
||||
Limits.minMaxLines(new_cols),
|
||||
reflowed.limits.lines.min,
|
||||
);
|
||||
try testing.expect(
|
||||
reflowed.total_rows - reflowed.rows <= reflowed.maxLines() or
|
||||
reflowed.total_rows - reflowed.rows <=
|
||||
reflowed.limits.max(.lines) or
|
||||
reflowed.pages.first.? ==
|
||||
reflowed.getTopLeft(.active).node,
|
||||
);
|
||||
@@ -9940,13 +10210,11 @@ test "PageList max lines applies to resize and clone" {
|
||||
});
|
||||
defer cloned.deinit();
|
||||
|
||||
try testing.expectEqual(s.explicit_max_lines, cloned.explicit_max_lines);
|
||||
try testing.expectEqual(s.min_max_lines, cloned.min_max_lines);
|
||||
try testing.expectEqual(s.maxLines(), cloned.maxLines());
|
||||
try testing.expectEqual(s.limits, cloned.limits);
|
||||
|
||||
try cloned.growRows(2 * cloned.maxLines());
|
||||
try cloned.growRows(2 * cloned.limits.max(.lines));
|
||||
try testing.expect(
|
||||
cloned.total_rows - cloned.rows <= cloned.maxLines() or
|
||||
cloned.total_rows - cloned.rows <= cloned.limits.max(.lines) or
|
||||
cloned.pages.first.? == cloned.getTopLeft(.active).node,
|
||||
);
|
||||
}
|
||||
@@ -17142,7 +17410,7 @@ test "PageList grow reuses non-standard page without leak" {
|
||||
try testing.expect(s.pages.first != s.pages.last);
|
||||
|
||||
// Continue growing until we exceed max_size AND the last page is full
|
||||
while (s.page_size + PagePool.item_size <= s.maxSize() or
|
||||
while (s.page_size + PagePool.item_size <= s.limits.max(.bytes) or
|
||||
s.pages.last.?.rows() < s.pages.last.?.capacity().rows)
|
||||
{
|
||||
_ = try s.grow();
|
||||
@@ -17242,7 +17510,9 @@ test "PageList grow non-standard page prune protection" {
|
||||
|
||||
// Verify prune path conditions are met
|
||||
try testing.expect(s.pages.first != s.pages.last);
|
||||
try testing.expect(s.page_size + PagePool.item_size > s.maxSize());
|
||||
try testing.expect(
|
||||
s.page_size + PagePool.item_size > s.limits.max(.bytes),
|
||||
);
|
||||
try testing.expect(s.totalRows() >= s.rows);
|
||||
|
||||
// Verify last page is at capacity (so grow must prune or allocate new)
|
||||
|
||||
@@ -3796,9 +3796,9 @@ test "Screen forwards optional scrollback limits" {
|
||||
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
s.pages.explicit_max_size,
|
||||
s.pages.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expectEqual(max_lines, s.pages.explicit_max_lines);
|
||||
try testing.expectEqual(max_lines, s.pages.limits.lines.explicit);
|
||||
try testing.expect(!s.no_scrollback);
|
||||
}
|
||||
|
||||
|
||||
@@ -442,6 +442,29 @@ pub fn gpa(self: *Terminal) Allocator {
|
||||
return self.screens.active.alloc;
|
||||
}
|
||||
|
||||
/// Change the primary screen's maximum scrollback allocation in bytes.
|
||||
///
|
||||
/// Null removes the byte limit and zero disables scrollback. Disabling
|
||||
/// scrollback also immediately erases retained history and changes future
|
||||
/// scrolling to use the no-scrollback path. The alternate screen is
|
||||
/// intentionally unaffected because it never retains scrollback.
|
||||
pub fn setScrollbackMaxBytes(self: *Terminal, max: ?usize) void {
|
||||
const primary = self.screens.get(.primary).?;
|
||||
primary.pages.setMaxBytes(max);
|
||||
primary.no_scrollback = max == 0;
|
||||
|
||||
if (primary.no_scrollback) primary.eraseHistory(null);
|
||||
}
|
||||
|
||||
/// Change the primary screen's maximum number of physical scrollback lines.
|
||||
///
|
||||
/// Null removes the line limit. The alternate screen is intentionally
|
||||
/// unaffected because it never retains scrollback.
|
||||
pub fn setScrollbackMaxLines(self: *Terminal, max: ?usize) void {
|
||||
const primary = self.screens.get(.primary).?;
|
||||
primary.pages.setMaxLines(max);
|
||||
}
|
||||
|
||||
/// Print UTF-8 encoded string to the terminal.
|
||||
pub fn printString(self: *Terminal, str: []const u8) !void {
|
||||
const view = try std.unicode.Utf8View.init(str);
|
||||
@@ -3900,14 +3923,138 @@ test "Terminal forwards optional scrollback limits" {
|
||||
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
t.screens.active.pages.explicit_max_size,
|
||||
t.screens.active.pages.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
max_lines,
|
||||
t.screens.active.pages.explicit_max_lines,
|
||||
t.screens.active.pages.limits.lines.explicit,
|
||||
);
|
||||
}
|
||||
|
||||
test "Terminal setScrollbackMaxBytes" {
|
||||
var t = try init(testing.io, testing.allocator, .{
|
||||
.cols = 80,
|
||||
.rows = 3,
|
||||
.max_scrollback_bytes = null,
|
||||
});
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
const primary = t.screens.get(.primary).?;
|
||||
const page_rows: usize = primary.pages.pages.first.?.capacity().rows;
|
||||
|
||||
// Build several complete pages of history so lowering the byte limit has
|
||||
// existing allocations to prune immediately.
|
||||
for (0..4 * page_rows) |_| try t.linefeed();
|
||||
const old_page_size = primary.pages.page_size;
|
||||
t.setScrollbackMaxBytes(1);
|
||||
try testing.expectEqual(@as(usize, 1), primary.pages.limits.bytes.explicit);
|
||||
try testing.expect(primary.pages.page_size < old_page_size);
|
||||
try testing.expect(
|
||||
primary.pages.page_size <= primary.pages.limits.max(.bytes),
|
||||
);
|
||||
try testing.expect(!primary.no_scrollback);
|
||||
|
||||
// Zero switches Screen behavior as well as PageList accounting, discards
|
||||
// all retained history, and prevents future linefeeds from recreating it.
|
||||
t.setScrollbackMaxBytes(0);
|
||||
try testing.expectEqual(@as(usize, 0), primary.pages.limits.bytes.explicit);
|
||||
try testing.expect(primary.no_scrollback);
|
||||
try testing.expectEqual(
|
||||
@as(usize, primary.pages.rows),
|
||||
primary.pages.total_rows,
|
||||
);
|
||||
try testing.expect(primary.pages.viewport == .active);
|
||||
|
||||
for (0..page_rows) |_| try t.linefeed();
|
||||
try testing.expectEqual(
|
||||
@as(usize, primary.pages.rows),
|
||||
primary.pages.total_rows,
|
||||
);
|
||||
|
||||
// Re-enabling unlimited scrollback affects subsequent output.
|
||||
t.setScrollbackMaxBytes(null);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
primary.pages.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expect(!primary.no_scrollback);
|
||||
for (0..page_rows) |_| try t.linefeed();
|
||||
try testing.expect(primary.pages.total_rows > primary.pages.rows);
|
||||
}
|
||||
|
||||
test "Terminal setScrollbackMaxLines" {
|
||||
var t = try init(testing.io, testing.allocator, .{
|
||||
.cols = 80,
|
||||
.rows = 3,
|
||||
.max_scrollback_bytes = null,
|
||||
.max_scrollback_lines = null,
|
||||
});
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
const primary = t.screens.get(.primary).?;
|
||||
const page_rows: usize = primary.pages.pages.first.?.capacity().rows;
|
||||
|
||||
for (0..4 * page_rows) |_| try t.linefeed();
|
||||
const old_total_rows = primary.pages.total_rows;
|
||||
t.setScrollbackMaxLines(page_rows);
|
||||
try testing.expectEqual(
|
||||
page_rows,
|
||||
primary.pages.limits.lines.explicit,
|
||||
);
|
||||
try testing.expect(primary.pages.total_rows < old_total_rows);
|
||||
try testing.expect(
|
||||
!primary.pages.limits.exceeded(&primary.pages, .lines),
|
||||
);
|
||||
|
||||
const limited_total_rows = primary.pages.total_rows;
|
||||
t.setScrollbackMaxLines(null);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
primary.pages.limits.lines.explicit,
|
||||
);
|
||||
try testing.expectEqual(limited_total_rows, primary.pages.total_rows);
|
||||
for (0..3 * page_rows) |_| try t.linefeed();
|
||||
try testing.expect(
|
||||
primary.pages.total_rows - primary.pages.rows > page_rows,
|
||||
);
|
||||
}
|
||||
|
||||
test "Terminal setScrollback only affects primary screen" {
|
||||
var t = try init(testing.io, testing.allocator, .{
|
||||
.cols = 80,
|
||||
.rows = 3,
|
||||
});
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
_ = try t.switchScreen(.alternate);
|
||||
const primary = t.screens.get(.primary).?;
|
||||
const alternate = t.screens.get(.alternate).?;
|
||||
|
||||
t.setScrollbackMaxBytes(123);
|
||||
t.setScrollbackMaxLines(456);
|
||||
|
||||
try testing.expectEqual(
|
||||
@as(usize, 123),
|
||||
primary.pages.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
@as(usize, 456),
|
||||
primary.pages.limits.lines.explicit,
|
||||
);
|
||||
try testing.expect(!primary.no_scrollback);
|
||||
|
||||
try testing.expectEqual(
|
||||
@as(usize, 0),
|
||||
alternate.pages.limits.bytes.explicit,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
alternate.pages.limits.lines.explicit,
|
||||
);
|
||||
try testing.expect(alternate.no_scrollback);
|
||||
try testing.expectEqual(alternate, t.screens.active);
|
||||
}
|
||||
|
||||
test "Terminal: resize resets synchronized output" {
|
||||
const alloc = testing.allocator;
|
||||
const io_impl = testing.io;
|
||||
@@ -4325,7 +4472,7 @@ pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen {
|
||||
.cols = self.cols,
|
||||
.rows = self.rows,
|
||||
.max_scrollback_bytes = switch (key) {
|
||||
.primary => primary.pages.explicit_max_size,
|
||||
.primary => primary.pages.limits.bytes.explicit,
|
||||
.alternate => 0,
|
||||
},
|
||||
|
||||
|
||||
@@ -223,7 +223,8 @@ test "terminal_new/free" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -258,7 +259,8 @@ test "format plain" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -284,7 +286,8 @@ test "format reflects terminal changes" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -316,7 +319,8 @@ test "format null returns required size" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -348,7 +352,8 @@ test "format buffer too small" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -381,7 +386,8 @@ test "format vt" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -408,7 +414,8 @@ test "format plain with selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -452,7 +459,8 @@ test "format html" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
|
||||
@@ -205,7 +205,8 @@ test "grid_ref_hyperlink_uri no hyperlink" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -228,7 +229,8 @@ test "grid_ref_hyperlink_uri with hyperlink" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
|
||||
@@ -96,7 +96,8 @@ test "tracked_grid_ref snapshots after terminal scroll" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -128,7 +129,8 @@ test "tracked_grid_ref reports no value after reset" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -154,7 +156,8 @@ test "tracked_grid_ref reports no value after alternate screen reset" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -194,7 +197,8 @@ test "tracked_grid_ref reports no value after terminal free" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
|
||||
terminal_c.vt_write(terminal, "A", 1);
|
||||
|
||||
@@ -257,7 +257,8 @@ test "setopt_from_terminal" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -283,7 +284,8 @@ test "setopt_from_terminal null" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
setopt_from_terminal(null, t);
|
||||
|
||||
@@ -659,7 +659,8 @@ test "placement_iterator next on empty storage" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -688,7 +689,8 @@ test "placement_iterator get before next returns invalid" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -719,7 +721,8 @@ test "placement_iterator with transmit and display" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -772,7 +775,8 @@ test "placement_iterator with multiple placements" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -829,7 +833,8 @@ test "placement_iterator_set layer filter" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -919,7 +924,8 @@ test "image_get_handle returns null for missing id" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -940,7 +946,8 @@ test "image_get_handle and image_get with transmitted image" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -990,7 +997,8 @@ test "placement_rect with transmit and display" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1051,7 +1059,8 @@ test "placement_pixel_size with transmit and display" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1106,7 +1115,8 @@ test "placement_grid_size with transmit and display" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1160,7 +1170,8 @@ test "placement_viewport_pos with transmit and display" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1205,7 +1216,8 @@ test "placement_viewport_pos fully off-screen above" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
|
||||
80,
|
||||
5,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
@@ -1244,7 +1256,8 @@ test "placement_viewport_pos top off-screen" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
|
||||
80,
|
||||
5,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
@@ -1287,7 +1300,8 @@ test "placement_viewport_pos bottom off-screen" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 0 },
|
||||
80,
|
||||
5,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
@@ -1327,7 +1341,8 @@ test "placement_viewport_pos top and bottom off-screen" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
|
||||
80,
|
||||
5,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
@@ -1378,7 +1393,8 @@ test "placement_source_rect defaults to full image" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1417,7 +1433,8 @@ test "placement_source_rect with explicit source rect" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1461,7 +1478,8 @@ test "placement_source_rect clamps to image bounds" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1522,7 +1540,8 @@ test "placement_render_info returns all fields" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1561,7 +1580,8 @@ test "placement_render_info off-screen sets viewport_visible false" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 5, .max_scrollback = 100 },
|
||||
80,
|
||||
5,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 5, 10, 20));
|
||||
@@ -1610,7 +1630,8 @@ test "image_get_multi success" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1664,7 +1685,8 @@ test "placement_get_multi success" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
try testing.expectEqual(Result.success, terminal_c.resize(t, 80, 24, 10, 20));
|
||||
@@ -1722,7 +1744,8 @@ test "storage generation via get" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1774,7 +1797,8 @@ test "image generation detects same-sized retransmission" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1835,7 +1859,8 @@ test "image generation via image_get_multi" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1871,7 +1896,8 @@ test "image compression and format always report decoded data" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -1917,7 +1943,8 @@ test "generation never recurs across resets and screen switches" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
|
||||
@@ -324,7 +324,8 @@ test "setopt_from_terminal" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -346,7 +347,8 @@ test "setopt_from_terminal null" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
setopt_from_terminal(null, t);
|
||||
|
||||
@@ -827,11 +827,8 @@ test "render: begin/end update" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 10,
|
||||
.rows = 3,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -952,11 +949,8 @@ test "render: row iterator new/free" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1008,11 +1002,8 @@ test "render: row get invalid data" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1047,11 +1038,8 @@ test "render: row set before iteration" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1081,11 +1069,8 @@ test "render: row get before iteration" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1115,11 +1100,8 @@ test "render: row get/set dirty" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1173,11 +1155,8 @@ test "render: row get selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 10,
|
||||
.rows = 3,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1227,11 +1206,8 @@ test "render: row cells get selected" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 10,
|
||||
.rows = 3,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1310,11 +1286,8 @@ test "render: row cells get has_styling" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 10,
|
||||
.rows = 3,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1390,7 +1363,8 @@ test "render: row cells get graphemes utf8" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 10, .rows = 3, .max_scrollback = 10_000 },
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1447,7 +1421,8 @@ test "render: row cells get graphemes utf8" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 10, .rows = 3, .max_scrollback = 10_000 },
|
||||
10,
|
||||
3,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1493,11 +1468,8 @@ test "render: row iterator next" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1543,11 +1515,8 @@ test "render: update" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1576,11 +1545,8 @@ test "render: colors get" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1618,11 +1584,8 @@ test "render: row cells bg_color no background" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1668,11 +1631,8 @@ test "render: row cells bg_color from style" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1720,11 +1680,8 @@ test "render: row cells bg_color from content tag" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1774,11 +1731,8 @@ test "render: row cells fg_color no foreground" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1824,11 +1778,8 @@ test "render: row cells fg_color from style" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1876,11 +1827,8 @@ test "render: colors get supports truncated sized struct" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1911,7 +1859,8 @@ test "render: get_multi success" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1947,7 +1896,8 @@ test "render: row_get_multi success" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1990,7 +1940,8 @@ test "render: row_cells_get_multi success" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
|
||||
@@ -397,7 +397,8 @@ test "selection_format_alloc uses active selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -457,7 +458,8 @@ test "selection_format_buf uses provided selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
@@ -513,7 +515,8 @@ test "selection_format_alloc returns no_value without active selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
80,
|
||||
24,
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
|
||||
@@ -752,7 +752,8 @@ test "selection gesture lifecycle and get" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -788,7 +789,8 @@ test "selection gesture get_multi" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -832,7 +834,8 @@ test "selection gesture get_multi returns first failing index" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -944,7 +947,8 @@ test "selection gesture event applies press" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -991,7 +995,8 @@ test "selection gesture event press requires ref" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1019,7 +1024,8 @@ test "selection gesture event null output still reports no selection" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1053,7 +1059,8 @@ test "selection gesture event applies release" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1104,7 +1111,8 @@ test "selection gesture release without ref marks dragged" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1151,7 +1159,8 @@ test "selection gesture event applies drag" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1223,7 +1232,8 @@ test "selection gesture drag requires ref and geometry" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1267,7 +1277,8 @@ test "selection gesture event applies autoscroll tick" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1350,7 +1361,8 @@ test "selection gesture autoscroll tick requires viewport and geometry" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1389,7 +1401,8 @@ test "selection gesture event applies deep press" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
@@ -1444,7 +1457,8 @@ test "selection gesture deep press without active anchor returns no value" {
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||
5,
|
||||
2,
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,6 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||
.{ "GhosttySurfacePosition", StructInfo.init(SurfacePosition) },
|
||||
.{ "GhosttyStyle", StructInfo.init(style_c.Style) },
|
||||
.{ "GhosttyStyleColor", StructInfo.init(style_c.Color) },
|
||||
.{ "GhosttyTerminalOptions", StructInfo.init(terminal.Options) },
|
||||
.{ "GhosttyTerminalScrollbar", StructInfo.init(terminal.TerminalScrollbar) },
|
||||
.{ "GhosttyTerminalScrollViewport", StructInfo.init(terminal.ScrollViewport) },
|
||||
});
|
||||
@@ -216,7 +215,6 @@ test "json parses" {
|
||||
// Verify we have all expected structs
|
||||
try std.testing.expect(root.contains("GhosttyClipboardContent"));
|
||||
try std.testing.expect(root.contains("GhosttyClipboardWrite"));
|
||||
try std.testing.expect(root.contains("GhosttyTerminalOptions"));
|
||||
try std.testing.expect(root.contains("GhosttyFormatterTerminalOptions"));
|
||||
|
||||
const clipboard_content = root.get("GhosttyClipboardContent").?.object;
|
||||
@@ -231,20 +229,7 @@ test "json parses" {
|
||||
try std.testing.expect(clipboard_write_fields.contains("contents"));
|
||||
try std.testing.expect(clipboard_write_fields.contains("contents_len"));
|
||||
|
||||
// Verify GhosttyTerminalOptions fields
|
||||
const term_opts = root.get("GhosttyTerminalOptions").?.object;
|
||||
try std.testing.expect(term_opts.contains("size"));
|
||||
try std.testing.expect(term_opts.contains("align"));
|
||||
try std.testing.expect(term_opts.contains("fields"));
|
||||
|
||||
const fields = term_opts.get("fields").?.object;
|
||||
try std.testing.expect(fields.contains("cols"));
|
||||
try std.testing.expect(fields.contains("rows"));
|
||||
try std.testing.expect(fields.contains("max_scrollback"));
|
||||
|
||||
// Verify field offsets make sense (cols should be at 0)
|
||||
const cols = fields.get("cols").?.object;
|
||||
try std.testing.expectEqual(0, cols.get("offset").?.integer);
|
||||
try std.testing.expect(!root.contains("GhosttyTerminalOptions"));
|
||||
}
|
||||
|
||||
test "struct sizes are non-zero" {
|
||||
|
||||
Reference in New Issue
Block a user