From a4db8bd7b550c010b99f26337d841395e319890a Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Mon, 20 Jan 2025 00:49:08 +0900 Subject: [PATCH] Make 'current-fg' inherit from 'fg' to simplify configuration If you do not want 'current-fg' to inherit attributes of 'fg', prefix it with 'regular:' to reset them. # italic and underline fzf --color fg:italic,current-fg:underline # only underline fzf --color fg:italic,current-fg:regular:underline --- CHANGELOG.md | 4 ++-- src/ansi.go | 2 +- src/options.go | 2 +- src/result.go | 17 ++++++++------- src/terminal.go | 56 ++++++++++++++++++++++++------------------------ src/tui/dummy.go | 6 ++++-- src/tui/light.go | 2 +- src/tui/tcell.go | 16 +++++++++----- src/tui/tui.go | 14 +++++++++++- 9 files changed, 70 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1dd62e..11774280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,11 +90,11 @@ Also, fzf now offers "style presets" for quick customization, which can be activ ls -al | fzf --nth -1 --color nth:reverse:bold # Dim the other parts - ls -al | fzf --nth -1 --color nth:regular,fg:dim,current-fg:dim + ls -al | fzf --nth -1 --color nth:regular,fg:dim # With 'change-nth'. The current nth option is exported as $FZF_NTH. ps -ef | fzf --reverse --header-lines 1 --header-border bottom --input-border \ - --color nth:regular,fg:dim,current-fg:dim \ + --color nth:regular,fg:dim \ --bind 'ctrl-n:change-nth(8..|1|2|3|4|5|6|7|)' \ --bind 'result:transform-prompt:echo "${FZF_NTH}> "' ``` diff --git a/src/ansi.go b/src/ansi.go index 37d9c767..687b9524 100644 --- a/src/ansi.go +++ b/src/ansi.go @@ -44,7 +44,7 @@ func (s *ansiState) ToString() string { } ret := "" - if s.attr&tui.Bold > 0 { + if s.attr&tui.Bold > 0 || s.attr&tui.BoldForce > 0 { ret += "1;" } if s.attr&tui.Dim > 0 { diff --git a/src/options.go b/src/options.go index cb077a17..2168284b 100644 --- a/src/options.go +++ b/src/options.go @@ -3135,7 +3135,7 @@ func postProcessOptions(opts *Options) error { boldify := func(c tui.ColorAttr) tui.ColorAttr { dup := c if (c.Attr & tui.AttrRegular) == 0 { - dup.Attr |= tui.Bold + dup.Attr |= tui.BoldForce } return dup } diff --git a/src/result.go b/src/result.go index ada31d59..10e0c6d6 100644 --- a/src/result.go +++ b/src/result.go @@ -150,10 +150,6 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t } for _, off := range nthOffsets { - // Exclude the whole line - if int(off[1])-int(off[0]) == result.item.text.Length() { - continue - } for i := off[0]; i < off[1]; i++ { cols[i].nth = true } @@ -190,7 +186,12 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t add := func(idx int) { if (curr.color || curr.nth || curr.match) && idx > start { if curr.match { - color := colMatch + var color tui.ColorPair + if curr.nth { + color = colBase.WithAttr(attrNth).Merge(colMatch) + } else { + color = colBase.Merge(colMatch) + } var url *url if curr.color && theme.Colored { ansi := itemColors[curr.index] @@ -206,13 +207,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t // echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline if color.Fg().IsDefault() && origColor.HasBg() { color = origColor + if curr.nth { + color = color.WithAttr(attrNth) + } } else { color = origColor.MergeNonDefault(color) } } - if curr.nth { - color = color.WithAttr(attrNth) - } colors = append(colors, colorOffset{ offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url}) } else if curr.color { diff --git a/src/terminal.go b/src/terminal.go index a5d8f767..b1d86489 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -2725,38 +2725,38 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat sort.Sort(ByOrder(charOffsets)) } - wholeCovered := len(t.nthCurrent) == 0 - for _, nth := range t.nthCurrent { - // Do we still want to apply a different style when the current nth - // covers the whole string? Probably not. And we can simplify the logic. - if nth.IsFull() { - wholeCovered = true - break + // When postTask is nil, we're printing header lines. No need to care about nth. + var nthOffsets []Offset + if postTask != nil { + wholeCovered := len(t.nthCurrent) == 0 + for _, nth := range t.nthCurrent { + // Do we still want to apply a different style when the current nth + // covers the whole string? Probably not. And we can simplify the logic. + if nth.IsFull() { + wholeCovered = true + break + } } - } - // But if 'nth' is set to 'regular', it's a sign that you're applying - // a different style to the rest of the string. e.g. 'nth:regular,fg:dim' - // In this case, we still need to apply and clear the style. - // We do the same when postTask is nil, which means we're printing header lines. - if t.nthAttr == tui.AttrRegular && wholeCovered || postTask == nil { - if t.nthAttr == tui.AttrRegular { + if wholeCovered && t.nthAttr&tui.AttrRegular > 0 { + // But if 'nth' is set to 'regular', it's a sign that you're applying + // a different style to the rest of the string. e.g. 'nth:regular,fg:dim' + // In this case, we still need to apply it to clear the style. colBase = colBase.WithAttr(t.nthAttr) } - } - var nthOffsets []Offset - if !wholeCovered && t.nthAttr > 0 && postTask != nil { - var tokens []Token - if item.transformed != nil { - tokens = item.transformed.tokens - } else { - tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent) + if !wholeCovered && t.nthAttr > 0 { + var tokens []Token + if item.transformed != nil { + tokens = item.transformed.tokens + } else { + tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent) + } + for _, token := range tokens { + start := token.prefixLength + end := start + int32(token.text.Length()) + nthOffsets = append(nthOffsets, Offset{int32(start), int32(end)}) + } + sort.Sort(ByOrder(nthOffsets)) } - for _, token := range tokens { - start := token.prefixLength - end := start + int32(token.text.Length()) - nthOffsets = append(nthOffsets, Offset{int32(start), int32(end)}) - } - sort.Sort(ByOrder(nthOffsets)) } allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, current) diff --git a/src/tui/dummy.go b/src/tui/dummy.go index aaa9a7ea..1e62e849 100644 --- a/src/tui/dummy.go +++ b/src/tui/dummy.go @@ -11,8 +11,9 @@ func HasFullscreenRenderer() bool { var DefaultBorderShape = BorderRounded func (a Attr) Merge(b Attr) Attr { - if b == AttrRegular { - return b + if b&AttrRegular > 0 { + // Only keep bold attribute set by the system + return b | (a & BoldForce) } return a | b @@ -22,6 +23,7 @@ const ( AttrUndefined = Attr(0) AttrRegular = Attr(1 << 8) AttrClear = Attr(1 << 9) + BoldForce = Attr(1 << 10) Bold = Attr(1) Dim = Attr(1 << 1) diff --git a/src/tui/light.go b/src/tui/light.go index 48202bce..f4688060 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -1011,7 +1011,7 @@ func attrCodes(attr Attr) []string { if (attr & AttrClear) > 0 { return codes } - if (attr & Bold) > 0 { + if (attr&Bold) > 0 || (attr&BoldForce) > 0 { codes = append(codes, "1") } if (attr & Dim) > 0 { diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 991052bd..0bf160c4 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -97,6 +97,7 @@ const ( AttrUndefined = Attr(0) AttrRegular = Attr(1 << 7) AttrClear = Attr(1 << 8) + BoldForce = Attr(1 << 10) ) func (r *FullscreenRenderer) PassThrough(str string) { @@ -141,6 +142,11 @@ func (c Color) Style() tcell.Color { } func (a Attr) Merge(b Attr) Attr { + if b&AttrRegular > 0 { + // Only keep bold attribute set by the system + return b | (a & BoldForce) + } + return a | b } @@ -556,13 +562,13 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, normal := ColBorder switch windowType { case WindowList: - normal = ColListBorder + normal = ColNormal case WindowHeader: - normal = ColHeaderBorder + normal = ColHeader case WindowInput: - normal = ColInputBorder + normal = ColInput case WindowPreview: - normal = ColPreviewBorder + normal = ColPreview } w := &TcellWindow{ color: r.theme.Colored, @@ -694,7 +700,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn { } style = style. Blink(a&Attr(tcell.AttrBlink) != 0). - Bold(a&Attr(tcell.AttrBold) != 0). + Bold(a&Attr(tcell.AttrBold) != 0 || a&BoldForce != 0). Dim(a&Attr(tcell.AttrDim) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0). diff --git a/src/tui/tui.go b/src/tui/tui.go index 212a1bed..58c1bec5 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -213,6 +213,16 @@ func NewColorAttr() ColorAttr { return ColorAttr{Color: colUndefined, Attr: AttrUndefined} } +func (a ColorAttr) Merge(other ColorAttr) ColorAttr { + if other.Color != colUndefined { + a.Color = other.Color + } + if other.Attr != AttrUndefined { + a.Attr = a.Attr.Merge(other.Attr) + } + return a +} + const ( colUndefined Color = -2 colDefault Color = -1 @@ -904,7 +914,9 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Match = o(baseTheme.Match, theme.Match) - theme.Current = o(baseTheme.Current, theme.Current) + // Inherit from 'fg', so that we don't have to write 'current-fg:dim' + // e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular + theme.Current = theme.Fg.Merge(o(baseTheme.Current, theme.Current)) theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch) theme.Spinner = o(baseTheme.Spinner, theme.Spinner) theme.Info = o(baseTheme.Info, theme.Info)