From cd7ab640c5b828dde0ba7b1b0196f5a78cc4c3f6 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Wed, 17 Mar 2021 20:13:25 +0100 Subject: [PATCH] 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 --- internal/display/bufwindow.go | 98 ++++++++++++++++++++++++----------- internal/display/softwrap.go | 51 ++++++++++++++++-- 2 files changed, 114 insertions(+), 35 deletions(-) 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 {