]> git.lizzy.rs Git - micro.git/commitdiff
Fix displaying incomplete tab or wide rune at the right edge of window
authorDmitry Maluka <dmitrymaluka@gmail.com>
Wed, 17 Mar 2021 19:13:25 +0000 (20:13 +0100)
committerDmitry Maluka <dmitrymaluka@gmail.com>
Thu, 8 Apr 2021 21:53:49 +0000 (23:53 +0200)
Fix displaying tabs and wide runes which don't fit in the window.
Don't overwrite the vertical divider and the adjacent window.

- For tabs: display only as many of the tab's spaces as fit in the window.

- For wide runes: if a rune doesn't fit, don't display it in this line at all.
  If softwrap is on, display this rune in the next line.

Fixes #1979

internal/display/bufwindow.go
internal/display/softwrap.go

index 0a9e174df056593165f3ab4f28c97fa95f9fe4ef..87bf27a53edbf22a474670ad4c1f7bc1c46ef15e 100644 (file)
@@ -261,40 +261,54 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
 
                totalwidth := w.StartCol - nColsBeforeStart
 
-               if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
-                       return bloc
-               }
                for len(line) > 0 {
-                       if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
-                               return bloc
-                       }
-
                        r, _, size := util.DecodeCharacter(line)
-                       draw()
+
                        width := 0
 
                        switch r {
                        case '\t':
                                ts := tabsize - (totalwidth % tabsize)
-                               width = ts
+                               width = util.Min(ts, maxWidth-vloc.X)
+                               totalwidth += ts
                        default:
                                width = runewidth.RuneWidth(r)
+                               totalwidth += width
                        }
 
+                       // If a wide rune does not fit in the window
+                       if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
+                               if vloc.Y+w.Y == svloc.Y {
+                                       return bloc
+                               }
+
+                               // We either stop or we wrap to draw the rune in the next line
+                               if !softwrap {
+                                       break
+                               } else {
+                                       vloc.Y++
+                                       if vloc.Y >= w.bufHeight {
+                                               break
+                                       }
+                                       vloc.X = w.gutterOffset
+                               }
+                       }
+
+                       draw()
+
                        // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
                        if width > 1 {
                                for i := 1; i < width; i++ {
-                                       if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
-                                               return bloc
-                                       }
                                        draw()
                                }
                        }
+
+                       if svloc.X < vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
+                               return bloc
+                       }
                        bloc.X++
                        line = line[size:]
 
-                       totalwidth += width
-
                        // If we reach the end of the window then we either stop or we wrap for softwrap
                        if vloc.X >= maxWidth {
                                if !softwrap {
@@ -623,26 +637,61 @@ func (w *BufWindow) displayBuffer() {
                        nColsBeforeStart--
                }
 
+               wrap := func() {
+                       vloc.X = 0
+                       if w.hasMessage {
+                               w.drawGutter(&vloc, &bloc)
+                       }
+                       if b.Settings["diffgutter"].(bool) {
+                               w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
+                       }
+
+                       // This will draw an empty line number because the current line is wrapped
+                       if b.Settings["ruler"].(bool) {
+                               w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
+                       }
+               }
+
                totalwidth := w.StartCol - nColsBeforeStart
                for len(line) > 0 {
                        r, combc, size := util.DecodeCharacter(line)
 
                        curStyle, _ = w.getStyle(curStyle, bloc)
 
-                       draw(r, combc, curStyle, true)
-
                        width := 0
 
                        char := ' '
                        switch r {
                        case '\t':
                                ts := tabsize - (totalwidth % tabsize)
-                               width = ts
+                               width = util.Min(ts, maxWidth-vloc.X)
+                               totalwidth += ts
                        default:
                                width = runewidth.RuneWidth(r)
                                char = '@'
+                               totalwidth += width
                        }
 
+                       // If a wide rune does not fit in the window
+                       if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
+                               for vloc.X < maxWidth {
+                                       draw(' ', nil, config.DefStyle, false)
+                               }
+
+                               // We either stop or we wrap to draw the rune in the next line
+                               if !softwrap {
+                                       break
+                               } else {
+                                       vloc.Y++
+                                       if vloc.Y >= w.bufHeight {
+                                               break
+                                       }
+                                       wrap()
+                               }
+                       }
+
+                       draw(r, combc, curStyle, true)
+
                        // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
                        if width > 1 {
                                for i := 1; i < width; i++ {
@@ -652,8 +701,6 @@ func (w *BufWindow) displayBuffer() {
                        bloc.X++
                        line = line[size:]
 
-                       totalwidth += width
-
                        // If we reach the end of the window then we either stop or we wrap for softwrap
                        if vloc.X >= maxWidth {
                                if !softwrap {
@@ -663,18 +710,7 @@ func (w *BufWindow) displayBuffer() {
                                        if vloc.Y >= w.bufHeight {
                                                break
                                        }
-                                       vloc.X = 0
-                                       if w.hasMessage {
-                                               w.drawGutter(&vloc, &bloc)
-                                       }
-                                       if b.Settings["diffgutter"].(bool) {
-                                               w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
-                                       }
-
-                                       // This will draw an empty line number because the current line is wrapped
-                                       if b.Settings["ruler"].(bool) {
-                                               w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
-                                       }
+                                       wrap()
                                }
                        }
                }
index bbcc99e089bb8d6db3e67ef0a33887d6ab8051fd..fa0f92c844e8b37c80ed48f7f89001b949ed7b47 100644 (file)
@@ -1,6 +1,7 @@
 package display
 
 import (
+       runewidth "github.com/mattn/go-runewidth"
        "github.com/zyedidia/micro/v2/internal/buffer"
        "github.com/zyedidia/micro/v2/internal/util"
 )
@@ -36,13 +37,55 @@ type SoftWrap interface {
 }
 
 func (w *BufWindow) getRow(loc buffer.Loc) int {
+       if loc.X <= 0 {
+               return 0
+       }
+
        if w.bufWidth <= 0 {
                return 0
        }
-       // TODO: this doesn't work quite correctly if there is an incomplete tab
-       // or wide character at the end of a row. See also issue #1979
-       x := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, util.IntOpt(w.Buf.Settings["tabsize"]))
-       return x / w.bufWidth
+
+       tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
+
+       line := w.Buf.LineBytes(loc.Y)
+       x := 0
+       visualx := 0
+       row := 0
+       totalwidth := 0
+
+       for len(line) > 0 {
+               r, _, size := util.DecodeCharacter(line)
+
+               width := 0
+               switch r {
+               case '\t':
+                       ts := tabsize - (totalwidth % tabsize)
+                       width = util.Min(ts, w.bufWidth-visualx)
+                       totalwidth += ts
+               default:
+                       width = runewidth.RuneWidth(r)
+                       totalwidth += width
+               }
+
+               // If a wide rune does not fit in the window
+               if visualx+width > w.bufWidth && visualx > 0 {
+                       row++
+                       visualx = 0
+               }
+
+               if x == loc.X {
+                       return row
+               }
+               x++
+               line = line[size:]
+
+               visualx += width
+               if visualx >= w.bufWidth {
+                       row++
+                       visualx = 0
+               }
+       }
+       return row
 }
 
 func (w *BufWindow) getRowCount(line int) int {