m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 08:13:40 -05:00

Render CR and LF as ␍ and ␊

Close #2529
This commit is contained in:
Junegunn Choi
2023-03-25 10:23:05 +09:00
parent e5103d9429
commit d7daf5f724
6 changed files with 95 additions and 55 deletions

View File

@@ -32,20 +32,26 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true)
r.stderrInternal(str, true, "")
}
// FIXME: Need better handling of non-displayable characters
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
const CR string = "\x1b[2m␍"
const LF string = "\x1b[2m␊"
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
bytes := []byte(str)
runes := []rune{}
for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes)
nlcr := r == '\n' || r == '\r'
if r >= 32 || r == '\x1b' || nlcr {
if r == utf8.RuneError || nlcr && !allowNLCR {
runes = append(runes, ' ')
} else {
if nlcr && !allowNLCR {
if r == '\r' {
runes = append(runes, []rune(CR+resetCode)...)
} else {
runes = append(runes, []rune(LF+resetCode)...)
}
} else if r != utf8.RuneError {
runes = append(runes, r)
}
}
@@ -54,8 +60,10 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
r.queued.WriteString(string(runes))
}
func (r *LightRenderer) csi(code string) {
r.stderr("\x1b[" + code)
func (r *LightRenderer) csi(code string) string {
fullcode := "\x1b[" + code
r.stderr(fullcode)
return fullcode
}
func (r *LightRenderer) flush() {
@@ -825,12 +833,12 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
}
func (w *LightWindow) csi(code string) {
w.renderer.csi(code)
func (w *LightWindow) csi(code string) string {
return w.renderer.csi(code)
}
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
w.renderer.stderrInternal(str, allowNLCR)
func (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {
w.renderer.stderrInternal(str, allowNLCR, resetCode)
}
func (w *LightWindow) Top() int {
@@ -936,10 +944,10 @@ func colorCodes(fg Color, bg Color) []string {
return codes
}
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) (bool, string) {
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0
code := w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0, code
}
func (w *LightWindow) Print(text string) {
@@ -951,16 +959,17 @@ func cleanse(str string) string {
}
func (w *LightWindow) CPrint(pair ColorPair, text string) {
w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false)
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false, code)
w.csi("m")
}
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
if w.csiColor(fg, bg, attr) {
hasColors, code := w.csiColor(fg, bg, attr)
if hasColors {
defer w.csi("m")
}
w.stderrInternal(cleanse(text), false)
w.stderrInternal(cleanse(text), false, code)
}
type wrappedLine struct {
@@ -980,6 +989,8 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
if len(rs) == 1 && rs[0] == '\t' {
w = tabstop - (prefixLength+width)%tabstop
str = repeat(' ', w)
} else if rs[0] == '\r' {
w++
} else {
w = runewidth.StringWidth(str)
}
@@ -998,12 +1009,12 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
return lines
}
func (w *LightWindow) fill(str string, onMove func()) FillReturn {
func (w *LightWindow) fill(str string, resetCode string) FillReturn {
allLines := strings.Split(str, "\n")
for i, line := range allLines {
lines := wrapLine(line, w.posx, w.width, w.tabstop)
for j, wl := range lines {
w.stderrInternal(wl.text, false)
w.stderrInternal(wl.text, false, resetCode)
w.posx += wl.displayWidth
// Wrap line
@@ -1013,7 +1024,7 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
}
w.MoveAndClear(w.posy, w.posx)
w.Move(w.posy+1, 0)
onMove()
w.renderer.stderr(resetCode)
}
}
}
@@ -1022,22 +1033,26 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
return FillSuspend
}
w.Move(w.posy+1, 0)
onMove()
w.renderer.stderr(resetCode)
return FillNextLine
}
return FillContinue
}
func (w *LightWindow) setBg() {
func (w *LightWindow) setBg() string {
if w.bg != colDefault {
w.csiColor(colDefault, w.bg, AttrRegular)
_, code := w.csiColor(colDefault, w.bg, AttrRegular)
return code
}
// Should clear dim attribute after ␍ in the preview window
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
return "\x1b[m"
}
func (w *LightWindow) Fill(text string) FillReturn {
w.Move(w.posy, w.posx)
w.setBg()
return w.fill(text, w.setBg)
code := w.setBg()
return w.fill(text, code)
}
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
@@ -1048,11 +1063,11 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
if bg == colDefault {
bg = w.bg
}
if w.csiColor(fg, bg, attr) {
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
defer w.csi("m")
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
return w.fill(text, resetCode)
}
return w.fill(text, w.setBg)
return w.fill(text, w.setBg())
}
func (w *LightWindow) FinishFill() {

View File

@@ -8,6 +8,7 @@ import (
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
@@ -572,26 +573,27 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
gr := uniseg.NewGraphemes(text)
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 {
r := rs[0]
if r < rune(' ') { // ignore control characters
continue
if r == '\r' {
st = style.Dim(true)
rs[0] = '␍'
} else if r == '\n' {
w.lastY++
lx = 0
continue
} else if r == '\u000D' { // skip carriage return
st = style.Dim(true)
rs[0] = '␊'
} else if r < rune(' ') { // ignore control characters
continue
}
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
}
lx += runewidth.StringWidth(string(rs))
lx += util.StringWidth(string(rs))
}
w.lastX += lx
}
@@ -620,13 +622,22 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Italic(a&Attr(tcell.AttrItalic) != 0)
gr := uniseg.NewGraphemes(text)
Loop:
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 && rs[0] == '\n' {
w.lastY++
w.lastX = 0
lx = 0
continue
if len(rs) == 1 {
r := rs[0]
switch r {
case '\r':
st = style.Dim(true)
rs[0] = '␍'
case '\n':
w.lastY++
w.lastX = 0
lx = 0
continue Loop
}
}
// word wrap:
@@ -643,8 +654,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
return FillSuspend
}
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
lx += runewidth.StringWidth(string(rs))
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
lx += util.StringWidth(string(rs))
}
w.lastX += lx
if w.lastX == w.width {