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
}
+ wordwrap := w.Buf.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
line := w.Buf.LineBytes(loc.Y)
x := 0
- visualx := 0
- row := 0
totalwidth := 0
+ wordwidth := 0
+ wordoffset := 0
+
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
+ line = line[size:]
width := 0
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)
totalwidth += width
}
- // If a wide rune does not fit in the window
- if visualx+width > w.bufWidth && visualx > 0 {
- row++
- visualx = 0
+ wordwidth += width
+
+ // Collect a complete word to know its width.
+ // If wordwrap is off, every single character is a complete "word".
+ if wordwrap {
+ if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
+ if x < loc.X {
+ wordoffset += width
+ x++
+ }
+ continue
+ }
+ }
+
+ // If a word (or just a wide rune) does not fit in the window
+ if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
+ vloc.Row++
+ vloc.VisualX = 0
}
if x == loc.X {
- return row
+ vloc.VisualX += wordoffset
+ return vloc
}
x++
+
+ vloc.VisualX += wordwidth
+
+ wordwidth = 0
+ wordoffset = 0
+
+ if vloc.VisualX >= w.bufWidth {
+ vloc.Row++
+ vloc.VisualX = 0
+ }
+ }
+ return vloc
+}
+
+func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
+ loc := buffer.Loc{X: 0, Y: svloc.Line}
+
+ if w.bufWidth <= 0 {
+ return loc
+ }
+
+ wordwrap := w.Buf.Settings["wordwrap"].(bool)
+ tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
+
+ line := w.Buf.LineBytes(svloc.Line)
+ vloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 0}
+
+ totalwidth := 0
+
+ var widths []int
+ if wordwrap {
+ widths = make([]int, 0, w.bufWidth)
+ } else {
+ widths = make([]int, 0, 1)
+ }
+ wordwidth := 0
+
+ for len(line) > 0 {
+ r, _, size := util.DecodeCharacter(line)
line = line[size:]
- visualx += width
- if visualx >= w.bufWidth {
- row++
- visualx = 0
+ 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
+ }
+
+ widths = append(widths, width)
+ wordwidth += width
+
+ // Collect a complete word to know its width.
+ // If wordwrap is off, every single character is a complete "word".
+ if wordwrap {
+ if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
+ continue
+ }
+ }
+
+ // If a word (or just a wide rune) does not fit in the window
+ if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
+ if vloc.Row == svloc.Row {
+ if wordwrap {
+ // it's a word, not a wide rune
+ loc.X--
+ }
+ return loc
+ }
+ vloc.Row++
+ vloc.VisualX = 0
+ }
+
+ for i := range widths {
+ vloc.VisualX += widths[i]
+ if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
+ return loc
+ }
+ loc.X++
+ }
+
+ widths = widths[:0]
+ wordwidth = 0
+
+ if vloc.VisualX >= w.bufWidth {
+ vloc.Row++
+ vloc.VisualX = 0
}
}
- return row
+ 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 {
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)
}