Merge branch 'cursor'

This commit is contained in:
Zachary Yedidia
2016-03-27 12:27:59 -04:00
4 changed files with 82 additions and 113 deletions

View File

@@ -1,33 +1,67 @@
package main package main
import ( import (
"io/ioutil"
"strconv"
"strings" "strings"
) )
// FromCharPos converts from a character position to an x, y position
func FromCharPos(loc int, buf *Buffer) (int, int) {
charNum := 0
x, y := 0, 0
for charNum+Count(buf.lines[y])+1 <= loc {
charNum += Count(buf.lines[y]) + 1
y++
}
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 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
// uses a rope to store text, to insert text we must have an index. It
// is also simpler to use character indicies for other tasks such as
// selection.
type Cursor struct { type Cursor struct {
v *View v *View
// We need three variables here because we insert text at loc but // The cursor display location
// display the cursor at x, y
x int x int
y int y int
loc int
// Start of the selection in charNum
selectionStart int selectionStart int
// We store the x, y of the start because when the user deletes the selection
// the cursor needs to go back to the start, and this is the simplest way
selectionStartX int
selectionStartY int
// End of the selection in charNum
// We don't need to store the x, y here because when if the user is selecting backwards
// and they delete the selection, the cursor is already in the right place
selectionEnd int selectionEnd 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)
}
// 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)
}
// ResetSelection resets the user's selection // ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() { func (c *Cursor) ResetSelection() {
c.selectionStart = 0 c.selectionStart = 0
@@ -43,12 +77,10 @@ func (c *Cursor) HasSelection() bool {
func (c *Cursor) DeleteSelection() { func (c *Cursor) DeleteSelection() {
if c.selectionStart > c.selectionEnd { if c.selectionStart > c.selectionEnd {
c.v.eh.Remove(c.selectionEnd, c.selectionStart+1) c.v.eh.Remove(c.selectionEnd, c.selectionStart+1)
// Since the cursor is already at the selection start we don't need to move c.SetLoc(c.selectionEnd)
} else { } else {
c.v.eh.Remove(c.selectionStart, c.selectionEnd+1) c.v.eh.Remove(c.selectionStart, c.selectionEnd+1)
c.loc -= c.selectionEnd - c.selectionStart c.SetLoc(c.selectionStart)
c.x = c.selectionStartX
c.y = c.selectionStartY
} }
} }
@@ -72,46 +104,33 @@ func (c *Cursor) RuneUnder() rune {
// Up moves the cursor up one line (if possible) // Up moves the cursor up one line (if possible)
func (c *Cursor) Up() { func (c *Cursor) Up() {
if c.y > 0 { if c.y > 0 {
runes := []rune(c.v.buf.lines[c.y])
c.loc -= len(runes[:c.x])
// Count the newline
c.loc--
c.y-- c.y--
runes = []rune(c.v.buf.lines[c.y])
runes := []rune(c.v.buf.lines[c.y])
if c.x > len(runes) { if c.x > len(runes) {
c.x = len(runes) c.x = len(runes)
} }
c.loc -= len(runes[c.x:])
} }
} }
// Down moves the cursor down one line (if possible) // Down moves the cursor down one line (if possible)
func (c *Cursor) Down() { func (c *Cursor) Down() {
if c.y < len(c.v.buf.lines)-1 { if c.y < len(c.v.buf.lines)-1 {
runes := []rune(c.v.buf.lines[c.y])
c.loc += len(runes[c.x:])
// Count the newline
c.loc++
c.y++ c.y++
runes = []rune(c.v.buf.lines[c.y])
runes := []rune(c.v.buf.lines[c.y])
if c.x > len(runes) { if c.x > len(runes) {
c.x = len(runes) c.x = len(runes)
} }
c.loc += len(runes[:c.x])
} }
} }
// 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 last line if it is at the beginning
func (c *Cursor) Left() { func (c *Cursor) Left() {
if c.loc == 0 { if c.Loc() == 0 {
return return
} }
if c.x > 0 { if c.x > 0 {
c.loc--
c.x-- c.x--
} else { } else {
c.Up() c.Up()
@@ -121,11 +140,10 @@ func (c *Cursor) Left() {
// 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() { func (c *Cursor) Right() {
if c.loc == c.v.buf.Len() { if c.Loc() == c.v.buf.Len() {
return return
} }
if c.x < Count(c.v.buf.lines[c.y]) { if c.x < Count(c.v.buf.lines[c.y]) {
c.loc++
c.x++ c.x++
} else { } else {
c.Down() c.Down()
@@ -135,21 +153,19 @@ func (c *Cursor) Right() {
// End moves the cursor to the end of the line it is on // End moves the cursor to the end of the line it is on
func (c *Cursor) End() { func (c *Cursor) End() {
runes := []rune(c.v.buf.lines[c.y]) c.x = Count(c.v.buf.lines[c.y])
c.loc += len(runes[c.x:])
c.x = len(runes)
} }
// Start moves the cursor to the start of the line it is on // Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() { func (c *Cursor) Start() {
runes := []rune(c.v.buf.lines[c.y])
c.loc -= len(runes[:c.x])
c.x = 0 c.x = 0
} }
// 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 { func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size
tabSize := options["tabsize"].(int) tabSize := options["tabsize"].(int)
// 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) visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1)
if visualPos > Count(visualLine) { if visualPos > Count(visualLine) {
visualPos = Count(visualLine) visualPos = Count(visualLine)
@@ -168,50 +184,6 @@ func (c *Cursor) GetVisualX() int {
return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1) return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
} }
// Distance returns the distance between the cursor and x, y in runes
func (c *Cursor) Distance(x, y int) int {
// Same line
if y == c.y {
return x - c.x
}
var distance int
runes := []rune(c.v.buf.lines[c.y])
if y > c.y {
distance += len(runes[c.x:])
// Newline
distance++
i := 1
for y != c.y+i {
distance += Count(c.v.buf.lines[c.y+i])
// Newline
distance++
i++
}
if x < Count(c.v.buf.lines[y]) {
distance += len([]rune(c.v.buf.lines[y])[:x])
} else {
distance += Count(c.v.buf.lines[y])
}
return distance
}
distance -= len(runes[:c.x])
// Newline
distance--
i := 1
for y != c.y-i {
distance -= Count(c.v.buf.lines[c.y-i])
// Newline
distance--
i++
}
if x >= 0 {
distance -= len([]rune(c.v.buf.lines[y])[x:])
}
return distance
}
// Display draws the cursor to the screen at the correct position // Display draws the cursor to the screen at the correct position
func (c *Cursor) Display() { func (c *Cursor) Display() {
if c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1 { if c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1 {

View File

@@ -239,7 +239,7 @@ func Match(v *View) SyntaxMatches {
} }
str := strings.Join(buf.lines[totalStart:totalEnd], "\n") str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
startNum := v.cursor.loc + v.cursor.Distance(0, totalStart) startNum := ToCharPos(0, totalStart, v.buf)
for _, rule := range rules { for _, rule := range rules {
if rule.startend { if rule.startend {

View File

@@ -5,9 +5,9 @@ import (
) )
const ( const (
// RopeSplitLength defines how large can a string be before it is split into two nodes // RopeSplitLength is the threshold used to split a leaf node into two child nodes.
RopeSplitLength = 1000 RopeSplitLength = 1000000000
// RopeJoinLength defines how short can a string be before it is joined // RopeJoinLength is the threshold used to join two child nodes into one leaf node.
RopeJoinLength = 500 RopeJoinLength = 500
// RopeRebalanceRatio = 1.2 // RopeRebalanceRatio = 1.2
) )
@@ -39,8 +39,9 @@ func (r *Rope) Adjust() {
if !r.valueNil { if !r.valueNil {
if r.len > RopeSplitLength { if r.len > RopeSplitLength {
divide := int(math.Floor(float64(r.len) / 2)) divide := int(math.Floor(float64(r.len) / 2))
r.left = NewRope(r.value[:divide]) runes := []rune(r.value)
r.right = NewRope(r.value[divide:]) r.left = NewRope(string(runes[:divide]))
r.right = NewRope(string(runes[divide:]))
r.valueNil = true r.valueNil = true
} }
} else { } else {

View File

@@ -74,7 +74,6 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.cursor = Cursor{ v.cursor = Cursor{
x: 0, x: 0,
y: 0, y: 0,
loc: 0,
v: v, v: v,
} }
@@ -238,7 +237,7 @@ func (v *View) Paste() {
v.cursor.ResetSelection() v.cursor.ResetSelection()
} }
clip, _ := clipboard.ReadAll() clip, _ := clipboard.ReadAll()
v.eh.Insert(v.cursor.loc, clip) v.eh.Insert(v.cursor.Loc(), clip)
// This is a bit weird... Not sure if there's a better way // This is a bit weird... Not sure if there's a better way
for i := 0; i < Count(clip); i++ { for i := 0; i < Count(clip); i++ {
v.cursor.Right() v.cursor.Right()
@@ -255,7 +254,6 @@ func (v *View) SelectAll() {
// Put the cursor at the beginning // Put the cursor at the beginning
v.cursor.x = 0 v.cursor.x = 0
v.cursor.y = 0 v.cursor.y = 0
v.cursor.loc = 0
} }
// OpenFile opens a new file in the current view // OpenFile opens a new file in the current view
@@ -306,8 +304,6 @@ func (v *View) MoveToMouseClick(x, y int) {
if x > Count(v.buf.lines[y]) { if x > Count(v.buf.lines[y]) {
x = Count(v.buf.lines[y]) x = Count(v.buf.lines[y])
} }
d := v.cursor.Distance(x, y)
v.cursor.loc += d
v.cursor.x = x v.cursor.x = x
v.cursor.y = y v.cursor.y = y
} }
@@ -339,12 +335,12 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.Right() v.cursor.Right()
case tcell.KeyEnter: case tcell.KeyEnter:
// Insert a newline // Insert a newline
v.eh.Insert(v.cursor.loc, "\n") v.eh.Insert(v.cursor.Loc(), "\n")
v.cursor.Right() v.cursor.Right()
v.UpdateLines(v.cursor.y-1, v.cursor.y) v.UpdateLines(v.cursor.y-1, v.cursor.y)
case tcell.KeySpace: case tcell.KeySpace:
// Insert a space // Insert a space
v.eh.Insert(v.cursor.loc, " ") v.eh.Insert(v.cursor.Loc(), " ")
v.cursor.Right() v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y) v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyBackspace2: case tcell.KeyBackspace2:
@@ -354,22 +350,23 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.ResetSelection() v.cursor.ResetSelection()
// Rehighlight the entire buffer // Rehighlight the entire buffer
v.UpdateLines(v.topline, v.topline+v.height) v.UpdateLines(v.topline, v.topline+v.height)
} else if v.cursor.loc > 0 { } else if v.cursor.Loc() > 0 {
// We have to do something a bit hacky here because we want to // We have to do something a bit hacky here because we want to
// delete the line by first moving left and then deleting backwards // delete the line by first moving left and then deleting backwards
// but the undo redo would place the cursor in the wrong place // but the undo redo would place the cursor in the wrong place
// So instead we move left, save the position, move back, delete // So instead we move left, save the position, move back, delete
// and restore the position // and restore the position
v.cursor.Left() v.cursor.Left()
cx, cy, cloc := v.cursor.x, v.cursor.y, v.cursor.loc cx, cy := v.cursor.x, v.cursor.y
v.cursor.Right() v.cursor.Right()
v.eh.Remove(v.cursor.loc-1, v.cursor.loc) loc := v.cursor.Loc()
v.cursor.x, v.cursor.y, v.cursor.loc = cx, cy, cloc v.eh.Remove(loc-1, loc)
v.cursor.x, v.cursor.y = cx, cy
v.UpdateLines(v.cursor.y, v.cursor.y+1) v.UpdateLines(v.cursor.y, v.cursor.y+1)
} }
case tcell.KeyTab: case tcell.KeyTab:
// Insert a tab // Insert a tab
v.eh.Insert(v.cursor.loc, "\t") v.eh.Insert(v.cursor.Loc(), "\t")
v.cursor.Right() v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y) v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyCtrlS: case tcell.KeyCtrlS:
@@ -418,7 +415,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.DeleteSelection() v.cursor.DeleteSelection()
v.cursor.ResetSelection() v.cursor.ResetSelection()
} }
v.eh.Insert(v.cursor.loc, string(e.Rune())) v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
v.cursor.Right() v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y) v.UpdateLines(v.cursor.y, v.cursor.y)
} }
@@ -437,12 +434,11 @@ func (v *View) HandleEvent(event tcell.Event) {
// Left click // Left click
v.MoveToMouseClick(x, y) v.MoveToMouseClick(x, y)
loc := v.cursor.Loc()
if v.mouseReleased { if v.mouseReleased {
v.cursor.selectionStart = v.cursor.loc v.cursor.selectionStart = loc
v.cursor.selectionStartX = v.cursor.x
v.cursor.selectionStartY = v.cursor.y
} }
v.cursor.selectionEnd = v.cursor.loc v.cursor.selectionEnd = loc
v.mouseReleased = false v.mouseReleased = false
case tcell.ButtonNone: case tcell.ButtonNone:
// Mouse event with no click // Mouse event with no click
@@ -455,7 +451,7 @@ func (v *View) HandleEvent(event tcell.Event) {
// events, this still allows the user to make selections, except only after they // events, this still allows the user to make selections, except only after they
// release the mouse // release the mouse
v.MoveToMouseClick(x, y) v.MoveToMouseClick(x, y)
v.cursor.selectionEnd = v.cursor.loc v.cursor.selectionEnd = v.cursor.Loc()
v.mouseReleased = true v.mouseReleased = true
} }
// We don't want to relocate because otherwise the view will be relocated // We don't want to relocate because otherwise the view will be relocated
@@ -486,7 +482,7 @@ func (v *View) DisplayView() {
matches := make(SyntaxMatches, len(v.matches)) matches := make(SyntaxMatches, len(v.matches))
// The character number of the character in the top left of the screen // The character number of the character in the top left of the screen
charNum := v.cursor.loc + v.cursor.Distance(0, v.topline) charNum := ToCharPos(0, v.topline, v.buf)
// Convert the length of buffer to a string, and get the length of the string // Convert the length of buffer to a string, and get the length of the string
// We are going to have to offset by that amount // We are going to have to offset by that amount