mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-05 06:30:28 +09:00
Fix softwrap scrolling issues (#1981)
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
This commit is contained in:
@@ -118,6 +118,9 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||
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))
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"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"
|
||||
@@ -19,21 +20,26 @@ import (
|
||||
// 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
|
||||
@@ -111,15 +117,9 @@ func (h *BufPane) ScrollDownAction() bool {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1243,45 +1243,31 @@ func (h *BufPane) JumpLine() bool {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1338,24 +1324,15 @@ func (h *BufPane) CursorPageDown() bool {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,6 @@ func WriteLog(s string) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +30,4 @@ func WriteLog(s string) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,51 +106,35 @@ func (w *BufWindow) Clear() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -199,11 +183,15 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
// 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
|
||||
@@ -473,14 +461,18 @@ func (w *BufWindow) displayBuffer() {
|
||||
// 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
|
||||
@@ -496,16 +488,28 @@ func (w *BufWindow) displayBuffer() {
|
||||
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
|
||||
@@ -517,7 +521,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
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()
|
||||
|
||||
@@ -590,6 +594,8 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
@@ -735,7 +741,7 @@ func (w *BufWindow) displayScrollBar() {
|
||||
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 {
|
||||
|
||||
@@ -72,6 +72,10 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
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())
|
||||
|
||||
149
internal/display/softwrap.go
Normal file
149
internal/display/softwrap.go
Normal file
@@ -0,0 +1,149 @@
|
||||
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)}
|
||||
}
|
||||
@@ -8,10 +8,13 @@ type View struct {
|
||||
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 {
|
||||
@@ -28,5 +31,6 @@ type Window interface {
|
||||
|
||||
type BWindow interface {
|
||||
Window
|
||||
SoftWrap
|
||||
SetBuffer(b *buffer.Buffer)
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ The packages and functions are listed below (in Go type signatures):
|
||||
- `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.
|
||||
|
||||
Reference in New Issue
Block a user