terminal: update parser to use new color parser and stream handler
parent
67b7a5f267
commit
3afc8019d5
|
|
@ -863,18 +863,24 @@ 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(
|
||||
.{ .surface = self },
|
||||
.color_change,
|
||||
.{
|
||||
.kind = switch (change.kind) {
|
||||
.background => .background,
|
||||
.foreground => .foreground,
|
||||
.cursor => .cursor,
|
||||
.kind = switch (change.target) {
|
||||
.palette => |v| @enumFromInt(v),
|
||||
.dynamic => |dyn| switch (dyn) {
|
||||
.foreground => .foreground,
|
||||
.background => .background,
|
||||
.cursor => .cursor,
|
||||
// Unsupported dynamic color change notification type
|
||||
else => break :color_change,
|
||||
},
|
||||
// Special colors aren't supported for change notification
|
||||
.special => break :color_change,
|
||||
},
|
||||
.r = change.color.r,
|
||||
.g = change.color.g,
|
||||
|
|
|
|||
|
|
@ -78,10 +78,7 @@ pub const Message = union(enum) {
|
|||
password_input: bool,
|
||||
|
||||
/// A terminal color was changed using OSC sequences.
|
||||
color_change: struct {
|
||||
kind: terminal.osc.Command.ColorOperation.Kind,
|
||||
color: terminal.color.RGB,
|
||||
},
|
||||
color_change: terminal.osc.color.ColoredTarget,
|
||||
|
||||
/// Notifies the surface that a tick of the timer that is timing
|
||||
/// out selection scrolling has occurred. "selection scrolling"
|
||||
|
|
|
|||
|
|
@ -915,15 +915,15 @@ 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);
|
||||
try testing.expect(cmd.color_operation.op == .osc_112);
|
||||
try testing.expect(cmd.color_operation.requests.count() == 1);
|
||||
var it = cmd.color_operation.requests.constIterator(0);
|
||||
{
|
||||
const op = it.next().?;
|
||||
try testing.expect(op.* == .reset);
|
||||
try testing.expectEqual(
|
||||
osc.Command.ColorOperation.Kind.cursor,
|
||||
op.reset,
|
||||
osc.color.Request{ .reset = .{ .dynamic = .cursor } },
|
||||
op.*,
|
||||
);
|
||||
}
|
||||
try std.testing.expect(it.next() == null);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const Allocator = mem.Allocator;
|
|||
const RGB = @import("color.zig").RGB;
|
||||
const kitty = @import("kitty.zig");
|
||||
const osc_color = @import("osc/color.zig");
|
||||
pub const color = osc_color;
|
||||
|
||||
const log = std.log.scoped(.osc);
|
||||
|
||||
|
|
@ -122,10 +123,10 @@ pub const Command = union(enum) {
|
|||
///
|
||||
/// Currently, these OSCs are handled by `color_operation`:
|
||||
///
|
||||
/// 4, 10, 11, 12, 104, 110, 111, 112
|
||||
/// 4, 5, 10-19, 104, 105, 110-119
|
||||
color_operation: struct {
|
||||
source: ColorOperation.Source,
|
||||
operations: ColorOperation.List = .{},
|
||||
op: osc_color.Operation,
|
||||
requests: osc_color.List = .{},
|
||||
terminator: Terminator = .st,
|
||||
},
|
||||
|
||||
|
|
@ -171,46 +172,6 @@ pub const Command = union(enum) {
|
|||
/// ConEmu GUI macro (OSC 9;6)
|
||||
conemu_guimacro: []const u8,
|
||||
|
||||
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 fn format(
|
||||
self: Source,
|
||||
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 const Kind = union(enum) {
|
||||
palette: u8,
|
||||
foreground,
|
||||
background,
|
||||
cursor,
|
||||
};
|
||||
|
||||
set: struct {
|
||||
kind: Kind,
|
||||
color: RGB,
|
||||
},
|
||||
reset: Kind,
|
||||
report: Kind,
|
||||
};
|
||||
|
||||
pub const ProgressReport = struct {
|
||||
pub const State = enum(c_int) {
|
||||
remove,
|
||||
|
|
@ -346,6 +307,12 @@ pub const Parser = struct {
|
|||
@"12",
|
||||
@"13",
|
||||
@"133",
|
||||
@"14",
|
||||
@"15",
|
||||
@"16",
|
||||
@"17",
|
||||
@"18",
|
||||
@"19",
|
||||
@"2",
|
||||
@"21",
|
||||
@"22",
|
||||
|
|
@ -371,21 +338,8 @@ pub const Parser = struct {
|
|||
clipboard_kind,
|
||||
clipboard_kind_end,
|
||||
|
||||
// Get/set color palette index
|
||||
osc_4_index,
|
||||
osc_4_color,
|
||||
|
||||
// Get/set foreground color
|
||||
osc_10,
|
||||
|
||||
// Get/set background color
|
||||
osc_11,
|
||||
|
||||
// Get/set cursor color
|
||||
osc_12,
|
||||
|
||||
// Reset color palette index
|
||||
osc_104,
|
||||
// OSC color operation.
|
||||
osc_color,
|
||||
|
||||
// Hyperlinks
|
||||
hyperlink_param_key,
|
||||
|
|
@ -492,7 +446,7 @@ pub const Parser = struct {
|
|||
// Some commands have their own memory management we need to clear.
|
||||
switch (self.command) {
|
||||
.kitty_color_protocol => |*v| v.list.deinit(),
|
||||
.color_operation => |*v| v.operations.deinit(self.alloc.?),
|
||||
.color_operation => |*v| v.requests.deinit(self.alloc.?),
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
|
@ -580,6 +534,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,
|
||||
},
|
||||
|
||||
|
|
@ -590,12 +550,10 @@ pub const Parser = struct {
|
|||
self.state = .invalid;
|
||||
break :osc_10;
|
||||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = .get_set_foreground,
|
||||
},
|
||||
};
|
||||
self.state = .osc_10;
|
||||
self.command = .{ .color_operation = .{
|
||||
.op = .osc_10,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
|
|
@ -603,11 +561,6 @@ pub const Parser = struct {
|
|||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.osc_10, .osc_11, .osc_12 => switch (c) {
|
||||
';' => self.parseOSC101112(false),
|
||||
else => {},
|
||||
},
|
||||
|
||||
.@"104" => switch (c) {
|
||||
';' => osc_104: {
|
||||
if (self.alloc == null) {
|
||||
|
|
@ -617,21 +570,16 @@ pub const Parser = struct {
|
|||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = .reset_palette,
|
||||
.op = .osc_104,
|
||||
},
|
||||
};
|
||||
self.state = .osc_104;
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.osc_104 => switch (c) {
|
||||
';' => self.parseOSC104(false),
|
||||
else => {},
|
||||
},
|
||||
|
||||
.@"11" => switch (c) {
|
||||
';' => osc_11: {
|
||||
if (self.alloc == null) {
|
||||
|
|
@ -639,47 +587,39 @@ pub const Parser = struct {
|
|||
self.state = .invalid;
|
||||
break :osc_11;
|
||||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = .get_set_background,
|
||||
},
|
||||
};
|
||||
self.state = .osc_11;
|
||||
self.command = .{ .color_operation = .{
|
||||
.op = .osc_11,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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;
|
||||
break :blk;
|
||||
}
|
||||
|
||||
const alloc = self.alloc orelse return;
|
||||
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = switch (c) {
|
||||
'0' => .reset_foreground,
|
||||
'1' => .reset_background,
|
||||
'2' => .reset_cursor,
|
||||
.op = switch (c) {
|
||||
'0' => .osc_110,
|
||||
'1' => .osc_111,
|
||||
'2' => .osc_112,
|
||||
'3' => .osc_113,
|
||||
'4' => .osc_114,
|
||||
'5' => .osc_115,
|
||||
'6' => .osc_116,
|
||||
'7' => .osc_117,
|
||||
'8' => .osc_118,
|
||||
'9' => .osc_119,
|
||||
else => unreachable,
|
||||
},
|
||||
},
|
||||
};
|
||||
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,
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
self.state = .swallow;
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
|
|
@ -692,12 +632,10 @@ pub const Parser = struct {
|
|||
self.state = .invalid;
|
||||
break :osc_12;
|
||||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = .get_set_cursor,
|
||||
},
|
||||
};
|
||||
self.state = .osc_12;
|
||||
self.command = .{ .color_operation = .{
|
||||
.op = .osc_12,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
|
|
@ -705,6 +643,19 @@ pub const Parser = struct {
|
|||
},
|
||||
|
||||
.@"13" => switch (c) {
|
||||
';' => 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 = .{
|
||||
.op = .osc_13,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
'3' => self.state = .@"133",
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
|
@ -714,6 +665,110 @@ 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 = .{
|
||||
.op = .osc_14,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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 = .{
|
||||
.op = .osc_15,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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 = .{
|
||||
.op = .osc_16,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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 = .{
|
||||
.op = .osc_17,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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 = .{
|
||||
.op = .osc_18,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
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 = .{
|
||||
.op = .osc_19,
|
||||
} };
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.osc_color => {},
|
||||
|
||||
.@"2" => switch (c) {
|
||||
'1' => self.state = .@"21",
|
||||
'2' => self.state = .@"22",
|
||||
|
|
@ -793,30 +848,32 @@ pub const Parser = struct {
|
|||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.source = .get_set_palette,
|
||||
.op = .osc_4,
|
||||
},
|
||||
};
|
||||
self.state = .osc_4_index;
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.osc_4_index => switch (c) {
|
||||
';' => self.state = .osc_4_color,
|
||||
else => {},
|
||||
},
|
||||
|
||||
.osc_4_color => switch (c) {
|
||||
';' => {
|
||||
self.parseOSC4(false);
|
||||
self.state = .osc_4_index;
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
|
||||
.@"5" => switch (c) {
|
||||
';' => osc_5: {
|
||||
if (self.alloc == null) {
|
||||
log.info("OSC 5 requires an allocator, but none was provided", .{});
|
||||
self.state = .invalid;
|
||||
break :osc_5;
|
||||
}
|
||||
self.command = .{
|
||||
.color_operation = .{
|
||||
.op = .osc_5,
|
||||
},
|
||||
};
|
||||
self.state = .osc_color;
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
'2' => self.state = .@"52",
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
|
@ -1480,178 +1537,28 @@ pub const Parser = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn endOscColor(self: *Parser) void {
|
||||
const alloc = self.alloc.?;
|
||||
assert(self.command == .color_operation);
|
||||
const data = self.buf[self.buf_start..self.buf_idx];
|
||||
self.command.color_operation.requests = osc_color.parse(
|
||||
alloc,
|
||||
self.command.color_operation.op,
|
||||
data,
|
||||
) catch |err| list: {
|
||||
log.info(
|
||||
"failed to parse OSC color request err={} data={s}",
|
||||
.{ err, data },
|
||||
);
|
||||
break :list .{};
|
||||
};
|
||||
}
|
||||
|
||||
fn endAllocableString(self: *Parser) void {
|
||||
const list = self.buf_dynamic.?;
|
||||
self.temp_state.str.* = list.items;
|
||||
}
|
||||
|
||||
fn parseOSC4(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;
|
||||
|
||||
const str = self.buf[self.buf_start .. self.buf_idx - (1 - @intFromBool(final))];
|
||||
self.buf_start = 0;
|
||||
self.buf_idx = 0;
|
||||
|
||||
var it = std.mem.splitScalar(u8, str, ';');
|
||||
const index_str = it.next() orelse {
|
||||
log.warn("OSC 4 is missing palette index", .{});
|
||||
return;
|
||||
};
|
||||
const spec_str = it.next() orelse {
|
||||
log.warn("OSC 4 is missing color spec", .{});
|
||||
return;
|
||||
};
|
||||
const index = std.fmt.parseUnsigned(u8, index_str, 10) catch |err| switch (err) {
|
||||
error.Overflow, error.InvalidCharacter => {
|
||||
log.warn("invalid color palette index in OSC 4: {s} {}", .{ index_str, err });
|
||||
return;
|
||||
},
|
||||
};
|
||||
if (std.mem.eql(u8, spec_str, "?")) {
|
||||
const op = operations.addOne(alloc) catch |err| {
|
||||
log.warn("unable to append color operation: {}", .{err});
|
||||
return;
|
||||
};
|
||||
op.* = .{
|
||||
.report = .{ .palette = index },
|
||||
};
|
||||
} else {
|
||||
const color = RGB.parse(spec_str) catch |err| {
|
||||
log.warn("invalid color specification in OSC 4: '{s}' {}", .{ spec_str, err });
|
||||
return;
|
||||
};
|
||||
const op = operations.addOne(alloc) catch |err| {
|
||||
log.warn("unable to append color operation: {}", .{err});
|
||||
return;
|
||||
};
|
||||
op.* = .{
|
||||
.set = .{
|
||||
.kind = .{
|
||||
.palette = index,
|
||||
},
|
||||
.color = color,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn parseOSC101112(self: *Parser, final: bool) void {
|
||||
assert(switch (self.state) {
|
||||
.osc_10, .osc_11, .osc_12 => 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,
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
if (spec_str.len == 0) {
|
||||
log.warn("OSC 1{s} requires an argument", .{switch (self.state) {
|
||||
.osc_10 => "0",
|
||||
.osc_11 => "1",
|
||||
.osc_12 => "2",
|
||||
else => unreachable,
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
const alloc = self.alloc orelse return;
|
||||
const operations = &self.command.color_operation.operations;
|
||||
|
||||
if (std.mem.eql(u8, spec_str, "?")) {
|
||||
const op = operations.addOne(alloc) catch |err| {
|
||||
log.warn("unable to append color operation: {}", .{err});
|
||||
return;
|
||||
};
|
||||
op.* = .{
|
||||
.report = switch (self.state) {
|
||||
.osc_10 => .foreground,
|
||||
.osc_11 => .background,
|
||||
.osc_12 => .cursor,
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const color = RGB.parse(spec_str) catch |err| {
|
||||
log.warn("invalid color specification in OSC 1{s}: {s} {}", .{
|
||||
switch (self.state) {
|
||||
.osc_10 => "0",
|
||||
.osc_11 => "1",
|
||||
.osc_12 => "2",
|
||||
else => unreachable,
|
||||
},
|
||||
spec_str,
|
||||
err,
|
||||
});
|
||||
return;
|
||||
};
|
||||
const op = operations.addOne(alloc) catch |err| {
|
||||
log.warn("unable to append color operation: {}", .{err});
|
||||
return;
|
||||
};
|
||||
op.* = .{
|
||||
.set = .{
|
||||
.kind = switch (self.state) {
|
||||
.osc_10 => .foreground,
|
||||
.osc_11 => .background,
|
||||
.osc_12 => .cursor,
|
||||
else => unreachable,
|
||||
},
|
||||
.color = color,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn parseOSC104(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;
|
||||
|
||||
const index_str = self.buf[self.buf_start .. self.buf_idx - (1 - @intFromBool(final))];
|
||||
self.buf_start = 0;
|
||||
self.buf_idx = 0;
|
||||
|
||||
const index = std.fmt.parseUnsigned(u8, index_str, 10) catch |err| switch (err) {
|
||||
error.Overflow, error.InvalidCharacter => {
|
||||
log.warn("invalid color palette index in OSC 104: {s} {}", .{ index_str, err });
|
||||
return;
|
||||
},
|
||||
};
|
||||
const op = self.command.color_operation.operations.addOne(alloc) catch |err| {
|
||||
log.warn("unable to append color operation: {}", .{err});
|
||||
return;
|
||||
};
|
||||
op.* = .{
|
||||
.reset = .{ .palette = index },
|
||||
};
|
||||
}
|
||||
|
||||
/// End the sequence and return the command, if any. If the return value
|
||||
/// is null, then no valid command was found. The optional terminator_ch
|
||||
/// is the final character in the OSC sequence. This is used to determine
|
||||
|
|
@ -1667,11 +1574,15 @@ pub const Parser = struct {
|
|||
|
||||
// Other cleanup we may have to do depending on state.
|
||||
switch (self.state) {
|
||||
.allocable_string => self.endAllocableString(),
|
||||
.semantic_exit_code => self.endSemanticExitCode(),
|
||||
.semantic_option_value => self.endSemanticOptionValue(),
|
||||
.hyperlink_uri => self.endHyperlink(),
|
||||
.string => self.endString(),
|
||||
.conemu_sleep_value => self.endConEmuSleepValue(),
|
||||
.kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true),
|
||||
.kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true),
|
||||
.osc_color => self.endOscColor(),
|
||||
|
||||
// We received OSC 9;X ST, but nothing else, finish off as a
|
||||
// desktop notification with "X" as the body.
|
||||
|
|
@ -1692,12 +1603,6 @@ pub const Parser = struct {
|
|||
.conemu_progress_value,
|
||||
=> {},
|
||||
|
||||
.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),
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
|
@ -1916,111 +1821,6 @@ test "OSC: end_of_input" {
|
|||
try testing.expect(cmd == .end_of_input);
|
||||
}
|
||||
|
||||
test "OSC: OSC110: reset foreground color" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "110";
|
||||
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 == .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,
|
||||
op.reset,
|
||||
);
|
||||
}
|
||||
try testing.expect(it.next() == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC111: reset background color" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
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 == .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,
|
||||
op.reset,
|
||||
);
|
||||
}
|
||||
try testing.expect(it.next() == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC112: reset cursor color" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "112";
|
||||
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 == .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,
|
||||
op.reset,
|
||||
);
|
||||
}
|
||||
try testing.expect(it.next() == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC112: reset cursor color with semicolon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "112;";
|
||||
for (input) |ch| p.next(ch);
|
||||
log.warn("finish: {s}", .{@tagName(p.state)});
|
||||
|
||||
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,
|
||||
op.reset,
|
||||
);
|
||||
}
|
||||
try testing.expect(it.next() == null);
|
||||
}
|
||||
|
||||
test "OSC: get/set clipboard" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ fn parseGetSetAnsiColor(
|
|||
) catch return result;
|
||||
|
||||
// Parse the color.
|
||||
const target: Request.Target = switch (op) {
|
||||
const target: Target = switch (op) {
|
||||
// OSC5 maps directly to the Special enum.
|
||||
.osc_5 => .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
|
|
@ -178,7 +178,7 @@ fn parseResetAnsiColor(
|
|||
) catch continue;
|
||||
|
||||
// Parse the color.
|
||||
const target: Request.Target = switch (op) {
|
||||
const target: Target = switch (op) {
|
||||
// OSC105 maps directly to the Special enum.
|
||||
.osc_105 => .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
|
|
@ -266,17 +266,17 @@ pub const Request = union(enum) {
|
|||
reset: Target,
|
||||
reset_palette,
|
||||
reset_special,
|
||||
};
|
||||
|
||||
pub const Target = union(enum) {
|
||||
palette: u8,
|
||||
special: SpecialColor,
|
||||
dynamic: DynamicColor,
|
||||
};
|
||||
pub const Target = union(enum) {
|
||||
palette: u8,
|
||||
special: SpecialColor,
|
||||
dynamic: DynamicColor,
|
||||
};
|
||||
|
||||
pub const ColoredTarget = struct {
|
||||
target: Target,
|
||||
color: RGB,
|
||||
};
|
||||
pub const ColoredTarget = struct {
|
||||
target: Target,
|
||||
color: RGB,
|
||||
};
|
||||
|
||||
test "osc4" {
|
||||
|
|
|
|||
|
|
@ -1565,7 +1565,11 @@ 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.op,
|
||||
&v.requests,
|
||||
v.terminator,
|
||||
);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1187,12 +1187,15 @@ pub const StreamHandler = struct {
|
|||
|
||||
pub fn handleColorOperation(
|
||||
self: *StreamHandler,
|
||||
source: terminal.osc.Command.ColorOperation.Source,
|
||||
operations: *const terminal.osc.Command.ColorOperation.List,
|
||||
op: terminal.osc.color.Operation,
|
||||
requests: *const terminal.osc.color.List,
|
||||
terminator: terminal.osc.Terminator,
|
||||
) !void {
|
||||
// We'll need op one day if we ever implement reporting special colors.
|
||||
_ = op;
|
||||
|
||||
// return early if there is nothing to do
|
||||
if (operations.count() == 0) return;
|
||||
if (requests.count() == 0) return;
|
||||
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var fba: std.heap.FixedBufferAllocator = .init(&buffer);
|
||||
|
|
@ -1201,63 +1204,71 @@ 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.*) {
|
||||
var it = requests.constIterator(0);
|
||||
while (it.next()) |req| {
|
||||
switch (req.*) {
|
||||
.set => |set| {
|
||||
switch (set.kind) {
|
||||
switch (set.target) {
|
||||
.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 => |dynamic| switch (dynamic) {
|
||||
.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.info("setting dynamic color {s} not implemented", .{
|
||||
@tagName(dynamic),
|
||||
}),
|
||||
},
|
||||
.special => log.info("setting special colors not implemented", .{}),
|
||||
}
|
||||
|
||||
// Notify the surface of the color change
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = set.kind,
|
||||
.target = set.target,
|
||||
.color = set.color,
|
||||
} });
|
||||
},
|
||||
|
||||
.reset => |kind| {
|
||||
switch (kind) {
|
||||
.palette => |i| {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
mask.unset(i);
|
||||
.reset => |target| switch (target) {
|
||||
.palette => |i| {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
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],
|
||||
},
|
||||
});
|
||||
},
|
||||
self.surfaceMessageWriter(.{
|
||||
.color_change = .{
|
||||
.target = target,
|
||||
.color = self.terminal.color_palette.colors[i],
|
||||
},
|
||||
});
|
||||
},
|
||||
.dynamic => |dynamic| switch (dynamic) {
|
||||
.foreground => {
|
||||
self.foreground_color = null;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
|
|
@ -1265,7 +1276,7 @@ pub const StreamHandler = struct {
|
|||
}, .{ .forever = {} });
|
||||
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .foreground,
|
||||
.target = target,
|
||||
.color = self.default_foreground_color,
|
||||
} });
|
||||
},
|
||||
|
|
@ -1276,7 +1287,7 @@ pub const StreamHandler = struct {
|
|||
}, .{ .forever = {} });
|
||||
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .background,
|
||||
.target = target,
|
||||
.color = self.default_background_color,
|
||||
} });
|
||||
},
|
||||
|
|
@ -1289,33 +1300,83 @@ pub const StreamHandler = struct {
|
|||
|
||||
if (self.default_cursor_color) |color| {
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .cursor,
|
||||
.target = target,
|
||||
.color = color,
|
||||
} });
|
||||
}
|
||||
},
|
||||
}
|
||||
.pointer_foreground,
|
||||
.pointer_background,
|
||||
.tektronix_foreground,
|
||||
.tektronix_background,
|
||||
.highlight_background,
|
||||
.tektronix_cursor,
|
||||
.highlight_foreground,
|
||||
=> log.warn("resetting dynamic color {s} not implemented", .{
|
||||
@tagName(dynamic),
|
||||
}),
|
||||
},
|
||||
.special => log.info("resetting special colors not implemented", .{}),
|
||||
},
|
||||
|
||||
.report => |kind| report: {
|
||||
if (self.osc_color_report_format == .none) break :report;
|
||||
.reset_palette => {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
var mask_iterator = mask.iterator(.{});
|
||||
while (mask_iterator.next()) |i| {
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
self.surfaceMessageWriter(.{
|
||||
.color_change = .{
|
||||
.target = .{ .palette = @intCast(i) },
|
||||
.color = self.terminal.color_palette.colors[i],
|
||||
},
|
||||
});
|
||||
}
|
||||
mask.* = .initEmpty();
|
||||
},
|
||||
|
||||
report = true;
|
||||
.reset_special => log.warn(
|
||||
"resetting all special colors not implemented",
|
||||
.{},
|
||||
),
|
||||
|
||||
.query => |kind| report: {
|
||||
if (self.osc_color_report_format == .none) break :report;
|
||||
|
||||
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 => |dynamic| switch (dynamic) {
|
||||
.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.info(
|
||||
"reporting dynamic color {s} not implemented",
|
||||
.{@tagName(dynamic)},
|
||||
);
|
||||
break :report;
|
||||
},
|
||||
},
|
||||
.special => {
|
||||
log.info("reporting special colors not implemented", .{});
|
||||
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,19 +1384,21 @@ pub const StreamHandler = struct {
|
|||
@as(u16, color.b) * 257,
|
||||
},
|
||||
),
|
||||
else => try writer.print(
|
||||
";rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
.dynamic => |dynamic| try writer.print(
|
||||
"\x1b]{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
.{
|
||||
@intFromEnum(dynamic),
|
||||
@as(u16, color.r) * 257,
|
||||
@as(u16, color.g) * 257,
|
||||
@as(u16, color.b) * 257,
|
||||
},
|
||||
),
|
||||
.special => unreachable,
|
||||
},
|
||||
|
||||
.@"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,22 +1406,27 @@ pub const StreamHandler = struct {
|
|||
@as(u16, color.b),
|
||||
},
|
||||
),
|
||||
else => try writer.print(
|
||||
";rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.dynamic => |dynamic| try writer.print(
|
||||
"\x1b]{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{
|
||||
@intFromEnum(dynamic),
|
||||
@as(u16, color.r),
|
||||
@as(u16, color.g),
|
||||
@as(u16, color.b),
|
||||
},
|
||||
),
|
||||
.special => unreachable,
|
||||
},
|
||||
|
||||
.none => unreachable,
|
||||
}
|
||||
|
||||
try writer.writeAll(terminator.string());
|
||||
},
|
||||
}
|
||||
}
|
||||
if (report) {
|
||||
|
||||
if (response.items.len > 0) {
|
||||
// If any of the operations were reports, finalize the report
|
||||
// string and send it to the terminal.
|
||||
try writer.writeAll(terminator.string());
|
||||
|
|
|
|||
Loading…
Reference in New Issue