X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fbindings.go;h=e3a02841718f97ab6cfd497607c1ea85436146c5;hb=71af765b4e4f368c4bbbcb3947f3497e17271b62;hp=fadad29c9ee02c60ff23b8df98888ea7672462b2;hpb=127ebc15b9fc8682b103f1a6b89dd1f38e3f3bc6;p=micro.git diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index fadad29c..e3a02841 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -1,97 +1,136 @@ 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, - "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, @@ -211,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, @@ -226,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() @@ -282,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 } @@ -307,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 } @@ -316,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' @@ -335,23 +406,109 @@ 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) if !ok { + TermMessage("Unknown keybinding: " + k) return } 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 @@ -374,19 +531,22 @@ 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", - "Space": "InsertSpace", "CtrlH": "Backspace", "Backspace": "Backspace", "Alt-CtrlH": "DeleteWordLeft", "Alt-Backspace": "DeleteWordLeft", "Tab": "IndentSelection,InsertTab", - "Backtab": "OutdentSelection", + "Backtab": "OutdentSelection,OutdentLine", "CtrlO": "OpenFile", "CtrlS": "Save", "CtrlF": "Find", @@ -401,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", @@ -419,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", } }