]> git.lizzy.rs Git - micro.git/blobdiff - internal/action/actions.go
Add support for copy-paste via OSC 52
[micro.git] / internal / action / actions.go
index 6d83ae4cd9371b542af9b01d0f29285243ddbe91..5d7b31e0238b9f0dd70ab9ca2a4515791a2eefea 100644 (file)
@@ -5,15 +5,14 @@ import (
        "runtime"
        "strings"
        "time"
-       "unicode/utf8"
-
-       "github.com/zyedidia/clipboard"
-       "github.com/zyedidia/micro/internal/buffer"
-       "github.com/zyedidia/micro/internal/config"
-       "github.com/zyedidia/micro/internal/screen"
-       "github.com/zyedidia/micro/internal/shell"
-       "github.com/zyedidia/micro/internal/util"
-       "github.com/zyedidia/micro/pkg/shellwords"
+
+       shellquote "github.com/kballard/go-shellquote"
+       "github.com/zyedidia/micro/v2/internal/buffer"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
+       "github.com/zyedidia/micro/v2/internal/config"
+       "github.com/zyedidia/micro/v2/internal/screen"
+       "github.com/zyedidia/micro/v2/internal/shell"
+       "github.com/zyedidia/micro/v2/internal/util"
        "github.com/zyedidia/tcell"
 )
 
@@ -23,6 +22,8 @@ func (h *BufPane) ScrollUp(n int) {
        if v.StartLine >= n {
                v.StartLine -= n
                h.SetView(v)
+       } else {
+               v.StartLine = 0
        }
 }
 
@@ -40,7 +41,7 @@ func (h *BufPane) ScrollDown(n int) {
 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
        b := h.Buf
        mx, my := e.Position()
-       mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my})
+       mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
        h.Cursor.Loc = mouseLoc
        if h.mouseReleased {
                if b.NumCursors() > 1 {
@@ -58,7 +59,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
                                h.doubleClick = false
 
                                h.Cursor.SelectLine()
-                               h.Cursor.CopySelection("primary")
+                               h.Cursor.CopySelection(clipboard.PrimaryReg)
                        } else {
                                // Double click
                                h.lastClickTime = time.Now()
@@ -67,7 +68,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
                                h.tripleClick = false
 
                                h.Cursor.SelectWord()
-                               h.Cursor.CopySelection("primary")
+                               h.Cursor.CopySelection(clipboard.PrimaryReg)
                        }
                } else {
                        h.doubleClick = false
@@ -86,25 +87,24 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
                        h.Cursor.AddWordToSelection()
                } else {
                        h.Cursor.SetSelectionEnd(h.Cursor.Loc)
-                       h.Cursor.CopySelection("primary")
                }
        }
 
        h.Cursor.StoreVisualX()
        h.lastLoc = mouseLoc
-       return false
+       return true
 }
 
 // ScrollUpAction scrolls the view up
 func (h *BufPane) ScrollUpAction() bool {
        h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
-       return false
+       return true
 }
 
 // ScrollDownAction scrolls the view up
 func (h *BufPane) ScrollDownAction() bool {
        h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
-       return false
+       return true
 }
 
 // Center centers the view on the cursor
@@ -118,6 +118,7 @@ func (h *BufPane) Center() bool {
                v.StartLine = 0
        }
        h.SetView(v)
+       h.Relocate()
        return true
 }
 
@@ -125,6 +126,7 @@ func (h *BufPane) Center() bool {
 func (h *BufPane) CursorUp() bool {
        h.Cursor.Deselect(true)
        h.Cursor.Up()
+       h.Relocate()
        return true
 }
 
@@ -132,6 +134,7 @@ func (h *BufPane) CursorUp() bool {
 func (h *BufPane) CursorDown() bool {
        h.Cursor.Deselect(true)
        h.Cursor.Down()
+       h.Relocate()
        return true
 }
 
@@ -156,6 +159,7 @@ func (h *BufPane) CursorLeft() bool {
                        h.Cursor.Left()
                }
        }
+       h.Relocate()
        return true
 }
 
@@ -170,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
                if tabstospaces && tabmovement {
                        tabsize := int(h.Buf.Settings["tabsize"].(float64))
                        line := h.Buf.LineBytes(h.Cursor.Y)
-                       if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
+                       if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
                                for i := 0; i < tabsize; i++ {
                                        h.Cursor.Right()
                                }
@@ -182,6 +186,7 @@ func (h *BufPane) CursorRight() bool {
                }
        }
 
+       h.Relocate()
        return true
 }
 
@@ -189,6 +194,7 @@ func (h *BufPane) CursorRight() bool {
 func (h *BufPane) WordRight() bool {
        h.Cursor.Deselect(false)
        h.Cursor.WordRight()
+       h.Relocate()
        return true
 }
 
@@ -196,6 +202,7 @@ func (h *BufPane) WordRight() bool {
 func (h *BufPane) WordLeft() bool {
        h.Cursor.Deselect(true)
        h.Cursor.WordLeft()
+       h.Relocate()
        return true
 }
 
@@ -206,6 +213,7 @@ func (h *BufPane) SelectUp() bool {
        }
        h.Cursor.Up()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -216,6 +224,7 @@ func (h *BufPane) SelectDown() bool {
        }
        h.Cursor.Down()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -231,6 +240,7 @@ func (h *BufPane) SelectLeft() bool {
        }
        h.Cursor.Left()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -246,6 +256,7 @@ func (h *BufPane) SelectRight() bool {
        }
        h.Cursor.Right()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -256,6 +267,7 @@ func (h *BufPane) SelectWordRight() bool {
        }
        h.Cursor.WordRight()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -266,18 +278,36 @@ func (h *BufPane) SelectWordLeft() bool {
        }
        h.Cursor.WordLeft()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
+       return true
+}
+
+// StartOfText moves the cursor to the start of the text of the line
+func (h *BufPane) StartOfText() bool {
+       h.Cursor.Deselect(true)
+       h.Cursor.StartOfText()
+       h.Relocate()
+       return true
+}
+
+// StartOfTextToggle toggles the cursor between the start of the text of the line
+// and the start of the line
+func (h *BufPane) StartOfTextToggle() bool {
+       h.Cursor.Deselect(true)
+       if h.Cursor.IsStartOfText() {
+               h.Cursor.Start()
+       } else {
+               h.Cursor.StartOfText()
+       }
+       h.Relocate()
        return true
 }
 
 // StartOfLine moves the cursor to the start of the line
 func (h *BufPane) StartOfLine() bool {
        h.Cursor.Deselect(true)
-       h.Cursor.StartOfText()
-       // if h.Cursor.X != 0 {
-       //      h.Cursor.Start()
-       // } else {
-       //      h.Cursor.StartOfText()
-       // }
+       h.Cursor.Start()
+       h.Relocate()
        return true
 }
 
@@ -285,12 +315,41 @@ func (h *BufPane) StartOfLine() bool {
 func (h *BufPane) EndOfLine() bool {
        h.Cursor.Deselect(true)
        h.Cursor.End()
+       h.Relocate()
        return true
 }
 
 // SelectLine selects the entire current line
 func (h *BufPane) SelectLine() bool {
        h.Cursor.SelectLine()
+       h.Relocate()
+       return true
+}
+
+// SelectToStartOfText selects to the start of the text on the current line
+func (h *BufPane) SelectToStartOfText() bool {
+       if !h.Cursor.HasSelection() {
+               h.Cursor.OrigSelection[0] = h.Cursor.Loc
+       }
+       h.Cursor.StartOfText()
+       h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
+       return true
+}
+
+// SelectToStartOfTextToggle toggles the selection between the start of the text
+// on the current line and the start of the line
+func (h *BufPane) SelectToStartOfTextToggle() bool {
+       if !h.Cursor.HasSelection() {
+               h.Cursor.OrigSelection[0] = h.Cursor.Loc
+       }
+       if h.Cursor.IsStartOfText() {
+               h.Cursor.Start()
+       } else {
+               h.Cursor.StartOfText()
+       }
+       h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -301,6 +360,7 @@ func (h *BufPane) SelectToStartOfLine() bool {
        }
        h.Cursor.Start()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -311,6 +371,7 @@ func (h *BufPane) SelectToEndOfLine() bool {
        }
        h.Cursor.End()
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -328,6 +389,7 @@ func (h *BufPane) ParagraphPrevious() bool {
        if line == 0 {
                h.Cursor.Loc = h.Buf.Start()
        }
+       h.Relocate()
        return true
 }
 
@@ -345,6 +407,7 @@ func (h *BufPane) ParagraphNext() bool {
        if line == h.Buf.LinesNum() {
                h.Cursor.Loc = h.Buf.End()
        }
+       h.Relocate()
        return true
 }
 
@@ -352,6 +415,7 @@ func (h *BufPane) ParagraphNext() bool {
 // on the user's settings
 func (h *BufPane) Retab() bool {
        h.Buf.Retab()
+       h.Relocate()
        return true
 }
 
@@ -360,6 +424,8 @@ func (h *BufPane) CursorStart() bool {
        h.Cursor.Deselect(true)
        h.Cursor.X = 0
        h.Cursor.Y = 0
+       h.Cursor.StoreVisualX()
+       h.Relocate()
        return true
 }
 
@@ -368,6 +434,7 @@ func (h *BufPane) CursorEnd() bool {
        h.Cursor.Deselect(true)
        h.Cursor.Loc = h.Buf.End()
        h.Cursor.StoreVisualX()
+       h.Relocate()
        return true
 }
 
@@ -378,6 +445,7 @@ func (h *BufPane) SelectToStart() bool {
        }
        h.CursorStart()
        h.Cursor.SelectTo(h.Buf.Start())
+       h.Relocate()
        return true
 }
 
@@ -388,6 +456,7 @@ func (h *BufPane) SelectToEnd() bool {
        }
        h.CursorEnd()
        h.Cursor.SelectTo(h.Buf.End())
+       h.Relocate()
        return true
 }
 
@@ -416,10 +485,11 @@ func (h *BufPane) InsertNewline() bool {
                // Remove the whitespaces if keepautoindent setting is off
                if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
                        line := h.Buf.LineBytes(h.Cursor.Y - 1)
-                       h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
+                       h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
                }
        }
        h.Cursor.LastVisualX = h.Cursor.GetVisualX()
+       h.Relocate()
        return true
 }
 
@@ -440,7 +510,7 @@ func (h *BufPane) Backspace() bool {
                // tab (tabSize number of spaces)
                lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
                tabSize := int(h.Buf.Settings["tabsize"].(float64))
-               if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
+               if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
                        loc := h.Cursor.Loc
                        h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
                } else {
@@ -449,6 +519,7 @@ func (h *BufPane) Backspace() bool {
                }
        }
        h.Cursor.LastVisualX = h.Cursor.GetVisualX()
+       h.Relocate()
        return true
 }
 
@@ -459,6 +530,7 @@ func (h *BufPane) DeleteWordRight() bool {
                h.Cursor.DeleteSelection()
                h.Cursor.ResetSelection()
        }
+       h.Relocate()
        return true
 }
 
@@ -469,6 +541,7 @@ func (h *BufPane) DeleteWordLeft() bool {
                h.Cursor.DeleteSelection()
                h.Cursor.ResetSelection()
        }
+       h.Relocate()
        return true
 }
 
@@ -483,6 +556,7 @@ func (h *BufPane) Delete() bool {
                        h.Buf.Remove(loc, loc.Move(1, h.Buf))
                }
        }
+       h.Relocate()
        return true
 }
 
@@ -503,21 +577,38 @@ func (h *BufPane) IndentSelection() bool {
                tabsize := int(h.Buf.Settings["tabsize"].(float64))
                indentsize := len(h.Buf.IndentString(tabsize))
                for y := startY; y <= endY; y++ {
-                       h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
-                       if y == startY && start.X > 0 {
-                               h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
-                       }
-                       if y == endY {
-                               h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
+                       if len(h.Buf.LineBytes(y)) > 0 {
+                               h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
+                               if y == startY && start.X > 0 {
+                                       h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
+                               }
+                               if y == endY {
+                                       h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
+                               }
                        }
                }
                h.Buf.RelocateCursors()
 
+               h.Relocate()
                return true
        }
        return false
 }
 
+// IndentLine moves the current line forward one indentation
+func (h *BufPane) IndentLine() bool {
+       if h.Cursor.HasSelection() {
+               return false
+       }
+
+       tabsize := int(h.Buf.Settings["tabsize"].(float64))
+       indentstr := h.Buf.IndentString(tabsize)
+       h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
+       h.Buf.RelocateCursors()
+       h.Relocate()
+       return true
+}
+
 // OutdentLine moves the current line back one indentation
 func (h *BufPane) OutdentLine() bool {
        if h.Cursor.HasSelection() {
@@ -531,6 +622,7 @@ func (h *BufPane) OutdentLine() bool {
                h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
        }
        h.Buf.RelocateCursors()
+       h.Relocate()
        return true
 }
 
@@ -557,29 +649,58 @@ func (h *BufPane) OutdentSelection() bool {
                }
                h.Buf.RelocateCursors()
 
+               h.Relocate()
                return true
        }
        return false
 }
 
-// InsertTab inserts a tab or spaces
-func (h *BufPane) InsertTab() bool {
+// Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
+func (h *BufPane) Autocomplete() bool {
        b := h.Buf
+
+       if h.Cursor.HasSelection() {
+               return false
+       }
+
+       if h.Cursor.X == 0 {
+               return false
+       }
+       r := h.Cursor.RuneUnder(h.Cursor.X)
+       prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
+       if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
+               // don't autocomplete if cursor is on alpha numeric character (middle of a word)
+               return false
+       }
+
        if b.HasSuggestions {
                b.CycleAutocomplete(true)
                return true
        }
+       return b.Autocomplete(buffer.BufferComplete)
+}
+
+// CycleAutocompleteBack cycles back in the autocomplete suggestion list
+func (h *BufPane) CycleAutocompleteBack() bool {
+       if h.Cursor.HasSelection() {
+               return false
+       }
 
-       l := b.LineBytes(h.Cursor.Y)
-       l = util.SliceStart(l, h.Cursor.X)
-       hasComplete := b.Autocomplete(buffer.BufferComplete)
-       if !hasComplete {
-               indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
-               tabBytes := len(indent)
-               bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
-               b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+       if h.Buf.HasSuggestions {
+               h.Buf.CycleAutocomplete(false)
                return true
        }
+       return false
+}
+
+// InsertTab inserts a tab or spaces
+func (h *BufPane) InsertTab() bool {
+       b := h.Buf
+       indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
+       tabBytes := len(indent)
+       bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
+       b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
+       h.Relocate()
        return true
 }
 
@@ -588,57 +709,89 @@ func (h *BufPane) SaveAll() bool {
        for _, b := range buffer.OpenBuffers {
                b.Save()
        }
-       return false
+       return true
 }
 
-// Save the buffer to disk
-func (h *BufPane) Save() bool {
+// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
+func (h *BufPane) SaveCB(action string, callback func()) bool {
        // If this is an empty buffer, ask for a filename
        if h.Buf.Path == "" {
-               h.SaveAs()
+               h.SaveAsCB(action, callback)
        } else {
-               h.saveBufToFile(h.Buf.Path)
+               noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
+               if noPrompt {
+                       return true
+               }
        }
-
        return false
 }
 
-// SaveAs saves the buffer to disk with the given name
-func (h *BufPane) SaveAs() bool {
+// Save the buffer to disk
+func (h *BufPane) Save() bool {
+       return h.SaveCB("Save", nil)
+}
+
+// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
+// The callback is only called if the save was successful
+func (h *BufPane) SaveAsCB(action string, callback func()) bool {
        InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
                if !canceled {
                        // the filename might or might not be quoted, so unquote first then join the strings.
-                       args, err := shellwords.Split(resp)
-                       filename := strings.Join(args, " ")
+                       args, err := shellquote.Split(resp)
                        if err != nil {
                                InfoBar.Error("Error parsing arguments: ", err)
                                return
                        }
-                       h.saveBufToFile(filename)
-
+                       if len(args) == 0 {
+                               InfoBar.Error("No filename given")
+                               return
+                       }
+                       filename := strings.Join(args, " ")
+                       noPrompt := h.saveBufToFile(filename, action, callback)
+                       if noPrompt {
+                               h.completeAction(action)
+                       }
                }
        })
        return false
 }
 
+// SaveAs saves the buffer to disk with the given name
+func (h *BufPane) SaveAs() bool {
+       return h.SaveAsCB("SaveAs", nil)
+}
+
 // This function saves the buffer to `filename` and changes the buffer's path and name
 // to `filename` if the save is successful
-func (h *BufPane) saveBufToFile(filename string) {
+// The callback is only called if the save was successful
+func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
        err := h.Buf.SaveAs(filename)
        if err != nil {
                if strings.HasSuffix(err.Error(), "permission denied") {
-                       InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
-                               if yes && !canceled {
-                                       err = h.Buf.SaveAsWithSudo(filename)
-                                       if err != nil {
-                                               InfoBar.Error(err)
-                                       } else {
-                                               h.Buf.Path = filename
-                                               h.Buf.SetName(filename)
-                                               InfoBar.Message("Saved " + filename)
+                       saveWithSudo := func() {
+                               err = h.Buf.SaveAsWithSudo(filename)
+                               if err != nil {
+                                       InfoBar.Error(err)
+                               } else {
+                                       h.Buf.Path = filename
+                                       h.Buf.SetName(filename)
+                                       InfoBar.Message("Saved " + filename)
+                                       if callback != nil {
+                                               callback()
                                        }
                                }
-                       })
+                       }
+                       if h.Buf.Settings["autosu"].(bool) {
+                               saveWithSudo()
+                       } else {
+                               InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
+                                       if yes && !canceled {
+                                               saveWithSudo()
+                                               h.completeAction(action)
+                                       }
+                               })
+                               return false
+                       }
                } else {
                        InfoBar.Error(err)
                }
@@ -646,15 +799,56 @@ func (h *BufPane) saveBufToFile(filename string) {
                h.Buf.Path = filename
                h.Buf.SetName(filename)
                InfoBar.Message("Saved " + filename)
+               if callback != nil {
+                       callback()
+               }
        }
+       return true
 }
 
 // Find opens a prompt and searches forward for the input
 func (h *BufPane) Find() bool {
+       return h.find(true)
+}
+
+// FindLiteral is the same as Find() but does not support regular expressions
+func (h *BufPane) FindLiteral() bool {
+       return h.find(false)
+}
+
+// Search searches for a given string/regex in the buffer and selects the next
+// match if a match is found
+// This function affects lastSearch and lastSearchRegex (saved searches) for
+// use with FindNext and FindPrevious
+func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
+       match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
+       if err != nil {
+               return err
+       }
+       if found {
+               h.Cursor.SetSelectionStart(match[0])
+               h.Cursor.SetSelectionEnd(match[1])
+               h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
+               h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
+               h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
+               h.lastSearch = str
+               h.lastSearchRegex = useRegex
+               h.Relocate()
+       } else {
+               h.Cursor.ResetSelection()
+       }
+       return nil
+}
+
+func (h *BufPane) find(useRegex bool) bool {
        h.searchOrig = h.Cursor.Loc
-       InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
+       prompt := "Find: "
+       if useRegex {
+               prompt = "Find (regex): "
+       }
+       InfoBar.Prompt(prompt, "", "Find", func(resp string) {
                // Event callback
-               match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
+               match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
                if found {
                        h.Cursor.SetSelectionStart(match[0])
                        h.Cursor.SetSelectionEnd(match[1])
@@ -669,7 +863,7 @@ func (h *BufPane) Find() bool {
        }, func(resp string, canceled bool) {
                // Finished callback
                if !canceled {
-                       match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
+                       match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
                        if err != nil {
                                InfoBar.Error(err)
                        }
@@ -680,6 +874,7 @@ func (h *BufPane) Find() bool {
                                h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
                                h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
                                h.lastSearch = resp
+                               h.lastSearchRegex = useRegex
                        } else {
                                h.Cursor.ResetSelection()
                                InfoBar.Message("No matches found")
@@ -690,7 +885,7 @@ func (h *BufPane) Find() bool {
                h.Relocate()
        })
 
-       return false
+       return true
 }
 
 // FindNext searches forwards for the last used search term
@@ -703,7 +898,7 @@ func (h *BufPane) FindNext() bool {
        if h.Cursor.HasSelection() {
                searchLoc = h.Cursor.CurSelection[1]
        }
-       match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
+       match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
        if err != nil {
                InfoBar.Error(err)
        }
@@ -716,6 +911,7 @@ func (h *BufPane) FindNext() bool {
        } else {
                h.Cursor.ResetSelection()
        }
+       h.Relocate()
        return true
 }
 
@@ -729,7 +925,7 @@ func (h *BufPane) FindPrevious() bool {
        if h.Cursor.HasSelection() {
                searchLoc = h.Cursor.CurSelection[0]
        }
-       match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
+       match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
        if err != nil {
                InfoBar.Error(err)
        }
@@ -742,6 +938,7 @@ func (h *BufPane) FindPrevious() bool {
        } else {
                h.Cursor.ResetSelection()
        }
+       h.Relocate()
        return true
 }
 
@@ -749,6 +946,7 @@ func (h *BufPane) FindPrevious() bool {
 func (h *BufPane) Undo() bool {
        h.Buf.Undo()
        InfoBar.Message("Undid action")
+       h.Relocate()
        return true
 }
 
@@ -756,16 +954,33 @@ func (h *BufPane) Undo() bool {
 func (h *BufPane) Redo() bool {
        h.Buf.Redo()
        InfoBar.Message("Redid action")
+       h.Relocate()
        return true
 }
 
 // Copy the selection to the system clipboard
 func (h *BufPane) Copy() bool {
        if h.Cursor.HasSelection() {
-               h.Cursor.CopySelection("clipboard")
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
                h.freshClip = true
                InfoBar.Message("Copied selection")
        }
+       h.Relocate()
+       return true
+}
+
+// Copy the current line to the clipboard
+func (h *BufPane) CopyLine() bool {
+       if h.Cursor.HasSelection() {
+               return false
+       } else {
+               h.Cursor.SelectLine()
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
+               h.freshClip = true
+               InfoBar.Message("Copied line")
+       }
+       h.Cursor.Deselect(true)
+       h.Relocate()
        return true
 }
 
@@ -777,10 +992,10 @@ func (h *BufPane) CutLine() bool {
        }
        if h.freshClip == true {
                if h.Cursor.HasSelection() {
-                       if clip, err := clipboard.ReadAll("clipboard"); err != nil {
-                               // messenger.Error(err)
+                       if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
+                               InfoBar.Error(err)
                        } else {
-                               clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
+                               clipboard.Write(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg)
                        }
                }
        } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
@@ -791,18 +1006,20 @@ func (h *BufPane) CutLine() bool {
        h.Cursor.DeleteSelection()
        h.Cursor.ResetSelection()
        InfoBar.Message("Cut line")
+       h.Relocate()
        return true
 }
 
 // Cut the selection to the system clipboard
 func (h *BufPane) Cut() bool {
        if h.Cursor.HasSelection() {
-               h.Cursor.CopySelection("clipboard")
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
                h.Cursor.DeleteSelection()
                h.Cursor.ResetSelection()
                h.freshClip = true
                InfoBar.Message("Cut selection")
 
+               h.Relocate()
                return true
        } else {
                return h.CutLine()
@@ -820,6 +1037,7 @@ func (h *BufPane) DuplicateLine() bool {
        }
 
        InfoBar.Message("Duplicated line")
+       h.Relocate()
        return true
 }
 
@@ -832,6 +1050,7 @@ func (h *BufPane) DeleteLine() bool {
        h.Cursor.DeleteSelection()
        h.Cursor.ResetSelection()
        InfoBar.Message("Deleted line")
+       h.Relocate()
        return true
 }
 
@@ -839,24 +1058,35 @@ func (h *BufPane) DeleteLine() bool {
 func (h *BufPane) MoveLinesUp() bool {
        if h.Cursor.HasSelection() {
                if h.Cursor.CurSelection[0].Y == 0 {
-                       InfoBar.Message("Can not move further up")
-                       return true
+                       InfoBar.Message("Cannot move further up")
+                       return false
                }
                start := h.Cursor.CurSelection[0].Y
                end := h.Cursor.CurSelection[1].Y
+               sel := 1
                if start > end {
                        end, start = start, end
+                       sel = 0
+               }
+
+               compensate := false
+               if h.Cursor.CurSelection[sel].X != 0 {
+                       end++
+               } else {
+                       compensate = true
                }
 
                h.Buf.MoveLinesUp(
                        start,
                        end,
                )
-               h.Cursor.CurSelection[1].Y -= 1
+               if compensate {
+                       h.Cursor.CurSelection[sel].Y -= 1
+               }
        } else {
                if h.Cursor.Loc.Y == 0 {
-                       InfoBar.Message("Can not move further up")
-                       return true
+                       InfoBar.Message("Cannot move further up")
+                       return false
                }
                h.Buf.MoveLinesUp(
                        h.Cursor.Loc.Y,
@@ -864,6 +1094,7 @@ func (h *BufPane) MoveLinesUp() bool {
                )
        }
 
+       h.Relocate()
        return true
 }
 
@@ -871,13 +1102,19 @@ func (h *BufPane) MoveLinesUp() bool {
 func (h *BufPane) MoveLinesDown() bool {
        if h.Cursor.HasSelection() {
                if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
-                       InfoBar.Message("Can not move further down")
-                       return true
+                       InfoBar.Message("Cannot move further down")
+                       return false
                }
                start := h.Cursor.CurSelection[0].Y
                end := h.Cursor.CurSelection[1].Y
+               sel := 1
                if start > end {
                        end, start = start, end
+                       sel = 0
+               }
+
+               if h.Cursor.CurSelection[sel].X != 0 {
+                       end++
                }
 
                h.Buf.MoveLinesDown(
@@ -886,8 +1123,8 @@ func (h *BufPane) MoveLinesDown() bool {
                )
        } else {
                if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
-                       InfoBar.Message("Can not move further down")
-                       return true
+                       InfoBar.Message("Cannot move further down")
+                       return false
                }
                h.Buf.MoveLinesDown(
                        h.Cursor.Loc.Y,
@@ -895,21 +1132,30 @@ func (h *BufPane) MoveLinesDown() bool {
                )
        }
 
+       h.Relocate()
        return true
 }
 
 // Paste whatever is in the system clipboard into the buffer
 // Delete and paste if the user has a selection
 func (h *BufPane) Paste() bool {
-       clip, _ := clipboard.ReadAll("clipboard")
+       clip, err := clipboard.Read(clipboard.ClipboardReg)
+       if err != nil {
+               InfoBar.Error(err)
+       }
        h.paste(clip)
+       h.Relocate()
        return true
 }
 
 // PastePrimary pastes from the primary clipboard (only use on linux)
 func (h *BufPane) PastePrimary() bool {
-       clip, _ := clipboard.ReadAll("primary")
+       clip, err := clipboard.Read(clipboard.PrimaryReg)
+       if err != nil {
+               InfoBar.Error(err)
+       }
        h.paste(clip)
+       h.Relocate()
        return true
 }
 
@@ -939,15 +1185,20 @@ func (h *BufPane) JumpToMatchingBrace() bool {
                r := h.Cursor.RuneUnder(h.Cursor.X)
                rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
                if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
-                       matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
-                       if left {
-                               h.Cursor.GotoLoc(matchingBrace)
+                       matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
+                       if found {
+                               if left {
+                                       h.Cursor.GotoLoc(matchingBrace)
+                               } else {
+                                       h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
+                               }
                        } else {
-                               h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
+                               return false
                        }
                }
        }
 
+       h.Relocate()
        return true
 }
 
@@ -958,6 +1209,7 @@ func (h *BufPane) SelectAll() bool {
        // Put the cursor at the beginning
        h.Cursor.X = 0
        h.Cursor.Y = 0
+       h.Relocate()
        return true
 }
 
@@ -968,7 +1220,17 @@ func (h *BufPane) OpenFile() bool {
                        h.HandleCommand(resp)
                }
        })
-       return false
+       return true
+}
+
+// OpenFile opens a new file in the buffer
+func (h *BufPane) JumpLine() bool {
+       InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
+               if !canceled {
+                       h.HandleCommand(resp)
+               }
+       })
+       return true
 }
 
 // Start moves the viewport to the start of the buffer
@@ -976,7 +1238,7 @@ func (h *BufPane) Start() bool {
        v := h.GetView()
        v.StartLine = 0
        h.SetView(v)
-       return false
+       return true
 }
 
 // End moves the viewport to the end of the buffer
@@ -990,7 +1252,7 @@ func (h *BufPane) End() bool {
                v.StartLine = h.Buf.LinesNum() - v.Height
                h.SetView(v)
        }
-       return false
+       return true
 }
 
 // PageUp scrolls the view up a page
@@ -1002,7 +1264,7 @@ func (h *BufPane) PageUp() bool {
                v.StartLine = 0
        }
        h.SetView(v)
-       return false
+       return true
 }
 
 // PageDown scrolls the view down a page
@@ -1013,7 +1275,7 @@ func (h *BufPane) PageDown() bool {
        } else if h.Buf.LinesNum() >= v.Height {
                v.StartLine = h.Buf.LinesNum() - v.Height
        }
-       return false
+       return true
 }
 
 // SelectPageUp selects up one page
@@ -1023,6 +1285,7 @@ func (h *BufPane) SelectPageUp() bool {
        }
        h.Cursor.UpN(h.GetView().Height)
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -1033,6 +1296,7 @@ func (h *BufPane) SelectPageDown() bool {
        }
        h.Cursor.DownN(h.GetView().Height)
        h.Cursor.SelectTo(h.Cursor.Loc)
+       h.Relocate()
        return true
 }
 
@@ -1046,6 +1310,7 @@ func (h *BufPane) CursorPageUp() bool {
                h.Cursor.StoreVisualX()
        }
        h.Cursor.UpN(h.GetView().Height)
+       h.Relocate()
        return true
 }
 
@@ -1059,6 +1324,7 @@ func (h *BufPane) CursorPageDown() bool {
                h.Cursor.StoreVisualX()
        }
        h.Cursor.DownN(h.GetView().Height)
+       h.Relocate()
        return true
 }
 
@@ -1071,7 +1337,7 @@ func (h *BufPane) HalfPageUp() bool {
                v.StartLine = 0
        }
        h.SetView(v)
-       return false
+       return true
 }
 
 // HalfPageDown scrolls the view down half a page
@@ -1085,7 +1351,22 @@ func (h *BufPane) HalfPageDown() bool {
                }
        }
        h.SetView(v)
-       return false
+       return true
+}
+
+// ToggleDiffGutter turns the diff gutter off and on
+func (h *BufPane) ToggleDiffGutter() bool {
+       if !h.Buf.Settings["diffgutter"].(bool) {
+               h.Buf.Settings["diffgutter"] = true
+               h.Buf.UpdateDiff(func(synchronous bool) {
+                       screen.Redraw()
+               })
+               InfoBar.Message("Enabled diff gutter")
+       } else {
+               h.Buf.Settings["diffgutter"] = false
+               InfoBar.Message("Disabled diff gutter")
+       }
+       return true
 }
 
 // ToggleRuler turns line numbers off and on
@@ -1097,13 +1378,13 @@ func (h *BufPane) ToggleRuler() bool {
                h.Buf.Settings["ruler"] = false
                InfoBar.Message("Disabled ruler")
        }
-       return false
+       return true
 }
 
 // ClearStatus clears the messenger bar
 func (h *BufPane) ClearStatus() bool {
        InfoBar.Message("")
-       return false
+       return true
 }
 
 // ToggleHelp toggles the help screen
@@ -1113,14 +1394,14 @@ func (h *BufPane) ToggleHelp() bool {
        } else {
                h.openHelp("help")
        }
-       return false
+       return true
 }
 
 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
 func (h *BufPane) ToggleKeyMenu() bool {
        config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
        Tabs.Resize()
-       return false
+       return true
 }
 
 // ShellMode opens a terminal to run a shell command
@@ -1132,7 +1413,7 @@ func (h *BufPane) ShellMode() bool {
                }
        })
 
-       return false
+       return true
 }
 
 // CommandMode lets the user enter a command
@@ -1142,18 +1423,30 @@ func (h *BufPane) CommandMode() bool {
                        h.HandleCommand(resp)
                }
        })
-       return false
+       return true
 }
 
 // ToggleOverwriteMode lets the user toggle the text overwrite mode
 func (h *BufPane) ToggleOverwriteMode() bool {
        h.isOverwriteMode = !h.isOverwriteMode
-       return false
+       return true
 }
 
 // Escape leaves current mode
 func (h *BufPane) Escape() bool {
-       return false
+       return true
+}
+
+// Deselect deselects on the current cursor
+func (h *BufPane) Deselect() bool {
+       h.Cursor.Deselect(true)
+       return true
+}
+
+// ClearInfo clears the infobar
+func (h *BufPane) ClearInfo() bool {
+       InfoBar.Message("")
+       return true
 }
 
 // Quit this will close the current tab or view that is open
@@ -1173,22 +1466,24 @@ func (h *BufPane) Quit() bool {
        if h.Buf.Modified() {
                if config.GlobalSettings["autosave"].(float64) > 0 {
                        // autosave on means we automatically save when quitting
-                       h.Save()
-                       quit()
+                       h.SaveCB("Quit", func() {
+                               quit()
+                       })
                } else {
                        InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
                                if !canceled && !yes {
                                        quit()
                                } else if !canceled && yes {
-                                       h.Save()
-                                       quit()
+                                       h.SaveCB("Quit", func() {
+                                               quit()
+                                       })
                                }
                        })
                }
        } else {
                quit()
        }
-       return false
+       return true
 }
 
 // QuitAll quits the whole editor; all splits and tabs
@@ -1220,7 +1515,7 @@ func (h *BufPane) QuitAll() bool {
                quit()
        }
 
-       return false
+       return true
 }
 
 // AddTab adds a new tab with an empty buffer
@@ -1232,74 +1527,80 @@ func (h *BufPane) AddTab() bool {
        Tabs.AddTab(tp)
        Tabs.SetActive(len(Tabs.List) - 1)
 
-       return false
+       return true
 }
 
 // PreviousTab switches to the previous tab in the tab list
 func (h *BufPane) PreviousTab() bool {
-       a := Tabs.Active()
-       Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
+       tabsLen := len(Tabs.List)
+       a := Tabs.Active() + tabsLen
+       Tabs.SetActive((a - 1) % tabsLen)
 
-       return false
+       return true
 }
 
 // NextTab switches to the next tab in the tab list
 func (h *BufPane) NextTab() bool {
        a := Tabs.Active()
-       Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
-       return false
+       Tabs.SetActive((a + 1) % len(Tabs.List))
+
+       return true
 }
 
 // VSplitAction opens an empty vertical split
 func (h *BufPane) VSplitAction() bool {
        h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
 
-       return false
+       return true
 }
 
 // HSplitAction opens an empty horizontal split
 func (h *BufPane) HSplitAction() bool {
        h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
 
-       return false
+       return true
 }
 
 // Unsplit closes all splits in the current tab except the active one
 func (h *BufPane) Unsplit() bool {
-       n := MainTab().GetNode(h.splitID)
-       n.Unsplit()
+       tab := h.tab
+       n := tab.GetNode(h.splitID)
+       ok := n.Unsplit()
+       if ok {
+               tab.RemovePane(tab.GetPane(h.splitID))
+               tab.Resize()
+               tab.SetActive(len(tab.Panes) - 1)
 
-       MainTab().RemovePane(MainTab().GetPane(h.splitID))
-       MainTab().Resize()
-       MainTab().SetActive(len(MainTab().Panes) - 1)
+               return true
+       }
        return false
 }
 
 // NextSplit changes the view to the next split
 func (h *BufPane) NextSplit() bool {
-       a := MainTab().active
-       if a < len(MainTab().Panes)-1 {
+       a := h.tab.active
+       if a < len(h.tab.Panes)-1 {
                a++
        } else {
                a = 0
        }
 
-       MainTab().SetActive(a)
+       h.tab.SetActive(a)
 
-       return false
+       return true
 }
 
 // PreviousSplit changes the view to the previous split
 func (h *BufPane) PreviousSplit() bool {
-       a := MainTab().active
+       a := h.tab.active
        if a > 0 {
                a--
        } else {
-               a = len(MainTab().Panes) - 1
+               a = len(h.tab.Panes) - 1
        }
-       MainTab().SetActive(a)
+       h.tab.SetActive(a)
 
-       return false
+       return true
 }
 
 var curmacro []interface{}
@@ -1314,6 +1615,7 @@ func (h *BufPane) ToggleMacro() bool {
        } else {
                InfoBar.Message("Stopped recording")
        }
+       h.Relocate()
        return true
 }
 
@@ -1326,10 +1628,11 @@ func (h *BufPane) PlayMacro() bool {
                switch t := action.(type) {
                case rune:
                        h.DoRuneInsert(t)
-               case Event:
-                       h.DoKeyEvent(t)
+               case func(*BufPane) bool:
+                       t(h)
                }
        }
+       h.Relocate()
        return true
 }
 
@@ -1339,6 +1642,7 @@ func (h *BufPane) SpawnMultiCursor() bool {
        if !spawner.HasSelection() {
                spawner.SelectWord()
                h.multiWord = true
+               h.Relocate()
                return true
        }
 
@@ -1369,6 +1673,42 @@ func (h *BufPane) SpawnMultiCursor() bool {
                InfoBar.Message("No matches found")
        }
 
+       h.Relocate()
+       return true
+}
+
+// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
+func (h *BufPane) SpawnMultiCursorUp() bool {
+       if h.Cursor.Y == 0 {
+               return false
+       } else {
+               h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
+               h.Cursor.Relocate()
+       }
+
+       c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
+       h.Buf.AddCursor(c)
+       h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
+       h.Buf.MergeCursors()
+
+       h.Relocate()
+       return true
+}
+
+// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
+func (h *BufPane) SpawnMultiCursorDown() bool {
+       if h.Cursor.Y+1 == h.Buf.LinesNum() {
+               return false
+       } else {
+               h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
+               h.Cursor.Relocate()
+       }
+
+       c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
+       h.Buf.AddCursor(c)
+       h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
+       h.Buf.MergeCursors()
+       h.Relocate()
        return true
 }
 
@@ -1403,19 +1743,19 @@ func (h *BufPane) SpawnMultiCursorSelect() bool {
                return false
        }
        InfoBar.Message("Added cursors from selection")
-       return false
+       return true
 }
 
 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
        b := h.Buf
        mx, my := e.Position()
-       mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
+       mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
        c := buffer.NewCursor(b, mouseLoc)
        b.AddCursor(c)
        b.MergeCursors()
 
-       return false
+       return true
 }
 
 // SkipMultiCursor moves the current multiple cursor to the next available position
@@ -1446,6 +1786,7 @@ func (h *BufPane) SkipMultiCursor() bool {
        } else {
                InfoBar.Message("No matches found")
        }
+       h.Relocate()
        return true
 }
 
@@ -1458,6 +1799,7 @@ func (h *BufPane) RemoveMultiCursor() bool {
        } else {
                h.multiWord = false
        }
+       h.Relocate()
        return true
 }
 
@@ -1465,9 +1807,11 @@ func (h *BufPane) RemoveMultiCursor() bool {
 func (h *BufPane) RemoveAllMultiCursors() bool {
        h.Buf.ClearCursors()
        h.multiWord = false
+       h.Relocate()
        return true
 }
 
+// None is an action that does nothing
 func (h *BufPane) None() bool {
-       return false
+       return true
 }