Repository: junegunn/fzf
Pull request #4370: Add 'alt-bg' color for striped lines
Author: login: junegunn
Labels: name: bug
description: Something isn't working
name: feature
description: New feature request
Created: 2025-05-02T01:42:33Z | Merged: 2025-05-04T05:32:06Z

<img width="1521" alt="image" src="https://github.com/user-attachments/assets/205cd484-f2ad-4d94-b79f-2bc471340a42" />

```sh
# seoul256-light
fzf --color=bg+:#E9E9E9,bg:#FFFFFF,border:#E1E1E1,spinner:#719899,hl:#719872,fg:#616161,alt-bg:#F1F1F1,info:#727100,pointer:#E12672,marker:#E17899,fg+:#616161,header:#719872,prompt:#0099BD,hl+:#719899,query:#616161 --highlight-line
```

## Test cases

### 'jump' should show alternating background colors even when 'alt-bg' is not defined as before.

  ```sh
  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

### Use alternating background color when 'alt-bg' is set

  ```sh
  go run main.go --color bg:238,alt-bg:237
  go run main.go --color bg:238,alt-bg:237 --highlight-line
  ```

### 'selected-bg' should take precedence

  ```sh
  go run main.go --color bg:238,alt-bg:237,selected-bg:232 \
                 --highlight-line --multi --bind 'load:select+up+select+up'
  ```

---

Close #4354


Linked issue #4354: Alternate list color
It would be nice to have the ability to alternate the list-bg/list-fg color.

Rofi has this feature:

![Image](https://github.com/user-attachments/assets/19609a20-e819-4502-8b2c-90042beac6aa)

For multiline items or items that wrap, this would make it much easier to see where each item begins and ends.
Linked issue comments: user: login: LangLangBart
created_at: 2025-04-22T15:43:57Z
body: > For multiline items or items that wrap, this would make it much easier to see where each item begins and ends.

`--gap`[^1] is quite helpful for this
```bash
printf "Test %.300d - \n" {1..10} | FZF_DEFAULT_OPTS= fzf --gap --wrap
```

<img src="https://github.com/user-attachments/assets/ca3e8798-db4e-48fd-b50f-af6cb159649e" width="400">

[^1]: [fzf/CHANGELOG.md 0.56.0](https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0560)
author_association: CONTRIBUTOR
user: login: alex-huff
created_at: 2025-04-22T15:49:13Z
body: 
> `--gap`[1](#user-content-fn-1-05c5c48d7f271a14fea725d642085b17) is quite helpful for this

Thanks for the suggestion, `--gap --gap-line="━"` works great for my use-case. Alternating colors still would help save space but at least I don't have trouble differentiating between different items anymore.
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-04-23T01:00:12Z
body: @LangLangBart Thanks for the comment 👍 

I remember testing the idea in the past, but I wasn't quite happy with the look. It found it to be a bit distracting.

You can trigger `jump` and preview how it would look.

```sh
fzf --bind load:jump --highlight-line --no-bold

# Less constrast
fzf --bind load:jump --highlight-line --no-bold --color bg+:239 
```
author_association: OWNER
user: login: alex-huff
created_at: 2025-04-23T04:01:57Z
body: > I remember testing the idea in the past, but I wasn't quite happy with the look. It found it to be a bit distracting.

Fair enough.

With a high contrast, (especially without `--highlight-line --no-bold` like shown below) I agree that it can be very distracting.

![Image](https://github.com/user-attachments/assets/bc856c6c-d5e9-4336-adb2-30245dcd9425)

With a low contrast, I personally think it isn't really distracting.

![Image](https://github.com/user-attachments/assets/01616476-8ef5-4988-ab6b-d6747a388279)
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-04-24T00:45:15Z
body: > With a low contrast, I personally think it isn't really distracting.

Right, I also experimented with different contrasts, only to find out that I much prefer the clean, minimal look without it. Anyway, that's just my personal preference, and it wouldn't hurt to provide an option.
author_association: OWNER
user: login: junegunn
created_at: 2025-05-02T01:46:28Z
body: See #4370 
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-02T07:22:55Z
body: > See [#4370](https://github.com/junegunn/fzf/pull/4370)

Works great!
author_association: CONTRIBUTOR

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b426e7479f..464faa9f5c8 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 e84e1eef38c..2159cf36bcf 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 e94e82f9bd9..1e4c890459c 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 28d42e7d06b..3e4a81c0de2 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 520ee4b1e43..79cc1ee2b64 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 a847c3b0fe3..a79d166f1fb 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 e181deaafbf..4a8ffbedf68 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},


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds support for an alternate background (alt‑bg) color for striped lines in the TUI.  
- Introduces a WithBg method in ColorPair and adds an AltBg field to ColorTheme.  
- Updates theme initialization in EmptyTheme, NoColorTheme, and init functions to include AltBg.  
- Adjusts Terminal printItem logic to apply alt‑bg for alternating line backgrounds.

### Reviewed Changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

| File              | Description                                                                            |
| ----------------- | -------------------------------------------------------------------------------------- |
| src/tui/tui.go    | Added the WithBg method and integrated AltBg into theme definitions and initializers.  |
| src/terminal.go   | Modified printItem logic to use the alt flag and apply the alt‑bg color for striped lines.|
| src/options.go    | Updated color parsing to include the alt‑bg option.                                    |

<details>
<summary>Files not reviewed (1)</summary>

* **man/man1/fzf.1**: Language not supported
</details>



state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T01:43:18Z
user: login: alex-huff
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T08:05:05Z
user: login: junegunn
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-02T08:10:00Z

Review comments:
diff_hunk: @@ -308,6 +308,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
 	return dup
 }
 
path: src/tui/tui.go
user: login: Copilot
body: Consider adding a comment to explain the merge logic in WithBg, particularly why the foreground is set to colUndefined when constructing bgPair to update only the background attributes.
```suggestion

// WithBg updates the background attributes of the ColorPair while leaving the
// foreground attributes unchanged. To achieve this, the foreground is set to
// colUndefined in the temporary bgPair, ensuring that only the background is
// modified during the merge operation.
```
created_at: 2025-05-02T01:43:17Z
author_association: NONE
diff_hunk: @@ -2791,17 +2791,26 @@ 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 !altBg.IsColorDefined() {
+			altBg = t.theme.DarkBg
+		}
 		if index < len(t.jumpLabels) {
 			// Striped
path: src/terminal.go
user: login: Copilot
body: [nitpick] Consider adding an inline comment to clarify the purpose of the 'alt' flag when computing striped line backgrounds. This would help explain its interplay with the 'current' flag and ensure the intended behavior is maintained.
```suggestion
			// Striped: Use the `alt` flag to determine if the current line should have an alternate background color.
			// This creates a striped effect for jump labels. The `alt` flag is true for even indices.
```
created_at: 2025-05-02T01:43:18Z
author_association: NONE
diff_hunk: @@ -2899,7 +2910,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 			}
 			return indentSize
 		}
-		finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask)
path: src/terminal.go
user: login: alex-huff
body: nitpick: `current` is always true on line 2902 so this change wasn't meaningful. If it was done for readability then line 2947 should probably be updated similarly.
created_at: 2025-05-02T08:05:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2899,7 +2910,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 			}
 			return indentSize
 		}
-		finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask)
path: src/terminal.go
user: login: junegunn
body: That's correct. This was changed during the implementation process, but it is no longer needed.
created_at: 2025-05-02T08:10:00Z
author_association: OWNER

PR comments:
user: login: junegunn
created_at: 2025-05-02T08:12:46Z
body: @alex-huff How do you feel about

```diff
diff --git a/src/terminal.go b/src/terminal.go
index be4973d7..54897773 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -2800,7 +2800,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 		}
 		if index < len(t.jumpLabels) {
 			// Striped
-			alt = index%2 == 0
+			alt = index%2 == 1
 			label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
 			if t.pointerLen == 0 {
 				extraWidth = 1
@@ -2810,7 +2810,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 		if current {
 			label = t.pointer
 		}
-		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 0
+		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
 	}
 
 	// Avoid unnecessary redraw
```

I can't quite decide which one I prefer.
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-02T08:23:03Z
body: > @alex-huff How do you feel about
> 
> ```diff
> diff --git a/src/terminal.go b/src/terminal.go
> index be4973d7..54897773 100644
> --- a/src/terminal.go
> +++ b/src/terminal.go
> @@ -2800,7 +2800,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
>  		}
>  		if index < len(t.jumpLabels) {
>  			// Striped
> -			alt = index%2 == 0
> +			alt = index%2 == 1
>  			label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
>  			if t.pointerLen == 0 {
>  				extraWidth = 1
> @@ -2810,7 +2810,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
>  		if current {
>  			label = t.pointer
>  		}
> -		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 0
> +		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
>  	}
>  
>  	// Avoid unnecessary redraw
> ```
> 
> I can't quite decide which one I prefer.

I see what you mean. I like that diff slightly more than the current behavior. Of course it is very subtle. Also, rofi behaves the same way.

![image](https://github.com/user-attachments/assets/a9b3a704-aeed-4dfe-8d71-4af660162ce0)
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-05-02T08:35:45Z
body: There are (subjective) pros and cons.

* `go run main.go --bind load:jump --highlight-line` doesn't look nice, because the first two consecutive lines are in the same background color.
    * That's because, in this case, `alt-bg` is undefined and we fall back to `current-bg`.
    * `go run main.go --bind load:jump --highlight-line --color alt-bg:237` looks acceptable
* Logically, it makes more sense to apply `alt-bg` to the second line, not the first.
* On the other hand, the input section (prompt and info) uses the default background color, so using `alt-bg` first can look more balanced to some eyes
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-02T08:44:01Z
body: > There are (subjective) pros and cons.
> 
> * `go run main.go --bind load:jump --highlight-line` doesn't look nice, because the first two consecutive lines are in the same background color.

Yeah, I imagine binding jump to load is quite common. Since the current item will almost always still be the first item after loading it probably makes sense to use the alternate background on odd items.
author_association: CONTRIBUTOR
user: login: alex-huff
created_at: 2025-05-02T09:14:48Z
body: One thing I have noticed is that it is quite hard to mentally keep track of an item when scrolling through the list since its background color changes constantly.

https://github.com/user-attachments/assets/64ef8707-ddb9-4536-b289-452b794c0fc8

Try to keep track of `go.mod` when I am scrolling through the item list. The first time I scroll through the list each item preserves its background color when scrolling. The second time I scroll through the list the background color of items changes (which is the current behavior). It is quite a bit easier to visualize scrolling when items keep their background color.

Sadly you can't just use `item.Index()` like I did since it breaks when items are filtered.
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-05-02T09:27:31Z
body: > It is quite a bit easier to visualize scrolling when items keep their background color.

I find the behavior distracting and not easy on the eyes. The whole list keeps flashing.
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-02T09:46:47Z
body: > > It is quite a bit easier to visualize scrolling when items keep their background color.
> 
> I find the behavior distracting and not easy on the eyes. The whole list keeps flashing.

If your eyes are fixated on a single spot on the screen than yes, I agree. If your eyes are following an item in the list as it scrolls than the opposite it true. In this case it is actually the current behavior that creates a flashing effect.
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-05-02T11:57:58Z
body: I understand your point. But when you're scrolling, you'll likely be looking at the "current" item under the cursor with `current-bg`, so the color is not changing there. Anyway, I tend to see the alternating colors as the colors of the fixed "slots" on screen rather than the colors of the "items".

> Sadly you can't just use `item.Index()` like I did since it breaks when items are filtered.

Trying to keep the background color of an item fixed defeats the initial purpose of making it easier to separate consecutive items on screen.
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-02T16:10:18Z
body: > Trying to keep the background color of an item fixed defeats the initial purpose of making it easier to separate consecutive items on screen.

Yeah if items preserving their background is desired than I think you could use something like `merger.FindIndex(item.Index())` for the index so that consecutive items on the screen would always have different background colors but each item's background color would stay the same while scrolling (at least until the list is updated). I was just using `item.Index()` when I was testing what it would "feel" like.

> I understand your point. But when you're scrolling, you'll likely be looking at the "current" item under the cursor with `current-bg`, so the color is not changing there.

Sure this makes sense. When I initially noticed the difficulty in tracking items while scrolling I was trying to scroll the list until all the items where "fresh". I do this a lot while using fzf. I lock my eyes on whenever item is at the top of the screen and then scroll until that item leaves the bottom of the screen. However, I should just be using the `pg-up` action for this so this really is my fault.

> Anyway, I tend to see the alternating colors as the colors of the fixed "slots" on screen rather than the colors of the "items".

Makes sense.
author_association: CONTRIBUTOR
user: login: junegunn
created_at: 2025-05-03T00:28:54Z
body: > However, I should just be using the `pg-up` action for this so this really is my fault.

Nah, no one's wrong here. We all have different workflows and expectations. We just have to make some choices because a program can't be infinitely customizable.

---

Another thing to note. This `alt-bg` currently doesn't apply to gap lines.

<img width="917" alt="image" src="https://github.com/user-attachments/assets/127cbba5-538a-4ef8-bb6a-0b15c4a6c514" />

I tried applying it to the gap lines, but it looked a little off.

<img width="916" alt="image" src="https://github.com/user-attachments/assets/f5699ca8-4d5e-411a-8e94-cf580950fdc1" />

---

> > There are (subjective) pros and cons.
> > 
> > * `go run main.go --bind load:jump --highlight-line` doesn't look nice, because the first two consecutive lines are in the same background color.
> 
> Yeah, I imagine binding jump to load is quite common. Since the current item will almost always still be the first item after loading it probably makes sense to use the alternate background on odd items.

One thing we can try is to do it differently on `jump` when no `alt-bg` is defined like so

```diff
diff --git a/src/terminal.go b/src/terminal.go
index f3a9a616..760a8a73 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -2795,12 +2795,14 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 	altBg := t.theme.AltBg
 	selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg
 	if t.jumping != jumpDisabled {
-		if !altBg.IsColorDefined() {
-			altBg = t.theme.DarkBg
-		}
 		if index < len(t.jumpLabels) {
 			// Striped
-			alt = 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
@@ -2810,7 +2812,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 		if current {
 			label = t.pointer
 		}
-		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 0
+		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
 	}
 
 	// Avoid unnecessary redraw
```

But then again, I still can't decide which version I prefer 🫠 

I think I wanted to avoid the first two lines being darker together at start, but that really depends on the brightness of `alt-bg`.

```sh
# When alt-bg is darker
go run main.go --color bg:238,current-bg:236,alt-bg:237 --highlight-line

# When alt-bg is brighter
go run main.go --color bg:238,current-bg:236,alt-bg:239 --highlight-line
```
author_association: OWNER
user: login: alex-huff
created_at: 2025-05-03T01:48:31Z
body: I prefer when `alt-bg` doesn't apply to gap lines. It feels rather asymmetrical otherwise. Also, When `alt-bg` is applied to the gap line, the line looks like part of the item (almost like an underline) when logically it should separate items as the name 'gap' implies.

I think for most people `alt-bg` and `--gap` are accomplishing the same thing: making it easier to distinguish between different items when they are potentially multi-line/wrapped. For this reason it seems unlikely there would be a compelling reason to use both `alt-bg` and `--gap` together anyway.
author_association: CONTRIBUTOR
user: login: alex-huff
created_at: 2025-05-03T02:19:52Z
body: > One thing we can try is to do it differently on `jump` when no `alt-bg` is defined like so
> 
> ```diff
> diff --git a/src/terminal.go b/src/terminal.go
> index f3a9a616..760a8a73 100644
> --- a/src/terminal.go
> +++ b/src/terminal.go
> @@ -2795,12 +2795,14 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
>  	altBg := t.theme.AltBg
>  	selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg
>  	if t.jumping != jumpDisabled {
> -		if !altBg.IsColorDefined() {
> -			altBg = t.theme.DarkBg
> -		}
>  		if index < len(t.jumpLabels) {
>  			// Striped
> -			alt = 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
> @@ -2810,7 +2812,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
>  		if current {
>  			label = t.pointer
>  		}
> -		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 0
> +		alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
>  	}
>  
>  	// Avoid unnecessary redraw
> ```
> 
> But then again, I still can't decide which version I prefer 🫠
> 
> I think I wanted to avoid the first two lines being darker together at start, but that really depends on the brightness of `alt-bg`.
> 
> ```shell
> # When alt-bg is darker
> go run main.go --color bg:238,current-bg:236,alt-bg:237 --highlight-line
> 
> # When alt-bg is brighter
> go run main.go --color bg:238,current-bg:236,alt-bg:239 --highlight-line
> ```

I think when the user chooses a reasonable `alt-bg` color (one that contrasts with both `list-bg` and `current-bg`) it isn't really a big deal (functionally or aesthetically) whether `index%2 == 0` or `index%2 == 1` is used. However when the alternate background color is the same as the current background color, which is generally the case when no `alt-bg` is specified, `index%2 == 0` is better.


author_association: CONTRIBUTOR