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.
pull/11468/head
Mitchell Hashimoto 2026-03-13 13:14:03 -07:00
parent 04fa71e237
commit 2044e5030f
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
23 changed files with 791 additions and 782 deletions

View File

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

View File

@ -14,24 +14,24 @@ pub fn main() !void {
defer stream.deinit(); defer stream.deinit();
// Basic text with newline // 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 // 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 // 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 // Cursor movement: ESC[5B = move down 5 lines
try stream.nextSlice("\x1b[5B"); stream.nextSlice("\x1b[5B");
try stream.nextSlice("Moved down!\r\n"); stream.nextSlice("Moved down!\r\n");
// Erase line: ESC[2K = clear entire line // Erase line: ESC[2K = clear entire line
try stream.nextSlice("\x1b[2K"); stream.nextSlice("\x1b[2K");
try stream.nextSlice("New content\r\n"); stream.nextSlice("New content\r\n");
// Multiple lines // 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 // Get the final terminal state as a plain string
const str = try t.plainString(alloc); 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 // Force a style on every single row, which
var s = self.terminal.vtStream(); var s = self.terminal.vtStream();
defer s.deinit(); defer s.deinit();
s.nextSlice("\x1b[48;2;20;40;60m") catch unreachable; s.nextSlice("\x1b[48;2;20;40;60m");
for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n") catch unreachable; for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n");
s.nextSlice("hello") catch unreachable; s.nextSlice("hello");
// Setup our terminal state // Setup our terminal state
const data_f: std.fs.File = (options.dataFile( const data_f: std.fs.File = (options.dataFile(
@ -120,10 +120,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached
stream.nextSlice(buf[0..n]) catch |err| { stream.nextSlice(buf[0..n]);
log.warn("error processing data file chunk err={}", .{err});
return error.BenchmarkFailed;
};
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -223,7 +223,7 @@ test "simple search" {
var s = t.vtStream(); var s = t.vtStream();
defer s.deinit(); 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"); var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit(); defer search.deinit();
@ -266,15 +266,15 @@ test "clear screen and search" {
var s = t.vtStream(); var s = t.vtStream();
defer s.deinit(); 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"); var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit(); defer search.deinit();
try testing.expect(try search.update(&t.screens.active.pages)); try testing.expect(try search.update(&t.screens.active.pages));
try s.nextSlice("\x1b[2J"); // Clear screen s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz"); s.nextSlice("Buzz\r\nFizz\r\nBuzz");
try testing.expect(try search.update(&t.screens.active.pages)); 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(); var s = t.vtStream();
defer s.deinit(); 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"); var search: ViewportSearch = try .init(alloc, "Fizz");
defer search.deinit(); defer search.deinit();
@ -313,9 +313,9 @@ test "clear screen and search dirty tracking" {
// Should not update since nothing changed // Should not update since nothing changed
try testing.expect(!try search.update(&t.screens.active.pages)); try testing.expect(!try search.update(&t.screens.active.pages));
try s.nextSlice("\x1b[2J"); // Clear screen s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz"); s.nextSlice("Buzz\r\nFizz\r\nBuzz");
// Should still not update since active area isn't dirty // Should still not update since active area isn't dirty
try testing.expect(!try search.update(&t.screens.active.pages)); try testing.expect(!try search.update(&t.screens.active.pages));
@ -349,14 +349,14 @@ test "history search, no active area" {
// Fill up first page // Fill up first page
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows; const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
try s.nextSlice("Fizz\r\n"); s.nextSlice("Fizz\r\n");
for (1..first_page_rows - 1) |_| try s.nextSlice("\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); try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page // 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 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); 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 kitty_color = @import("kitty/color.zig");
const Terminal = @import("Terminal.zig"); const Terminal = @import("Terminal.zig");
const log = std.log.scoped(.stream_readonly);
/// This is a Stream implementation that processes actions against /// This is a Stream implementation that processes actions against
/// a Terminal and updates the Terminal state. It is called "readonly" because /// a Terminal and updates the Terminal state. It is called "readonly" because
/// it only processes actions that modify terminal state, while ignoring /// it only processes actions that modify terminal state, while ignoring
@ -45,6 +47,16 @@ pub const Handler = struct {
self: *Handler, self: *Handler,
comptime action: Action.Tag, comptime action: Action.Tag,
value: Action.Value(action), 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 { ) !void {
switch (action) { switch (action) {
.print => try self.terminal.print(value.cp), .print => try self.terminal.print(value.cp),
@ -402,7 +414,7 @@ test "basic print" {
var s: Stream = .initAlloc(testing.allocator, .init(&t)); var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit(); 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, 5), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
@ -419,12 +431,12 @@ test "cursor movement" {
defer s.deinit(); defer s.deinit();
// Move cursor using escape sequences // 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.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Move to position 2,3 // 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, 2), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
} }
@ -437,13 +449,13 @@ test "erase operations" {
defer s.deinit(); defer s.deinit();
// Print some text // 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, 11), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); 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 // Move cursor to position 1,6 and erase from cursor to end of line
try s.nextSlice("\x1B[1;6H"); s.nextSlice("\x1B[1;6H");
try s.nextSlice("\x1B[K"); s.nextSlice("\x1B[K");
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -457,7 +469,7 @@ test "tabs" {
var s: Stream = .initAlloc(testing.allocator, .init(&t)); var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit(); defer s.deinit();
try s.nextSlice("A\tB"); s.nextSlice("A\tB");
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.x);
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
@ -474,9 +486,9 @@ test "modes" {
// Test wraparound mode // Test wraparound mode
try testing.expect(t.modes.get(.wraparound)); 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 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)); try testing.expect(t.modes.get(.wraparound));
} }
@ -488,7 +500,7 @@ test "scrolling regions" {
defer s.deinit(); defer s.deinit();
// Set scrolling region from line 5 to 20 // 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, 4), t.scrolling_region.top);
try testing.expectEqual(@as(usize, 19), t.scrolling_region.bottom); try testing.expectEqual(@as(usize, 19), t.scrolling_region.bottom);
try testing.expectEqual(@as(usize, 0), t.scrolling_region.left); try testing.expectEqual(@as(usize, 0), t.scrolling_region.left);
@ -503,8 +515,8 @@ test "charsets" {
defer s.deinit(); defer s.deinit();
// Configure G0 as DEC special graphics // Configure G0 as DEC special graphics
try s.nextSlice("\x1B(0"); s.nextSlice("\x1B(0");
try s.nextSlice("`"); // Should print diamond character s.nextSlice("`"); // Should print diamond character
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -519,18 +531,18 @@ test "alt screen" {
defer s.deinit(); defer s.deinit();
// Write to primary screen // Write to primary screen
try s.nextSlice("Primary"); s.nextSlice("Primary");
try testing.expectEqual(.primary, t.screens.active_key); try testing.expectEqual(.primary, t.screens.active_key);
// Switch to alt screen // Switch to alt screen
try s.nextSlice("\x1B[?1049h"); s.nextSlice("\x1B[?1049h");
try testing.expectEqual(.alternate, t.screens.active_key); try testing.expectEqual(.alternate, t.screens.active_key);
// Write to alt screen // Write to alt screen
try s.nextSlice("Alt"); s.nextSlice("Alt");
// Switch back to primary // Switch back to primary
try s.nextSlice("\x1B[?1049l"); s.nextSlice("\x1B[?1049l");
try testing.expectEqual(.primary, t.screens.active_key); try testing.expectEqual(.primary, t.screens.active_key);
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
@ -546,20 +558,20 @@ test "cursor save and restore" {
defer s.deinit(); defer s.deinit();
// Move cursor to 10,15 // 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, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
// Save cursor // Save cursor
try s.nextSlice("\x1B7"); s.nextSlice("\x1B7");
// Move cursor elsewhere // 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.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Restore cursor // 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, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
} }
@ -572,7 +584,7 @@ test "attributes" {
defer s.deinit(); defer s.deinit();
// Set bold and write text // 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 // Verify we can write attributes - just check the string was written
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
@ -588,7 +600,7 @@ test "DECALN screen alignment" {
defer s.deinit(); defer s.deinit();
// Run DECALN // Run DECALN
try s.nextSlice("\x1B#8"); s.nextSlice("\x1B#8");
// Verify entire screen is filled with 'E' // Verify entire screen is filled with 'E'
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
@ -608,13 +620,13 @@ test "full reset" {
defer s.deinit(); defer s.deinit();
// Make some changes // Make some changes
try s.nextSlice("Hello"); s.nextSlice("Hello");
try s.nextSlice("\x1B[10;20H"); s.nextSlice("\x1B[10;20H");
try s.nextSlice("\x1B[5;20r"); // Set scroll region s.nextSlice("\x1B[5;20r"); // Set scroll region
try s.nextSlice("\x1B[?7l"); // Disable wraparound s.nextSlice("\x1B[?7l"); // Disable wraparound
// Full reset // Full reset
try s.nextSlice("\x1Bc"); s.nextSlice("\x1Bc");
// Verify reset state // Verify reset state
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
@ -632,12 +644,12 @@ test "ignores query actions" {
defer s.deinit(); defer s.deinit();
// These should be ignored without error // These should be ignored without error
try s.nextSlice("\x1B[c"); // Device attributes s.nextSlice("\x1B[c"); // Device attributes
try s.nextSlice("\x1B[5n"); // Device status report s.nextSlice("\x1B[5n"); // Device status report
try s.nextSlice("\x1B[6n"); // Cursor position report s.nextSlice("\x1B[6n"); // Cursor position report
// Terminal should still be functional // Terminal should still be functional
try s.nextSlice("Test"); s.nextSlice("Test");
const str = try t.plainString(testing.allocator); const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
try testing.expectEqualStrings("Test", 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]; const default_color_0 = t.colors.palette.original[0];
// Set color 0 to red // 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, 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].g);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].b); try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].b);
try testing.expect(t.colors.palette.mask.isSet(0)); try testing.expect(t.colors.palette.mask.isSet(0));
// Reset color 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.expectEqual(default_color_0, t.colors.palette.current[0]);
try testing.expect(!t.colors.palette.mask.isSet(0)); try testing.expect(!t.colors.palette.mask.isSet(0));
} }
@ -674,15 +686,15 @@ test "OSC 104 reset all palette colors" {
defer s.deinit(); defer s.deinit();
// Set multiple colors // Set multiple colors
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\"); s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
try s.nextSlice("\x1b]4;1;rgb:00/ff/00\x1b\\"); 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;2;rgb:00/00/ff\x1b\\");
try testing.expect(t.colors.palette.mask.isSet(0)); 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(1));
try testing.expect(t.colors.palette.mask.isSet(2)); try testing.expect(t.colors.palette.mask.isSet(2));
// Reset all palette colors // 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[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[1], t.colors.palette.current[1]);
try testing.expectEqual(t.colors.palette.original[2], t.colors.palette.current[2]); 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); try testing.expect(t.colors.foreground.get() == null);
// Set foreground to red // 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().?; const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0xff), fg.r); try testing.expectEqual(@as(u8, 0xff), fg.r);
try testing.expectEqual(@as(u8, 0x00), fg.g); try testing.expectEqual(@as(u8, 0x00), fg.g);
try testing.expectEqual(@as(u8, 0x00), fg.b); try testing.expectEqual(@as(u8, 0x00), fg.b);
// Reset foreground // Reset foreground
try s.nextSlice("\x1b]110\x1b\\"); s.nextSlice("\x1b]110\x1b\\");
try testing.expect(t.colors.foreground.get() == null); try testing.expect(t.colors.foreground.get() == null);
} }
@ -721,14 +733,14 @@ test "OSC 11 set and reset background color" {
defer s.deinit(); defer s.deinit();
// Set background to green // 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().?; const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x00), bg.r); try testing.expectEqual(@as(u8, 0x00), bg.r);
try testing.expectEqual(@as(u8, 0xff), bg.g); try testing.expectEqual(@as(u8, 0xff), bg.g);
try testing.expectEqual(@as(u8, 0x00), bg.b); try testing.expectEqual(@as(u8, 0x00), bg.b);
// Reset background // Reset background
try s.nextSlice("\x1b]111\x1b\\"); s.nextSlice("\x1b]111\x1b\\");
try testing.expect(t.colors.background.get() == null); try testing.expect(t.colors.background.get() == null);
} }
@ -740,14 +752,14 @@ test "OSC 12 set and reset cursor color" {
defer s.deinit(); defer s.deinit();
// Set cursor to blue // 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().?; const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0x00), cursor.r); try testing.expectEqual(@as(u8, 0x00), cursor.r);
try testing.expectEqual(@as(u8, 0x00), cursor.g); try testing.expectEqual(@as(u8, 0x00), cursor.g);
try testing.expectEqual(@as(u8, 0xff), cursor.b); try testing.expectEqual(@as(u8, 0xff), cursor.b);
// Reset cursor // Reset cursor
try s.nextSlice("\x1b]112\x1b\\"); s.nextSlice("\x1b]112\x1b\\");
// After reset, cursor might be null (using default) // After reset, cursor might be null (using default)
} }
@ -759,7 +771,7 @@ test "kitty color protocol set palette" {
defer s.deinit(); defer s.deinit();
// Set palette color 5 to magenta using kitty protocol // 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, 0xff), t.colors.palette.current[5].r);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[5].g); try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[5].g);
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[5].b); 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 // Set and then reset palette color
const original = t.colors.palette.original[7]; 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 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.expectEqual(original, t.colors.palette.current[7]);
try testing.expect(!t.colors.palette.mask.isSet(7)); try testing.expect(!t.colors.palette.mask.isSet(7));
} }
@ -792,7 +804,7 @@ test "kitty color protocol set foreground" {
defer s.deinit(); defer s.deinit();
// Set foreground using kitty protocol // 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().?; const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0x12), fg.r); try testing.expectEqual(@as(u8, 0x12), fg.r);
try testing.expectEqual(@as(u8, 0x34), fg.g); try testing.expectEqual(@as(u8, 0x34), fg.g);
@ -807,7 +819,7 @@ test "kitty color protocol set background" {
defer s.deinit(); defer s.deinit();
// Set background using kitty protocol // 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().?; const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x78), bg.r); try testing.expectEqual(@as(u8, 0x78), bg.r);
try testing.expectEqual(@as(u8, 0x9a), bg.g); try testing.expectEqual(@as(u8, 0x9a), bg.g);
@ -822,7 +834,7 @@ test "kitty color protocol set cursor" {
defer s.deinit(); defer s.deinit();
// Set cursor using kitty protocol // 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().?; const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0xde), cursor.r); try testing.expectEqual(@as(u8, 0xde), cursor.r);
try testing.expectEqual(@as(u8, 0xf0), cursor.g); try testing.expectEqual(@as(u8, 0xf0), cursor.g);
@ -837,10 +849,10 @@ test "kitty color protocol reset foreground" {
defer s.deinit(); defer s.deinit();
// Set and reset foreground // 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 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 // After reset, should be unset
try testing.expect(t.colors.foreground.get() == null); 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; t.flags.dirty.palette = false;
// Setting palette color should set dirty flag // 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); try testing.expect(t.flags.dirty.palette);
// Clear and test reset // Clear and test reset
t.flags.dirty.palette = false; 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); try testing.expect(t.flags.dirty.palette);
// Clear and test kitty protocol // Clear and test kitty protocol
t.flags.dirty.palette = false; 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); try testing.expect(t.flags.dirty.palette);
} }
@ -877,8 +889,8 @@ test "semantic prompt fresh line" {
var s: Stream = .initAlloc(testing.allocator, .init(&t)); var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit(); defer s.deinit();
try s.nextSlice("Hello"); s.nextSlice("Hello");
try s.nextSlice("\x1b]133;L\x07"); s.nextSlice("\x1b]133;L\x07");
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); 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(); defer s.deinit();
// Write some text and then send OSC 133;A (fresh_line_new_prompt) // Write some text and then send OSC 133;A (fresh_line_new_prompt)
try s.nextSlice("Hello"); s.nextSlice("Hello");
try s.nextSlice("\x1b]133;A\x07"); s.nextSlice("\x1b]133;A\x07");
// Should do a fresh line (carriage return + index) // Should do a fresh line (carriage return + index)
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); 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); try testing.expectEqual(.prompt, t.screens.active.cursor.semantic_content);
// Test with redraw option // Test with redraw option
try s.nextSlice("prompt$ "); s.nextSlice("prompt$ ");
try s.nextSlice("\x1b]133;A;redraw=1\x07"); s.nextSlice("\x1b]133;A;redraw=1\x07");
try testing.expect(t.flags.shell_redraws_prompt == .true); 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(); defer s.deinit();
// Write some text and then send OSC 133;A (fresh_line_new_prompt) // Write some text and then send OSC 133;A (fresh_line_new_prompt)
try s.nextSlice("Hello"); s.nextSlice("Hello");
try s.nextSlice("\x1b]133;A\x07"); s.nextSlice("\x1b]133;A\x07");
try s.nextSlice("prompt$ "); s.nextSlice("prompt$ ");
try s.nextSlice("\x1b]133;B\x07"); s.nextSlice("\x1b]133;B\x07");
try testing.expectEqual(.input, t.screens.active.cursor.semantic_content); 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); try testing.expectEqual(.output, t.screens.active.cursor.semantic_content);
} }
@ -932,10 +944,10 @@ test "semantic prompt prompt_start" {
defer s.deinit(); defer s.deinit();
// Write some text // Write some text
try s.nextSlice("Hello"); s.nextSlice("Hello");
// OSC 133;P marks the start of a prompt (without fresh line behavior) // 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(.prompt, t.screens.active.cursor.semantic_content);
try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
@ -949,8 +961,8 @@ test "semantic prompt new_command" {
defer s.deinit(); defer s.deinit();
// Write some text // Write some text
try s.nextSlice("Hello"); s.nextSlice("Hello");
try s.nextSlice("\x1b]133;N\x07"); s.nextSlice("\x1b]133;N\x07");
// Should behave like fresh_line_new_prompt - cursor moves to column 0 // Should behave like fresh_line_new_prompt - cursor moves to column 0
// on next line since we had content // on next line since we had content
@ -967,7 +979,7 @@ test "semantic prompt new_command at column zero" {
defer s.deinit(); defer s.deinit();
// OSC 133;N when already at column 0 should stay on same line // 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.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
try testing.expectEqual(.prompt, t.screens.active.cursor.semantic_content); 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(); defer s.deinit();
// Set input terminated by EOL // 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); try testing.expectEqual(.input, t.screens.active.cursor.semantic_content);
// Linefeed should reset semantic content to output // Linefeed should reset semantic content to output
try s.nextSlice("\n"); s.nextSlice("\n");
try testing.expectEqual(.output, t.screens.active.cursor.semantic_content); 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)); var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit(); 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. // correct but we'll get the active contents soon.
var stream = t.vtStream(); var stream = t.vtStream();
defer stream.deinit(); defer stream.deinit();
stream.nextSlice(content) catch |err| { stream.nextSlice(content);
log.info("failed to process pane history for pane id={}: {}", .{ id, err });
return err;
};
// Populate the active area to be empty since this is only history. // 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. // 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(); var stream = t.vtStream();
defer stream.deinit(); defer stream.deinit();
stream.nextSlice(content) catch |err| { stream.nextSlice(content);
log.info("failed to process pane visible for pane id={}: {}", .{ id, err });
return err;
};
} }
fn receivedOutput( fn receivedOutput(
@ -1117,10 +1111,7 @@ pub const Viewer = struct {
var stream = t.vtStream(); var stream = t.vtStream();
defer stream.deinit(); defer stream.deinit();
stream.nextSlice(data) catch |err| { stream.nextSlice(data);
log.info("failed to process output for pane id={}: {}", .{ id, err });
return err;
};
} }
fn initLayout( 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}); log.err("error recording pty read in inspector err={}", .{err});
}; };
self.terminal_stream.next(byte) catch |err| self.terminal_stream.next(byte);
log.err("error processing terminal data: {}", .{err});
} }
} else { } else {
self.terminal_stream.nextSlice(buf) catch |err| self.terminal_stream.nextSlice(buf);
log.err("error processing terminal data: {}", .{err});
} }
// If our stream handling caused messages to be sent to the mailbox // If our stream handling caused messages to be sent to the mailbox

View File

@ -176,6 +176,16 @@ pub const StreamHandler = struct {
self: *StreamHandler, self: *StreamHandler,
comptime action: Stream.Action.Tag, comptime action: Stream.Action.Tag,
value: Stream.Action.Value(action), 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 { ) !void {
// The branch hints here are based on real world data // The branch hints here are based on real world data
// which indicates that the most common actions are: // which indicates that the most common actions are:

View File

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