From 8a0bd1f786c979597274f1cc9c8aa93c654fdbd2 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Wed, 2 Jan 2019 20:07:48 -0500 Subject: [PATCH] Working horizontal scrolling --- cmd/micro/action/bindings.go | 4 +- cmd/micro/buffer/cursor.go | 29 +-------- cmd/micro/display/infowindow.go | 3 +- cmd/micro/display/window.go | 106 ++++++++++++++++++++++---------- cmd/micro/util/util.go | 45 +++++++++++++- 5 files changed, 122 insertions(+), 65 deletions(-) diff --git a/cmd/micro/action/bindings.go b/cmd/micro/action/bindings.go index b1e2685b..3ed86dc0 100644 --- a/cmd/micro/action/bindings.go +++ b/cmd/micro/action/bindings.go @@ -376,8 +376,8 @@ func DefaultBindings() map[string]string { "Backspace": "Backspace", "Alt-CtrlH": "DeleteWordLeft", "Alt-Backspace": "DeleteWordLeft", - "Tab": "IndentSelection,InsertTab", - "Backtab": "OutdentSelection,OutdentLine", + "Tab": "InsertTab", + "Backtab": "OutdentLine", "CtrlO": "OpenFile", "CtrlS": "Save", "CtrlF": "Find", diff --git a/cmd/micro/buffer/cursor.go b/cmd/micro/buffer/cursor.go index 0679b9be..aded3ba2 100644 --- a/cmd/micro/buffer/cursor.go +++ b/cmd/micro/buffer/cursor.go @@ -3,7 +3,6 @@ package buffer import ( "unicode/utf8" - runewidth "github.com/mattn/go-runewidth" "github.com/zyedidia/clipboard" "github.com/zyedidia/micro/cmd/micro/util" ) @@ -89,33 +88,7 @@ func (c *Cursor) GetVisualX() int { // 4 visual spaces) func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int { tabsize := int(c.buf.Settings["tabsize"].(float64)) - - // Scan rune by rune until we exceed the visual width that we are - // looking for. Then we can return the character position we have found - i := 0 // char pos - width := 0 // string visual width - for len(b) > 0 { - r, size := utf8.DecodeRune(b) - b = b[size:] - - switch r { - case '\t': - ts := tabsize - (width % tabsize) - width += ts - default: - width += runewidth.RuneWidth(r) - } - - if width >= visualPos { - if width == visualPos { - i++ - } - break - } - i++ - } - - return i + return util.GetCharPosInLine(b, visualPos, tabsize) } // Start moves the cursor to the start of the line it is on diff --git a/cmd/micro/display/infowindow.go b/cmd/micro/display/infowindow.go index cc696f62..499b4401 100644 --- a/cmd/micro/display/infowindow.go +++ b/cmd/micro/display/infowindow.go @@ -103,7 +103,8 @@ func (i *InfoWindow) displayBuffer() { vlocX := utf8.RuneCountInString(i.Msg) tabsize := 4 - line, nColsBeforeStart := util.SliceVisualEnd(line, blocX, tabsize) + line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize) + blocX = bslice draw := func(r rune, style tcell.Style) { if nColsBeforeStart <= 0 { diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index 3bcf6bd2..4243a97f 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -13,9 +13,13 @@ import ( ) type View struct { - X, Y int // X,Y location of the view - Width, Height int // Width and height of the view - StartLine, StartCol int // Start line and start column of the view (vertical/horizontal scroll) + 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) + // 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 } type Window interface { @@ -37,7 +41,8 @@ type BufWindow struct { sline *StatusLine - lineHeight []int + lineHeight []int + gutterOffset int } // NewBufWindow creates a new window at a location in the screen with a width and height @@ -60,6 +65,39 @@ func (v *View) SetView(view *View) { v = view } +func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) { + tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) + width := 0 + bloc := buffer.Loc{0, lineN} + b := w.Buf.LineBytes(lineN) + curStyle := config.DefStyle + var s *tcell.Style + for len(b) > 0 { + r, size := utf8.DecodeRune(b) + + curStyle, found := w.getStyle(curStyle, bloc, r) + if found { + s = &curStyle + } + + w := 0 + switch r { + case '\t': + ts := tabsize - (width % tabsize) + w = ts + default: + w = runewidth.RuneWidth(r) + } + if width+w > n { + return b, n - width, bloc.X, s + } + width += w + b = b[size:] + bloc.X++ + } + return b, n - width, bloc.X, s +} + // Clear resets all cells in this window to the default style func (w *BufWindow) Clear() { for y := 0; y < w.Height; y++ { @@ -117,18 +155,18 @@ func (w *BufWindow) Relocate() bool { ret = true } - // TODO: horizontal scroll - // if !b.Settings["softwrap"].(bool) { - // cx := activeC.GetVisualX() - // if cx < w.StartCol { - // w.StartCol = cx - // ret = true - // } - // if cx+v.lineNumOffset+1 > v.leftCol+v.Width { - // v.leftCol = cx - v.Width + v.lineNumOffset + 1 - // ret = true - // } - // } + // horizontal relocation (scrolling) + if !b.Settings["softwrap"].(bool) { + cx := activeC.GetVisualX() + if cx < w.StartCol { + w.StartCol = cx + ret = true + } + if cx+w.gutterOffset+1 > w.StartCol+w.Width { + w.StartCol = cx - w.Width + w.gutterOffset + 1 + ret = true + } + } return ret } @@ -158,7 +196,7 @@ func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc { vloc := buffer.Loc{X: 0, Y: 0} // this represents the current draw position in the buffer (char positions) - bloc := buffer.Loc{X: w.StartCol, Y: w.StartLine} + bloc := buffer.Loc{X: -1, Y: w.StartLine} for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { vloc.X = 0 @@ -166,12 +204,9 @@ func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc { vloc.X += maxLineNumLength + 1 } - if svloc.X <= vloc.X && vloc.Y == svloc.Y { - return bloc - } - line := b.LineBytes(bloc.Y) - line, nColsBeforeStart := util.SliceVisualEnd(line, bloc.X, tabsize) + line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize) + bloc.X = bslice draw := func() { if nColsBeforeStart <= 0 { @@ -182,7 +217,11 @@ func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc { w.lineHeight[vloc.Y] = bloc.Y - totalwidth := bloc.X - nColsBeforeStart + totalwidth := w.StartCol - nColsBeforeStart + + if svloc.X <= vloc.X && vloc.Y == svloc.Y { + return bloc + } for len(line) > 0 { if vloc.X == svloc.X && vloc.Y == svloc.Y { return bloc @@ -269,12 +308,12 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL // getStyle returns the highlight style for the given character position // If there is no change to the current highlight style it just returns that -func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) tcell.Style { +func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) { if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok { s := config.GetColor(group.String()) - return s + return s, true } - return style + return style, false } func (w *BufWindow) showCursor(x, y int, main bool) { @@ -329,7 +368,7 @@ func (w *BufWindow) displayBuffer() { vloc := buffer.Loc{X: 0, Y: 0} // this represents the current draw position in the buffer (char positions) - bloc := buffer.Loc{X: w.StartCol, Y: w.StartLine} + bloc := buffer.Loc{X: -1, Y: w.StartLine} activeC := b.GetActiveCursor() @@ -344,8 +383,13 @@ func (w *BufWindow) displayBuffer() { w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc) } - line := b.LineBytes(bloc.Y) - line, nColsBeforeStart := util.SliceVisualEnd(line, bloc.X, tabsize) + w.gutterOffset = vloc.X + + line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) + if startStyle != nil { + curStyle = *startStyle + } + bloc.X = bslice draw := func(r rune, style tcell.Style) { if nColsBeforeStart <= 0 { @@ -376,14 +420,14 @@ func (w *BufWindow) displayBuffer() { w.lineHeight[vloc.Y] = bloc.Y - totalwidth := bloc.X - nColsBeforeStart + totalwidth := w.StartCol - nColsBeforeStart for len(line) > 0 { if activeC.X == bloc.X && activeC.Y == bloc.Y { w.showCursor(vloc.X, vloc.Y, true) } r, size := utf8.DecodeRune(line) - curStyle = w.getStyle(curStyle, bloc, r) + curStyle, _ = w.getStyle(curStyle, bloc, r) draw(r, curStyle) diff --git a/cmd/micro/util/util.go b/cmd/micro/util/util.go index 02b5ffd9..4d4d97ab 100644 --- a/cmd/micro/util/util.go +++ b/cmd/micro/util/util.go @@ -54,8 +54,10 @@ func SliceStart(slc []byte, index int) []byte { // SliceVisualEnd will take a byte slice and slice off the start // up to a given visual index. If the index is in the middle of a // rune the number of visual columns into the rune will be returned -func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int) { +// It will also return the char pos of the first character of the slice +func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) { width := 0 + i := 0 for len(b) > 0 { r, size := utf8.DecodeRune(b) @@ -68,12 +70,13 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int) { w = runewidth.RuneWidth(r) } if width+w > n { - return b, n - width + return b, n - width, i } width += w b = b[size:] + i++ } - return b, width + return b, n - width, i } // Abs is a simple absolute value function for ints @@ -87,6 +90,9 @@ func Abs(n int) int { // StringWidth returns the visual width of a byte array indexed from 0 to n (rune index) // with a given tabsize func StringWidth(b []byte, n, tabsize int) int { + if n <= 0 { + return 0 + } i := 0 width := 0 for len(b) > 0 { @@ -263,3 +269,36 @@ func GetLeadingWhitespace(b []byte) []byte { func IntOpt(opt interface{}) int { return int(opt.(float64)) } + +// GetCharPosInLine gets the char position of a visual x y +// coordinate (this is necessary because tabs are 1 char but +// 4 visual spaces) +func GetCharPosInLine(b []byte, visualPos int, tabsize int) int { + + // Scan rune by rune until we exceed the visual width that we are + // looking for. Then we can return the character position we have found + i := 0 // char pos + width := 0 // string visual width + for len(b) > 0 { + r, size := utf8.DecodeRune(b) + b = b[size:] + + switch r { + case '\t': + ts := tabsize - (width % tabsize) + width += ts + default: + width += runewidth.RuneWidth(r) + } + + if width >= visualPos { + if width == visualPos { + i++ + } + break + } + i++ + } + + return i +}