diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index 0a9e174d..87bf27a5 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -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() } } } diff --git a/internal/display/softwrap.go b/internal/display/softwrap.go index bbcc99e0..fa0f92c8 100644 --- a/internal/display/softwrap.go +++ b/internal/display/softwrap.go @@ -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 {