From 7a3d1e6e300b6c5fec650f7a332fb7afdc3b15c5 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Wed, 17 Mar 2021 21:16:52 +0100 Subject: [PATCH] Add VLoc, VLocFromLoc and LocFromVLoc VLoc allows any location in the buffer to be represented as a visual location in the linewrapped buffer. In particular, this is useful for implementing moving cursor up and down within a wrapped line. --- internal/display/infowindow.go | 8 ++- internal/display/softwrap.go | 120 ++++++++++++++++++++++++++++----- 2 files changed, 108 insertions(+), 20 deletions(-) diff --git a/internal/display/infowindow.go b/internal/display/infowindow.go index 530dce67..1892de39 100644 --- a/internal/display/infowindow.go +++ b/internal/display/infowindow.go @@ -75,9 +75,11 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc { func (i *InfoWindow) BufWidth() int { return i.Width } func (i *InfoWindow) BufHeight() int { return 1 } -func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s } -func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 } -func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} } +func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s } +func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 } +func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} } +func (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc { return VLoc{SLoc{0, 0}, loc.X} } +func (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} } func (i *InfoWindow) Clear() { for x := 0; x < i.Width; x++ { diff --git a/internal/display/softwrap.go b/internal/display/softwrap.go index fa0f92c8..2cbfe175 100644 --- a/internal/display/softwrap.go +++ b/internal/display/softwrap.go @@ -30,27 +30,36 @@ func (s SLoc) GreaterThan(b SLoc) bool { return s.Line == b.Line && s.Row > b.Row } +// VLoc represents a location in the buffer as a visual location in the +// linewrapped buffer. +type VLoc struct { + SLoc + VisualX int +} + type SoftWrap interface { Scroll(s SLoc, n int) SLoc Diff(s1, s2 SLoc) int SLocFromLoc(loc buffer.Loc) SLoc + VLocFromLoc(loc buffer.Loc) VLoc + LocFromVLoc(vloc VLoc) buffer.Loc } -func (w *BufWindow) getRow(loc buffer.Loc) int { +func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc { + vloc := VLoc{SLoc: SLoc{loc.Y, 0}, VisualX: 0} + if loc.X <= 0 { - return 0 + return vloc } if w.bufWidth <= 0 { - return 0 + return vloc } 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 { @@ -60,7 +69,7 @@ func (w *BufWindow) getRow(loc buffer.Loc) int { switch r { case '\t': ts := tabsize - (totalwidth % tabsize) - width = util.Min(ts, w.bufWidth-visualx) + width = util.Min(ts, w.bufWidth-vloc.VisualX) totalwidth += ts default: width = runewidth.RuneWidth(r) @@ -68,28 +77,81 @@ func (w *BufWindow) getRow(loc buffer.Loc) int { } // If a wide rune does not fit in the window - if visualx+width > w.bufWidth && visualx > 0 { - row++ - visualx = 0 + if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 { + vloc.Row++ + vloc.VisualX = 0 } if x == loc.X { - return row + return vloc } x++ line = line[size:] - visualx += width - if visualx >= w.bufWidth { - row++ - visualx = 0 + vloc.VisualX += width + if vloc.VisualX >= w.bufWidth { + vloc.Row++ + vloc.VisualX = 0 } } - return row + return vloc +} + +func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc { + loc := buffer.Loc{X: 0, Y: svloc.Line} + + if w.bufWidth <= 0 { + return loc + } + + tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) + + line := w.Buf.LineBytes(svloc.Line) + vloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 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-vloc.VisualX) + totalwidth += ts + default: + width = runewidth.RuneWidth(r) + totalwidth += width + } + + // If a wide rune does not fit in the window + if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 { + if vloc.Row == svloc.Row { + return loc + } + vloc.Row++ + vloc.VisualX = 0 + } + + vloc.VisualX += width + if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX { + return loc + } + loc.X++ + line = line[size:] + + if vloc.VisualX >= w.bufWidth { + vloc.Row++ + vloc.VisualX = 0 + } + } + return loc } func (w *BufWindow) getRowCount(line int) int { - return w.getRow(buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}) + 1 + eol := buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line} + return w.getVLocFromLoc(eol).Row + 1 } func (w *BufWindow) scrollUp(s SLoc, n int) SLoc { @@ -184,5 +246,29 @@ func (w *BufWindow) SLocFromLoc(loc buffer.Loc) SLoc { if !w.Buf.Settings["softwrap"].(bool) { return SLoc{loc.Y, 0} } - return SLoc{loc.Y, w.getRow(loc)} + return w.getVLocFromLoc(loc).SLoc +} + +// VLocFromLoc takes a position in the buffer and returns the corresponding +// visual location in the linewrapped buffer. +func (w *BufWindow) VLocFromLoc(loc buffer.Loc) VLoc { + if !w.Buf.Settings["softwrap"].(bool) { + tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) + + visualx := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, tabsize) + return VLoc{SLoc{loc.Y, 0}, visualx} + } + return w.getVLocFromLoc(loc) +} + +// LocFromVLoc takes a visual location in the linewrapped buffer and returns +// the position in the buffer corresponding to this visual location. +func (w *BufWindow) LocFromVLoc(vloc VLoc) buffer.Loc { + if !w.Buf.Settings["softwrap"].(bool) { + tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) + + x := util.GetCharPosInLine(w.Buf.LineBytes(vloc.Line), vloc.VisualX, tabsize) + return buffer.Loc{x, vloc.Line} + } + return w.getLocFromVLoc(vloc) }