mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-09 11:53:47 -05: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
|
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
||||||
// calling FindStringIndex() on the below regex (which was originally used):
|
// 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) {
|
func nextAnsiEscapeSequence(s string) (int, int) {
|
||||||
// fast check for ANSI escape sequences
|
// fast check for ANSI escape sequences
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < len(s); i++ {
|
for ; i < len(s); i++ {
|
||||||
switch 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
|
// We ignore the fact that '\x08' cannot be the first char
|
||||||
// in the string and be an escape sequence for the sake of
|
// in the string and be an escape sequence for the sake of
|
||||||
// speed and simplicity.
|
// speed and simplicity.
|
||||||
@@ -174,6 +174,9 @@ func nextAnsiEscapeSequence(s string) (int, int) {
|
|||||||
Loop:
|
Loop:
|
||||||
for ; i < len(s); i++ {
|
for ; i < len(s); i++ {
|
||||||
switch s[i] {
|
switch s[i] {
|
||||||
|
case '\n':
|
||||||
|
// match: `\n`
|
||||||
|
return i, i + 1
|
||||||
case '\x08':
|
case '\x08':
|
||||||
// backtrack to match: `.\x08`
|
// backtrack to match: `.\x08`
|
||||||
if i > 0 && s[i-1] != '\n' {
|
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)
|
output.WriteString(prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := interpretCode(str[start:idx], state)
|
code := str[start:idx]
|
||||||
if !newState.equals(state) {
|
newState := interpretCode(code, state)
|
||||||
|
if code == "\n" || !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
(&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() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
if pstate == nil {
|
if pstate == nil {
|
||||||
@@ -349,6 +366,13 @@ func parseAnsiCode(s string) (int, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
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
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1, nil}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
@@ -435,6 +459,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
state.attr = 0
|
state.attr = 0
|
||||||
|
state.lbg = -1
|
||||||
state256 = 0
|
state256 = 0
|
||||||
default:
|
default:
|
||||||
if num >= 30 && num <= 37 {
|
if num >= 30 && num <= 37 {
|
||||||
@@ -477,6 +502,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
state.attr = 0
|
state.attr = 0
|
||||||
|
state.lbg = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if state256 > 0 {
|
if state256 > 0 {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
||||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.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) {
|
func testParserReference(t testing.TB, str string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|||||||
11
src/core.go
11
src/core.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,6 +79,16 @@ func Run(opts *Options) (int, error) {
|
|||||||
prevLineAnsiState = lineAnsiState
|
prevLineAnsiState = lineAnsiState
|
||||||
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
||||||
lineAnsiState = newState
|
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
|
return util.ToChars(stringBytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ type colorOffset struct {
|
|||||||
url *url
|
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 {
|
type Result struct {
|
||||||
item *Item
|
item *Item
|
||||||
points [4]uint16
|
points [4]uint16
|
||||||
@@ -149,12 +153,20 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
color bool
|
color bool
|
||||||
match bool
|
match bool
|
||||||
nth 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 colorIndex, ansi := range itemColors {
|
||||||
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
if ansi.offset[0] == ansi.offset[1] && ansi.color.attr&tui.FullBg > 0 {
|
||||||
cols[i] = cellInfo{colorIndex, true, false, false}
|
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
|
start := 0
|
||||||
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
||||||
if !theme.Colored {
|
if !theme.Colored {
|
||||||
@@ -194,6 +206,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
}
|
}
|
||||||
var colors []colorOffset
|
var colors []colorOffset
|
||||||
add := func(idx int) {
|
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.color || curr.nth || curr.match) && idx > start {
|
||||||
if curr.match {
|
if curr.match {
|
||||||
var color tui.ColorPair
|
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())
|
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
|
width += extraWidth
|
||||||
if (current || selected || alt) && t.highlightLine {
|
if (current || selected || alt) && t.highlightLine || lbg.IsFullBgMarker() {
|
||||||
color := tui.ColSelected
|
color := tui.ColSelected
|
||||||
if current {
|
if lbg.IsFullBgMarker() {
|
||||||
|
color = lbg
|
||||||
|
} else if current {
|
||||||
color = tui.ColCurrent
|
color = tui.ColCurrent
|
||||||
} else if alt {
|
} else if alt {
|
||||||
color = color.WithBg(altBg)
|
color = color.WithBg(altBg)
|
||||||
@@ -3311,7 +3313,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
|
|||||||
return t.displayWidthWithLimit(runes, 0, max) > max
|
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
|
var displayWidth int
|
||||||
item := result.item
|
item := result.item
|
||||||
matchOffsets := []Offset{}
|
matchOffsets := []Offset{}
|
||||||
@@ -3396,9 +3398,19 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
line := lines[lineOffset]
|
line := lines[lineOffset]
|
||||||
finalLineNum = lineNum
|
finalLineNum = lineNum
|
||||||
offsets := []colorOffset{}
|
offsets := []colorOffset{}
|
||||||
for _, offset := range allOffsets {
|
lbg := tui.NoColorPair()
|
||||||
if offset.offset[0] >= int32(from+len(line)) {
|
var lineLen int
|
||||||
allOffsets = allOffsets[len(offsets):]
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3406,23 +3418,30 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset.offset[1] < int32(from+len(line)) {
|
if offset.offset[1] < lineEnd {
|
||||||
offset.offset[0] -= int32(from)
|
offset.offset[0] -= int32(from)
|
||||||
offset.offset[1] -= int32(from)
|
offset.offset[1] -= int32(from)
|
||||||
offsets = append(offsets, offset)
|
offsets = append(offsets, offset)
|
||||||
} else {
|
} else {
|
||||||
|
if idx < len(allOffsets)-1 {
|
||||||
|
next := allOffsets[idx+1]
|
||||||
|
if next.IsFullBgMarker(lineEnd) {
|
||||||
|
lbg = next.color
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
dupe := offset
|
dupe := offset
|
||||||
dupe.offset[0] = int32(from + len(line))
|
dupe.offset[0] = lineEnd
|
||||||
|
|
||||||
offset.offset[0] -= int32(from)
|
offset.offset[0] -= int32(from)
|
||||||
offset.offset[1] = int32(from + len(line))
|
offset.offset[1] = lineEnd
|
||||||
offsets = append(offsets, offset)
|
offsets = append(offsets, offset)
|
||||||
|
|
||||||
allOffsets = append([]colorOffset{dupe}, allOffsets[len(offsets):]...)
|
allOffsets = append([]colorOffset{dupe}, allOffsets[idx+1:]...)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
from += len(line)
|
from += lineLen
|
||||||
if lineOffset < skipLines {
|
if lineOffset < skipLines {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -3553,7 +3572,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
t.printColoredString(t.window, line, offsets, colBase)
|
t.printColoredString(t.window, line, offsets, colBase)
|
||||||
}
|
}
|
||||||
if postTask != nil {
|
if postTask != nil {
|
||||||
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
|
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw, lbg)
|
||||||
} else {
|
} else {
|
||||||
t.markOtherLine(actualLineNum)
|
t.markOtherLine(actualLineNum)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
AttrRegular = Attr(1 << 8)
|
AttrRegular = Attr(1 << 8)
|
||||||
AttrClear = Attr(1 << 9)
|
AttrClear = Attr(1 << 9)
|
||||||
BoldForce = Attr(1 << 10)
|
BoldForce = Attr(1 << 10)
|
||||||
|
FullBg = Attr(1 << 11)
|
||||||
|
|
||||||
Bold = Attr(1)
|
Bold = Attr(1)
|
||||||
Dim = Attr(1 << 1)
|
Dim = Attr(1 << 1)
|
||||||
|
|||||||
@@ -273,6 +273,10 @@ func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
|
|||||||
return ColorPair{fg, bg, attr}
|
return ColorPair{fg, bg, attr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NoColorPair() ColorPair {
|
||||||
|
return ColorPair{-1, -1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) Fg() Color {
|
func (p ColorPair) Fg() Color {
|
||||||
return p.fg
|
return p.fg
|
||||||
}
|
}
|
||||||
@@ -285,6 +289,10 @@ func (p ColorPair) Attr() Attr {
|
|||||||
return p.attr
|
return p.attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) IsFullBgMarker() bool {
|
||||||
|
return p.attr&FullBg > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) HasBg() bool {
|
func (p ColorPair) HasBg() bool {
|
||||||
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
||||||
p.attr&Reverse > 0 && p.fg != colDefault
|
p.attr&Reverse > 0 && p.fg != colDefault
|
||||||
|
|||||||
Reference in New Issue
Block a user