summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-06-24 22:50:02 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-06-25 02:12:10 +0900
commit4811e52af3ed7f89e82baa908f9a8a25fb593361 (patch)
treeda7d88eadf9a0e8044b4748dd790cd599117b663 /src
parent8d81730ec2855d3d32a171f8b675430ffb4c4d59 (diff)
downloadfzf-4811e52af3ed7f89e82baa908f9a8a25fb593361.tar.gz
Support full-line background color in the list section
Close #4432
Diffstat (limited to 'src')
-rw-r--r--src/ansi.go34
-rw-r--r--src/ansi_test.go2
-rw-r--r--src/core.go11
-rw-r--r--src/result.go27
-rw-r--r--src/terminal.go45
-rw-r--r--src/tui/dummy.go1
-rw-r--r--src/tui/tui.go8
7 files changed, 106 insertions, 22 deletions
diff --git a/src/ansi.go b/src/ansi.go
index 79359d34..cbe73c21 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,27 @@ 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')
+ // 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 +366,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}
@@ -435,6 +459,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state.fg = -1
state.bg = -1
state.attr = 0
+ state.lbg = -1
state256 = 0
default:
if num >= 30 && num <= 37 {
@@ -477,6 +502,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state.fg = -1
state.bg = -1
state.attr = 0
+ state.lbg = -1
}
if state256 > 0 {
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/src/core.go b/src/core.go
index 7a762e3c..2fca98fb 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 !opts.ReadZero && 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
}
}
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..f5ded439 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,19 @@ 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()
+ var lineLen int
+ for idx, offset := range allOffsets {
+ lineLen = len(line)
+ if lineLen > 0 && line[lineLen-1] == '\n' {
+ lineLen--
+ }
+ lineEnd := int32(from + lineLen)
+ if offset.offset[0] >= lineEnd {
+ if offset.IsFullBgMarker(lineEnd) {
+ lbg = offset.color
+ }
+ allOffsets = allOffsets[idx:]
break
}
@@ -3406,23 +3418,30 @@ 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
}
}
- from += len(line)
+ from += lineLen
if lineOffset < skipLines {
continue
}
@@ -3553,7 +3572,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