From 465ac5b1b7ad895951f459cfd4de578b14e0e741 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2025 09:25:55 -0700 Subject: [PATCH] clean up some of the color usage, use exhaustive switches --- src/config/Config.zig | 48 ++++++++-------- src/renderer/generic.zig | 100 +++++++++++++++++++++------------- src/termio/Termio.zig | 19 ++++--- src/termio/stream_handler.zig | 17 +++--- 4 files changed, 107 insertions(+), 77 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 3cb808179..5d9093bba 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -601,8 +601,8 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"selection-foreground": ?DynamicColor = null, -@"selection-background": ?DynamicColor = null, +@"selection-foreground": ?TerminalColor = null, +@"selection-background": ?TerminalColor = null, /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as @@ -659,7 +659,7 @@ palette: Palette = .{}, /// * `cell-background` - Match the cell background color. /// (Available since version 1.2.0) /// -@"cursor-color": ?DynamicColor = null, +@"cursor-color": ?TerminalColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -712,7 +712,7 @@ palette: Palette = .{}, /// Since version 1.2.0, this can also be set to `cell-foreground` to match /// the cell foreground color, or `cell-background` to match the cell /// background color. -@"cursor-text": ?DynamicColor = null, +@"cursor-text": ?TerminalColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -4463,16 +4463,14 @@ pub const Color = struct { } }; -/// Represents the color values that can be set to a non-static value. -/// -/// Can either be a Color or one of the special values -/// "cell-foreground" or "cell-background". -pub const DynamicColor = union(enum) { +/// Represents color values that can also reference special color +/// values such as "cell-foreground" or "cell-background". +pub const TerminalColor = union(enum) { color: Color, @"cell-foreground", @"cell-background", - pub fn parseCLI(input_: ?[]const u8) !DynamicColor { + pub fn parseCLI(input_: ?[]const u8) !TerminalColor { const input = input_ orelse return error.ValueRequired; if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; @@ -4480,7 +4478,7 @@ pub const DynamicColor = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: DynamicColor, formatter: anytype) !void { + pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { switch (self) { .color => try self.color.formatEntry(formatter), @@ -4494,23 +4492,23 @@ pub const DynamicColor = union(enum) { const testing = std.testing; try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, - try DynamicColor.parseCLI("#4e2a84"), + TerminalColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try TerminalColor.parseCLI("#4e2a84"), ); try testing.expectEqual( - DynamicColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, - try DynamicColor.parseCLI("black"), + TerminalColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try TerminalColor.parseCLI("black"), ); try testing.expectEqual( - DynamicColor{.@"cell-foreground"}, - try DynamicColor.parseCLI("cell-foreground"), + TerminalColor{.@"cell-foreground"}, + try TerminalColor.parseCLI("cell-foreground"), ); try testing.expectEqual( - DynamicColor{.@"cell-background"}, - try DynamicColor.parseCLI("cell-background"), + TerminalColor{.@"cell-background"}, + try TerminalColor.parseCLI("cell-background"), ); - try testing.expectError(error.InvalidValue, DynamicColor.parseCLI("a")); + try testing.expectError(error.InvalidValue, TerminalColor.parseCLI("a")); } test "formatConfig" { @@ -4518,7 +4516,7 @@ pub const DynamicColor = union(enum) { var buf = std.ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var sc: DynamicColor = .{.@"cell-foreground"}; + var sc: TerminalColor = .{.@"cell-foreground"}; try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); } @@ -8172,11 +8170,11 @@ test "compatibility: removed cursor-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"cursor-color", ); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"cursor-text", ); } @@ -8196,11 +8194,11 @@ test "compatibility: removed selection-invert-fg-bg" { try cfg.finalize(); try testing.expectEqual( - DynamicColor.@"cell-background", + TerminalColor.@"cell-background", cfg.@"selection-foreground", ); try testing.expectEqual( - DynamicColor.@"cell-foreground", + TerminalColor.@"cell-foreground", cfg.@"selection-background", ); } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 617862e1c..e7faf633f 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -133,7 +133,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// 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.DynamicColor, + default_cursor_color: ?configpkg.Config.TerminalColor, /// 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 @@ -509,14 +509,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, cursor_opacity: f64, - cursor_text: ?configpkg.Config.DynamicColor, + cursor_text: ?configpkg.Config.TerminalColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?configpkg.Config.DynamicColor, - selection_foreground: ?configpkg.Config.DynamicColor, + selection_background: ?configpkg.Config.TerminalColor, + selection_foreground: ?configpkg.Config.TerminalColor, bold_is_bright: bool, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, @@ -2548,21 +2548,31 @@ pub fn Renderer(comptime GraphicsAPI: type) type { else false; + // The `_style` suffixed values are the colors based on + // the cell style (SGR), before applying any additional + // configuration, inversions, selections, etc. const bg_style = style.bg(cell, color_palette); - const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = style.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.selection_background) |selection_color| - // Use the selection background if set, otherwise the default fg color. - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // If we have an explicit selection background color + // specified int he config, use that + if (self.config.selection_background) |v| { + break :bg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, - } - else - self.foreground_color orelse self.default_foreground_color; + }; + } + + // If no configuration, then our selection background + // is our foreground color. + break :bg self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2582,22 +2592,27 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - const final_bg = bg_style orelse self.background_color orelse self.default_background_color; + // Our happy-path non-selection background color + // is our style or our configured defaults. + const final_bg = bg_style orelse + self.background_color orelse + self.default_background_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, not inverted, and set to cell-background // - Cell is inverted and not selected if (selected) { - // Use the selection foreground if set, otherwise the default bg color. - break :fg if (self.config.selection_foreground) |selection_color| - switch (selection_color) { - .color => selection_color.color.toTerminalRGB(), + // Use the selection foreground if set + if (self.config.selection_foreground) |v| { + break :fg switch (v) { + .color => |color| color.toTerminalRGB(), .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, - } - else - self.background_color orelse self.default_background_color; + }; + } + + break :fg self.background_color orelse self.default_background_color; } break :fg if (style.flags.inverse) @@ -2787,25 +2802,36 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse if (self.default_cursor_color) |color| color: { - // If cursor-color is set, then compute the correct color. - // Otherwise, use the foreground color - if (color == .color) { - // Use the color set by cursor-color, if any. - break :color color.color.toTerminalRGB(); - } + const cursor_color = cursor_color: { + // If an explicit cursor color was set by OSC 12, use that. + if (self.cursor_color) |v| break :cursor_color v; - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; - const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + // Use our configured color if specified + if (self.default_cursor_color) |v| switch (v) { + .color => |color| break :cursor_color color.toTerminalRGB(), + inline .@"cell-foreground", + .@"cell-background", + => |_, tag| { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg( + color_palette, + self.config.bold_is_bright, + ) orelse self.foreground_color orelse self.default_foreground_color; + const bg_style = sty.bg( + screen.cursor.page_cell, + color_palette, + ) orelse self.background_color orelse self.default_background_color; - break :color switch (color) { - // If the cell is reversed, use the opposite cell color instead. - .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, - .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, - else => unreachable, + break :cursor_color switch (tag) { + .color => unreachable, + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + }; + }, }; - } else self.foreground_color orelse self.default_foreground_color; + + break :cursor_color self.foreground_color orelse self.default_foreground_color; + }; self.addCursor(screen, style, cursor_color); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index fda52c375..4b5b93641 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,7 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.DynamicColor, + cursor_color: ?configpkg.Config.TerminalColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -263,13 +263,16 @@ 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 = if (opts.config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + 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, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 9946b0b8a..040132f03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -121,13 +121,16 @@ pub const StreamHandler = struct { 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 = if (config.cursor_color) |color| - switch (color) { - .color => color.color.toTerminalRGB(), - else => null, - } - else - null; + 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| {