terminal: move bg, fg, cursor color state into Terminal (#9412)
This moves the bg, fg, cursor color state into the `Terminal` struct, including all default and override handling. I've rewritten our color palette handling so that overrides, defaults, resets, etc are all handled by the terminal package. I've added much more unit test coverage. This has various benefits: * libghostty now handles OSC and Kitty color operations * formatters can be aware of all of these colors (not implemented in this PR) * renderer has way less inter-thread messages * renderer uses less memory * termio stream handler uses less memory and becomes simpler * override logic, changing defaults can all be unit tested * we have unit tests for kitty color operations end-to-end now (cc @jcollie heyo!) There's a ton of risk on this PR too. There's a lot of really tiny conditionals EVERYWHERE and I'm sure I got at least one wrong. We'll let this bake in tip to find those, I'm sure they're minor and will show up as just the wrong color somewhere. **AI disclosure:** Amp wrote many of the tests. I did all the implementation.pull/9414/head
commit
4c504560d4
|
|
@ -31,7 +31,7 @@ pub fn main() !void {
|
||||||
// Use TerminalFormatter to emit HTML
|
// Use TerminalFormatter to emit HTML
|
||||||
const formatter: ghostty_vt.formatter.TerminalFormatter = .init(&t, .{
|
const formatter: ghostty_vt.formatter.TerminalFormatter = .init(&t, .{
|
||||||
.emit = .html,
|
.emit = .html,
|
||||||
.palette = &t.color_palette.colors,
|
.palette = &t.colors.palette.current,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write to stdout
|
// Write to stdout
|
||||||
|
|
|
||||||
|
|
@ -4960,6 +4960,13 @@ pub const TerminalColor = union(enum) {
|
||||||
return .{ .color = try Color.parseCLI(input) };
|
return .{ .color = try Color.parseCLI(input) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toTerminalRGB(self: TerminalColor) ?terminal.color.RGB {
|
||||||
|
return switch (self) {
|
||||||
|
.color => |v| v.toTerminalRGB(),
|
||||||
|
.@"cell-foreground", .@"cell-background" => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Used by Formatter
|
/// Used by Formatter
|
||||||
pub fn formatEntry(self: TerminalColor, formatter: formatterpkg.EntryFormatter) !void {
|
pub fn formatEntry(self: TerminalColor, formatter: formatterpkg.EntryFormatter) !void {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ pub const Cell = struct {
|
||||||
switch (self.style.fg_color) {
|
switch (self.style.fg_color) {
|
||||||
.none => cimgui.c.igText("default"),
|
.none => cimgui.c.igText("default"),
|
||||||
.palette => |idx| {
|
.palette => |idx| {
|
||||||
const rgb = t.color_palette.colors[idx];
|
const rgb = t.colors.palette.current[idx];
|
||||||
cimgui.c.igValue_Int("Palette", idx);
|
cimgui.c.igValue_Int("Palette", idx);
|
||||||
var color: [3]f32 = .{
|
var color: [3]f32 = .{
|
||||||
@as(f32, @floatFromInt(rgb.r)) / 255,
|
@as(f32, @floatFromInt(rgb.r)) / 255,
|
||||||
|
|
@ -169,7 +169,7 @@ pub const Cell = struct {
|
||||||
switch (self.style.bg_color) {
|
switch (self.style.bg_color) {
|
||||||
.none => cimgui.c.igText("default"),
|
.none => cimgui.c.igText("default"),
|
||||||
.palette => |idx| {
|
.palette => |idx| {
|
||||||
const rgb = t.color_palette.colors[idx];
|
const rgb = t.colors.palette.current[idx];
|
||||||
cimgui.c.igValue_Int("Palette", idx);
|
cimgui.c.igValue_Int("Palette", idx);
|
||||||
var color: [3]f32 = .{
|
var color: [3]f32 = .{
|
||||||
@as(f32, @floatFromInt(rgb.r)) / 255,
|
@as(f32, @floatFromInt(rgb.r)) / 255,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ pub fn renderInTable(
|
||||||
switch (cursor.style.fg_color) {
|
switch (cursor.style.fg_color) {
|
||||||
.none => cimgui.c.igText("default"),
|
.none => cimgui.c.igText("default"),
|
||||||
.palette => |idx| {
|
.palette => |idx| {
|
||||||
const rgb = t.color_palette.colors[idx];
|
const rgb = t.colors.palette.current[idx];
|
||||||
cimgui.c.igValue_Int("Palette", idx);
|
cimgui.c.igValue_Int("Palette", idx);
|
||||||
var color: [3]f32 = .{
|
var color: [3]f32 = .{
|
||||||
@as(f32, @floatFromInt(rgb.r)) / 255,
|
@as(f32, @floatFromInt(rgb.r)) / 255,
|
||||||
|
|
@ -90,7 +90,7 @@ pub fn renderInTable(
|
||||||
switch (cursor.style.bg_color) {
|
switch (cursor.style.bg_color) {
|
||||||
.none => cimgui.c.igText("default"),
|
.none => cimgui.c.igText("default"),
|
||||||
.palette => |idx| {
|
.palette => |idx| {
|
||||||
const rgb = t.color_palette.colors[idx];
|
const rgb = t.colors.palette.current[idx];
|
||||||
cimgui.c.igValue_Int("Palette", idx);
|
cimgui.c.igValue_Int("Palette", idx);
|
||||||
var color: [3]f32 = .{
|
var color: [3]f32 = .{
|
||||||
@as(f32, @floatFromInt(rgb.r)) / 255,
|
@as(f32, @floatFromInt(rgb.r)) / 255,
|
||||||
|
|
|
||||||
|
|
@ -437,21 +437,6 @@ fn drainMailbox(self: *Thread) !void {
|
||||||
grid.set.deref(grid.old_key);
|
grid.set.deref(grid.old_key);
|
||||||
},
|
},
|
||||||
|
|
||||||
.foreground_color => |color| {
|
|
||||||
self.renderer.foreground_color = color;
|
|
||||||
self.renderer.markDirty();
|
|
||||||
},
|
|
||||||
|
|
||||||
.background_color => |color| {
|
|
||||||
self.renderer.background_color = color;
|
|
||||||
self.renderer.markDirty();
|
|
||||||
},
|
|
||||||
|
|
||||||
.cursor_color => |color| {
|
|
||||||
self.renderer.cursor_color = color;
|
|
||||||
self.renderer.markDirty();
|
|
||||||
},
|
|
||||||
|
|
||||||
.resize => |v| self.renderer.setScreenSize(v),
|
.resize => |v| self.renderer.setScreenSize(v),
|
||||||
|
|
||||||
.change_config => |config| {
|
.change_config => |config| {
|
||||||
|
|
|
||||||
|
|
@ -120,30 +120,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
scrollbar: terminal.Scrollbar,
|
scrollbar: terminal.Scrollbar,
|
||||||
scrollbar_dirty: bool,
|
scrollbar_dirty: bool,
|
||||||
|
|
||||||
/// The foreground color set by an OSC 10 sequence. If unset then
|
|
||||||
/// default_foreground_color is used.
|
|
||||||
foreground_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Foreground color set in the user's config file.
|
|
||||||
default_foreground_color: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The background color set by an OSC 11 sequence. If unset then
|
|
||||||
/// default_background_color is used.
|
|
||||||
background_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Background color set in the user's config file.
|
|
||||||
default_background_color: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The cursor color set by an OSC 12 sequence. If unset then
|
|
||||||
/// default_cursor_color is used.
|
|
||||||
cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Default cursor color when no color is set explicitly by an OSC 12 command.
|
|
||||||
/// This is cursor color as set in the user's config, if any. If no cursor color
|
|
||||||
/// is set in the user's config, then the cursor color is determined by the
|
|
||||||
/// current foreground color.
|
|
||||||
default_cursor_color: ?configpkg.Config.TerminalColor,
|
|
||||||
|
|
||||||
/// The current set of cells to render. This is rebuilt on every frame
|
/// The current set of cells to render. This is rebuilt on every frame
|
||||||
/// but we keep this around so that we don't reallocate. Each set of
|
/// but we keep this around so that we don't reallocate. Each set of
|
||||||
/// cells goes into a separate shader.
|
/// cells goes into a separate shader.
|
||||||
|
|
@ -691,12 +667,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
.focused = true,
|
.focused = true,
|
||||||
.scrollbar = .zero,
|
.scrollbar = .zero,
|
||||||
.scrollbar_dirty = false,
|
.scrollbar_dirty = false,
|
||||||
.foreground_color = null,
|
|
||||||
.default_foreground_color = options.config.foreground,
|
|
||||||
.background_color = null,
|
|
||||||
.default_background_color = options.config.background,
|
|
||||||
.cursor_color = null,
|
|
||||||
.default_cursor_color = options.config.cursor_color,
|
|
||||||
|
|
||||||
// Render state
|
// Render state
|
||||||
.cells = .{},
|
.cells = .{},
|
||||||
|
|
@ -1094,10 +1064,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
// Data we extract out of the critical area.
|
// Data we extract out of the critical area.
|
||||||
const Critical = struct {
|
const Critical = struct {
|
||||||
bg: terminal.color.RGB,
|
bg: terminal.color.RGB,
|
||||||
|
fg: terminal.color.RGB,
|
||||||
screen: terminal.Screen,
|
screen: terminal.Screen,
|
||||||
screen_type: terminal.ScreenType,
|
screen_type: terminal.ScreenType,
|
||||||
mouse: renderer.State.Mouse,
|
mouse: renderer.State.Mouse,
|
||||||
preedit: ?renderer.State.Preedit,
|
preedit: ?renderer.State.Preedit,
|
||||||
|
cursor_color: ?terminal.color.RGB,
|
||||||
cursor_style: ?renderer.CursorStyle,
|
cursor_style: ?renderer.CursorStyle,
|
||||||
color_palette: terminal.color.Palette,
|
color_palette: terminal.color.Palette,
|
||||||
scrollbar: terminal.Scrollbar,
|
scrollbar: terminal.Scrollbar,
|
||||||
|
|
@ -1132,36 +1104,16 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
// cross-thread mailbox message within the IO path.
|
// cross-thread mailbox message within the IO path.
|
||||||
const scrollbar = state.terminal.screen.pages.scrollbar();
|
const scrollbar = state.terminal.screen.pages.scrollbar();
|
||||||
|
|
||||||
// Swap bg/fg if the terminal is reversed
|
// Get our bg/fg, swap them if reversed.
|
||||||
const bg = self.background_color orelse self.default_background_color;
|
const RGB = terminal.color.RGB;
|
||||||
const fg = self.foreground_color orelse self.default_foreground_color;
|
const bg: RGB, const fg: RGB = colors: {
|
||||||
defer {
|
const bg = state.terminal.colors.background.get().?;
|
||||||
if (self.background_color) |*c| {
|
const fg = state.terminal.colors.foreground.get().?;
|
||||||
c.* = bg;
|
break :colors if (state.terminal.modes.get(.reverse_colors))
|
||||||
} else {
|
.{ fg, bg }
|
||||||
self.default_background_color = bg;
|
else
|
||||||
}
|
.{ bg, fg };
|
||||||
|
};
|
||||||
if (self.foreground_color) |*c| {
|
|
||||||
c.* = fg;
|
|
||||||
} else {
|
|
||||||
self.default_foreground_color = fg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.terminal.modes.get(.reverse_colors)) {
|
|
||||||
if (self.background_color) |*c| {
|
|
||||||
c.* = fg;
|
|
||||||
} else {
|
|
||||||
self.default_background_color = fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.foreground_color) |*c| {
|
|
||||||
c.* = bg;
|
|
||||||
} else {
|
|
||||||
self.default_foreground_color = bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the viewport pin so that we can compare it to the current.
|
// Get the viewport pin so that we can compare it to the current.
|
||||||
const viewport_pin = state.terminal.screen.pages.pin(.{ .viewport = .{} }).?;
|
const viewport_pin = state.terminal.screen.pages.pin(.{ .viewport = .{} }).?;
|
||||||
|
|
@ -1252,13 +1204,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
self.cells_viewport = viewport_pin;
|
self.cells_viewport = viewport_pin;
|
||||||
|
|
||||||
break :critical .{
|
break :critical .{
|
||||||
.bg = self.background_color orelse self.default_background_color,
|
.bg = bg,
|
||||||
|
.fg = fg,
|
||||||
.screen = screen_copy,
|
.screen = screen_copy,
|
||||||
.screen_type = state.terminal.active_screen,
|
.screen_type = state.terminal.active_screen,
|
||||||
.mouse = state.mouse,
|
.mouse = state.mouse,
|
||||||
.preedit = preedit,
|
.preedit = preedit,
|
||||||
|
.cursor_color = state.terminal.colors.cursor.get(),
|
||||||
.cursor_style = cursor_style,
|
.cursor_style = cursor_style,
|
||||||
.color_palette = state.terminal.color_palette.colors,
|
.color_palette = state.terminal.colors.palette.current,
|
||||||
.scrollbar = scrollbar,
|
.scrollbar = scrollbar,
|
||||||
.full_rebuild = full_rebuild,
|
.full_rebuild = full_rebuild,
|
||||||
};
|
};
|
||||||
|
|
@ -1277,6 +1231,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
critical.preedit,
|
critical.preedit,
|
||||||
critical.cursor_style,
|
critical.cursor_style,
|
||||||
&critical.color_palette,
|
&critical.color_palette,
|
||||||
|
critical.bg,
|
||||||
|
critical.fg,
|
||||||
|
critical.cursor_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Notify our shaper we're done for the frame. For some shapers,
|
// Notify our shaper we're done for the frame. For some shapers,
|
||||||
|
|
@ -2104,11 +2061,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
self.uniforms.bools.use_linear_blending = config.blending.isLinear();
|
self.uniforms.bools.use_linear_blending = config.blending.isLinear();
|
||||||
self.uniforms.bools.use_linear_correction = config.blending == .@"linear-corrected";
|
self.uniforms.bools.use_linear_correction = config.blending == .@"linear-corrected";
|
||||||
|
|
||||||
// Set our new colors
|
|
||||||
self.default_background_color = config.background;
|
|
||||||
self.default_foreground_color = config.foreground;
|
|
||||||
self.default_cursor_color = config.cursor_color;
|
|
||||||
|
|
||||||
const bg_image_config_changed =
|
const bg_image_config_changed =
|
||||||
self.config.bg_image_fit != config.bg_image_fit or
|
self.config.bg_image_fit != config.bg_image_fit or
|
||||||
self.config.bg_image_position != config.bg_image_position or
|
self.config.bg_image_position != config.bg_image_position or
|
||||||
|
|
@ -2370,6 +2322,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
preedit: ?renderer.State.Preedit,
|
preedit: ?renderer.State.Preedit,
|
||||||
cursor_style_: ?renderer.CursorStyle,
|
cursor_style_: ?renderer.CursorStyle,
|
||||||
color_palette: *const terminal.color.Palette,
|
color_palette: *const terminal.color.Palette,
|
||||||
|
background: terminal.color.RGB,
|
||||||
|
foreground: terminal.color.RGB,
|
||||||
|
terminal_cursor_color: ?terminal.color.RGB,
|
||||||
) !void {
|
) !void {
|
||||||
self.draw_mutex.lock();
|
self.draw_mutex.lock();
|
||||||
defer self.draw_mutex.unlock();
|
defer self.draw_mutex.unlock();
|
||||||
|
|
@ -2503,12 +2458,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
.extend => if (y == 0) {
|
.extend => if (y == 0) {
|
||||||
self.uniforms.padding_extend.up = !row.neverExtendBg(
|
self.uniforms.padding_extend.up = !row.neverExtendBg(
|
||||||
color_palette,
|
color_palette,
|
||||||
self.background_color orelse self.default_background_color,
|
background,
|
||||||
);
|
);
|
||||||
} else if (y == self.cells.size.rows - 1) {
|
} else if (y == self.cells.size.rows - 1) {
|
||||||
self.uniforms.padding_extend.down = !row.neverExtendBg(
|
self.uniforms.padding_extend.down = !row.neverExtendBg(
|
||||||
color_palette,
|
color_palette,
|
||||||
self.background_color orelse self.default_background_color,
|
background,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -2629,7 +2584,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
// configuration, inversions, selections, etc.
|
// configuration, inversions, selections, etc.
|
||||||
const bg_style = style.bg(cell, color_palette);
|
const bg_style = style.bg(cell, color_palette);
|
||||||
const fg_style = style.fg(.{
|
const fg_style = style.fg(.{
|
||||||
.default = self.foreground_color orelse self.default_foreground_color,
|
.default = foreground,
|
||||||
.palette = color_palette,
|
.palette = color_palette,
|
||||||
.bold = self.config.bold_color,
|
.bold = self.config.bold_color,
|
||||||
});
|
});
|
||||||
|
|
@ -2649,7 +2604,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
|
|
||||||
// If no configuration, then our selection background
|
// If no configuration, then our selection background
|
||||||
// is our foreground color.
|
// is our foreground color.
|
||||||
break :bg self.foreground_color orelse self.default_foreground_color;
|
break :bg foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not selected
|
// Not selected
|
||||||
|
|
@ -2671,9 +2626,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
const fg = fg: {
|
const fg = fg: {
|
||||||
// Our happy-path non-selection background color
|
// Our happy-path non-selection background color
|
||||||
// is our style or our configured defaults.
|
// is our style or our configured defaults.
|
||||||
const final_bg = bg_style orelse
|
const final_bg = bg_style orelse background;
|
||||||
self.background_color orelse
|
|
||||||
self.default_background_color;
|
|
||||||
|
|
||||||
// Whether we need to use the bg color as our fg color:
|
// Whether we need to use the bg color as our fg color:
|
||||||
// - Cell is selected, inverted, and set to cell-foreground
|
// - Cell is selected, inverted, and set to cell-foreground
|
||||||
|
|
@ -2689,7 +2642,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
break :fg self.background_color orelse self.default_background_color;
|
break :fg background;
|
||||||
}
|
}
|
||||||
|
|
||||||
break :fg if (style.flags.inverse)
|
break :fg if (style.flags.inverse)
|
||||||
|
|
@ -2703,7 +2656,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
|
|
||||||
// Set the cell's background color.
|
// Set the cell's background color.
|
||||||
{
|
{
|
||||||
const rgb = bg orelse self.background_color orelse self.default_background_color;
|
const rgb = bg orelse background;
|
||||||
|
|
||||||
// Determine our background alpha. If we have transparency configured
|
// Determine our background alpha. If we have transparency configured
|
||||||
// then this is dynamic depending on some situations. This is all
|
// then this is dynamic depending on some situations. This is all
|
||||||
|
|
@ -2888,24 +2841,24 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
const style = cursor_style_ orelse break :cursor;
|
const style = cursor_style_ orelse break :cursor;
|
||||||
const cursor_color = cursor_color: {
|
const cursor_color = cursor_color: {
|
||||||
// If an explicit cursor color was set by OSC 12, use that.
|
// If an explicit cursor color was set by OSC 12, use that.
|
||||||
if (self.cursor_color) |v| break :cursor_color v;
|
if (terminal_cursor_color) |v| break :cursor_color v;
|
||||||
|
|
||||||
// Use our configured color if specified
|
// Use our configured color if specified
|
||||||
if (self.default_cursor_color) |v| switch (v) {
|
if (self.config.cursor_color) |v| switch (v) {
|
||||||
.color => |color| break :cursor_color color.toTerminalRGB(),
|
.color => |color| break :cursor_color color.toTerminalRGB(),
|
||||||
inline .@"cell-foreground",
|
inline .@"cell-foreground",
|
||||||
.@"cell-background",
|
.@"cell-background",
|
||||||
=> |_, tag| {
|
=> |_, tag| {
|
||||||
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
||||||
const fg_style = sty.fg(.{
|
const fg_style = sty.fg(.{
|
||||||
.default = self.foreground_color orelse self.default_foreground_color,
|
.default = foreground,
|
||||||
.palette = color_palette,
|
.palette = color_palette,
|
||||||
.bold = self.config.bold_color,
|
.bold = self.config.bold_color,
|
||||||
});
|
});
|
||||||
const bg_style = sty.bg(
|
const bg_style = sty.bg(
|
||||||
screen.cursor.page_cell,
|
screen.cursor.page_cell,
|
||||||
color_palette,
|
color_palette,
|
||||||
) orelse self.background_color orelse self.default_background_color;
|
) orelse background;
|
||||||
|
|
||||||
break :cursor_color switch (tag) {
|
break :cursor_color switch (tag) {
|
||||||
.color => unreachable,
|
.color => unreachable,
|
||||||
|
|
@ -2915,7 +2868,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
break :cursor_color self.foreground_color orelse self.default_foreground_color;
|
break :cursor_color foreground;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.addCursor(screen, style, cursor_color);
|
self.addCursor(screen, style, cursor_color);
|
||||||
|
|
@ -2950,11 +2903,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
|
|
||||||
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
||||||
const fg_style = sty.fg(.{
|
const fg_style = sty.fg(.{
|
||||||
.default = self.foreground_color orelse self.default_foreground_color,
|
.default = foreground,
|
||||||
.palette = color_palette,
|
.palette = color_palette,
|
||||||
.bold = self.config.bold_color,
|
.bold = self.config.bold_color,
|
||||||
});
|
});
|
||||||
const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color;
|
const bg_style = sty.bg(
|
||||||
|
screen.cursor.page_cell,
|
||||||
|
color_palette,
|
||||||
|
) orelse background;
|
||||||
|
|
||||||
break :blk switch (txt) {
|
break :blk switch (txt) {
|
||||||
// If the cell is reversed, use the opposite cell color instead.
|
// If the cell is reversed, use the opposite cell color instead.
|
||||||
|
|
@ -2962,7 +2918,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
.@"cell-background" => if (sty.flags.inverse) fg_style else bg_style,
|
.@"cell-background" => if (sty.flags.inverse) fg_style else bg_style,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
} else self.background_color orelse self.default_background_color;
|
} else background;
|
||||||
|
|
||||||
self.uniforms.cursor_color = .{
|
self.uniforms.cursor_color = .{
|
||||||
uniform_color.r,
|
uniform_color.r,
|
||||||
|
|
@ -2978,7 +2934,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
const range = preedit_range.?;
|
const range = preedit_range.?;
|
||||||
var x = range.x[0];
|
var x = range.x[0];
|
||||||
for (preedit_v.codepoints[range.cp_offset..]) |cp| {
|
for (preedit_v.codepoints[range.cp_offset..]) |cp| {
|
||||||
self.addPreeditCell(cp, .{ .x = x, .y = range.y }) catch |err| {
|
self.addPreeditCell(
|
||||||
|
cp,
|
||||||
|
.{ .x = x, .y = range.y },
|
||||||
|
background,
|
||||||
|
foreground,
|
||||||
|
) catch |err| {
|
||||||
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
||||||
x,
|
x,
|
||||||
range.y,
|
range.y,
|
||||||
|
|
@ -3253,10 +3214,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||||
self: *Self,
|
self: *Self,
|
||||||
cp: renderer.State.Preedit.Codepoint,
|
cp: renderer.State.Preedit.Codepoint,
|
||||||
coord: terminal.Coordinate,
|
coord: terminal.Coordinate,
|
||||||
|
screen_bg: terminal.color.RGB,
|
||||||
|
screen_fg: terminal.color.RGB,
|
||||||
) !void {
|
) !void {
|
||||||
// Preedit is rendered inverted
|
// Preedit is rendered inverted
|
||||||
const bg = self.foreground_color orelse self.default_foreground_color;
|
const bg = screen_fg;
|
||||||
const fg = self.background_color orelse self.default_background_color;
|
const fg = screen_bg;
|
||||||
|
|
||||||
// Render the glyph for our preedit text
|
// Render the glyph for our preedit text
|
||||||
const render_ = self.font_grid.renderCodepoint(
|
const render_ = self.font_grid.renderCodepoint(
|
||||||
|
|
|
||||||
|
|
@ -42,16 +42,6 @@ pub const Message = union(enum) {
|
||||||
old_key: font.SharedGridSet.Key,
|
old_key: font.SharedGridSet.Key,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Change the foreground color as set by an OSC 10 command, if any.
|
|
||||||
foreground_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Change the background color as set by an OSC 11 command, if any.
|
|
||||||
background_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Change the cursor color. This can be done separately from changing the
|
|
||||||
/// config file in response to an OSC 12 command.
|
|
||||||
cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Changes the size. The screen size might change, padding, grid, etc.
|
/// Changes the size. The screen size might change, padding, grid, etc.
|
||||||
resize: renderer.Size,
|
resize: renderer.Size,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,17 +73,8 @@ scrolling_region: ScrollingRegion,
|
||||||
/// The last reported pwd, if any.
|
/// The last reported pwd, if any.
|
||||||
pwd: std.ArrayList(u8),
|
pwd: std.ArrayList(u8),
|
||||||
|
|
||||||
/// The default color palette. This is only modified by changing the config file
|
/// The color state for this terminal.
|
||||||
/// and is used to reset the palette when receiving an OSC 104 command.
|
colors: Colors,
|
||||||
default_palette: color.Palette = color.default,
|
|
||||||
|
|
||||||
/// The color palette to use. The mask indicates which palette indices have been
|
|
||||||
/// modified with OSC 4
|
|
||||||
color_palette: struct {
|
|
||||||
const Mask = std.StaticBitSet(@typeInfo(color.Palette).array.len);
|
|
||||||
colors: color.Palette = color.default,
|
|
||||||
mask: Mask = .initEmpty(),
|
|
||||||
} = .{},
|
|
||||||
|
|
||||||
/// The previous printed character. This is used for the repeat previous
|
/// The previous printed character. This is used for the repeat previous
|
||||||
/// char CSI (ESC [ <n> b).
|
/// char CSI (ESC [ <n> b).
|
||||||
|
|
@ -134,6 +125,23 @@ flags: packed struct {
|
||||||
dirty: Dirty = .{},
|
dirty: Dirty = .{},
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
|
/// The various color configurations a terminal maintains and that can
|
||||||
|
/// be set dynamically via OSC, with defaults usually coming from a
|
||||||
|
/// configuration.
|
||||||
|
pub const Colors = struct {
|
||||||
|
background: color.DynamicRGB,
|
||||||
|
foreground: color.DynamicRGB,
|
||||||
|
cursor: color.DynamicRGB,
|
||||||
|
palette: color.DynamicPalette,
|
||||||
|
|
||||||
|
pub const default: Colors = .{
|
||||||
|
.background = .unset,
|
||||||
|
.foreground = .unset,
|
||||||
|
.cursor = .unset,
|
||||||
|
.palette = .default,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/// This is a set of dirty flags the renderer can use to determine
|
/// This is a set of dirty flags the renderer can use to determine
|
||||||
/// what parts of the screen need to be redrawn. It is up to the renderer
|
/// what parts of the screen need to be redrawn. It is up to the renderer
|
||||||
/// to clear these flags.
|
/// to clear these flags.
|
||||||
|
|
@ -199,6 +207,7 @@ pub const Options = struct {
|
||||||
cols: size.CellCountInt,
|
cols: size.CellCountInt,
|
||||||
rows: size.CellCountInt,
|
rows: size.CellCountInt,
|
||||||
max_scrollback: usize = 10_000,
|
max_scrollback: usize = 10_000,
|
||||||
|
colors: Colors = .default,
|
||||||
|
|
||||||
/// The default mode state. When the terminal gets a reset, it
|
/// The default mode state. When the terminal gets a reset, it
|
||||||
/// will revert back to this state.
|
/// will revert back to this state.
|
||||||
|
|
@ -212,7 +221,7 @@ pub fn init(
|
||||||
) !Terminal {
|
) !Terminal {
|
||||||
const cols = opts.cols;
|
const cols = opts.cols;
|
||||||
const rows = opts.rows;
|
const rows = opts.rows;
|
||||||
return Terminal{
|
return .{
|
||||||
.cols = cols,
|
.cols = cols,
|
||||||
.rows = rows,
|
.rows = rows,
|
||||||
.active_screen = .primary,
|
.active_screen = .primary,
|
||||||
|
|
@ -226,6 +235,7 @@ pub fn init(
|
||||||
.right = cols - 1,
|
.right = cols - 1,
|
||||||
},
|
},
|
||||||
.pwd = .empty,
|
.pwd = .empty,
|
||||||
|
.colors = opts.colors,
|
||||||
.modes = .{
|
.modes = .{
|
||||||
.values = opts.default_modes,
|
.values = opts.default_modes,
|
||||||
.default = opts.default_modes,
|
.default = opts.default_modes,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const colorpkg = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const x11_color = @import("x11_color.zig");
|
const x11_color = @import("x11_color.zig");
|
||||||
|
|
@ -45,6 +47,97 @@ pub const default: Palette = default: {
|
||||||
/// Palette is the 256 color palette.
|
/// Palette is the 256 color palette.
|
||||||
pub const Palette = [256]RGB;
|
pub const Palette = [256]RGB;
|
||||||
|
|
||||||
|
/// A palette that can have its colors changed and reset. Purposely built
|
||||||
|
/// for terminal color operations.
|
||||||
|
pub const DynamicPalette = struct {
|
||||||
|
/// The current palette including any user modifications.
|
||||||
|
current: Palette,
|
||||||
|
|
||||||
|
/// The original/default palette values.
|
||||||
|
original: Palette,
|
||||||
|
|
||||||
|
/// A bitset where each bit represents whether the corresponding
|
||||||
|
/// palette index has been modified from its default value.
|
||||||
|
mask: Mask,
|
||||||
|
|
||||||
|
const Mask = std.StaticBitSet(@typeInfo(Palette).array.len);
|
||||||
|
|
||||||
|
pub const default: DynamicPalette = .init(colorpkg.default);
|
||||||
|
|
||||||
|
/// Initialize a dynamic palette with a default palette.
|
||||||
|
pub fn init(def: Palette) DynamicPalette {
|
||||||
|
return .{
|
||||||
|
.current = def,
|
||||||
|
.original = def,
|
||||||
|
.mask = .initEmpty(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a custom color at the given palette index.
|
||||||
|
pub fn set(self: *DynamicPalette, idx: u8, color: RGB) void {
|
||||||
|
self.current[idx] = color;
|
||||||
|
self.mask.set(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the color at the given palette index to its original value.
|
||||||
|
pub fn reset(self: *DynamicPalette, idx: u8) void {
|
||||||
|
self.current[idx] = self.original[idx];
|
||||||
|
self.mask.unset(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset all colors to their original values.
|
||||||
|
pub fn resetAll(self: *DynamicPalette) void {
|
||||||
|
self.* = .init(self.original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the default palette, but preserve the changed values.
|
||||||
|
pub fn changeDefault(self: *DynamicPalette, def: Palette) void {
|
||||||
|
self.original = def;
|
||||||
|
|
||||||
|
// Fast path, the palette is usually not changed.
|
||||||
|
if (self.mask.count() == 0) {
|
||||||
|
self.current = self.original;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are usually less set than unset, so iterate over the changed
|
||||||
|
// values and override them.
|
||||||
|
var current = def;
|
||||||
|
var it = self.mask.iterator(.{});
|
||||||
|
while (it.next()) |idx| current[idx] = self.current[idx];
|
||||||
|
self.current = current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// RGB value that can be changed and reset. This can also be totally unset
|
||||||
|
/// in every way, in which case the caller can determine their own ultimate
|
||||||
|
/// default.
|
||||||
|
pub const DynamicRGB = struct {
|
||||||
|
override: ?RGB,
|
||||||
|
default: ?RGB,
|
||||||
|
|
||||||
|
pub const unset: DynamicRGB = .{ .override = null, .default = null };
|
||||||
|
|
||||||
|
pub fn init(def: RGB) DynamicRGB {
|
||||||
|
return .{
|
||||||
|
.override = null,
|
||||||
|
.default = def,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *const DynamicRGB) ?RGB {
|
||||||
|
return self.override orelse self.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: *DynamicRGB, color: RGB) void {
|
||||||
|
self.override = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *DynamicRGB) void {
|
||||||
|
self.override = self.default;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Color names in the standard 8 or 16 color palette.
|
/// Color names in the standard 8 or 16 color palette.
|
||||||
pub const Name = enum(u8) {
|
pub const Name = enum(u8) {
|
||||||
black = 0,
|
black = 0,
|
||||||
|
|
@ -456,3 +549,118 @@ test "RGB.parse" {
|
||||||
try testing.expectError(error.InvalidFormat, RGB.parse("#fffff"));
|
try testing.expectError(error.InvalidFormat, RGB.parse("#fffff"));
|
||||||
try testing.expectError(error.InvalidFormat, RGB.parse("#gggggg"));
|
try testing.expectError(error.InvalidFormat, RGB.parse("#gggggg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: init" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
try testing.expectEqual(default, p.current);
|
||||||
|
try testing.expectEqual(default, p.original);
|
||||||
|
try testing.expectEqual(@as(usize, 0), p.mask.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: set" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
const new_color = RGB{ .r = 255, .g = 0, .b = 0 };
|
||||||
|
|
||||||
|
p.set(0, new_color);
|
||||||
|
try testing.expectEqual(new_color, p.current[0]);
|
||||||
|
try testing.expect(p.mask.isSet(0));
|
||||||
|
try testing.expectEqual(@as(usize, 1), p.mask.count());
|
||||||
|
|
||||||
|
try testing.expectEqual(default[0], p.original[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: reset" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
const new_color = RGB{ .r = 255, .g = 0, .b = 0 };
|
||||||
|
|
||||||
|
p.set(0, new_color);
|
||||||
|
try testing.expect(p.mask.isSet(0));
|
||||||
|
|
||||||
|
p.reset(0);
|
||||||
|
try testing.expectEqual(default[0], p.current[0]);
|
||||||
|
try testing.expect(!p.mask.isSet(0));
|
||||||
|
try testing.expectEqual(@as(usize, 0), p.mask.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: resetAll" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
const new_color = RGB{ .r = 255, .g = 0, .b = 0 };
|
||||||
|
|
||||||
|
p.set(0, new_color);
|
||||||
|
p.set(5, new_color);
|
||||||
|
p.set(10, new_color);
|
||||||
|
try testing.expectEqual(@as(usize, 3), p.mask.count());
|
||||||
|
|
||||||
|
p.resetAll();
|
||||||
|
try testing.expectEqual(default, p.current);
|
||||||
|
try testing.expectEqual(default, p.original);
|
||||||
|
try testing.expectEqual(@as(usize, 0), p.mask.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: changeDefault with no changes" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
var new_palette = default;
|
||||||
|
new_palette[0] = RGB{ .r = 100, .g = 100, .b = 100 };
|
||||||
|
|
||||||
|
p.changeDefault(new_palette);
|
||||||
|
try testing.expectEqual(new_palette, p.original);
|
||||||
|
try testing.expectEqual(new_palette, p.current);
|
||||||
|
try testing.expectEqual(@as(usize, 0), p.mask.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: changeDefault preserves changes" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
const custom_color = RGB{ .r = 255, .g = 0, .b = 0 };
|
||||||
|
|
||||||
|
p.set(5, custom_color);
|
||||||
|
try testing.expect(p.mask.isSet(5));
|
||||||
|
|
||||||
|
var new_palette = default;
|
||||||
|
new_palette[0] = RGB{ .r = 100, .g = 100, .b = 100 };
|
||||||
|
new_palette[5] = RGB{ .r = 50, .g = 50, .b = 50 };
|
||||||
|
|
||||||
|
p.changeDefault(new_palette);
|
||||||
|
|
||||||
|
try testing.expectEqual(new_palette, p.original);
|
||||||
|
try testing.expectEqual(new_palette[0], p.current[0]);
|
||||||
|
try testing.expectEqual(custom_color, p.current[5]);
|
||||||
|
try testing.expect(p.mask.isSet(5));
|
||||||
|
try testing.expectEqual(@as(usize, 1), p.mask.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "DynamicPalette: changeDefault with multiple changes" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: DynamicPalette = .init(default);
|
||||||
|
const red = RGB{ .r = 255, .g = 0, .b = 0 };
|
||||||
|
const green = RGB{ .r = 0, .g = 255, .b = 0 };
|
||||||
|
const blue = RGB{ .r = 0, .g = 0, .b = 255 };
|
||||||
|
|
||||||
|
p.set(1, red);
|
||||||
|
p.set(2, green);
|
||||||
|
p.set(3, blue);
|
||||||
|
|
||||||
|
var new_palette = default;
|
||||||
|
new_palette[0] = RGB{ .r = 50, .g = 50, .b = 50 };
|
||||||
|
new_palette[1] = RGB{ .r = 60, .g = 60, .b = 60 };
|
||||||
|
|
||||||
|
p.changeDefault(new_palette);
|
||||||
|
|
||||||
|
try testing.expectEqual(new_palette[0], p.current[0]);
|
||||||
|
try testing.expectEqual(red, p.current[1]);
|
||||||
|
try testing.expectEqual(green, p.current[2]);
|
||||||
|
try testing.expectEqual(blue, p.current[3]);
|
||||||
|
try testing.expectEqual(@as(usize, 3), p.mask.count());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ pub const TerminalFormatter = struct {
|
||||||
.plain => break :palette,
|
.plain => break :palette,
|
||||||
|
|
||||||
.vt => {
|
.vt => {
|
||||||
for (self.terminal.color_palette.colors, 0..) |rgb, i| {
|
for (self.terminal.colors.palette.current, 0..) |rgb, i| {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
"\x1b]4;{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}\x1b\\",
|
"\x1b]4;{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}\x1b\\",
|
||||||
.{ i, rgb.r, rgb.g, rgb.b },
|
.{ i, rgb.r, rgb.g, rgb.b },
|
||||||
|
|
@ -236,7 +236,7 @@ pub const TerminalFormatter = struct {
|
||||||
// For HTML, we emit CSS to setup our palette variables.
|
// For HTML, we emit CSS to setup our palette variables.
|
||||||
.html => {
|
.html => {
|
||||||
try writer.writeAll("<style>:root{");
|
try writer.writeAll("<style>:root{");
|
||||||
for (self.terminal.color_palette.colors, 0..) |rgb, i| {
|
for (self.terminal.colors.palette.current, 0..) |rgb, i| {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
"--vt-palette-{d}: #{x:0>2}{x:0>2}{x:0>2};",
|
"--vt-palette-{d}: #{x:0>2}{x:0>2}{x:0>2};",
|
||||||
.{ i, rgb.r, rgb.g, rgb.b },
|
.{ i, rgb.r, rgb.g, rgb.b },
|
||||||
|
|
@ -3839,9 +3839,9 @@ test "TerminalFormatter vt with palette" {
|
||||||
try s2.nextSlice(output);
|
try s2.nextSlice(output);
|
||||||
|
|
||||||
// Verify the palettes match
|
// Verify the palettes match
|
||||||
try testing.expectEqual(t.color_palette.colors[0], t2.color_palette.colors[0]);
|
try testing.expectEqual(t.colors.palette.current[0], t2.colors.palette.current[0]);
|
||||||
try testing.expectEqual(t.color_palette.colors[1], t2.color_palette.colors[1]);
|
try testing.expectEqual(t.colors.palette.current[1], t2.colors.palette.current[1]);
|
||||||
try testing.expectEqual(t.color_palette.colors[255], t2.color_palette.colors[255]);
|
try testing.expectEqual(t.colors.palette.current[255], t2.colors.palette.current[255]);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "TerminalFormatter with selection" {
|
test "TerminalFormatter with selection" {
|
||||||
|
|
@ -4972,7 +4972,7 @@ test "Page VT with palette option emits RGB" {
|
||||||
{
|
{
|
||||||
builder.clearRetainingCapacity();
|
builder.clearRetainingCapacity();
|
||||||
var opts: Options = .vt;
|
var opts: Options = .vt;
|
||||||
opts.palette = &t.color_palette.colors;
|
opts.palette = &t.colors.palette.current;
|
||||||
var formatter: PageFormatter = .init(page, opts);
|
var formatter: PageFormatter = .init(page, opts);
|
||||||
try formatter.format(&builder.writer);
|
try formatter.format(&builder.writer);
|
||||||
const output = builder.writer.buffered();
|
const output = builder.writer.buffered();
|
||||||
|
|
@ -5021,7 +5021,7 @@ test "Page html with palette option emits RGB" {
|
||||||
{
|
{
|
||||||
builder.clearRetainingCapacity();
|
builder.clearRetainingCapacity();
|
||||||
var opts: Options = .{ .emit = .html };
|
var opts: Options = .{ .emit = .html };
|
||||||
opts.palette = &t.color_palette.colors;
|
opts.palette = &t.colors.palette.current;
|
||||||
var formatter: PageFormatter = .init(page, opts);
|
var formatter: PageFormatter = .init(page, opts);
|
||||||
try formatter.format(&builder.writer);
|
try formatter.format(&builder.writer);
|
||||||
const output = builder.writer.buffered();
|
const output = builder.writer.buffered();
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ pub const Handler = struct {
|
||||||
.end_of_command => self.terminal.screen.cursor.page_row.semantic_prompt = .input,
|
.end_of_command => self.terminal.screen.cursor.page_row.semantic_prompt = .input,
|
||||||
.mouse_shape => self.terminal.mouse_shape = value,
|
.mouse_shape => self.terminal.mouse_shape = value,
|
||||||
.color_operation => try self.colorOperation(value.op, &value.requests),
|
.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,
|
// No supported DCS commands have any terminal-modifying effects,
|
||||||
// but they may in the future. For now we just ignore it.
|
// but they may in the future. For now we just ignore it.
|
||||||
|
|
@ -186,7 +187,6 @@ pub const Handler = struct {
|
||||||
.device_attributes,
|
.device_attributes,
|
||||||
.device_status,
|
.device_status,
|
||||||
.kitty_keyboard_query,
|
.kitty_keyboard_query,
|
||||||
.kitty_color_report,
|
|
||||||
.window_title,
|
.window_title,
|
||||||
.report_pwd,
|
.report_pwd,
|
||||||
.show_desktop_notification,
|
.show_desktop_notification,
|
||||||
|
|
@ -306,31 +306,53 @@ pub const Handler = struct {
|
||||||
.set => |set| {
|
.set => |set| {
|
||||||
switch (set.target) {
|
switch (set.target) {
|
||||||
.palette => |i| {
|
.palette => |i| {
|
||||||
self.terminal.color_palette.colors[i] = set.color;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.mask.set(i);
|
self.terminal.colors.palette.set(i, set.color);
|
||||||
},
|
},
|
||||||
.dynamic,
|
.dynamic => |dynamic| switch (dynamic) {
|
||||||
.special,
|
.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) {
|
.reset => |target| switch (target) {
|
||||||
.palette => |i| {
|
.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];
|
self.terminal.colors.palette.reset(i);
|
||||||
mask.unset(i);
|
|
||||||
},
|
},
|
||||||
.dynamic,
|
.dynamic => |dynamic| switch (dynamic) {
|
||||||
.special,
|
.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 => {
|
.reset_palette => {
|
||||||
const mask = &self.terminal.color_palette.mask;
|
const mask = &self.terminal.colors.palette.mask;
|
||||||
var mask_iterator = mask.iterator(.{});
|
var mask_it = mask.iterator(.{});
|
||||||
while (mask_iterator.next()) |i| {
|
while (mask_it.next()) |i| {
|
||||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
self.terminal.flags.dirty.palette = true;
|
||||||
|
self.terminal.colors.palette.reset(@intCast(i));
|
||||||
}
|
}
|
||||||
mask.* = .initEmpty();
|
mask.* = .initEmpty();
|
||||||
},
|
},
|
||||||
|
|
@ -341,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" {
|
test "basic print" {
|
||||||
|
|
@ -599,19 +656,19 @@ test "OSC 4 set and reset palette" {
|
||||||
defer s.deinit();
|
defer s.deinit();
|
||||||
|
|
||||||
// Save default color
|
// Save default color
|
||||||
const default_color_0 = t.default_palette[0];
|
const default_color_0 = t.colors.palette.original[0];
|
||||||
|
|
||||||
// Set color 0 to red
|
// Set color 0 to red
|
||||||
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
|
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
|
||||||
try testing.expectEqual(@as(u8, 0xff), t.color_palette.colors[0].r);
|
try testing.expectEqual(@as(u8, 0xff), t.colors.palette.current[0].r);
|
||||||
try testing.expectEqual(@as(u8, 0x00), t.color_palette.colors[0].g);
|
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].g);
|
||||||
try testing.expectEqual(@as(u8, 0x00), t.color_palette.colors[0].b);
|
try testing.expectEqual(@as(u8, 0x00), t.colors.palette.current[0].b);
|
||||||
try testing.expect(t.color_palette.mask.isSet(0));
|
try testing.expect(t.colors.palette.mask.isSet(0));
|
||||||
|
|
||||||
// Reset color 0
|
// Reset color 0
|
||||||
try s.nextSlice("\x1b]104;0\x1b\\");
|
try s.nextSlice("\x1b]104;0\x1b\\");
|
||||||
try testing.expectEqual(default_color_0, t.color_palette.colors[0]);
|
try testing.expectEqual(default_color_0, t.colors.palette.current[0]);
|
||||||
try testing.expect(!t.color_palette.mask.isSet(0));
|
try testing.expect(!t.colors.palette.mask.isSet(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "OSC 104 reset all palette colors" {
|
test "OSC 104 reset all palette colors" {
|
||||||
|
|
@ -625,16 +682,195 @@ test "OSC 104 reset all palette colors" {
|
||||||
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
|
try s.nextSlice("\x1b]4;0;rgb:ff/00/00\x1b\\");
|
||||||
try s.nextSlice("\x1b]4;1;rgb:00/ff/00\x1b\\");
|
try s.nextSlice("\x1b]4;1;rgb:00/ff/00\x1b\\");
|
||||||
try s.nextSlice("\x1b]4;2;rgb:00/00/ff\x1b\\");
|
try s.nextSlice("\x1b]4;2;rgb:00/00/ff\x1b\\");
|
||||||
try testing.expect(t.color_palette.mask.isSet(0));
|
try testing.expect(t.colors.palette.mask.isSet(0));
|
||||||
try testing.expect(t.color_palette.mask.isSet(1));
|
try testing.expect(t.colors.palette.mask.isSet(1));
|
||||||
try testing.expect(t.color_palette.mask.isSet(2));
|
try testing.expect(t.colors.palette.mask.isSet(2));
|
||||||
|
|
||||||
// Reset all palette colors
|
// Reset all palette colors
|
||||||
try s.nextSlice("\x1b]104\x1b\\");
|
try s.nextSlice("\x1b]104\x1b\\");
|
||||||
try testing.expectEqual(t.default_palette[0], t.color_palette.colors[0]);
|
try testing.expectEqual(t.colors.palette.original[0], t.colors.palette.current[0]);
|
||||||
try testing.expectEqual(t.default_palette[1], t.color_palette.colors[1]);
|
try testing.expectEqual(t.colors.palette.original[1], t.colors.palette.current[1]);
|
||||||
try testing.expectEqual(t.default_palette[2], t.color_palette.colors[2]);
|
try testing.expectEqual(t.colors.palette.original[2], t.colors.palette.current[2]);
|
||||||
try testing.expect(!t.color_palette.mask.isSet(0));
|
try testing.expect(!t.colors.palette.mask.isSet(0));
|
||||||
try testing.expect(!t.color_palette.mask.isSet(1));
|
try testing.expect(!t.colors.palette.mask.isSet(1));
|
||||||
try testing.expect(!t.color_palette.mask.isSet(2));
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -231,11 +231,19 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
|
||||||
.rows = grid_size.rows,
|
.rows = grid_size.rows,
|
||||||
.max_scrollback = opts.full_config.@"scrollback-limit",
|
.max_scrollback = opts.full_config.@"scrollback-limit",
|
||||||
.default_modes = default_modes,
|
.default_modes = default_modes,
|
||||||
|
.colors = .{
|
||||||
|
.background = .init(opts.config.background.toTerminalRGB()),
|
||||||
|
.foreground = .init(opts.config.foreground.toTerminalRGB()),
|
||||||
|
.cursor = cursor: {
|
||||||
|
const color = opts.config.cursor_color orelse break :cursor .unset;
|
||||||
|
const rgb = color.toTerminalRGB() orelse break :cursor .unset;
|
||||||
|
break :cursor .init(rgb);
|
||||||
|
},
|
||||||
|
.palette = .init(opts.config.palette),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
term.default_palette = opts.config.palette;
|
|
||||||
term.color_palette.colors = opts.config.palette;
|
|
||||||
|
|
||||||
// Set the image size limits
|
// Set the image size limits
|
||||||
try term.screen.kitty_images.setLimit(
|
try term.screen.kitty_images.setLimit(
|
||||||
|
|
@ -262,39 +270,20 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
|
||||||
|
|
||||||
// Create our stream handler. This points to memory in self so it
|
// Create our stream handler. This points to memory in self so it
|
||||||
// isn't safe to use until self.* is set.
|
// isn't safe to use until self.* is set.
|
||||||
const handler: StreamHandler = handler: {
|
const handler: StreamHandler = .{
|
||||||
const default_cursor_color: ?terminalpkg.color.RGB = color: {
|
.alloc = alloc,
|
||||||
if (opts.config.cursor_color) |color| switch (color) {
|
.termio_mailbox = &self.mailbox,
|
||||||
.color => break :color color.color.toTerminalRGB(),
|
.surface_mailbox = opts.surface_mailbox,
|
||||||
.@"cell-foreground",
|
.renderer_state = opts.renderer_state,
|
||||||
.@"cell-background",
|
.renderer_wakeup = opts.renderer_wakeup,
|
||||||
=> {},
|
.renderer_mailbox = opts.renderer_mailbox,
|
||||||
};
|
.size = &self.size,
|
||||||
|
.terminal = &self.terminal,
|
||||||
break :color null;
|
.osc_color_report_format = opts.config.osc_color_report_format,
|
||||||
};
|
.clipboard_write = opts.config.clipboard_write,
|
||||||
|
.enquiry_response = opts.config.enquiry_response,
|
||||||
break :handler .{
|
.default_cursor_style = opts.config.cursor_style,
|
||||||
.alloc = alloc,
|
.default_cursor_blink = opts.config.cursor_blink,
|
||||||
.termio_mailbox = &self.mailbox,
|
|
||||||
.surface_mailbox = opts.surface_mailbox,
|
|
||||||
.renderer_state = opts.renderer_state,
|
|
||||||
.renderer_wakeup = opts.renderer_wakeup,
|
|
||||||
.renderer_mailbox = opts.renderer_mailbox,
|
|
||||||
.size = &self.size,
|
|
||||||
.terminal = &self.terminal,
|
|
||||||
.osc_color_report_format = opts.config.osc_color_report_format,
|
|
||||||
.clipboard_write = opts.config.clipboard_write,
|
|
||||||
.enquiry_response = opts.config.enquiry_response,
|
|
||||||
.default_foreground_color = opts.config.foreground.toTerminalRGB(),
|
|
||||||
.default_background_color = opts.config.background.toTerminalRGB(),
|
|
||||||
.default_cursor_style = opts.config.cursor_style,
|
|
||||||
.default_cursor_blink = opts.config.cursor_blink,
|
|
||||||
.default_cursor_color = default_cursor_color,
|
|
||||||
.cursor_color = null,
|
|
||||||
.foreground_color = null,
|
|
||||||
.background_color = null,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const thread_enter_state = try ThreadEnterState.create(
|
const thread_enter_state = try ThreadEnterState.create(
|
||||||
|
|
@ -449,18 +438,17 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi
|
||||||
// - command, working-directory: we never restart the underlying
|
// - command, working-directory: we never restart the underlying
|
||||||
// process so we don't care or need to know about these.
|
// process so we don't care or need to know about these.
|
||||||
|
|
||||||
// Update the default palette. Note this will only apply to new colors drawn
|
// Update the default palette.
|
||||||
// since we decode all palette colors to RGB on usage.
|
self.terminal.colors.palette.changeDefault(config.palette);
|
||||||
self.terminal.default_palette = config.palette;
|
self.terminal.flags.dirty.palette = true;
|
||||||
|
|
||||||
// Update the active palette, except for any colors that were modified with
|
// Update all our other colors
|
||||||
// OSC 4
|
self.terminal.colors.background.default = config.background.toTerminalRGB();
|
||||||
for (0..config.palette.len) |i| {
|
self.terminal.colors.foreground.default = config.foreground.toTerminalRGB();
|
||||||
if (!self.terminal.color_palette.mask.isSet(i)) {
|
self.terminal.colors.cursor.default = cursor: {
|
||||||
self.terminal.color_palette.colors[i] = config.palette[i];
|
const color = config.cursor_color orelse break :cursor null;
|
||||||
self.terminal.flags.dirty.palette = true;
|
break :cursor color.toTerminalRGB() orelse break :cursor null;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Set the image size limits
|
// Set the image size limits
|
||||||
try self.terminal.screen.kitty_images.setLimit(
|
try self.terminal.screen.kitty_images.setLimit(
|
||||||
|
|
|
||||||
|
|
@ -45,22 +45,6 @@ pub const StreamHandler = struct {
|
||||||
default_cursor: bool = true,
|
default_cursor: bool = true,
|
||||||
default_cursor_style: terminal.CursorStyle,
|
default_cursor_style: terminal.CursorStyle,
|
||||||
default_cursor_blink: ?bool,
|
default_cursor_blink: ?bool,
|
||||||
default_cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// Actual cursor color. This can be changed with OSC 12. If unset, falls
|
|
||||||
/// back to the default cursor color.
|
|
||||||
cursor_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The default foreground and background color are those set by the user's
|
|
||||||
/// config file.
|
|
||||||
default_foreground_color: terminal.color.RGB,
|
|
||||||
default_background_color: terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The foreground and background color as set by an OSC 10 or OSC 11
|
|
||||||
/// sequence. If unset then the respective color falls back to the default
|
|
||||||
/// value.
|
|
||||||
foreground_color: ?terminal.color.RGB,
|
|
||||||
background_color: ?terminal.color.RGB,
|
|
||||||
|
|
||||||
/// The response to use for ENQ requests. The memory is owned by
|
/// The response to use for ENQ requests. The memory is owned by
|
||||||
/// whoever owns StreamHandler.
|
/// whoever owns StreamHandler.
|
||||||
|
|
@ -114,20 +98,8 @@ pub const StreamHandler = struct {
|
||||||
self.osc_color_report_format = config.osc_color_report_format;
|
self.osc_color_report_format = config.osc_color_report_format;
|
||||||
self.clipboard_write = config.clipboard_write;
|
self.clipboard_write = config.clipboard_write;
|
||||||
self.enquiry_response = config.enquiry_response;
|
self.enquiry_response = config.enquiry_response;
|
||||||
self.default_foreground_color = config.foreground.toTerminalRGB();
|
|
||||||
self.default_background_color = config.background.toTerminalRGB();
|
|
||||||
self.default_cursor_style = config.cursor_style;
|
self.default_cursor_style = config.cursor_style;
|
||||||
self.default_cursor_blink = config.cursor_blink;
|
self.default_cursor_blink = config.cursor_blink;
|
||||||
self.default_cursor_color = color: {
|
|
||||||
if (config.cursor_color) |color| switch (color) {
|
|
||||||
.color => break :color color.color.toTerminalRGB(),
|
|
||||||
.@"cell-foreground",
|
|
||||||
.@"cell-background",
|
|
||||||
=> {},
|
|
||||||
};
|
|
||||||
|
|
||||||
break :color null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// If our cursor is the default, then we update it immediately.
|
// If our cursor is the default, then we update it immediately.
|
||||||
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
|
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
|
||||||
|
|
@ -224,7 +196,6 @@ pub const StreamHandler = struct {
|
||||||
.erase_display_above => self.terminal.eraseDisplay(.above, value),
|
.erase_display_above => self.terminal.eraseDisplay(.above, value),
|
||||||
.erase_display_complete => {
|
.erase_display_complete => {
|
||||||
try self.terminal.scrollViewport(.{ .bottom = {} });
|
try self.terminal.scrollViewport(.{ .bottom = {} });
|
||||||
try self.queueRender();
|
|
||||||
self.terminal.eraseDisplay(.complete, value);
|
self.terminal.eraseDisplay(.complete, value);
|
||||||
},
|
},
|
||||||
.erase_display_scrollback => self.terminal.eraseDisplay(.scrollback, value),
|
.erase_display_scrollback => self.terminal.eraseDisplay(.scrollback, value),
|
||||||
|
|
@ -597,10 +568,7 @@ pub const StreamHandler = struct {
|
||||||
.autorepeat => {},
|
.autorepeat => {},
|
||||||
|
|
||||||
// Schedule a render since we changed colors
|
// Schedule a render since we changed colors
|
||||||
.reverse_colors => {
|
.reverse_colors => self.terminal.flags.dirty.reverse_colors = true,
|
||||||
self.terminal.flags.dirty.reverse_colors = true;
|
|
||||||
try self.queueRender();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Origin resets cursor pos. This is called whether or not
|
// Origin resets cursor pos. This is called whether or not
|
||||||
// we're enabling or disabling origin mode and whether or
|
// we're enabling or disabling origin mode and whether or
|
||||||
|
|
@ -616,17 +584,14 @@ pub const StreamHandler = struct {
|
||||||
|
|
||||||
.alt_screen_legacy => {
|
.alt_screen_legacy => {
|
||||||
self.terminal.switchScreenMode(.@"47", enabled);
|
self.terminal.switchScreenMode(.@"47", enabled);
|
||||||
try self.queueRender();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.alt_screen => {
|
.alt_screen => {
|
||||||
self.terminal.switchScreenMode(.@"1047", enabled);
|
self.terminal.switchScreenMode(.@"1047", enabled);
|
||||||
try self.queueRender();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.alt_screen_save_cursor_clear_enter => {
|
.alt_screen_save_cursor_clear_enter => {
|
||||||
self.terminal.switchScreenMode(.@"1049", enabled);
|
self.terminal.switchScreenMode(.@"1049", enabled);
|
||||||
try self.queueRender();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mode 1048 is xterm's conditional save cursor depending
|
// Mode 1048 is xterm's conditional save cursor depending
|
||||||
|
|
@ -662,7 +627,6 @@ pub const StreamHandler = struct {
|
||||||
// forever.
|
// forever.
|
||||||
.synchronized_output => {
|
.synchronized_output => {
|
||||||
if (enabled) self.messageWriter(.{ .start_synchronized_output = {} });
|
if (enabled) self.messageWriter(.{ .start_synchronized_output = {} });
|
||||||
try self.queueRender();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.linefeed => {
|
.linefeed => {
|
||||||
|
|
@ -1133,28 +1097,12 @@ pub const StreamHandler = struct {
|
||||||
switch (set.target) {
|
switch (set.target) {
|
||||||
.palette => |i| {
|
.palette => |i| {
|
||||||
self.terminal.flags.dirty.palette = true;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.colors[i] = set.color;
|
self.terminal.colors.palette.set(i, set.color);
|
||||||
self.terminal.color_palette.mask.set(i);
|
|
||||||
},
|
},
|
||||||
.dynamic => |dynamic| switch (dynamic) {
|
.dynamic => |dynamic| switch (dynamic) {
|
||||||
.foreground => {
|
.foreground => self.terminal.colors.foreground.set(set.color),
|
||||||
self.foreground_color = set.color;
|
.background => self.terminal.colors.background.set(set.color),
|
||||||
self.rendererMessageWriter(.{
|
.cursor => self.terminal.colors.cursor.set(set.color),
|
||||||
.foreground_color = set.color,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.background => {
|
|
||||||
self.background_color = set.color;
|
|
||||||
self.rendererMessageWriter(.{
|
|
||||||
.background_color = set.color,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.cursor => {
|
|
||||||
self.cursor_color = set.color;
|
|
||||||
self.rendererMessageWriter(.{
|
|
||||||
.cursor_color = set.color,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.pointer_foreground,
|
.pointer_foreground,
|
||||||
.pointer_background,
|
.pointer_background,
|
||||||
.tektronix_foreground,
|
.tektronix_foreground,
|
||||||
|
|
@ -1178,52 +1126,44 @@ pub const StreamHandler = struct {
|
||||||
|
|
||||||
.reset => |target| switch (target) {
|
.reset => |target| switch (target) {
|
||||||
.palette => |i| {
|
.palette => |i| {
|
||||||
const mask = &self.terminal.color_palette.mask;
|
|
||||||
self.terminal.flags.dirty.palette = true;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
self.terminal.colors.palette.reset(i);
|
||||||
mask.unset(i);
|
|
||||||
|
|
||||||
self.surfaceMessageWriter(.{
|
self.surfaceMessageWriter(.{
|
||||||
.color_change = .{
|
.color_change = .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.color = self.terminal.color_palette.colors[i],
|
.color = self.terminal.colors.palette.current[i],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.dynamic => |dynamic| switch (dynamic) {
|
.dynamic => |dynamic| switch (dynamic) {
|
||||||
.foreground => {
|
.foreground => {
|
||||||
self.foreground_color = null;
|
self.terminal.colors.foreground.reset();
|
||||||
self.rendererMessageWriter(.{
|
|
||||||
.foreground_color = self.foreground_color,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.surfaceMessageWriter(.{ .color_change = .{
|
if (self.terminal.colors.foreground.default) |c| {
|
||||||
.target = target,
|
|
||||||
.color = self.default_foreground_color,
|
|
||||||
} });
|
|
||||||
},
|
|
||||||
.background => {
|
|
||||||
self.background_color = null;
|
|
||||||
self.rendererMessageWriter(.{
|
|
||||||
.background_color = self.background_color,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.surfaceMessageWriter(.{ .color_change = .{
|
|
||||||
.target = target,
|
|
||||||
.color = self.default_background_color,
|
|
||||||
} });
|
|
||||||
},
|
|
||||||
.cursor => {
|
|
||||||
self.cursor_color = null;
|
|
||||||
|
|
||||||
self.rendererMessageWriter(.{
|
|
||||||
.cursor_color = self.cursor_color,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self.default_cursor_color) |color| {
|
|
||||||
self.surfaceMessageWriter(.{ .color_change = .{
|
self.surfaceMessageWriter(.{ .color_change = .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.color = color,
|
.color = c,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.background => {
|
||||||
|
self.terminal.colors.background.reset();
|
||||||
|
|
||||||
|
if (self.terminal.colors.background.default) |c| {
|
||||||
|
self.surfaceMessageWriter(.{ .color_change = .{
|
||||||
|
.target = target,
|
||||||
|
.color = c,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.cursor => {
|
||||||
|
self.terminal.colors.cursor.reset();
|
||||||
|
|
||||||
|
if (self.terminal.colors.cursor.default) |c| {
|
||||||
|
self.surfaceMessageWriter(.{ .color_change = .{
|
||||||
|
.target = target,
|
||||||
|
.color = c,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1242,15 +1182,15 @@ pub const StreamHandler = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
.reset_palette => {
|
.reset_palette => {
|
||||||
const mask = &self.terminal.color_palette.mask;
|
const mask = &self.terminal.colors.palette.mask;
|
||||||
var mask_iterator = mask.iterator(.{});
|
var mask_it = mask.iterator(.{});
|
||||||
while (mask_iterator.next()) |i| {
|
while (mask_it.next()) |i| {
|
||||||
self.terminal.flags.dirty.palette = true;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
self.terminal.colors.palette.reset(@intCast(i));
|
||||||
self.surfaceMessageWriter(.{
|
self.surfaceMessageWriter(.{
|
||||||
.color_change = .{
|
.color_change = .{
|
||||||
.target = .{ .palette = @intCast(i) },
|
.target = .{ .palette = @intCast(i) },
|
||||||
.color = self.terminal.color_palette.colors[i],
|
.color = self.terminal.colors.palette.current[i],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1266,14 +1206,12 @@ pub const StreamHandler = struct {
|
||||||
if (self.osc_color_report_format == .none) break :report;
|
if (self.osc_color_report_format == .none) break :report;
|
||||||
|
|
||||||
const color = switch (kind) {
|
const color = switch (kind) {
|
||||||
.palette => |i| self.terminal.color_palette.colors[i],
|
.palette => |i| self.terminal.colors.palette.current[i],
|
||||||
.dynamic => |dynamic| switch (dynamic) {
|
.dynamic => |dynamic| switch (dynamic) {
|
||||||
.foreground => self.foreground_color orelse self.default_foreground_color,
|
.foreground => self.terminal.colors.foreground.get().?,
|
||||||
.background => self.background_color orelse self.default_background_color,
|
.background => self.terminal.colors.background.get().?,
|
||||||
.cursor => self.cursor_color orelse
|
.cursor => self.terminal.colors.cursor.get() orelse
|
||||||
self.default_cursor_color orelse
|
self.terminal.colors.foreground.get().?,
|
||||||
self.foreground_color orelse
|
|
||||||
self.default_foreground_color,
|
|
||||||
.pointer_foreground,
|
.pointer_foreground,
|
||||||
.pointer_background,
|
.pointer_background,
|
||||||
.tektronix_foreground,
|
.tektronix_foreground,
|
||||||
|
|
@ -1399,11 +1337,11 @@ pub const StreamHandler = struct {
|
||||||
if (stream.written().len == 0) try writer.writeAll("\x1b]21");
|
if (stream.written().len == 0) try writer.writeAll("\x1b]21");
|
||||||
|
|
||||||
const color: terminal.color.RGB = switch (key) {
|
const color: terminal.color.RGB = switch (key) {
|
||||||
.palette => |palette| self.terminal.color_palette.colors[palette],
|
.palette => |palette| self.terminal.colors.palette.current[palette],
|
||||||
.special => |special| switch (special) {
|
.special => |special| switch (special) {
|
||||||
.foreground => self.foreground_color orelse self.default_foreground_color,
|
.foreground => self.terminal.colors.foreground.get(),
|
||||||
.background => self.background_color orelse self.default_background_color,
|
.background => self.terminal.colors.background.get(),
|
||||||
.cursor => self.cursor_color orelse self.default_cursor_color,
|
.cursor => self.terminal.colors.cursor.get(),
|
||||||
else => {
|
else => {
|
||||||
log.warn("ignoring unsupported kitty color protocol key: {f}", .{key});
|
log.warn("ignoring unsupported kitty color protocol key: {f}", .{key});
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1422,71 +1360,39 @@ pub const StreamHandler = struct {
|
||||||
.set => |v| switch (v.key) {
|
.set => |v| switch (v.key) {
|
||||||
.palette => |palette| {
|
.palette => |palette| {
|
||||||
self.terminal.flags.dirty.palette = true;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.colors[palette] = v.color;
|
self.terminal.colors.palette.set(palette, v.color);
|
||||||
self.terminal.color_palette.mask.unset(palette);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.special => |special| {
|
.special => |special| switch (special) {
|
||||||
const msg: renderer.Message = switch (special) {
|
.foreground => self.terminal.colors.foreground.set(v.color),
|
||||||
.foreground => msg: {
|
.background => self.terminal.colors.background.set(v.color),
|
||||||
self.foreground_color = v.color;
|
.cursor => self.terminal.colors.cursor.set(v.color),
|
||||||
break :msg .{ .foreground_color = v.color };
|
else => {
|
||||||
},
|
log.warn(
|
||||||
.background => msg: {
|
"ignoring unsupported kitty color protocol key: {f}",
|
||||||
self.background_color = v.color;
|
.{v.key},
|
||||||
break :msg .{ .background_color = v.color };
|
);
|
||||||
},
|
continue;
|
||||||
.cursor => msg: {
|
},
|
||||||
self.cursor_color = v.color;
|
|
||||||
break :msg .{ .cursor_color = v.color };
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
log.warn(
|
|
||||||
"ignoring unsupported kitty color protocol key: {f}",
|
|
||||||
.{v.key},
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// See messageWriter which has similar logic and
|
|
||||||
// explains why we may have to do this.
|
|
||||||
self.rendererMessageWriter(msg);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.reset => |key| switch (key) {
|
.reset => |key| switch (key) {
|
||||||
.palette => |palette| {
|
.palette => |palette| {
|
||||||
self.terminal.flags.dirty.palette = true;
|
self.terminal.flags.dirty.palette = true;
|
||||||
self.terminal.color_palette.colors[palette] = self.terminal.default_palette[palette];
|
self.terminal.colors.palette.reset(palette);
|
||||||
self.terminal.color_palette.mask.unset(palette);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.special => |special| {
|
.special => |special| switch (special) {
|
||||||
const msg: renderer.Message = switch (special) {
|
.foreground => self.terminal.colors.foreground.reset(),
|
||||||
.foreground => msg: {
|
.background => self.terminal.colors.background.reset(),
|
||||||
self.foreground_color = null;
|
.cursor => self.terminal.colors.cursor.reset(),
|
||||||
break :msg .{ .foreground_color = self.foreground_color };
|
else => {
|
||||||
},
|
log.warn(
|
||||||
.background => msg: {
|
"ignoring unsupported kitty color protocol key: {f}",
|
||||||
self.background_color = null;
|
.{key},
|
||||||
break :msg .{ .background_color = self.background_color };
|
);
|
||||||
},
|
continue;
|
||||||
.cursor => msg: {
|
},
|
||||||
self.cursor_color = null;
|
|
||||||
break :msg .{ .cursor_color = self.cursor_color };
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
log.warn(
|
|
||||||
"ignoring unsupported kitty color protocol key: {f}",
|
|
||||||
.{key},
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// See messageWriter which has similar logic and
|
|
||||||
// explains why we may have to do this.
|
|
||||||
self.rendererMessageWriter(msg);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue