From 77343bb06e65c7d7015e367ef4461f2bece80c4b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 30 Oct 2025 09:26:58 -0700 Subject: [PATCH] terminal: move color state fully into the terminal for fg/bg/cursor --- src/config/Config.zig | 7 ++ src/terminal/color.zig | 4 +- src/termio/Termio.zig | 69 +++++++++---------- src/termio/stream_handler.zig | 126 ++++++++++++++-------------------- 4 files changed, 92 insertions(+), 114 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index a9aaf8f86..78ea19aef 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4960,6 +4960,13 @@ pub const TerminalColor = union(enum) { 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 pub fn formatEntry(self: TerminalColor, formatter: formatterpkg.EntryFormatter) !void { switch (self) { diff --git a/src/terminal/color.zig b/src/terminal/color.zig index e4b71fe63..4492d65ae 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -130,11 +130,11 @@ pub const DynamicRGB = struct { } pub fn set(self: *DynamicRGB, color: RGB) void { - self.current = color; + self.override = color; } pub fn reset(self: *DynamicRGB) void { - self.current = self.default; + self.override = self.default; } }; diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index f5e0af221..1e181a137 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -231,10 +231,19 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { .rows = grid_size.rows, .max_scrollback = opts.full_config.@"scrollback-limit", .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); - term.colors.palette.changeDefault(opts.config.palette); // Set the image size limits try term.screen.kitty_images.setLimit( @@ -261,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 // isn't safe to use until self.* is set. - const handler: StreamHandler = handler: { - const default_cursor_color: ?terminalpkg.color.RGB = color: { - if (opts.config.cursor_color) |color| switch (color) { - .color => break :color color.color.toTerminalRGB(), - .@"cell-foreground", - .@"cell-background", - => {}, - }; - - break :color null; - }; - - break :handler .{ - .alloc = alloc, - .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 handler: StreamHandler = .{ + .alloc = alloc, + .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_cursor_style = opts.config.cursor_style, + .default_cursor_blink = opts.config.cursor_blink, }; const thread_enter_state = try ThreadEnterState.create( @@ -448,11 +438,18 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi // - command, working-directory: we never restart the underlying // 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 - // since we decode all palette colors to RGB on usage. + // Update the default palette. self.terminal.colors.palette.changeDefault(config.palette); self.terminal.flags.dirty.palette = true; + // Update all our other colors + self.terminal.colors.background.default = config.background.toTerminalRGB(); + self.terminal.colors.foreground.default = config.foreground.toTerminalRGB(); + self.terminal.colors.cursor.default = cursor: { + const color = config.cursor_color orelse break :cursor null; + break :cursor color.toTerminalRGB() orelse break :cursor null; + }; + // Set the image size limits try self.terminal.screen.kitty_images.setLimit( self.alloc, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 8f3f845d6..551145cfb 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -45,22 +45,6 @@ pub const StreamHandler = struct { default_cursor: bool = true, default_cursor_style: terminal.CursorStyle, 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 /// whoever owns StreamHandler. @@ -114,20 +98,8 @@ pub const StreamHandler = struct { self.osc_color_report_format = config.osc_color_report_format; self.clipboard_write = config.clipboard_write; 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_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 (self.default_cursor) self.setCursorStyle(.default) catch |err| { @@ -1137,19 +1109,19 @@ pub const StreamHandler = struct { }, .dynamic => |dynamic| switch (dynamic) { .foreground => { - self.foreground_color = set.color; + self.terminal.colors.foreground.set(set.color); self.rendererMessageWriter(.{ .foreground_color = set.color, }); }, .background => { - self.background_color = set.color; + self.terminal.colors.background.set(set.color); self.rendererMessageWriter(.{ .background_color = set.color, }); }, .cursor => { - self.cursor_color = set.color; + self.terminal.colors.cursor.set(set.color); self.rendererMessageWriter(.{ .cursor_color = set.color, }); @@ -1189,38 +1161,42 @@ pub const StreamHandler = struct { }, .dynamic => |dynamic| switch (dynamic) { .foreground => { - self.foreground_color = null; + self.terminal.colors.foreground.reset(); self.rendererMessageWriter(.{ - .foreground_color = self.foreground_color, + .foreground_color = null, }); - self.surfaceMessageWriter(.{ .color_change = .{ - .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| { + if (self.terminal.colors.foreground.default) |c| { self.surfaceMessageWriter(.{ .color_change = .{ .target = target, - .color = color, + .color = c, + } }); + } + }, + .background => { + self.terminal.colors.background.reset(); + self.rendererMessageWriter(.{ + .background_color = null, + }); + + if (self.terminal.colors.background.default) |c| { + self.surfaceMessageWriter(.{ .color_change = .{ + .target = target, + .color = c, + } }); + } + }, + .cursor => { + self.terminal.colors.cursor.reset(); + + self.rendererMessageWriter(.{ + .cursor_color = null, + }); + + if (self.terminal.colors.cursor.default) |c| { + self.surfaceMessageWriter(.{ .color_change = .{ + .target = target, + .color = c, } }); } }, @@ -1265,12 +1241,10 @@ pub const StreamHandler = struct { const color = switch (kind) { .palette => |i| self.terminal.colors.palette.current[i], .dynamic => |dynamic| switch (dynamic) { - .foreground => self.foreground_color orelse self.default_foreground_color, - .background => self.background_color orelse self.default_background_color, - .cursor => self.cursor_color orelse - self.default_cursor_color orelse - self.foreground_color orelse - self.default_foreground_color, + .foreground => self.terminal.colors.foreground.get().?, + .background => self.terminal.colors.background.get().?, + .cursor => self.terminal.colors.cursor.get() orelse + self.terminal.colors.foreground.get().?, .pointer_foreground, .pointer_background, .tektronix_foreground, @@ -1398,9 +1372,9 @@ pub const StreamHandler = struct { const color: terminal.color.RGB = switch (key) { .palette => |palette| self.terminal.colors.palette.current[palette], .special => |special| switch (special) { - .foreground => self.foreground_color orelse self.default_foreground_color, - .background => self.background_color orelse self.default_background_color, - .cursor => self.cursor_color orelse self.default_cursor_color, + .foreground => self.terminal.colors.foreground.get(), + .background => self.terminal.colors.background.get(), + .cursor => self.terminal.colors.cursor.get(), else => { log.warn("ignoring unsupported kitty color protocol key: {f}", .{key}); continue; @@ -1425,15 +1399,15 @@ pub const StreamHandler = struct { .special => |special| { const msg: renderer.Message = switch (special) { .foreground => msg: { - self.foreground_color = v.color; + self.terminal.colors.foreground.set(v.color); break :msg .{ .foreground_color = v.color }; }, .background => msg: { - self.background_color = v.color; + self.terminal.colors.background.set(v.color); break :msg .{ .background_color = v.color }; }, .cursor => msg: { - self.cursor_color = v.color; + self.terminal.colors.cursor.set(v.color); break :msg .{ .cursor_color = v.color }; }, else => { @@ -1459,16 +1433,16 @@ pub const StreamHandler = struct { .special => |special| { const msg: renderer.Message = switch (special) { .foreground => msg: { - self.foreground_color = null; - break :msg .{ .foreground_color = self.foreground_color }; + self.terminal.colors.foreground.reset(); + break :msg .{ .foreground_color = null }; }, .background => msg: { - self.background_color = null; - break :msg .{ .background_color = self.background_color }; + self.terminal.colors.background.reset(); + break :msg .{ .background_color = null }; }, .cursor => msg: { - self.cursor_color = null; - break :msg .{ .cursor_color = self.cursor_color }; + self.terminal.colors.cursor.reset(); + break :msg .{ .cursor_color = null }; }, else => { log.warn(