]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/bindings.go
Code optimisation (#1117)
[micro.git] / cmd / micro / bindings.go
index b537a9e12e9241082c1005e2baf533b0c7e63d2e..e3a02841718f97ab6cfd497607c1ea85436146c5 100644 (file)
 package main
 
 import (
+       "fmt"
        "io/ioutil"
        "os"
        "strings"
+       "unicode"
 
-       "github.com/zyedidia/json5/encoding/json5"
+       "github.com/flynn/json5"
        "github.com/zyedidia/tcell"
 )
 
+var bindingsStr map[string]string
 var bindings map[Key][]func(*View, bool) bool
+var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
 var helpBinding string
+var kmenuBinding string
+
+var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
+       "MousePress":       (*View).MousePress,
+       "MouseMultiCursor": (*View).MouseMultiCursor,
+}
 
 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,
-       "MoveLinesUp":         (*View).MoveLinesUp,
-       "MoveLinesDown":       (*View).MoveLinesDown,
-       "IndentSelection":     (*View).IndentSelection,
-       "OutdentSelection":    (*View).OutdentSelection,
-       "OutdentLine":         (*View).OutdentLine,
-       "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,
-       "Escape":              (*View).Escape,
-       "Quit":                (*View).Quit,
-       "QuitAll":             (*View).QuitAll,
-       "AddTab":              (*View).AddTab,
-       "PreviousTab":         (*View).PreviousTab,
-       "NextTab":             (*View).NextTab,
-       "NextSplit":           (*View).NextSplit,
-       "PreviousSplit":       (*View).PreviousSplit,
-       "Unsplit":             (*View).Unsplit,
-       "VSplit":              (*View).VSplitBinding,
-       "HSplit":              (*View).HSplitBinding,
-       "ToggleMacro":         (*View).ToggleMacro,
-       "PlayMacro":           (*View).PlayMacro,
+       "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,
+       "SelectLine":             (*View).SelectLine,
+       "SelectToStartOfLine":    (*View).SelectToStartOfLine,
+       "SelectToEndOfLine":      (*View).SelectToEndOfLine,
+       "ParagraphPrevious":      (*View).ParagraphPrevious,
+       "ParagraphNext":          (*View).ParagraphNext,
+       "InsertNewline":          (*View).InsertNewline,
+       "InsertSpace":            (*View).InsertSpace,
+       "Backspace":              (*View).Backspace,
+       "Delete":                 (*View).Delete,
+       "InsertTab":              (*View).InsertTab,
+       "Save":                   (*View).Save,
+       "SaveAll":                (*View).SaveAll,
+       "SaveAs":                 (*View).SaveAs,
+       "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,
+       "MoveLinesUp":            (*View).MoveLinesUp,
+       "MoveLinesDown":          (*View).MoveLinesDown,
+       "IndentSelection":        (*View).IndentSelection,
+       "OutdentSelection":       (*View).OutdentSelection,
+       "OutdentLine":            (*View).OutdentLine,
+       "Paste":                  (*View).Paste,
+       "PastePrimary":           (*View).PastePrimary,
+       "SelectAll":              (*View).SelectAll,
+       "OpenFile":               (*View).OpenFile,
+       "Start":                  (*View).Start,
+       "End":                    (*View).End,
+       "PageUp":                 (*View).PageUp,
+       "PageDown":               (*View).PageDown,
+       "SelectPageUp":           (*View).SelectPageUp,
+       "SelectPageDown":         (*View).SelectPageDown,
+       "HalfPageUp":             (*View).HalfPageUp,
+       "HalfPageDown":           (*View).HalfPageDown,
+       "StartOfLine":            (*View).StartOfLine,
+       "EndOfLine":              (*View).EndOfLine,
+       "ToggleHelp":             (*View).ToggleHelp,
+       "ToggleKeyMenu":          (*View).ToggleKeyMenu,
+       "ToggleRuler":            (*View).ToggleRuler,
+       "JumpLine":               (*View).JumpLine,
+       "ClearStatus":            (*View).ClearStatus,
+       "ShellMode":              (*View).ShellMode,
+       "CommandMode":            (*View).CommandMode,
+       "ToggleOverwriteMode":    (*View).ToggleOverwriteMode,
+       "Escape":                 (*View).Escape,
+       "Quit":                   (*View).Quit,
+       "QuitAll":                (*View).QuitAll,
+       "AddTab":                 (*View).AddTab,
+       "PreviousTab":            (*View).PreviousTab,
+       "NextTab":                (*View).NextTab,
+       "NextSplit":              (*View).NextSplit,
+       "PreviousSplit":          (*View).PreviousSplit,
+       "Unsplit":                (*View).Unsplit,
+       "VSplit":                 (*View).VSplitBinding,
+       "HSplit":                 (*View).HSplitBinding,
+       "ToggleMacro":            (*View).ToggleMacro,
+       "PlayMacro":              (*View).PlayMacro,
+       "Suspend":                (*View).Suspend,
+       "ScrollUp":               (*View).ScrollUpAction,
+       "ScrollDown":             (*View).ScrollDownAction,
+       "SpawnMultiCursor":       (*View).SpawnMultiCursor,
+       "SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
+       "RemoveMultiCursor":      (*View).RemoveMultiCursor,
+       "RemoveAllMultiCursors":  (*View).RemoveAllMultiCursors,
+       "SkipMultiCursor":        (*View).SkipMultiCursor,
+       "JumpToMatchingBrace":    (*View).JumpToMatchingBrace,
 
        // This was changed to InsertNewline but I don't want to break backwards compatibility
        "InsertEnter": (*View).InsertNewline,
 }
 
+var bindingMouse = map[string]tcell.ButtonMask{
+       "MouseLeft":       tcell.Button1,
+       "MouseMiddle":     tcell.Button2,
+       "MouseRight":      tcell.Button3,
+       "MouseWheelUp":    tcell.WheelUp,
+       "MouseWheelDown":  tcell.WheelDown,
+       "MouseWheelLeft":  tcell.WheelLeft,
+       "MouseWheelRight": tcell.WheelRight,
+}
+
 var bindingKeys = map[string]tcell.Key{
        "Up":             tcell.KeyUp,
        "Down":           tcell.KeyDown,
@@ -212,11 +250,14 @@ var bindingKeys = map[string]tcell.Key{
        "CtrlRightSq":    tcell.KeyCtrlRightSq,
        "CtrlCarat":      tcell.KeyCtrlCarat,
        "CtrlUnderscore": tcell.KeyCtrlUnderscore,
+       "CtrlPageUp":     tcell.KeyCtrlPgUp,
+       "CtrlPageDown":   tcell.KeyCtrlPgDn,
        "Tab":            tcell.KeyTab,
        "Esc":            tcell.KeyEsc,
        "Escape":         tcell.KeyEscape,
        "Enter":          tcell.KeyEnter,
        "Backspace":      tcell.KeyBackspace2,
+       "OldBackspace":   tcell.KeyBackspace,
 
        // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
        "PgUp":   tcell.KeyPgUp,
@@ -227,12 +268,16 @@ var bindingKeys = map[string]tcell.Key{
 type Key struct {
        keyCode   tcell.Key
        modifiers tcell.ModMask
+       buttons   tcell.ButtonMask
        r         rune
+       escape    string
 }
 
 // InitBindings initializes the keybindings for micro
 func InitBindings() {
        bindings = make(map[Key][]func(*View, bool) bool)
+       bindingsStr = make(map[string]string)
+       mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
 
        var parsed map[string]string
        defaults := DefaultBindings()
@@ -283,21 +328,35 @@ modSearch:
                case strings.HasPrefix(k, "Shift"):
                        k = k[5:]
                        modifiers |= tcell.ModShift
+               case strings.HasPrefix(k, "\x1b"):
+                       return Key{
+                               keyCode:   -1,
+                               modifiers: modifiers,
+                               buttons:   -1,
+                               r:         0,
+                               escape:    k,
+                       }, true
                default:
                        break modSearch
                }
        }
 
+       if len(k) == 0 {
+               return Key{buttons: -1}, false
+       }
+
        // 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.
+               k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
                if code, ok := bindingKeys["Ctrl"+k]; ok {
                        // It is, we're done.
                        return Key{
                                keyCode:   code,
                                modifiers: modifiers,
+                               buttons:   -1,
                                r:         0,
                        }, true
                }
@@ -308,6 +367,16 @@ modSearch:
                return Key{
                        keyCode:   code,
                        modifiers: modifiers,
+                       buttons:   -1,
+                       r:         0,
+               }, true
+       }
+
+       // See if we can find the key in bindingMouse
+       if code, ok := bindingMouse[k]; ok {
+               return Key{
+                       modifiers: modifiers,
+                       buttons:   code,
                        r:         0,
                }, true
        }
@@ -317,12 +386,13 @@ modSearch:
                return Key{
                        keyCode:   tcell.KeyRune,
                        modifiers: modifiers,
+                       buttons:   -1,
                        r:         rune(k[0]),
                }, true
        }
 
        // We don't know what happened.
-       return Key{}, false
+       return Key{buttons: -1}, false
 }
 
 // findAction will find 'action' using string 'v'
@@ -336,6 +406,53 @@ func findAction(v string) (action func(*View, bool) bool) {
        return action
 }
 
+func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
+       action, ok := mouseBindingActions[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 = LuaFunctionMouseBinding(v)
+       }
+       return action
+}
+
+// TryBindKey tries to bind a key by writing to configDir/bindings.json
+// This function is unused for now
+func TryBindKey(k, v string) {
+       filename := configDir + "/bindings.json"
+       if _, e := os.Stat(filename); e == nil {
+               input, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       TermMessage("Error reading bindings.json file: " + err.Error())
+                       return
+               }
+
+               conflict := -1
+               lines := strings.Split(string(input), "\n")
+               for i, l := range lines {
+                       parts := strings.Split(l, ":")
+                       if len(parts) >= 2 {
+                               if strings.Contains(parts[0], k) {
+                                       conflict = i
+                                       TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
+                               }
+                       }
+               }
+
+               binding := fmt.Sprintf("    \"%s\": \"%s\",", k, v)
+               if conflict == -1 {
+                       lines = append([]string{lines[0], binding}, lines[conflict:]...)
+               } else {
+                       lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
+               }
+               txt := strings.Join(lines, "\n")
+               err = ioutil.WriteFile(filename, []byte(txt), 0644)
+               if err != nil {
+                       TermMessage("Error")
+               }
+       }
+}
+
 // BindKey takes a key and an action and binds the two together
 func BindKey(k, v string) {
        key, ok := findKey(k)
@@ -346,14 +463,52 @@ func BindKey(k, v string) {
        if v == "ToggleHelp" {
                helpBinding = k
        }
+       if v == "ToggleKeyMenu" {
+               kmenuBinding = k
+       }
+       if helpBinding == k && v != "ToggleHelp" {
+               helpBinding = ""
+       }
+       if kmenuBinding == k && v != "ToggleKeyMenu" {
+               kmenuBinding = ""
+       }
 
        actionNames := strings.Split(v, ",")
+       if actionNames[0] == "UnbindKey" {
+               delete(bindings, key)
+               delete(mouseBindings, key)
+               delete(bindingsStr, k)
+               if len(actionNames) == 1 {
+                       return
+               }
+               actionNames = append(actionNames[:0], actionNames[1:]...)
+       }
        actions := make([]func(*View, bool) bool, 0, len(actionNames))
+       mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
        for _, actionName := range actionNames {
-               actions = append(actions, findAction(actionName))
+               if strings.HasPrefix(actionName, "Mouse") {
+                       mouseActions = append(mouseActions, findMouseAction(actionName))
+               } else if strings.HasPrefix(actionName, "command:") {
+                       cmd := strings.SplitN(actionName, ":", 2)[1]
+                       actions = append(actions, CommandAction(cmd))
+               } else if strings.HasPrefix(actionName, "command-edit:") {
+                       cmd := strings.SplitN(actionName, ":", 2)[1]
+                       actions = append(actions, CommandEditAction(cmd))
+               } else {
+                       actions = append(actions, findAction(actionName))
+               }
        }
 
-       bindings[key] = actions
+       if len(actions) > 0 {
+               // Can't have a binding be both mouse and normal
+               delete(mouseBindings, key)
+               bindings[key] = actions
+               bindingsStr[k] = v
+       } else if len(mouseActions) > 0 {
+               // Can't have a binding be both mouse and normal
+               delete(bindings, key)
+               mouseBindings[key] = mouseActions
+       }
 }
 
 // DefaultBindings returns a map containing micro's default keybindings
@@ -376,11 +531,15 @@ func DefaultBindings() map[string]string {
                "CtrlLeft":       "StartOfLine",
                "CtrlRight":      "EndOfLine",
                "CtrlShiftLeft":  "SelectToStartOfLine",
+               "ShiftHome":      "SelectToStartOfLine",
                "CtrlShiftRight": "SelectToEndOfLine",
+               "ShiftEnd":       "SelectToEndOfLine",
                "CtrlUp":         "CursorStart",
                "CtrlDown":       "CursorEnd",
                "CtrlShiftUp":    "SelectToStart",
                "CtrlShiftDown":  "SelectToEnd",
+               "Alt-{":          "ParagraphPrevious",
+               "Alt-}":          "ParagraphNext",
                "Enter":          "InsertNewline",
                "CtrlH":          "Backspace",
                "Backspace":      "Backspace",
@@ -402,15 +561,18 @@ func DefaultBindings() map[string]string {
                "CtrlV":          "Paste",
                "CtrlA":          "SelectAll",
                "CtrlT":          "AddTab",
-               "CtrlRightSq":    "PreviousTab",
-               "CtrlBackslash":  "NextTab",
+               "Alt,":           "PreviousTab",
+               "Alt.":           "NextTab",
                "Home":           "StartOfLine",
                "End":            "EndOfLine",
                "CtrlHome":       "CursorStart",
                "CtrlEnd":        "CursorEnd",
                "PageUp":         "CursorPageUp",
                "PageDown":       "CursorPageDown",
+               "CtrlPageUp":     "PreviousTab",
+               "CtrlPageDown":   "NextTab",
                "CtrlG":          "ToggleHelp",
+               "Alt-g":          "ToggleKeyMenu",
                "CtrlR":          "ToggleRuler",
                "CtrlL":          "JumpLine",
                "Delete":         "Delete",
@@ -420,21 +582,35 @@ func DefaultBindings() map[string]string {
                "CtrlW":          "NextSplit",
                "CtrlU":          "ToggleMacro",
                "CtrlJ":          "PlayMacro",
+               "Insert":         "ToggleOverwriteMode",
 
                // Emacs-style keybindings
                "Alt-f": "WordRight",
                "Alt-b": "WordLeft",
                "Alt-a": "StartOfLine",
                "Alt-e": "EndOfLine",
-               "Alt-p": "CursorUp",
-               "Alt-n": "CursorDown",
+               // "Alt-p": "CursorUp",
+               // "Alt-n": "CursorDown",
 
                // Integration with file managers
-               "F1":  "ToggleHelp",
                "F2":  "Save",
+               "F3":  "Find",
                "F4":  "Quit",
                "F7":  "Find",
                "F10": "Quit",
                "Esc": "Escape",
+
+               // Mouse bindings
+               "MouseWheelUp":   "ScrollUp",
+               "MouseWheelDown": "ScrollDown",
+               "MouseLeft":      "MousePress",
+               "MouseMiddle":    "PastePrimary",
+               "Ctrl-MouseLeft": "MouseMultiCursor",
+
+               "Alt-n": "SpawnMultiCursor",
+               "Alt-m": "SpawnMultiCursorSelect",
+               "Alt-p": "RemoveMultiCursor",
+               "Alt-c": "RemoveAllMultiCursors",
+               "Alt-x": "SkipMultiCursor",
        }
 }