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 totalwidth := w.StartCol - nColsBeforeStart
if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
for len(line) > 0 { for len(line) > 0 {
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
r, _, size := util.DecodeCharacter(line) r, _, size := util.DecodeCharacter(line)
draw()
width := 0 width := 0
switch r { switch r {
case '\t': case '\t':
ts := tabsize - (totalwidth % tabsize) ts := tabsize - (totalwidth % tabsize)
width = ts width = util.Min(ts, maxWidth-vloc.X)
totalwidth += ts
default: default:
width = runewidth.RuneWidth(r) 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 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 { if width > 1 {
for i := 1; i < width; i++ { for i := 1; i < width; i++ {
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
draw() draw()
} }
} }
if svloc.X < vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
return bloc
}
bloc.X++ bloc.X++
line = line[size:] line = line[size:]
totalwidth += width
// If we reach the end of the window then we either stop or we wrap for softwrap // If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= maxWidth { if vloc.X >= maxWidth {
if !softwrap { if !softwrap {
@@ -623,46 +637,7 @@ func (w *BufWindow) displayBuffer() {
nColsBeforeStart-- nColsBeforeStart--
} }
totalwidth := w.StartCol - nColsBeforeStart wrap := func() {
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
default:
width = runewidth.RuneWidth(r)
char = '@'
}
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, nil, curStyle, false)
}
}
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 {
break
} else {
vloc.Y++
if vloc.Y >= w.bufHeight {
break
}
vloc.X = 0 vloc.X = 0
if w.hasMessage { if w.hasMessage {
w.drawGutter(&vloc, &bloc) w.drawGutter(&vloc, &bloc)
@@ -676,6 +651,67 @@ func (w *BufWindow) displayBuffer() {
w.drawLineNum(lineNumStyle, true, &vloc, &bloc) 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)
width := 0
char := ' '
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
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++ {
draw(char, nil, curStyle, false)
}
}
bloc.X++
line = line[size:]
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= maxWidth {
if !softwrap {
break
} else {
vloc.Y++
if vloc.Y >= w.bufHeight {
break
}
wrap()
}
} }
} }

View File

@@ -1,6 +1,7 @@
package display package display
import ( import (
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
) )
@@ -36,13 +37,55 @@ type SoftWrap interface {
} }
func (w *BufWindow) getRow(loc buffer.Loc) int { func (w *BufWindow) getRow(loc buffer.Loc) int {
if loc.X <= 0 {
return 0
}
if w.bufWidth <= 0 { if w.bufWidth <= 0 {
return 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 tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
x := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, util.IntOpt(w.Buf.Settings["tabsize"]))
return x / w.bufWidth 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 { func (w *BufWindow) getRowCount(line int) int {