From cd9517b67964ec281605d4375f5b4a37f8a77282 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 4 May 2025 14:32:06 +0900 Subject: [PATCH] Add 'alt-bg' color for striped lines (#4370) Test cases: 1. 'jump' should show alternating background colors even when 'alt-bg' is not defined as before. go run main.go --bind load:jump Two differences: * The alternating lines will not be in bold (was a bug) * The marker column will not be rendered with alternating background color 2. Use alternating background color when 'alt-bg' is set go run main.go --color bg:238,alt-bg:237 go run main.go --color bg:238,alt-bg:237 --highlight-line 3. 'selected-bg' should take precedence go run main.go --color bg:238,alt-bg:237,selected-bg:232 \ --highlight-line --multi --bind 'load:select+up+select+up' 4. Should work with text with ANSI colors declare -f | perl -0777 -pe 's/^}\n/}\0/gm' | bat --plain --language bash --color always | go run main.go --read0 --ansi --reverse --multi \ --color bg:237,alt-bg:238,current-bg:236 --highlight-line --- Close #4354 Fix #4372 --- CHANGELOG.md | 17 +++++++++++++++++ man/man1/fzf.1 | 1 + src/options.go | 2 ++ src/result.go | 14 +++----------- src/result_test.go | 4 ++-- src/terminal.go | 29 +++++++++++++++++++++++------ src/tui/tui.go | 12 ++++++++++++ 7 files changed, 60 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b426e74..464faa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ CHANGELOG ========= +0.62.0 +------ +- Added `alt-bg` color to create striped lines to visually separate rows + ```sh + fzf --color bg:237,alt-bg:238,current-bg:236 --highlight-line + + declare -f | perl -0777 -pe 's/^}\n/}\0/gm' | + bat --plain --language bash --color always | + fzf --read0 --ansi --reverse --multi \ + --color bg:237,alt-bg:238,current-bg:236 --highlight-line + ``` +- [fish] Improvements in CTRL-R binding (@bitraid) + - You can trigger CTRL-R in the middle of a command to insert the selected item + - You can delete history items with SHIFT-DEL +- Bug fixes and improvements + - Fixed unnecessary 100ms delay after `reload` + 0.61.3 ------ - Reverted #4351 as it caused `tmux run-shell 'fzf --tmux'` to fail (#4559 #4560) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index e84e1eef..2159cf36 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -270,6 +270,7 @@ color mappings. \fBcurrent\-bg (bg+) \fRBackground (current line) \fBgutter \fRGutter on the left \fBcurrent\-hl (hl+) \fRHighlighted substrings (current line) + \fBalt\-bg \fRAlternate background color to create striped lines \fBquery (input\-fg) \fRQuery string \fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR) \fBinfo \fRInfo line (match counters) diff --git a/src/options.go b/src/options.go index e94e82f9..1e4c8904 100644 --- a/src/options.go +++ b/src/options.go @@ -1295,6 +1295,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro mergeAttr(&theme.Current) case "current-bg", "bg+": mergeAttr(&theme.DarkBg) + case "alt-bg": + mergeAttr(&theme.AltBg) case "selected-fg": mergeAttr(&theme.SelectedFg) case "selected-bg": diff --git a/src/result.go b/src/result.go index 28d42e7d..3e4a81c0 100644 --- a/src/result.go +++ b/src/result.go @@ -119,7 +119,7 @@ func minRank() Result { return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} } -func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset { +func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr) []colorOffset { itemColors := result.item.Colors() // No ANSI codes @@ -182,18 +182,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t fg := ansi.color.fg bg := ansi.color.bg if fg == -1 { - if current { - fg = theme.Current.Color - } else { - fg = theme.Fg.Color - } + fg = colBase.Fg() } if bg == -1 { - if current { - bg = theme.DarkBg.Color - } else { - bg = theme.Bg.Color - } + bg = colBase.Bg() } return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base) } diff --git a/src/result_test.go b/src/result_test.go index 520ee4b1..79cc1ee2 100644 --- a/src/result_test.go +++ b/src/result_test.go @@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) { colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) - colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true) + colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined) assert := func(idx int, b int32, e int32, c tui.ColorPair) { o := colors[idx] if o.offset[0] != b || o.offset[1] != e || o.color != c { @@ -158,7 +158,7 @@ func TestColorOffset(t *testing.T) { nthOffsets := []Offset{{37, 39}, {42, 45}} for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} { - colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true) + colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr) // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}} // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}} diff --git a/src/terminal.go b/src/terminal.go index a847c3b0..a79d166f 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1258,7 +1258,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) printFn := func(window tui.Window, limit int) { if offsets == nil { // tui.Col* are not initialized until renderer.Init() - offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr, false) + offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr) } for limit > 0 { if length > limit { @@ -2791,17 +2791,28 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu _, selected := t.selected[item.Index()] label := "" extraWidth := 0 + alt := false + altBg := t.theme.AltBg + selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg if t.jumping != jumpDisabled { if index < len(t.jumpLabels) { // Striped - current = index%2 == 0 + if !altBg.IsColorDefined() { + altBg = t.theme.DarkBg + alt = index%2 == 0 + } else { + alt = index%2 == 1 + } label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1)) if t.pointerLen == 0 { extraWidth = 1 } } - } else if current { - label = t.pointer + } else { + if current { + label = t.pointer + } + alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1 } // Avoid unnecessary redraw @@ -2828,10 +2839,12 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1) postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) { width += extraWidth - if (current || selected) && t.highlightLine { + if (current || selected || alt) && t.highlightLine { color := tui.ColSelected if current { color = tui.ColCurrent + } else if alt { + color = color.WithBg(altBg) } fillSpaces := maxWidth - width if wrapped { @@ -2929,6 +2942,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu base = tui.ColNormal match = tui.ColMatch } + if alt { + base = base.WithBg(altBg) + match = match.WithBg(altBg) + } finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask) } for i := 0; i < t.gap && finalLineNum < maxLine; i++ { @@ -3027,7 +3044,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat sort.Sort(ByOrder(nthOffsets)) } } - allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, current) + allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr) maxLines := 1 if t.canSpanMultiLines() { diff --git a/src/tui/tui.go b/src/tui/tui.go index e181deaa..4a8ffbed 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -308,6 +308,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair { return dup } +func (p ColorPair) WithBg(bg ColorAttr) ColorPair { + dup := p + bgPair := ColorPair{colUndefined, bg.Color, bg.Attr} + return dup.Merge(bgPair) +} + func (p ColorPair) MergeAttr(other ColorPair) ColorPair { return p.WithAttr(other.attr) } @@ -328,6 +334,7 @@ type ColorTheme struct { Bg ColorAttr ListFg ColorAttr ListBg ColorAttr + AltBg ColorAttr Nth ColorAttr SelectedFg ColorAttr SelectedBg ColorAttr @@ -735,6 +742,7 @@ func EmptyTheme() *ColorTheme { Bg: ColorAttr{colUndefined, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined}, + AltBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -780,6 +788,7 @@ func NoColorTheme() *ColorTheme { Bg: ColorAttr{colDefault, AttrUndefined}, ListFg: ColorAttr{colDefault, AttrUndefined}, ListBg: ColorAttr{colDefault, AttrUndefined}, + AltBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colDefault, AttrUndefined}, SelectedBg: ColorAttr{colDefault, AttrUndefined}, SelectedMatch: ColorAttr{colDefault, AttrUndefined}, @@ -825,6 +834,7 @@ func init() { Bg: ColorAttr{colDefault, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined}, + AltBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -864,6 +874,7 @@ func init() { Bg: ColorAttr{colDefault, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined}, + AltBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -903,6 +914,7 @@ func init() { Bg: ColorAttr{colDefault, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined}, + AltBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},