From c96dd58401c237f4b77405ce221fcb9912d72144 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 15 Jul 2025 15:13:42 -0500 Subject: [PATCH] osc: once again, fix up osd color operations Fixes #7951 PR #7429 did a lot to improve handling of various OSC escape codes that get/set/reset colors. However, it didn't get it _quite_ right. This PR fixes OSC color handling to hopefully get things closer to perfect. --- src/Surface.zig | 26 +- src/terminal/Parser.zig | 3 +- src/terminal/osc.zig | 1098 +++++++++++++++++++++++++++++---- src/terminal/stream.zig | 2 +- src/termio/stream_handler.zig | 192 +++--- 5 files changed, 1122 insertions(+), 199 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index af0a742c6..321750676 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -857,7 +857,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { }, .unlocked); }, - .color_change => |change| { + .color_change => |change| color_change: { // Notify our apprt, but don't send a mode 2031 DSR report // because VT sequences were used to change the color. _ = try self.rt_app.performAction( @@ -865,10 +865,26 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { .color_change, .{ .kind = switch (change.kind) { - .background => .background, - .foreground => .foreground, - .cursor => .cursor, - .palette => |v| @enumFromInt(v), + .palette => |color| @enumFromInt(color), + .dynamic_color => |color| switch (color) { + .foreground => .background, + .background => .foreground, + .cursor => .cursor, + .pointer_foreground, + .pointer_background, + .tektronix_foreground, + .tektronix_background, + .highlight_background, + .tektronix_cursor, + .highlight_foreground, + => { + log.warn("changing dynamic color {d} ({s}) is not implemented", .{ + @intFromEnum(color), + @tagName(color), + }); + break :color_change; + }, + }, }, .r = change.color.r, .g = change.color.g, diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index ec3f322f6..ecd538a32 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -897,14 +897,13 @@ test "osc: 112 incomplete sequence" { const cmd = a[0].?.osc_dispatch; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .bel); - try testing.expect(cmd.color_operation.source == .reset_cursor); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .reset); try testing.expectEqual( - osc.Command.ColorOperation.Kind.cursor, + osc.Command.ColorOperation.Kind{ .dynamic_color = .cursor }, op.reset, ); } diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index d0b59e834..e7514f2b1 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -119,9 +119,15 @@ pub const Command = union(enum) { /// /// 4, 10, 11, 12, 104, 110, 111, 112 color_operation: struct { - source: ColorOperation.Source, operations: ColorOperation.List = .{}, terminator: Terminator = .st, + next_dynamic_color: ?ColorOperation.DynamicColor = null, + + pub fn incrementNextDynamicColor(self: *@This()) void { + if (self.next_dynamic_color) |color| { + self.next_dynamic_color = color.next() catch null; + } + } }, /// Kitty color protocol, OSC 21 @@ -167,35 +173,47 @@ pub const Command = union(enum) { wait_input: void, pub const ColorOperation = union(enum) { - pub const Source = enum(u16) { - // these numbers are based on the OSC operation code - // see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands - get_set_palette = 4, - get_set_foreground = 10, - get_set_background = 11, - get_set_cursor = 12, - reset_palette = 104, - reset_foreground = 110, - reset_background = 111, - reset_cursor = 112, + pub const List = std.SegmentedList(ColorOperation, 2); + + pub const DynamicColor = enum(u8) { + foreground = 10, + background = 11, + cursor = 12, + pointer_foreground = 13, + pointer_background = 14, + tektronix_foreground = 15, + tektronix_background = 16, + highlight_background = 17, + tektronix_cursor = 18, + highlight_foreground = 19, pub fn format( - self: Source, + self: DynamicColor, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { try std.fmt.formatInt(@intFromEnum(self), 10, .lower, options, writer); } - }; - pub const List = std.SegmentedList(ColorOperation, 2); + pub fn next(self: DynamicColor) error{IllegalDynamicColor}!DynamicColor { + if (self == .highlight_foreground) return error.IllegalDynamicColor; + return @enumFromInt(@intFromEnum(self) + 1); + } + + test "DynamicColor" { + { + try std.testing.expectEqual(.background, DynamicColor.foreground.next()); + } + { + try std.testing.expectError(error.IllegalDynamicColor, DynamicColor.highlight_foreground.next()); + } + } + }; pub const Kind = union(enum) { palette: u8, - foreground, - background, - cursor, + dynamic_color: DynamicColor, }; set: struct { @@ -319,6 +337,12 @@ pub const Parser = struct { @"12", @"13", @"133", + @"14", + @"15", + @"16", + @"17", + @"18", + @"19", @"2", @"21", @"22", @@ -357,6 +381,27 @@ pub const Parser = struct { // Get/set cursor color osc_12, + // Get/set pointer foreground color + osc_13, + + // Get/set pointer background color + osc_14, + + // Get/set tektronix foreground color + osc_15, + + // Get/set tektronix background color + osc_16, + + // Get/set highlight background color + osc_17, + + // Get/set tektronix cursor color + osc_18, + + // Get/set highlight foreground color + osc_19, + // Reset color palette index osc_104, @@ -529,6 +574,12 @@ pub const Parser = struct { '1' => self.state = .@"11", '2' => self.state = .@"12", '3' => self.state = .@"13", + '4' => self.state = .@"14", + '5' => self.state = .@"15", + '6' => self.state = .@"16", + '7' => self.state = .@"17", + '8' => self.state = .@"18", + '9' => self.state = .@"19", else => self.state = .invalid, }, @@ -541,7 +592,7 @@ pub const Parser = struct { } self.command = .{ .color_operation = .{ - .source = .get_set_foreground, + .next_dynamic_color = .foreground, }, }; self.state = .osc_10; @@ -552,8 +603,18 @@ pub const Parser = struct { else => self.state = .invalid, }, - .osc_10, .osc_11, .osc_12 => switch (c) { - ';' => self.parseOSC101112(false), + .osc_10, + .osc_11, + .osc_12, + .osc_13, + .osc_14, + .osc_15, + .osc_16, + .osc_17, + .osc_18, + .osc_19, + => switch (c) { + ';' => self.parseGetSetDynamicColor(false), else => {}, }, @@ -565,9 +626,7 @@ pub const Parser = struct { break :osc_104; } self.command = .{ - .color_operation = .{ - .source = .reset_palette, - }, + .color_operation = .{}, }; self.state = .osc_104; self.buf_start = self.buf_idx; @@ -577,7 +636,7 @@ pub const Parser = struct { }, .osc_104 => switch (c) { - ';' => self.parseOSC104(false), + ';' => self.parseResetPalette(false), else => {}, }, @@ -590,14 +649,14 @@ pub const Parser = struct { } self.command = .{ .color_operation = .{ - .source = .get_set_background, + .next_dynamic_color = .background, }, }; self.state = .osc_11; self.buf_start = self.buf_idx; self.complete = true; }, - '0'...'2' => blk: { + '0'...'9' => blk: { if (self.alloc == null) { log.warn("OSC 11{c} requires an allocator, but none was provided", .{c}); self.state = .invalid; @@ -606,25 +665,23 @@ pub const Parser = struct { const alloc = self.alloc orelse return; - self.command = .{ - .color_operation = .{ - .source = switch (c) { - '0' => .reset_foreground, - '1' => .reset_background, - '2' => .reset_cursor, - else => unreachable, - }, - }, - }; + self.command = .{ .color_operation = .{} }; const op = self.command.color_operation.operations.addOne(alloc) catch |err| { log.warn("unable to append color operation: {}", .{err}); return; }; op.* = .{ .reset = switch (c) { - '0' => .foreground, - '1' => .background, - '2' => .cursor, + '0' => .{ .dynamic_color = .foreground }, + '1' => .{ .dynamic_color = .background }, + '2' => .{ .dynamic_color = .cursor }, + '3' => .{ .dynamic_color = .pointer_foreground }, + '4' => .{ .dynamic_color = .pointer_background }, + '5' => .{ .dynamic_color = .tektronix_foreground }, + '6' => .{ .dynamic_color = .tektronix_background }, + '7' => .{ .dynamic_color = .highlight_background }, + '8' => .{ .dynamic_color = .tektronix_cursor }, + '9' => .{ .dynamic_color = .highlight_foreground }, else => unreachable, }, }; @@ -643,7 +700,7 @@ pub const Parser = struct { } self.command = .{ .color_operation = .{ - .source = .get_set_cursor, + .next_dynamic_color = .cursor, }, }; self.state = .osc_12; @@ -655,6 +712,21 @@ pub const Parser = struct { .@"13" => switch (c) { '3' => self.state = .@"133", + ';' => osc_13: { + if (self.alloc == null) { + log.warn("OSC 13 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_13; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .pointer_foreground, + }, + }; + self.state = .osc_13; + self.buf_start = self.buf_idx; + self.complete = true; + }, else => self.state = .invalid, }, @@ -663,6 +735,120 @@ pub const Parser = struct { else => self.state = .invalid, }, + .@"14" => switch (c) { + ';' => osc_14: { + if (self.alloc == null) { + log.warn("OSC 14 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_14; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .pointer_background, + }, + }; + self.state = .osc_14; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + + .@"15" => switch (c) { + ';' => osc_15: { + if (self.alloc == null) { + log.warn("OSC 15 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_15; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .tektronix_foreground, + }, + }; + self.state = .osc_15; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + + .@"16" => switch (c) { + ';' => osc_16: { + if (self.alloc == null) { + log.warn("OSC 16 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_16; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .tektronix_background, + }, + }; + self.state = .osc_16; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + + .@"17" => switch (c) { + ';' => osc_17: { + if (self.alloc == null) { + log.warn("OSC 17 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_17; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .highlight_background, + }, + }; + self.state = .osc_17; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + + .@"18" => switch (c) { + ';' => osc_18: { + if (self.alloc == null) { + log.warn("OSC 18 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_18; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .tektronix_cursor, + }, + }; + self.state = .osc_18; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + + .@"19" => switch (c) { + ';' => osc_19: { + if (self.alloc == null) { + log.warn("OSC 19 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_19; + } + self.command = .{ + .color_operation = .{ + .next_dynamic_color = .highlight_foreground, + }, + }; + self.state = .osc_19; + self.buf_start = self.buf_idx; + self.complete = true; + }, + else => self.state = .invalid, + }, + .@"2" => switch (c) { '1' => self.state = .@"21", '2' => self.state = .@"22", @@ -741,9 +927,7 @@ pub const Parser = struct { break :osc_4; } self.command = .{ - .color_operation = .{ - .source = .get_set_palette, - }, + .color_operation = .{}, }; self.state = .osc_4_index; self.buf_start = self.buf_idx; @@ -759,7 +943,7 @@ pub const Parser = struct { .osc_4_color => switch (c) { ';' => { - self.parseOSC4(false); + self.parseGetSetPalette(false); self.state = .osc_4_index; }, else => {}, @@ -1357,10 +1541,10 @@ pub const Parser = struct { self.temp_state.str.* = list.items; } - fn parseOSC4(self: *Parser, final: bool) void { + /// Handle parsing OSC 4 + fn parseGetSetPalette(self: *Parser, final: bool) void { assert(self.state == .osc_4_color); assert(self.command == .color_operation); - assert(self.command.color_operation.source == .get_set_palette); const alloc = self.alloc orelse return; const operations = &self.command.color_operation.operations; @@ -1412,33 +1596,33 @@ pub const Parser = struct { } } - fn parseOSC101112(self: *Parser, final: bool) void { + /// Parse OSC 10-19 + fn parseGetSetDynamicColor(self: *Parser, final: bool) void { assert(switch (self.state) { - .osc_10, .osc_11, .osc_12 => true, + .osc_10, + .osc_11, + .osc_12, + .osc_13, + .osc_14, + .osc_15, + .osc_16, + .osc_17, + .osc_18, + .osc_19, + => true, else => false, }); assert(self.command == .color_operation); - assert(self.command.color_operation.source == switch (self.state) { - .osc_10 => Command.ColorOperation.Source.get_set_foreground, - .osc_11 => Command.ColorOperation.Source.get_set_background, - .osc_12 => Command.ColorOperation.Source.get_set_cursor, - else => unreachable, - }); - const spec_str = self.buf[self.buf_start .. self.buf_idx - (1 - @intFromBool(final))]; - - if (self.command.color_operation.operations.count() > 0) { - // don't emit the warning if the string is empty - if (spec_str.len == 0) return; - - log.warn("OSC 1{s} can only accept 1 color", .{switch (self.state) { - .osc_10 => "0", - .osc_11 => "1", - .osc_12 => "2", - else => unreachable, - }}); + const dynamic_color = self.command.color_operation.next_dynamic_color orelse { + log.warn("impossible next dynamic color", .{}); return; - } + }; + + const buf_end = self.buf_idx - (1 - @intFromBool(final)); + const spec_str = self.buf[self.buf_start..buf_end]; + + self.buf_start = self.buf_idx; if (spec_str.len == 0) { log.warn("OSC 1{s} requires an argument", .{switch (self.state) { @@ -1459,11 +1643,8 @@ pub const Parser = struct { return; }; op.* = .{ - .report = switch (self.state) { - .osc_10 => .foreground, - .osc_11 => .background, - .osc_12 => .cursor, - else => unreachable, + .report = .{ + .dynamic_color = dynamic_color, }, }; } else { @@ -1486,22 +1667,21 @@ pub const Parser = struct { }; op.* = .{ .set = .{ - .kind = switch (self.state) { - .osc_10 => .foreground, - .osc_11 => .background, - .osc_12 => .cursor, - else => unreachable, + .kind = .{ + .dynamic_color = dynamic_color, }, .color = color, }, }; } + + self.command.color_operation.incrementNextDynamicColor(); } - fn parseOSC104(self: *Parser, final: bool) void { + /// Parse OSC 104 + fn parseResetPalette(self: *Parser, final: bool) void { assert(self.state == .osc_104); assert(self.command == .color_operation); - assert(self.command.color_operation.source == .reset_palette); const alloc = self.alloc orelse return; @@ -1547,9 +1727,19 @@ pub const Parser = struct { .allocable_string => self.endAllocableString(), .kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true), .kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true), - .osc_4_color => self.parseOSC4(true), - .osc_10, .osc_11, .osc_12 => self.parseOSC101112(true), - .osc_104 => self.parseOSC104(true), + .osc_4_color => self.parseGetSetPalette(true), + .osc_10, + .osc_11, + .osc_12, + .osc_13, + .osc_14, + .osc_15, + .osc_16, + .osc_17, + .osc_18, + .osc_19, + => self.parseGetSetDynamicColor(true), + .osc_104 => self.parseResetPalette(true), else => {}, } @@ -1776,14 +1966,13 @@ test "OSC: OSC110: reset foreground color" { const cmd = p.end(null).?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .reset_foreground); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .reset); try testing.expectEqual( - Command.ColorOperation.Kind.foreground, + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, op.reset, ); } @@ -1802,14 +1991,13 @@ test "OSC: OSC111: reset background color" { const cmd = p.end(null).?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .reset_background); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .reset); try testing.expectEqual( - Command.ColorOperation.Kind.background, + Command.ColorOperation.Kind{ .dynamic_color = .background }, op.reset, ); } @@ -1828,14 +2016,13 @@ test "OSC: OSC112: reset cursor color" { const cmd = p.end(null).?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .reset_cursor); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .reset); try testing.expectEqual( - Command.ColorOperation.Kind.cursor, + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, op.reset, ); } @@ -1855,14 +2042,188 @@ test "OSC: OSC112: reset cursor color with semicolon" { const cmd = p.end(0x07).?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .bel); - try testing.expect(cmd.color_operation.source == .reset_cursor); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .reset); try testing.expectEqual( - Command.ColorOperation.Kind.cursor, + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC113: reset pointer foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "113"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_foreground }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC114: reset pointer background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "114"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_background }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC115: reset tektronix foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "115"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_foreground }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC116: reset tektronix background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "116"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_background }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC117: reset highlight background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "117"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_background }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC118: reset tektronix cursor color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "118"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_cursor }, + op.reset, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC119: reset highlight foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "119"; + for (input) |ch| p.next(ch); + + const cmd = p.end(null).?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .reset); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_foreground }, op.reset, ); } @@ -1976,14 +2337,142 @@ test "OSC: OSC10: report foreground color" { try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .get_set_foreground); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .report); try testing.expectEqual( - Command.ColorOperation.Kind.foreground, + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, + op.report, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC10: report multiple 1" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "10;?;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = ESC followed by \ + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 2); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .background }, + op.report, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC10: report multiple 2" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "10;?;?;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = ESC followed by \ + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 3); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .background }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, + op.report, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC10: report multiple 3" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "10;?;?;?;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = ESC followed by \ + const cmd = p.end('\x1b').?; + + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 4); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .background }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, + op.report, + ); + } + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_foreground }, op.report, ); } @@ -2002,14 +2491,13 @@ test "OSC: OSC10: set foreground color" { const cmd = p.end('\x07').?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .bel); - try testing.expect(cmd.color_operation.source == .get_set_foreground); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .set); try testing.expectEqual( - Command.ColorOperation.Kind.foreground, + Command.ColorOperation.Kind{ .dynamic_color = .foreground }, op.set.kind, ); try testing.expectEqual( @@ -2033,14 +2521,13 @@ test "OSC: OSC11: report background color" { const cmd = p.end('\x07').?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .bel); - try testing.expect(cmd.color_operation.source == .get_set_background); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .report); try testing.expectEqual( - Command.ColorOperation.Kind.background, + Command.ColorOperation.Kind{ .dynamic_color = .background }, op.report, ); } @@ -2060,14 +2547,13 @@ test "OSC: OSC11: set background color" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .get_set_background); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .set); try testing.expectEqual( - Command.ColorOperation.Kind.background, + Command.ColorOperation.Kind{ .dynamic_color = .background }, op.set.kind, ); try testing.expectEqual( @@ -2091,14 +2577,13 @@ test "OSC: OSC12: report cursor color" { const cmd = p.end('\x07').?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .bel); - try testing.expect(cmd.color_operation.source == .get_set_cursor); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .report); try testing.expectEqual( - Command.ColorOperation.Kind.cursor, + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, op.report, ); } @@ -2118,14 +2603,405 @@ test "OSC: OSC12: set cursor color" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); try testing.expectEqual(cmd.color_operation.terminator, .st); - try testing.expect(cmd.color_operation.source == .get_set_cursor); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { const op = it.next().?; try testing.expect(op.* == .set); try testing.expectEqual( - Command.ColorOperation.Kind.cursor, + Command.ColorOperation.Kind{ .dynamic_color = .cursor }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC13: report pointer foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "13;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_foreground }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC13: set pointer foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "13;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_foreground }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC14: report pointer background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "14;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_background }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC14: set pointer background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "14;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .pointer_background }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC15: report tektronix foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "15;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_foreground }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC15: set tektronix foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "15;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_foreground }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC16: report tektronix background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "16;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_background }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC16: set tektronix background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "16;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_background }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC17: report highlight background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "17;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_background }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC17: set highlight background color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "17;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_background }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC18: report tektronix cursor color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "18;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_cursor }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC18: set tektronix cursor color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "18;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .tektronix_cursor }, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } + try testing.expect(it.next() == null); +} + +test "OSC: OSC19: report highlight foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "19;?"; + for (input) |ch| p.next(ch); + + // This corresponds to ST = BEL character + const cmd = p.end('\x07').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .report); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_foreground }, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(it.next() == null); +} + +test "OSC: OSC19: set highlight foreground color" { + const testing = std.testing; + + var p: Parser = .{ .alloc = testing.allocator }; + defer p.deinit(); + + const input = "19;rgb:f/ff/ffff"; + for (input) |ch| p.next(ch); + + const cmd = p.end('\x1b').?; + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.operations.count() == 1); + var it = cmd.color_operation.operations.constIterator(0); + { + const op = it.next().?; + try testing.expect(op.* == .set); + try testing.expectEqual( + Command.ColorOperation.Kind{ .dynamic_color = .highlight_foreground }, op.set.kind, ); try testing.expectEqual( @@ -2147,7 +3023,6 @@ test "OSC: OSC4: get palette color 1" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2173,7 +3048,6 @@ test "OSC: OSC4: get palette color 2" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 2); var it = cmd.color_operation.operations.constIterator(0); { @@ -2207,7 +3081,6 @@ test "OSC: OSC4: set palette color 1" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2236,7 +3109,6 @@ test "OSC: OSC4: set palette color 2" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 2); var it = cmd.color_operation.operations.constIterator(0); { @@ -2277,7 +3149,6 @@ test "OSC: OSC4: get with invalid index 1" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2302,7 +3173,6 @@ test "OSC: OSC4: get with invalid index 2" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 2); var it = cmd.color_operation.operations.constIterator(0); { @@ -2336,7 +3206,6 @@ test "OSC: OSC4: multiple get 8a" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 8); var it = cmd.color_operation.operations.constIterator(0); { @@ -2418,7 +3287,6 @@ test "OSC: OSC4: multiple get 8b" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 8); var it = cmd.color_operation.operations.constIterator(0); { @@ -2499,7 +3367,6 @@ test "OSC: OSC4: set with invalid index" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2528,7 +3395,6 @@ test "OSC: OSC4: mix get/set palette color" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 2); var it = cmd.color_operation.operations.constIterator(0); { @@ -2565,7 +3431,6 @@ test "OSC: OSC4: incomplete color/spec 1" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 0); var it = cmd.color_operation.operations.constIterator(0); try testing.expect(it.next() == null); @@ -2582,7 +3447,6 @@ test "OSC: OSC4: incomplete color/spec 2" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .get_set_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2607,7 +3471,6 @@ test "OSC: OSC104: reset palette color 1" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .reset_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2632,7 +3495,6 @@ test "OSC: OSC104: reset palette color 2" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .reset_palette); try testing.expectEqual(2, cmd.color_operation.operations.count()); var it = cmd.color_operation.operations.constIterator(0); { @@ -2665,7 +3527,6 @@ test "OSC: OSC104: invalid palette index" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .reset_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { @@ -2690,7 +3551,6 @@ test "OSC: OSC104: empty palette index" { const cmd = p.end('\x1b').?; try testing.expect(cmd == .color_operation); - try testing.expect(cmd.color_operation.source == .reset_palette); try testing.expect(cmd.color_operation.operations.count() == 1); var it = cmd.color_operation.operations.constIterator(0); { diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index fd30720b3..3196d77b7 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -1557,7 +1557,7 @@ pub fn Stream(comptime Handler: type) type { .color_operation => |v| { if (@hasDecl(T, "handleColorOperation")) { - try self.handler.handleColorOperation(v.source, &v.operations, v.terminator); + try self.handler.handleColorOperation(&v.operations, v.terminator); return; } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 039b11c03..3b1d36193 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -1187,7 +1187,6 @@ pub const StreamHandler = struct { pub fn handleColorOperation( self: *StreamHandler, - source: terminal.osc.Command.ColorOperation.Source, operations: *const terminal.osc.Command.ColorOperation.List, terminator: terminal.osc.Terminator, ) !void { @@ -1201,38 +1200,52 @@ pub const StreamHandler = struct { var response: std.ArrayListUnmanaged(u8) = .empty; const writer = response.writer(alloc); - var report: bool = false; - - try writer.print("\x1b]{}", .{source}); - var it = operations.constIterator(0); while (it.next()) |op| { switch (op.*) { - .set => |set| { + .set => |set| set: { switch (set.kind) { .palette => |i| { self.terminal.flags.dirty.palette = true; self.terminal.color_palette.colors[i] = set.color; self.terminal.color_palette.mask.set(i); }, - .foreground => { - self.foreground_color = set.color; - _ = self.renderer_mailbox.push(.{ - .foreground_color = set.color, - }, .{ .forever = {} }); - }, - .background => { - self.background_color = set.color; - _ = self.renderer_mailbox.push(.{ - .background_color = set.color, - }, .{ .forever = {} }); - }, - .cursor => { - self.cursor_color = set.color; - _ = self.renderer_mailbox.push(.{ - .cursor_color = set.color, - }, .{ .forever = {} }); + .dynamic_color => |i| { + switch (i) { + .foreground => { + self.foreground_color = set.color; + _ = self.renderer_mailbox.push(.{ + .foreground_color = set.color, + }, .{ .forever = {} }); + }, + .background => { + self.background_color = set.color; + _ = self.renderer_mailbox.push(.{ + .background_color = set.color, + }, .{ .forever = {} }); + }, + .cursor => { + self.cursor_color = set.color; + _ = self.renderer_mailbox.push(.{ + .cursor_color = set.color, + }, .{ .forever = {} }); + }, + .pointer_foreground, + .pointer_background, + .tektronix_foreground, + .tektronix_background, + .highlight_background, + .tektronix_cursor, + .highlight_foreground, + => { + log.warn("setting dynamic color {} ({s}) not implemented", .{ + i, + @tagName(i), + }); + break :set; + }, + } }, } @@ -1243,7 +1256,7 @@ pub const StreamHandler = struct { } }); }, - .reset => |kind| { + .reset => |kind| reset: { switch (kind) { .palette => |i| { const mask = &self.terminal.color_palette.mask; @@ -1258,40 +1271,58 @@ pub const StreamHandler = struct { }, }); }, - .foreground => { - self.foreground_color = null; - _ = self.renderer_mailbox.push(.{ - .foreground_color = self.foreground_color, - }, .{ .forever = {} }); + .dynamic_color => |i| { + switch (i) { + .foreground => { + self.foreground_color = null; + _ = self.renderer_mailbox.push(.{ + .foreground_color = self.foreground_color, + }, .{ .forever = {} }); - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .foreground, - .color = self.default_foreground_color, - } }); - }, - .background => { - self.background_color = null; - _ = self.renderer_mailbox.push(.{ - .background_color = self.background_color, - }, .{ .forever = {} }); + self.surfaceMessageWriter(.{ .color_change = .{ + .kind = kind, + .color = self.default_foreground_color, + } }); + }, + .background => { + self.background_color = null; + _ = self.renderer_mailbox.push(.{ + .background_color = self.background_color, + }, .{ .forever = {} }); - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .background, - .color = self.default_background_color, - } }); - }, - .cursor => { - self.cursor_color = null; + self.surfaceMessageWriter(.{ .color_change = .{ + .kind = kind, + .color = self.default_background_color, + } }); + }, + .cursor => { + self.cursor_color = null; - _ = self.renderer_mailbox.push(.{ - .cursor_color = self.cursor_color, - }, .{ .forever = {} }); + _ = self.renderer_mailbox.push(.{ + .cursor_color = self.cursor_color, + }, .{ .forever = {} }); - if (self.default_cursor_color) |color| { - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .cursor, - .color = color, - } }); + if (self.default_cursor_color) |color| { + self.surfaceMessageWriter(.{ .color_change = .{ + .kind = kind, + .color = color, + } }); + } + }, + .pointer_foreground, + .pointer_background, + .tektronix_foreground, + .tektronix_background, + .highlight_background, + .tektronix_cursor, + .highlight_foreground, + => { + log.warn("resetting dynamic color {} ({s}) not implemented", .{ + i, + @tagName(i), + }); + break :reset; + }, } }, } @@ -1300,22 +1331,36 @@ pub const StreamHandler = struct { .report => |kind| report: { if (self.osc_color_report_format == .none) break :report; - report = true; - const color = switch (kind) { .palette => |i| self.terminal.color_palette.colors[i], - .foreground => self.foreground_color orelse self.default_foreground_color, - .background => self.background_color orelse self.default_background_color, - .cursor => self.cursor_color orelse - self.default_cursor_color orelse - self.foreground_color orelse - self.default_foreground_color, + .dynamic_color => |i| switch (i) { + .foreground => self.foreground_color orelse self.default_foreground_color, + .background => self.background_color orelse self.default_background_color, + .cursor => self.cursor_color orelse + self.default_cursor_color orelse + self.foreground_color orelse + self.default_foreground_color, + .pointer_foreground, + .pointer_background, + .tektronix_foreground, + .tektronix_background, + .highlight_background, + .tektronix_cursor, + .highlight_foreground, + => { + log.warn("reporting dynamic color {} ({s}) not implemented", .{ + i, + @tagName(i), + }); + break :report; + }, + }, }; switch (self.osc_color_report_format) { .@"16-bit" => switch (kind) { .palette => |i| try writer.print( - ";{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}", + "\x1b]4;{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}", .{ i, @as(u16, color.r) * 257, @@ -1323,9 +1368,10 @@ pub const StreamHandler = struct { @as(u16, color.b) * 257, }, ), - else => try writer.print( - ";rgb:{x:0>4}/{x:0>4}/{x:0>4}", + .dynamic_color => |i| try writer.print( + "\x1b]{};rgb:{x:0>4}/{x:0>4}/{x:0>4}", .{ + i, @as(u16, color.r) * 257, @as(u16, color.g) * 257, @as(u16, color.b) * 257, @@ -1335,7 +1381,7 @@ pub const StreamHandler = struct { .@"8-bit" => switch (kind) { .palette => |i| try writer.print( - ";{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}", + "\x1b]4;{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}", .{ i, @as(u16, color.r), @@ -1343,9 +1389,10 @@ pub const StreamHandler = struct { @as(u16, color.b), }, ), - else => try writer.print( - ";rgb:{x:0>2}/{x:0>2}/{x:0>2}", + .dynamic_color => |i| try writer.print( + "\x1b]{};rgb:{x:0>2}/{x:0>2}/{x:0>2}", .{ + i, @as(u16, color.r), @as(u16, color.g), @as(u16, color.b), @@ -1355,13 +1402,14 @@ pub const StreamHandler = struct { .none => unreachable, } + + try writer.writeAll(terminator.string()); }, } } - if (report) { - // If any of the operations were reports, finalize the report - // string and send it to the terminal. - try writer.writeAll(terminator.string()); + + if (response.items.len > 0) { + // If any of the operations were reports send it to the terminal. const msg = try termio.Message.writeReq(self.alloc, response.items); self.messageWriter(msg); }