From afb351f8385d8b895671cb398d13fb39e06611f4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 8 Aug 2026 08:14:58 -0700 Subject: [PATCH] terminal/stream: fast-path APC termination APC payload bytes are bulk consumed, but the terminating byte still passed through the generic parser action loop. Handle ESC and C1 ST directly after bulk consumption while leaving other transitions on the scalar path. --- src/terminal/stream.zig | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 65c7cc5e0..37200a2c7 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -766,8 +766,26 @@ pub fn Stream(comptime H: type) type { if (self.parser.state == .sos_pm_apc_string) { offset += self.consumeApcString(input[offset..]); if (offset >= input.len) return input.len; - // The next byte exits the string state; let - // nextNonUtf8 below handle it. + + // Fast-path normal string termination. This matches + // Parser.next's exit and entry actions while avoiding + // the generic action loop for every completed APC. + switch (input[offset]) { + std.ascii.control_code.esc => { + self.parser.clear(); + self.parser.state = .escape; + self.handler.vt(.apc_end, {}); + offset += 1; + continue; + }, + 0x9C => { + self.parser.state = .ground; + self.handler.vt(.apc_end, {}); + offset += 1; + continue; + }, + else => {}, + } } } @@ -3989,6 +4007,18 @@ test "stream: apc bulk slice" { } } +test "stream: apc bulk slice C1 ST" { + var s: Stream(ApcTestHandler) = .init(.{ .handler = .{} }); + s.nextSlice("\x1b_Gpayload\x9c"); + + try testing.expectEqual(@as(usize, 1), s.handler.started); + try testing.expectEqual(@as(usize, 1), s.handler.ended); + try testing.expectEqualStrings( + "Gpayload", + s.handler.buf[0..s.handler.len], + ); +} + test "stream: apc bulk slice split across inputs" { var s: Stream(ApcTestHandler) = .init(.{ .handler = .{} }); s.nextSlice("\x1b_Gf=24,s=10");