terminal: make stream processing infallible

The terminal.Stream next/nextSlice functions can now no longer fail.
All prior failure modes were fully isolated in the handler `vt`
callbacks. As such, vt callbacks are now required to not return an error
and handle their own errors somehow.

Allowing streams to be fallible before was an incorrect design. It
caused problematic scenarios like in `nextSlice` early terminating
processing due to handler errors. This should not be possible.

There is no safe way to bubble up vt errors through the stream because
if nextSlice is called and multiple errors are returned, we can't
coalesce them. We could modify that to return a partial result but its
just more work for stream that is unnecessary. The handler can do all of
this.

This work was discovered due to cleanups to prepare for more C APIs.
Less errors make C APIs easier to implement! And, it helps clean up our
Zig, too.
This commit is contained in:
Mitchell Hashimoto
2026-03-13 13:14:03 -07:00
parent 04fa71e237
commit 2044e5030f
23 changed files with 791 additions and 782 deletions

View File

@@ -23,8 +23,8 @@ pub fn main() !void {
// Replace \n with \r\n
for (buf[0..n]) |byte| {
if (byte == '\n') try stream.next('\r');
try stream.next(byte);
if (byte == '\n') stream.next('\r');
stream.next(byte);
}
}

View File

@@ -14,24 +14,24 @@ pub fn main() !void {
defer stream.deinit();
// Basic text with newline
try stream.nextSlice("Hello, World!\r\n");
stream.nextSlice("Hello, World!\r\n");
// ANSI color codes: ESC[1;32m = bold green, ESC[0m = reset
try stream.nextSlice("\x1b[1;32mGreen Text\x1b[0m\r\n");
stream.nextSlice("\x1b[1;32mGreen Text\x1b[0m\r\n");
// Cursor positioning: ESC[1;1H = move to row 1, column 1
try stream.nextSlice("\x1b[1;1HTop-left corner\r\n");
stream.nextSlice("\x1b[1;1HTop-left corner\r\n");
// Cursor movement: ESC[5B = move down 5 lines
try stream.nextSlice("\x1b[5B");
try stream.nextSlice("Moved down!\r\n");
stream.nextSlice("\x1b[5B");
stream.nextSlice("Moved down!\r\n");
// Erase line: ESC[2K = clear entire line
try stream.nextSlice("\x1b[2K");
try stream.nextSlice("New content\r\n");
stream.nextSlice("\x1b[2K");
stream.nextSlice("New content\r\n");
// Multiple lines
try stream.nextSlice("Line A\r\nLine B\r\nLine C\r\n");
stream.nextSlice("Line A\r\nLine B\r\nLine C\r\n");
// Get the final terminal state as a plain string
const str = try t.plainString(alloc);

View File

@@ -94,9 +94,9 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
// Force a style on every single row, which
var s = self.terminal.vtStream();
defer s.deinit();
s.nextSlice("\x1b[48;2;20;40;60m") catch unreachable;
for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n") catch unreachable;
s.nextSlice("hello") catch unreachable;
s.nextSlice("\x1b[48;2;20;40;60m");
for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n");
s.nextSlice("hello");
// Setup our terminal state
const data_f: std.fs.File = (options.dataFile(
@@ -120,10 +120,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
stream.nextSlice(buf[0..n]) catch |err| {
log.warn("error processing data file chunk err={}", .{err});
return error.BenchmarkFailed;
};
stream.nextSlice(buf[0..n]);
}
}

View File

@@ -125,10 +125,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
self.stream.nextSlice(buf[0..n]) catch |err| {
log.warn("error processing data file chunk err={}", .{err});
return error.BenchmarkFailed;
};
self.stream.nextSlice(buf[0..n]);
}
}
@@ -142,9 +139,11 @@ const Handler = struct {
self: *Handler,
comptime action: Stream.Action.Tag,
value: Stream.Action.Value(action),
) !void {
) void {
switch (action) {
.print => try self.t.print(value.cp),
.print => self.t.print(value.cp) catch |err| {
log.warn("error processing benchmark print err={}", .{err});
},
else => {},
}
}

View File

@@ -850,7 +850,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD");
s.nextSlice("ABCD");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -874,7 +874,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD EFG");
s.nextSlice("ABCD EFG");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -897,7 +897,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A😃D");
s.nextSlice("A😃D");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -922,7 +922,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(bad);
s.nextSlice(bad);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -955,8 +955,8 @@ test "run iterator: empty cells with background set" {
var s = t.vtStream();
defer s.deinit();
// Set red background
try s.nextSlice("\x1b[48;2;255;0;0m");
try s.nextSlice("A");
s.nextSlice("\x1b[48;2;255;0;0m");
s.nextSlice("A");
// Get our first row
{
@@ -1014,7 +1014,7 @@ test "shape" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1053,7 +1053,7 @@ test "shape nerd fonts" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1086,7 +1086,7 @@ test "shape inconsolata ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">=");
s.nextSlice(">=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1115,7 +1115,7 @@ test "shape inconsolata ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("===");
s.nextSlice("===");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1152,7 +1152,7 @@ test "shape monaspace ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("===");
s.nextSlice("===");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1190,7 +1190,7 @@ test "shape left-replaced lig in last run" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("!==");
s.nextSlice("!==");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1228,7 +1228,7 @@ test "shape left-replaced lig in early run" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("!==X");
s.nextSlice("!==X");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1263,7 +1263,7 @@ test "shape U+3C9 with JB Mono" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("\u{03C9} foo");
s.nextSlice("\u{03C9} foo");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1300,7 +1300,7 @@ test "shape emoji width" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("👍");
s.nextSlice("👍");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1390,7 +1390,7 @@ test "shape variation selector VS15" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1429,7 +1429,7 @@ test "shape variation selector VS16" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1463,9 +1463,9 @@ test "shape with empty cells in between" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A");
try s.nextSlice("\x1b[5C"); // 5 spaces forward
try s.nextSlice("B");
s.nextSlice("A");
s.nextSlice("\x1b[5C"); // 5 spaces forward
s.nextSlice("B");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1510,7 +1510,7 @@ test "shape Combining characters" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1560,7 +1560,7 @@ test "shape Devanagari string" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("अपार्टमेंट");
s.nextSlice("अपार्टमेंट");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1619,7 +1619,7 @@ test "shape Tai Tham vowels (position differs from advance)" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1680,7 +1680,7 @@ test "shape Tai Tham letters (position.y differs from advance)" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1740,7 +1740,7 @@ test "shape Javanese ligatures" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1803,7 +1803,7 @@ test "shape Chakma vowel sign with ligature (vowel sign renders first)" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1874,7 +1874,7 @@ test "shape Bengali ligatures with out of order vowels" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1929,7 +1929,7 @@ test "shape box glyphs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1967,7 +1967,7 @@ test "shape selection boundary" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("a1b2c3d4e5");
s.nextSlice("a1b2c3d4e5");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2072,7 +2072,7 @@ test "shape cursor boundary" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("a1b2c3d4e5");
s.nextSlice("a1b2c3d4e5");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2209,7 +2209,7 @@ test "shape cursor boundary and colored emoji" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("👍🏼");
s.nextSlice("👍🏼");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2306,7 +2306,7 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">=");
s.nextSlice(">=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2332,9 +2332,9 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">");
try s.nextSlice("\x1b[1m"); // Bold
try s.nextSlice("=");
s.nextSlice(">");
s.nextSlice("\x1b[1m"); // Bold
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2361,11 +2361,11 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3
try s.nextSlice("\x1b[38;2;1;2;3m");
try s.nextSlice(">");
s.nextSlice("\x1b[38;2;1;2;3m");
s.nextSlice(">");
// RGB 3, 2, 1
try s.nextSlice("\x1b[38;2;3;2;1m");
try s.nextSlice("=");
s.nextSlice("\x1b[38;2;3;2;1m");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2392,11 +2392,11 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3 bg
try s.nextSlice("\x1b[48;2;1;2;3m");
try s.nextSlice(">");
s.nextSlice("\x1b[48;2;1;2;3m");
s.nextSlice(">");
// RGB 3, 2, 1 bg
try s.nextSlice("\x1b[48;2;3;2;1m");
try s.nextSlice("=");
s.nextSlice("\x1b[48;2;3;2;1m");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2423,9 +2423,9 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3 bg
try s.nextSlice("\x1b[48;2;1;2;3m");
try s.nextSlice(">");
try s.nextSlice("=");
s.nextSlice("\x1b[48;2;1;2;3m");
s.nextSlice(">");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -2468,7 +2468,7 @@ test "shape high plane sprite font codepoint" {
var s = t.vtStream();
defer s.deinit();
// U+1FB70: Vertical One Eighth Block-2
try s.nextSlice("\u{1FB70}");
s.nextSlice("\u{1FB70}");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);

View File

@@ -448,7 +448,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD");
s.nextSlice("ABCD");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -472,7 +472,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD EFG");
s.nextSlice("ABCD EFG");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -495,7 +495,7 @@ test "run iterator" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A😃D");
s.nextSlice("A😃D");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -533,7 +533,7 @@ test "run iterator: empty cells with background set" {
var s = t.vtStream();
defer s.deinit();
// Set red background and write A
try s.nextSlice("\x1b[48;2;255;0;0mA");
s.nextSlice("\x1b[48;2;255;0;0mA");
// Get our first row
{
@@ -592,7 +592,7 @@ test "shape" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -626,7 +626,7 @@ test "shape inconsolata ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">=");
s.nextSlice(">=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -655,7 +655,7 @@ test "shape inconsolata ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("===");
s.nextSlice("===");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -692,7 +692,7 @@ test "shape monaspace ligs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("===");
s.nextSlice("===");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -732,7 +732,7 @@ test "shape arabic forced LTR" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(@embedFile("testdata/arabic.txt"));
s.nextSlice(@embedFile("testdata/arabic.txt"));
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -773,7 +773,7 @@ test "shape emoji width" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("👍");
s.nextSlice("👍");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -870,7 +870,7 @@ test "shape variation selector VS15" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -911,7 +911,7 @@ test "shape variation selector VS16" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -950,9 +950,9 @@ test "shape with empty cells in between" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A");
try s.nextSlice("\x1b[5C");
try s.nextSlice("B");
s.nextSlice("A");
s.nextSlice("\x1b[5C");
s.nextSlice("B");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -997,7 +997,7 @@ test "shape Combining characters" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1048,7 +1048,7 @@ test "shape Devanagari string" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("अपार्टमेंट");
s.nextSlice("अपार्टमेंट");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1111,7 +1111,7 @@ test "shape Tai Tham vowels (position differs from advance)" {
// var s = t.vtStream();
// defer s.deinit();
// try s.nextSlice(buf[0..buf_idx]);
// s.nextSlice(buf[0..buf_idx]);
// var state: terminal.RenderState = .empty;
// defer state.deinit(alloc);
@@ -1170,7 +1170,7 @@ test "shape Tibetan characters" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1232,7 +1232,7 @@ test "shape Tai Tham letters (run_offset.y differs from zero)" {
// var s = t.vtStream();
// defer s.deinit();
// try s.nextSlice(buf[0..buf_idx]);
// s.nextSlice(buf[0..buf_idx]);
// var state: terminal.RenderState = .empty;
// defer state.deinit(alloc);
@@ -1295,7 +1295,7 @@ test "shape Javanese ligatures" {
// var s = t.vtStream();
// defer s.deinit();
// try s.nextSlice(buf[0..buf_idx]);
// s.nextSlice(buf[0..buf_idx]);
// var state: terminal.RenderState = .empty;
// defer state.deinit(alloc);
@@ -1358,7 +1358,7 @@ test "shape Chakma vowel sign with ligature (vowel sign renders first)" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1433,7 +1433,7 @@ test "shape Bengali ligatures with out of order vowels" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1487,7 +1487,7 @@ test "shape box glyphs" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(buf[0..buf_idx]);
s.nextSlice(buf[0..buf_idx]);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1526,7 +1526,7 @@ test "shape selection boundary" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("a1b2c3d4e5");
s.nextSlice("a1b2c3d4e5");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1631,7 +1631,7 @@ test "shape cursor boundary" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("a1b2c3d4e5");
s.nextSlice("a1b2c3d4e5");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1771,7 +1771,7 @@ test "shape cursor boundary and colored emoji" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("👍🏼");
s.nextSlice("👍🏼");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1868,7 +1868,7 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">=");
s.nextSlice(">=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1894,9 +1894,9 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice(">");
try s.nextSlice("\x1b[1m");
try s.nextSlice("=");
s.nextSlice(">");
s.nextSlice("\x1b[1m");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1923,11 +1923,11 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3
try s.nextSlice("\x1b[38;2;1;2;3m");
try s.nextSlice(">");
s.nextSlice("\x1b[38;2;1;2;3m");
s.nextSlice(">");
// RGB 3, 2, 1
try s.nextSlice("\x1b[38;2;3;2;1m");
try s.nextSlice("=");
s.nextSlice("\x1b[38;2;3;2;1m");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1954,11 +1954,11 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3 bg
try s.nextSlice("\x1b[48;2;1;2;3m");
try s.nextSlice(">");
s.nextSlice("\x1b[48;2;1;2;3m");
s.nextSlice(">");
// RGB 3, 2, 1 bg
try s.nextSlice("\x1b[48;2;3;2;1m");
try s.nextSlice("=");
s.nextSlice("\x1b[48;2;3;2;1m");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -1985,9 +1985,9 @@ test "shape cell attribute change" {
var s = t.vtStream();
defer s.deinit();
// RGB 1, 2, 3 bg
try s.nextSlice("\x1b[48;2;1;2;3m");
try s.nextSlice(">");
try s.nextSlice("=");
s.nextSlice("\x1b[48;2;1;2;3m");
s.nextSlice(">");
s.nextSlice("=");
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);

View File

@@ -54,7 +54,7 @@ pub const Stream = struct {
.events = &self.events,
};
defer self.parser_stream.handler.state = null;
try self.parser_stream.nextSlice(data);
self.parser_stream.nextSlice(data);
}
pub fn draw(
@@ -736,7 +736,7 @@ const VTHandler = struct {
self: *VTHandler,
comptime action: VTHandler.Stream.Action.Tag,
value: VTHandler.Stream.Action.Value(action),
) !void {
) void {
_ = self;
_ = value;
}

View File

@@ -528,7 +528,7 @@ test "Cell constraint widths" {
// symbol->nothing: 2
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -540,7 +540,7 @@ test "Cell constraint widths" {
// symbol->character: 1
{
t.fullReset();
try s.nextSlice("z");
s.nextSlice("z");
try state.update(alloc, &t);
try testing.expectEqual(1, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -552,7 +552,7 @@ test "Cell constraint widths" {
// symbol->space: 2
{
t.fullReset();
try s.nextSlice(" z");
s.nextSlice(" z");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -563,7 +563,7 @@ test "Cell constraint widths" {
// symbol->no-break space: 1
{
t.fullReset();
try s.nextSlice("\u{00a0}z");
s.nextSlice("\u{00a0}z");
try state.update(alloc, &t);
try testing.expectEqual(1, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -575,7 +575,7 @@ test "Cell constraint widths" {
// symbol->end of row: 1
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(1, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -587,7 +587,7 @@ test "Cell constraint widths" {
// character->symbol: 2
{
t.fullReset();
try s.nextSlice("z");
s.nextSlice("z");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -599,7 +599,7 @@ test "Cell constraint widths" {
// symbol->symbol: 1,1
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(1, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -616,7 +616,7 @@ test "Cell constraint widths" {
// symbol->space->symbol: 2,2
{
t.fullReset();
try s.nextSlice(" ");
s.nextSlice(" ");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -633,7 +633,7 @@ test "Cell constraint widths" {
// symbol->powerline: 1 (dedicated test because powerline is special-cased in cellpkg)
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(1, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -645,7 +645,7 @@ test "Cell constraint widths" {
// powerline->symbol: 2 (dedicated test because powerline is special-cased in cellpkg)
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -657,7 +657,7 @@ test "Cell constraint widths" {
// powerline->nothing: 2 (dedicated test because powerline is special-cased in cellpkg)
{
t.fullReset();
try s.nextSlice("");
s.nextSlice("");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),
@@ -669,7 +669,7 @@ test "Cell constraint widths" {
// powerline->space: 2 (dedicated test because powerline is special-cased in cellpkg)
{
t.fullReset();
try s.nextSlice(" z");
s.nextSlice(" z");
try state.update(alloc, &t);
try testing.expectEqual(2, constraintWidth(
state.row_data.get(0).cells.items(.raw),

View File

@@ -148,7 +148,7 @@ test "renderCellMap" {
var s = t.vtStream();
defer s.deinit();
const str = "1ABCD2EFGH\r\n3IJKL";
try s.nextSlice(str);
s.nextSlice(str);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -201,7 +201,7 @@ test "renderCellMap hover links" {
var s = t.vtStream();
defer s.deinit();
const str = "1ABCD2EFGH\r\n3IJKL";
try s.nextSlice(str);
s.nextSlice(str);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);
@@ -279,7 +279,7 @@ test "renderCellMap mods no match" {
var s = t.vtStream();
defer s.deinit();
const str = "1ABCD2EFGH\r\n3IJKL";
try s.nextSlice(str);
s.nextSlice(str);
var state: terminal.RenderState = .empty;
defer state.deinit(alloc);

File diff suppressed because it is too large Load Diff

View File

@@ -908,7 +908,7 @@ test "basic text" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABCD");
s.nextSlice("ABCD");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -944,9 +944,9 @@ test "styled text" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("\x1b[1mA"); // Bold
try s.nextSlice("\x1b[0;3mB"); // Italic
try s.nextSlice("\x1b[0;4mC"); // Underline
s.nextSlice("\x1b[1mA"); // Bold
s.nextSlice("\x1b[0;3mB"); // Italic
s.nextSlice("\x1b[0;4mC"); // Underline
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -990,8 +990,8 @@ test "grapheme" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A");
try s.nextSlice("👨‍"); // this has a ZWJ
s.nextSlice("A");
s.nextSlice("👨‍"); // this has a ZWJ
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1037,7 +1037,7 @@ test "cursor state in viewport" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A\x1b[H");
s.nextSlice("A\x1b[H");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1052,14 +1052,14 @@ test "cursor state in viewport" {
try testing.expect(state.cursor.style.default());
// Set a style on the cursor
try s.nextSlice("\x1b[1m"); // Bold
s.nextSlice("\x1b[1m"); // Bold
try state.update(alloc, &t);
try testing.expect(!state.cursor.style.default());
try testing.expect(state.cursor.style.flags.bold);
try s.nextSlice("\x1b[0m"); // Reset style
s.nextSlice("\x1b[0m"); // Reset style
// Move cursor to 2,1
try s.nextSlice("\x1b[2;3H");
s.nextSlice("\x1b[2;3H");
try state.update(alloc, &t);
try testing.expectEqual(2, state.cursor.active.x);
try testing.expectEqual(1, state.cursor.active.y);
@@ -1079,7 +1079,7 @@ test "cursor state out of viewport" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A\r\nB\r\nC\r\nD\r\n");
s.nextSlice("A\r\nB\r\nC\r\nD\r\n");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1139,7 +1139,7 @@ test "dirty state" {
}
// Write to first line
try s.nextSlice("A");
s.nextSlice("A");
try state.update(alloc, &t);
try testing.expectEqual(.partial, state.dirty);
{
@@ -1170,7 +1170,7 @@ test "colors" {
try state.update(alloc, &t);
// Change cursor color
try s.nextSlice("\x1b]12;#FF0000\x07");
s.nextSlice("\x1b]12;#FF0000\x07");
try state.update(alloc, &t);
const c = state.colors.cursor.?;
@@ -1179,7 +1179,7 @@ test "colors" {
try testing.expectEqual(0, c.b);
// Change palette color 0 to White
try s.nextSlice("\x1b]4;0;#FFFFFF\x07");
s.nextSlice("\x1b]4;0;#FFFFFF\x07");
try state.update(alloc, &t);
const p0 = state.colors.palette[0];
try testing.expectEqual(0xFF, p0.r);
@@ -1275,7 +1275,7 @@ test "linkCells" {
defer state.deinit(alloc);
// Create a hyperlink
try s.nextSlice("\x1b]8;;http://example.com\x1b\\LINK\x1b]8;;\x1b\\");
s.nextSlice("\x1b]8;;http://example.com\x1b\\LINK\x1b]8;;\x1b\\");
try state.update(alloc, &t);
// Query link at 0,0
@@ -1306,7 +1306,7 @@ test "string" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("AB");
s.nextSlice("AB");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1345,12 +1345,12 @@ test "linkCells with scrollback spanning pages" {
const first_page_cap = pages.pages.first.?.data.capacity.rows;
// Fill first page
for (0..first_page_cap - 1) |_| try s.nextSlice("\r\n");
for (0..first_page_cap - 1) |_| s.nextSlice("\r\n");
// Create second page with hyperlink
try s.nextSlice("\r\n");
try s.nextSlice("\x1b]8;;http://example.com\x1b\\LINK\x1b]8;;\x1b\\");
for (0..(tail_rows - 1)) |_| try s.nextSlice("\r\n");
s.nextSlice("\r\n");
s.nextSlice("\x1b]8;;http://example.com\x1b\\LINK\x1b]8;;\x1b\\");
for (0..(tail_rows - 1)) |_| s.nextSlice("\r\n");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1416,7 +1416,7 @@ test "dirty row resets highlights" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("ABC");
s.nextSlice("ABC");
var state: RenderState = .empty;
defer state.deinit(alloc);
@@ -1451,8 +1451,8 @@ test "dirty row resets highlights" {
}
// Write to row 0 to make it dirty
try s.nextSlice("\x1b[H"); // Move to home
try s.nextSlice("X");
s.nextSlice("\x1b[H"); // Move to home
s.nextSlice("X");
try state.update(alloc, &t);
// Verify the highlight was reset on the dirty row

View File

@@ -853,7 +853,7 @@ test {
var stream = t.vtStream();
defer stream.deinit();
try stream.nextSlice("Hello, world");
stream.nextSlice("Hello, world");
var ud: TestUserData = .{};
defer ud.deinit();

View File

@@ -108,7 +108,7 @@ test "simple search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ActiveSearch = try .init(alloc, "Fizz");
defer search.deinit();
@@ -148,15 +148,15 @@ test "clear screen and search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ActiveSearch = try .init(alloc, "Fizz");
defer search.deinit();
_ = try search.update(&t.screens.active.pages);
try s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz");
s.nextSlice("\x1b[2J"); // Clear screen
s.nextSlice("\x1b[H"); // Move cursor home
s.nextSlice("Buzz\r\nFizz\r\nBuzz");
_ = try search.update(&t.screens.active.pages);
{

View File

@@ -141,7 +141,7 @@ test "simple search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: PageListSearch = try .init(
alloc,
@@ -191,14 +191,14 @@ test "feed multiple pages with matches" {
// Fill up first page
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Fizz");
for (0..first_page_rows - 1) |_| s.nextSlice("\r\n");
s.nextSlice("Fizz");
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page
try s.nextSlice("\r\n");
s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("Buzz\r\nFizz");
s.nextSlice("Buzz\r\nFizz");
var search: PageListSearch = try .init(
alloc,
@@ -235,13 +235,13 @@ test "feed multiple pages no matches" {
// Fill up first page
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Hello");
for (0..first_page_rows - 1) |_| s.nextSlice("\r\n");
s.nextSlice("Hello");
// Create second page
try s.nextSlice("\r\n");
s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("World");
s.nextSlice("World");
var search: PageListSearch = try .init(
alloc,
@@ -275,14 +275,14 @@ test "feed iteratively through multiple matches" {
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page with a match at the end
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Page1Test");
for (0..first_page_rows - 1) |_| s.nextSlice("\r\n");
s.nextSlice("Page1Test");
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page with a match
try s.nextSlice("\r\n");
s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("Page2Test");
s.nextSlice("Page2Test");
var search: PageListSearch = try .init(
alloc,
@@ -316,13 +316,13 @@ test "feed with match spanning page boundary" {
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page ending with "Te"
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
for (0..t.screens.active.pages.cols - 2) |_| try s.nextSlice("x");
try s.nextSlice("Te");
for (0..first_page_rows - 1) |_| s.nextSlice("\r\n");
for (0..t.screens.active.pages.cols - 2) |_| s.nextSlice("x");
s.nextSlice("Te");
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Second page starts with "st"
try s.nextSlice("st");
s.nextSlice("st");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
var search: PageListSearch = try .init(
@@ -370,15 +370,15 @@ test "feed with match spanning page boundary with newline" {
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page ending with "Te"
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
for (0..t.screens.active.pages.cols - 2) |_| try s.nextSlice("x");
try s.nextSlice("Te");
for (0..first_page_rows - 1) |_| s.nextSlice("\r\n");
for (0..t.screens.active.pages.cols - 2) |_| s.nextSlice("x");
s.nextSlice("Te");
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Second page starts with "st"
try s.nextSlice("\r\n");
s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("st");
s.nextSlice("st");
var search: PageListSearch = try .init(
alloc,

View File

@@ -827,7 +827,7 @@ test "simple search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -877,10 +877,10 @@ test "simple search with history" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("hello.");
s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("hello.");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -917,7 +917,7 @@ test "reload active with history change" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\n");
s.nextSlice("Fizz\r\n");
// Start up our search which will populate our initial active area.
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
@@ -930,9 +930,9 @@ test "reload active with history change" {
}
// Grow into two pages so our history pin will move.
while (list.totalPages() < 2) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("2Fizz");
while (list.totalPages() < 2) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("2Fizz");
// Active area changed so reload
try search.reloadActive();
@@ -969,7 +969,7 @@ test "reload active with history change" {
// Reset the screen which will make our pin garbage.
t.fullReset();
try s.nextSlice("WeFizzing");
s.nextSlice("WeFizzing");
try search.reloadActive();
try search.searchAll();
@@ -998,7 +998,7 @@ test "active change contents" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fuzz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fuzz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1006,8 +1006,8 @@ test "active change contents" {
try testing.expectEqual(1, search.active_results.items.len);
// Erase the screen, move our cursor to the top, and change contents.
try s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
try s.nextSlice("Bang\r\nFizz\r\nHello!");
s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
s.nextSlice("Bang\r\nFizz\r\nHello!");
try search.reloadActive();
try search.searchAll();
@@ -1038,7 +1038,7 @@ test "select next" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1097,7 +1097,7 @@ test "select in active changes contents completely" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1118,8 +1118,8 @@ test "select in active changes contents completely" {
}
// Erase the screen, move our cursor to the top, and change contents.
try s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
try s.nextSlice("Fuzz\r\nFizz\r\nHello!");
s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
s.nextSlice("Fuzz\r\nFizz\r\nHello!");
try search.reloadActive();
{
@@ -1136,8 +1136,8 @@ test "select in active changes contents completely" {
}
// Erase the screen, redraw with same contents.
try s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
try s.nextSlice("Fuzz\r\nFizz\r\nFizz");
s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
s.nextSlice("Fuzz\r\nFizz\r\nFizz");
try search.reloadActive();
{
@@ -1167,10 +1167,10 @@ test "select into history" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("hello.");
s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("hello.");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1191,8 +1191,8 @@ test "select into history" {
}
// Erase the screen, redraw with same contents.
try s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
try s.nextSlice("yo yo");
s.nextSlice("\x1b[2J\x1b[H"); // Clear screen and move home
s.nextSlice("yo yo");
try search.reloadActive();
{
@@ -1209,7 +1209,7 @@ test "select into history" {
}
// Create some new history by adding more lines.
try s.nextSlice("\r\nfizz\r\nfizz\r\nfizz"); // Clear screen and move home
s.nextSlice("\r\nfizz\r\nfizz\r\nfizz"); // Clear screen and move home
try search.reloadActive();
{
// Our selection should not move since the history is still not
@@ -1233,7 +1233,7 @@ test "select prev" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1292,7 +1292,7 @@ test "select prev then next" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1342,10 +1342,10 @@ test "select prev with history" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("Fizz.");
s.nextSlice("Fizz\r\n");
while (list.totalPages() < 3) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("Fizz.");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1399,9 +1399,9 @@ test "screen search no scrollback has no history" {
// no way to test it using public APIs, but at the time of writing
// this test, CSI 22 J (scroll complete) pushes into scrollback
// with alt screen.
try s.nextSlice("Fizz\r\n");
try s.nextSlice("\x1b[22J");
try s.nextSlice("hello.");
s.nextSlice("Fizz\r\n");
s.nextSlice("\x1b[22J");
s.nextSlice("hello.");
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
defer search.deinit();
@@ -1431,10 +1431,10 @@ test "reloadActive partial history cleanup on appendSlice error" {
// Write multiple "Fizz" matches that will end up in history.
// We need enough content to push "Fizz" entries into scrollback.
try s.nextSlice("Fizz\r\nFizz\r\n");
while (list.totalPages() < 3) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("Fizz.");
s.nextSlice("Fizz\r\nFizz\r\n");
while (list.totalPages() < 3) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("Fizz.");
// Complete initial search
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
@@ -1443,9 +1443,9 @@ test "reloadActive partial history cleanup on appendSlice error" {
// Now trigger reloadActive by adding more content that changes the
// active/history boundary. First add more "Fizz" entries to history.
try s.nextSlice("\r\nFizz\r\nFizz\r\n");
while (list.totalPages() < 4) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
s.nextSlice("\r\nFizz\r\nFizz\r\n");
while (list.totalPages() < 4) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
// Arm the tripwire to fail at appendSlice (after the loop completes).
// At this point, there are FlattenedHighlight items in the results list
@@ -1478,10 +1478,10 @@ test "reloadActive partial history cleanup on loop append error" {
// Write multiple "Fizz" matches that will end up in history.
// We need enough content to push "Fizz" entries into scrollback.
try s.nextSlice("Fizz\r\nFizz\r\n");
while (list.totalPages() < 3) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("Fizz.");
s.nextSlice("Fizz\r\nFizz\r\n");
while (list.totalPages() < 3) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
s.nextSlice("Fizz.");
// Complete initial search
var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
@@ -1490,9 +1490,9 @@ test "reloadActive partial history cleanup on loop append error" {
// Now trigger reloadActive by adding more content that changes the
// active/history boundary. First add more "Fizz" entries to history.
try s.nextSlice("\r\nFizz\r\nFizz\r\n");
while (list.totalPages() < 4) try s.nextSlice("\r\n");
for (0..list.rows) |_| try s.nextSlice("\r\n");
s.nextSlice("\r\nFizz\r\nFizz\r\n");
while (list.totalPages() < 4) s.nextSlice("\r\n");
for (0..list.rows) |_| s.nextSlice("\r\n");
// Arm the tripwire to fail after the first loop append succeeds.
// This leaves at least one FlattenedHighlight in the results list

View File

@@ -1583,7 +1583,7 @@ test "SlidingWindow single append soft wrapped" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A\r\nxxboo!\r\nC");
s.nextSlice("A\r\nxxboo!\r\nC");
// We want to test single-page cases.
const screen = t.screens.active;
@@ -1620,7 +1620,7 @@ test "SlidingWindow single append reversed soft wrapped" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("A\r\nxxboo!\r\nC");
s.nextSlice("A\r\nxxboo!\r\nC");
// We want to test single-page cases.
const screen = t.screens.active;

View File

@@ -223,7 +223,7 @@ test "simple search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit();
@@ -266,15 +266,15 @@ test "clear screen and search" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit();
try testing.expect(try search.update(&t.screens.active.pages));
try s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz");
s.nextSlice("\x1b[2J"); // Clear screen
s.nextSlice("\x1b[H"); // Move cursor home
s.nextSlice("Buzz\r\nFizz\r\nBuzz");
try testing.expect(try search.update(&t.screens.active.pages));
{
@@ -299,7 +299,7 @@ test "clear screen and search dirty tracking" {
var s = t.vtStream();
defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang");
var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit();
@@ -313,9 +313,9 @@ test "clear screen and search dirty tracking" {
// Should not update since nothing changed
try testing.expect(!try search.update(&t.screens.active.pages));
try s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz");
s.nextSlice("\x1b[2J"); // Clear screen
s.nextSlice("\x1b[H"); // Move cursor home
s.nextSlice("Buzz\r\nFizz\r\nBuzz");
// Should still not update since active area isn't dirty
try testing.expect(!try search.update(&t.screens.active.pages));
@@ -349,14 +349,14 @@ test "history search, no active area" {
// Fill up first page
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
try s.nextSlice("Fizz\r\n");
for (1..first_page_rows - 1) |_| try s.nextSlice("\r\n");
s.nextSlice("Fizz\r\n");
for (1..first_page_rows - 1) |_| s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page
try s.nextSlice("\r\n");
s.nextSlice("\r\n");
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("Buzz\r\nFizz");
s.nextSlice("Buzz\r\nFizz");
t.scrollViewport(.top);

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@ const osc_color = @import("osc/parsers/color.zig");
const kitty_color = @import("kitty/color.zig");
const Terminal = @import("Terminal.zig");
const log = std.log.scoped(.stream_readonly);
/// This is a Stream implementation that processes actions against
/// a Terminal and updates the Terminal state. It is called "readonly" because
/// it only processes actions that modify terminal state, while ignoring
@@ -45,6 +47,16 @@ pub const Handler = struct {
self: *Handler,
comptime action: Action.Tag,
value: Action.Value(action),
) void {
self.vtFallible(action, value) catch |err| {
log.warn("error handling VT action action={} err={}", .{ action, err });
};
}
inline fn vtFallible(
self: *Handler,
comptime action: Action.Tag,
value: Action.Value(action),
) !void {
switch (action) {
.print => try self.terminal.print(value.cp),
@@ -402,7 +414,7 @@ test "basic print" {
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
try s.nextSlice("Hello");
s.nextSlice("Hello");
try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
@@ -419,12 +431,12 @@ test "cursor movement" {
defer s.deinit();
// Move cursor using escape sequences
try s.nextSlice("Hello\x1B[1;1H");
s.nextSlice("Hello\x1B[1;1H");
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Move to position 2,3
try s.nextSlice("\x1B[2;3H");
s.nextSlice("\x1B[2;3H");
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
}
@@ -437,13 +449,13 @@ test "erase operations" {
defer s.deinit();
// Print some text
try s.nextSlice("Hello World");
s.nextSlice("Hello World");
try testing.expectEqual(@as(usize, 11), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Move cursor to position 1,6 and erase from cursor to end of line
try s.nextSlice("\x1B[1;6H");
try s.nextSlice("\x1B[K");
s.nextSlice("\x1B[1;6H");
s.nextSlice("\x1B[K");
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
@@ -457,7 +469,7 @@ test "tabs" {
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
try s.nextSlice("A\tB");
s.nextSlice("A\tB");
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.x);
const str = try t.plainString(testing.allocator);
@@ -474,9 +486,9 @@ test "modes" {
// Test wraparound mode
try testing.expect(t.modes.get(.wraparound));
try s.nextSlice("\x1B[?7l"); // Disable wraparound
s.nextSlice("\x1B[?7l"); // Disable wraparound
try testing.expect(!t.modes.get(.wraparound));
try s.nextSlice("\x1B[?7h"); // Enable wraparound
s.nextSlice("\x1B[?7h"); // Enable wraparound
try testing.expect(t.modes.get(.wraparound));
}
@@ -488,7 +500,7 @@ test "scrolling regions" {
defer s.deinit();
// Set scrolling region from line 5 to 20
try s.nextSlice("\x1B[5;20r");
s.nextSlice("\x1B[5;20r");
try testing.expectEqual(@as(usize, 4), t.scrolling_region.top);
try testing.expectEqual(@as(usize, 19), t.scrolling_region.bottom);
try testing.expectEqual(@as(usize, 0), t.scrolling_region.left);
@@ -503,8 +515,8 @@ test "charsets" {
defer s.deinit();
// Configure G0 as DEC special graphics
try s.nextSlice("\x1B(0");
try s.nextSlice("`"); // Should print diamond character
s.nextSlice("\x1B(0");
s.nextSlice("`"); // Should print diamond character
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
@@ -519,18 +531,18 @@ test "alt screen" {
defer s.deinit();
// Write to primary screen
try s.nextSlice("Primary");
s.nextSlice("Primary");
try testing.expectEqual(.primary, t.screens.active_key);
// Switch to alt screen
try s.nextSlice("\x1B[?1049h");
s.nextSlice("\x1B[?1049h");
try testing.expectEqual(.alternate, t.screens.active_key);
// Write to alt screen
try s.nextSlice("Alt");
s.nextSlice("Alt");
// Switch back to primary
try s.nextSlice("\x1B[?1049l");
s.nextSlice("\x1B[?1049l");
try testing.expectEqual(.primary, t.screens.active_key);
const str = try t.plainString(testing.allocator);
@@ -546,20 +558,20 @@ test "cursor save and restore" {
defer s.deinit();
// Move cursor to 10,15
try s.nextSlice("\x1B[10;15H");
s.nextSlice("\x1B[10;15H");
try testing.expectEqual(@as(usize, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
// Save cursor
try s.nextSlice("\x1B7");
s.nextSlice("\x1B7");
// Move cursor elsewhere
try s.nextSlice("\x1B[1;1H");
s.nextSlice("\x1B[1;1H");
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Restore cursor
try s.nextSlice("\x1B8");
s.nextSlice("\x1B8");
try testing.expectEqual(@as(usize, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
}
@@ -572,7 +584,7 @@ test "attributes" {
defer s.deinit();
// Set bold and write text
try s.nextSlice("\x1B[1mBold\x1B[0m");
s.nextSlice("\x1B[1mBold\x1B[0m");
// Verify we can write attributes - just check the string was written
const str = try t.plainString(testing.allocator);
@@ -588,7 +600,7 @@ test "DECALN screen alignment" {
defer s.deinit();
// Run DECALN
try s.nextSlice("\x1B#8");
s.nextSlice("\x1B#8");
// Verify entire screen is filled with 'E'
const str = try t.plainString(testing.allocator);
@@ -608,13 +620,13 @@ test "full reset" {
defer s.deinit();
// Make some changes
try s.nextSlice("Hello");
try s.nextSlice("\x1B[10;20H");
try s.nextSlice("\x1B[5;20r"); // Set scroll region
try s.nextSlice("\x1B[?7l"); // Disable wraparound
s.nextSlice("Hello");
s.nextSlice("\x1B[10;20H");
s.nextSlice("\x1B[5;20r"); // Set scroll region
s.nextSlice("\x1B[?7l"); // Disable wraparound
// Full reset
try s.nextSlice("\x1Bc");
s.nextSlice("\x1Bc");
// Verify reset state
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
@@ -632,12 +644,12 @@ test "ignores query actions" {
defer s.deinit();
// These should be ignored without error
try s.nextSlice("\x1B[c"); // Device attributes
try s.nextSlice("\x1B[5n"); // Device status report
try s.nextSlice("\x1B[6n"); // Cursor position report
s.nextSlice("\x1B[c"); // Device attributes
s.nextSlice("\x1B[5n"); // Device status report
s.nextSlice("\x1B[6n"); // Cursor position report
// Terminal should still be functional
try s.nextSlice("Test");
s.nextSlice("Test");
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("Test", str);
@@ -654,14 +666,14 @@ test "OSC 4 set and reset palette" {
const default_color_0 = t.colors.palette.original[0];
// Set color 0 to red
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[0].r);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].g);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].b);
try testing.expect(t.colors.palette.mask.isSet(0));
// Reset color 0
try s.nextSlice("\x1b]104;0\x1b\\");
s.nextSlice("\x1b]104;0\x1b\\");
try testing.expectEqual(default_color_0, t.colors.palette.current[0]);
try testing.expect(!t.colors.palette.mask.isSet(0));
}
@@ -674,15 +686,15 @@ test "OSC 104 reset all palette colors" {
defer s.deinit();
// Set multiple colors
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
try s.nextSlice("\x1b]4;1;rgb:00/ff/00\x1b\\");
try s.nextSlice("\x1b]4;2;rgb:00/00/ff\x1b\\");
s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
s.nextSlice("\x1b]4;1;rgb:00/ff/00\x1b\\");
s.nextSlice("\x1b]4;2;rgb:00/00/ff\x1b\\");
try testing.expect(t.colors.palette.mask.isSet(0));
try testing.expect(t.colors.palette.mask.isSet(1));
try testing.expect(t.colors.palette.mask.isSet(2));
// Reset all palette colors
try s.nextSlice("\x1b]104\x1b\\");
s.nextSlice("\x1b]104\x1b\\");
try testing.expectEqual(t.colors.palette.original[0], t.colors.palette.current[0]);
try testing.expectEqual(t.colors.palette.original[1], t.colors.palette.current[1]);
try testing.expectEqual(t.colors.palette.original[2], t.colors.palette.current[2]);
@@ -702,14 +714,14 @@ test "OSC 10 set and reset foreground color" {
try testing.expect(t.colors.foreground.get() == null);
// Set foreground to red
try s.nextSlice("\x1b]10;rgb:ff/00/00\x1b\\");
s.nextSlice("\x1b]10;rgb:ff/00/00\x1b\\");
const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0xff), fg.r);
try testing.expectEqual(@as(u8, 0x00), fg.g);
try testing.expectEqual(@as(u8, 0x00), fg.b);
// Reset foreground
try s.nextSlice("\x1b]110\x1b\\");
s.nextSlice("\x1b]110\x1b\\");
try testing.expect(t.colors.foreground.get() == null);
}
@@ -721,14 +733,14 @@ test "OSC 11 set and reset background color" {
defer s.deinit();
// Set background to green
try s.nextSlice("\x1b]11;rgb:00/ff/00\x1b\\");
s.nextSlice("\x1b]11;rgb:00/ff/00\x1b\\");
const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x00), bg.r);
try testing.expectEqual(@as(u8, 0xff), bg.g);
try testing.expectEqual(@as(u8, 0x00), bg.b);
// Reset background
try s.nextSlice("\x1b]111\x1b\\");
s.nextSlice("\x1b]111\x1b\\");
try testing.expect(t.colors.background.get() == null);
}
@@ -740,14 +752,14 @@ test "OSC 12 set and reset cursor color" {
defer s.deinit();
// Set cursor to blue
try s.nextSlice("\x1b]12;rgb:00/00/ff\x1b\\");
s.nextSlice("\x1b]12;rgb:00/00/ff\x1b\\");
const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0x00), cursor.r);
try testing.expectEqual(@as(u8, 0x00), cursor.g);
try testing.expectEqual(@as(u8, 0xff), cursor.b);
// Reset cursor
try s.nextSlice("\x1b]112\x1b\\");
s.nextSlice("\x1b]112\x1b\\");
// After reset, cursor might be null (using default)
}
@@ -759,7 +771,7 @@ test "kitty color protocol set palette" {
defer s.deinit();
// Set palette color 5 to magenta using kitty protocol
try s.nextSlice("\x1b]21;5=rgb:ff/00/ff\x1b\\");
s.nextSlice("\x1b]21;5=rgb:ff/00/ff\x1b\\");
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[5].r);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[5].g);
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[5].b);
@@ -776,10 +788,10 @@ test "kitty color protocol reset palette" {
// Set and then reset palette color
const original = t.colors.palette.original[7];
try s.nextSlice("\x1b]21;7=rgb:aa/bb/cc\x1b\\");
s.nextSlice("\x1b]21;7=rgb:aa/bb/cc\x1b\\");
try testing.expect(t.colors.palette.mask.isSet(7));
try s.nextSlice("\x1b]21;7=\x1b\\");
s.nextSlice("\x1b]21;7=\x1b\\");
try testing.expectEqual(original, t.colors.palette.current[7]);
try testing.expect(!t.colors.palette.mask.isSet(7));
}
@@ -792,7 +804,7 @@ test "kitty color protocol set foreground" {
defer s.deinit();
// Set foreground using kitty protocol
try s.nextSlice("\x1b]21;foreground=rgb:12/34/56\x1b\\");
s.nextSlice("\x1b]21;foreground=rgb:12/34/56\x1b\\");
const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0x12), fg.r);
try testing.expectEqual(@as(u8, 0x34), fg.g);
@@ -807,7 +819,7 @@ test "kitty color protocol set background" {
defer s.deinit();
// Set background using kitty protocol
try s.nextSlice("\x1b]21;background=rgb:78/9a/bc\x1b\\");
s.nextSlice("\x1b]21;background=rgb:78/9a/bc\x1b\\");
const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x78), bg.r);
try testing.expectEqual(@as(u8, 0x9a), bg.g);
@@ -822,7 +834,7 @@ test "kitty color protocol set cursor" {
defer s.deinit();
// Set cursor using kitty protocol
try s.nextSlice("\x1b]21;cursor=rgb:de/f0/12\x1b\\");
s.nextSlice("\x1b]21;cursor=rgb:de/f0/12\x1b\\");
const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0xde), cursor.r);
try testing.expectEqual(@as(u8, 0xf0), cursor.g);
@@ -837,10 +849,10 @@ test "kitty color protocol reset foreground" {
defer s.deinit();
// Set and reset foreground
try s.nextSlice("\x1b]21;foreground=rgb:11/22/33\x1b\\");
s.nextSlice("\x1b]21;foreground=rgb:11/22/33\x1b\\");
try testing.expect(t.colors.foreground.get() != null);
try s.nextSlice("\x1b]21;foreground=\x1b\\");
s.nextSlice("\x1b]21;foreground=\x1b\\");
// After reset, should be unset
try testing.expect(t.colors.foreground.get() == null);
}
@@ -856,17 +868,17 @@ test "palette dirty flag set on color change" {
t.flags.dirty.palette = false;
// Setting palette color should set dirty flag
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
try testing.expect(t.flags.dirty.palette);
// Clear and test reset
t.flags.dirty.palette = false;
try s.nextSlice("\x1b]104;0\x1b\\");
s.nextSlice("\x1b]104;0\x1b\\");
try testing.expect(t.flags.dirty.palette);
// Clear and test kitty protocol
t.flags.dirty.palette = false;
try s.nextSlice("\x1b]21;1=rgb:00/ff/00\x1b\\");
s.nextSlice("\x1b]21;1=rgb:00/ff/00\x1b\\");
try testing.expect(t.flags.dirty.palette);
}
@@ -877,8 +889,8 @@ test "semantic prompt fresh line" {
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
try s.nextSlice("Hello");
try s.nextSlice("\x1b]133;L\x07");
s.nextSlice("Hello");
s.nextSlice("\x1b]133;L\x07");
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
}
@@ -891,8 +903,8 @@ test "semantic prompt fresh line new prompt" {
defer s.deinit();
// Write some text and then send OSC 133;A (fresh_line_new_prompt)
try s.nextSlice("Hello");
try s.nextSlice("\x1b]133;A\x07");
s.nextSlice("Hello");
s.nextSlice("\x1b]133;A\x07");
// Should do a fresh line (carriage return + index)
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
@@ -902,8 +914,8 @@ test "semantic prompt fresh line new prompt" {
try testing.expectEqual(.prompt, t.screens.active.cursor.semantic_content);
// Test with redraw option
try s.nextSlice("prompt$ ");
try s.nextSlice("\x1b]133;A;redraw=1\x07");
s.nextSlice("prompt$ ");
s.nextSlice("\x1b]133;A;redraw=1\x07");
try testing.expect(t.flags.shell_redraws_prompt == .true);
}
@@ -915,12 +927,12 @@ test "semantic prompt end of input, then start output" {
defer s.deinit();
// Write some text and then send OSC 133;A (fresh_line_new_prompt)
try s.nextSlice("Hello");
try s.nextSlice("\x1b]133;A\x07");
try s.nextSlice("prompt$ ");
try s.nextSlice("\x1b]133;B\x07");
s.nextSlice("Hello");
s.nextSlice("\x1b]133;A\x07");
s.nextSlice("prompt$ ");
s.nextSlice("\x1b]133;B\x07");
try testing.expectEqual(.input, t.screens.active.cursor.semantic_content);
try s.nextSlice("\x1b]133;C\x07");
s.nextSlice("\x1b]133;C\x07");
try testing.expectEqual(.output, t.screens.active.cursor.semantic_content);
}
@@ -932,10 +944,10 @@ test "semantic prompt prompt_start" {
defer s.deinit();
// Write some text
try s.nextSlice("Hello");
s.nextSlice("Hello");
// OSC 133;P marks the start of a prompt (without fresh line behavior)
try s.nextSlice("\x1b]133;P\x07");
s.nextSlice("\x1b]133;P\x07");
try testing.expectEqual(.prompt, t.screens.active.cursor.semantic_content);
try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
@@ -949,8 +961,8 @@ test "semantic prompt new_command" {
defer s.deinit();
// Write some text
try s.nextSlice("Hello");
try s.nextSlice("\x1b]133;N\x07");
s.nextSlice("Hello");
s.nextSlice("\x1b]133;N\x07");
// Should behave like fresh_line_new_prompt - cursor moves to column 0
// on next line since we had content
@@ -967,7 +979,7 @@ test "semantic prompt new_command at column zero" {
defer s.deinit();
// OSC 133;N when already at column 0 should stay on same line
try s.nextSlice("\x1b]133;N\x07");
s.nextSlice("\x1b]133;N\x07");
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
try testing.expectEqual(.prompt, t.screens.active.cursor.semantic_content);
@@ -981,11 +993,11 @@ test "semantic prompt end_prompt_start_input_terminate_eol clears on linefeed" {
defer s.deinit();
// Set input terminated by EOL
try s.nextSlice("\x1b]133;I\x07");
s.nextSlice("\x1b]133;I\x07");
try testing.expectEqual(.input, t.screens.active.cursor.semantic_content);
// Linefeed should reset semantic content to output
try s.nextSlice("\n");
s.nextSlice("\n");
try testing.expectEqual(.output, t.screens.active.cursor.semantic_content);
}
@@ -1002,5 +1014,5 @@ test "stream: CSI W with intermediate but no params" {
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
try s.nextSlice("\x1b[?W");
s.nextSlice("\x1b[?W");
}

View File

@@ -1053,10 +1053,7 @@ pub const Viewer = struct {
// correct but we'll get the active contents soon.
var stream = t.vtStream();
defer stream.deinit();
stream.nextSlice(content) catch |err| {
log.info("failed to process pane history for pane id={}: {}", .{ id, err });
return err;
};
stream.nextSlice(content);
// Populate the active area to be empty since this is only history.
// We'll fill it with blanks and move the cursor to the top-left.
@@ -1097,10 +1094,7 @@ pub const Viewer = struct {
var stream = t.vtStream();
defer stream.deinit();
stream.nextSlice(content) catch |err| {
log.info("failed to process pane visible for pane id={}: {}", .{ id, err });
return err;
};
stream.nextSlice(content);
}
fn receivedOutput(
@@ -1117,10 +1111,7 @@ pub const Viewer = struct {
var stream = t.vtStream();
defer stream.deinit();
stream.nextSlice(data) catch |err| {
log.info("failed to process output for pane id={}: {}", .{ id, err });
return err;
};
stream.nextSlice(data);
}
fn initLayout(

View File

@@ -721,12 +721,10 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
log.err("error recording pty read in inspector err={}", .{err});
};
self.terminal_stream.next(byte) catch |err|
log.err("error processing terminal data: {}", .{err});
self.terminal_stream.next(byte);
}
} else {
self.terminal_stream.nextSlice(buf) catch |err|
log.err("error processing terminal data: {}", .{err});
self.terminal_stream.nextSlice(buf);
}
// If our stream handling caused messages to be sent to the mailbox

View File

@@ -176,6 +176,16 @@ pub const StreamHandler = struct {
self: *StreamHandler,
comptime action: Stream.Action.Tag,
value: Stream.Action.Value(action),
) void {
self.vtFallible(action, value) catch |err| {
log.warn("error handling VT action action={} err={}", .{ action, err });
};
}
inline fn vtFallible(
self: *StreamHandler,
comptime action: Stream.Action.Tag,
value: Stream.Action.Value(action),
) !void {
// The branch hints here are based on real world data
// which indicates that the most common actions are:

View File

@@ -43,11 +43,9 @@ pub export fn zig_fuzz_test(
if (mode & 1 == 0) {
// Slice path — exercises SIMD fast-path if enabled
stream.nextSlice(data) catch |err|
std.debug.panic("nextSlice: {}", .{err});
stream.nextSlice(data);
} else {
// Scalar path — exercises byte-at-a-time UTF-8 decoding
for (data) |byte| _ = stream.next(byte) catch |err|
std.debug.panic("next: {}", .{err});
for (data) |byte| stream.next(byte);
}
}