]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/bindings.go
Merge pull request #316 from elopio/snapcraft
[micro.git] / cmd / micro / bindings.go
index c6b1a866aff2beafc7a5b40a5e485024f860c927..da6328bebb68279811b779e37c42c2b2982268a6 100644 (file)
 package main
 
 import (
-       "encoding/json"
-       "errors"
        "io/ioutil"
        "os"
-       "os/exec"
        "strings"
-       "time"
 
-       "github.com/mitchellh/go-homedir"
-       "github.com/zyedidia/clipboard"
+       "github.com/yosuke-furukawa/json5/encoding/json5"
        "github.com/zyedidia/tcell"
 )
 
-var bindings map[tcell.Key]func(*View) bool
+var bindings map[Key][]func(*View, bool) bool
+var helpBinding string
+
+var bindingActions = map[string]func(*View, bool) bool{
+       "CursorUp":            (*View).CursorUp,
+       "CursorDown":          (*View).CursorDown,
+       "CursorPageUp":        (*View).CursorPageUp,
+       "CursorPageDown":      (*View).CursorPageDown,
+       "CursorLeft":          (*View).CursorLeft,
+       "CursorRight":         (*View).CursorRight,
+       "CursorStart":         (*View).CursorStart,
+       "CursorEnd":           (*View).CursorEnd,
+       "SelectToStart":       (*View).SelectToStart,
+       "SelectToEnd":         (*View).SelectToEnd,
+       "SelectUp":            (*View).SelectUp,
+       "SelectDown":          (*View).SelectDown,
+       "SelectLeft":          (*View).SelectLeft,
+       "SelectRight":         (*View).SelectRight,
+       "WordRight":           (*View).WordRight,
+       "WordLeft":            (*View).WordLeft,
+       "SelectWordRight":     (*View).SelectWordRight,
+       "SelectWordLeft":      (*View).SelectWordLeft,
+       "DeleteWordRight":     (*View).DeleteWordRight,
+       "DeleteWordLeft":      (*View).DeleteWordLeft,
+       "SelectToStartOfLine": (*View).SelectToStartOfLine,
+       "SelectToEndOfLine":   (*View).SelectToEndOfLine,
+       "InsertNewline":       (*View).InsertNewline,
+       "InsertSpace":         (*View).InsertSpace,
+       "Backspace":           (*View).Backspace,
+       "Delete":              (*View).Delete,
+       "InsertTab":           (*View).InsertTab,
+       "Save":                (*View).Save,
+       "Find":                (*View).Find,
+       "FindNext":            (*View).FindNext,
+       "FindPrevious":        (*View).FindPrevious,
+       "Center":              (*View).Center,
+       "Undo":                (*View).Undo,
+       "Redo":                (*View).Redo,
+       "Copy":                (*View).Copy,
+       "Cut":                 (*View).Cut,
+       "CutLine":             (*View).CutLine,
+       "DuplicateLine":       (*View).DuplicateLine,
+       "DeleteLine":          (*View).DeleteLine,
+       "IndentSelection":     (*View).IndentSelection,
+       "OutdentSelection":    (*View).OutdentSelection,
+       "Paste":               (*View).Paste,
+       "PastePrimary":        (*View).PastePrimary,
+       "SelectAll":           (*View).SelectAll,
+       "OpenFile":            (*View).OpenFile,
+       "Start":               (*View).Start,
+       "End":                 (*View).End,
+       "PageUp":              (*View).PageUp,
+       "PageDown":            (*View).PageDown,
+       "HalfPageUp":          (*View).HalfPageUp,
+       "HalfPageDown":        (*View).HalfPageDown,
+       "StartOfLine":         (*View).StartOfLine,
+       "EndOfLine":           (*View).EndOfLine,
+       "ToggleHelp":          (*View).ToggleHelp,
+       "ToggleRuler":         (*View).ToggleRuler,
+       "JumpLine":            (*View).JumpLine,
+       "ClearStatus":         (*View).ClearStatus,
+       "ShellMode":           (*View).ShellMode,
+       "CommandMode":         (*View).CommandMode,
+       "Quit":                (*View).Quit,
+       "QuitAll":             (*View).QuitAll,
+       "AddTab":              (*View).AddTab,
+       "PreviousTab":         (*View).PreviousTab,
+       "NextTab":             (*View).NextTab,
+       "NextSplit":           (*View).NextSplit,
+       "PreviousSplit":       (*View).PreviousSplit,
+       "ToggleMacro":         (*View).ToggleMacro,
+       "PlayMacro":           (*View).PlayMacro,
+
+       // This was changed to InsertNewline but I don't want to break backwards compatibility
+       "InsertEnter": (*View).InsertNewline,
+}
+
+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)
+type Key struct {
+       keyCode   tcell.Key
+       modifiers tcell.ModMask
+       r         rune
+}
 
 // InitBindings initializes the keybindings for micro
 func InitBindings() {
-       bindings = make(map[tcell.Key]func(*View) bool)
-
-       actions := map[string]func(*View) bool{
-               "CursorUp":            (*View).CursorUp,
-               "CursorDown":          (*View).CursorDown,
-               "CursorLeft":          (*View).CursorLeft,
-               "CursorRight":         (*View).CursorRight,
-               "CursorStart":         (*View).CursorStart,
-               "CursorEnd":           (*View).CursorEnd,
-               "SelectToStart":       (*View).SelectToStart,
-               "SelectToEnd":         (*View).SelectToEnd,
-               "SelectUp":            (*View).SelectUp,
-               "SelectDown":          (*View).SelectDown,
-               "SelectLeft":          (*View).SelectLeft,
-               "SelectRight":         (*View).SelectRight,
-               "WordRight":           (*View).WordRight,
-               "WordLeft":            (*View).WordLeft,
-               "SelectWordRight":     (*View).SelectWordRight,
-               "SelectWordLeft":      (*View).SelectWordLeft,
-               "SelectToStartOfLine": (*View).SelectToStartOfLine,
-               "SelectToEndOfLine":   (*View).SelectToEndOfLine,
-               "InsertEnter":         (*View).InsertEnter,
-               "InsertSpace":         (*View).InsertSpace,
-               "Backspace":           (*View).Backspace,
-               "Delete":              (*View).Delete,
-               "InsertTab":           (*View).InsertTab,
-               "Save":                (*View).Save,
-               "Find":                (*View).Find,
-               "FindNext":            (*View).FindNext,
-               "FindPrevious":        (*View).FindPrevious,
-               "Undo":                (*View).Undo,
-               "Redo":                (*View).Redo,
-               "Copy":                (*View).Copy,
-               "Cut":                 (*View).Cut,
-               "CutLine":             (*View).CutLine,
-               "Paste":               (*View).Paste,
-               "SelectAll":           (*View).SelectAll,
-               "OpenFile":            (*View).OpenFile,
-               "Start":               (*View).Start,
-               "End":                 (*View).End,
-               "PageUp":              (*View).PageUp,
-               "PageDown":            (*View).PageDown,
-               "HalfPageUp":          (*View).HalfPageUp,
-               "HalfPageDown":        (*View).HalfPageDown,
-               "StartOfLine":         (*View).StartOfLine,
-               "EndOfLine":           (*View).EndOfLine,
-               "ToggleRuler":         (*View).ToggleRuler,
-       }
-
-       keys := map[string]tcell.Key{
-               "Up":             tcell.KeyUp,
-               "Down":           tcell.KeyDown,
-               "Right":          tcell.KeyRight,
-               "Left":           tcell.KeyLeft,
-               "AltUp":          tcell.KeyAltUp,
-               "AltDown":        tcell.KeyAltDown,
-               "AltLeft":        tcell.KeyAltLeft,
-               "AltRight":       tcell.KeyAltRight,
-               "CtrlUp":         tcell.KeyCtrlUp,
-               "CtrlDown":       tcell.KeyCtrlDown,
-               "CtrlLeft":       tcell.KeyCtrlLeft,
-               "CtrlRight":      tcell.KeyCtrlRight,
-               "ShiftUp":        tcell.KeyShiftUp,
-               "ShiftDown":      tcell.KeyShiftDown,
-               "ShiftLeft":      tcell.KeyShiftLeft,
-               "ShiftRight":     tcell.KeyShiftRight,
-               "AltShiftUp":     tcell.KeyAltShiftUp,
-               "AltShiftDown":   tcell.KeyAltShiftDown,
-               "AltShiftLeft":   tcell.KeyAltShiftLeft,
-               "AltShiftRight":  tcell.KeyAltShiftRight,
-               "CtrlShiftUp":    tcell.KeyCtrlShiftUp,
-               "CtrlShiftDown":  tcell.KeyCtrlShiftDown,
-               "CtrlShiftLeft":  tcell.KeyCtrlShiftLeft,
-               "CtrlShiftRight": tcell.KeyCtrlShiftRight,
-               "UpLeft":         tcell.KeyUpLeft,
-               "UpRight":        tcell.KeyUpRight,
-               "DownLeft":       tcell.KeyDownLeft,
-               "DownRight":      tcell.KeyDownRight,
-               "Center":         tcell.KeyCenter,
-               "PgUp":           tcell.KeyPgUp,
-               "PgDn":           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,
-               "Space":          tcell.KeySpace,
-               "Backspace2":     tcell.KeyBackspace2,
-       }
+       bindings = make(map[Key][]func(*View, bool) bool)
 
        var parsed map[string]string
        defaults := DefaultBindings()
@@ -226,18 +239,113 @@ func InitBindings() {
                        return
                }
 
-               err = json.Unmarshal(input, &parsed)
+               err = json5.Unmarshal(input, &parsed)
                if err != nil {
                        TermMessage("Error reading bindings.json:", err.Error())
                }
        }
 
-       for k, v := range defaults {
-               bindings[keys[k]] = actions[v]
+       parseBindings(defaults)
+       parseBindings(parsed)
+}
+
+func parseBindings(userBindings map[string]string) {
+       for k, v := range userBindings {
+               BindKey(k, v)
+       }
+}
+
+// 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
+               }
+       }
+
+       // 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
        }
-       for k, v := range parsed {
-               bindings[keys[k]] = actions[v]
+
+       // We don't know what happened.
+       return Key{}, false
+}
+
+// findAction will find 'action' using string 'v'
+func findAction(v string) (action func(*View, bool) 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
+}
+
+// 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) 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
@@ -263,11 +371,14 @@ func DefaultBindings() map[string]string {
                "CtrlDown":       "CursorEnd",
                "CtrlShiftUp":    "SelectToStart",
                "CtrlShiftDown":  "SelectToEnd",
-               "Enter":          "InsertEnter",
+               "Enter":          "InsertNewline",
                "Space":          "InsertSpace",
                "Backspace":      "Backspace",
                "Backspace2":     "Backspace",
-               "Tab":            "InsertTab",
+               "Alt-Backspace":  "DeleteWordLeft",
+               "Alt-Backspace2": "DeleteWordLeft",
+               "Tab":            "IndentSelection,InsertTab",
+               "Backtab":        "OutdentSelection",
                "CtrlO":          "OpenFile",
                "CtrlS":          "Save",
                "CtrlF":          "Find",
@@ -278,593 +389,36 @@ func DefaultBindings() map[string]string {
                "CtrlC":          "Copy",
                "CtrlX":          "Cut",
                "CtrlK":          "CutLine",
+               "CtrlD":          "DuplicateLine",
                "CtrlV":          "Paste",
                "CtrlA":          "SelectAll",
-               "Home":           "Start",
-               "End":            "End",
-               "PgUp":           "PageUp",
-               "PgDn":           "PageDown",
-               "CtrlU":          "HalfPageUp",
-               "CtrlD":          "HalfPageDown",
+               "CtrlT":          "AddTab",
+               "CtrlRightSq":    "PreviousTab",
+               "CtrlBackslash":  "NextTab",
+               "Home":           "StartOfLine",
+               "End":            "EndOfLine",
+               "CtrlHome":       "CursorStart",
+               "CtrlEnd":        "CursorEnd",
+               "PageUp":         "CursorPageUp",
+               "PageDown":       "CursorPageDown",
+               "CtrlG":          "ToggleHelp",
                "CtrlR":          "ToggleRuler",
+               "CtrlL":          "JumpLine",
                "Delete":         "Delete",
+               "Esc":            "ClearStatus",
+               "CtrlB":          "ShellMode",
+               "CtrlQ":          "Quit",
+               "CtrlE":          "CommandMode",
+               "CtrlW":          "NextSplit",
+               "CtrlU":          "ToggleMacro",
+               "CtrlJ":          "PlayMacro",
+
+               // Emacs-style keybindings
+               "Alt-f": "WordRight",
+               "Alt-b": "WordLeft",
+               "Alt-a": "StartOfLine",
+               "Alt-e": "EndOfLine",
+               "Alt-p": "CursorUp",
+               "Alt-n": "CursorDown",
        }
 }
-
-// CursorUp moves the cursor up
-func (v *View) CursorUp() bool {
-       v.cursor.ResetSelection()
-       v.cursor.Up()
-       return true
-}
-
-// CursorDown moves the cursor down
-func (v *View) CursorDown() bool {
-       v.cursor.ResetSelection()
-       v.cursor.Down()
-       return true
-}
-
-// CursorLeft moves the cursor left
-func (v *View) CursorLeft() bool {
-       if v.cursor.HasSelection() {
-               v.cursor.SetLoc(v.cursor.curSelection[0])
-               v.cursor.ResetSelection()
-       } else {
-               v.cursor.Left()
-       }
-       return true
-}
-
-// CursorRight moves the cursor right
-func (v *View) CursorRight() bool {
-       if v.cursor.HasSelection() {
-               v.cursor.SetLoc(v.cursor.curSelection[1] - 1)
-               v.cursor.ResetSelection()
-       } else {
-               v.cursor.Right()
-       }
-       return true
-}
-
-// WordRight moves the cursor one word to the right
-func (v *View) WordRight() bool {
-       v.cursor.WordRight()
-       return true
-}
-
-// WordLeft moves the cursor one word to the left
-func (v *View) WordLeft() bool {
-       v.cursor.WordLeft()
-       return true
-}
-
-// 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.Up()
-       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.Down()
-       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 := Count(v.buf.text) - 1
-       if loc > count {
-               loc = count
-       }
-       if !v.cursor.HasSelection() {
-               v.cursor.origSelection[0] = loc
-       }
-       v.cursor.Left()
-       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 := Count(v.buf.text) - 1
-       if loc > count {
-               loc = count
-       }
-       if !v.cursor.HasSelection() {
-               v.cursor.origSelection[0] = loc
-       }
-       v.cursor.Right()
-       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.WordRight()
-       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.WordLeft()
-       v.cursor.SelectTo(v.cursor.Loc())
-       return true
-}
-
-// StartOfLine moves the cursor to the start of the line
-func (v *View) StartOfLine() bool {
-       v.cursor.Start()
-       return true
-}
-
-// EndOfLine moves the cursor to the end of the line
-func (v *View) EndOfLine() bool {
-       v.cursor.End()
-       return true
-}
-
-// 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.Start()
-       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.End()
-       v.cursor.SelectTo(v.cursor.Loc())
-       return true
-}
-
-// CursorStart moves the cursor to the start of the buffer
-func (v *View) CursorStart() bool {
-       v.cursor.x = 0
-       v.cursor.y = 0
-       return true
-}
-
-// CursorEnd moves the cursor to the end of the buffer
-func (v *View) CursorEnd() bool {
-       v.cursor.SetLoc(len(v.buf.text))
-       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.CursorStart()
-       v.cursor.SelectTo(0)
-       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.CursorEnd()
-       v.cursor.SelectTo(len(v.buf.text))
-       return true
-}
-
-// InsertSpace inserts a space
-func (v *View) InsertSpace() bool {
-       // Insert a space
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       }
-       v.eh.Insert(v.cursor.Loc(), " ")
-       v.cursor.Right()
-       return true
-}
-
-// InsertEnter inserts a newline plus possible some whitespace if autoindent is on
-func (v *View) InsertEnter() bool {
-       // Insert a newline
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       }
-
-       v.eh.Insert(v.cursor.Loc(), "\n")
-       ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
-       v.cursor.Right()
-
-       if settings["autoindent"].(bool) {
-               v.eh.Insert(v.cursor.Loc(), ws)
-               for i := 0; i < len(ws); i++ {
-                       v.cursor.Right()
-               }
-       }
-       v.cursor.lastVisualX = v.cursor.GetVisualX()
-       return true
-}
-
-// Backspace deletes the previous character
-func (v *View) Backspace() bool {
-       // Delete a character
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       } else if v.cursor.Loc() > 0 {
-               // 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
-               // So instead we move left, save the position, move back, delete
-               // 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
-               // tab (tabSize number of spaces)
-               lineStart := v.buf.lines[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)
-                       cx, cy := v.cursor.x, v.cursor.y
-                       v.cursor.SetLoc(loc)
-                       v.eh.Remove(loc-tabSize, loc)
-                       v.cursor.x, v.cursor.y = cx, cy
-               } else {
-                       v.cursor.Left()
-                       cx, cy := v.cursor.x, v.cursor.y
-                       v.cursor.Right()
-                       loc := v.cursor.Loc()
-                       v.eh.Remove(loc-1, loc)
-                       v.cursor.x, v.cursor.y = cx, cy
-               }
-       }
-       v.cursor.lastVisualX = v.cursor.GetVisualX()
-       return true
-}
-
-// Delete deletes the next character
-func (v *View) Delete() bool {
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       } else {
-               loc := v.cursor.Loc()
-               if loc < len(v.buf.text) {
-                       v.eh.Remove(loc, loc+1)
-               }
-       }
-       return true
-}
-
-// InsertTab inserts a tab or spaces
-func (v *View) InsertTab() bool {
-       // Insert a tab
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       }
-       if settings["tabsToSpaces"].(bool) {
-               tabSize := int(settings["tabsize"].(float64))
-               v.eh.Insert(v.cursor.Loc(), Spaces(tabSize))
-               for i := 0; i < tabSize; i++ {
-                       v.cursor.Right()
-               }
-       } else {
-               v.eh.Insert(v.cursor.Loc(), "\t")
-               v.cursor.Right()
-       }
-       return true
-}
-
-// Save the buffer to disk
-func (v *View) Save() bool {
-       // If this is an empty buffer, ask for a filename
-       if v.buf.path == "" {
-               filename, canceled := messenger.Prompt("Filename: ")
-               if !canceled {
-                       v.buf.path = filename
-                       v.buf.name = filename
-               } else {
-                       return true
-               }
-       }
-       err := v.buf.Save()
-       if err != nil {
-               messenger.Error(err.Error())
-       } else {
-               messenger.Message("Saved " + v.buf.path)
-               switch v.buf.filetype {
-               case "Go":
-                       v.GoSave()
-               }
-       }
-       return true
-}
-
-// GoSave saves the current file (must be a go file) and runs goimports or gofmt
-// depending on the user's configuration
-func (v *View) GoSave() {
-       if settings["goimports"] == true {
-               messenger.Message("Running goimports...")
-               err := goimports(v.buf.path)
-               if err != nil {
-                       messenger.Error(err)
-               } else {
-                       messenger.Message("Saved " + v.buf.path)
-               }
-               v.reOpen()
-       } else if settings["gofmt"] == true {
-               messenger.Message("Running gofmt...")
-               err := gofmt(v.buf.path)
-               if err != nil {
-                       messenger.Error(err)
-               } else {
-                       messenger.Message("Saved " + v.buf.path)
-               }
-               v.reOpen()
-               return
-       }
-
-       return
-}
-
-// Find opens a prompt and searches forward for the input
-func (v *View) Find() bool {
-       if v.cursor.HasSelection() {
-               searchStart = v.cursor.curSelection[1]
-       } else {
-               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-       }
-       BeginSearch()
-       return true
-}
-
-// FindNext searches forwards for the last used search term
-func (v *View) FindNext() bool {
-       if v.cursor.HasSelection() {
-               searchStart = v.cursor.curSelection[1]
-       } else {
-               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-       }
-       messenger.Message("Find: " + lastSearch)
-       Search(lastSearch, v, true)
-       return true
-}
-
-// FindPrevious searches backwards for the last used search term
-func (v *View) FindPrevious() bool {
-       if v.cursor.HasSelection() {
-               searchStart = v.cursor.curSelection[0]
-       } else {
-               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-       }
-       messenger.Message("Find: " + lastSearch)
-       Search(lastSearch, v, false)
-       return true
-}
-
-// Undo undoes the last action
-func (v *View) Undo() bool {
-       v.eh.Undo()
-       return true
-}
-
-// Redo redoes the last action
-func (v *View) Redo() bool {
-       v.eh.Redo()
-       return true
-}
-
-// Copy the selection to the system clipboard
-func (v *View) Copy() bool {
-       if v.cursor.HasSelection() {
-               clipboard.WriteAll(v.cursor.GetSelection())
-               v.freshClip = true
-       }
-       return true
-}
-
-// CutLine cuts the current line to the clipboard
-func (v *View) CutLine() bool {
-       v.cursor.SelectLine()
-       if v.freshClip == true {
-
-               if v.cursor.HasSelection() {
-                       if clip, err := clipboard.ReadAll(); err != nil {
-                               messenger.Error(err)
-                       } else {
-                               clipboard.WriteAll(clip + v.cursor.GetSelection())
-                       }
-               }
-       } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
-               v.Copy()
-       }
-       v.freshClip = true
-       v.lastCutTime = time.Now()
-       v.cursor.DeleteSelection()
-       v.cursor.ResetSelection()
-       return true
-}
-
-// Cut the selection to the system clipboard
-func (v *View) Cut() bool {
-       if v.cursor.HasSelection() {
-               clipboard.WriteAll(v.cursor.GetSelection())
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-               v.freshClip = true
-       }
-       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 {
-       if v.cursor.HasSelection() {
-               v.cursor.DeleteSelection()
-               v.cursor.ResetSelection()
-       }
-       clip, _ := clipboard.ReadAll()
-       v.eh.Insert(v.cursor.Loc(), clip)
-       v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
-       v.freshClip = false
-       return true
-}
-
-// SelectAll selects the entire buffer
-func (v *View) SelectAll() bool {
-       v.cursor.curSelection[1] = 0
-       v.cursor.curSelection[0] = v.buf.Len()
-       // Put the cursor at the beginning
-       v.cursor.x = 0
-       v.cursor.y = 0
-       return true
-}
-
-// 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: ")
-               if canceled {
-                       return true
-               }
-               home, _ := homedir.Dir()
-               filename = strings.Replace(filename, "~", home, 1)
-               file, err := ioutil.ReadFile(filename)
-
-               if err != nil {
-                       messenger.Error(err.Error())
-                       return true
-               }
-               buf := NewBuffer(string(file), filename)
-               v.OpenBuffer(buf)
-       }
-       return true
-}
-
-// Start moves the viewport to the start of the buffer
-func (v *View) Start() bool {
-       v.topline = 0
-       return false
-}
-
-// End moves the viewport to the end of the buffer
-func (v *View) End() bool {
-       if v.height > len(v.buf.lines) {
-               v.topline = 0
-       } else {
-               v.topline = len(v.buf.lines) - v.height
-       }
-       return false
-}
-
-// PageUp scrolls the view up a page
-func (v *View) PageUp() bool {
-       if v.topline > v.height {
-               v.ScrollUp(v.height)
-       } else {
-               v.topline = 0
-       }
-       return false
-}
-
-// PageDown scrolls the view down a page
-func (v *View) PageDown() bool {
-       if len(v.buf.lines)-(v.topline+v.height) > v.height {
-               v.ScrollDown(v.height)
-       } else if len(v.buf.lines) >= v.height {
-               v.topline = len(v.buf.lines) - v.height
-       }
-       return false
-}
-
-// HalfPageUp scrolls the view up half a page
-func (v *View) HalfPageUp() bool {
-       if v.topline > v.height/2 {
-               v.ScrollUp(v.height / 2)
-       } else {
-               v.topline = 0
-       }
-       return false
-}
-
-// HalfPageDown scrolls the view down half a page
-func (v *View) HalfPageDown() bool {
-       if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
-               v.ScrollDown(v.height / 2)
-       } else {
-               if len(v.buf.lines) >= v.height {
-                       v.topline = len(v.buf.lines) - v.height
-               }
-       }
-       return false
-}
-
-// ToggleRuler turns line numbers off and on
-func (v *View) ToggleRuler() bool {
-       if settings["ruler"] == false {
-               settings["ruler"] = true
-       } else {
-               settings["ruler"] = false
-       }
-       return false
-}
-
-// None is no action
-func None() bool {
-       return false
-}
-
-// gofmt runs gofmt on a file
-func gofmt(file string) error {
-       cmd := exec.Command("gofmt", "-w", file)
-       cmd.Start()
-       err := cmd.Wait()
-       if err != nil {
-               return errors.New("Check syntax ") //TODO: highlight or display locations
-       }
-       return nil
-}
-
-// goimports runs goimports on a file
-func goimports(file string) error {
-       cmd := exec.Command("goimports", "-w", file)
-       cmd.Start()
-       err := cmd.Wait()
-       if err != nil {
-               return errors.New("Check syntax ") //TODO: highlight or display locations
-       }
-       return nil
-}