From fc3e092d53d350643929bf5ab1793822cebd9ff4 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 29 Apr 2026 16:08:16 -0500 Subject: [PATCH 1/5] fix(ui): render +/-/~ prefix with explicit fg on highlighted lines The highlighted line styles set only background and rely on chroma to paint per-token fg inline. The +/-/~ prefix is prepended outside the chroma path, so the prefix bytes have no fg ANSI applied and fall through to the terminal default fg. On light themes (e.g. colorblind-light) in kitty sessions with light terminal default fg, this makes the markers near-invisible against the light add/remove backgrounds. Add Resolver.LineFg helper (mirrors LineBg) plus ColorKeyModifyLineFg for the collapsed-mode ~ prefix. Introduce Model.wrapPrefixForHighlight that injects raw truecolor ANSI fg + ResetFg only when chroma highlighting is active. Apply at all three highlighted-line render sites: styleDiffContent in diffview.go and both collapsed-mode paths in collapsed.go. --- app/ui/collapsed.go | 9 ++++--- app/ui/diffview.go | 14 ++++++++++- app/ui/mocks/style_resolver.go | 44 ++++++++++++++++++++++++++++++++++ app/ui/model.go | 1 + app/ui/style/color_key_enum.go | 10 ++++++-- app/ui/style/enums.go | 1 + app/ui/style/resolver.go | 19 +++++++++++++++ app/ui/style/resolver_test.go | 27 +++++++++++++++++++++ 8 files changed, 119 insertions(+), 6 deletions(-) diff --git a/app/ui/collapsed.go b/app/ui/collapsed.go index 2d146163..59f90580 100644 --- a/app/ui/collapsed.go +++ b/app/ui/collapsed.go @@ -105,8 +105,10 @@ func (m Model) renderCollapsedAddLine(b *strings.Builder, idx int, dl diff.DiffL numGutter, blGutter := m.lineGutters(dl) bgColor := m.resolver.Color(style.ColorKeyAddLineBg) + prefixFg := m.resolver.LineFg(diff.ChangeAdd) if modified { bgColor = m.resolver.Color(style.ColorKeyModifyLineBg) + prefixFg = m.resolver.Color(style.ColorKeyModifyLineFg) } // wrap mode: break long lines at word boundaries with continuation markers @@ -115,13 +117,14 @@ func (m Model) renderCollapsedAddLine(b *strings.Builder, idx int, dl diff.DiffL gutter: gutter, numGutter: numGutter, blGutter: blGutter, isCursor: isCursor, hasHighlight: hasHighlight, lineStyle: lineStyle, hlStyle: lineHlStyle, bgColor: bgColor, + prefixFg: prefixFg, }) return } content := lineStyle.Render(gutter + lineContent) if hasHighlight { - content = lineHlStyle.Render(gutter + textContent) + content = lineHlStyle.Render(m.wrapPrefixForHighlight(gutter, prefixFg, true) + textContent) } content = m.applyHorizontalScroll(content, bgColor) content = m.extendLineBg(content, bgColor) @@ -139,7 +142,7 @@ type wrappedLineCtx struct { gutter, numGutter, blGutter string isCursor, hasHighlight bool lineStyle, hlStyle lipgloss.Style - bgColor style.Color + bgColor, prefixFg style.Color } // renderWrappedCollapsedLine renders a collapsed add line with word wrapping, producing continuation lines with ↪ markers. @@ -158,7 +161,7 @@ func (m Model) renderWrappedCollapsedLine(b *strings.Builder, textContent string var styled string if ctx.hasHighlight { - styled = ctx.hlStyle.Render(prefix + vl) + styled = ctx.hlStyle.Render(m.wrapPrefixForHighlight(prefix, ctx.prefixFg, true) + vl) } else { styled = ctx.lineStyle.Render(prefix + vl) } diff --git a/app/ui/diffview.go b/app/ui/diffview.go index 236df8c8..6b33c034 100644 --- a/app/ui/diffview.go +++ b/app/ui/diffview.go @@ -491,6 +491,18 @@ func (m Model) linePrefix(changeType diff.ChangeType) string { } } +// wrapPrefixForHighlight wraps the +/-/~ prefix in explicit raw ANSI fg when +// chroma highlighting is on. Highlighted line styles intentionally set only +// background (chroma owns per-token fg for content), so the prefix would +// otherwise inherit the terminal default fg and may render invisibly on +// light theme backgrounds. +func (m Model) wrapPrefixForHighlight(prefix string, fg style.Color, hasHighlight bool) string { + if !hasHighlight || fg == "" { + return prefix + } + return string(fg) + prefix + string(style.ResetFg) +} + // highlightSearchMatches wraps each occurrence of the search term in the visible text // with ANSI background color sequence (preserving syntax foreground within matches). // works with both plain text and ANSI-coded content by stripping ANSI to find match positions. @@ -549,7 +561,7 @@ func (m Model) styleDiffContent(changeType diff.ChangeType, prefix, content stri if isSearchMatch && m.search.term != "" { content = m.highlightSearchMatches(content, changeType) } - + prefix = m.wrapPrefixForHighlight(prefix, m.resolver.LineFg(changeType), hasHighlight) return m.resolver.LineStyle(changeType, hasHighlight).Render(prefix + content) } diff --git a/app/ui/mocks/style_resolver.go b/app/ui/mocks/style_resolver.go index c6f43a83..4c303d5b 100644 --- a/app/ui/mocks/style_resolver.go +++ b/app/ui/mocks/style_resolver.go @@ -26,6 +26,9 @@ import ( // LineBgFunc: func(change diff.ChangeType) style.Color { // panic("mock out the LineBg method") // }, +// LineFgFunc: func(change diff.ChangeType) style.Color { +// panic("mock out the LineFg method") +// }, // LineStyleFunc: func(change diff.ChangeType, highlighted bool) lipgloss.Style { // panic("mock out the LineStyle method") // }, @@ -51,6 +54,9 @@ type styleResolverMock struct { // LineBgFunc mocks the LineBg method. LineBgFunc func(change diff.ChangeType) style.Color + // LineFgFunc mocks the LineFg method. + LineFgFunc func(change diff.ChangeType) style.Color + // LineStyleFunc mocks the LineStyle method. LineStyleFunc func(change diff.ChangeType, highlighted bool) lipgloss.Style @@ -77,6 +83,11 @@ type styleResolverMock struct { // Change is the change argument value. Change diff.ChangeType } + // LineFg holds details about calls to the LineFg method. + LineFg []struct { + // Change is the change argument value. + Change diff.ChangeType + } // LineStyle holds details about calls to the LineStyle method. LineStyle []struct { // Change is the change argument value. @@ -98,6 +109,7 @@ type styleResolverMock struct { lockColor sync.RWMutex lockIndicatorBg sync.RWMutex lockLineBg sync.RWMutex + lockLineFg sync.RWMutex lockLineStyle sync.RWMutex lockStyle sync.RWMutex lockWordDiffBg sync.RWMutex @@ -199,6 +211,38 @@ func (mock *styleResolverMock) LineBgCalls() []struct { return calls } +// LineFg calls LineFgFunc. +func (mock *styleResolverMock) LineFg(change diff.ChangeType) style.Color { + if mock.LineFgFunc == nil { + panic("styleResolverMock.LineFgFunc: method is nil but styleResolver.LineFg was just called") + } + callInfo := struct { + Change diff.ChangeType + }{ + Change: change, + } + mock.lockLineFg.Lock() + mock.calls.LineFg = append(mock.calls.LineFg, callInfo) + mock.lockLineFg.Unlock() + return mock.LineFgFunc(change) +} + +// LineFgCalls gets all the calls that were made to LineFg. +// Check the length with: +// +// len(mockedstyleResolver.LineFgCalls()) +func (mock *styleResolverMock) LineFgCalls() []struct { + Change diff.ChangeType +} { + var calls []struct { + Change diff.ChangeType + } + mock.lockLineFg.RLock() + calls = mock.calls.LineFg + mock.lockLineFg.RUnlock() + return calls +} + // LineStyle calls LineStyleFunc. func (mock *styleResolverMock) LineStyle(change diff.ChangeType, highlighted bool) lipgloss.Style { if mock.LineStyleFunc == nil { diff --git a/app/ui/model.go b/app/ui/model.go index e8ef5ffb..29367969 100644 --- a/app/ui/model.go +++ b/app/ui/model.go @@ -63,6 +63,7 @@ type styleResolver interface { Color(k style.ColorKey) style.Color Style(k style.StyleKey) lipgloss.Style LineBg(change diff.ChangeType) style.Color + LineFg(change diff.ChangeType) style.Color LineStyle(change diff.ChangeType, highlighted bool) lipgloss.Style WordDiffBg(change diff.ChangeType) style.Color IndicatorBg(change diff.ChangeType) style.Color diff --git a/app/ui/style/color_key_enum.go b/app/ui/style/color_key_enum.go index 5554b65c..ddb7a666 100644 --- a/app/ui/style/color_key_enum.go +++ b/app/ui/style/color_key_enum.go @@ -46,6 +46,7 @@ var _colorKeyParseMap = map[string]ColorKey{ "searchbg": ColorKeySearchBg, "addlinefg": ColorKeyAddLineFg, "removelinefg": ColorKeyRemoveLineFg, + "modifylinefg": ColorKeyModifyLineFg, "normalfg": ColorKeyNormalFg, "selectedfg": ColorKeySelectedFg, } @@ -87,8 +88,9 @@ var ( ColorKeySearchBg = ColorKey{name: "SearchBg", value: 12} ColorKeyAddLineFg = ColorKey{name: "AddLineFg", value: 13} ColorKeyRemoveLineFg = ColorKey{name: "RemoveLineFg", value: 14} - ColorKeyNormalFg = ColorKey{name: "NormalFg", value: 15} - ColorKeySelectedFg = ColorKey{name: "SelectedFg", value: 16} + ColorKeyModifyLineFg = ColorKey{name: "ModifyLineFg", value: 15} + ColorKeyNormalFg = ColorKey{name: "NormalFg", value: 16} + ColorKeySelectedFg = ColorKey{name: "SelectedFg", value: 17} ) // ColorKeyValues contains all possible enum values @@ -108,6 +110,7 @@ var ColorKeyValues = []ColorKey{ ColorKeySearchBg, ColorKeyAddLineFg, ColorKeyRemoveLineFg, + ColorKeyModifyLineFg, ColorKeyNormalFg, ColorKeySelectedFg, } @@ -129,6 +132,7 @@ var ColorKeyNames = []string{ "SearchBg", "AddLineFg", "RemoveLineFg", + "ModifyLineFg", "NormalFg", "SelectedFg", } @@ -184,6 +188,8 @@ var _ = func() bool { var _ colorKey = colorKeyAddLineFg // This avoids "defined but not used" linter error for colorKeyRemoveLineFg var _ colorKey = colorKeyRemoveLineFg + // This avoids "defined but not used" linter error for colorKeyModifyLineFg + var _ colorKey = colorKeyModifyLineFg // This avoids "defined but not used" linter error for colorKeyNormalFg var _ colorKey = colorKeyNormalFg // This avoids "defined but not used" linter error for colorKeySelectedFg diff --git a/app/ui/style/enums.go b/app/ui/style/enums.go index 79762f06..a0537588 100644 --- a/app/ui/style/enums.go +++ b/app/ui/style/enums.go @@ -27,6 +27,7 @@ const ( colorKeySearchBg colorKeyAddLineFg colorKeyRemoveLineFg + colorKeyModifyLineFg colorKeyNormalFg colorKeySelectedFg ) diff --git a/app/ui/style/resolver.go b/app/ui/style/resolver.go index 3767022e..53f09de1 100644 --- a/app/ui/style/resolver.go +++ b/app/ui/style/resolver.go @@ -69,6 +69,8 @@ func (r Resolver) Color(k ColorKey) Color { return Color(ansiColor(r.colors.AddFg, 38)) case ColorKeyRemoveLineFg: return Color(ansiColor(r.colors.RemoveFg, 38)) + case ColorKeyModifyLineFg: + return Color(ansiColor(r.colors.ModifyFg, 38)) case ColorKeyNormalFg: return Color(ansiColor(r.colors.Normal, 38)) case ColorKeySelectedFg: @@ -100,6 +102,23 @@ func (r Resolver) LineBg(change diff.ChangeType) Color { } } +// LineFg returns the ANSI foreground escape sequence for a diff change type. +// Used to color the +/-/~ prefix when chroma highlighting is active and the +// highlighted line style intentionally omits foreground (chroma owns content fg). +// ChangeAdd → AddFg, ChangeRemove → RemoveFg, everything else → empty. +// Modify lines (collapsed mode) are synthesized in the UI layer and not a +// diff change type — call Color(ColorKeyModifyLineFg) directly for those. +func (r Resolver) LineFg(change diff.ChangeType) Color { + switch change { + case diff.ChangeAdd: + return Color(ansiColor(r.colors.AddFg, 38)) + case diff.ChangeRemove: + return Color(ansiColor(r.colors.RemoveFg, 38)) + default: + return "" + } +} + // LineStyle returns the lipgloss.Style for a diff line based on change type // and whether syntax highlighting is active. func (r Resolver) LineStyle(change diff.ChangeType, highlighted bool) lipgloss.Style { diff --git a/app/ui/style/resolver_test.go b/app/ui/style/resolver_test.go index fe6b7cef..bfe6331d 100644 --- a/app/ui/style/resolver_test.go +++ b/app/ui/style/resolver_test.go @@ -219,6 +219,33 @@ func TestResolver_LineBg(t *testing.T) { } } +func TestResolver_LineFg(t *testing.T) { + r := NewResolver(fullColorsForTesting) + + tests := []struct { + name string + change diff.ChangeType + want bool // true if non-empty expected + }{ + {"add", diff.ChangeAdd, true}, + {"remove", diff.ChangeRemove, true}, + {"context", diff.ChangeContext, false}, + {"divider", diff.ChangeDivider, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := r.LineFg(tt.change) + if tt.want { + assert.NotEmpty(t, string(got), "expected non-empty LineFg for %s", tt.change) + assert.Contains(t, string(got), "\033[38;2;", "expected ANSI fg sequence") + } else { + assert.Empty(t, string(got), "expected empty LineFg for %s", tt.change) + } + }) + } +} + func TestResolver_LineStyle(t *testing.T) { r := NewResolver(fullColorsForTesting) From 052ff807931c112e24ca554f92d8e73d19b73b70 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 29 Apr 2026 16:13:47 -0500 Subject: [PATCH 2/5] fix(ui): force lipgloss truecolor profile to match raw-ANSI helpers revdiff's raw-ANSI helpers in style.ansiColor always emit truecolor escape sequences, but lipgloss respects the termenv-detected color profile, which can downgrade to ANSI256/ANSI/Ascii based on TERM and COLORTERM. Inside tmux or terminals where detection regresses, lipgloss renders pane borders and other styled elements in degraded colors while raw-ANSI paths (line prefix wrap, overlay title injection) keep truecolor. The mismatch shows up as washed-out pane borders that don't reflect the theme's accent and border colors and indistinct active vs inactive panes on light themes. Force lipgloss to truecolor at startup when colors are enabled, so both paths emit identical ANSI and rendered output matches the theme. --- app/main.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/main.go b/app/main.go index a5176784..2d9cb1c9 100644 --- a/app/main.go +++ b/app/main.go @@ -6,7 +6,9 @@ import ( "os" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/jessevdk/go-flags" + "github.com/muesli/termenv" "github.com/umputun/revdiff/app/annotation" "github.com/umputun/revdiff/app/diff" @@ -83,6 +85,17 @@ func main() { } func run(opts options) error { + // force lipgloss to truecolor when colors are enabled. revdiff's raw-ANSI + // helpers (style.ansiColor) always emit truecolor, but lipgloss respects + // the termenv-detected profile, which can downgrade to ANSI256 / ANSI in + // tmux or terminals where TERM/COLORTERM detection regresses. The mismatch + // makes lipgloss-rendered colors (pane borders, file tree fg) look wrong + // while raw-ANSI paths (line prefix wrap, overlay title injection) render + // correctly. Forcing truecolor unifies the two paths. + if !opts.NoColors { + lipgloss.SetColorProfile(termenv.TrueColor) + } + store := annotation.NewStore() hl := highlight.New(opts.ChromaStyle, !opts.NoColors) keysPath := opts.Keys From f3b5cfdb95168afd75c0a108593cc9ba7c7c8157 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 29 Apr 2026 16:14:03 -0500 Subject: [PATCH 3/5] fix(ui): wrap theme list non-selected names in normal fg ANSI formatEntry returns " " + swatch + " " + name for non-selected rows. The swatch wraps its glyph in fg + reset, so after the swatch the trailing name has no fg ANSI applied and falls through to the terminal default fg. On light themes rendered in a session whose terminal default fg is light (e.g. kitty default light scheme), this makes non-selected theme names near-invisible against the popup's light background. Wrap name in raw normal-fg ANSI + ResetFg so it renders in the theme's configured normal foreground, matching the contrast the selected row gets from StyleKeyFileSelected. --- app/ui/overlay/themeselect.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/ui/overlay/themeselect.go b/app/ui/overlay/themeselect.go index 5f80b566..21fb4b44 100644 --- a/app/ui/overlay/themeselect.go +++ b/app/ui/overlay/themeselect.go @@ -163,7 +163,15 @@ func (t *themeSelectOverlay) formatEntry(item ThemeItem, width int, selected boo return styled } - return " " + swatch + " " + name + // wrap the name in raw normal-fg ANSI: the swatch resets fg after rendering + // its own colored glyph, so name would otherwise inherit terminal default fg + // and render invisibly on a session whose default fg matches the theme's + // pane background (e.g. light kitty session on a light theme). + normal := string(resolver.Color(style.ColorKeyNormalFg)) + if normal == "" { + return " " + swatch + " " + name + } + return " " + swatch + " " + normal + name + string(style.ResetFg) } func (t *themeSelectOverlay) maxVisible() int { From 121394035f40e73c880dceb06f56f0fd914b149d Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 29 Apr 2026 16:16:03 -0500 Subject: [PATCH 4/5] fix(theme): widen border luminance on colorblind-light color-border was #595959 (luminance ~0.10), close to color-accent #0031a9 (~0.07). At thin border widths active vs inactive panes appeared nearly identical on light bg. Bump border to #b0b0b0 so inactive panes recede visually and the deep-navy accent stands out, matching the contrast level of catppuccin-latte. The colorblind-friendly aspect (no red/green pair) is preserved. --- themes/gallery/colorblind-light | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/gallery/colorblind-light b/themes/gallery/colorblind-light index 4e6280a9..2dcd3344 100644 --- a/themes/gallery/colorblind-light +++ b/themes/gallery/colorblind-light @@ -4,7 +4,7 @@ chroma-style = modus-operandi color-accent = #0031a9 -color-border = #595959 +color-border = #b0b0b0 color-normal = #000000 color-muted = #595959 color-selected-fg = #0f0f0f From 69439ce1ad81142b1635b4a73e70fac6c20a0850 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 29 Apr 2026 16:31:44 -0500 Subject: [PATCH 5/5] fix(ui): use search-match fg for prefix on collapsed search-match lines Addresses Copilot's review on PR #160. When a line in collapsed mode is a search match and chroma highlighting is on, the previous fix wrapped the +/~ prefix in the add-fg or modify-fg color, mismatching the search-match background. The non-highlighted path uses the SearchMatch lipgloss style (fg + bg), so the prefix renders in search-fg there. With highlighting on, the lipgloss style drops the foreground for chroma to own content fg, so the prefix wrap must inject search-fg explicitly to match. Add ColorKeySearchFg (mirrors the existing ColorKeyAddLineFg / ModifyLineFg pattern) and use it in renderCollapsedAddLine when isSearchMatch is true. --- app/ui/collapsed.go | 7 +++++++ app/ui/style/color_key_enum.go | 10 ++++++++-- app/ui/style/enums.go | 1 + app/ui/style/resolver.go | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/ui/collapsed.go b/app/ui/collapsed.go index 59f90580..8d9003c6 100644 --- a/app/ui/collapsed.go +++ b/app/ui/collapsed.go @@ -110,6 +110,13 @@ func (m Model) renderCollapsedAddLine(b *strings.Builder, idx int, dl diff.DiffL bgColor = m.resolver.Color(style.ColorKeyModifyLineBg) prefixFg = m.resolver.Color(style.ColorKeyModifyLineFg) } + if isSearchMatch { + // match the non-highlighted search-match path which uses the search-match + // lipgloss style (fg + bg). when chroma highlighting is on, lineHlStyle + // drops the foreground for chroma to own content fg, so the prefix wrap + // must inject search-fg explicitly. + prefixFg = m.resolver.Color(style.ColorKeySearchFg) + } // wrap mode: break long lines at word boundaries with continuation markers if m.modes.wrap { diff --git a/app/ui/style/color_key_enum.go b/app/ui/style/color_key_enum.go index ddb7a666..51154959 100644 --- a/app/ui/style/color_key_enum.go +++ b/app/ui/style/color_key_enum.go @@ -47,6 +47,7 @@ var _colorKeyParseMap = map[string]ColorKey{ "addlinefg": ColorKeyAddLineFg, "removelinefg": ColorKeyRemoveLineFg, "modifylinefg": ColorKeyModifyLineFg, + "searchfg": ColorKeySearchFg, "normalfg": ColorKeyNormalFg, "selectedfg": ColorKeySelectedFg, } @@ -89,8 +90,9 @@ var ( ColorKeyAddLineFg = ColorKey{name: "AddLineFg", value: 13} ColorKeyRemoveLineFg = ColorKey{name: "RemoveLineFg", value: 14} ColorKeyModifyLineFg = ColorKey{name: "ModifyLineFg", value: 15} - ColorKeyNormalFg = ColorKey{name: "NormalFg", value: 16} - ColorKeySelectedFg = ColorKey{name: "SelectedFg", value: 17} + ColorKeySearchFg = ColorKey{name: "SearchFg", value: 16} + ColorKeyNormalFg = ColorKey{name: "NormalFg", value: 17} + ColorKeySelectedFg = ColorKey{name: "SelectedFg", value: 18} ) // ColorKeyValues contains all possible enum values @@ -111,6 +113,7 @@ var ColorKeyValues = []ColorKey{ ColorKeyAddLineFg, ColorKeyRemoveLineFg, ColorKeyModifyLineFg, + ColorKeySearchFg, ColorKeyNormalFg, ColorKeySelectedFg, } @@ -133,6 +136,7 @@ var ColorKeyNames = []string{ "AddLineFg", "RemoveLineFg", "ModifyLineFg", + "SearchFg", "NormalFg", "SelectedFg", } @@ -190,6 +194,8 @@ var _ = func() bool { var _ colorKey = colorKeyRemoveLineFg // This avoids "defined but not used" linter error for colorKeyModifyLineFg var _ colorKey = colorKeyModifyLineFg + // This avoids "defined but not used" linter error for colorKeySearchFg + var _ colorKey = colorKeySearchFg // This avoids "defined but not used" linter error for colorKeyNormalFg var _ colorKey = colorKeyNormalFg // This avoids "defined but not used" linter error for colorKeySelectedFg diff --git a/app/ui/style/enums.go b/app/ui/style/enums.go index a0537588..c52548c4 100644 --- a/app/ui/style/enums.go +++ b/app/ui/style/enums.go @@ -28,6 +28,7 @@ const ( colorKeyAddLineFg colorKeyRemoveLineFg colorKeyModifyLineFg + colorKeySearchFg colorKeyNormalFg colorKeySelectedFg ) diff --git a/app/ui/style/resolver.go b/app/ui/style/resolver.go index 53f09de1..77be5b13 100644 --- a/app/ui/style/resolver.go +++ b/app/ui/style/resolver.go @@ -71,6 +71,8 @@ func (r Resolver) Color(k ColorKey) Color { return Color(ansiColor(r.colors.RemoveFg, 38)) case ColorKeyModifyLineFg: return Color(ansiColor(r.colors.ModifyFg, 38)) + case ColorKeySearchFg: + return Color(ansiColor(r.colors.SearchFg, 38)) case ColorKeyNormalFg: return Color(ansiColor(r.colors.Normal, 38)) case ColorKeySelectedFg: