Repository: junegunn/fzf
Pull request #4431 (2025-06-23 15:28:07)

Bump github.com/charlievieth/fastwalk from 1.0.10 to 1.0.12
Bumps [github.com/charlievieth/fastwalk](https://github.com/charlievieth/fastwalk) from 1.0.10 to 1.0.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/charlievieth/fastwalk/releases">github.com/charlievieth/fastwalk's releases</a>.</em></p>
<blockquote>
<h2>Fix tests on Windows/MSYS and improve test coverage</h2>
<p><code>v1.0.12</code></p>
<p>commit fc359aa6488b381ba29e0e6178ab3af36a8eaa90
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Fri Jun 20 02:04:34 2025 -0400</p>
<pre><code>README: remove codecov link
</code></pre>
<p>commit 0c0cafe7e38d11298abba665a4f70ea50ce1bc4b
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Fri Jun 20 01:44:41 2025 -0400</p>
<pre><code>internal/dirent: add tests for readInt
</code></pre>
<p>commit a22fcf9a208c39840fc42732f0be4543fa9c275b
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Fri Jun 20 00:37:47 2025 -0400</p>
<pre><code>Add codecov config to ignore internal/dirent filees
</code></pre>
<p>commit d277324d94e04c1e0d7f352c76cf9a133554824d
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Fri Jun 20 00:35:27 2025 -0400</p>
<pre><code>README: add codecov link
</code></pre>
<p>commit 5449d1152aee030e449d67e387c6c350bddba1e4
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Thu Jun 19 23:40:59 2025 -0400</p>
<pre><code>internal/dirent: move arch dependent code to make codecov happy
<p>This is a pointless commit that simply moves the arch specific readInt*<br />
functions to their respective build tagged files. The goal here is to<br />
make codecov report a faired value (I don't really care about this, but<br />
some do so here we are).<br />
</code></pre></p>
<p>commit e692243489eeee10ccbe4dc9f24e9b7aa926bb57
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Tue Jun 3 19:31:56 2025 -0400</p>
<pre><code>gh: fix codeql analysis
</code></pre>
<p>commit f4026d6dfcf938fc8d5d00108c04f995a8f61b43
Author: Charlie Vieth <a href="mailto:charlie.vieth@gmail.com">charlie.vieth@gmail.com</a>
Date:   Wed May 14 22:28:08 2025 -0400</p>
<pre><code>scripts/bench_comp: fixup benchmark script
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/charlievieth/fastwalk/commit/fc359aa6488b381ba29e0e6178ab3af36a8eaa90"><code>fc359aa</code></a> README: remove codecov link</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/0c0cafe7e38d11298abba665a4f70ea50ce1bc4b"><code>0c0cafe</code></a> internal/dirent: add tests for readInt</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/a22fcf9a208c39840fc42732f0be4543fa9c275b"><code>a22fcf9</code></a> Add codecov config to ignore internal/dirent filees</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/d277324d94e04c1e0d7f352c76cf9a133554824d"><code>d277324</code></a> README: add codecov link</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/5449d1152aee030e449d67e387c6c350bddba1e4"><code>5449d11</code></a> internal/dirent: move arch dependent code to make codecov happy</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/e692243489eeee10ccbe4dc9f24e9b7aa926bb57"><code>e692243</code></a> gh: fix codeql analysis</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/f4026d6dfcf938fc8d5d00108c04f995a8f61b43"><code>f4026d6</code></a> scripts/bench_comp: fixup benchmark script</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/bc5b513dc10438260287d7bf1b1a785d4b9720d1"><code>bc5b513</code></a> fastwalk: add example tests</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/d740698b87fd1079b9e167557de81d6d6266a34f"><code>d740698</code></a> fastwalk: fix tests for Windows/MSYS</li>
<li><a href="https://github.com/charlievieth/fastwalk/commit/6965c863709e7af3869a8e31d379aa44927986ba"><code>6965c86</code></a> fastwalk: fix comment in LongFileName test</li>
<li>Additional commits viewable in <a href="https://github.com/charlievieth/fastwalk/compare/v1.0.10...v1.0.12">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/charlievieth/fastwalk&package-manager=go_modules&previous-version=1.0.10&new-version=1.0.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44de2ef2..a1fb749c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -59,6 +59,12 @@ CHANGELOG
       --bind "focus:+bg-transform-ghost:$GETTER" \
       --bind "focus:+bg-transform-prompt:$GETTER"
   ```
+- Added support for full-line background color in the list section
+  ```sh
+  for i in $(seq 16 255); do
+    echo -e "\x1b[48;5;${i}m\x1b[0Khello"
+  done | fzf --ansi
+  ```
 - SSH completion enhancements by @akinomyoga
 - Bug fixes and improvements
 
diff --git a/go.mod b/go.mod
index d4f0b170..f1ec901d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,7 @@
 module github.com/junegunn/fzf
 
 require (
-	github.com/charlievieth/fastwalk v1.0.10
+	github.com/charlievieth/fastwalk v1.0.12
 	github.com/gdamore/tcell/v2 v2.8.1
 	github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
 	github.com/mattn/go-isatty v0.0.20
diff --git a/go.sum b/go.sum
index e37721c5..614e875b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/charlievieth/fastwalk v1.0.10 h1:0qUbvA2O+K+X+IrTfZTC0UH2DK5MOA+KjVfStAHUnGg=
-github.com/charlievieth/fastwalk v1.0.10/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
+github.com/charlievieth/fastwalk v1.0.12 h1:pwfxe1LajixViQqo7EFLXU2+mQxb6OaO0CeNdVwRKTg=
+github.com/charlievieth/fastwalk v1.0.12/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
 github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
 github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
 github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
diff --git a/src/algo/algo.go b/src/algo/algo.go
index d6a9a663..cc855e1a 100644
--- a/src/algo/algo.go
+++ b/src/algo/algo.go
@@ -827,7 +827,7 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
 
 	// For simplicity, only look at the bonus at the first character position
 	pidx := 0
-	bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
+	bestPos, bonus, bbonus, bestBonus := -1, int16(0), int16(0), int16(-1)
 	for index := 0; index < lenRunes; index++ {
 		index_ := indexAt(index, lenRunes, forward)
 		char := text.Get(index_)
@@ -849,7 +849,16 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
 				bonus = bonusAt(text, index_)
 			}
 			if boundaryCheck {
-				ok = bonus >= bonusBoundary
+				if forward && pidx_ == 0 {
+					bbonus = bonus
+				} else if !forward && pidx_ == lenPattern-1 {
+					if index_ < lenRunes-1 {
+						bbonus = bonusAt(text, index_+1)
+					} else {
+						bbonus = bonusBoundaryWhite
+					}
+				}
+				ok = bbonus >= bonusBoundary
 				if ok && pidx_ == 0 {
 					ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
 				}
diff --git a/src/ansi.go b/src/ansi.go
index 79359d34..1311620d 100644
--- a/src/ansi.go
+++ b/src/ansi.go
@@ -156,13 +156,13 @@ func isCtrlSeqStart(c uint8) bool {
 // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
 // calling FindStringIndex() on the below regex (which was originally used):
 //
-// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
+// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08|\n)"
 func nextAnsiEscapeSequence(s string) (int, int) {
 	// fast check for ANSI escape sequences
 	i := 0
 	for ; i < len(s); i++ {
 		switch s[i] {
-		case '\x0e', '\x0f', '\x1b', '\x08':
+		case '\x0e', '\x0f', '\x1b', '\x08', '\n':
 			// We ignore the fact that '\x08' cannot be the first char
 			// in the string and be an escape sequence for the sake of
 			// speed and simplicity.
@@ -174,6 +174,9 @@ func nextAnsiEscapeSequence(s string) (int, int) {
 Loop:
 	for ; i < len(s); i++ {
 		switch s[i] {
+		case '\n':
+			// match: `\n`
+			return i, i + 1
 		case '\x08':
 			// backtrack to match: `.\x08`
 			if i > 0 && s[i-1] != '\n' {
@@ -265,13 +268,28 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
 			output.WriteString(prev)
 		}
 
-		newState := interpretCode(str[start:idx], state)
-		if !newState.equals(state) {
+		code := str[start:idx]
+		newState := interpretCode(code, state)
+		if code == "\n" || !newState.equals(state) {
 			if state != nil {
 				// Update last offset
 				(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
 			}
 
+			if code == "\n" {
+				output.WriteRune('\n')
+				runeCount++
+				// Full-background marker
+				if newState.lbg >= 0 {
+					marker := newState
+					marker.attr |= tui.FullBg
+					offsets = append(offsets, ansiOffset{
+						[2]int32{int32(runeCount), int32(runeCount)},
+						marker,
+					})
+				}
+			}
+
 			if newState.colored() {
 				// Append new offset
 				if pstate == nil {
@@ -349,6 +367,13 @@ func parseAnsiCode(s string) (int, string) {
 }
 
 func interpretCode(ansiCode string, prevState *ansiState) ansiState {
+	if ansiCode == "\n" {
+		if prevState != nil {
+			return *prevState
+		}
+		return ansiState{-1, -1, 0, -1, nil}
+	}
+
 	var state ansiState
 	if prevState == nil {
 		state = ansiState{-1, -1, 0, -1, nil}
@@ -375,10 +400,15 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
 		return state
 	}
 
-	if len(ansiCode) <= 3 {
+	reset := func() {
 		state.fg = -1
 		state.bg = -1
 		state.attr = 0
+		state.lbg = -1
+	}
+
+	if len(ansiCode) <= 3 {
+		reset()
 		return state
 	}
 	ansiCode = ansiCode[2 : len(ansiCode)-1]
@@ -432,9 +462,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
 				case 29:
 					state.attr = state.attr &^ tui.StrikeThrough
 				case 0:
-					state.fg = -1
-					state.bg = -1
-					state.attr = 0
+					reset()
 					state256 = 0
 				default:
 					if num >= 30 && num <= 37 {
@@ -474,9 +502,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
 
 	// Empty sequence: reset
 	if count == 0 {
-		state.fg = -1
-		state.bg = -1
-		state.attr = 0
+		reset()
 	}
 
 	if state256 > 0 {
diff --git a/src/core.go b/src/core.go
index 46cd957a..ac42143d 100644
--- a/src/core.go
+++ b/src/core.go
@@ -6,6 +6,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/junegunn/fzf/src/tui"
 	"github.com/junegunn/fzf/src/util"
 )
 
@@ -78,6 +79,16 @@ func Run(opts *Options) (int, error) {
 			prevLineAnsiState = lineAnsiState
 			trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
 			lineAnsiState = newState
+
+			// Full line background is found. Add a special marker.
+			if offsets != nil && newState != nil && len(*offsets) > 0 && newState.lbg >= 0 {
+				marker := (*offsets)[len(*offsets)-1]
+				marker.offset[0] = marker.offset[1]
+				marker.color.bg = newState.lbg
+				marker.color.attr = marker.color.attr | tui.FullBg
+				newOffsets := append(*offsets, marker)
+				offsets = &newOffsets
+			}
 			return util.ToChars(stringBytes(trimmed)), offsets
 		}
 	}
@@ -126,7 +137,17 @@ func Run(opts *Options) (int, error) {
 				return false
 			}
 			item.text, item.colors = ansiProcessor(stringBytes(transformed))
-			item.text.TrimTrailingWhitespaces()
+
+			// We should not trim trailing whitespaces with background colors
+			var maxColorOffset int32
+			if item.colors != nil {
+				for _, ansi := range *item.colors {
+					if ansi.color.bg >= 0 {
+						maxColorOffset = util.Max32(maxColorOffset, ansi.offset[1])
+					}
+				}
+			}
+			item.text.TrimTrailingWhitespaces(int(maxColorOffset))
 			item.text.Index = itemIndex
 			item.origText = &data
 			itemIndex++
diff --git a/src/result.go b/src/result.go
index 3c63e2e4..3dfd58cc 100644
--- a/src/result.go
+++ b/src/result.go
@@ -19,6 +19,10 @@ type colorOffset struct {
 	url    *url
 }
 
+func (co colorOffset) IsFullBgMarker(at int32) bool {
+	return at == co.offset[0] && at == co.offset[1] && co.color.Attr()&tui.FullBg > 0
+}
+
 type Result struct {
 	item   *Item
 	points [4]uint16
@@ -149,12 +153,20 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
 		color bool
 		match bool
 		nth   bool
+		fbg   tui.Color
 	}
 
-	cols := make([]cellInfo, maxCol)
+	cols := make([]cellInfo, maxCol+1)
+	for idx := range cols {
+		cols[idx].fbg = -1
+	}
 	for colorIndex, ansi := range itemColors {
-		for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
-			cols[i] = cellInfo{colorIndex, true, false, false}
+		if ansi.offset[0] == ansi.offset[1] && ansi.color.attr&tui.FullBg > 0 {
+			cols[ansi.offset[0]].fbg = ansi.color.lbg
+		} else {
+			for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
+				cols[i] = cellInfo{colorIndex, true, false, false, cols[i].fbg}
+			}
 		}
 	}
 
@@ -176,7 +188,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
 	// ------------  ----  --  ----
 	//   ++++++++      ++++++++++
 	// --++++++++--  --++++++++++---
-	var curr cellInfo = cellInfo{0, false, false, false}
+	curr := cellInfo{0, false, false, false, -1}
 	start := 0
 	ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
 		if !theme.Colored {
@@ -194,6 +206,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
 	}
 	var colors []colorOffset
 	add := func(idx int) {
+		if curr.fbg >= 0 {
+			colors = append(colors, colorOffset{
+				offset: [2]int32{int32(start), int32(start)},
+				color:  tui.NewColorPair(-1, curr.fbg, tui.FullBg),
+				match:  false,
+				url:    nil})
+		}
 		if (curr.color || curr.nth || curr.match) && idx > start {
 			if curr.match {
 				var color tui.ColorPair
diff --git a/src/terminal.go b/src/terminal.go
index d68415a0..9c627e12 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -3155,11 +3155,13 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
 	}
 
 	maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + t.barCol())
-	postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
+	postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool, lbg tui.ColorPair) {
 		width += extraWidth
-		if (current || selected || alt) && t.highlightLine {
+		if (current || selected || alt) && t.highlightLine || lbg.IsFullBgMarker() {
 			color := tui.ColSelected
-			if current {
+			if lbg.IsFullBgMarker() {
+				color = lbg
+			} else if current {
 				color = tui.ColCurrent
 			} else if alt {
 				color = color.WithBg(altBg)
@@ -3311,7 +3313,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
 	return t.displayWidthWithLimit(runes, 0, max) > max
 }
 
-func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass) int, postTask func(int, int, bool, bool)) int {
+func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass) int, postTask func(int, int, bool, bool, tui.ColorPair)) int {
 	var displayWidth int
 	item := result.item
 	matchOffsets := []Offset{}
@@ -3396,9 +3398,14 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
 		line := lines[lineOffset]
 		finalLineNum = lineNum
 		offsets := []colorOffset{}
-		for _, offset := range allOffsets {
-			if offset.offset[0] >= int32(from+len(line)) {
-				allOffsets = allOffsets[len(offsets):]
+		lbg := tui.NoColorPair()
+		for idx, offset := range allOffsets {
+			lineEnd := int32(from + len(line))
+			if offset.offset[0] >= lineEnd {
+				if offset.IsFullBgMarker(lineEnd) {
+					lbg = offset.color
+				}
+				allOffsets = allOffsets[idx:]
 				break
 			}
 
@@ -3406,19 +3413,26 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
 				continue
 			}
 
-			if offset.offset[1] < int32(from+len(line)) {
+			if offset.offset[1] < lineEnd {
 				offset.offset[0] -= int32(from)
 				offset.offset[1] -= int32(from)
 				offsets = append(offsets, offset)
 			} else {
+				if idx < len(allOffsets)-1 {
+					next := allOffsets[idx+1]
+					if next.IsFullBgMarker(lineEnd) {
+						lbg = next.color
+						idx++
+					}
+				}
 				dupe := offset
-				dupe.offset[0] = int32(from + len(line))
+				dupe.offset[0] = lineEnd
 
 				offset.offset[0] -= int32(from)
-				offset.offset[1] = int32(from + len(line))
+				offset.offset[1] = lineEnd
 				offsets = append(offsets, offset)
 
-				allOffsets = append([]colorOffset{dupe}, allOffsets[len(offsets):]...)
+				allOffsets = append([]colorOffset{dupe}, allOffsets[idx+1:]...)
 				break
 			}
 		}
@@ -3553,7 +3567,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
 			t.printColoredString(t.window, line, offsets, colBase)
 		}
 		if postTask != nil {
-			postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
+			postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw, lbg)
 		} else {
 			t.markOtherLine(actualLineNum)
 		}
diff --git a/src/tui/dummy.go b/src/tui/dummy.go
index 47c7d1e2..a9888036 100644
--- a/src/tui/dummy.go
+++ b/src/tui/dummy.go
@@ -24,6 +24,7 @@ const (
 	AttrRegular   = Attr(1 << 8)
 	AttrClear     = Attr(1 << 9)
 	BoldForce     = Attr(1 << 10)
+	FullBg        = Attr(1 << 11)
 
 	Bold          = Attr(1)
 	Dim           = Attr(1 << 1)
diff --git a/src/tui/tui.go b/src/tui/tui.go
index c8844753..c899ee78 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -273,6 +273,10 @@ func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
 	return ColorPair{fg, bg, attr}
 }
 
+func NoColorPair() ColorPair {
+	return ColorPair{-1, -1, 0}
+}
+
 func (p ColorPair) Fg() Color {
 	return p.fg
 }
@@ -285,6 +289,10 @@ func (p ColorPair) Attr() Attr {
 	return p.attr
 }
 
+func (p ColorPair) IsFullBgMarker() bool {
+	return p.attr&FullBg > 0
+}
+
 func (p ColorPair) HasBg() bool {
 	return p.attr&Reverse == 0 && p.bg != colDefault ||
 		p.attr&Reverse > 0 && p.fg != colDefault
diff --git a/src/util/chars.go b/src/util/chars.go
index e5234303..b1a03fa9 100644
--- a/src/util/chars.go
+++ b/src/util/chars.go
@@ -184,9 +184,10 @@ func (chars *Chars) TrailingWhitespaces() int {
 	return whitespaces
 }
 
-func (chars *Chars) TrimTrailingWhitespaces() {
+func (chars *Chars) TrimTrailingWhitespaces(maxIndex int) {
 	whitespaces := chars.TrailingWhitespaces()
-	chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
+	end := len(chars.slice) - whitespaces
+	chars.slice = chars.slice[0:Max(end, maxIndex)]
 }
 
 func (chars *Chars) TrimSuffix(runes []rune) {


Test patch:
diff --git a/src/ansi_test.go b/src/ansi_test.go
index e3431231..7dfc7bba 100644
--- a/src/ansi_test.go
+++ b/src/ansi_test.go
@@ -22,7 +22,7 @@ import (
 //     (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
 //   - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
 //   - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
-var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
+var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08|\n)")
 
 func testParserReference(t testing.TB, str string) {
 	t.Helper()
diff --git a/test/test_filter.rb b/test/test_filter.rb
index 3e604412..4a520442 100644
--- a/test/test_filter.rb
+++ b/test/test_filter.rb
@@ -304,11 +304,11 @@ class TestFilter < TestBase
   def test_boundary_match
     # Underscore boundaries should be ranked lower
     {
-      default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
-      path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
-      history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
+      default: [' xyz '] + %w[/xyz/ [xyz] -xyz- -xyz_ _xyz- _xyz_],
+      path: ['/xyz/', ' xyz '] + %w[[xyz] -xyz- -xyz_ _xyz- _xyz_],
+      history: ['[xyz]', '-xyz-', ' xyz '] + %w[/xyz/ -xyz_ _xyz- _xyz_]
     }.each do |scheme, expected|
-      result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
+      result = `printf -- 'xxyzx\n-xxyz\nxyzx-\n_xyz_\n_xyz-\n-xyz_\n[xyz]\n-xyz-\n xyz \n/xyz/\n' | #{FZF} -f"'xyz'" --scheme=#{scheme}`.lines(chomp: true)
       assert_equal expected, result
     end
   end