diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index 330be01e..f093fe7b 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -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") diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index da5e6757..df938296 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -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 diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 1e41a7b2..dac6ed58 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -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 { diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go index 5eb43b50..1bb55d61 100644 --- a/cmd/micro/cursor.go +++ b/cmd/micro/cursor.go @@ -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]) } } diff --git a/cmd/micro/eventhandler.go b/cmd/micro/eventhandler.go index 4904a84e..2c5076d1 100644 --- a/cmd/micro/eventhandler.go +++ b/cmd/micro/eventhandler.go @@ -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) } diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 078177b3..e6371235 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -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() }