Store cursor and eventhandler in buffer instead of view

This is better design because the cursor and eventhandler are things the
buffer should know about directly but the view shouldn't. This should
make it easier to add persistent undo or location saving between
sessions (see #107).
This commit is contained in:
Zachary Yedidia
2016-05-22 15:01:02 -04:00
parent 6eeda5d21f
commit df684ec505
6 changed files with 92 additions and 87 deletions

View File

@@ -506,7 +506,7 @@ func (v *View) InsertSpace() bool {
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
v.eh.Insert(v.Cursor.Loc(), " ")
v.Buf.Insert(v.Cursor.Loc(), " ")
v.Cursor.Right()
return true
}
@@ -519,12 +519,12 @@ func (v *View) InsertEnter() bool {
v.Cursor.ResetSelection()
}
v.eh.Insert(v.Cursor.Loc(), "\n")
v.Buf.Insert(v.Cursor.Loc(), "\n")
ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y])
v.Cursor.Right()
if settings["autoindent"].(bool) {
v.eh.Insert(v.Cursor.Loc(), ws)
v.Buf.Insert(v.Cursor.Loc(), ws)
for i := 0; i < len(ws); i++ {
v.Cursor.Right()
}
@@ -556,14 +556,14 @@ func (v *View) Backspace() bool {
v.Cursor.SetLoc(loc - tabSize)
cx, cy := v.Cursor.x, v.Cursor.y
v.Cursor.SetLoc(loc)
v.eh.Remove(loc-tabSize, loc)
v.Buf.Remove(loc-tabSize, loc)
v.Cursor.x, v.Cursor.y = cx, cy
} else {
v.Cursor.Left()
cx, cy := v.Cursor.x, v.Cursor.y
v.Cursor.Right()
loc := v.Cursor.Loc()
v.eh.Remove(loc-1, loc)
v.Buf.Remove(loc-1, loc)
v.Cursor.x, v.Cursor.y = cx, cy
}
}
@@ -579,7 +579,7 @@ func (v *View) Delete() bool {
} else {
loc := v.Cursor.Loc()
if loc < v.Buf.Len() {
v.eh.Remove(loc, loc+1)
v.Buf.Remove(loc, loc+1)
}
}
return true
@@ -594,12 +594,12 @@ func (v *View) InsertTab() bool {
}
if settings["tabsToSpaces"].(bool) {
tabSize := int(settings["tabsize"].(float64))
v.eh.Insert(v.Cursor.Loc(), Spaces(tabSize))
v.Buf.Insert(v.Cursor.Loc(), Spaces(tabSize))
for i := 0; i < tabSize; i++ {
v.Cursor.Right()
}
} else {
v.eh.Insert(v.Cursor.Loc(), "\t")
v.Buf.Insert(v.Cursor.Loc(), "\t")
v.Cursor.Right()
}
return true
@@ -663,14 +663,14 @@ func (v *View) FindPrevious() bool {
// Undo undoes the last action
func (v *View) Undo() bool {
v.eh.Undo()
v.Buf.Undo()
messenger.Message("Undid action")
return true
}
// Redo redoes the last action
func (v *View) Redo() bool {
v.eh.Redo()
v.Buf.Redo()
messenger.Message("Redid action")
return true
}
@@ -722,7 +722,7 @@ func (v *View) Cut() bool {
// DuplicateLine duplicates the current line
func (v *View) DuplicateLine() bool {
v.Cursor.End()
v.eh.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
@@ -736,7 +736,7 @@ func (v *View) Paste() bool {
v.Cursor.ResetSelection()
}
clip, _ := clipboard.ReadAll()
v.eh.Insert(v.Cursor.Loc(), clip)
v.Buf.Insert(v.Cursor.Loc(), clip)
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
v.freshClip = false
messenger.Message("Pasted clipboard")

View File

@@ -10,9 +10,14 @@ import (
// It uses a rope to efficiently store the string and contains some
// simple functions for saving and wrapper functions for modifying the rope
type Buffer struct {
// The eventhandler for undo/redo
*EventHandler
// Stores the text of the buffer
r *rope.Rope
Cursor Cursor
// Path to the file on disk
Path string
// Name of the buffer on the status line
@@ -43,6 +48,15 @@ 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()
@@ -84,8 +98,8 @@ func (b *Buffer) SaveAs(filename string) error {
return err
}
// Insert a string into the rope
func (b *Buffer) Insert(idx int, value string) {
// This directly inserts value at idx, bypassing all undo/redo
func (b *Buffer) insert(idx int, value string) {
b.IsModified = true
b.r = b.r.Insert(idx, value)
b.Update()
@@ -93,7 +107,8 @@ func (b *Buffer) Insert(idx int, value string) {
// Remove a slice of the rope from start to end (exclusive)
// Returns the string that was removed
func (b *Buffer) Remove(start, end int) string {
// This directly removes from start to end from the buffer, bypassing all undo/redo
func (b *Buffer) remove(start, end int) string {
b.IsModified = true
if start < 0 {
start = 0

View File

@@ -162,7 +162,7 @@ func HandleCommand(input string, view *View) {
}
if choice {
view.Cursor.DeleteSelection()
view.eh.Insert(match[0], replace)
view.Buf.Insert(match[0], replace)
view.Cursor.ResetSelection()
messenger.Reset()
} else {
@@ -174,7 +174,7 @@ func HandleCommand(input string, view *View) {
continue
}
} else {
view.eh.Replace(match[0], match[1], replace)
view.Buf.Replace(match[0], match[1], replace)
}
}
if !found {

View File

@@ -43,7 +43,7 @@ func ToCharPos(x, y int, buf *Buffer) int {
// is also simpler to use character indicies for other tasks such as
// selection.
type Cursor struct {
v *View
Buf *Buffer
// The cursor display location
x int
@@ -64,7 +64,7 @@ type Cursor struct {
// 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)
c.x, c.y = FromCharPos(loc, c.Buf)
c.lastVisualX = c.GetVisualX()
}
@@ -72,7 +72,7 @@ func (c *Cursor) SetLoc(loc int) {
// 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)
return ToCharPos(c.x, c.y, c.Buf)
}
// ResetSelection resets the user's selection
@@ -89,12 +89,12 @@ func (c *Cursor) HasSelection() bool {
// DeleteSelection deletes the currently selected text
func (c *Cursor) DeleteSelection() {
if c.curSelection[0] > c.curSelection[1] {
c.v.eh.Remove(c.curSelection[1], c.curSelection[0])
c.Buf.Remove(c.curSelection[1], c.curSelection[0])
c.SetLoc(c.curSelection[1])
} else if c.GetSelection() == "" {
return
} else {
c.v.eh.Remove(c.curSelection[0], c.curSelection[1])
c.Buf.Remove(c.curSelection[0], c.curSelection[1])
c.SetLoc(c.curSelection[0])
}
}
@@ -102,9 +102,9 @@ func (c *Cursor) DeleteSelection() {
// GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string {
if c.curSelection[0] > c.curSelection[1] {
return c.v.Buf.Substr(c.curSelection[1], c.curSelection[0])
return c.Buf.Substr(c.curSelection[1], c.curSelection[0])
}
return c.v.Buf.Substr(c.curSelection[0], c.curSelection[1])
return c.Buf.Substr(c.curSelection[0], c.curSelection[1])
}
// SelectLine selects the current line
@@ -112,7 +112,7 @@ func (c *Cursor) SelectLine() {
c.Start()
c.curSelection[0] = c.Loc()
c.End()
if c.v.Buf.NumLines-1 > c.y {
if c.Buf.NumLines-1 > c.y {
c.curSelection[1] = c.Loc() + 1
} else {
c.curSelection[1] = c.Loc()
@@ -143,7 +143,7 @@ func (c *Cursor) AddLineToSelection() {
// SelectWord selects the word the cursor is currently on
func (c *Cursor) SelectWord() {
if len(c.v.Buf.Lines[c.y]) == 0 {
if len(c.Buf.Lines[c.y]) == 0 {
return
}
@@ -161,14 +161,14 @@ func (c *Cursor) SelectWord() {
backward--
}
c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
c.curSelection[0] = ToCharPos(backward, c.y, c.Buf)
c.origSelection[0] = c.curSelection[0]
for forward < Count(c.v.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.v.Buf) + 1
c.curSelection[1] = ToCharPos(forward, c.y, c.Buf) + 1
c.origSelection[1] = c.curSelection[1]
c.SetLoc(c.curSelection[1])
}
@@ -189,18 +189,18 @@ func (c *Cursor) AddWordToSelection() {
backward--
}
c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf)
c.curSelection[0] = ToCharPos(backward, c.y, c.Buf)
c.curSelection[1] = c.origSelection[1]
}
if loc > c.origSelection[1] {
forward := c.x
for forward < Count(c.v.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.v.Buf) + 1
c.curSelection[1] = ToCharPos(forward, c.y, c.Buf) + 1
c.curSelection[0] = c.origSelection[0]
}
@@ -222,13 +222,13 @@ func (c *Cursor) SelectTo(loc int) {
func (c *Cursor) WordRight() {
c.Right()
for IsWhitespace(c.RuneUnder(c.x)) {
if c.x == Count(c.v.Buf.Lines[c.y]) {
if c.x == Count(c.Buf.Lines[c.y]) {
return
}
c.Right()
}
for !IsWhitespace(c.RuneUnder(c.x)) {
if c.x == Count(c.v.Buf.Lines[c.y]) {
if c.x == Count(c.Buf.Lines[c.y]) {
return
}
c.Right()
@@ -255,7 +255,7 @@ func (c *Cursor) WordLeft() {
// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
line := []rune(c.v.Buf.Lines[c.y])
line := []rune(c.Buf.Lines[c.y])
if len(line) == 0 {
return '\n'
}
@@ -272,7 +272,7 @@ func (c *Cursor) Up() {
if c.y > 0 {
c.y--
runes := []rune(c.v.Buf.Lines[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)
@@ -282,10 +282,10 @@ func (c *Cursor) Up() {
// Down moves the cursor down one line (if possible)
func (c *Cursor) Down() {
if c.y < c.v.Buf.NumLines-1 {
if c.y < c.Buf.NumLines-1 {
c.y++
runes := []rune(c.v.Buf.Lines[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)
@@ -309,10 +309,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.Buf.Len() {
return
}
if c.x < Count(c.v.Buf.Lines[c.y]) {
if c.x < Count(c.Buf.Lines[c.y]) {
c.x++
} else {
c.Down()
@@ -323,7 +323,7 @@ func (c *Cursor) Right() {
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
c.x = Count(c.v.Buf.Lines[c.y])
c.x = Count(c.Buf.Lines[c.y])
c.lastVisualX = c.GetVisualX()
}
@@ -338,7 +338,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.v.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)
}
@@ -351,7 +351,7 @@ 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.v.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)
}
@@ -361,23 +361,13 @@ func (c *Cursor) GetVisualX() int {
func (c *Cursor) Relocate() {
if c.y < 0 {
c.y = 0
} else if c.y >= c.v.Buf.NumLines {
c.y = c.v.Buf.NumLines - 1
} 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.v.Buf.Lines[c.y]) {
c.x = Count(c.v.Buf.Lines[c.y])
}
}
// Display draws the cursor to the screen at the correct position
func (c *Cursor) Display() {
// Don't draw the cursor if it is out of the viewport or if it has a selection
if (c.y-c.v.Topline < 0 || c.y-c.v.Topline > c.v.height-1) || c.HasSelection() {
screen.HideCursor()
} else {
screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.Topline)
} else if c.x > Count(c.Buf.Lines[c.y]) {
c.x = Count(c.Buf.Lines[c.y])
}
}

View File

@@ -27,9 +27,9 @@ type TextEvent struct {
// ExecuteTextEvent runs a text event
func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
if t.eventType == TextEventInsert {
buf.Insert(t.start, t.text)
buf.insert(t.start, t.text)
} else if t.eventType == TextEventRemove {
t.text = buf.Remove(t.start, t.end)
t.text = buf.remove(t.start, t.end)
}
}
@@ -41,24 +41,24 @@ func UndoTextEvent(t *TextEvent, buf *Buffer) {
// EventHandler executes text manipulations and allows undoing and redoing
type EventHandler struct {
v *View
buf *Buffer
undo *Stack
redo *Stack
}
// NewEventHandler returns a new EventHandler
func NewEventHandler(v *View) *EventHandler {
func NewEventHandler(buf *Buffer) *EventHandler {
eh := new(EventHandler)
eh.undo = new(Stack)
eh.redo = new(Stack)
eh.v = v
eh.buf = buf
return eh
}
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start int, text string) {
e := &TextEvent{
c: eh.v.Cursor,
c: eh.buf.Cursor,
eventType: TextEventInsert,
text: text,
start: start,
@@ -71,7 +71,7 @@ func (eh *EventHandler) Insert(start int, text string) {
// Remove creates a remove text event and executes it
func (eh *EventHandler) Remove(start, end int) {
e := &TextEvent{
c: eh.v.Cursor,
c: eh.buf.Cursor,
eventType: TextEventRemove,
start: start,
end: end,
@@ -92,7 +92,7 @@ func (eh *EventHandler) Execute(t *TextEvent) {
eh.redo = new(Stack)
}
eh.undo.Push(t)
ExecuteTextEvent(t, eh.v.Buf)
ExecuteTextEvent(t, eh.buf)
}
// Undo the first event in the undo stack
@@ -135,12 +135,12 @@ func (eh *EventHandler) UndoOneEvent() {
te := t.(*TextEvent)
// Undo it
// Modifies the text event
UndoTextEvent(te, eh.v.Buf)
UndoTextEvent(te, eh.buf)
// Set the cursor in the right place
teCursor := te.c
te.c = eh.v.Cursor
eh.v.Cursor = teCursor
te.c = eh.buf.Cursor
eh.buf.Cursor = teCursor
// Push it to the redo stack
eh.redo.Push(te)
@@ -183,11 +183,11 @@ func (eh *EventHandler) RedoOneEvent() {
te := t.(*TextEvent)
// Modifies the text event
UndoTextEvent(te, eh.v.Buf)
UndoTextEvent(te, eh.buf)
teCursor := te.c
te.c = eh.v.Cursor
eh.v.Cursor = teCursor
te.c = eh.buf.Cursor
eh.buf.Cursor = teCursor
eh.undo.Push(te)
}

View File

@@ -15,7 +15,8 @@ import (
// It stores information about the cursor, and the viewport
// that the user sees the buffer from.
type View struct {
Cursor Cursor
// A pointer to the buffer's cursor for ease of access
Cursor *Cursor
// The topmost line, used for vertical scrolling
Topline int
@@ -33,9 +34,6 @@ type View struct {
// How much to offset because of line numbers
lineNumOffset int
// The eventhandler for undo/redo
eh *EventHandler
// Holds the list of gutter messages
messages map[string][]GutterMessage
@@ -90,8 +88,6 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
v.OpenBuffer(buf)
v.eh = NewEventHandler(v)
v.messages = make(map[string][]GutterMessage)
v.sline = Statusline{
@@ -162,18 +158,12 @@ func (v *View) CanClose(msg string) bool {
// This resets the topline, event handler and cursor.
func (v *View) OpenBuffer(buf *Buffer) {
v.Buf = buf
v.Cursor = &buf.Cursor
v.Topline = 0
v.leftCol = 0
// Put the cursor at the first spot
v.Cursor = Cursor{
x: 0,
y: 0,
v: v,
}
v.Cursor.ResetSelection()
v.messages = make(map[string][]GutterMessage)
v.eh = NewEventHandler(v)
v.matches = Match(v)
// Set mouseReleased to true because we assume the mouse is not being pressed when
@@ -269,7 +259,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.DeleteSelection()
v.Cursor.ResetSelection()
}
v.eh.Insert(v.Cursor.Loc(), string(e.Rune()))
v.Buf.Insert(v.Cursor.Loc(), string(e.Rune()))
v.Cursor.Right()
} else {
for key, action := range bindings {
@@ -293,7 +283,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.ResetSelection()
}
clip := e.Text()
v.eh.Insert(v.Cursor.Loc(), clip)
v.Buf.Insert(v.Cursor.Loc(), clip)
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
v.freshClip = false
case *tcell.EventMouse:
@@ -595,10 +585,20 @@ 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() {
screen.HideCursor()
} else {
screen.ShowCursor(v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.y-v.Topline)
}
}
// Display renders the view, the cursor, and statusline
func (v *View) Display() {
v.DisplayView()
v.Cursor.Display()
v.DisplayCursor()
if settings["statusline"].(bool) {
v.sline.Display()
}