mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-29 22:27:13 +09:00
Add savecursor option
This adds the `savecursor` option which will remember where the cursor was when the file was closed and put it back when the file is opened again. The option is off by default so that people aren't confused as to why the cursor isn't at the start of a file when they open it. This commit also adds a more general ability to serialize a buffer so various components can be saved (which could also be useful for persistent undo). Fixes #107
This commit is contained in:
@@ -46,192 +46,203 @@ func ToCharPos(x, y int, buf *Buffer) int {
|
||||
// is also simpler to use character indicies for other tasks such as
|
||||
// selection.
|
||||
type Cursor struct {
|
||||
Buf *Buffer
|
||||
buf *Buffer
|
||||
|
||||
// The cursor display location
|
||||
x int
|
||||
y int
|
||||
X int
|
||||
Y int
|
||||
|
||||
// Last cursor x position
|
||||
lastVisualX int
|
||||
LastVisualX int
|
||||
|
||||
// The current selection as a range of character numbers (inclusive)
|
||||
curSelection [2]int
|
||||
CurSelection [2]int
|
||||
// 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]int
|
||||
}
|
||||
|
||||
// Clamp makes sure that the cursor is in the bounds of the buffer
|
||||
// It cannot be less than 0 or greater than the buffer length
|
||||
func (c *Cursor) Clamp() {
|
||||
loc := c.Loc()
|
||||
if loc < 0 {
|
||||
c.SetLoc(0)
|
||||
} else if loc > c.buf.Len() {
|
||||
c.SetLoc(c.buf.Len())
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Buf)
|
||||
c.lastVisualX = c.GetVisualX()
|
||||
c.X, c.Y = FromCharPos(loc, c.buf)
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// 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.Buf)
|
||||
return ToCharPos(c.X, c.Y, c.buf)
|
||||
}
|
||||
|
||||
// ResetSelection resets the user's selection
|
||||
func (c *Cursor) ResetSelection() {
|
||||
c.curSelection[0] = 0
|
||||
c.curSelection[1] = 0
|
||||
c.CurSelection[0] = 0
|
||||
c.CurSelection[1] = 0
|
||||
}
|
||||
|
||||
// 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.Buf.Remove(c.curSelection[1], c.curSelection[0])
|
||||
c.SetLoc(c.curSelection[1])
|
||||
if c.CurSelection[0] > c.CurSelection[1] {
|
||||
c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
|
||||
c.SetLoc(c.CurSelection[1])
|
||||
} else if c.GetSelection() == "" {
|
||||
return
|
||||
} else {
|
||||
c.Buf.Remove(c.curSelection[0], c.curSelection[1])
|
||||
c.SetLoc(c.curSelection[0])
|
||||
c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
|
||||
c.SetLoc(c.CurSelection[0])
|
||||
}
|
||||
}
|
||||
|
||||
// GetSelection returns the cursor's selection
|
||||
func (c *Cursor) GetSelection() string {
|
||||
if c.curSelection[0] > c.curSelection[1] {
|
||||
return c.Buf.Substr(c.curSelection[1], c.curSelection[0])
|
||||
if c.CurSelection[0] > c.CurSelection[1] {
|
||||
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])
|
||||
}
|
||||
|
||||
// SelectLine selects the current line
|
||||
func (c *Cursor) SelectLine() {
|
||||
c.Start()
|
||||
c.curSelection[0] = c.Loc()
|
||||
c.CurSelection[0] = c.Loc()
|
||||
c.End()
|
||||
if c.Buf.NumLines-1 > c.y {
|
||||
c.curSelection[1] = c.Loc() + 1
|
||||
if c.buf.NumLines-1 > c.Y {
|
||||
c.CurSelection[1] = c.Loc() + 1
|
||||
} else {
|
||||
c.curSelection[1] = c.Loc()
|
||||
c.CurSelection[1] = 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 loc < c.OrigSelection[0] {
|
||||
c.Start()
|
||||
c.curSelection[0] = c.Loc()
|
||||
c.curSelection[1] = c.origSelection[1]
|
||||
c.CurSelection[0] = c.Loc()
|
||||
c.CurSelection[1] = c.OrigSelection[1]
|
||||
}
|
||||
if loc > c.origSelection[1] {
|
||||
if loc > c.OrigSelection[1] {
|
||||
c.End()
|
||||
c.curSelection[1] = c.Loc()
|
||||
c.curSelection[0] = c.origSelection[0]
|
||||
c.CurSelection[1] = c.Loc()
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
}
|
||||
|
||||
if loc < c.origSelection[1] && loc > c.origSelection[0] {
|
||||
c.curSelection = c.origSelection
|
||||
if loc < c.OrigSelection[1] && loc > c.OrigSelection[0] {
|
||||
c.CurSelection = c.OrigSelection
|
||||
}
|
||||
}
|
||||
|
||||
// SelectWord selects the word the cursor is currently on
|
||||
func (c *Cursor) SelectWord() {
|
||||
if len(c.Buf.Lines[c.y]) == 0 {
|
||||
if len(c.buf.Lines[c.Y]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !IsWordChar(string(c.RuneUnder(c.x))) {
|
||||
if !IsWordChar(string(c.RuneUnder(c.X))) {
|
||||
loc := c.Loc()
|
||||
c.curSelection[0] = loc
|
||||
c.curSelection[1] = loc + 1
|
||||
c.origSelection = c.curSelection
|
||||
c.CurSelection[0] = loc
|
||||
c.CurSelection[1] = loc + 1
|
||||
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.Buf)
|
||||
c.origSelection[0] = c.curSelection[0]
|
||||
c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf)
|
||||
c.OrigSelection[0] = c.CurSelection[0]
|
||||
|
||||
for forward < Count(c.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
for forward < Count(c.buf.Lines[c.Y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.Buf) + 1
|
||||
c.origSelection[1] = c.curSelection[1]
|
||||
c.SetLoc(c.curSelection[1])
|
||||
c.CurSelection[1] = ToCharPos(forward, c.Y, c.buf) + 1
|
||||
c.OrigSelection[1] = c.CurSelection[1]
|
||||
c.SetLoc(c.CurSelection[1])
|
||||
}
|
||||
|
||||
// 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 loc > c.OrigSelection[0] && loc < c.OrigSelection[1] {
|
||||
c.CurSelection = c.OrigSelection
|
||||
return
|
||||
}
|
||||
|
||||
if loc < c.origSelection[0] {
|
||||
backward := c.x
|
||||
if loc < c.OrigSelection[0] {
|
||||
backward := c.X
|
||||
|
||||
for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
|
||||
backward--
|
||||
}
|
||||
|
||||
c.curSelection[0] = ToCharPos(backward, c.y, c.Buf)
|
||||
c.curSelection[1] = c.origSelection[1]
|
||||
c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf)
|
||||
c.CurSelection[1] = c.OrigSelection[1]
|
||||
}
|
||||
|
||||
if loc > c.origSelection[1] {
|
||||
forward := c.x
|
||||
if loc > c.OrigSelection[1] {
|
||||
forward := c.X
|
||||
|
||||
for forward < Count(c.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
for forward < Count(c.buf.Lines[c.Y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.curSelection[1] = ToCharPos(forward, c.y, c.Buf) + 1
|
||||
c.curSelection[0] = c.origSelection[0]
|
||||
c.CurSelection[1] = ToCharPos(forward, c.Y, c.buf) + 1
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
}
|
||||
|
||||
c.SetLoc(c.curSelection[1])
|
||||
c.SetLoc(c.CurSelection[1])
|
||||
}
|
||||
|
||||
// SelectTo selects from the current cursor location to the given location
|
||||
func (c *Cursor) SelectTo(loc int) {
|
||||
if loc > c.origSelection[0] {
|
||||
c.curSelection[0] = c.origSelection[0]
|
||||
c.curSelection[1] = loc
|
||||
if loc > c.OrigSelection[0] {
|
||||
c.CurSelection[0] = c.OrigSelection[0]
|
||||
c.CurSelection[1] = loc
|
||||
} else {
|
||||
c.curSelection[0] = loc
|
||||
c.curSelection[1] = c.origSelection[0]
|
||||
c.CurSelection[0] = loc
|
||||
c.CurSelection[1] = c.OrigSelection[0]
|
||||
}
|
||||
}
|
||||
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (c *Cursor) WordRight() {
|
||||
c.Right()
|
||||
for IsWhitespace(c.RuneUnder(c.x)) {
|
||||
if c.x == Count(c.Buf.Lines[c.y]) {
|
||||
for IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == Count(c.buf.Lines[c.Y]) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
for !IsWhitespace(c.RuneUnder(c.x)) {
|
||||
if c.x == Count(c.Buf.Lines[c.y]) {
|
||||
for !IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == Count(c.buf.Lines[c.Y]) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
@@ -241,14 +252,14 @@ func (c *Cursor) WordRight() {
|
||||
// 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 {
|
||||
for IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == 0 {
|
||||
return
|
||||
}
|
||||
c.Left()
|
||||
}
|
||||
for !IsWhitespace(c.RuneUnder(c.x)) {
|
||||
if c.x == 0 {
|
||||
for !IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == 0 {
|
||||
return
|
||||
}
|
||||
c.Left()
|
||||
@@ -258,7 +269,7 @@ func (c *Cursor) WordLeft() {
|
||||
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := []rune(c.Buf.Lines[c.y])
|
||||
line := []rune(c.buf.Lines[c.Y])
|
||||
if len(line) == 0 {
|
||||
return '\n'
|
||||
}
|
||||
@@ -272,26 +283,26 @@ func (c *Cursor) RuneUnder(x int) rune {
|
||||
|
||||
// Up moves the cursor up one line (if possible)
|
||||
func (c *Cursor) Up() {
|
||||
if c.y > 0 {
|
||||
c.y--
|
||||
if c.Y > 0 {
|
||||
c.Y--
|
||||
|
||||
runes := []rune(c.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.Lines[c.Y])
|
||||
c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Down moves the cursor down one line (if possible)
|
||||
func (c *Cursor) Down() {
|
||||
if c.y < c.Buf.NumLines-1 {
|
||||
c.y++
|
||||
if c.Y < c.buf.NumLines-1 {
|
||||
c.Y++
|
||||
|
||||
runes := []rune(c.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.Lines[c.Y])
|
||||
c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,39 +312,39 @@ func (c *Cursor) Left() {
|
||||
if c.Loc() == 0 {
|
||||
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
|
||||
func (c *Cursor) Right() {
|
||||
if c.Loc() == c.Buf.Len() {
|
||||
if c.Loc() == c.buf.Len() {
|
||||
return
|
||||
}
|
||||
if c.x < Count(c.Buf.Lines[c.y]) {
|
||||
c.x++
|
||||
if c.X < Count(c.buf.Lines[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.Buf.Lines[c.y])
|
||||
c.lastVisualX = c.GetVisualX()
|
||||
c.X = Count(c.buf.Lines[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()
|
||||
}
|
||||
|
||||
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
|
||||
@@ -341,7 +352,7 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
||||
// Get the tab size
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
// This is the visual line -- every \t replaced with the correct number of spaces
|
||||
visualLine := strings.Replace(c.Buf.Lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
|
||||
visualLine := strings.Replace(c.buf.Lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
|
||||
if visualPos > Count(visualLine) {
|
||||
visualPos = Count(visualLine)
|
||||
}
|
||||
@@ -354,23 +365,23 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
||||
|
||||
// GetVisualX returns the x value of the cursor in visual spaces
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
runes := []rune(c.Buf.Lines[c.y])
|
||||
runes := []rune(c.buf.Lines[c.Y])
|
||||
tabSize := int(settings["tabsize"].(float64))
|
||||
return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
|
||||
return c.X + NumOccurences(string(runes[:c.X]), '\t')*(tabSize-1)
|
||||
}
|
||||
|
||||
// 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.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.Lines[c.y]) {
|
||||
c.x = Count(c.Buf.Lines[c.y])
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
} else if c.X > Count(c.buf.Lines[c.Y]) {
|
||||
c.X = Count(c.buf.Lines[c.Y])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user