terminal: readonly stream can update more colors now

pull/9412/head
Mitchell Hashimoto 2025-10-30 09:58:27 -07:00
parent 2daecd94a5
commit 27a98123a0
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 260 additions and 10 deletions

View File

@ -161,6 +161,7 @@ pub const Handler = struct {
.end_of_command => self.terminal.screen.cursor.page_row.semantic_prompt = .input,
.mouse_shape => self.terminal.mouse_shape = value,
.color_operation => try self.colorOperation(value.op, &value.requests),
.kitty_color_report => try self.kittyColorOperation(value),
// No supported DCS commands have any terminal-modifying effects,
// but they may in the future. For now we just ignore it.
@ -186,7 +187,6 @@ pub const Handler = struct {
.device_attributes,
.device_status,
.kitty_keyboard_query,
.kitty_color_report,
.window_title,
.report_pwd,
.show_desktop_notification,
@ -305,21 +305,57 @@ pub const Handler = struct {
switch (req.*) {
.set => |set| {
switch (set.target) {
.palette => |i| self.terminal.colors.palette.set(i, set.color),
.dynamic,
.special,
=> {},
.palette => |i| {
self.terminal.flags.dirty.palette = true;
self.terminal.colors.palette.set(i, set.color);
},
.dynamic => |dynamic| switch (dynamic) {
.foreground => self.terminal.colors.foreground.set(set.color),
.background => self.terminal.colors.background.set(set.color),
.cursor => self.terminal.colors.cursor.set(set.color),
.pointer_foreground,
.pointer_background,
.tektronix_foreground,
.tektronix_background,
.highlight_background,
.tektronix_cursor,
.highlight_foreground,
=> {},
},
.special => {},
}
},
.reset => |target| switch (target) {
.palette => |i| self.terminal.colors.palette.reset(i),
.dynamic,
.special,
=> {},
.palette => |i| {
self.terminal.flags.dirty.palette = true;
self.terminal.colors.palette.reset(i);
},
.dynamic => |dynamic| switch (dynamic) {
.foreground => self.terminal.colors.foreground.reset(),
.background => self.terminal.colors.background.reset(),
.cursor => self.terminal.colors.cursor.reset(),
.pointer_foreground,
.pointer_background,
.tektronix_foreground,
.tektronix_background,
.highlight_background,
.tektronix_cursor,
.highlight_foreground,
=> {},
},
.special => {},
},
.reset_palette => self.terminal.colors.palette.resetAll(),
.reset_palette => {
const mask = &self.terminal.colors.palette.mask;
var mask_it = mask.iterator(.{});
while (mask_it.next()) |i| {
self.terminal.flags.dirty.palette = true;
self.terminal.colors.palette.reset(@intCast(i));
}
mask.* = .initEmpty();
},
.query,
.reset_special,
@ -327,6 +363,41 @@ pub const Handler = struct {
}
}
}
fn kittyColorOperation(
self: *Handler,
request: @import("kitty/color.zig").OSC,
) !void {
for (request.list.items) |item| {
switch (item) {
.set => |v| switch (v.key) {
.palette => |palette| {
self.terminal.flags.dirty.palette = true;
self.terminal.colors.palette.set(palette, v.color);
},
.special => |special| switch (special) {
.foreground => self.terminal.colors.foreground.set(v.color),
.background => self.terminal.colors.background.set(v.color),
.cursor => self.terminal.colors.cursor.set(v.color),
else => {},
},
},
.reset => |key| switch (key) {
.palette => |palette| {
self.terminal.flags.dirty.palette = true;
self.terminal.colors.palette.reset(palette);
},
.special => |special| switch (special) {
.foreground => self.terminal.colors.foreground.reset(),
.background => self.terminal.colors.background.reset(),
.cursor => self.terminal.colors.cursor.reset(),
else => {},
},
},
.query => {},
}
}
}
};
test "basic print" {
@ -624,3 +695,182 @@ test "OSC 104 reset all palette colors" {
try testing.expect(!t.colors.palette.mask.isSet(1));
try testing.expect(!t.colors.palette.mask.isSet(2));
}
test "OSC 10 set and reset foreground color" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Initially unset
try testing.expect(t.colors.foreground.get() == null);
// Set foreground to red
try s.nextSlice("\x1b]10;rgb:ff/00/00\x1b\\");
const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0xff), fg.r);
try testing.expectEqual(@as(u8, 0x00), fg.g);
try testing.expectEqual(@as(u8, 0x00), fg.b);
// Reset foreground
try s.nextSlice("\x1b]110\x1b\\");
try testing.expect(t.colors.foreground.get() == null);
}
test "OSC 11 set and reset background color" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set background to green
try s.nextSlice("\x1b]11;rgb:00/ff/00\x1b\\");
const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x00), bg.r);
try testing.expectEqual(@as(u8, 0xff), bg.g);
try testing.expectEqual(@as(u8, 0x00), bg.b);
// Reset background
try s.nextSlice("\x1b]111\x1b\\");
try testing.expect(t.colors.background.get() == null);
}
test "OSC 12 set and reset cursor color" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set cursor to blue
try s.nextSlice("\x1b]12;rgb:00/00/ff\x1b\\");
const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0x00), cursor.r);
try testing.expectEqual(@as(u8, 0x00), cursor.g);
try testing.expectEqual(@as(u8, 0xff), cursor.b);
// Reset cursor
try s.nextSlice("\x1b]112\x1b\\");
// After reset, cursor might be null (using default)
}
test "kitty color protocol set palette" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set palette color 5 to magenta using kitty protocol
try s.nextSlice("\x1b]21;5=rgb:ff/00/ff\x1b\\");
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[5].r);
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[5].g);
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[5].b);
try testing.expect(t.colors.palette.mask.isSet(5));
try testing.expect(t.flags.dirty.palette);
}
test "kitty color protocol reset palette" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set and then reset palette color
const original = t.colors.palette.original[7];
try s.nextSlice("\x1b]21;7=rgb:aa/bb/cc\x1b\\");
try testing.expect(t.colors.palette.mask.isSet(7));
try s.nextSlice("\x1b]21;7=\x1b\\");
try testing.expectEqual(original, t.colors.palette.current[7]);
try testing.expect(!t.colors.palette.mask.isSet(7));
}
test "kitty color protocol set foreground" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set foreground using kitty protocol
try s.nextSlice("\x1b]21;foreground=rgb:12/34/56\x1b\\");
const fg = t.colors.foreground.get().?;
try testing.expectEqual(@as(u8, 0x12), fg.r);
try testing.expectEqual(@as(u8, 0x34), fg.g);
try testing.expectEqual(@as(u8, 0x56), fg.b);
}
test "kitty color protocol set background" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set background using kitty protocol
try s.nextSlice("\x1b]21;background=rgb:78/9a/bc\x1b\\");
const bg = t.colors.background.get().?;
try testing.expectEqual(@as(u8, 0x78), bg.r);
try testing.expectEqual(@as(u8, 0x9a), bg.g);
try testing.expectEqual(@as(u8, 0xbc), bg.b);
}
test "kitty color protocol set cursor" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set cursor using kitty protocol
try s.nextSlice("\x1b]21;cursor=rgb:de/f0/12\x1b\\");
const cursor = t.colors.cursor.get().?;
try testing.expectEqual(@as(u8, 0xde), cursor.r);
try testing.expectEqual(@as(u8, 0xf0), cursor.g);
try testing.expectEqual(@as(u8, 0x12), cursor.b);
}
test "kitty color protocol reset foreground" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Set and reset foreground
try s.nextSlice("\x1b]21;foreground=rgb:11/22/33\x1b\\");
try testing.expect(t.colors.foreground.get() != null);
try s.nextSlice("\x1b]21;foreground=\x1b\\");
// After reset, should be unset
try testing.expect(t.colors.foreground.get() == null);
}
test "palette dirty flag set on color change" {
var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator);
var s: Stream = .initAlloc(testing.allocator, .init(&t));
defer s.deinit();
// Clear dirty flag
t.flags.dirty.palette = false;
// Setting palette color should set dirty flag
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
try testing.expect(t.flags.dirty.palette);
// Clear and test reset
t.flags.dirty.palette = false;
try s.nextSlice("\x1b]104;0\x1b\\");
try testing.expect(t.flags.dirty.palette);
// Clear and test kitty protocol
t.flags.dirty.palette = false;
try s.nextSlice("\x1b]21;1=rgb:00/ff/00\x1b\\");
try testing.expect(t.flags.dirty.palette);
}