diff options
| author | Junegunn Choi <junegunn.c@gmail.com> | 2021-05-14 11:43:32 +0900 |
|---|---|---|
| committer | Junegunn Choi <junegunn.c@gmail.com> | 2021-05-14 11:44:44 +0900 |
| commit | 3f75a8369f63f2bd6ac3686fc5d88f2bc128e610 (patch) | |
| tree | fe7b9a042f9356a4169538c0571cfca79f7401bf /src/tui | |
| parent | 4cd621e877cb3a8e44b12ba3a7ce58709862922f (diff) | |
| download | fzf-3f75a8369f63f2bd6ac3686fc5d88f2bc128e610.tar.gz | |
Replace RuneWidth to StringWidth to handle grapheme clusters
Fix #2482
Diffstat (limited to 'src/tui')
| -rw-r--r-- | src/tui/light.go | 31 | ||||
| -rw-r--r-- | src/tui/tcell.go | 82 |
2 files changed, 58 insertions, 55 deletions
diff --git a/src/tui/light.go b/src/tui/light.go index 91b4c18e..d3e3faba 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -10,7 +10,8 @@ import ( "time" "unicode/utf8" - "github.com/junegunn/fzf/src/util" + "github.com/mattn/go-runewidth" + "github.com/rivo/uniseg" "golang.org/x/term" ) @@ -50,7 +51,7 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) { } bytes = bytes[sz:] } - r.queued += string(runes) + r.queued.WriteString(string(runes)) } func (r *LightRenderer) csi(code string) { @@ -58,9 +59,9 @@ func (r *LightRenderer) csi(code string) { } func (r *LightRenderer) flush() { - if len(r.queued) > 0 { - fmt.Fprint(os.Stderr, r.queued) - r.queued = "" + if r.queued.Len() > 0 { + fmt.Fprint(os.Stderr, r.queued.String()) + r.queued.Reset() } } @@ -82,7 +83,7 @@ type LightRenderer struct { escDelay int fullscreen bool upOneLine bool - queued string + queued strings.Builder y int x int maxHeightFunc func(int) int @@ -889,20 +890,26 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin lines := []wrappedLine{} width := 0 line := "" - for _, r := range input { - w := util.RuneWidth(r, prefixLength+width, 8) - width += w - str := string(r) - if r == '\t' { + gr := uniseg.NewGraphemes(input) + for gr.Next() { + rs := gr.Runes() + str := string(rs) + var w int + if len(rs) == 1 && rs[0] == '\t' { + w = tabstop - (prefixLength+width)%tabstop str = repeat(' ', w) + } else { + w = runewidth.StringWidth(str) } + width += w + if prefixLength+width <= max { line += str } else { lines = append(lines, wrappedLine{string(line), width - w}) line = str prefixLength = 0 - width = util.RuneWidth(r, prefixLength, 8) + width = w } } lines = append(lines, wrappedLine{string(line), width}) diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 938c1ba0..859e6709 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -5,7 +5,6 @@ package tui import ( "os" "time" - "unicode/utf8" "runtime" @@ -13,6 +12,7 @@ import ( "github.com/gdamore/tcell/encoding" "github.com/mattn/go-runewidth" + "github.com/rivo/uniseg" ) func HasFullscreenRenderer() bool { @@ -482,7 +482,6 @@ func (w *TcellWindow) Print(text string) { } func (w *TcellWindow) printString(text string, pair ColorPair) { - t := text lx := 0 a := pair.Attr() @@ -496,33 +495,28 @@ func (w *TcellWindow) printString(text string, pair ColorPair) { Dim(a&Attr(tcell.AttrDim) != 0) } - for { - if len(t) == 0 { - break - } - r, size := utf8.DecodeRuneInString(t) - t = t[size:] - - if r < rune(' ') { // ignore control characters - continue - } - - if r == '\n' { - w.lastY++ - lx = 0 - } else { + gr := uniseg.NewGraphemes(text) + for gr.Next() { + rs := gr.Runes() - if r == '\u000D' { // skip carriage return + if len(rs) == 1 { + r := rs[0] + if r < rune(' ') { // ignore control characters + continue + } else if r == '\n' { + w.lastY++ + lx = 0 + continue + } else if r == '\u000D' { // skip carriage return 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, r, nil, style) - } - lx += runewidth.RuneWidth(r) } + 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) + } + lx += runewidth.StringWidth(string(rs)) } w.lastX += lx } @@ -549,30 +543,32 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn { Underline(a&Attr(tcell.AttrUnderline) != 0). Italic(a&Attr(tcell.AttrItalic) != 0) - for _, r := range text { - if r == '\n' { + gr := uniseg.NewGraphemes(text) + for gr.Next() { + rs := gr.Runes() + if len(rs) == 1 && rs[0] == '\n' { w.lastY++ w.lastX = 0 lx = 0 - } else { - var xPos = w.left + w.lastX + lx - - // word wrap: - if xPos >= (w.left + w.width) { - w.lastY++ - w.lastX = 0 - lx = 0 - xPos = w.left - } - var yPos = w.top + w.lastY + continue + } - if yPos >= (w.top + w.height) { - return FillSuspend - } + // word wrap: + xPos := w.left + w.lastX + lx + if xPos >= (w.left + w.width) { + w.lastY++ + w.lastX = 0 + lx = 0 + xPos = w.left + } - _screen.SetContent(xPos, yPos, r, nil, style) - lx += runewidth.RuneWidth(r) + yPos := w.top + w.lastY + if yPos >= (w.top + w.height) { + return FillSuspend } + + _screen.SetContent(xPos, yPos, rs[0], rs[1:], style) + lx += runewidth.StringWidth(string(rs)) } w.lastX += lx if w.lastX == w.width { |
