terminal: OSC 4/5 matches xterm
parent
aba8981d28
commit
b4fdefc2c0
|
|
@ -10,6 +10,8 @@ pub const ParseError = Allocator.Error || error{
|
|||
|
||||
/// The possible operations we support for colors.
|
||||
pub const Operation = enum {
|
||||
osc_4,
|
||||
osc_5,
|
||||
osc_110,
|
||||
osc_111,
|
||||
osc_112,
|
||||
|
|
@ -39,6 +41,8 @@ pub fn parse(
|
|||
) ParseError!List {
|
||||
var it = std.mem.tokenizeScalar(u8, buf, ';');
|
||||
return switch (op) {
|
||||
.osc_4 => try parseGetSetAnsiColor(alloc, .osc_4, &it),
|
||||
.osc_5 => try parseGetSetAnsiColor(alloc, .osc_5, &it),
|
||||
.osc_110 => try parseResetDynamicColor(alloc, .foreground, &it),
|
||||
.osc_111 => try parseResetDynamicColor(alloc, .background, &it),
|
||||
.osc_112 => try parseResetDynamicColor(alloc, .cursor, &it),
|
||||
|
|
@ -52,12 +56,74 @@ pub fn parse(
|
|||
};
|
||||
}
|
||||
|
||||
/// OSC 4/5
|
||||
fn parseGetSetAnsiColor(
|
||||
alloc: Allocator,
|
||||
comptime op: Operation,
|
||||
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 ChangeAnsiColorRequest)
|
||||
|
||||
var result: List = .{};
|
||||
errdefer result.deinit(alloc);
|
||||
while (true) {
|
||||
// We expect a `c; spec` pair. If either doesn't exist then
|
||||
// we return the results up to this point.
|
||||
const color_str = it.next() orelse return result;
|
||||
const spec_str = it.next() orelse return result;
|
||||
|
||||
// Color must be numeric. u9 because that'll fit our palette + special
|
||||
const color: u9 = std.fmt.parseInt(
|
||||
u9,
|
||||
color_str,
|
||||
10,
|
||||
) catch return result;
|
||||
|
||||
// Parse the color.
|
||||
const target: Request.Target = switch (op) {
|
||||
// OSC5 maps directly to the Special enum.
|
||||
.osc_5 => .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color) orelse return result,
|
||||
) catch return result },
|
||||
|
||||
// OSC4 maps 0-255 to palette, 256-259 to special offset
|
||||
// by the palette count.
|
||||
.osc_4 => if (std.math.cast(u8, color)) |idx| .{
|
||||
.palette = idx,
|
||||
} else .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color - 256) orelse return result,
|
||||
) catch return result },
|
||||
|
||||
else => comptime unreachable,
|
||||
};
|
||||
|
||||
// "?" always results in a query.
|
||||
if (std.mem.eql(u8, spec_str, "?")) {
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .query = target };
|
||||
continue;
|
||||
}
|
||||
|
||||
const rgb = RGB.parse(spec_str) catch return result;
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .set = .{
|
||||
.target = target,
|
||||
.color = rgb,
|
||||
} };
|
||||
}
|
||||
}
|
||||
|
||||
/// OSC 110-119: Reset Dynamic Colors
|
||||
fn parseResetDynamicColor(
|
||||
alloc: Allocator,
|
||||
color: DynamicColor,
|
||||
it: *std.mem.TokenIterator(u8, .scalar),
|
||||
) Allocator.Error!List {
|
||||
var result: List = .{};
|
||||
errdefer result.deinit(alloc);
|
||||
if (it.next() != null) return result;
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .reset = .{ .dynamic = color } };
|
||||
|
|
@ -95,6 +161,216 @@ pub const Request = union(enum) {
|
|||
};
|
||||
};
|
||||
|
||||
test "osc4" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Test every palette index
|
||||
for (0..std.math.maxInt(u8)) |idx| {
|
||||
// Simple color set
|
||||
// printf '\e]4;0;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Simple color query
|
||||
// printf '\e]4;0;?\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};?",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .query = .{ .palette = @intCast(idx) } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Trailing invalid data produces results up to that point
|
||||
// printf '\e]4;0;red;\e\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red;",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Whitespace doesn't produce a working value in xterm but we
|
||||
// allow it because Kitty does and it seems harmless.
|
||||
//
|
||||
// printf '\e]4;0;red \e\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red ",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Test every special color
|
||||
for (0..@typeInfo(SpecialColor).@"enum".fields.len) |i| {
|
||||
const special = try std.meta.intToEnum(SpecialColor, i);
|
||||
|
||||
// Simple color set
|
||||
// printf '\e]4;256;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{256 + i},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .special = special },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "osc5" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Test every special color
|
||||
for (0..@typeInfo(SpecialColor).@"enum".fields.len) |i| {
|
||||
const special = try std.meta.intToEnum(SpecialColor, i);
|
||||
|
||||
// Simple color set
|
||||
// printf '\e]4;256;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{i},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_5, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .special = special },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "osc4: multiple requests" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// printf '\e]4;0;red;1;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;1;blue",
|
||||
);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 1 },
|
||||
.color = RGB{ .r = 0, .g = 0, .b = 255 },
|
||||
} },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple requests with same index overwrite each other
|
||||
// printf '\e]4;0;red;0;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;0;blue",
|
||||
);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 0, .g = 0, .b = 255 },
|
||||
} },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// OSC 110-119: Reset Dynamic Colors
|
||||
test "reset dynamic" {
|
||||
const testing = std.testing;
|
||||
|
|
@ -111,7 +387,7 @@ test "reset dynamic" {
|
|||
// printf '\e]110\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, "");
|
||||
errdefer list.deinit(alloc);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .dynamic = color } },
|
||||
|
|
@ -124,7 +400,7 @@ test "reset dynamic" {
|
|||
// printf '\e]110;\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, ";");
|
||||
errdefer list.deinit(alloc);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .dynamic = color } },
|
||||
|
|
@ -137,7 +413,7 @@ test "reset dynamic" {
|
|||
// printf '\e]110 \e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, " ");
|
||||
errdefer list.deinit(alloc);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(0, list.count());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue