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