mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-18 23:05:40 -04:00

Softwrap implementation enhanced to fix various issues with scrolling, centering, relocating etc. The main idea is simple: work not with simple line numbers but with (Line, Row) pairs, where Line is a line number in the buffer and Row is a visual line (a row) number within this line. The logic remains mostly the same, but simple arithmetic operations on line numbers are replaced with corresponding operations on (Line, Row) pairs. Fixes #632, #1657
150 lines
3.3 KiB
Go
150 lines
3.3 KiB
Go
package display
|
|
|
|
import (
|
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
|
"github.com/zyedidia/micro/v2/internal/util"
|
|
)
|
|
|
|
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
|
|
// in the buffer. When softwrap is enabled, a buffer line may be displayed as
|
|
// multiple visual lines (rows). So SLoc stores a number of a line in the buffer
|
|
// and a number of a row within this line.
|
|
type SLoc struct {
|
|
Line, Row int
|
|
}
|
|
|
|
// LessThan returns true if s is less b
|
|
func (s SLoc) LessThan(b SLoc) bool {
|
|
if s.Line < b.Line {
|
|
return true
|
|
}
|
|
return s.Line == b.Line && s.Row < b.Row
|
|
}
|
|
|
|
// GreaterThan returns true if s is bigger than b
|
|
func (s SLoc) GreaterThan(b SLoc) bool {
|
|
if s.Line > b.Line {
|
|
return true
|
|
}
|
|
return s.Line == b.Line && s.Row > b.Row
|
|
}
|
|
|
|
type SoftWrap interface {
|
|
Scroll(s SLoc, n int) SLoc
|
|
Diff(s1, s2 SLoc) int
|
|
SLocFromLoc(loc buffer.Loc) SLoc
|
|
}
|
|
|
|
func (w *BufWindow) getRow(loc buffer.Loc) int {
|
|
width := w.Width - w.gutterOffset
|
|
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
|
width--
|
|
}
|
|
if width <= 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 / width
|
|
}
|
|
|
|
func (w *BufWindow) getRowCount(line int) int {
|
|
return w.getRow(buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}) + 1
|
|
}
|
|
|
|
func (w *BufWindow) scrollUp(s SLoc, n int) SLoc {
|
|
for n > 0 {
|
|
if n <= s.Row {
|
|
s.Row -= n
|
|
n = 0
|
|
} else if s.Line > 0 {
|
|
s.Line--
|
|
n -= s.Row + 1
|
|
s.Row = w.getRowCount(s.Line) - 1
|
|
} else {
|
|
s.Row = 0
|
|
break
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (w *BufWindow) scrollDown(s SLoc, n int) SLoc {
|
|
for n > 0 {
|
|
rc := w.getRowCount(s.Line)
|
|
if n < rc-s.Row {
|
|
s.Row += n
|
|
n = 0
|
|
} else if s.Line < w.Buf.LinesNum()-1 {
|
|
s.Line++
|
|
n -= rc - s.Row
|
|
s.Row = 0
|
|
} else {
|
|
s.Row = rc - 1
|
|
break
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (w *BufWindow) scroll(s SLoc, n int) SLoc {
|
|
if n < 0 {
|
|
return w.scrollUp(s, -n)
|
|
}
|
|
return w.scrollDown(s, n)
|
|
}
|
|
|
|
func (w *BufWindow) diff(s1, s2 SLoc) int {
|
|
n := 0
|
|
for s1.LessThan(s2) {
|
|
if s1.Line < s2.Line {
|
|
n += w.getRowCount(s1.Line) - s1.Row
|
|
s1.Line++
|
|
s1.Row = 0
|
|
} else {
|
|
n += s2.Row - s1.Row
|
|
s1.Row = s2.Row
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Scroll returns the location which is n visual lines below the location s
|
|
// i.e. the result of scrolling n lines down. n can be negative,
|
|
// which means scrolling up. The returned location is guaranteed to be
|
|
// within the buffer boundaries.
|
|
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
|
if !w.Buf.Settings["softwrap"].(bool) {
|
|
s.Line += n
|
|
if s.Line < 0 {
|
|
s.Line = 0
|
|
}
|
|
if s.Line > w.Buf.LinesNum()-1 {
|
|
s.Line = w.Buf.LinesNum() - 1
|
|
}
|
|
return s
|
|
}
|
|
return w.scroll(s, n)
|
|
}
|
|
|
|
// Diff returns the difference (the vertical distance) between two SLocs.
|
|
func (w *BufWindow) Diff(s1, s2 SLoc) int {
|
|
if !w.Buf.Settings["softwrap"].(bool) {
|
|
return s2.Line - s1.Line
|
|
}
|
|
if s1.GreaterThan(s2) {
|
|
return -w.diff(s2, s1)
|
|
}
|
|
return w.diff(s1, s2)
|
|
}
|
|
|
|
// SLocFromLoc takes a position in the buffer and returns the location
|
|
// of the visual line containing this position.
|
|
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)}
|
|
}
|