OSC: convert OSC 110, 111, and 112 and add more tests
parent
5ec1c15ecf
commit
5bb7492955
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } };
|
||||
self.complete = true;
|
||||
';' => 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;
|
||||
},
|
||||
'1' => {
|
||||
self.command = .{ .reset_color = .{ .kind = .background, .value = undefined } };
|
||||
self.complete = true;
|
||||
'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;
|
||||
},
|
||||
'2' => {
|
||||
self.command = .{ .reset_color = .{ .kind = .cursor, .value = undefined } };
|
||||
self.complete = true;
|
||||
'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;
|
||||
},
|
||||
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" {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue