]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/buffer/cursor.go
Add some comments
[micro.git] / cmd / micro / buffer / cursor.go
index 34741ad37c0f34a8094a1250da474c99413d67cb..0679b9be026516ccb8c6fb3372b6f835f913d912 100644 (file)
@@ -20,7 +20,7 @@ func InBounds(pos Loc, buf *Buffer) bool {
 // The Cursor struct stores the location of the cursor in the buffer
 // as well as the selection
 type Cursor struct {
-       Buf *Buffer
+       buf *Buffer
        Loc
 
        // Last cursor x position
@@ -37,6 +37,23 @@ type Cursor struct {
        Num int
 }
 
+func NewCursor(b *Buffer, l Loc) *Cursor {
+       c := &Cursor{
+               buf: b,
+               Loc: l,
+       }
+       c.StoreVisualX()
+       return c
+}
+
+func (c *Cursor) SetBuf(b *Buffer) {
+       c.buf = b
+}
+
+func (c *Cursor) Buf() *Buffer {
+       return c.buf
+}
+
 // Goto puts the cursor at the given cursor's location and gives
 // the current cursor its selection too
 func (c *Cursor) Goto(b Cursor) {
@@ -48,7 +65,7 @@ func (c *Cursor) Goto(b Cursor) {
 // the current cursor its selection too
 func (c *Cursor) GotoLoc(l Loc) {
        c.X, c.Y = l.X, l.Y
-       c.LastVisualX = c.GetVisualX()
+       c.StoreVisualX()
 }
 
 // GetVisualX returns the x value of the cursor in visual spaces
@@ -58,8 +75,8 @@ func (c *Cursor) GetVisualX() int {
                return 0
        }
 
-       bytes := c.Buf.LineBytes(c.Y)
-       tabsize := int(c.Buf.Settings["tabsize"].(float64))
+       bytes := c.buf.LineBytes(c.Y)
+       tabsize := int(c.buf.Settings["tabsize"].(float64))
        if c.X > utf8.RuneCount(bytes) {
                c.X = utf8.RuneCount(bytes) - 1
        }
@@ -71,7 +88,7 @@ func (c *Cursor) GetVisualX() int {
 // coordinate (this is necessary because tabs are 1 char but
 // 4 visual spaces)
 func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
-       tabsize := int(c.Buf.Settings["tabsize"].(float64))
+       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
@@ -89,11 +106,13 @@ func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
                        width += runewidth.RuneWidth(r)
                }
 
-               i++
-
                if width >= visualPos {
+                       if width == visualPos {
+                               i++
+                       }
                        break
                }
+               i++
        }
 
        return i
@@ -105,9 +124,21 @@ func (c *Cursor) Start() {
        c.LastVisualX = c.GetVisualX()
 }
 
+// StartOfText moves the cursor to the first non-whitespace rune of
+// the line it is on
+func (c *Cursor) StartOfText() {
+       c.Start()
+       for util.IsWhitespace(c.RuneUnder(c.X)) {
+               if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
+                       break
+               }
+               c.Right()
+       }
+}
+
 // End moves the cursor to the end of the line it is on
 func (c *Cursor) End() {
-       c.X = utf8.RuneCount(c.Buf.LineBytes(c.Y))
+       c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
        c.LastVisualX = c.GetVisualX()
 }
 
@@ -115,7 +146,7 @@ func (c *Cursor) End() {
 // or "clipboard"
 func (c *Cursor) CopySelection(target string) {
        if c.HasSelection() {
-               if target != "primary" || c.Buf.Settings["useprimary"].(bool) {
+               if target != "primary" || c.buf.Settings["useprimary"].(bool) {
                        clipboard.WriteAll(string(c.GetSelection()), target)
                }
        }
@@ -123,8 +154,8 @@ func (c *Cursor) CopySelection(target string) {
 
 // ResetSelection resets the user's selection
 func (c *Cursor) ResetSelection() {
-       c.CurSelection[0] = c.Buf.Start()
-       c.CurSelection[1] = c.Buf.Start()
+       c.CurSelection[0] = c.buf.Start()
+       c.CurSelection[1] = c.buf.Start()
 }
 
 // SetSelectionStart sets the start of the selection
@@ -145,23 +176,38 @@ func (c *Cursor) HasSelection() bool {
 // DeleteSelection deletes the currently selected text
 func (c *Cursor) DeleteSelection() {
        if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
-               c.Buf.Remove(c.CurSelection[1], c.CurSelection[0])
+               c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
                c.Loc = c.CurSelection[1]
        } else if !c.HasSelection() {
                return
        } else {
-               c.Buf.Remove(c.CurSelection[0], c.CurSelection[1])
+               c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
                c.Loc = c.CurSelection[0]
        }
 }
 
+// Deselect closes the cursor's current selection
+// Start indicates whether the cursor should be placed
+// at the start or end of the selection
+func (c *Cursor) Deselect(start bool) {
+       if c.HasSelection() {
+               if start {
+                       c.Loc = c.CurSelection[0]
+               } else {
+                       c.Loc = c.CurSelection[1]
+               }
+               c.ResetSelection()
+               c.StoreVisualX()
+       }
+}
+
 // GetSelection returns the cursor's selection
 func (c *Cursor) GetSelection() []byte {
-       if InBounds(c.CurSelection[0], c.Buf) && InBounds(c.CurSelection[1], c.Buf) {
+       if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
                if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
-                       return c.Buf.Substr(c.CurSelection[1], c.CurSelection[0])
+                       return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
                }
-               return c.Buf.Substr(c.CurSelection[0], c.CurSelection[1])
+               return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
        }
        return []byte{}
 }
@@ -171,8 +217,8 @@ func (c *Cursor) SelectLine() {
        c.Start()
        c.SetSelectionStart(c.Loc)
        c.End()
-       if len(c.Buf.lines)-1 > c.Y {
-               c.SetSelectionEnd(c.Loc.Move(1, c.Buf))
+       if len(c.buf.lines)-1 > c.Y {
+               c.SetSelectionEnd(c.Loc.Move(1, c.buf))
        } else {
                c.SetSelectionEnd(c.Loc)
        }
@@ -189,7 +235,7 @@ func (c *Cursor) AddLineToSelection() {
        }
        if c.Loc.GreaterThan(c.OrigSelection[1]) {
                c.End()
-               c.SetSelectionEnd(c.Loc.Move(1, c.Buf))
+               c.SetSelectionEnd(c.Loc.Move(1, c.buf))
                c.SetSelectionStart(c.OrigSelection[0])
        }
 
@@ -203,11 +249,11 @@ func (c *Cursor) UpN(amount int) {
        proposedY := c.Y - amount
        if proposedY < 0 {
                proposedY = 0
-       } else if proposedY >= len(c.Buf.lines) {
-               proposedY = len(c.Buf.lines) - 1
+       } else if proposedY >= len(c.buf.lines) {
+               proposedY = len(c.buf.lines) - 1
        }
 
-       bytes := c.Buf.LineBytes(proposedY)
+       bytes := c.buf.LineBytes(proposedY)
        c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
 
        if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
@@ -235,7 +281,7 @@ func (c *Cursor) Down() {
 // Left moves the cursor left one cell (if possible) or to
 // the previous line if it is at the beginning
 func (c *Cursor) Left() {
-       if c.Loc == c.Buf.Start() {
+       if c.Loc == c.buf.Start() {
                return
        }
        if c.X > 0 {
@@ -244,22 +290,22 @@ func (c *Cursor) Left() {
                c.Up()
                c.End()
        }
-       c.LastVisualX = c.GetVisualX()
+       c.StoreVisualX()
 }
 
 // Right moves the cursor right one cell (if possible) or
 // to the next line if it is at the end
 func (c *Cursor) Right() {
-       if c.Loc == c.Buf.End() {
+       if c.Loc == c.buf.End() {
                return
        }
-       if c.X < utf8.RuneCount(c.Buf.LineBytes(c.Y)) {
+       if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
                c.X++
        } else {
                c.Down()
                c.Start()
        }
-       c.LastVisualX = c.GetVisualX()
+       c.StoreVisualX()
 }
 
 // Relocate makes sure that the cursor is inside the bounds
@@ -268,13 +314,154 @@ func (c *Cursor) Right() {
 func (c *Cursor) Relocate() {
        if c.Y < 0 {
                c.Y = 0
-       } else if c.Y >= len(c.Buf.lines) {
-               c.Y = len(c.Buf.lines) - 1
+       } else if c.Y >= len(c.buf.lines) {
+               c.Y = len(c.buf.lines) - 1
        }
 
        if c.X < 0 {
                c.X = 0
-       } else if c.X > utf8.RuneCount(c.Buf.LineBytes(c.Y)) {
-               c.X = utf8.RuneCount(c.Buf.LineBytes(c.Y))
+       } else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
+               c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
+       }
+}
+
+// SelectWord selects the word the cursor is currently on
+func (c *Cursor) SelectWord() {
+       if len(c.buf.LineBytes(c.Y)) == 0 {
+               return
+       }
+
+       if !util.IsWordChar(c.RuneUnder(c.X)) {
+               c.SetSelectionStart(c.Loc)
+               c.SetSelectionEnd(c.Loc.Move(1, c.buf))
+               c.OrigSelection = c.CurSelection
+               return
+       }
+
+       forward, backward := c.X, c.X
+
+       for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {
+               backward--
+       }
+
+       c.SetSelectionStart(Loc{backward, c.Y})
+       c.OrigSelection[0] = c.CurSelection[0]
+
+       lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
+       for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
+               forward++
+       }
+
+       c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
+       c.OrigSelection[1] = c.CurSelection[1]
+       c.Loc = c.CurSelection[1]
+}
+
+// AddWordToSelection adds the word the cursor is currently on
+// to the selection
+func (c *Cursor) AddWordToSelection() {
+       if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
+               c.CurSelection = c.OrigSelection
+               return
        }
+
+       if c.Loc.LessThan(c.OrigSelection[0]) {
+               backward := c.X
+
+               for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {
+                       backward--
+               }
+
+               c.SetSelectionStart(Loc{backward, c.Y})
+               c.SetSelectionEnd(c.OrigSelection[1])
+       }
+
+       if c.Loc.GreaterThan(c.OrigSelection[1]) {
+               forward := c.X
+
+               lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
+               for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
+                       forward++
+               }
+
+               c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
+               c.SetSelectionStart(c.OrigSelection[0])
+       }
+
+       c.Loc = c.CurSelection[1]
+}
+
+// SelectTo selects from the current cursor location to the given
+// location
+func (c *Cursor) SelectTo(loc Loc) {
+       if loc.GreaterThan(c.OrigSelection[0]) {
+               c.SetSelectionStart(c.OrigSelection[0])
+               c.SetSelectionEnd(loc)
+       } else {
+               c.SetSelectionStart(loc)
+               c.SetSelectionEnd(c.OrigSelection[0])
+       }
+}
+
+// WordRight moves the cursor one word to the right
+func (c *Cursor) WordRight() {
+       for util.IsWhitespace(c.RuneUnder(c.X)) {
+               if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
+                       c.Right()
+                       return
+               }
+               c.Right()
+       }
+       c.Right()
+       for util.IsWordChar(c.RuneUnder(c.X)) {
+               if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
+                       return
+               }
+               c.Right()
+       }
+}
+
+// WordLeft moves the cursor one word to the left
+func (c *Cursor) WordLeft() {
+       c.Left()
+       for util.IsWhitespace(c.RuneUnder(c.X)) {
+               if c.X == 0 {
+                       return
+               }
+               c.Left()
+       }
+       c.Left()
+       for util.IsWordChar(c.RuneUnder(c.X)) {
+               if c.X == 0 {
+                       return
+               }
+               c.Left()
+       }
+       c.Right()
+}
+
+// RuneUnder returns the rune under the given x position
+func (c *Cursor) RuneUnder(x int) rune {
+       line := c.buf.LineBytes(c.Y)
+       if len(line) == 0 || x >= utf8.RuneCount(line) {
+               return '\n'
+       } else if x < 0 {
+               x = 0
+       }
+       i := 0
+       for len(line) > 0 {
+               r, size := utf8.DecodeRune(line)
+               line = line[size:]
+
+               if i == x {
+                       return r
+               }
+
+               i++
+       }
+       return '\n'
+}
+
+func (c *Cursor) StoreVisualX() {
+       c.LastVisualX = c.GetVisualX()
 }