X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fcursor.go;h=452268ba12b1f3802623be7fca8c5d33097e81e0;hb=71af765b4e4f368c4bbbcb3947f3497e17271b62;hp=b60f466e9bb7b6798fe7c5b2465f21b5ffa624f5;hpb=77caf5878d72a617b3720bd920394e03acb3f77a;p=micro.git diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go index b60f466e..452268ba 100644 --- a/cmd/micro/cursor.go +++ b/cmd/micro/cursor.go @@ -1,41 +1,9 @@ package main import ( - "strings" + "github.com/zyedidia/clipboard" ) -// FromCharPos converts from a character position to an x, y position -func FromCharPos(loc int, buf *Buffer) (int, int) { - return FromCharPosStart(0, 0, 0, loc, buf) -} - -// FromCharPosStart converts from a character position to an x, y position, starting at the specified character location -func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) { - charNum := startLoc - x, y := startX, startY - - lineLen := Count(buf.lines[y]) + 1 - for charNum+lineLen <= loc { - charNum += lineLen - y++ - lineLen = Count(buf.lines[y]) + 1 - } - x = loc - charNum - - return x, y -} - -// ToCharPos converts from an x, y position to a character position -func ToCharPos(x, y int, buf *Buffer) int { - loc := 0 - for i := 0; i < y; i++ { - // + 1 for the newline - loc += Count(buf.lines[i]) + 1 - } - loc += x - return loc -} - // The Cursor struct stores the location of the cursor in the view // The complicated part about the cursor is storing its location. // The cursor must be displayed at an x, y location, but since the buffer @@ -43,267 +11,390 @@ func ToCharPos(x, y int, buf *Buffer) int { // is also simpler to use character indicies for other tasks such as // selection. type Cursor struct { - v *View - - // The cursor display location - x int - y int + buf *Buffer + Loc // Last cursor x position - lastVisualX int + LastVisualX int // The current selection as a range of character numbers (inclusive) - curSelection [2]int + CurSelection [2]Loc // The original selection as a range of character numbers // This is used for line and word selection where it is necessary // to know what the original selection was - origSelection [2]int + OrigSelection [2]Loc + + // Which cursor index is this (for multiple cursors) + Num int } -// SetLoc sets the location of the cursor in terms of character number -// and not x, y location -// It's just a simple wrapper of FromCharPos -func (c *Cursor) SetLoc(loc int) { - c.x, c.y = FromCharPos(loc, c.v.buf) - c.lastVisualX = c.GetVisualX() +// Goto puts the cursor at the given cursor's location and gives +// the current cursor its selection too +func (c *Cursor) Goto(b Cursor) { + c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX + c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection } -// Loc gets the cursor location in terms of character number instead -// of x, y location -// It's just a simple wrapper of ToCharPos -func (c *Cursor) Loc() int { - return ToCharPos(c.x, c.y, c.v.buf) +// GotoLoc puts the cursor at the given cursor's location and gives +// the current cursor its selection too +func (c *Cursor) GotoLoc(l Loc) { + c.X, c.Y = l.X, l.Y + c.LastVisualX = c.GetVisualX() +} + +// CopySelection copies the user's selection to either "primary" +// or "clipboard" +func (c *Cursor) CopySelection(target string) { + if c.HasSelection() { + if target != "primary" || c.buf.Settings["useprimary"].(bool) { + clipboard.WriteAll(c.GetSelection(), target) + } + } } // ResetSelection resets the user's selection func (c *Cursor) ResetSelection() { - c.curSelection[0] = 0 - c.curSelection[1] = 0 + c.CurSelection[0] = c.buf.Start() + c.CurSelection[1] = c.buf.Start() +} + +// SetSelectionStart sets the start of the selection +func (c *Cursor) SetSelectionStart(pos Loc) { + c.CurSelection[0] = pos +} + +// SetSelectionEnd sets the end of the selection +func (c *Cursor) SetSelectionEnd(pos Loc) { + c.CurSelection[1] = pos } // HasSelection returns whether or not the user has selected anything func (c *Cursor) HasSelection() bool { - return c.curSelection[0] != c.curSelection[1] + return c.CurSelection[0] != c.CurSelection[1] } // DeleteSelection deletes the currently selected text func (c *Cursor) DeleteSelection() { - if c.curSelection[0] > c.curSelection[1] { - c.v.eh.Remove(c.curSelection[1], c.curSelection[0]) - c.SetLoc(c.curSelection[1]) + if c.CurSelection[0].GreaterThan(c.CurSelection[1]) { + c.buf.Remove(c.CurSelection[1], c.CurSelection[0]) + c.Loc = c.CurSelection[1] + } else if !c.HasSelection() { + return } else { - c.v.eh.Remove(c.curSelection[0], c.curSelection[1]) - c.SetLoc(c.curSelection[0]) + c.buf.Remove(c.CurSelection[0], c.CurSelection[1]) + c.Loc = c.CurSelection[0] } } // GetSelection returns the cursor's selection func (c *Cursor) GetSelection() string { - if c.curSelection[0] > c.curSelection[1] { - return string([]rune(c.v.buf.text)[c.curSelection[1]:c.curSelection[0]]) + 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[0], c.CurSelection[1]) } - return string([]rune(c.v.buf.text)[c.curSelection[0]:c.curSelection[1]]) + return "" } // SelectLine selects the current line func (c *Cursor) SelectLine() { c.Start() - c.curSelection[0] = c.Loc() + c.SetSelectionStart(c.Loc) c.End() - c.curSelection[1] = c.Loc() + 1 + if c.buf.NumLines-1 > c.Y { + c.SetSelectionEnd(c.Loc.Move(1, c.buf)) + } else { + c.SetSelectionEnd(c.Loc) + } - c.origSelection = c.curSelection + c.OrigSelection = c.CurSelection } // AddLineToSelection adds the current line to the selection func (c *Cursor) AddLineToSelection() { - loc := c.Loc() - - if loc < c.origSelection[0] { + if c.Loc.LessThan(c.OrigSelection[0]) { c.Start() - c.curSelection[0] = c.Loc() - c.curSelection[1] = c.origSelection[1] + c.SetSelectionStart(c.Loc) + c.SetSelectionEnd(c.OrigSelection[1]) } - if loc > c.origSelection[1] { + if c.Loc.GreaterThan(c.OrigSelection[1]) { c.End() - c.curSelection[1] = c.Loc() - c.curSelection[0] = c.origSelection[0] + c.SetSelectionEnd(c.Loc.Move(1, c.buf)) + c.SetSelectionStart(c.OrigSelection[0]) } - if loc < c.origSelection[1] && loc > c.origSelection[0] { - c.curSelection = c.origSelection + if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) { + c.CurSelection = c.OrigSelection } } // SelectWord selects the word the cursor is currently on func (c *Cursor) SelectWord() { - if len(c.v.buf.lines[c.y]) == 0 { + if len(c.buf.Line(c.Y)) == 0 { return } - if !IsWordChar(string(c.RuneUnder(c.x))) { - loc := c.Loc() - c.curSelection[0] = loc - c.curSelection[1] = loc + 1 - c.origSelection = c.curSelection + if !IsWordChar(string(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 + forward, backward := c.X, c.X for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) { backward-- } - c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf) - c.origSelection[0] = c.curSelection[0] + c.SetSelectionStart(Loc{backward, c.Y}) + c.OrigSelection[0] = c.CurSelection[0] - for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 - c.origSelection[1] = c.curSelection[1] + 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 +// AddWordToSelection adds the word the cursor is currently on +// to the selection func (c *Cursor) AddWordToSelection() { - loc := c.Loc() - - if loc > c.origSelection[0] && loc < c.origSelection[1] { - c.curSelection = c.origSelection + if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) { + c.CurSelection = c.OrigSelection return } - if loc < c.origSelection[0] { - backward := c.x + if c.Loc.LessThan(c.OrigSelection[0]) { + backward := c.X for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) { backward-- } - c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf) - c.curSelection[1] = c.origSelection[1] + c.SetSelectionStart(Loc{backward, c.Y}) + c.SetSelectionEnd(c.OrigSelection[1]) } - if loc > c.origSelection[1] { - forward := c.x + if c.Loc.GreaterThan(c.OrigSelection[1]) { + forward := c.X - for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 - c.curSelection[0] = c.origSelection[0] + 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 IsWhitespace(c.RuneUnder(c.X)) { + if c.X == Count(c.buf.Line(c.Y)) { + c.Right() + return + } + c.Right() + } + c.Right() + for IsWordChar(string(c.RuneUnder(c.X))) { + if c.X == Count(c.buf.Line(c.Y)) { + return + } + c.Right() } } +// WordLeft moves the cursor one word to the left +func (c *Cursor) WordLeft() { + c.Left() + for IsWhitespace(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + c.Left() + for IsWordChar(string(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 := []rune(c.v.buf.lines[c.y]) + line := []rune(c.buf.Line(c.Y)) + if len(line) == 0 { + return '\n' + } if x >= len(line) { - x = len(line) - 1 + return '\n' } else if x < 0 { x = 0 } return line[x] } -// Up moves the cursor up one line (if possible) -func (c *Cursor) Up() { - if c.y > 0 { - c.y-- +// UpN moves the cursor up N lines (if possible) +func (c *Cursor) UpN(amount int) { + proposedY := c.Y - amount + if proposedY < 0 { + proposedY = 0 + c.LastVisualX = 0 + } else if proposedY >= c.buf.NumLines { + proposedY = c.buf.NumLines - 1 + } - runes := []rune(c.v.buf.lines[c.y]) - c.x = c.GetCharPosInLine(c.y, c.lastVisualX) - if c.x > len(runes) { - c.x = len(runes) - } + runes := []rune(c.buf.Line(c.Y)) + c.X = c.GetCharPosInLine(proposedY, c.LastVisualX) + + if c.X > len(runes) || (amount < 0 && proposedY == c.Y) { + c.X = len(runes) } + + c.Y = proposedY +} + +// DownN moves the cursor down N lines (if possible) +func (c *Cursor) DownN(amount int) { + c.UpN(-amount) +} + +// Up moves the cursor up one line (if possible) +func (c *Cursor) Up() { + c.UpN(1) } // Down moves the cursor down one line (if possible) func (c *Cursor) Down() { - if c.y < len(c.v.buf.lines)-1 { - c.y++ - - runes := []rune(c.v.buf.lines[c.y]) - c.x = c.GetCharPosInLine(c.y, c.lastVisualX) - if c.x > len(runes) { - c.x = len(runes) - } - } + c.DownN(1) } -// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning +// 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() == 0 { + if c.Loc == c.buf.Start() { return } - if c.x > 0 { - c.x-- + if c.X > 0 { + c.X-- } else { c.Up() c.End() } - c.lastVisualX = c.GetVisualX() + c.LastVisualX = c.GetVisualX() } -// Right moves the cursor right one cell (if possible) or to the next line if it is at the end +// 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.v.buf.Len() { + if c.Loc == c.buf.End() { return } - if c.x < Count(c.v.buf.lines[c.y]) { - c.x++ + if c.X < Count(c.buf.Line(c.Y)) { + c.X++ } else { c.Down() c.Start() } - c.lastVisualX = c.GetVisualX() + c.LastVisualX = c.GetVisualX() } // End moves the cursor to the end of the line it is on func (c *Cursor) End() { - c.x = Count(c.v.buf.lines[c.y]) - c.lastVisualX = c.GetVisualX() + c.X = Count(c.buf.Line(c.Y)) + c.LastVisualX = c.GetVisualX() } // Start moves the cursor to the start of the line it is on func (c *Cursor) Start() { - c.x = 0 - c.lastVisualX = c.GetVisualX() + c.X = 0 + 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 IsWhitespace(c.RuneUnder(c.X)) { + if c.X == Count(c.buf.Line(c.Y)) { + break + } + c.Right() + } } -// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces) +// GetCharPosInLine gets the char position of a visual x y +// coordinate (this is necessary because tabs are 1 char but +// 4 visual spaces) func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int { // Get the tab size - tabSize := settings.TabSize - // This is the visual line -- every \t replaced with the correct number of spaces - visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1) - if visualPos > Count(visualLine) { - visualPos = Count(visualLine) + tabSize := int(c.buf.Settings["tabsize"].(float64)) + visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize) + if visualPos > visualLineLen { + visualPos = visualLineLen } - numTabs := NumOccurences(visualLine[:visualPos], '\t') - if visualPos >= (tabSize-1)*numTabs { - return visualPos - (tabSize-1)*numTabs + width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize) + if visualPos >= width { + return visualPos - width } return visualPos / tabSize } // GetVisualX returns the x value of the cursor in visual spaces func (c *Cursor) GetVisualX() int { - runes := []rune(c.v.buf.lines[c.y]) - tabSize := settings.TabSize - return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1) + runes := []rune(c.buf.Line(c.Y)) + tabSize := int(c.buf.Settings["tabsize"].(float64)) + if c.X > len(runes) { + c.X = len(runes) - 1 + } + + if c.X < 0 { + c.X = 0 + } + + return StringWidth(string(runes[:c.X]), tabSize) } -// Display draws the cursor to the screen at the correct position -func (c *Cursor) Display() { - // Don't draw the cursor if it is out of the viewport or if it has a selection - if (c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1) || c.HasSelection() { - screen.HideCursor() - } else { - screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline) +// StoreVisualX stores the current visual x value in the cursor +func (c *Cursor) StoreVisualX() { + c.LastVisualX = c.GetVisualX() +} + +// Relocate makes sure that the cursor is inside the bounds +// of the buffer If it isn't, it moves it to be within the +// buffer's lines +func (c *Cursor) Relocate() { + if c.Y < 0 { + c.Y = 0 + } else if c.Y >= c.buf.NumLines { + c.Y = c.buf.NumLines - 1 + } + + if c.X < 0 { + c.X = 0 + } else if c.X > Count(c.buf.Line(c.Y)) { + c.X = Count(c.buf.Line(c.Y)) } }