//! An ANSI VT Parser const Parser = @This(); const std = @import("std"); const Reader = std.Io.Reader; const ansi = @import("ansi.zig"); /// A terminal event const Event = union(enum) { print: []const u8, c0: ansi.C0, escape: []const u8, ss2: u8, ss3: u8, csi: ansi.CSI, osc: []const u8, apc: []const u8, }; buf: std.array_list.Managed(u8), /// a leftover byte from a ground event pending_byte: ?u8 = null, pub fn parseReader(self: *Parser, reader: *Reader) !Event { self.buf.clearRetainingCapacity(); while (true) { const b = if (self.pending_byte) |p| p else try reader.takeByte(); self.pending_byte = null; switch (b) { // Escape sequence 0x1b => { const next = try reader.takeByte(); switch (next) { 0x4E => return .{ .ss2 = try reader.takeByte() }, 0x4F => return .{ .ss3 = try reader.takeByte() }, 0x50 => try skipUntilST(reader), // DCS 0x58 => try skipUntilST(reader), // SOS 0x5B => return self.parseCsi(reader), // CSI 0x5D => return self.parseOsc(reader), // OSC 0x5E => try skipUntilST(reader), // PM 0x5F => return self.parseApc(reader), // APC 0x20...0x2F => { try self.buf.append(next); return self.parseEscape(reader); // ESC }, else => { try self.buf.append(next); return .{ .escape = self.buf.items }; }, } }, // C0 control 0x00...0x1a, 0x1c...0x1f, => return .{ .c0 = @enumFromInt(b) }, else => { try self.buf.append(b); return self.parseGround(reader); }, } } } inline fn parseGround(self: *Parser, reader: *Reader) !Event { var buf: [1]u8 = undefined; { std.debug.assert(self.buf.items.len > 0); // Handle first byte const len = try std.unicode.utf8ByteSequenceLength(self.buf.items[0]); var i: usize = 1; while (i < len) : (i += 1) { const read = try reader.readSliceShort(&buf); if (read == 0) return error.EOF; try self.buf.append(buf[0]); } } while (true) { if (reader.bufferedLen() == 0) return .{ .print = self.buf.items }; const n = try reader.readSliceShort(&buf); if (n == 0) return error.EOF; const b = buf[0]; switch (b) { 0x00...0x1f => { self.pending_byte = b; return .{ .print = self.buf.items }; }, else => { try self.buf.append(b); const len = try std.unicode.utf8ByteSequenceLength(b); var i: usize = 1; while (i < len) : (i += 1) { const read = try reader.readSliceShort(&buf); if (read == 0) return error.EOF; try self.buf.append(buf[0]); } }, } } } /// parse until b >= 0x30 inline fn parseEscape(self: *Parser, reader: *Reader) !Event { while (true) { const b = try reader.takeByte(); switch (b) { 0x20...0x2F => continue, else => { try self.buf.append(b); return .{ .escape = self.buf.items }; }, } } } inline fn parseApc(self: *Parser, reader: *Reader) !Event { while (true) { const b = try reader.takeByte(); switch (b) { 0x00...0x17, 0x19, 0x1c...0x1f, => continue, 0x1b => { _ = try reader.discard(std.Io.Limit.limited(1)); return .{ .apc = self.buf.items }; }, else => try self.buf.append(b), } } } /// Skips sequences until we see an ST (String Terminator, ESC \) inline fn skipUntilST(reader: *Reader) !void { _ = try reader.discardDelimiterExclusive('\x1b'); _ = try reader.discard(std.Io.Limit.limited(1)); } /// Parses an OSC sequence inline fn parseOsc(self: *Parser, reader: *Reader) !Event { while (true) { const b = try reader.takeByte(); switch (b) { 0x00...0x06, 0x08...0x17, 0x19, 0x1c...0x1f, => continue, 0x1b => { _ = try reader.discard(std.Io.Limit.limited(1)); return .{ .osc = self.buf.items }; }, 0x07 => return .{ .osc = self.buf.items }, else => try self.buf.append(b), } } } inline fn parseCsi(self: *Parser, reader: *Reader) !Event { var intermediate: ?u8 = null; var pm: ?u8 = null; while (true) { const b = try reader.takeByte(); switch (b) { 0x20...0x2F => intermediate = b, 0x30...0x3B => try self.buf.append(b), 0x3C...0x3F => pm = b, // we only allow one // Really we should execute C0 controls, but we just ignore them 0x40...0xFF => return .{ .csi = .{ .intermediate = intermediate, .private_marker = pm, .params = self.buf.items, .final = b, }, }, else => continue, } } }