]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/bindings.go
Fix replace cursor relocation
[micro.git] / cmd / micro / bindings.go
index 7e6f708158c3f6da5b2133d85792279326f4eea9..56a9503a9de4c0e5c5450c984976ab7fc7347c68 100644 (file)
@@ -13,7 +13,7 @@ import (
        "github.com/zyedidia/tcell"
 )
 
-var bindings map[Key]func(*View) bool
+var bindings map[Key][]func(*View) bool
 var helpBinding string
 
 var bindingActions = map[string]func(*View) bool{
@@ -54,6 +54,7 @@ var bindingActions = map[string]func(*View) bool{
        "Cut":                 (*View).Cut,
        "CutLine":             (*View).CutLine,
        "DuplicateLine":       (*View).DuplicateLine,
+       "DeleteLine":          (*View).DeleteLine,
        "Paste":               (*View).Paste,
        "SelectAll":           (*View).SelectAll,
        "OpenFile":            (*View).OpenFile,
@@ -72,153 +73,140 @@ var bindingActions = map[string]func(*View) bool{
        "ShellMode":           (*View).ShellMode,
        "CommandMode":         (*View).CommandMode,
        "Quit":                (*View).Quit,
+       "AddTab":              (*View).AddTab,
+       "PreviousTab":         (*View).PreviousTab,
+       "NextTab":             (*View).NextTab,
 }
 
-var bindingKeys = map[string]Key{
-       "Up":             Key{tcell.KeyUp, tcell.ModNone, 0},
-       "Down":           Key{tcell.KeyDown, tcell.ModNone, 0},
-       "Right":          Key{tcell.KeyRight, tcell.ModNone, 0},
-       "Left":           Key{tcell.KeyLeft, tcell.ModNone, 0},
-       "AltUp":          Key{tcell.KeyUp, tcell.ModAlt, 0},
-       "AltDown":        Key{tcell.KeyDown, tcell.ModAlt, 0},
-       "AltLeft":        Key{tcell.KeyLeft, tcell.ModAlt, 0},
-       "AltRight":       Key{tcell.KeyRight, tcell.ModAlt, 0},
-       "CtrlUp":         Key{tcell.KeyUp, tcell.ModCtrl, 0},
-       "CtrlDown":       Key{tcell.KeyDown, tcell.ModCtrl, 0},
-       "CtrlLeft":       Key{tcell.KeyLeft, tcell.ModCtrl, 0},
-       "CtrlRight":      Key{tcell.KeyRight, tcell.ModCtrl, 0},
-       "ShiftUp":        Key{tcell.KeyUp, tcell.ModShift, 0},
-       "ShiftDown":      Key{tcell.KeyDown, tcell.ModShift, 0},
-       "ShiftLeft":      Key{tcell.KeyLeft, tcell.ModShift, 0},
-       "ShiftRight":     Key{tcell.KeyRight, tcell.ModShift, 0},
-       "AltShiftUp":     Key{tcell.KeyUp, tcell.ModShift | tcell.ModAlt, 0},
-       "AltShiftDown":   Key{tcell.KeyDown, tcell.ModShift | tcell.ModAlt, 0},
-       "AltShiftLeft":   Key{tcell.KeyLeft, tcell.ModShift | tcell.ModAlt, 0},
-       "AltShiftRight":  Key{tcell.KeyRight, tcell.ModShift | tcell.ModAlt, 0},
-       "CtrlShiftUp":    Key{tcell.KeyUp, tcell.ModShift | tcell.ModCtrl, 0},
-       "CtrlShiftDown":  Key{tcell.KeyDown, tcell.ModShift | tcell.ModCtrl, 0},
-       "CtrlShiftLeft":  Key{tcell.KeyLeft, tcell.ModShift | tcell.ModCtrl, 0},
-       "CtrlShiftRight": Key{tcell.KeyRight, tcell.ModShift | tcell.ModCtrl, 0},
-       "UpLeft":         Key{tcell.KeyUpLeft, tcell.ModNone, 0},
-       "UpRight":        Key{tcell.KeyUpRight, tcell.ModNone, 0},
-       "DownLeft":       Key{tcell.KeyDownLeft, tcell.ModNone, 0},
-       "DownRight":      Key{tcell.KeyDownRight, tcell.ModNone, 0},
-       "Center":         Key{tcell.KeyCenter, tcell.ModNone, 0},
-       "PgUp":           Key{tcell.KeyPgUp, tcell.ModNone, 0},
-       "PgDn":           Key{tcell.KeyPgDn, tcell.ModNone, 0},
-       "Home":           Key{tcell.KeyHome, tcell.ModNone, 0},
-       "End":            Key{tcell.KeyEnd, tcell.ModNone, 0},
-       "Insert":         Key{tcell.KeyInsert, tcell.ModNone, 0},
-       "Delete":         Key{tcell.KeyDelete, tcell.ModNone, 0},
-       "Help":           Key{tcell.KeyHelp, tcell.ModNone, 0},
-       "Exit":           Key{tcell.KeyExit, tcell.ModNone, 0},
-       "Clear":          Key{tcell.KeyClear, tcell.ModNone, 0},
-       "Cancel":         Key{tcell.KeyCancel, tcell.ModNone, 0},
-       "Print":          Key{tcell.KeyPrint, tcell.ModNone, 0},
-       "Pause":          Key{tcell.KeyPause, tcell.ModNone, 0},
-       "Backtab":        Key{tcell.KeyBacktab, tcell.ModNone, 0},
-       "F1":             Key{tcell.KeyF1, tcell.ModNone, 0},
-       "F2":             Key{tcell.KeyF2, tcell.ModNone, 0},
-       "F3":             Key{tcell.KeyF3, tcell.ModNone, 0},
-       "F4":             Key{tcell.KeyF4, tcell.ModNone, 0},
-       "F5":             Key{tcell.KeyF5, tcell.ModNone, 0},
-       "F6":             Key{tcell.KeyF6, tcell.ModNone, 0},
-       "F7":             Key{tcell.KeyF7, tcell.ModNone, 0},
-       "F8":             Key{tcell.KeyF8, tcell.ModNone, 0},
-       "F9":             Key{tcell.KeyF9, tcell.ModNone, 0},
-       "F10":            Key{tcell.KeyF10, tcell.ModNone, 0},
-       "F11":            Key{tcell.KeyF11, tcell.ModNone, 0},
-       "F12":            Key{tcell.KeyF12, tcell.ModNone, 0},
-       "F13":            Key{tcell.KeyF13, tcell.ModNone, 0},
-       "F14":            Key{tcell.KeyF14, tcell.ModNone, 0},
-       "F15":            Key{tcell.KeyF15, tcell.ModNone, 0},
-       "F16":            Key{tcell.KeyF16, tcell.ModNone, 0},
-       "F17":            Key{tcell.KeyF17, tcell.ModNone, 0},
-       "F18":            Key{tcell.KeyF18, tcell.ModNone, 0},
-       "F19":            Key{tcell.KeyF19, tcell.ModNone, 0},
-       "F20":            Key{tcell.KeyF20, tcell.ModNone, 0},
-       "F21":            Key{tcell.KeyF21, tcell.ModNone, 0},
-       "F22":            Key{tcell.KeyF22, tcell.ModNone, 0},
-       "F23":            Key{tcell.KeyF23, tcell.ModNone, 0},
-       "F24":            Key{tcell.KeyF24, tcell.ModNone, 0},
-       "F25":            Key{tcell.KeyF25, tcell.ModNone, 0},
-       "F26":            Key{tcell.KeyF26, tcell.ModNone, 0},
-       "F27":            Key{tcell.KeyF27, tcell.ModNone, 0},
-       "F28":            Key{tcell.KeyF28, tcell.ModNone, 0},
-       "F29":            Key{tcell.KeyF29, tcell.ModNone, 0},
-       "F30":            Key{tcell.KeyF30, tcell.ModNone, 0},
-       "F31":            Key{tcell.KeyF31, tcell.ModNone, 0},
-       "F32":            Key{tcell.KeyF32, tcell.ModNone, 0},
-       "F33":            Key{tcell.KeyF33, tcell.ModNone, 0},
-       "F34":            Key{tcell.KeyF34, tcell.ModNone, 0},
-       "F35":            Key{tcell.KeyF35, tcell.ModNone, 0},
-       "F36":            Key{tcell.KeyF36, tcell.ModNone, 0},
-       "F37":            Key{tcell.KeyF37, tcell.ModNone, 0},
-       "F38":            Key{tcell.KeyF38, tcell.ModNone, 0},
-       "F39":            Key{tcell.KeyF39, tcell.ModNone, 0},
-       "F40":            Key{tcell.KeyF40, tcell.ModNone, 0},
-       "F41":            Key{tcell.KeyF41, tcell.ModNone, 0},
-       "F42":            Key{tcell.KeyF42, tcell.ModNone, 0},
-       "F43":            Key{tcell.KeyF43, tcell.ModNone, 0},
-       "F44":            Key{tcell.KeyF44, tcell.ModNone, 0},
-       "F45":            Key{tcell.KeyF45, tcell.ModNone, 0},
-       "F46":            Key{tcell.KeyF46, tcell.ModNone, 0},
-       "F47":            Key{tcell.KeyF47, tcell.ModNone, 0},
-       "F48":            Key{tcell.KeyF48, tcell.ModNone, 0},
-       "F49":            Key{tcell.KeyF49, tcell.ModNone, 0},
-       "F50":            Key{tcell.KeyF50, tcell.ModNone, 0},
-       "F51":            Key{tcell.KeyF51, tcell.ModNone, 0},
-       "F52":            Key{tcell.KeyF52, tcell.ModNone, 0},
-       "F53":            Key{tcell.KeyF53, tcell.ModNone, 0},
-       "F54":            Key{tcell.KeyF54, tcell.ModNone, 0},
-       "F55":            Key{tcell.KeyF55, tcell.ModNone, 0},
-       "F56":            Key{tcell.KeyF56, tcell.ModNone, 0},
-       "F57":            Key{tcell.KeyF57, tcell.ModNone, 0},
-       "F58":            Key{tcell.KeyF58, tcell.ModNone, 0},
-       "F59":            Key{tcell.KeyF59, tcell.ModNone, 0},
-       "F60":            Key{tcell.KeyF60, tcell.ModNone, 0},
-       "F61":            Key{tcell.KeyF61, tcell.ModNone, 0},
-       "F62":            Key{tcell.KeyF62, tcell.ModNone, 0},
-       "F63":            Key{tcell.KeyF63, tcell.ModNone, 0},
-       "F64":            Key{tcell.KeyF64, tcell.ModNone, 0},
-       "CtrlSpace":      Key{tcell.KeyCtrlSpace, tcell.ModCtrl, 0},
-       "CtrlA":          Key{tcell.KeyCtrlA, tcell.ModCtrl, 0},
-       "CtrlB":          Key{tcell.KeyCtrlB, tcell.ModCtrl, 0},
-       "CtrlC":          Key{tcell.KeyCtrlC, tcell.ModCtrl, 0},
-       "CtrlD":          Key{tcell.KeyCtrlD, tcell.ModCtrl, 0},
-       "CtrlE":          Key{tcell.KeyCtrlE, tcell.ModCtrl, 0},
-       "CtrlF":          Key{tcell.KeyCtrlF, tcell.ModCtrl, 0},
-       "CtrlG":          Key{tcell.KeyCtrlG, tcell.ModCtrl, 0},
-       "CtrlH":          Key{tcell.KeyCtrlH, tcell.ModCtrl, 0},
-       "CtrlI":          Key{tcell.KeyCtrlI, tcell.ModCtrl, 0},
-       "CtrlJ":          Key{tcell.KeyCtrlJ, tcell.ModCtrl, 0},
-       "CtrlK":          Key{tcell.KeyCtrlK, tcell.ModCtrl, 0},
-       "CtrlL":          Key{tcell.KeyCtrlL, tcell.ModCtrl, 0},
-       "CtrlM":          Key{tcell.KeyCtrlM, tcell.ModCtrl, 0},
-       "CtrlN":          Key{tcell.KeyCtrlN, tcell.ModCtrl, 0},
-       "CtrlO":          Key{tcell.KeyCtrlO, tcell.ModCtrl, 0},
-       "CtrlP":          Key{tcell.KeyCtrlP, tcell.ModCtrl, 0},
-       "CtrlQ":          Key{tcell.KeyCtrlQ, tcell.ModCtrl, 0},
-       "CtrlR":          Key{tcell.KeyCtrlR, tcell.ModCtrl, 0},
-       "CtrlS":          Key{tcell.KeyCtrlS, tcell.ModCtrl, 0},
-       "CtrlT":          Key{tcell.KeyCtrlT, tcell.ModCtrl, 0},
-       "CtrlU":          Key{tcell.KeyCtrlU, tcell.ModCtrl, 0},
-       "CtrlV":          Key{tcell.KeyCtrlV, tcell.ModCtrl, 0},
-       "CtrlW":          Key{tcell.KeyCtrlW, tcell.ModCtrl, 0},
-       "CtrlX":          Key{tcell.KeyCtrlX, tcell.ModCtrl, 0},
-       "CtrlY":          Key{tcell.KeyCtrlY, tcell.ModCtrl, 0},
-       "CtrlZ":          Key{tcell.KeyCtrlZ, tcell.ModCtrl, 0},
-       "CtrlLeftSq":     Key{tcell.KeyCtrlLeftSq, tcell.ModCtrl, 0},
-       "CtrlBackslash":  Key{tcell.KeyCtrlBackslash, tcell.ModCtrl, 0},
-       "CtrlRightSq":    Key{tcell.KeyCtrlRightSq, tcell.ModCtrl, 0},
-       "CtrlCarat":      Key{tcell.KeyCtrlCarat, tcell.ModCtrl, 0},
-       "CtrlUnderscore": Key{tcell.KeyCtrlUnderscore, tcell.ModCtrl, 0},
-       "Backspace":      Key{tcell.KeyBackspace, tcell.ModNone, 0},
-       "Tab":            Key{tcell.KeyTab, tcell.ModNone, 0},
-       "Esc":            Key{tcell.KeyEsc, tcell.ModNone, 0},
-       "Escape":         Key{tcell.KeyEscape, tcell.ModNone, 0},
-       "Enter":          Key{tcell.KeyEnter, tcell.ModNone, 0},
-       "Backspace2":     Key{tcell.KeyBackspace2, tcell.ModNone, 0},
+var bindingKeys = map[string]tcell.Key{
+       "Up":             tcell.KeyUp,
+       "Down":           tcell.KeyDown,
+       "Right":          tcell.KeyRight,
+       "Left":           tcell.KeyLeft,
+       "UpLeft":         tcell.KeyUpLeft,
+       "UpRight":        tcell.KeyUpRight,
+       "DownLeft":       tcell.KeyDownLeft,
+       "DownRight":      tcell.KeyDownRight,
+       "Center":         tcell.KeyCenter,
+       "PageUp":         tcell.KeyPgUp,
+       "PageDown":       tcell.KeyPgDn,
+       "Home":           tcell.KeyHome,
+       "End":            tcell.KeyEnd,
+       "Insert":         tcell.KeyInsert,
+       "Delete":         tcell.KeyDelete,
+       "Help":           tcell.KeyHelp,
+       "Exit":           tcell.KeyExit,
+       "Clear":          tcell.KeyClear,
+       "Cancel":         tcell.KeyCancel,
+       "Print":          tcell.KeyPrint,
+       "Pause":          tcell.KeyPause,
+       "Backtab":        tcell.KeyBacktab,
+       "F1":             tcell.KeyF1,
+       "F2":             tcell.KeyF2,
+       "F3":             tcell.KeyF3,
+       "F4":             tcell.KeyF4,
+       "F5":             tcell.KeyF5,
+       "F6":             tcell.KeyF6,
+       "F7":             tcell.KeyF7,
+       "F8":             tcell.KeyF8,
+       "F9":             tcell.KeyF9,
+       "F10":            tcell.KeyF10,
+       "F11":            tcell.KeyF11,
+       "F12":            tcell.KeyF12,
+       "F13":            tcell.KeyF13,
+       "F14":            tcell.KeyF14,
+       "F15":            tcell.KeyF15,
+       "F16":            tcell.KeyF16,
+       "F17":            tcell.KeyF17,
+       "F18":            tcell.KeyF18,
+       "F19":            tcell.KeyF19,
+       "F20":            tcell.KeyF20,
+       "F21":            tcell.KeyF21,
+       "F22":            tcell.KeyF22,
+       "F23":            tcell.KeyF23,
+       "F24":            tcell.KeyF24,
+       "F25":            tcell.KeyF25,
+       "F26":            tcell.KeyF26,
+       "F27":            tcell.KeyF27,
+       "F28":            tcell.KeyF28,
+       "F29":            tcell.KeyF29,
+       "F30":            tcell.KeyF30,
+       "F31":            tcell.KeyF31,
+       "F32":            tcell.KeyF32,
+       "F33":            tcell.KeyF33,
+       "F34":            tcell.KeyF34,
+       "F35":            tcell.KeyF35,
+       "F36":            tcell.KeyF36,
+       "F37":            tcell.KeyF37,
+       "F38":            tcell.KeyF38,
+       "F39":            tcell.KeyF39,
+       "F40":            tcell.KeyF40,
+       "F41":            tcell.KeyF41,
+       "F42":            tcell.KeyF42,
+       "F43":            tcell.KeyF43,
+       "F44":            tcell.KeyF44,
+       "F45":            tcell.KeyF45,
+       "F46":            tcell.KeyF46,
+       "F47":            tcell.KeyF47,
+       "F48":            tcell.KeyF48,
+       "F49":            tcell.KeyF49,
+       "F50":            tcell.KeyF50,
+       "F51":            tcell.KeyF51,
+       "F52":            tcell.KeyF52,
+       "F53":            tcell.KeyF53,
+       "F54":            tcell.KeyF54,
+       "F55":            tcell.KeyF55,
+       "F56":            tcell.KeyF56,
+       "F57":            tcell.KeyF57,
+       "F58":            tcell.KeyF58,
+       "F59":            tcell.KeyF59,
+       "F60":            tcell.KeyF60,
+       "F61":            tcell.KeyF61,
+       "F62":            tcell.KeyF62,
+       "F63":            tcell.KeyF63,
+       "F64":            tcell.KeyF64,
+       "CtrlSpace":      tcell.KeyCtrlSpace,
+       "CtrlA":          tcell.KeyCtrlA,
+       "CtrlB":          tcell.KeyCtrlB,
+       "CtrlC":          tcell.KeyCtrlC,
+       "CtrlD":          tcell.KeyCtrlD,
+       "CtrlE":          tcell.KeyCtrlE,
+       "CtrlF":          tcell.KeyCtrlF,
+       "CtrlG":          tcell.KeyCtrlG,
+       "CtrlH":          tcell.KeyCtrlH,
+       "CtrlI":          tcell.KeyCtrlI,
+       "CtrlJ":          tcell.KeyCtrlJ,
+       "CtrlK":          tcell.KeyCtrlK,
+       "CtrlL":          tcell.KeyCtrlL,
+       "CtrlM":          tcell.KeyCtrlM,
+       "CtrlN":          tcell.KeyCtrlN,
+       "CtrlO":          tcell.KeyCtrlO,
+       "CtrlP":          tcell.KeyCtrlP,
+       "CtrlQ":          tcell.KeyCtrlQ,
+       "CtrlR":          tcell.KeyCtrlR,
+       "CtrlS":          tcell.KeyCtrlS,
+       "CtrlT":          tcell.KeyCtrlT,
+       "CtrlU":          tcell.KeyCtrlU,
+       "CtrlV":          tcell.KeyCtrlV,
+       "CtrlW":          tcell.KeyCtrlW,
+       "CtrlX":          tcell.KeyCtrlX,
+       "CtrlY":          tcell.KeyCtrlY,
+       "CtrlZ":          tcell.KeyCtrlZ,
+       "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
+       "CtrlBackslash":  tcell.KeyCtrlBackslash,
+       "CtrlRightSq":    tcell.KeyCtrlRightSq,
+       "CtrlCarat":      tcell.KeyCtrlCarat,
+       "CtrlUnderscore": tcell.KeyCtrlUnderscore,
+       "Backspace":      tcell.KeyBackspace,
+       "Tab":            tcell.KeyTab,
+       "Esc":            tcell.KeyEsc,
+       "Escape":         tcell.KeyEscape,
+       "Enter":          tcell.KeyEnter,
+       "Backspace2":     tcell.KeyBackspace2,
+
+       // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
+       "PgUp":   tcell.KeyPgUp,
+       "PgDown": tcell.KeyPgDn,
 }
 
 // The Key struct holds the data for a keypress (keycode + modifiers)
@@ -230,7 +218,7 @@ type Key struct {
 
 // InitBindings initializes the keybindings for micro
 func InitBindings() {
-       bindings = make(map[Key]func(*View) bool)
+       bindings = make(map[Key][]func(*View) bool)
 
        var parsed map[string]string
        defaults := DefaultBindings()
@@ -259,31 +247,97 @@ func parseBindings(userBindings map[string]string) {
        }
 }
 
-// BindKey takes a key and an action and binds the two together
-func BindKey(k, v string) {
-       var key Key
-       if strings.Contains(k, "Alt-") {
-               split := strings.Split(k, "-")
-               if len(split[1]) > 1 {
-                       key = Key{bindingKeys[split[1]].keyCode, bindingKeys[k].modifiers | tcell.ModAlt, 0}
-               } else {
-                       key = Key{tcell.KeyRune, tcell.ModAlt, rune(k[len(k)-1])}
+// findKey will find binding Key 'b' using string 'k'
+func findKey(k string) (b Key, ok bool) {
+       modifiers := tcell.ModNone
+
+       // First, we'll strip off all the modifiers in the name and add them to the
+       // ModMask
+modSearch:
+       for {
+               switch {
+               case strings.HasPrefix(k, "-"):
+                       // We optionally support dashes between modifiers
+                       k = k[1:]
+               case strings.HasPrefix(k, "Ctrl"):
+                       k = k[4:]
+                       modifiers |= tcell.ModCtrl
+               case strings.HasPrefix(k, "Alt"):
+                       k = k[3:]
+                       modifiers |= tcell.ModAlt
+               case strings.HasPrefix(k, "Shift"):
+                       k = k[5:]
+                       modifiers |= tcell.ModShift
+               default:
+                       break modSearch
                }
-       } else {
-               key = bindingKeys[k]
        }
 
-       action := bindingActions[v]
-       if _, ok := bindingActions[v]; !ok {
+       // Control is handled specially, since some character codes in bindingKeys
+       // are different when Control is depressed. We should check for Control keys
+       // first.
+       if modifiers&tcell.ModCtrl != 0 {
+               // see if the key is in bindingKeys with the Ctrl prefix.
+               if code, ok := bindingKeys["Ctrl"+k]; ok {
+                       // It is, we're done.
+                       return Key{
+                               keyCode:   code,
+                               modifiers: modifiers,
+                               r:         0,
+                       }, true
+               }
+       }
+
+       // See if we can find the key in bindingKeys
+       if code, ok := bindingKeys[k]; ok {
+               return Key{
+                       keyCode:   code,
+                       modifiers: modifiers,
+                       r:         0,
+               }, true
+       }
+
+       // If we were given one character, then we've got a rune.
+       if len(k) == 1 {
+               return Key{
+                       keyCode:   tcell.KeyRune,
+                       modifiers: modifiers,
+                       r:         rune(k[0]),
+               }, true
+       }
+
+       // We don't know what happened.
+       return Key{}, false
+}
+
+// findAction will find 'action' using string 'v'
+func findAction(v string) (action func(*View) bool) {
+       action, ok := bindingActions[v]
+       if !ok {
                // If the user seems to be binding a function that doesn't exist
                // We hope that it's a lua function that exists and bind it to that
                action = LuaFunctionBinding(v)
        }
+       return action
+}
 
-       bindings[key] = action
+// BindKey takes a key and an action and binds the two together
+func BindKey(k, v string) {
+       key, ok := findKey(k)
+       if !ok {
+               return
+       }
        if v == "ToggleHelp" {
                helpBinding = k
        }
+
+       actionNames := strings.Split(v, ",")
+       actions := make([]func(*View) bool, 0, len(actionNames))
+       for _, actionName := range actionNames {
+               actions = append(actions, findAction(actionName))
+       }
+
+       bindings[key] = actions
 }
 
 // DefaultBindings returns a map containing micro's default keybindings
@@ -329,10 +383,13 @@ func DefaultBindings() map[string]string {
                "CtrlD":          "DuplicateLine",
                "CtrlV":          "Paste",
                "CtrlA":          "SelectAll",
+               "CtrlT":          "AddTab",
+               "CtrlRightSq":    "PreviousTab",
+               "CtrlBackslash":  "NextTab",
                "Home":           "Start",
                "End":            "End",
-               "PgUp":           "PageUp",
-               "PgDn":           "PageDown",
+               "PageUp":         "CursorPageUp",
+               "PageDown":       "CursorPageDown",
                "CtrlG":          "ToggleHelp",
                "CtrlR":          "ToggleRuler",
                "CtrlL":          "JumpLine",
@@ -355,7 +412,7 @@ func DefaultBindings() map[string]string {
 // CursorUp moves the cursor up
 func (v *View) CursorUp() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[0])
+               v.Cursor.Loc = v.Cursor.CurSelection[0]
                v.Cursor.ResetSelection()
        }
        v.Cursor.Up()
@@ -365,7 +422,7 @@ func (v *View) CursorUp() bool {
 // CursorDown moves the cursor down
 func (v *View) CursorDown() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[1])
+               v.Cursor.Loc = v.Cursor.CurSelection[1]
                v.Cursor.ResetSelection()
        }
        v.Cursor.Down()
@@ -375,7 +432,7 @@ func (v *View) CursorDown() bool {
 // CursorLeft moves the cursor left
 func (v *View) CursorLeft() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[0])
+               v.Cursor.Loc = v.Cursor.CurSelection[0]
                v.Cursor.ResetSelection()
        } else {
                v.Cursor.Left()
@@ -386,7 +443,7 @@ func (v *View) CursorLeft() bool {
 // CursorRight moves the cursor right
 func (v *View) CursorRight() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[1] - 1)
+               v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
                v.Cursor.ResetSelection()
        } else {
                v.Cursor.Right()
@@ -408,75 +465,71 @@ func (v *View) WordLeft() bool {
 
 // SelectUp selects up one line
 func (v *View) SelectUp() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.Up()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectDown selects down one line
 func (v *View) SelectDown() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.Down()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectLeft selects the character to the left of the cursor
 func (v *View) SelectLeft() bool {
-       loc := v.Cursor.Loc()
-       count := v.Buf.Len() - 1
-       if loc > count {
+       loc := v.Cursor.Loc
+       count := v.Buf.End().Move(-1, v.Buf)
+       if loc.GreaterThan(count) {
                loc = count
        }
        if !v.Cursor.HasSelection() {
                v.Cursor.OrigSelection[0] = loc
        }
        v.Cursor.Left()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectRight selects the character to the right of the cursor
 func (v *View) SelectRight() bool {
-       loc := v.Cursor.Loc()
-       count := v.Buf.Len() - 1
-       if loc > count {
+       loc := v.Cursor.Loc
+       count := v.Buf.End().Move(-1, v.Buf)
+       if loc.GreaterThan(count) {
                loc = count
        }
        if !v.Cursor.HasSelection() {
                v.Cursor.OrigSelection[0] = loc
        }
        v.Cursor.Right()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectWordRight selects the word to the right of the cursor
 func (v *View) SelectWordRight() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.WordRight()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectWordLeft selects the word to the left of the cursor
 func (v *View) SelectWordLeft() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.WordLeft()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
@@ -494,23 +547,21 @@ func (v *View) EndOfLine() bool {
 
 // SelectToStartOfLine selects to the start of the current line
 func (v *View) SelectToStartOfLine() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.Start()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
 // SelectToEndOfLine selects to the end of the current line
 func (v *View) SelectToEndOfLine() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.Cursor.End()
-       v.Cursor.SelectTo(v.Cursor.Loc())
+       v.Cursor.SelectTo(v.Cursor.Loc)
        return true
 }
 
@@ -523,29 +574,27 @@ func (v *View) CursorStart() bool {
 
 // CursorEnd moves the cursor to the end of the buffer
 func (v *View) CursorEnd() bool {
-       v.Cursor.SetLoc(v.Buf.Len())
+       v.Cursor.Loc = v.Buf.End()
        return true
 }
 
 // SelectToStart selects the text from the cursor to the start of the buffer
 func (v *View) SelectToStart() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.CursorStart()
-       v.Cursor.SelectTo(0)
+       v.Cursor.SelectTo(v.Buf.Start())
        return true
 }
 
 // SelectToEnd selects the text from the cursor to the end of the buffer
 func (v *View) SelectToEnd() bool {
-       loc := v.Cursor.Loc()
        if !v.Cursor.HasSelection() {
-               v.Cursor.OrigSelection[0] = loc
+               v.Cursor.OrigSelection[0] = v.Cursor.Loc
        }
        v.CursorEnd()
-       v.Cursor.SelectTo(v.Buf.Len())
+       v.Cursor.SelectTo(v.Buf.End())
        return true
 }
 
@@ -555,7 +604,7 @@ func (v *View) InsertSpace() bool {
                v.Cursor.DeleteSelection()
                v.Cursor.ResetSelection()
        }
-       v.Buf.Insert(v.Cursor.Loc(), " ")
+       v.Buf.Insert(v.Cursor.Loc, " ")
        v.Cursor.Right()
        return true
 }
@@ -568,12 +617,12 @@ func (v *View) InsertEnter() bool {
                v.Cursor.ResetSelection()
        }
 
-       v.Buf.Insert(v.Cursor.Loc(), "\n")
-       ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.Y])
+       v.Buf.Insert(v.Cursor.Loc, "\n")
+       ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
        v.Cursor.Right()
 
        if settings["autoindent"].(bool) {
-               v.Buf.Insert(v.Cursor.Loc(), ws)
+               v.Buf.Insert(v.Cursor.Loc, ws)
                for i := 0; i < len(ws); i++ {
                        v.Cursor.Right()
                }
@@ -588,7 +637,7 @@ func (v *View) Backspace() bool {
        if v.Cursor.HasSelection() {
                v.Cursor.DeleteSelection()
                v.Cursor.ResetSelection()
-       } else if v.Cursor.Loc() > 0 {
+       } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
                // We have to do something a bit hacky here because we want to
                // delete the line by first moving left and then deleting backwards
                // but the undo redo would place the cursor in the wrong place
@@ -598,21 +647,21 @@ func (v *View) Backspace() bool {
                // 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
                // tab (tabSize number of spaces)
-               lineStart := v.Buf.Lines[v.Cursor.Y][:v.Cursor.X]
+               lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
                tabSize := int(settings["tabsize"].(float64))
                if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
-                       loc := v.Cursor.Loc()
-                       v.Cursor.SetLoc(loc - tabSize)
+                       loc := v.Cursor.Loc
+                       v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
                        cx, cy := v.Cursor.X, v.Cursor.Y
-                       v.Cursor.SetLoc(loc)
-                       v.Buf.Remove(loc-tabSize, loc)
+                       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-1, loc)
+                       loc := v.Cursor.Loc
+                       v.Buf.Remove(loc.Move(-1, v.Buf), loc)
                        v.Cursor.X, v.Cursor.Y = cx, cy
                }
        }
@@ -646,9 +695,9 @@ func (v *View) Delete() bool {
                v.Cursor.DeleteSelection()
                v.Cursor.ResetSelection()
        } else {
-               loc := v.Cursor.Loc()
-               if loc < v.Buf.Len() {
-                       v.Buf.Remove(loc, loc+1)
+               loc := v.Cursor.Loc
+               if loc.LessThan(v.Buf.End()) {
+                       v.Buf.Remove(loc, loc.Move(1, v.Buf))
                }
        }
        return true
@@ -663,12 +712,12 @@ func (v *View) InsertTab() bool {
        }
        if settings["tabstospaces"].(bool) {
                tabSize := int(settings["tabsize"].(float64))
-               v.Buf.Insert(v.Cursor.Loc(), Spaces(tabSize))
+               v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
                for i := 0; i < tabSize; i++ {
                        v.Cursor.Right()
                }
        } else {
-               v.Buf.Insert(v.Cursor.Loc(), "\t")
+               v.Buf.Insert(v.Cursor.Loc, "\t")
                v.Cursor.Right()
        }
        return true
@@ -682,29 +731,43 @@ func (v *View) Save() bool {
        }
        // If this is an empty buffer, ask for a filename
        if v.Buf.Path == "" {
-               filename, canceled := messenger.Prompt("Filename: ", "Save")
+               filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
                if !canceled {
                        v.Buf.Path = filename
                        v.Buf.Name = filename
                } else {
-                       return true
+                       return false
                }
        }
        err := v.Buf.Save()
        if err != nil {
-               messenger.Error(err.Error())
+               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()
+                               if err != nil {
+                                       messenger.Error(err.Error())
+                                       return false
+                               }
+                       }
+                       messenger.Reset()
+                       messenger.Clear()
+                       messenger.Message("Saved " + v.Buf.Path)
+               } else {
+                       messenger.Error(err.Error())
+               }
        } else {
                messenger.Message("Saved " + v.Buf.Path)
        }
-       return true
+       return false
 }
 
 // Find opens a prompt and searches forward for the input
 func (v *View) Find() bool {
        if v.Cursor.HasSelection() {
-               searchStart = v.Cursor.CurSelection[1]
+               searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
        } else {
-               searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
+               searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
        }
        BeginSearch()
        return true
@@ -713,9 +776,9 @@ func (v *View) Find() bool {
 // FindNext searches forwards for the last used search term
 func (v *View) FindNext() bool {
        if v.Cursor.HasSelection() {
-               searchStart = v.Cursor.CurSelection[1]
+               searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
        } else {
-               searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
+               searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
        }
        messenger.Message("Finding: " + lastSearch)
        Search(lastSearch, v, true)
@@ -725,9 +788,9 @@ func (v *View) FindNext() bool {
 // FindPrevious searches backwards for the last used search term
 func (v *View) FindPrevious() bool {
        if v.Cursor.HasSelection() {
-               searchStart = v.Cursor.CurSelection[0]
+               searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
        } else {
-               searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
+               searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
        }
        messenger.Message("Finding: " + lastSearch)
        Search(lastSearch, v, false)
@@ -798,12 +861,24 @@ func (v *View) Cut() bool {
 // DuplicateLine duplicates the current line
 func (v *View) DuplicateLine() bool {
        v.Cursor.End()
-       v.Buf.Insert(v.Cursor.Loc(), "\n"+v.Buf.Lines[v.Cursor.Y])
+       v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
        v.Cursor.Right()
        messenger.Message("Duplicated line")
        return true
 }
 
+// DeleteLine deletes the current line
+func (v *View) DeleteLine() bool {
+       v.Cursor.SelectLine()
+       if !v.Cursor.HasSelection() {
+               return false
+       }
+       v.Cursor.DeleteSelection()
+       v.Cursor.ResetSelection()
+       messenger.Message("Deleted line")
+       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() bool {
@@ -812,8 +887,8 @@ func (v *View) Paste() bool {
                v.Cursor.ResetSelection()
        }
        clip, _ := clipboard.ReadAll()
-       v.Buf.Insert(v.Cursor.Loc(), clip)
-       v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
+       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
@@ -821,8 +896,8 @@ func (v *View) Paste() bool {
 
 // SelectAll selects the entire buffer
 func (v *View) SelectAll() bool {
-       v.Cursor.CurSelection[0] = 0
-       v.Cursor.CurSelection[1] = v.Buf.Len()
+       v.Cursor.CurSelection[0] = v.Buf.Start()
+       v.Cursor.CurSelection[1] = v.Buf.End()
        // Put the cursor at the beginning
        v.Cursor.X = 0
        v.Cursor.Y = 0
@@ -832,9 +907,9 @@ func (v *View) SelectAll() bool {
 // OpenFile opens a new file in the buffer
 func (v *View) OpenFile() bool {
        if v.CanClose("Continue? (yes, no, save) ") {
-               filename, canceled := messenger.Prompt("File to open: ", "Open")
+               filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
                if canceled {
-                       return true
+                       return false
                }
                home, _ := homedir.Dir()
                filename = strings.Replace(filename, "~", home, 1)
@@ -842,12 +917,13 @@ func (v *View) OpenFile() bool {
 
                if err != nil {
                        messenger.Error(err.Error())
-                       return true
+                       return false
                }
-               buf := NewBuffer(string(file), filename)
+               buf := NewBuffer(file, filename)
                v.OpenBuffer(buf)
+               return true
        }
-       return true
+       return false
 }
 
 // Start moves the viewport to the start of the buffer
@@ -889,7 +965,7 @@ func (v *View) PageDown() bool {
 // CursorPageUp places the cursor a page up
 func (v *View) CursorPageUp() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[0])
+               v.Cursor.Loc = v.Cursor.CurSelection[0]
                v.Cursor.ResetSelection()
        }
        v.Cursor.UpN(v.height)
@@ -899,7 +975,7 @@ func (v *View) CursorPageUp() bool {
 // CursorPageDown places the cursor a page up
 func (v *View) CursorPageDown() bool {
        if v.Cursor.HasSelection() {
-               v.Cursor.SetLoc(v.Cursor.CurSelection[1])
+               v.Cursor.Loc = v.Cursor.CurSelection[1]
                v.Cursor.ResetSelection()
        }
        v.Cursor.DownN(v.height)
@@ -943,7 +1019,7 @@ func (v *View) ToggleRuler() bool {
 // JumpLine jumps to a line and moves the view accordingly.
 func (v *View) JumpLine() bool {
        // Prompt for line number
-       linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber")
+       linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
        if canceled {
                return false
        }
@@ -973,7 +1049,7 @@ func (v *View) ClearStatus() bool {
 func (v *View) ToggleHelp() bool {
        if !v.helpOpen {
                v.lastBuffer = v.Buf
-               helpBuffer := NewBuffer(helpTxt, "help.md")
+               helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
                helpBuffer.Name = "Help"
                v.helpOpen = true
                v.OpenBuffer(helpBuffer)
@@ -986,7 +1062,7 @@ func (v *View) ToggleHelp() bool {
 
 // ShellMode opens a terminal to run a shell command
 func (v *View) ShellMode() bool {
-       input, canceled := messenger.Prompt("$ ", "Shell")
+       input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
        if !canceled {
                // The true here is for openTerm to make the command interactive
                HandleShellCommand(input, true)
@@ -996,7 +1072,7 @@ func (v *View) ShellMode() bool {
 
 // CommandMode lets the user enter a command
 func (v *View) CommandMode() bool {
-       input, canceled := messenger.Prompt("> ", "Command")
+       input, canceled := messenger.Prompt("> ", "Command", NoCompletion)
        if !canceled {
                HandleCommand(input)
        }
@@ -1012,10 +1088,55 @@ func (v *View) Quit() bool {
                return v.ToggleHelp()
        }
        // Make sure not to quit if there are unsaved changes
-       if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
-               views[mainView].CloseBuffer()
-               screen.Fini()
-               os.Exit(0)
+       if v.CanClose("Quit anyway? (yes, no, save) ") {
+               v.CloseBuffer()
+               if len(tabs) > 1 {
+                       if len(tabs[v.TabNum].views) == 1 {
+                               tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
+                               for i, t := range tabs {
+                                       t.SetNum(i)
+                               }
+                               if curTab >= len(tabs) {
+                                       curTab--
+                               }
+                               if curTab == 0 {
+                                       CurView().Resize(screen.Size())
+                                       CurView().matches = Match(CurView())
+                               }
+                       }
+               } else {
+                       screen.Fini()
+                       os.Exit(0)
+               }
+       }
+       return false
+}
+
+func (v *View) AddTab() bool {
+       tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
+       tab.SetNum(len(tabs))
+       tabs = append(tabs, tab)
+       curTab++
+       if len(tabs) == 2 {
+               for _, t := range tabs {
+                       for _, v := range t.views {
+                               v.Resize(screen.Size())
+                       }
+               }
+       }
+       return true
+}
+
+func (v *View) PreviousTab() bool {
+       if curTab > 0 {
+               curTab--
+       }
+       return false
+}
+
+func (v *View) NextTab() bool {
+       if curTab < len(tabs)-1 {
+               curTab++
        }
        return false
 }