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:
Zachary Yedidia 2016-05-28 17:29:49 -04:00
parent 96c7bc67c0
commit a92a7dc4e6
11 changed files with 352 additions and 270 deletions

View file

@ -336,7 +336,7 @@ func DefaultBindings() map[string]string {
// CursorUp moves the cursor up
func (v *View) CursorUp() bool {
if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.curSelection[0])
v.Cursor.SetLoc(v.Cursor.CurSelection[0])
v.Cursor.ResetSelection()
}
v.Cursor.Up()
@ -346,7 +346,7 @@ func (v *View) CursorUp() bool {
// CursorDown moves the cursor down
func (v *View) CursorDown() bool {
if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.curSelection[1])
v.Cursor.SetLoc(v.Cursor.CurSelection[1])
v.Cursor.ResetSelection()
}
v.Cursor.Down()
@ -356,7 +356,7 @@ func (v *View) CursorDown() bool {
// CursorLeft moves the cursor left
func (v *View) CursorLeft() bool {
if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.curSelection[0])
v.Cursor.SetLoc(v.Cursor.CurSelection[0])
v.Cursor.ResetSelection()
} else {
v.Cursor.Left()
@ -367,7 +367,7 @@ func (v *View) CursorLeft() bool {
// CursorRight moves the cursor right
func (v *View) CursorRight() bool {
if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.curSelection[1] - 1)
v.Cursor.SetLoc(v.Cursor.CurSelection[1] - 1)
v.Cursor.ResetSelection()
} else {
v.Cursor.Right()
@ -391,7 +391,7 @@ func (v *View) WordLeft() bool {
func (v *View) SelectUp() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.Up()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -402,7 +402,7 @@ func (v *View) SelectUp() bool {
func (v *View) SelectDown() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.Down()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -417,7 +417,7 @@ func (v *View) SelectLeft() bool {
loc = count
}
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.Left()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -432,7 +432,7 @@ func (v *View) SelectRight() bool {
loc = count
}
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.Right()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -443,7 +443,7 @@ func (v *View) SelectRight() bool {
func (v *View) SelectWordRight() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.WordRight()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -454,7 +454,7 @@ func (v *View) SelectWordRight() bool {
func (v *View) SelectWordLeft() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.WordLeft()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -477,7 +477,7 @@ func (v *View) EndOfLine() bool {
func (v *View) SelectToStartOfLine() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.Start()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -488,7 +488,7 @@ func (v *View) SelectToStartOfLine() bool {
func (v *View) SelectToEndOfLine() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.Cursor.End()
v.Cursor.SelectTo(v.Cursor.Loc())
@ -497,8 +497,8 @@ func (v *View) SelectToEndOfLine() bool {
// CursorStart moves the cursor to the start of the buffer
func (v *View) CursorStart() bool {
v.Cursor.x = 0
v.Cursor.y = 0
v.Cursor.X = 0
v.Cursor.Y = 0
return true
}
@ -512,7 +512,7 @@ func (v *View) CursorEnd() bool {
func (v *View) SelectToStart() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.CursorStart()
v.Cursor.SelectTo(0)
@ -523,7 +523,7 @@ func (v *View) SelectToStart() bool {
func (v *View) SelectToEnd() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() {
v.Cursor.origSelection[0] = loc
v.Cursor.OrigSelection[0] = loc
}
v.CursorEnd()
v.Cursor.SelectTo(v.Buf.Len())
@ -550,7 +550,7 @@ func (v *View) InsertEnter() bool {
}
v.Buf.Insert(v.Cursor.Loc(), "\n")
ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y])
ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.Y])
v.Cursor.Right()
if settings["autoindent"].(bool) {
@ -559,7 +559,7 @@ func (v *View) InsertEnter() bool {
v.Cursor.Right()
}
}
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
return true
}
@ -579,25 +579,25 @@ func (v *View) Backspace() bool {
// If the user is using spaces instead of tabs and they are deleting
// whitespace at the start of the line, we should delete as if its a
// tab (tabSize number of spaces)
lineStart := v.Buf.Lines[v.Cursor.y][:v.Cursor.x]
lineStart := v.Buf.Lines[v.Cursor.Y][:v.Cursor.X]
tabSize := int(settings["tabsize"].(float64))
if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
loc := v.Cursor.Loc()
v.Cursor.SetLoc(loc - tabSize)
cx, cy := v.Cursor.x, v.Cursor.y
cx, cy := v.Cursor.X, v.Cursor.Y
v.Cursor.SetLoc(loc)
v.Buf.Remove(loc-tabSize, loc)
v.Cursor.x, v.Cursor.y = cx, cy
v.Cursor.X, v.Cursor.Y = cx, cy
} else {
v.Cursor.Left()
cx, cy := v.Cursor.x, v.Cursor.y
cx, cy := v.Cursor.X, v.Cursor.Y
v.Cursor.Right()
loc := v.Cursor.Loc()
v.Buf.Remove(loc-1, loc)
v.Cursor.x, v.Cursor.y = cx, cy
v.Cursor.X, v.Cursor.Y = cx, cy
}
}
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
return true
}
@ -663,9 +663,9 @@ func (v *View) Save() bool {
// Find opens a prompt and searches forward for the input
func (v *View) Find() bool {
if v.Cursor.HasSelection() {
searchStart = v.Cursor.curSelection[1]
searchStart = v.Cursor.CurSelection[1]
} else {
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
}
BeginSearch()
return true
@ -674,9 +674,9 @@ func (v *View) Find() bool {
// FindNext searches forwards for the last used search term
func (v *View) FindNext() bool {
if v.Cursor.HasSelection() {
searchStart = v.Cursor.curSelection[1]
searchStart = v.Cursor.CurSelection[1]
} else {
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
}
messenger.Message("Finding: " + lastSearch)
Search(lastSearch, v, true)
@ -686,9 +686,9 @@ func (v *View) FindNext() bool {
// FindPrevious searches backwards for the last used search term
func (v *View) FindPrevious() bool {
if v.Cursor.HasSelection() {
searchStart = v.Cursor.curSelection[0]
searchStart = v.Cursor.CurSelection[0]
} else {
searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
}
messenger.Message("Finding: " + lastSearch)
Search(lastSearch, v, false)
@ -759,7 +759,7 @@ func (v *View) Cut() bool {
// DuplicateLine duplicates the current line
func (v *View) DuplicateLine() bool {
v.Cursor.End()
v.Buf.Insert(v.Cursor.Loc(), "\n"+v.Buf.Lines[v.Cursor.y])
v.Buf.Insert(v.Cursor.Loc(), "\n"+v.Buf.Lines[v.Cursor.Y])
v.Cursor.Right()
messenger.Message("Duplicated line")
return true
@ -782,11 +782,11 @@ func (v *View) Paste() bool {
// SelectAll selects the entire buffer
func (v *View) SelectAll() bool {
v.Cursor.curSelection[0] = 0
v.Cursor.curSelection[1] = v.Buf.Len()
v.Cursor.CurSelection[0] = 0
v.Cursor.CurSelection[1] = v.Buf.Len()
// Put the cursor at the beginning
v.Cursor.x = 0
v.Cursor.y = 0
v.Cursor.X = 0
v.Cursor.Y = 0
return true
}
@ -896,8 +896,8 @@ func (v *View) JumpLine() bool {
}
// Move cursor and view if possible.
if lineint < v.Buf.NumLines {
v.Cursor.x = 0
v.Cursor.y = lineint
v.Cursor.X = 0
v.Cursor.Y = lineint
return true
}
messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
@ -954,6 +954,7 @@ func (v *View) Quit() bool {
}
// Make sure not to quit if there are unsaved changes
if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
views[mainView].CloseBuffer()
screen.Fini()
os.Exit(0)
}

View file

@ -1,9 +1,13 @@
package main
import (
"github.com/vinzmay/go-rope"
"encoding/gob"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/vinzmay/go-rope"
)
// Buffer stores the text for files that are loaded into the text editor
@ -48,18 +52,46 @@ func NewBuffer(txt, path string) *Buffer {
b.Path = path
b.Name = path
// Put the cursor at the first spot
b.Cursor = Cursor{
x: 0,
y: 0,
Buf: b,
}
b.EventHandler = NewEventHandler(b)
b.Update()
b.UpdateRules()
if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
os.Mkdir(configDir+"/buffers/", os.ModePerm)
}
if settings["savecursor"].(bool) {
absPath, _ := filepath.Abs(b.Path)
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {
var cursor Cursor
decoder := gob.NewDecoder(file)
err = decoder.Decode(&cursor)
if err != nil {
TermMessage(err.Error())
}
b.Cursor = cursor
b.Cursor.buf = b
b.Cursor.Clamp()
} else {
// Put the cursor at the first spot
b.Cursor = Cursor{
X: 0,
Y: 0,
buf: b,
}
}
file.Close()
} else {
// Put the cursor at the first spot
b.Cursor = Cursor{
X: 0,
Y: 0,
buf: b,
}
}
return b
}
@ -87,6 +119,21 @@ func (b *Buffer) Save() error {
return b.SaveAs(b.Path)
}
// Serialize serializes the buffer to configDir/buffers
func (b *Buffer) Serialize() error {
if settings["savecursor"].(bool) {
absPath, _ := filepath.Abs(b.Path)
file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {
enc := gob.NewEncoder(file)
err = enc.Encode(b.Cursor)
}
file.Close()
return err
}
return nil
}
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func (b *Buffer) SaveAs(filename string) error {
b.UpdateRules()
@ -94,6 +141,7 @@ func (b *Buffer) SaveAs(filename string) error {
err := ioutil.WriteFile(filename, data, 0644)
if err == nil {
b.IsModified = false
err = b.Serialize()
}
return err
}

View file

@ -172,7 +172,7 @@ func HandleCommand(input string) {
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
if canceled {
if view.Cursor.HasSelection() {
view.Cursor.SetLoc(view.Cursor.curSelection[0])
view.Cursor.SetLoc(view.Cursor.CurSelection[0])
view.Cursor.ResetSelection()
}
messenger.Reset()
@ -185,9 +185,9 @@ func HandleCommand(input string) {
messenger.Reset()
} else {
if view.Cursor.HasSelection() {
searchStart = view.Cursor.curSelection[1]
searchStart = view.Cursor.CurSelection[1]
} else {
searchStart = ToCharPos(view.Cursor.x, view.Cursor.y, view.Buf)
searchStart = ToCharPos(view.Cursor.X, view.Cursor.Y, view.Buf)
}
continue
}

View file

@ -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])
}
}

File diff suppressed because one or more lines are too long

View file

@ -119,9 +119,9 @@ func Search(searchStr string, v *View, down bool) {
return
}
v.Cursor.curSelection[0] = charPos + runePos(match[0], str)
v.Cursor.curSelection[1] = charPos + runePos(match[1], str)
v.Cursor.x, v.Cursor.y = FromCharPos(charPos+match[1]-1, v.Buf)
v.Cursor.CurSelection[0] = charPos + runePos(match[0], str)
v.Cursor.CurSelection[1] = charPos + runePos(match[1], str)
v.Cursor.X, v.Cursor.Y = FromCharPos(charPos+match[1]-1, v.Buf)
if v.Relocate() {
v.matches = Match(v)
}

View file

@ -77,6 +77,7 @@ func DefaultSettings() map[string]interface{} {
"ignorecase": false,
"indentchar": " ",
"ruler": true,
"savecursor": false,
"scrollspeed": float64(2),
"scrollmargin": float64(3),
"statusline": true,

View file

@ -33,7 +33,7 @@ func (sline *Statusline) Display() {
// We use GetVisualX() here because otherwise we get the column number in runes
// so a '\t' is only 1, when it should be tabSize
columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1)
lineNum := strconv.Itoa(sline.view.Cursor.y + 1)
lineNum := strconv.Itoa(sline.view.Cursor.Y + 1)
file += " (" + lineNum + "," + columnNum + ")"

View file

@ -1,7 +1,9 @@
package main
import (
"path/filepath"
"strconv"
"strings"
"unicode/utf8"
)
@ -118,6 +120,12 @@ func ParseBool(str string) (bool, error) {
return strconv.ParseBool(str)
}
// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
path = filepath.ToSlash(path)
return strings.Replace(path, "/", "%", -1)
}
func runePos(p int, str string) int {
return utf8.RuneCountInString(str[:p])
}

View file

@ -166,6 +166,7 @@ func (v *View) CanClose(msg string) bool {
// OpenBuffer opens a new buffer in this view.
// This resets the topline, event handler and cursor.
func (v *View) OpenBuffer(buf *Buffer) {
v.CloseBuffer()
v.Buf = buf
v.Cursor = &buf.Cursor
v.Topline = 0
@ -181,6 +182,13 @@ func (v *View) OpenBuffer(buf *Buffer) {
v.lastClickTime = time.Time{}
}
// CloseBuffer performs any closing functions on the buffer
func (v *View) CloseBuffer() {
if v.Buf != nil {
v.Buf.Serialize()
}
}
// ReOpen reloads the current buffer
func (v *View) ReOpen() {
if v.CanClose("Continue? (yes, no, save) ") {
@ -203,7 +211,7 @@ func (v *View) ReOpen() {
// This is useful if the user has scrolled far away, and then starts typing
func (v *View) Relocate() bool {
ret := false
cy := v.Cursor.y
cy := v.Cursor.Y
scrollmargin := int(settings["scrollmargin"].(float64))
if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
v.Topline = cy - scrollmargin
@ -253,9 +261,9 @@ func (v *View) MoveToMouseClick(x, y int) {
if x > Count(v.Buf.Lines[y]) {
x = Count(v.Buf.Lines[y])
}
v.Cursor.x = x
v.Cursor.y = y
v.Cursor.lastVisualX = v.Cursor.GetVisualX()
v.Cursor.X = x
v.Cursor.Y = y
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
}
// HandleEvent handles an event passed by the main loop
@ -345,9 +353,9 @@ func (v *View) HandleEvent(event tcell.Event) {
v.lastClickTime = time.Now()
loc := v.Cursor.Loc()
v.Cursor.origSelection[0] = loc
v.Cursor.curSelection[0] = loc
v.Cursor.curSelection[1] = loc
v.Cursor.OrigSelection[0] = loc
v.Cursor.CurSelection[0] = loc
v.Cursor.CurSelection[1] = loc
}
v.mouseReleased = false
} else if !v.mouseReleased {
@ -357,7 +365,7 @@ func (v *View) HandleEvent(event tcell.Event) {
} else if v.doubleClick {
v.Cursor.AddWordToSelection()
} else {
v.Cursor.curSelection[1] = v.Cursor.Loc()
v.Cursor.CurSelection[1] = v.Cursor.Loc()
}
}
case tcell.ButtonNone:
@ -373,7 +381,7 @@ func (v *View) HandleEvent(event tcell.Event) {
if !v.doubleClick && !v.tripleClick {
v.MoveToMouseClick(x, y)
v.Cursor.curSelection[1] = v.Cursor.Loc()
v.Cursor.CurSelection[1] = v.Cursor.Loc()
}
v.mouseReleased = true
}
@ -487,7 +495,7 @@ func (v *View) DisplayView() {
x++
screen.SetContent(x, lineN, '>', nil, gutterStyle)
x++
if v.Cursor.y == lineN+v.Topline {
if v.Cursor.Y == lineN+v.Topline {
messenger.Message(msg.msg)
messenger.gutterMessage = true
}
@ -499,7 +507,7 @@ func (v *View) DisplayView() {
x++
screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault)
x++
if v.Cursor.y == lineN+v.Topline && messenger.gutterMessage {
if v.Cursor.Y == lineN+v.Topline && messenger.gutterMessage {
messenger.Reset()
messenger.gutterMessage = false
}
@ -542,8 +550,8 @@ func (v *View) DisplayView() {
}
if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] ||
charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) {
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) {
lineStyle = tcell.StyleDefault.Reverse(true)
@ -560,8 +568,8 @@ func (v *View) DisplayView() {
lineIndentStyle = style
}
if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] ||
charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) {
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) {
lineIndentStyle = tcell.StyleDefault.Reverse(true)
@ -591,8 +599,8 @@ func (v *View) DisplayView() {
// The newline may be selected, in which case we should draw the selection style
// with a space to represent it
if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] ||
charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) {
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) {
selectStyle := defStyle.Reverse(true)
@ -609,10 +617,10 @@ func (v *View) DisplayView() {
// DisplayCursor draws the current buffer's cursor to the screen
func (v *View) DisplayCursor() {
// Don't draw the cursor if it is out of the viewport or if it has a selection
if (v.Cursor.y-v.Topline < 0 || v.Cursor.y-v.Topline > v.height-1) || v.Cursor.HasSelection() {
if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.height-1) || v.Cursor.HasSelection() {
screen.HideCursor()
} else {
screen.ShowCursor(v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.y-v.Topline)
screen.ShowCursor(v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline)
}
}

View file

@ -214,6 +214,11 @@ Here are the options that you can set:
default value: `on`
* `savecursor`: remember where the cursor was last time the file was opened and
put it there when you open the file again
default value: `off`
* `scrollmargin`: amount of lines you would like to see above and below the cursor
default value: `3`