8 "github.com/zyedidia/json5/encoding/json5"
9 "github.com/zyedidia/tcell"
12 var bindings map[Key][]func(*View, bool) bool
13 var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
14 var helpBinding string
16 var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
17 "MousePress": (*View).MousePress,
20 var bindingActions = map[string]func(*View, bool) bool{
21 "CursorUp": (*View).CursorUp,
22 "CursorDown": (*View).CursorDown,
23 "CursorPageUp": (*View).CursorPageUp,
24 "CursorPageDown": (*View).CursorPageDown,
25 "CursorLeft": (*View).CursorLeft,
26 "CursorRight": (*View).CursorRight,
27 "CursorStart": (*View).CursorStart,
28 "CursorEnd": (*View).CursorEnd,
29 "SelectToStart": (*View).SelectToStart,
30 "SelectToEnd": (*View).SelectToEnd,
31 "SelectUp": (*View).SelectUp,
32 "SelectDown": (*View).SelectDown,
33 "SelectLeft": (*View).SelectLeft,
34 "SelectRight": (*View).SelectRight,
35 "WordRight": (*View).WordRight,
36 "WordLeft": (*View).WordLeft,
37 "SelectWordRight": (*View).SelectWordRight,
38 "SelectWordLeft": (*View).SelectWordLeft,
39 "DeleteWordRight": (*View).DeleteWordRight,
40 "DeleteWordLeft": (*View).DeleteWordLeft,
41 "SelectToStartOfLine": (*View).SelectToStartOfLine,
42 "SelectToEndOfLine": (*View).SelectToEndOfLine,
43 "InsertNewline": (*View).InsertNewline,
44 "InsertSpace": (*View).InsertSpace,
45 "Backspace": (*View).Backspace,
46 "Delete": (*View).Delete,
47 "InsertTab": (*View).InsertTab,
49 "SaveAll": (*View).SaveAll,
50 "SaveAs": (*View).SaveAs,
52 "FindNext": (*View).FindNext,
53 "FindPrevious": (*View).FindPrevious,
54 "Center": (*View).Center,
59 "CutLine": (*View).CutLine,
60 "DuplicateLine": (*View).DuplicateLine,
61 "DeleteLine": (*View).DeleteLine,
62 "MoveLinesUp": (*View).MoveLinesUp,
63 "MoveLinesDown": (*View).MoveLinesDown,
64 "IndentSelection": (*View).IndentSelection,
65 "OutdentSelection": (*View).OutdentSelection,
66 "OutdentLine": (*View).OutdentLine,
67 "Paste": (*View).Paste,
68 "PastePrimary": (*View).PastePrimary,
69 "SelectAll": (*View).SelectAll,
70 "OpenFile": (*View).OpenFile,
71 "Start": (*View).Start,
73 "PageUp": (*View).PageUp,
74 "PageDown": (*View).PageDown,
75 "HalfPageUp": (*View).HalfPageUp,
76 "HalfPageDown": (*View).HalfPageDown,
77 "StartOfLine": (*View).StartOfLine,
78 "EndOfLine": (*View).EndOfLine,
79 "ToggleHelp": (*View).ToggleHelp,
80 "ToggleRuler": (*View).ToggleRuler,
81 "JumpLine": (*View).JumpLine,
82 "ClearStatus": (*View).ClearStatus,
83 "ShellMode": (*View).ShellMode,
84 "CommandMode": (*View).CommandMode,
85 "Escape": (*View).Escape,
87 "QuitAll": (*View).QuitAll,
88 "AddTab": (*View).AddTab,
89 "PreviousTab": (*View).PreviousTab,
90 "NextTab": (*View).NextTab,
91 "NextSplit": (*View).NextSplit,
92 "PreviousSplit": (*View).PreviousSplit,
93 "Unsplit": (*View).Unsplit,
94 "VSplit": (*View).VSplitBinding,
95 "HSplit": (*View).HSplitBinding,
96 "ToggleMacro": (*View).ToggleMacro,
97 "PlayMacro": (*View).PlayMacro,
98 "Suspend": (*View).Suspend,
99 "ScrollUp": (*View).ScrollUpAction,
100 "ScrollDown": (*View).ScrollDownAction,
101 "SpawnMultiCursor": (*View).SpawnMultiCursor,
102 "RemoveMultiCursor": (*View).RemoveMultiCursor,
103 "RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
104 "SkipMultiCursor": (*View).SkipMultiCursor,
106 // This was changed to InsertNewline but I don't want to break backwards compatibility
107 "InsertEnter": (*View).InsertNewline,
110 var bindingMouse = map[string]tcell.ButtonMask{
111 "MouseLeft": tcell.Button1,
112 "MouseMiddle": tcell.Button2,
113 "MouseRight": tcell.Button3,
114 "MouseWheelUp": tcell.WheelUp,
115 "MouseWheelDown": tcell.WheelDown,
116 "MouseWheelLeft": tcell.WheelLeft,
117 "MouseWheelRight": tcell.WheelRight,
120 var bindingKeys = map[string]tcell.Key{
122 "Down": tcell.KeyDown,
123 "Right": tcell.KeyRight,
124 "Left": tcell.KeyLeft,
125 "UpLeft": tcell.KeyUpLeft,
126 "UpRight": tcell.KeyUpRight,
127 "DownLeft": tcell.KeyDownLeft,
128 "DownRight": tcell.KeyDownRight,
129 "Center": tcell.KeyCenter,
130 "PageUp": tcell.KeyPgUp,
131 "PageDown": tcell.KeyPgDn,
132 "Home": tcell.KeyHome,
134 "Insert": tcell.KeyInsert,
135 "Delete": tcell.KeyDelete,
136 "Help": tcell.KeyHelp,
137 "Exit": tcell.KeyExit,
138 "Clear": tcell.KeyClear,
139 "Cancel": tcell.KeyCancel,
140 "Print": tcell.KeyPrint,
141 "Pause": tcell.KeyPause,
142 "Backtab": tcell.KeyBacktab,
207 "CtrlSpace": tcell.KeyCtrlSpace,
208 "CtrlA": tcell.KeyCtrlA,
209 "CtrlB": tcell.KeyCtrlB,
210 "CtrlC": tcell.KeyCtrlC,
211 "CtrlD": tcell.KeyCtrlD,
212 "CtrlE": tcell.KeyCtrlE,
213 "CtrlF": tcell.KeyCtrlF,
214 "CtrlG": tcell.KeyCtrlG,
215 "CtrlH": tcell.KeyCtrlH,
216 "CtrlI": tcell.KeyCtrlI,
217 "CtrlJ": tcell.KeyCtrlJ,
218 "CtrlK": tcell.KeyCtrlK,
219 "CtrlL": tcell.KeyCtrlL,
220 "CtrlM": tcell.KeyCtrlM,
221 "CtrlN": tcell.KeyCtrlN,
222 "CtrlO": tcell.KeyCtrlO,
223 "CtrlP": tcell.KeyCtrlP,
224 "CtrlQ": tcell.KeyCtrlQ,
225 "CtrlR": tcell.KeyCtrlR,
226 "CtrlS": tcell.KeyCtrlS,
227 "CtrlT": tcell.KeyCtrlT,
228 "CtrlU": tcell.KeyCtrlU,
229 "CtrlV": tcell.KeyCtrlV,
230 "CtrlW": tcell.KeyCtrlW,
231 "CtrlX": tcell.KeyCtrlX,
232 "CtrlY": tcell.KeyCtrlY,
233 "CtrlZ": tcell.KeyCtrlZ,
234 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
235 "CtrlBackslash": tcell.KeyCtrlBackslash,
236 "CtrlRightSq": tcell.KeyCtrlRightSq,
237 "CtrlCarat": tcell.KeyCtrlCarat,
238 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
241 "Escape": tcell.KeyEscape,
242 "Enter": tcell.KeyEnter,
243 "Backspace": tcell.KeyBackspace2,
245 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
246 "PgUp": tcell.KeyPgUp,
247 "PgDown": tcell.KeyPgDn,
250 // The Key struct holds the data for a keypress (keycode + modifiers)
253 modifiers tcell.ModMask
254 buttons tcell.ButtonMask
258 // InitBindings initializes the keybindings for micro
259 func InitBindings() {
260 bindings = make(map[Key][]func(*View, bool) bool)
261 mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
263 var parsed map[string]string
264 defaults := DefaultBindings()
266 filename := configDir + "/bindings.json"
267 if _, e := os.Stat(filename); e == nil {
268 input, err := ioutil.ReadFile(filename)
270 TermMessage("Error reading bindings.json file: " + err.Error())
274 err = json5.Unmarshal(input, &parsed)
276 TermMessage("Error reading bindings.json:", err.Error())
280 parseBindings(defaults)
281 parseBindings(parsed)
284 func parseBindings(userBindings map[string]string) {
285 for k, v := range userBindings {
290 // findKey will find binding Key 'b' using string 'k'
291 func findKey(k string) (b Key, ok bool) {
292 modifiers := tcell.ModNone
294 // First, we'll strip off all the modifiers in the name and add them to the
299 case strings.HasPrefix(k, "-"):
300 // We optionally support dashes between modifiers
302 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
303 // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
305 modifiers |= tcell.ModCtrl
306 case strings.HasPrefix(k, "Alt"):
308 modifiers |= tcell.ModAlt
309 case strings.HasPrefix(k, "Shift"):
311 modifiers |= tcell.ModShift
317 // Control is handled specially, since some character codes in bindingKeys
318 // are different when Control is depressed. We should check for Control keys
320 if modifiers&tcell.ModCtrl != 0 {
321 // see if the key is in bindingKeys with the Ctrl prefix.
322 if code, ok := bindingKeys["Ctrl"+k]; ok {
323 // It is, we're done.
326 modifiers: modifiers,
333 // See if we can find the key in bindingKeys
334 if code, ok := bindingKeys[k]; ok {
337 modifiers: modifiers,
343 // See if we can find the key in bindingMouse
344 if code, ok := bindingMouse[k]; ok {
346 modifiers: modifiers,
352 // If we were given one character, then we've got a rune.
355 keyCode: tcell.KeyRune,
356 modifiers: modifiers,
362 // We don't know what happened.
363 return Key{buttons: -1}, false
366 // findAction will find 'action' using string 'v'
367 func findAction(v string) (action func(*View, bool) bool) {
368 action, ok := bindingActions[v]
370 // If the user seems to be binding a function that doesn't exist
371 // We hope that it's a lua function that exists and bind it to that
372 action = LuaFunctionBinding(v)
377 func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
378 action, ok := mouseBindingActions[v]
380 // If the user seems to be binding a function that doesn't exist
381 // We hope that it's a lua function that exists and bind it to that
382 action = LuaFunctionMouseBinding(v)
387 // BindKey takes a key and an action and binds the two together
388 func BindKey(k, v string) {
389 key, ok := findKey(k)
391 TermMessage("Unknown keybinding: " + k)
394 if v == "ToggleHelp" {
397 if helpBinding == k && v != "ToggleHelp" {
401 actionNames := strings.Split(v, ",")
402 if actionNames[0] == "UnbindKey" {
403 delete(bindings, key)
404 delete(mouseBindings, key)
405 if len(actionNames) == 1 {
408 actionNames = append(actionNames[:0], actionNames[1:]...)
410 actions := make([]func(*View, bool) bool, 0, len(actionNames))
411 mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
412 for _, actionName := range actionNames {
413 if strings.HasPrefix(actionName, "Mouse") {
414 mouseActions = append(mouseActions, findMouseAction(actionName))
416 actions = append(actions, findAction(actionName))
420 if len(actions) > 0 {
421 // Can't have a binding be both mouse and normal
422 delete(mouseBindings, key)
423 bindings[key] = actions
424 } else if len(mouseActions) > 0 {
425 // Can't have a binding be both mouse and normal
426 delete(bindings, key)
427 mouseBindings[key] = mouseActions
431 // DefaultBindings returns a map containing micro's default keybindings
432 func DefaultBindings() map[string]string {
433 return map[string]string{
435 "Down": "CursorDown",
436 "Right": "CursorRight",
437 "Left": "CursorLeft",
438 "ShiftUp": "SelectUp",
439 "ShiftDown": "SelectDown",
440 "ShiftLeft": "SelectLeft",
441 "ShiftRight": "SelectRight",
442 "AltLeft": "WordLeft",
443 "AltRight": "WordRight",
444 "AltUp": "MoveLinesUp",
445 "AltDown": "MoveLinesDown",
446 "AltShiftRight": "SelectWordRight",
447 "AltShiftLeft": "SelectWordLeft",
448 "CtrlLeft": "StartOfLine",
449 "CtrlRight": "EndOfLine",
450 "CtrlShiftLeft": "SelectToStartOfLine",
451 "ShiftHome": "SelectToStartOfLine",
452 "CtrlShiftRight": "SelectToEndOfLine",
453 "ShiftEnd": "SelectToEndOfLine",
454 "CtrlUp": "CursorStart",
455 "CtrlDown": "CursorEnd",
456 "CtrlShiftUp": "SelectToStart",
457 "CtrlShiftDown": "SelectToEnd",
458 "Enter": "InsertNewline",
459 "CtrlH": "Backspace",
460 "Backspace": "Backspace",
461 "Alt-CtrlH": "DeleteWordLeft",
462 "Alt-Backspace": "DeleteWordLeft",
463 "Tab": "IndentSelection,InsertTab",
464 "Backtab": "OutdentSelection,OutdentLine",
469 "CtrlP": "FindPrevious",
475 "CtrlD": "DuplicateLine",
477 "CtrlA": "SelectAll",
479 "Alt,": "PreviousTab",
481 "Home": "StartOfLine",
483 "CtrlHome": "CursorStart",
484 "CtrlEnd": "CursorEnd",
485 "PageUp": "CursorPageUp",
486 "PageDown": "CursorPageDown",
487 "CtrlG": "ToggleHelp",
488 "CtrlR": "ToggleRuler",
491 "CtrlB": "ShellMode",
493 "CtrlE": "CommandMode",
494 "CtrlW": "NextSplit",
495 "CtrlU": "ToggleMacro",
496 "CtrlJ": "PlayMacro",
498 // Emacs-style keybindings
499 "Alt-f": "WordRight",
501 "Alt-a": "StartOfLine",
502 "Alt-e": "EndOfLine",
503 // "Alt-p": "CursorUp",
504 // "Alt-n": "CursorDown",
506 // Integration with file managers
516 "MouseWheelUp": "ScrollUp",
517 "MouseWheelDown": "ScrollDown",
518 "MouseLeft": "MousePress",
519 "MouseMiddle": "PastePrimary",
521 "Alt-n": "SpawnMultiCursor",
522 "Alt-p": "RemoveMultiCursor",
523 "Alt-c": "RemoveAllMultiCursors",
524 "Alt-x": "SkipMultiCursor",