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,31 +1,65 @@
package main
import (
"io/ioutil"
"strconv"
"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 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 {
v *View
// We need three variables here because we insert text at loc but
// display the cursor at x, y
x int
y int
loc int
// The cursor display location
x int
y int
// Start of the selection in charNum
selectionStart int
selectionEnd 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
// 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)
}
// 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
// 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
@@ -43,12 +77,10 @@ func (c *Cursor) HasSelection() bool {
func (c *Cursor) DeleteSelection() {
if c.selectionStart > c.selectionEnd {
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 {
c.v.eh.Remove(c.selectionStart, c.selectionEnd+1)
c.loc -= c.selectionEnd - c.selectionStart
c.x = c.selectionStartX
c.y = c.selectionStartY
c.SetLoc(c.selectionStart)
}
}
@@ -72,46 +104,33 @@ func (c *Cursor) RuneUnder() rune {
// Up moves the cursor up one line (if possible)
func (c *Cursor) Up() {
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--
runes = []rune(c.v.buf.lines[c.y])
runes := []rune(c.v.buf.lines[c.y])
if c.x > len(runes) {
c.x = len(runes)
}
c.loc -= len(runes[c.x:])
}
}
// Down moves the cursor down one line (if possible)
func (c *Cursor) Down() {
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++
runes = []rune(c.v.buf.lines[c.y])
runes := []rune(c.v.buf.lines[c.y])
if 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
func (c *Cursor) Left() {
if c.loc == 0 {
if c.Loc() == 0 {
return
}
if c.x > 0 {
c.loc--
c.x--
} else {
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
func (c *Cursor) Right() {
if c.loc == c.v.buf.Len() {
if c.Loc() == c.v.buf.Len() {
return
}
if c.x < Count(c.v.buf.lines[c.y]) {
c.loc++
c.x++
} else {
c.Down()
@@ -135,21 +153,19 @@ func (c *Cursor) Right() {
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
runes := []rune(c.v.buf.lines[c.y])
c.loc += len(runes[c.x:])
c.x = len(runes)
c.x = Count(c.v.buf.lines[c.y])
}
// Start moves the cursor to the start of the line it is on
func (c *Cursor) Start() {
runes := []rune(c.v.buf.lines[c.y])
c.loc -= len(runes[:c.x])
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)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size
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)
if 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)
}
// 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
func (c *Cursor) Display() {
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")
startNum := v.cursor.loc + v.cursor.Distance(0, totalStart)
startNum := ToCharPos(0, totalStart, v.buf)
for _, rule := range rules {
if rule.startend {

View File

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

View File

@@ -72,10 +72,9 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.topline = 0
// Put the cursor at the first spot
v.cursor = Cursor{
x: 0,
y: 0,
loc: 0,
v: v,
x: 0,
y: 0,
v: v,
}
v.eh = NewEventHandler(v)
@@ -238,7 +237,7 @@ func (v *View) Paste() {
v.cursor.ResetSelection()
}
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
for i := 0; i < Count(clip); i++ {
v.cursor.Right()
@@ -255,7 +254,6 @@ func (v *View) SelectAll() {
// Put the cursor at the beginning
v.cursor.x = 0
v.cursor.y = 0
v.cursor.loc = 0
}
// 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]) {
x = Count(v.buf.lines[y])
}
d := v.cursor.Distance(x, y)
v.cursor.loc += d
v.cursor.x = x
v.cursor.y = y
}
@@ -339,12 +335,12 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.Right()
case tcell.KeyEnter:
// Insert a newline
v.eh.Insert(v.cursor.loc, "\n")
v.eh.Insert(v.cursor.Loc(), "\n")
v.cursor.Right()
v.UpdateLines(v.cursor.y-1, v.cursor.y)
case tcell.KeySpace:
// Insert a space
v.eh.Insert(v.cursor.loc, " ")
v.eh.Insert(v.cursor.Loc(), " ")
v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyBackspace2:
@@ -354,22 +350,23 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.ResetSelection()
// Rehighlight the entire buffer
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
// delete the line by first moving left and then deleting backwards
// but the undo redo would place the cursor in the wrong place
// So instead we move left, save the position, move back, delete
// and restore the position
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.eh.Remove(v.cursor.loc-1, v.cursor.loc)
v.cursor.x, v.cursor.y, v.cursor.loc = cx, cy, cloc
loc := v.cursor.Loc()
v.eh.Remove(loc-1, loc)
v.cursor.x, v.cursor.y = cx, cy
v.UpdateLines(v.cursor.y, v.cursor.y+1)
}
case tcell.KeyTab:
// Insert a tab
v.eh.Insert(v.cursor.loc, "\t")
v.eh.Insert(v.cursor.Loc(), "\t")
v.cursor.Right()
v.UpdateLines(v.cursor.y, v.cursor.y)
case tcell.KeyCtrlS:
@@ -418,7 +415,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.cursor.DeleteSelection()
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.UpdateLines(v.cursor.y, v.cursor.y)
}
@@ -437,12 +434,11 @@ func (v *View) HandleEvent(event tcell.Event) {
// Left click
v.MoveToMouseClick(x, y)
loc := v.cursor.Loc()
if v.mouseReleased {
v.cursor.selectionStart = v.cursor.loc
v.cursor.selectionStartX = v.cursor.x
v.cursor.selectionStartY = v.cursor.y
v.cursor.selectionStart = loc
}
v.cursor.selectionEnd = v.cursor.loc
v.cursor.selectionEnd = loc
v.mouseReleased = false
case tcell.ButtonNone:
// 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
// release the mouse
v.MoveToMouseClick(x, y)
v.cursor.selectionEnd = v.cursor.loc
v.cursor.selectionEnd = v.cursor.Loc()
v.mouseReleased = true
}
// 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))
// 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
// We are going to have to offset by that amount