Fix displaying incomplete tab or wide rune at the right edge of window

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
This commit is contained in:
Dmitry Maluka 2021-03-17 20:13:25 +01:00
parent a1651aec2f
commit cd7ab640c5
2 changed files with 114 additions and 35 deletions

View 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()
}
}
}

View 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 {