ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
return buffer.Loc{x, y}
}))
+ ulua.L.SetField(pkg, "SLoc", luar.New(ulua.L, func(line, row int) display.SLoc {
+ return display.SLoc{line, row}
+ }))
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config"
+ "github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
// ScrollUp is not an action
func (h *BufPane) ScrollUp(n int) {
v := h.GetView()
- if v.StartLine >= n {
- v.StartLine -= n
- h.SetView(v)
- } else {
- v.StartLine = 0
- }
+ v.StartLine = h.Scroll(v.StartLine, -n)
+ h.SetView(v)
}
// ScrollDown is not an action
func (h *BufPane) ScrollDown(n int) {
v := h.GetView()
- if v.StartLine <= h.Buf.LinesNum()-1-n {
- v.StartLine += n
- h.SetView(v)
+ v.StartLine = h.Scroll(v.StartLine, n)
+ h.SetView(v)
+}
+
+// If the user has scrolled past the last line, ScrollAdjust can be used
+// to shift the view so that the last line is at the bottom
+func (h *BufPane) ScrollAdjust() {
+ v := h.GetView()
+ end := h.SLocFromLoc(h.Buf.End())
+ if h.Diff(v.StartLine, end) < v.Height-1 {
+ v.StartLine = h.Scroll(end, -v.Height+1)
}
+ h.SetView(v)
}
// MousePress is the event that should happen when a normal click happens
// Center centers the view on the cursor
func (h *BufPane) Center() bool {
v := h.GetView()
- v.StartLine = h.Cursor.Y - v.Height/2
- if v.StartLine+v.Height > h.Buf.LinesNum() {
- v.StartLine = h.Buf.LinesNum() - v.Height
- }
- if v.StartLine < 0 {
- v.StartLine = 0
- }
+ v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -v.Height/2)
h.SetView(v)
- h.Relocate()
+ h.ScrollAdjust()
return true
}
// Start moves the viewport to the start of the buffer
func (h *BufPane) Start() bool {
v := h.GetView()
- v.StartLine = 0
+ v.StartLine = display.SLoc{0, 0}
h.SetView(v)
return true
}
// End moves the viewport to the end of the buffer
func (h *BufPane) End() bool {
- // TODO: softwrap problems?
v := h.GetView()
- if v.Height > h.Buf.LinesNum() {
- v.StartLine = 0
- h.SetView(v)
- } else {
- v.StartLine = h.Buf.LinesNum() - v.Height
- h.SetView(v)
- }
+ v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -v.Height+1)
+ h.SetView(v)
return true
}
// PageUp scrolls the view up a page
func (h *BufPane) PageUp() bool {
v := h.GetView()
- if v.StartLine > v.Height {
- h.ScrollUp(v.Height)
- } else {
- v.StartLine = 0
- }
- h.SetView(v)
+ h.ScrollUp(v.Height)
return true
}
// PageDown scrolls the view down a page
func (h *BufPane) PageDown() bool {
v := h.GetView()
- if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
- h.ScrollDown(v.Height)
- } else if h.Buf.LinesNum() >= v.Height {
- v.StartLine = h.Buf.LinesNum() - v.Height
- }
+ h.ScrollDown(v.Height)
+ h.ScrollAdjust()
return true
}
// HalfPageUp scrolls the view up half a page
func (h *BufPane) HalfPageUp() bool {
v := h.GetView()
- if v.StartLine > v.Height/2 {
- h.ScrollUp(v.Height / 2)
- } else {
- v.StartLine = 0
- }
- h.SetView(v)
+ h.ScrollUp(v.Height / 2)
return true
}
// HalfPageDown scrolls the view down half a page
func (h *BufPane) HalfPageDown() bool {
v := h.GetView()
- if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
- h.ScrollDown(v.Height / 2)
- } else if h.Buf.LinesNum() >= v.Height {
- v.StartLine = h.Buf.LinesNum() - v.Height
- }
- h.SetView(v)
+ h.ScrollDown(v.Height / 2)
+ h.ScrollAdjust()
return true
}
buffer.WriteLog(s)
if LogBufPane != nil {
LogBufPane.CursorEnd()
- v := LogBufPane.GetView()
- endY := buffer.LogBuf.End().Y
-
- if endY > v.StartLine+v.Height {
- v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
- LogBufPane.SetView(v)
- }
}
}
func (h *BufPane) OpenLogBuf() {
LogBufPane = h.HSplitBuf(buffer.LogBuf)
LogBufPane.CursorEnd()
-
- v := LogBufPane.GetView()
- endY := buffer.LogBuf.End().Y
-
- if endY > v.StartLine+v.Height {
- v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
- LogBufPane.SetView(v)
- }
}
}
}
-// Bottomline returns the line number of the lowest line in the view
-// You might think that this is obviously just v.StartLine + v.Height
-// but if softwrap is enabled things get complicated since one buffer
-// line can take up multiple lines in the view
-func (w *BufWindow) Bottomline() int {
- if !w.Buf.Settings["softwrap"].(bool) {
- h := w.StartLine + w.Height - 1
- if w.drawStatus {
- h--
- }
- return h
- }
-
- l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
-
- return l.Y
-}
-
// Relocate moves the view window so that the cursor is in view
// This is useful if the user has scrolled far away, and then starts typing
// Returns true if the window location is moved
func (w *BufWindow) Relocate() bool {
b := w.Buf
- // how many buffer lines are in the view
- height := w.Bottomline() + 1 - w.StartLine
- h := w.Height
+ height := w.Height
if w.drawStatus {
- h--
+ height--
}
ret := false
activeC := w.Buf.GetActiveCursor()
- cy := activeC.Y
scrollmargin := int(b.Settings["scrollmargin"].(float64))
- if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
- w.StartLine = cy - scrollmargin
+
+ c := w.SLocFromLoc(activeC.Loc)
+ bStart := SLoc{0, 0}
+ bEnd := w.SLocFromLoc(b.End())
+
+ if c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {
+ w.StartLine = w.Scroll(c, -scrollmargin)
ret = true
- } else if cy < w.StartLine {
- w.StartLine = cy
+ } else if c.LessThan(w.StartLine) {
+ w.StartLine = c
ret = true
}
- if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
- w.StartLine = cy - height + 1 + scrollmargin
+ if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessThan(w.Scroll(bEnd, -scrollmargin+1)) {
+ w.StartLine = w.Scroll(c, -height+1+scrollmargin)
ret = true
- } else if cy >= b.LinesNum()-scrollmargin && cy >= height {
- w.StartLine = b.LinesNum() - height
+ } else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
+ w.StartLine = w.Scroll(bEnd, -height+1)
ret = true
}
// this represents the current draw position
// within the current window
vloc := buffer.Loc{X: 0, Y: 0}
+ if softwrap {
+ // the start line may be partially out of the current window
+ vloc.Y = -w.StartLine.Row
+ }
// this represents the current draw position in the buffer (char positions)
- bloc := buffer.Loc{X: -1, Y: w.StartLine}
+ bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
- for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
+ for ; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0
if hasMessage {
vloc.X += 2
// this represents the current draw position
// within the current window
vloc := buffer.Loc{X: 0, Y: 0}
+ if softwrap {
+ // the start line may be partially out of the current window
+ vloc.Y = -w.StartLine.Row
+ }
// this represents the current draw position in the buffer (char positions)
- bloc := buffer.Loc{X: -1, Y: w.StartLine}
+ bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
cursors := b.GetCursors()
curStyle := config.DefStyle
- for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
+ for ; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0
currentLine := false
s = curNumStyle
}
- if hasMessage {
- w.drawGutter(&vloc, &bloc)
- }
+ if vloc.Y >= 0 {
+ if hasMessage {
+ w.drawGutter(&vloc, &bloc)
+ }
- if b.Settings["diffgutter"].(bool) {
- w.drawDiffGutter(s, false, &vloc, &bloc)
- }
+ if b.Settings["diffgutter"].(bool) {
+ w.drawDiffGutter(s, false, &vloc, &bloc)
+ }
- if b.Settings["ruler"].(bool) {
- w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
+ if b.Settings["ruler"].(bool) {
+ w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
+ }
+ } else {
+ if hasMessage {
+ vloc.X += 2
+ }
+ if b.Settings["diffgutter"].(bool) {
+ vloc.X++
+ }
+ if b.Settings["ruler"].(bool) {
+ vloc.X += maxLineNumLength + 1
+ }
}
w.gutterOffset = vloc.X
bloc.X = bslice
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
- if nColsBeforeStart <= 0 {
+ if nColsBeforeStart <= 0 && vloc.Y >= 0 {
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
}
}
}
+ }
+ if nColsBeforeStart <= 0 {
vloc.X++
}
nColsBeforeStart--
if barsize < 1 {
barsize = 1
}
- barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
+ barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))
scrollBarStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["scrollbar"]; ok {
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 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) Clear() {
for x := 0; x < i.Width; x++ {
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
--- /dev/null
+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)}
+}
X, Y int // X,Y location of the view
Width, Height int // Width and height of the view
- // Start line and start column of the view (vertical/horizontal scroll)
+ // Start line of the view (for vertical scroll)
+ StartLine SLoc
+
+ // Start column of the view (for horizontal scroll)
// note that since the starting column of every line is different if the view
// is scrolled, StartCol is a visual index (will be the same for every line)
- StartLine, StartCol int
+ StartCol int
}
type Window interface {
type BWindow interface {
Window
+ SoftWrap
SetBuffer(b *buffer.Buffer)
}
- `MTError` error message.
- `Loc(x, y int) Loc`: creates a new location struct.
+ - `SLoc(line, row int) display.SLoc`: creates a new scrolling location struct.
- `BTDefault`: default buffer type.
- `BTLog`: log buffer type.