diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2023-03-25 10:23:05 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2023-03-25 10:41:19 +0900 |
commit | d7daf5f72411f29eca3ab398c7a8a951ca230cad (patch) | |
tree | 4ac325a32da748f3ecf447972726cafdf502a0bb /src/tui | |
parent | e5103d94290eb9d65807a9f1ecee673bd9ce7cc2 (diff) |
Render CR and LF as ␍ and ␊
Close #2529
Diffstat (limited to 'src/tui')
-rw-r--r-- | src/tui/light.go | 75 | ||||
-rw-r--r-- | src/tui/tcell.go | 41 |
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 { |