From 52790fb92c0ba71bcd68c159e2f700c583097cc1 Mon Sep 17 00:00:00 2001 From: Robert Ian Hawdon Date: Wed, 23 Apr 2025 12:03:26 +0100 Subject: [PATCH] Added bold-color option --- src/config.zig | 1 + src/config/Config.zig | 113 ++++++++++++++++++++++++++++++++++++++- src/renderer/generic.zig | 29 ++++++---- src/terminal/style.zig | 69 +++++++++++++++++++----- 4 files changed, 187 insertions(+), 25 deletions(-) diff --git a/src/config.zig b/src/config.zig index b6fecde4e..efc9fd973 100644 --- a/src/config.zig +++ b/src/config.zig @@ -14,6 +14,7 @@ pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; // Field types +pub const BoldColor = Config.BoldColor; pub const ClipboardAccess = Config.ClipboardAccess; pub const Command = Config.Command; pub const ConfirmCloseSurface = Config.ConfirmCloseSurface; diff --git a/src/config/Config.zig b/src/config/Config.zig index f9454bf98..a53986bc9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -69,6 +69,10 @@ pub const compatibility = std.StaticStringMap( // this behavior. This applies to selection too. .{ "cursor-invert-fg-bg", compatCursorInvertFgBg }, .{ "selection-invert-fg-bg", compatSelectionInvertFgBg }, + + // Ghostty 1.2 merged `bold-is-bright` into the new `bold-color` + // by setting the value to "bright". + .{ "bold-is-bright", compatBoldIsBright }, }); /// The font families to use. @@ -2804,8 +2808,24 @@ else /// notifications using certain escape sequences such as OSC 9 or OSC 777. @"desktop-notifications": bool = true, -/// If `true`, the bold text will use the bright color palette. -@"bold-is-bright": bool = false, +/// Modifies the color used for bold text in the terminal. +/// +/// This can be set to a specific color, using the same format as +/// `background` or `foreground` (e.g. `#RRGGBB` but other formats +/// are also supported; see the aforementioned documentation). If a +/// specific color is set, this color will always be used for all +/// bold text regardless of the terminal's color scheme. +/// +/// This can also be set to `bright`, which uses the bright color palette +/// for bold text. For example, if the text is red, then the bold will +/// use the bright red color. The terminal palette is set with `palette` +/// but can also be overridden by the terminal application itself using +/// escape sequences such as OSC 4. (Since Ghostty 1.2.0, the previous +/// configuration `bold-is-bright` is deprecated and replaced by this +/// usage). +/// +/// Available since Ghostty 1.2.0. +@"bold-color": ?BoldColor = null, /// This will be used to set the `TERM` environment variable. /// HACK: We set this with an `xterm` prefix because vim uses that to enable key @@ -3910,6 +3930,23 @@ fn compatSelectionInvertFgBg( return true; } +fn compatBoldIsBright( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "bold-is-bright")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"bold-color" = .bright; + } + + return true; +} + /// Create a shallow copy of this config. This will share all the memory /// allocated with the previous config but will have a new arena for /// any changes or new allocations. The config should have `deinit` @@ -4537,6 +4574,58 @@ pub const TerminalColor = union(enum) { } }; +/// Represents color values that can be used for bold. See `bold-color`. +pub const BoldColor = union(enum) { + color: Color, + bright, + + pub fn parseCLI(input_: ?[]const u8) !BoldColor { + const input = input_ orelse return error.ValueRequired; + if (std.mem.eql(u8, input, "bright")) return .bright; + return .{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: BoldColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + .bright => try formatter.formatEntry( + [:0]const u8, + @tagName(self), + ), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try BoldColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try BoldColor.parseCLI("black"), + ); + try testing.expectEqual( + BoldColor.bright, + try BoldColor.parseCLI("bright"), + ); + + try testing.expectError(error.InvalidValue, BoldColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: BoldColor = .bright; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = bright\n", buf.items); + } +}; + pub const ColorList = struct { const Self = @This(); @@ -8236,3 +8325,23 @@ test "compatibility: removed selection-invert-fg-bg" { ); } } + +test "compatibility: removed bold-is-bright" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--bold-is-bright", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + BoldColor.bright, + cfg.@"bold-color", + ); + } +} diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 829563075..3965d302a 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -519,7 +519,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { foreground: terminal.color.RGB, selection_background: ?configpkg.Config.TerminalColor, selection_foreground: ?configpkg.Config.TerminalColor, - bold_is_bright: bool, + bold_color: ?configpkg.BoldColor, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, custom_shaders: configpkg.RepeatablePath, @@ -580,7 +580,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .background = config.background.toTerminalRGB(), .foreground = config.foreground.toTerminalRGB(), - .bold_is_bright = config.@"bold-is-bright", + .bold_color = config.@"bold-color", + .min_contrast = @floatCast(config.@"minimum-contrast"), .padding_color = config.@"window-padding-color", @@ -2540,10 +2541,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // 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(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); // The final background color for the cell. const bg = bg: { @@ -2801,10 +2803,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .@"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 fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); const bg_style = sty.bg( screen.cursor.page_cell, color_palette, @@ -2852,7 +2855,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type { } 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 fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .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; break :blk switch (txt) { diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 865e15f64..78afcdf39 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const configpkg = @import("../config.zig"); const color = @import("color.zig"); const sgr = @import("sgr.zig"); const page = @import("page.zig"); @@ -115,24 +116,68 @@ pub const Style = struct { }; } - /// Returns the fg color for a cell with this style given the palette. + pub const Fg = struct { + /// The default color to use if the style doesn't specify a + /// foreground color and no configuration options override + /// it. + default: color.RGB, + + /// The current color palette. Required to map palette indices to + /// real color values. + palette: *const color.Palette, + + /// If specified, the color to use for bold text. + bold: ?configpkg.BoldColor = null, + }; + + /// Returns the fg color for a cell with this style given the palette + /// and various configuration options. pub fn fg( self: Style, - palette: *const color.Palette, - bold_is_bright: bool, - ) ?color.RGB { + opts: Fg, + ) color.RGB { + // Note we don't pull the bold check to the top-level here because + // we don't want to duplicate the conditional multiple times since + // certain colors require more checks (e.g. `bold_is_bright`). + return switch (self.fg_color) { - .none => null, - .palette => |idx| palette: { - if (bold_is_bright and self.flags.bold) { - const bright_offset = @intFromEnum(color.Name.bright_black); - if (idx < bright_offset) - break :palette palette[idx + bright_offset]; + .none => default: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .bright => {}, + .color => |v| break :default v.toTerminalRGB(), + }; } - break :palette palette[idx]; + break :default opts.default; + }, + + .palette => |idx| palette: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :palette v.toTerminalRGB(), + .bright => { + const bright_offset = @intFromEnum(color.Name.bright_black); + if (idx < bright_offset) { + break :palette opts.palette[idx + bright_offset]; + } + }, + }; + } + + break :palette opts.palette[idx]; + }, + + .rgb => |rgb| rgb: { + if (self.flags.bold and rgb.eql(opts.default)) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :rgb v.toTerminalRGB(), + .bright => {}, + }; + } + + break :rgb rgb; }, - .rgb => |rgb| rgb, }; }