From 5bb74929554e298651298d32f73ef29de14e4294 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 23 May 2025 20:44:33 -0500 Subject: [PATCH] OSC: convert OSC 110, 111, and 112 and add more tests --- src/terminal/Parser.zig | 21 +- src/terminal/osc.zig | 554 +++++++++++++++++++++++++--------- src/terminal/stream.zig | 21 -- src/termio/stream_handler.zig | 197 ------------ 4 files changed, 437 insertions(+), 356 deletions(-) diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 14ed6d6df..80772d71f 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -877,7 +877,12 @@ test "osc: change window title (end in esc)" { // https://github.com/darrenstarr/VtNetCore/pull/14 // Saw this on HN, decided to add a test case because why not. test "osc: 112 incomplete sequence" { - var p = init(); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = init(); + p.osc_parser.alloc = arena.allocator(); + _ = p.next(0x1B); _ = p.next(']'); _ = p.next('1'); @@ -892,8 +897,18 @@ test "osc: 112 incomplete sequence" { try testing.expect(a[2] == null); const cmd = a[0].?.osc_dispatch; - try testing.expect(cmd == .reset_color); - try testing.expectEqual(cmd.reset_color.kind, .cursor); + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.source == .osc_112); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .reset); + try testing.expectEqual( + osc.Command.ColorKind.cursor, + op.reset, + ); + } } } diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 778196160..7b9239e43 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -116,37 +116,6 @@ pub const Command = union(enum) { terminator: Terminator = .st, }, - /// OSC 4, OSC 10, and OSC 11 color report. - report_color: struct { - /// OSC 4 requests a palette color, OSC 10 requests the foreground - /// color, OSC 11 the background color. - kind: ColorKind, - - /// We must reply with the same string terminator (ST) as used in the - /// request. - terminator: Terminator = .st, - }, - - /// Modify the foreground (OSC 10) or background color (OSC 11), or a palette color (OSC 4) - set_color: struct { - /// OSC 4 sets a palette color, OSC 10 sets the foreground color, OSC 11 - /// the background color. - kind: ColorKind, - - /// The color spec as a string - value: []const u8, - }, - - /// Reset a palette color (OSC 104) or the foreground (OSC 110), background - /// (OSC 111), or cursor (OSC 112) color. - reset_color: struct { - kind: ColorKind, - - /// OSC 104 can have parameters indicating which palette colors to - /// reset. - value: []const u8, - }, - /// Kitty color protocol, OSC 21 /// https://sw.kovidgoyal.net/kitty/color-stack/#id1 kitty_color_protocol: kitty.color.OSC, @@ -195,6 +164,9 @@ pub const Command = union(enum) { osc_11 = 11, osc_12 = 12, osc_104 = 104, + osc_110 = 110, + osc_111 = 111, + osc_112 = 112, pub fn format( self: ColorOperationSource, @@ -347,15 +319,6 @@ pub const Parser = struct { @"8", @"9", - // OSC 10 is used to query or set the current foreground color. - query_fg_color, - - // OSC 11 is used to query or set the current background color. - query_bg_color, - - // OSC 12 is used to query or set the current cursor color. - query_cursor_color, - // We're in a semantic prompt OSC command but we aren't sure // what the command is yet, i.e. `133;` semantic_prompt, @@ -372,9 +335,27 @@ pub const Parser = struct { // Get/set color palette index osc_4, + // Get/set foreground color + osc_10, + + // Get/set background color + osc_11, + + // Get/set cursor color + osc_12, + // Reset color palette index osc_104, + // Reset foreground color + osc_110, + + // Reset background color + osc_111, + + // Reset cursor color + osc_112, + // Hyperlinks hyperlink_param_key, hyperlink_param_value, @@ -548,15 +529,26 @@ pub const Parser = struct { }, .@"10" => switch (c) { - ';' => self.state = .query_fg_color, + ';' => osc_10: { + if (self.alloc == null) { + log.warn("OSC 10 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_10; + } + self.state = .osc_10; + self.buf_start = self.buf_idx; + self.complete = true; + }, '4' => self.state = .@"104", else => self.state = .invalid, }, + .osc_10 => {}, + .@"104" => switch (c) { ';' => osc_104: { if (self.alloc == null) { - log.info("OSC 104 requires an allocator, but none was provided", .{}); + log.warn("OSC 104 requires an allocator, but none was provided", .{}); self.state = .invalid; break :osc_104; } @@ -570,30 +562,73 @@ pub const Parser = struct { .osc_104 => {}, .@"11" => switch (c) { - ';' => self.state = .query_bg_color, - '0' => { - self.command = .{ .reset_color = .{ .kind = .foreground, .value = undefined } }; + ';' => osc_11: { + if (self.alloc == null) { + log.warn("OSC 11 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_11; + } + self.state = .osc_11; + self.buf_start = self.buf_idx; self.complete = true; - self.state = .invalid; }, - '1' => { - self.command = .{ .reset_color = .{ .kind = .background, .value = undefined } }; + '0' => osc_110: { + if (self.alloc == null) { + log.warn("OSC 110 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_110; + } + self.state = .osc_110; + self.buf_start = self.buf_idx; self.complete = true; - self.state = .invalid; }, - '2' => { - self.command = .{ .reset_color = .{ .kind = .cursor, .value = undefined } }; + '1' => osc_111: { + if (self.alloc == null) { + log.warn("OSC 111 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_111; + } + self.state = .osc_111; + self.buf_start = self.buf_idx; + self.complete = true; + }, + '2' => osc_112: { + if (self.alloc == null) { + log.warn("OSC 112 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_112; + } + self.state = .osc_112; + self.buf_start = self.buf_idx; self.complete = true; - self.state = .invalid; }, else => self.state = .invalid, }, + .osc_11 => {}, + + .osc_110 => {}, + + .osc_111 => {}, + + .osc_112 => {}, + .@"12" => switch (c) { - ';' => self.state = .query_cursor_color, + ';' => osc_12: { + if (self.alloc == null) { + log.warn("OSC 12 requires an allocator, but none was provided", .{}); + self.state = .invalid; + break :osc_12; + } + self.state = .osc_12; + self.buf_start = self.buf_idx; + self.complete = true; + }, else => self.state = .invalid, }, + .osc_12 => {}, + .@"13" => switch (c) { '3' => self.state = .@"133", else => self.state = .invalid, @@ -978,60 +1013,6 @@ pub const Parser = struct { }, }, - .query_fg_color => switch (c) { - '?' => { - self.command = .{ .report_color = .{ .kind = .foreground } }; - self.complete = true; - self.state = .invalid; - }, - else => { - self.command = .{ .set_color = .{ - .kind = .foreground, - .value = "", - } }; - - self.state = .string; - self.temp_state = .{ .str = &self.command.set_color.value }; - self.buf_start = self.buf_idx - 1; - }, - }, - - .query_bg_color => switch (c) { - '?' => { - self.command = .{ .report_color = .{ .kind = .background } }; - self.complete = true; - self.state = .invalid; - }, - else => { - self.command = .{ .set_color = .{ - .kind = .background, - .value = "", - } }; - - self.state = .string; - self.temp_state = .{ .str = &self.command.set_color.value }; - self.buf_start = self.buf_idx - 1; - }, - }, - - .query_cursor_color => switch (c) { - '?' => { - self.command = .{ .report_color = .{ .kind = .cursor } }; - self.complete = true; - self.state = .invalid; - }, - else => { - self.command = .{ .set_color = .{ - .kind = .cursor, - .value = "", - } }; - - self.state = .string; - self.temp_state = .{ .str = &self.command.set_color.value }; - self.buf_start = self.buf_idx - 1; - }, - }, - .semantic_prompt => switch (c) { 'A' => { self.state = .semantic_option_start; @@ -1397,6 +1378,115 @@ pub const Parser = struct { } } + fn parseOSC101112(self: *Parser) void { + assert(switch (self.state) { + .osc_10, .osc_11, .osc_12 => true, + else => false, + }); + + const alloc = self.alloc orelse return; + + self.command = .{ + .color_operation = .{ + .source = switch (self.state) { + .osc_10 => .osc_10, + .osc_11 => .osc_11, + .osc_12 => .osc_12, + else => unreachable, + }, + .operations = std.ArrayListUnmanaged(Command.ColorOperation).initCapacity(alloc, 1) catch |err| { + log.warn("unable to allocate memory for OSC 10/11/12 parsing: {}", .{err}); + self.state = .invalid; + return; + }, + }, + }; + const str = self.buf[self.buf_start..self.buf_idx]; + var it = std.mem.splitScalar(u8, str, ';'); + const color_str = it.next() orelse { + log.warn("OSC 10/11/12 requires an argument", .{}); + self.state = .invalid; + return; + }; + if (std.mem.eql(u8, color_str, "?")) { + self.command.color_operation.operations.append( + alloc, + .{ + .report = switch (self.state) { + .osc_10 => .foreground, + .osc_11 => .background, + .osc_12 => .cursor, + else => unreachable, + }, + }, + ) catch |err| { + log.warn("unable to append color operation: {}", .{err}); + return; + }; + } else { + const color = RGB.parse(color_str) catch |err| { + log.warn("invalid color specification in OSC 10/11/12: {s} {}", .{ color_str, err }); + return; + }; + self.command.color_operation.operations.append( + alloc, + .{ + .set = .{ + .kind = switch (self.state) { + .osc_10 => .foreground, + .osc_11 => .background, + .osc_12 => .cursor, + else => unreachable, + }, + .color = color, + }, + }, + ) catch |err| { + log.warn("unable to append color operation: {}", .{err}); + return; + }; + } + } + + fn parseOSC110111112(self: *Parser) void { + assert(switch (self.state) { + .osc_110, .osc_111, .osc_112 => true, + else => false, + }); + + const alloc = self.alloc orelse return; + + self.command = .{ + .color_operation = .{ + .source = switch (self.state) { + .osc_110 => .osc_110, + .osc_111 => .osc_111, + .osc_112 => .osc_112, + else => unreachable, + }, + .operations = std.ArrayListUnmanaged(Command.ColorOperation).initCapacity(alloc, 1) catch |err| { + log.warn("unable to allocate memory for OSC 110/111/112 parsing: {}", .{err}); + self.state = .invalid; + return; + }, + }, + }; + self.command.color_operation.operations.append( + alloc, + .{ + .reset = switch (self.state) { + .osc_110 => .foreground, + .osc_111 => .background, + .osc_112 => .cursor, + else => unreachable, + }, + }, + ) catch |err| { + log.warn("unable to append color operation: {}", .{err}); + return; + }; + } + fn parseOSC104(self: *Parser) void { assert(self.state == .osc_104); @@ -1457,13 +1547,22 @@ 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 => self.parseOSC4(), - .osc_104 => self.parseOSC104(), + .osc_4, + => self.parseOSC4(), + .osc_10, + .osc_11, + .osc_12, + => self.parseOSC101112(), + .osc_104, + => self.parseOSC104(), + .osc_110, + .osc_111, + .osc_112, + => self.parseOSC110111112(), else => {}, } switch (self.command) { - .report_color => |*c| c.terminator = .init(terminator_ch), .kitty_color_protocol => |*c| c.terminator = .init(terminator_ch), .color_operation => |*c| c.terminator = .init(terminator_ch), else => {}, @@ -1674,17 +1773,86 @@ test "OSC: end_of_input" { try testing.expect(cmd == .end_of_input); } -test "OSC: reset cursor color" { +test "OSC: OSC110: reset cursor color" { const testing = std.testing; - var p: Parser = .{}; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); - const input = "112"; + var p: Parser = .{ .alloc = arena.allocator() }; + + const input = "110"; for (input) |ch| p.next(ch); const cmd = p.end(null).?; - try testing.expect(cmd == .reset_color); - try testing.expectEqual(cmd.reset_color.kind, .cursor); + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.source == .osc_110); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .reset); + try testing.expectEqual( + Command.ColorKind.foreground, + op.reset, + ); + } +} + +test "OSC: OSC111: reset cursor color" { + const testing = std.testing; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; + + const input = "111"; + 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.source == .osc_111); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .reset); + try testing.expectEqual( + Command.ColorKind.background, + op.reset, + ); + } +} + +test "OSC: OSC112: reset cursor color" { + const testing = std.testing; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; + + const input = "112"; + for (input) |ch| { + log.warn("feeding {c} {s}", .{ ch, @tagName(p.state) }); + p.next(ch); + } + log.warn("finish: {s}", .{@tagName(p.state)}); + + 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 == .osc_112); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .reset); + try testing.expectEqual( + Command.ColorKind.cursor, + op.reset, + ); + } } test "OSC: get/set clipboard" { @@ -1781,62 +1949,178 @@ test "OSC: longer than buffer" { try testing.expect(p.complete == false); } -test "OSC: report default foreground color" { +test "OSC: OSC10: report default foreground color" { const testing = std.testing; - var p: Parser = .{}; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; 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 == .report_color); - try testing.expectEqual(cmd.report_color.kind, .foreground); - try testing.expectEqual(cmd.report_color.terminator, .st); + + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.source == .osc_10); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .report); + try testing.expectEqual( + Command.ColorKind.foreground, + op.report, + ); + } } -test "OSC: set foreground color" { +test "OSC: OSC10: set foreground color" { const testing = std.testing; - var p: Parser = .{}; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; const input = "10;rgbi:0.0/0.5/1.0"; for (input) |ch| p.next(ch); const cmd = p.end('\x07').?; - try testing.expect(cmd == .set_color); - try testing.expectEqual(cmd.set_color.kind, .foreground); - try testing.expectEqualStrings(cmd.set_color.value, "rgbi:0.0/0.5/1.0"); + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.source == .osc_10); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .set); + try testing.expectEqual( + Command.ColorKind.foreground, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0x00, .g = 0x7f, .b = 0xff }, + op.set.color, + ); + } } -test "OSC: report default background color" { +test "OSC: OSC11: report default background color" { const testing = std.testing; - var p: Parser = .{}; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; const input = "11;?"; for (input) |ch| p.next(ch); // This corresponds to ST = BEL character const cmd = p.end('\x07').?; - try testing.expect(cmd == .report_color); - try testing.expectEqual(cmd.report_color.kind, .background); - try testing.expectEqual(cmd.report_color.terminator, .bel); + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .bel); + try testing.expect(cmd.color_operation.source == .osc_11); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .report); + try testing.expectEqual( + Command.ColorKind.background, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); } -test "OSC: set background color" { +test "OSC: OSC11: set background color" { const testing = std.testing; - var p: Parser = .{}; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; const input = "11;rgb:f/ff/ffff"; for (input) |ch| p.next(ch); const cmd = p.end('\x1b').?; - try testing.expect(cmd == .set_color); - try testing.expectEqual(cmd.set_color.kind, .background); - try testing.expectEqualStrings(cmd.set_color.value, "rgb:f/ff/ffff"); + try testing.expect(cmd == .color_operation); + try testing.expectEqual(cmd.color_operation.terminator, .st); + try testing.expect(cmd.color_operation.source == .osc_11); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .set); + try testing.expectEqual( + Command.ColorKind.background, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } +} + +test "OSC: OSC12: report background color" { + const testing = std.testing; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; + + const input = "12;?"; + 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.source == .osc_12); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .report); + try testing.expectEqual( + Command.ColorKind.cursor, + op.report, + ); + } + try testing.expectEqual(cmd.color_operation.terminator, .bel); +} + +test "OSC: OSC12: set background color" { + const testing = std.testing; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var p: Parser = .{ .alloc = arena.allocator() }; + + const input = "12;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.source == .osc_12); + try testing.expect(cmd.color_operation.operations.items.len == 1); + { + const op = cmd.color_operation.operations.items[0]; + try testing.expect(op == .set); + try testing.expectEqual( + Command.ColorKind.cursor, + op.set.kind, + ); + try testing.expectEqual( + RGB{ .r = 0xff, .g = 0xff, .b = 0xff }, + op.set.color, + ); + } } test "OSC: OSC4: get palette color 1" { diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 2a1ae80c9..08ce23098 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -1562,27 +1562,6 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, - .report_color => |v| { - if (@hasDecl(T, "reportColor")) { - try self.handler.reportColor(v.kind, v.terminator); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); - }, - - .set_color => |v| { - if (@hasDecl(T, "setColor")) { - try self.handler.setColor(v.kind, v.value); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); - }, - - .reset_color => |v| { - if (@hasDecl(T, "resetColor")) { - try self.handler.resetColor(v.kind, v.value); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); - }, - .kitty_color_protocol => |v| { if (@hasDecl(T, "sendKittyColorReport")) { try self.handler.sendKittyColorReport(v); diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 57a1eeacf..396aae01f 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -1370,203 +1370,6 @@ pub const StreamHandler = struct { } } - /// Implements OSC 4, OSC 10, and OSC 11, which reports palette color, - /// default foreground color, and background color respectively. - pub fn reportColor( - self: *StreamHandler, - kind: terminal.osc.Command.ColorKind, - terminator: terminal.osc.Terminator, - ) !void { - if (self.osc_color_report_format == .none) return; - - 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, - }; - - var msg: termio.Message = .{ .write_small = .{} }; - const resp = switch (self.osc_color_report_format) { - .@"16-bit" => switch (kind) { - .palette => |i| try std.fmt.bufPrint( - &msg.write_small.data, - "\x1B]{s};{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}{s}", - .{ - kind.code(), - i, - @as(u16, color.r) * 257, - @as(u16, color.g) * 257, - @as(u16, color.b) * 257, - terminator.string(), - }, - ), - else => try std.fmt.bufPrint( - &msg.write_small.data, - "\x1B]{s};rgb:{x:0>4}/{x:0>4}/{x:0>4}{s}", - .{ - kind.code(), - @as(u16, color.r) * 257, - @as(u16, color.g) * 257, - @as(u16, color.b) * 257, - terminator.string(), - }, - ), - }, - - .@"8-bit" => switch (kind) { - .palette => |i| try std.fmt.bufPrint( - &msg.write_small.data, - "\x1B]{s};{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}{s}", - .{ - kind.code(), - i, - @as(u16, color.r), - @as(u16, color.g), - @as(u16, color.b), - terminator.string(), - }, - ), - else => try std.fmt.bufPrint( - &msg.write_small.data, - "\x1B]{s};rgb:{x:0>2}/{x:0>2}/{x:0>2}{s}", - .{ - kind.code(), - @as(u16, color.r), - @as(u16, color.g), - @as(u16, color.b), - terminator.string(), - }, - ), - }, - .none => unreachable, // early return above - }; - msg.write_small.len = @intCast(resp.len); - self.messageWriter(msg); - } - - pub fn setColor( - self: *StreamHandler, - kind: terminal.osc.Command.ColorKind, - value: []const u8, - ) !void { - const color = try terminal.color.RGB.parse(value); - - switch (kind) { - .palette => |i| { - self.terminal.flags.dirty.palette = true; - self.terminal.color_palette.colors[i] = color; - self.terminal.color_palette.mask.set(i); - }, - .foreground => { - self.foreground_color = color; - _ = self.renderer_mailbox.push(.{ - .foreground_color = color, - }, .{ .forever = {} }); - }, - .background => { - self.background_color = color; - _ = self.renderer_mailbox.push(.{ - .background_color = color, - }, .{ .forever = {} }); - }, - .cursor => { - self.cursor_color = color; - _ = self.renderer_mailbox.push(.{ - .cursor_color = color, - }, .{ .forever = {} }); - }, - } - - // Notify the surface of the color change - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = kind, - .color = color, - } }); - } - - pub fn resetColor( - self: *StreamHandler, - kind: terminal.osc.Command.ColorKind, - value: []const u8, - ) !void { - switch (kind) { - .palette => { - const mask = &self.terminal.color_palette.mask; - if (value.len == 0) { - // Find all bit positions in the mask which are set and - // reset those indices to the default palette - var it = mask.iterator(.{}); - while (it.next()) |i| { - self.terminal.flags.dirty.palette = true; - self.terminal.color_palette.colors[i] = self.terminal.default_palette[i]; - mask.unset(i); - - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .{ .palette = @intCast(i) }, - .color = self.terminal.color_palette.colors[i], - } }); - } - } else { - var it = std.mem.tokenizeScalar(u8, value, ';'); - while (it.next()) |param| { - // Skip invalid parameters - const i = std.fmt.parseUnsigned(u8, param, 10) catch continue; - if (mask.isSet(i)) { - self.terminal.flags.dirty.palette = true; - self.terminal.color_palette.colors[i] = self.terminal.default_palette[i]; - mask.unset(i); - - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .{ .palette = @intCast(i) }, - .color = self.terminal.color_palette.colors[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 = .background, - .color = self.default_background_color, - } }); - }, - .cursor => { - self.cursor_color = null; - - _ = self.renderer_mailbox.push(.{ - .cursor_color = self.cursor_color, - }, .{ .forever = {} }); - - if (self.default_cursor_color) |color| { - self.surfaceMessageWriter(.{ .color_change = .{ - .kind = .cursor, - .color = color, - } }); - } - }, - } - } - pub fn showDesktopNotification( self: *StreamHandler, title: []const u8,