mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-03 03:10:22 +09:00
Merge branch 'cursor'
This commit is contained in:
142
src/cursor.go
142
src/cursor.go
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
11
src/rope.go
11
src/rope.go
@@ -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 {
|
||||
|
||||
40
src/view.go
40
src/view.go
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user