terminal: OSC 10-19

pull/8590/head
Mitchell Hashimoto 2025-09-11 11:12:01 -07:00
parent b4fdefc2c0
commit d7523e3e37
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 114 additions and 0 deletions

View File

@ -12,6 +12,16 @@ pub const ParseError = Allocator.Error || error{
pub const Operation = enum {
osc_4,
osc_5,
osc_10,
osc_11,
osc_12,
osc_13,
osc_14,
osc_15,
osc_16,
osc_17,
osc_18,
osc_19,
osc_110,
osc_111,
osc_112,
@ -43,6 +53,16 @@ pub fn parse(
return switch (op) {
.osc_4 => try parseGetSetAnsiColor(alloc, .osc_4, &it),
.osc_5 => try parseGetSetAnsiColor(alloc, .osc_5, &it),
.osc_10 => try parseGetSetDynamicColor(alloc, .foreground, &it),
.osc_11 => try parseGetSetDynamicColor(alloc, .background, &it),
.osc_12 => try parseGetSetDynamicColor(alloc, .cursor, &it),
.osc_13 => try parseGetSetDynamicColor(alloc, .pointer_foreground, &it),
.osc_14 => try parseGetSetDynamicColor(alloc, .pointer_background, &it),
.osc_15 => try parseGetSetDynamicColor(alloc, .tektronix_foreground, &it),
.osc_16 => try parseGetSetDynamicColor(alloc, .tektronix_background, &it),
.osc_17 => try parseGetSetDynamicColor(alloc, .highlight_background, &it),
.osc_18 => try parseGetSetDynamicColor(alloc, .tektronix_cursor, &it),
.osc_19 => try parseGetSetDynamicColor(alloc, .highlight_foreground, &it),
.osc_110 => try parseResetDynamicColor(alloc, .foreground, &it),
.osc_111 => try parseResetDynamicColor(alloc, .background, &it),
.osc_112 => try parseResetDynamicColor(alloc, .cursor, &it),
@ -116,6 +136,40 @@ fn parseGetSetAnsiColor(
}
}
/// OSC 10-19: Get/Set Dynamic Colors
fn parseGetSetDynamicColor(
alloc: Allocator,
start: DynamicColor,
it: *std.mem.TokenIterator(u8, .scalar),
) Allocator.Error!List {
// Note: in ANY error scenario below we return the accumulated results.
// This matches the xterm behavior (see misc.c ChangeColorsRequest)
var result: List = .{};
var color: DynamicColor = start;
while (true) {
const spec_str = it.next() orelse return result;
if (std.mem.eql(u8, spec_str, "?")) {
const req = try result.addOne(alloc);
req.* = .{ .query = .{ .dynamic = color } };
} else {
const rgb = RGB.parse(spec_str) catch return result;
const req = try result.addOne(alloc);
req.* = .{ .set = .{
.target = .{ .dynamic = color },
.color = rgb,
} };
}
// Each successive value uses the next color so long as it exists.
color = std.meta.intToEnum(
DynamicColor,
@intFromEnum(color) + 1,
) catch return result;
}
}
/// OSC 110-119: Reset Dynamic Colors
fn parseResetDynamicColor(
alloc: Allocator,
@ -371,6 +425,66 @@ test "osc4: multiple requests" {
}
}
// OSC 10-19: Get/Set Dynamic Colors
test "dynamic" {
const testing = std.testing;
const alloc = testing.allocator;
inline for (@typeInfo(DynamicColor).@"enum".fields) |field| {
const color = @field(DynamicColor, field.name);
const op = @field(Operation, std.fmt.comptimePrint(
"osc_{d}",
.{field.value},
));
// Example script:
// printf '\e]10;red\e\\'
{
var list = try parse(alloc, op, "red");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
Request{ .set = .{
.target = .{ .dynamic = color },
.color = RGB{ .r = 255, .g = 0, .b = 0 },
} },
list.at(0).*,
);
}
}
}
test "dynamic multiple" {
const testing = std.testing;
const alloc = testing.allocator;
// Example script:
// printf '\e]11;red;blue\e\\'
{
var list = try parse(
alloc,
.osc_11,
"red;blue",
);
defer list.deinit(alloc);
try testing.expectEqual(2, list.count());
try testing.expectEqual(
Request{ .set = .{
.target = .{ .dynamic = .background },
.color = RGB{ .r = 255, .g = 0, .b = 0 },
} },
list.at(0).*,
);
try testing.expectEqual(
Request{ .set = .{
.target = .{ .dynamic = .cursor },
.color = RGB{ .r = 0, .g = 0, .b = 255 },
} },
list.at(1).*,
);
}
}
// OSC 110-119: Reset Dynamic Colors
test "reset dynamic" {
const testing = std.testing;