diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 24a72a44a..15a372b2e 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -611,12 +611,88 @@ pub fn Stream(comptime H: type) type { var offset: usize = 0; while (self.parser.state != .ground) { if (offset >= input.len) return input.len; + + // Bulk-consume CSI parameter bytes. This can't be used + // for handlers with a vtRaw hook because it dispatches + // the CSI directly (see nextNonUtf8). + if (comptime !@hasDecl(T, "vtRaw")) { + if (self.parser.state == .csi_param) { + offset += self.consumeCsiParams(input[offset..]); + if (offset >= input.len) return input.len; + // If we're still in csi_param then the next byte + // isn't a parameter byte; let nextNonUtf8 below + // handle it. Otherwise re-check our state. + if (self.parser.state != .csi_param) continue; + } + } + self.nextNonUtf8(input[offset]); offset += 1; } return offset; } + /// Bulk-consume CSI parameter bytes (digits and separators) + /// and, if reached, the final byte (dispatching the CSI). + /// Returns the number of bytes consumed. Stops at the first + /// byte that isn't handled here, leaving the parser in the + /// csi_param state so the caller can process that byte. + fn consumeCsiParams(self: *Self, input: []const u8) usize { + const p = &self.parser; + assert(p.state == .csi_param); + + // Accumulate parser state in locals for the hot loop. + var acc = p.param_acc; + var acc_idx = p.param_acc_idx; + var idx = p.params_idx; + + var offset: usize = 0; + while (offset < input.len) { + const c = input[offset]; + switch (c) { + // A parameter digit. + '0'...'9' => { + if (idx < Parser.MAX_PARAMS) { + acc *|= 10; + acc +|= c - '0'; + acc_idx |= 1; + } + offset += 1; + }, + + // A parameter separator. + ':', ';' => { + if (idx < Parser.MAX_PARAMS) { + p.params[idx] = acc; + if (c == ':') p.params_sep.set(idx); + idx += 1; + acc = 0; + acc_idx = 0; + } + offset += 1; + }, + + // A final byte: dispatch the CSI. + 0x40...0x7E => { + p.param_acc = acc; + p.param_acc_idx = acc_idx; + p.params_idx = idx; + self.csiDispatchFinal(c); + return offset + 1; + }, + + // Anything else (C0 controls, intermediates, etc.) + // is handled by the caller. + else => break, + } + } + + p.param_acc = acc; + p.param_acc_idx = acc_idx; + p.params_idx = idx; + return offset; + } + /// Like nextSlice but takes one byte and is necessarily a scalar /// operation that can't use SIMD. Prefer nextSlice if you can and /// try to get multiple bytes at once.