diff --git a/src/terminal/snapshot/snapshot.zig b/src/terminal/snapshot/snapshot.zig index a6b3146aa..2153802b4 100644 --- a/src/terminal/snapshot/snapshot.zig +++ b/src/terminal/snapshot/snapshot.zig @@ -413,6 +413,12 @@ pub const Decoder = struct { page.DecodeError || Allocator.Error || error{ + /// `next` was called before `ready` completed successfully. + DecoderNotReady, + + /// A prior `next` call failed and invalidated the stream position. + DecoderFailed, + /// A HISTORY names a key not declared by TERMINAL. UnexpectedHistoryKey, @@ -457,7 +463,8 @@ pub const Decoder = struct { switch (self.state) { // `ready` must succeed before history exists to decode, and a // failed decoder no longer knows its stream position. - .start, .failed => unreachable, + .start => return error.DecoderNotReady, + .failed => return error.DecoderFailed, .finished => return null, .history => {}, } @@ -1774,6 +1781,23 @@ test "incremental decode restores a renderable terminal at READY" { try testing.expectEqualStrings(&test_complete_fixture, reencoded.written()); } +test "incremental next reports invalid decoder states" { + const testing = std.testing; + var source: std.Io.Reader = .fixed(""); + var decoder: Decoder = .init(&source); + var terminal_value = try Terminal.init( + testing.io, + testing.allocator, + .{ .cols = 1, .rows = 1 }, + ); + defer terminal_value.deinit(testing.allocator); + + try testing.expectError( + error.DecoderNotReady, + decoder.next(testing.allocator, &terminal_value), + ); +} + test "incremental decode applies history below live PTY output" { const testing = std.testing; @@ -2065,6 +2089,10 @@ test "incremental decode failure leaves applied history usable" { error.EndOfStream, decoder.next(testing.allocator, &restored), ); + try testing.expectError( + error.DecoderFailed, + decoder.next(testing.allocator, &restored), + ); // The transferred terminal was never owned by the decoder: it remains // valid with the contiguous history prefix that did apply, and only