mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-15 21:37:09 +09:00
Merge branch 'cursor'
This commit is contained in:
142
src/cursor.go
142
src/cursor.go
@@ -1,31 +1,65 @@
|
|||||||
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
|
||||||
|
selectionEnd int
|
||||||
|
}
|
||||||
|
|
||||||
// We store the x, y of the start because when the user deletes the selection
|
// SetLoc sets the location of the cursor in terms of character number
|
||||||
// the cursor needs to go back to the start, and this is the simplest way
|
// and not x, y location
|
||||||
selectionStartX int
|
// It's just a simple wrapper of FromCharPos
|
||||||
selectionStartY int
|
func (c *Cursor) SetLoc(loc int) {
|
||||||
|
c.x, c.y = FromCharPos(loc, c.v.buf)
|
||||||
|
}
|
||||||
|
|
||||||
// End of the selection in charNum
|
// Loc gets the cursor location in terms of character number instead
|
||||||
// We don't need to store the x, y here because when if the user is selecting backwards
|
// of x, y location
|
||||||
// and they delete the selection, the cursor is already in the right place
|
// It's just a simple wrapper of ToCharPos
|
||||||
selectionEnd int
|
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
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
11
src/rope.go
11
src/rope.go
@@ -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 {
|
||||||
|
|||||||
40
src/view.go
40
src/view.go
@@ -72,10 +72,9 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
|
|||||||
v.topline = 0
|
v.topline = 0
|
||||||
// Put the cursor at the first spot
|
// Put the cursor at the first spot
|
||||||
v.cursor = Cursor{
|
v.cursor = Cursor{
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
loc: 0,
|
v: v,
|
||||||
v: v,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v.eh = NewEventHandler(v)
|
v.eh = NewEventHandler(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
|
||||||
|
|||||||
Reference in New Issue
Block a user