]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/actions.go
Merge cursors properly
[micro.git] / cmd / micro / actions.go
index 590719f5395fa7791f82085b98613fa962721f98..5b29e1e91a1047c773cd43c5e42c4b6e2a2f38f0 100644 (file)
@@ -1,22 +1,21 @@
 package main
 
 import (
-       "io/ioutil"
        "os"
        "strconv"
        "strings"
        "time"
 
-       "github.com/mitchellh/go-homedir"
        "github.com/yuin/gopher-lua"
        "github.com/zyedidia/clipboard"
+       "github.com/zyedidia/tcell"
 )
 
 // PreActionCall executes the lua pre callback if possible
-func PreActionCall(funcName string, view *View) bool {
+func PreActionCall(funcName string, view *View, args ...interface{}) bool {
        executeAction := true
-       for _, pl := range loadedPlugins {
-               ret, err := Call(pl+".pre"+funcName, view)
+       for pl := range loadedPlugins {
+               ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
                if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
                        TermMessage(err)
                        continue
@@ -29,10 +28,10 @@ func PreActionCall(funcName string, view *View) bool {
 }
 
 // PostActionCall executes the lua plugin callback if possible
-func PostActionCall(funcName string, view *View) bool {
+func PostActionCall(funcName string, view *View, args ...interface{}) bool {
        relocate := true
-       for _, pl := range loadedPlugins {
-               ret, err := Call(pl+".on"+funcName, view)
+       for pl := range loadedPlugins {
+               ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
                if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
                        TermMessage(err)
                        continue
@@ -53,15 +52,107 @@ func (v *View) deselect(index int) bool {
        return false
 }
 
+// MousePress is the event that should happen when a normal click happens
+// This is almost always bound to left click
+func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
+       if usePlugin && !PreActionCall("MousePress", v, e) {
+               return false
+       }
+
+       x, y := e.Position()
+       x -= v.lineNumOffset - v.leftCol + v.x
+       y += v.Topline - v.y
+
+       // This is usually bound to left click
+       if v.mouseReleased {
+               v.MoveToMouseClick(x, y)
+               if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
+                       if v.doubleClick {
+                               // Triple click
+                               v.lastClickTime = time.Now()
+
+                               v.tripleClick = true
+                               v.doubleClick = false
+
+                               v.Cursor.SelectLine()
+                               v.Cursor.CopySelection("primary")
+                       } else {
+                               // Double click
+                               v.lastClickTime = time.Now()
+
+                               v.doubleClick = true
+                               v.tripleClick = false
+
+                               v.Cursor.SelectWord()
+                               v.Cursor.CopySelection("primary")
+                       }
+               } else {
+                       v.doubleClick = false
+                       v.tripleClick = false
+                       v.lastClickTime = time.Now()
+
+                       v.Cursor.OrigSelection[0] = v.Cursor.Loc
+                       v.Cursor.CurSelection[0] = v.Cursor.Loc
+                       v.Cursor.CurSelection[1] = v.Cursor.Loc
+               }
+               v.mouseReleased = false
+       } else if !v.mouseReleased {
+               v.MoveToMouseClick(x, y)
+               if v.tripleClick {
+                       v.Cursor.AddLineToSelection()
+               } else if v.doubleClick {
+                       v.Cursor.AddWordToSelection()
+               } else {
+                       v.Cursor.SetSelectionEnd(v.Cursor.Loc)
+                       v.Cursor.CopySelection("primary")
+               }
+       }
+
+       if usePlugin {
+               PostActionCall("MousePress", v, e)
+       }
+       return false
+}
+
+// ScrollUpAction scrolls the view up
+func (v *View) ScrollUpAction(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("ScrollUp", v) {
+               return false
+       }
+
+       scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
+       v.ScrollUp(scrollspeed)
+
+       if usePlugin {
+               PostActionCall("ScrollUp", v)
+       }
+       return false
+}
+
+// ScrollDownAction scrolls the view up
+func (v *View) ScrollDownAction(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("ScrollDown", v) {
+               return false
+       }
+
+       scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
+       v.ScrollDown(scrollspeed)
+
+       if usePlugin {
+               PostActionCall("ScrollDown", v)
+       }
+       return false
+}
+
 // Center centers the view on the cursor
 func (v *View) Center(usePlugin bool) bool {
        if usePlugin && !PreActionCall("Center", v) {
                return false
        }
 
-       v.Topline = v.Cursor.Y - v.height/2
-       if v.Topline+v.height > v.Buf.NumLines {
-               v.Topline = v.Buf.NumLines - v.height
+       v.Topline = v.Cursor.Y - v.Height/2
+       if v.Topline+v.Height > v.Buf.NumLines {
+               v.Topline = v.Buf.NumLines - v.Height
        }
        if v.Topline < 0 {
                v.Topline = 0
@@ -113,7 +204,21 @@ func (v *View) CursorLeft(usePlugin bool) bool {
                v.Cursor.Loc = v.Cursor.CurSelection[0]
                v.Cursor.ResetSelection()
        } else {
-               v.Cursor.Left()
+               tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
+               tabmovement := v.Buf.Settings["tabmovement"].(bool)
+               if tabstospaces && tabmovement {
+                       tabsize := int(v.Buf.Settings["tabsize"].(float64))
+                       line := v.Buf.Line(v.Cursor.Y)
+                       if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
+                               for i := 0; i < tabsize; i++ {
+                                       v.Cursor.Left()
+                               }
+                       } else {
+                               v.Cursor.Left()
+                       }
+               } else {
+                       v.Cursor.Left()
+               }
        }
 
        if usePlugin {
@@ -132,7 +237,21 @@ func (v *View) CursorRight(usePlugin bool) bool {
                v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
                v.Cursor.ResetSelection()
        } else {
-               v.Cursor.Right()
+               tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
+               tabmovement := v.Buf.Settings["tabmovement"].(bool)
+               if tabstospaces && tabmovement {
+                       tabsize := int(v.Buf.Settings["tabsize"].(float64))
+                       line := v.Buf.Line(v.Cursor.Y)
+                       if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
+                               for i := 0; i < tabsize; i++ {
+                                       v.Cursor.Right()
+                               }
+                       } else {
+                               v.Cursor.Right()
+                       }
+               } else {
+                       v.Cursor.Right()
+               }
        }
 
        if usePlugin {
@@ -361,6 +480,8 @@ func (v *View) CursorStart(usePlugin bool) bool {
                return false
        }
 
+       v.deselect(0)
+
        v.Cursor.X = 0
        v.Cursor.Y = 0
 
@@ -433,7 +554,7 @@ func (v *View) InsertSpace(usePlugin bool) bool {
                v.Cursor.ResetSelection()
        }
        v.Buf.Insert(v.Cursor.Loc, " ")
-       v.Cursor.Right()
+       // v.Cursor.Right()
 
        if usePlugin {
                return PostActionCall("InsertSpace", v)
@@ -453,17 +574,18 @@ func (v *View) InsertNewline(usePlugin bool) bool {
                v.Cursor.ResetSelection()
        }
 
-       v.Buf.Insert(v.Cursor.Loc, "\n")
        ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
-       v.Cursor.Right()
+       v.Buf.Insert(v.Cursor.Loc, "\n")
+       // v.Cursor.Right()
 
        if v.Buf.Settings["autoindent"].(bool) {
                v.Buf.Insert(v.Cursor.Loc, ws)
-               for i := 0; i < len(ws); i++ {
-                       v.Cursor.Right()
-               }
+               // for i := 0; i < len(ws); i++ {
+               //      v.Cursor.Right()
+               // }
 
-               if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) {
+               // Remove the whitespaces if keepautoindent setting is off
+               if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
                        line := v.Buf.Line(v.Cursor.Y - 1)
                        v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
                }
@@ -494,24 +616,16 @@ func (v *View) Backspace(usePlugin bool) bool {
                // and restore the position
 
                // If the user is using spaces instead of tabs and they are deleting
-               // whitespace at the start of the line, we should delete as if its a
+               // whitespace at the start of the line, we should delete as if it's a
                // tab (tabSize number of spaces)
                lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
                tabSize := int(v.Buf.Settings["tabsize"].(float64))
                if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
                        loc := v.Cursor.Loc
-                       v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
-                       cx, cy := v.Cursor.X, v.Cursor.Y
-                       v.Cursor.Loc = loc
                        v.Buf.Remove(loc.Move(-tabSize, v.Buf), 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.Buf.Remove(loc.Move(-1, v.Buf), loc)
-                       v.Cursor.X, v.Cursor.Y = cx, cy
                }
        }
        v.Cursor.LastVisualX = v.Cursor.GetVisualX()
@@ -587,31 +701,23 @@ func (v *View) IndentSelection(usePlugin bool) bool {
        }
 
        if v.Cursor.HasSelection() {
-               start := v.Cursor.CurSelection[0].Y
-               end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
-               endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
-               for i := start; i <= end; i++ {
-                       if v.Buf.Settings["tabstospaces"].(bool) {
-                               tabsize := int(v.Buf.Settings["tabsize"].(float64))
-                               v.Buf.Insert(Loc{0, i}, Spaces(tabsize))
-                               if i == start {
-                                       if v.Cursor.CurSelection[0].X > 0 {
-                                               v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(tabsize, v.Buf)
-                                       }
-                               }
-                               if i == end {
-                                       v.Cursor.CurSelection[1] = Loc{endX + tabsize + 1, end}
-                               }
-                       } else {
-                               v.Buf.Insert(Loc{0, i}, "\t")
-                               if i == start {
-                                       if v.Cursor.CurSelection[0].X > 0 {
-                                               v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(1, v.Buf)
-                                       }
-                               }
-                               if i == end {
-                                       v.Cursor.CurSelection[1] = Loc{endX + 2, end}
-                               }
+               start := v.Cursor.CurSelection[0]
+               end := v.Cursor.CurSelection[1]
+               if end.Y < start.Y {
+                       start, end = end, start
+               }
+
+               startY := start.Y
+               endY := end.Move(-1, v.Buf).Y
+               endX := end.Move(-1, v.Buf).X
+               for y := startY; y <= endY; y++ {
+                       tabsize := len(v.Buf.IndentString())
+                       v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
+                       if y == startY && start.X > 0 {
+                               v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
+                       }
+                       if y == endY {
+                               v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
                        }
                }
                v.Cursor.Relocate()
@@ -624,6 +730,30 @@ func (v *View) IndentSelection(usePlugin bool) bool {
        return false
 }
 
+// OutdentLine moves the current line back one indentation
+func (v *View) OutdentLine(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("OutdentLine", v) {
+               return false
+       }
+
+       if v.Cursor.HasSelection() {
+               return false
+       }
+
+       for x := 0; x < len(v.Buf.IndentString()); x++ {
+               if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
+                       break
+               }
+               v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
+       }
+       v.Cursor.Relocate()
+
+       if usePlugin {
+               return PostActionCall("OutdentLine", v)
+       }
+       return true
+}
+
 // OutdentSelection takes the current selection and moves it back one indent level
 func (v *View) OutdentSelection(usePlugin bool) bool {
        if usePlugin && !PreActionCall("OutdentSelection", v) {
@@ -631,37 +761,26 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
        }
 
        if v.Cursor.HasSelection() {
-               start := v.Cursor.CurSelection[0].Y
-               end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
-               endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
-               for i := start; i <= end; i++ {
-                       if len(GetLeadingWhitespace(v.Buf.Line(i))) > 0 {
-                               if v.Buf.Settings["tabstospaces"].(bool) {
-                                       tabsize := int(v.Buf.Settings["tabsize"].(float64))
-                                       for j := 0; j < tabsize; j++ {
-                                               if len(GetLeadingWhitespace(v.Buf.Line(i))) == 0 {
-                                                       break
-                                               }
-                                               v.Buf.Remove(Loc{0, i}, Loc{1, i})
-                                               if i == start {
-                                                       if v.Cursor.CurSelection[0].X > 0 {
-                                                               v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
-                                                       }
-                                               }
-                                               if i == end {
-                                                       v.Cursor.CurSelection[1] = Loc{endX - j, end}
-                                               }
-                                       }
-                               } else {
-                                       v.Buf.Remove(Loc{0, i}, Loc{1, i})
-                                       if i == start {
-                                               if v.Cursor.CurSelection[0].X > 0 {
-                                                       v.Cursor.CurSelection[0] = v.Cursor.CurSelection[0].Move(-1, v.Buf)
-                                               }
-                                       }
-                                       if i == end {
-                                               v.Cursor.CurSelection[1] = Loc{endX, end}
-                                       }
+               start := v.Cursor.CurSelection[0]
+               end := v.Cursor.CurSelection[1]
+               if end.Y < start.Y {
+                       start, end = end, start
+               }
+
+               startY := start.Y
+               endY := end.Move(-1, v.Buf).Y
+               endX := end.Move(-1, v.Buf).X
+               for y := startY; y <= endY; y++ {
+                       for x := 0; x < len(v.Buf.IndentString()); x++ {
+                               if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
+                                       break
+                               }
+                               v.Buf.Remove(Loc{0, y}, Loc{1, y})
+                               if y == startY && start.X > 0 {
+                                       v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
+                               }
+                               if y == endY {
+                                       v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
                                }
                        }
                }
@@ -684,20 +803,13 @@ func (v *View) InsertTab(usePlugin bool) bool {
        if v.Cursor.HasSelection() {
                return false
        }
-       // Insert a tab
-       if v.Buf.Settings["tabstospaces"].(bool) {
-               tabSize := int(v.Buf.Settings["tabsize"].(float64))
-               if remainder := v.Cursor.Loc.X % tabSize; remainder != 0 {
-                       tabSize = tabSize - remainder
-               }
-               v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
-               for i := 0; i < tabSize; i++ {
-                       v.Cursor.Right()
-               }
-       } else {
-               v.Buf.Insert(v.Cursor.Loc, "\t")
-               v.Cursor.Right()
-       }
+
+       tabBytes := len(v.Buf.IndentString())
+       bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
+       v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
+       // for i := 0; i < bytesUntilIndent; i++ {
+       //      v.Cursor.Right()
+       // }
 
        if usePlugin {
                return PostActionCall("InsertTab", v)
@@ -705,37 +817,63 @@ func (v *View) InsertTab(usePlugin bool) bool {
        return true
 }
 
+// SaveAll saves all open buffers
+func (v *View) SaveAll(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("SaveAll", v) {
+               return false
+       }
+
+       for _, t := range tabs {
+               for _, v := range t.views {
+                       v.Save(false)
+               }
+       }
+
+       if usePlugin {
+               return PostActionCall("SaveAll", v)
+       }
+       return false
+}
+
 // Save the buffer to disk
 func (v *View) Save(usePlugin bool) bool {
        if usePlugin && !PreActionCall("Save", v) {
                return false
        }
 
-       if v.Help {
-               // We can't save the help text
+       if v.Type.scratch == true {
+               // We can't save any view type with scratch set. eg help and log text
                return false
        }
        // If this is an empty buffer, ask for a filename
        if v.Buf.Path == "" {
-               filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
-               if !canceled {
-                       v.Buf.Path = filename
-                       v.Buf.Name = filename
-               } else {
-                       return false
-               }
+               v.SaveAs(false)
+       } else {
+               v.saveToFile(v.Buf.Path)
        }
-       err := v.Buf.Save()
+
+       if usePlugin {
+               return PostActionCall("Save", v)
+       }
+       return false
+}
+
+// This function saves the buffer to `filename` and changes the buffer's path and name
+// to `filename` if the save is successful
+func (v *View) saveToFile(filename string) {
+       err := v.Buf.SaveAs(filename)
        if err != nil {
                if strings.HasSuffix(err.Error(), "permission denied") {
                        choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
                        if choice {
-                               err = v.Buf.SaveWithSudo()
+                               err = v.Buf.SaveAsWithSudo(filename)
                                if err != nil {
                                        messenger.Error(err.Error())
-                                       return false
+                               } else {
+                                       v.Buf.Path = filename
+                                       v.Buf.name = filename
+                                       messenger.Message("Saved " + filename)
                                }
-                               messenger.Message("Saved " + v.Buf.Path)
                        }
                        messenger.Reset()
                        messenger.Clear()
@@ -743,12 +881,21 @@ func (v *View) Save(usePlugin bool) bool {
                        messenger.Error(err.Error())
                }
        } else {
-               messenger.Message("Saved " + v.Buf.Path)
+               v.Buf.Path = filename
+               v.Buf.name = filename
+               messenger.Message("Saved " + filename)
        }
+}
 
-       if usePlugin {
-               return PostActionCall("Save", v)
+// SaveAs saves the buffer to disk with the given name
+func (v *View) SaveAs(usePlugin bool) bool {
+       filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
+       if !canceled {
+               // the filename might or might not be quoted, so unquote first then join the strings.
+               filename = strings.Join(SplitCommandArgs(filename), " ")
+               v.saveToFile(filename)
        }
+
        return false
 }
 
@@ -758,12 +905,15 @@ func (v *View) Find(usePlugin bool) bool {
                return false
        }
 
+       searchStr := ""
        if v.Cursor.HasSelection() {
                searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
+               searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
+               searchStr = v.Cursor.GetSelection()
        } else {
                searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
        }
-       BeginSearch()
+       BeginSearch(searchStr)
 
        if usePlugin {
                return PostActionCall("Find", v)
@@ -779,9 +929,13 @@ func (v *View) FindNext(usePlugin bool) bool {
 
        if v.Cursor.HasSelection() {
                searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
+               // lastSearch = v.Cursor.GetSelection()
        } else {
                searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
        }
+       if lastSearch == "" {
+               return true
+       }
        messenger.Message("Finding: " + lastSearch)
        Search(lastSearch, v, true)
 
@@ -848,7 +1002,7 @@ func (v *View) Copy(usePlugin bool) bool {
        }
 
        if v.Cursor.HasSelection() {
-               clipboard.WriteAll(v.Cursor.GetSelection())
+               v.Cursor.CopySelection("clipboard")
                v.freshClip = true
                messenger.Message("Copied selection")
        }
@@ -871,10 +1025,10 @@ func (v *View) CutLine(usePlugin bool) bool {
        }
        if v.freshClip == true {
                if v.Cursor.HasSelection() {
-                       if clip, err := clipboard.ReadAll(); err != nil {
+                       if clip, err := clipboard.ReadAll("clipboard"); err != nil {
                                messenger.Error(err)
                        } else {
-                               clipboard.WriteAll(clip + v.Cursor.GetSelection())
+                               clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
                        }
                }
        } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
@@ -899,7 +1053,7 @@ func (v *View) Cut(usePlugin bool) bool {
        }
 
        if v.Cursor.HasSelection() {
-               clipboard.WriteAll(v.Cursor.GetSelection())
+               v.Cursor.CopySelection("clipboard")
                v.Cursor.DeleteSelection()
                v.Cursor.ResetSelection()
                v.freshClip = true
@@ -914,15 +1068,20 @@ func (v *View) Cut(usePlugin bool) bool {
        return false
 }
 
-// DuplicateLine duplicates the current line
+// DuplicateLine duplicates the current line or selection
 func (v *View) DuplicateLine(usePlugin bool) bool {
        if usePlugin && !PreActionCall("DuplicateLine", v) {
                return false
        }
 
-       v.Cursor.End()
-       v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
-       v.Cursor.Right()
+       if v.Cursor.HasSelection() {
+               v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
+       } else {
+               v.Cursor.End()
+               v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
+               // v.Cursor.Right()
+       }
+
        messenger.Message("Duplicated line")
 
        if usePlugin {
@@ -951,6 +1110,84 @@ func (v *View) DeleteLine(usePlugin bool) bool {
        return true
 }
 
+// MoveLinesUp moves up the current line or selected lines if any
+func (v *View) MoveLinesUp(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("MoveLinesUp", v) {
+               return false
+       }
+
+       if v.Cursor.HasSelection() {
+               if v.Cursor.CurSelection[0].Y == 0 {
+                       messenger.Message("Can not move further up")
+                       return true
+               }
+               v.Buf.MoveLinesUp(
+                       v.Cursor.CurSelection[0].Y,
+                       v.Cursor.CurSelection[1].Y,
+               )
+               v.Cursor.UpN(1)
+               v.Cursor.CurSelection[0].Y -= 1
+               v.Cursor.CurSelection[1].Y -= 1
+               messenger.Message("Moved up selected line(s)")
+       } else {
+               if v.Cursor.Loc.Y == 0 {
+                       messenger.Message("Can not move further up")
+                       return true
+               }
+               v.Buf.MoveLinesUp(
+                       v.Cursor.Loc.Y,
+                       v.Cursor.Loc.Y+1,
+               )
+               v.Cursor.UpN(1)
+               messenger.Message("Moved up current line")
+       }
+       v.Buf.IsModified = true
+
+       if usePlugin {
+               return PostActionCall("MoveLinesUp", v)
+       }
+       return true
+}
+
+// MoveLinesDown moves down the current line or selected lines if any
+func (v *View) MoveLinesDown(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("MoveLinesDown", v) {
+               return false
+       }
+
+       if v.Cursor.HasSelection() {
+               if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
+                       messenger.Message("Can not move further down")
+                       return true
+               }
+               v.Buf.MoveLinesDown(
+                       v.Cursor.CurSelection[0].Y,
+                       v.Cursor.CurSelection[1].Y,
+               )
+               v.Cursor.DownN(1)
+               v.Cursor.CurSelection[0].Y += 1
+               v.Cursor.CurSelection[1].Y += 1
+               messenger.Message("Moved down selected line(s)")
+       } else {
+               if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
+                       messenger.Message("Can not move further down")
+                       return true
+               }
+               v.Buf.MoveLinesDown(
+                       v.Cursor.Loc.Y,
+                       v.Cursor.Loc.Y+1,
+               )
+               v.Cursor.DownN(1)
+               messenger.Message("Moved down current line")
+       }
+       v.Buf.IsModified = true
+
+       if usePlugin {
+               return PostActionCall("MoveLinesDown", v)
+       }
+       return true
+}
+
 // Paste whatever is in the system clipboard into the buffer
 // Delete and paste if the user has a selection
 func (v *View) Paste(usePlugin bool) bool {
@@ -958,18 +1195,23 @@ func (v *View) Paste(usePlugin bool) bool {
                return false
        }
 
-       leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
+       clip, _ := clipboard.ReadAll("clipboard")
+       v.paste(clip)
 
-       if v.Cursor.HasSelection() {
-               v.Cursor.DeleteSelection()
-               v.Cursor.ResetSelection()
+       if usePlugin {
+               return PostActionCall("Paste", v)
        }
-       clip, _ := clipboard.ReadAll()
-       clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
-       v.Buf.Insert(v.Cursor.Loc, clip)
-       v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
-       v.freshClip = false
-       messenger.Message("Pasted clipboard")
+       return true
+}
+
+// PastePrimary pastes from the primary clipboard (only use on linux)
+func (v *View) PastePrimary(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("Paste", v) {
+               return false
+       }
+
+       clip, _ := clipboard.ReadAll("primary")
+       v.paste(clip)
 
        if usePlugin {
                return PostActionCall("Paste", v)
@@ -983,8 +1225,8 @@ func (v *View) SelectAll(usePlugin bool) bool {
                return false
        }
 
-       v.Cursor.CurSelection[0] = v.Buf.Start()
-       v.Cursor.CurSelection[1] = v.Buf.End()
+       v.Cursor.SetSelectionStart(v.Buf.Start())
+       v.Cursor.SetSelectionEnd(v.Buf.End())
        // Put the cursor at the beginning
        v.Cursor.X = 0
        v.Cursor.Y = 0
@@ -1001,28 +1243,14 @@ func (v *View) OpenFile(usePlugin bool) bool {
                return false
        }
 
-       if v.CanClose("Continue? (y,n,s) ", 'y', 'n', 's') {
-               filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
-               if canceled {
-                       return false
-               }
-               home, _ := homedir.Dir()
-               filename = strings.Replace(filename, "~", home, 1)
-               file, err := ioutil.ReadFile(filename)
-
-               var buf *Buffer
-               if err != nil {
-                       // File does not exist -- create an empty buffer with that name
-                       buf = NewBuffer([]byte{}, filename)
-               } else {
-                       buf = NewBuffer(file, filename)
-               }
-               v.OpenBuffer(buf)
-
-               if usePlugin {
-                       return PostActionCall("OpenFile", v)
+       if v.CanClose() {
+               input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
+               if !canceled {
+                       HandleCommand(input)
+                       if usePlugin {
+                               return PostActionCall("OpenFile", v)
+                       }
                }
-               return true
        }
        return false
 }
@@ -1047,10 +1275,10 @@ func (v *View) End(usePlugin bool) bool {
                return false
        }
 
-       if v.height > v.Buf.NumLines {
+       if v.Height > v.Buf.NumLines {
                v.Topline = 0
        } else {
-               v.Topline = v.Buf.NumLines - v.height
+               v.Topline = v.Buf.NumLines - v.Height
        }
 
        if usePlugin {
@@ -1065,8 +1293,8 @@ func (v *View) PageUp(usePlugin bool) bool {
                return false
        }
 
-       if v.Topline > v.height {
-               v.ScrollUp(v.height)
+       if v.Topline > v.Height {
+               v.ScrollUp(v.Height)
        } else {
                v.Topline = 0
        }
@@ -1083,10 +1311,10 @@ func (v *View) PageDown(usePlugin bool) bool {
                return false
        }
 
-       if v.Buf.NumLines-(v.Topline+v.height) > v.height {
-               v.ScrollDown(v.height)
-       } else if v.Buf.NumLines >= v.height {
-               v.Topline = v.Buf.NumLines - v.height
+       if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
+               v.ScrollDown(v.Height)
+       } else if v.Buf.NumLines >= v.Height {
+               v.Topline = v.Buf.NumLines - v.Height
        }
 
        if usePlugin {
@@ -1107,7 +1335,7 @@ func (v *View) CursorPageUp(usePlugin bool) bool {
                v.Cursor.Loc = v.Cursor.CurSelection[0]
                v.Cursor.ResetSelection()
        }
-       v.Cursor.UpN(v.height)
+       v.Cursor.UpN(v.Height)
 
        if usePlugin {
                return PostActionCall("CursorPageUp", v)
@@ -1127,7 +1355,7 @@ func (v *View) CursorPageDown(usePlugin bool) bool {
                v.Cursor.Loc = v.Cursor.CurSelection[1]
                v.Cursor.ResetSelection()
        }
-       v.Cursor.DownN(v.height)
+       v.Cursor.DownN(v.Height)
 
        if usePlugin {
                return PostActionCall("CursorPageDown", v)
@@ -1141,8 +1369,8 @@ func (v *View) HalfPageUp(usePlugin bool) bool {
                return false
        }
 
-       if v.Topline > v.height/2 {
-               v.ScrollUp(v.height / 2)
+       if v.Topline > v.Height/2 {
+               v.ScrollUp(v.Height / 2)
        } else {
                v.Topline = 0
        }
@@ -1159,11 +1387,11 @@ func (v *View) HalfPageDown(usePlugin bool) bool {
                return false
        }
 
-       if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
-               v.ScrollDown(v.height / 2)
+       if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
+               v.ScrollDown(v.Height / 2)
        } else {
-               if v.Buf.NumLines >= v.height {
-                       v.Topline = v.Buf.NumLines - v.height
+               if v.Buf.NumLines >= v.Height {
+                       v.Topline = v.Buf.NumLines - v.Height
                }
        }
 
@@ -1200,7 +1428,7 @@ func (v *View) JumpLine(usePlugin bool) bool {
        }
 
        // Prompt for line number
-       linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
+       linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
        if canceled {
                return false
        }
@@ -1244,7 +1472,7 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
                return false
        }
 
-       if !v.Help {
+       if v.Type != vtHelp {
                // Open the default help
                v.openHelp("help")
        } else {
@@ -1263,10 +1491,10 @@ func (v *View) ShellMode(usePlugin bool) bool {
                return false
        }
 
-       input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
+       input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
        if !canceled {
                // The true here is for openTerm to make the command interactive
-               HandleShellCommand(input, true)
+               HandleShellCommand(input, true, true)
                if usePlugin {
                        return PostActionCall("ShellMode", v)
                }
@@ -1280,7 +1508,7 @@ func (v *View) CommandMode(usePlugin bool) bool {
                return false
        }
 
-       input, canceled := messenger.Prompt("> ", "Command", CommandCompletion)
+       input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
        if !canceled {
                HandleCommand(input)
                if usePlugin {
@@ -1291,17 +1519,30 @@ func (v *View) CommandMode(usePlugin bool) bool {
        return false
 }
 
-// Quit quits the editor
-// This behavior needs to be changed and should really only quit the editor if this
-// is the last view
-// However, since micro only supports one view for now, it doesn't really matter
+// Escape leaves current mode
+func (v *View) Escape(usePlugin bool) bool {
+       // check if user is searching, or the last search is still active
+       if searching || lastSearch != "" {
+               ExitSearch(v)
+               return true
+       }
+       // check if a prompt is shown, hide it and don't quit
+       if messenger.hasPrompt {
+               messenger.Reset() // FIXME
+               return true
+       }
+
+       return false
+}
+
+// Quit this will close the current tab or view that is open
 func (v *View) Quit(usePlugin bool) bool {
        if usePlugin && !PreActionCall("Quit", v) {
                return false
        }
 
        // Make sure not to quit if there are unsaved changes
-       if v.CanClose("Quit anyway? (y,n,s) ", 'y', 'n', 's') {
+       if v.CanClose() {
                v.CloseBuffer()
                if len(tabs[curTab].views) > 1 {
                        v.splitNode.Delete()
@@ -1317,9 +1558,7 @@ func (v *View) Quit(usePlugin bool) bool {
                                        curTab--
                                }
                                if curTab == 0 {
-                                       // CurView().Resize(screen.Size())
                                        CurView().ToggleTabbar()
-                                       CurView().matches = Match(CurView())
                                }
                        }
                } else {
@@ -1347,25 +1586,30 @@ func (v *View) QuitAll(usePlugin bool) bool {
        closeAll := true
        for _, tab := range tabs {
                for _, v := range tab.views {
-                       if !v.CanClose("Quit anyway? (y,n,s) ", 'y', 'n', 's') {
+                       if !v.CanClose() {
                                closeAll = false
                        }
                }
        }
 
        if closeAll {
-               for _, tab := range tabs {
-                       for _, v := range tab.views {
-                               v.CloseBuffer()
+               // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
+               shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
+
+               if shouldQuit {
+                       for _, tab := range tabs {
+                               for _, v := range tab.views {
+                                       v.CloseBuffer()
+                               }
                        }
-               }
 
-               if usePlugin {
-                       PostActionCall("QuitAll", v)
-               }
+                       if usePlugin {
+                               PostActionCall("QuitAll", v)
+                       }
 
-               screen.Fini()
-               os.Exit(0)
+                       screen.Fini()
+                       os.Exit(0)
+               }
        }
 
        return false
@@ -1377,10 +1621,10 @@ func (v *View) AddTab(usePlugin bool) bool {
                return false
        }
 
-       tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
+       tab := NewTabFromView(NewView(NewBufferFromString("", "")))
        tab.SetNum(len(tabs))
        tabs = append(tabs, tab)
-       curTab++
+       curTab = len(tabs) - 1
        if len(tabs) == 2 {
                for _, t := range tabs {
                        for _, v := range t.views {
@@ -1431,6 +1675,55 @@ func (v *View) NextTab(usePlugin bool) bool {
        return false
 }
 
+// VSplitBinding opens an empty vertical split
+func (v *View) VSplitBinding(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("VSplit", v) {
+               return false
+       }
+
+       v.VSplit(NewBufferFromString("", ""))
+
+       if usePlugin {
+               return PostActionCall("VSplit", v)
+       }
+       return false
+}
+
+// HSplitBinding opens an empty horizontal split
+func (v *View) HSplitBinding(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("HSplit", v) {
+               return false
+       }
+
+       v.HSplit(NewBufferFromString("", ""))
+
+       if usePlugin {
+               return PostActionCall("HSplit", v)
+       }
+       return false
+}
+
+// Unsplit closes all splits in the current tab except the active one
+func (v *View) Unsplit(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("Unsplit", v) {
+               return false
+       }
+
+       curView := tabs[curTab].CurView
+       for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
+               view := tabs[curTab].views[i]
+               if view != nil && view.Num != curView {
+                       view.Quit(true)
+                       // messenger.Message("Quit ", view.Buf.Path)
+               }
+       }
+
+       if usePlugin {
+               return PostActionCall("Unsplit", v)
+       }
+       return false
+}
+
 // NextSplit changes the view to the next split
 func (v *View) NextSplit(usePlugin bool) bool {
        if usePlugin && !PreActionCall("NextSplit", v) {
@@ -1438,10 +1731,10 @@ func (v *View) NextSplit(usePlugin bool) bool {
        }
 
        tab := tabs[curTab]
-       if tab.curView < len(tab.views)-1 {
-               tab.curView++
+       if tab.CurView < len(tab.views)-1 {
+               tab.CurView++
        } else {
-               tab.curView = 0
+               tab.CurView = 0
        }
 
        if usePlugin {
@@ -1457,10 +1750,10 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
        }
 
        tab := tabs[curTab]
-       if tab.curView > 0 {
-               tab.curView--
+       if tab.CurView > 0 {
+               tab.CurView--
        } else {
-               tab.curView = len(tab.views) - 1
+               tab.CurView = len(tab.views) - 1
        }
 
        if usePlugin {
@@ -1469,7 +1762,201 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
        return false
 }
 
-// None is no action
-func None() bool {
+var curMacro []interface{}
+var recordingMacro bool
+
+// ToggleMacro toggles recording of a macro
+func (v *View) ToggleMacro(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("ToggleMacro", v) {
+               return false
+       }
+
+       recordingMacro = !recordingMacro
+
+       if recordingMacro {
+               curMacro = []interface{}{}
+               messenger.Message("Recording")
+       } else {
+               messenger.Message("Stopped recording")
+       }
+
+       if usePlugin {
+               return PostActionCall("ToggleMacro", v)
+       }
+       return true
+}
+
+// PlayMacro plays back the most recently recorded macro
+func (v *View) PlayMacro(usePlugin bool) bool {
+       if usePlugin && !PreActionCall("PlayMacro", v) {
+               return false
+       }
+
+       for _, action := range curMacro {
+               switch t := action.(type) {
+               case rune:
+                       // Insert a character
+                       if v.Cursor.HasSelection() {
+                               v.Cursor.DeleteSelection()
+                               v.Cursor.ResetSelection()
+                       }
+                       v.Buf.Insert(v.Cursor.Loc, string(t))
+                       // v.Cursor.Right()
+
+                       for pl := range loadedPlugins {
+                               _, err := Call(pl+".onRune", string(t), v)
+                               if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
+                                       TermMessage(err)
+                               }
+                       }
+               case func(*View, bool) bool:
+                       t(v, true)
+               }
+       }
+
+       if usePlugin {
+               return PostActionCall("PlayMacro", v)
+       }
+       return true
+}
+
+// SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
+func (v *View) SpawnMultiCursor(usePlugin bool) bool {
+       spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
+       // You can only spawn a cursor from the main cursor
+       if v.Cursor == spawner {
+               if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
+                       return false
+               }
+
+               if !spawner.HasSelection() {
+                       spawner.SelectWord()
+               } else {
+                       c := &Cursor{
+                               buf: v.Buf,
+                       }
+
+                       sel := spawner.GetSelection()
+
+                       searchStart = ToCharPos(spawner.CurSelection[1], v.Buf)
+                       v.Cursor = c
+                       Search(sel, v, true)
+
+                       for _, cur := range v.Buf.cursors {
+                               if c.Loc == cur.Loc {
+                                       return false
+                               }
+                       }
+                       v.Buf.cursors = append(v.Buf.cursors, c)
+                       v.Buf.UpdateCursors()
+                       v.Relocate()
+                       v.Cursor = spawner
+               }
+
+               if usePlugin {
+                       PostActionCall("SpawnMultiCursor", v)
+               }
+       }
+
+       return false
+}
+
+// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
+func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
+       if v.Cursor == &v.Buf.Cursor {
+               if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
+                       return false
+               }
+               x, y := e.Position()
+               x -= v.lineNumOffset - v.leftCol + v.x
+               y += v.Topline - v.y
+
+               c := &Cursor{
+                       buf: v.Buf,
+               }
+               v.Cursor = c
+               v.MoveToMouseClick(x, y)
+               v.Relocate()
+               v.Cursor = &v.Buf.Cursor
+
+               v.Buf.cursors = append(v.Buf.cursors, c)
+               v.Buf.UpdateCursors()
+
+               if usePlugin {
+                       PostActionCall("SpawnMultiCursorAtMouse", v)
+               }
+       }
+       return false
+}
+
+// SkipMultiCursor moves the current multiple cursor to the next available position
+func (v *View) SkipMultiCursor(usePlugin bool) bool {
+       cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
+
+       if v.Cursor == cursor {
+               if usePlugin && !PreActionCall("SkipMultiCursor", v) {
+                       return false
+               }
+               sel := cursor.GetSelection()
+
+               searchStart = ToCharPos(cursor.CurSelection[1], v.Buf)
+               v.Cursor = cursor
+               Search(sel, v, true)
+               v.Relocate()
+               v.Cursor = cursor
+
+               if usePlugin {
+                       PostActionCall("SkipMultiCursor", v)
+               }
+       }
+       return false
+}
+
+// RemoveMultiCursor removes the latest multiple cursor
+func (v *View) RemoveMultiCursor(usePlugin bool) bool {
+       end := len(v.Buf.cursors)
+       if end > 1 {
+               lastOne := v.Buf.cursors[end-1]
+               if v.Cursor == lastOne {
+                       if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
+                               return false
+                       }
+
+                       v.Buf.cursors[end-1] = nil
+                       v.Buf.cursors = v.Buf.cursors[:end-1]
+                       v.Buf.UpdateCursors()
+                       v.Relocate()
+
+                       if usePlugin {
+                               return PostActionCall("RemoveMultiCursor", v)
+                       }
+                       return true
+               }
+       } else {
+               v.RemoveAllMultiCursors(usePlugin)
+       }
+       return false
+}
+
+// RemoveAllMultiCursors removes all cursors except the base cursor
+func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
+       if v.Cursor == v.Buf.cursors[len(v.Buf.cursors)-1] {
+               if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
+                       return false
+               }
+
+               for i := 1; i < len(v.Buf.cursors); i++ {
+                       v.Buf.cursors[i] = nil
+               }
+               v.Buf.cursors = v.Buf.cursors[:1]
+               v.Buf.UpdateCursors()
+               v.Cursor.ResetSelection()
+               v.Relocate()
+
+               if usePlugin {
+                       return PostActionCall("RemoveAllMultiCursors", v)
+               }
+               return true
+       }
        return false
 }