mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 04:02:01 -07:00
34
src/ansi.go
34
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 {
|
||||
|
@@ -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()
|
||||
|
11
src/core.go
11
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
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user