terminal: make stream processing infallible

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

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

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

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

View File

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

View File

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