summaryrefslogtreecommitdiffstats
path: root/src/tui
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2023-03-25 10:23:05 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2023-03-25 10:41:19 +0900
commitd7daf5f72411f29eca3ab398c7a8a951ca230cad (patch)
tree4ac325a32da748f3ecf447972726cafdf502a0bb /src/tui
parente5103d94290eb9d65807a9f1ecee673bd9ce7cc2 (diff)
Render CR and LF as ␍ and ␊
Close #2529
Diffstat (limited to 'src/tui')
-rw-r--r--src/tui/light.go75
-rw-r--r--src/tui/tcell.go41
2 files changed, 71 insertions, 45 deletions
diff --git a/src/tui/light.go b/src/tui/light.go
index 322584dd..411238d4 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -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() {
diff --git a/src/tui/tcell.go b/src/tui/tcell.go
index ad0182cf..b2cab0fb 100644
--- a/src/tui/tcell.go
+++ b/src/tui/tcell.go
@@ -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 {