8 "github.com/flynn/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
15 var kmenuBinding string
17 var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
18 "MousePress": (*View).MousePress,
19 "MouseMultiCursor": (*View).MouseMultiCursor,
22 var bindingActions = map[string]func(*View, bool) bool{
23 "CursorUp": (*View).CursorUp,
24 "CursorDown": (*View).CursorDown,
25 "CursorPageUp": (*View).CursorPageUp,
26 "CursorPageDown": (*View).CursorPageDown,
27 "CursorLeft": (*View).CursorLeft,
28 "CursorRight": (*View).CursorRight,
29 "CursorStart": (*View).CursorStart,
30 "CursorEnd": (*View).CursorEnd,
31 "SelectToStart": (*View).SelectToStart,
32 "SelectToEnd": (*View).SelectToEnd,
33 "SelectUp": (*View).SelectUp,
34 "SelectDown": (*View).SelectDown,
35 "SelectLeft": (*View).SelectLeft,
36 "SelectRight": (*View).SelectRight,
37 "WordRight": (*View).WordRight,
38 "WordLeft": (*View).WordLeft,
39 "SelectWordRight": (*View).SelectWordRight,
40 "SelectWordLeft": (*View).SelectWordLeft,
41 "DeleteWordRight": (*View).DeleteWordRight,
42 "DeleteWordLeft": (*View).DeleteWordLeft,
43 "SelectToStartOfLine": (*View).SelectToStartOfLine,
44 "SelectToEndOfLine": (*View).SelectToEndOfLine,
45 "ParagraphPrevious": (*View).ParagraphPrevious,
46 "ParagraphNext": (*View).ParagraphNext,
47 "InsertNewline": (*View).InsertNewline,
48 "InsertSpace": (*View).InsertSpace,
49 "Backspace": (*View).Backspace,
50 "Delete": (*View).Delete,
51 "InsertTab": (*View).InsertTab,
53 "SaveAll": (*View).SaveAll,
54 "SaveAs": (*View).SaveAs,
56 "FindNext": (*View).FindNext,
57 "FindPrevious": (*View).FindPrevious,
58 "Center": (*View).Center,
63 "CutLine": (*View).CutLine,
64 "DuplicateLine": (*View).DuplicateLine,
65 "DeleteLine": (*View).DeleteLine,
66 "MoveLinesUp": (*View).MoveLinesUp,
67 "MoveLinesDown": (*View).MoveLinesDown,
68 "IndentSelection": (*View).IndentSelection,
69 "OutdentSelection": (*View).OutdentSelection,
70 "OutdentLine": (*View).OutdentLine,
71 "Paste": (*View).Paste,
72 "PastePrimary": (*View).PastePrimary,
73 "SelectAll": (*View).SelectAll,
74 "OpenFile": (*View).OpenFile,
75 "Start": (*View).Start,
77 "PageUp": (*View).PageUp,
78 "PageDown": (*View).PageDown,
79 "HalfPageUp": (*View).HalfPageUp,
80 "HalfPageDown": (*View).HalfPageDown,
81 "StartOfLine": (*View).StartOfLine,
82 "EndOfLine": (*View).EndOfLine,
83 "ToggleHelp": (*View).ToggleHelp,
84 "ToggleKeyMenu": (*View).ToggleKeyMenu,
85 "ToggleRuler": (*View).ToggleRuler,
86 "JumpLine": (*View).JumpLine,
87 "ClearStatus": (*View).ClearStatus,
88 "ShellMode": (*View).ShellMode,
89 "CommandMode": (*View).CommandMode,
90 "Escape": (*View).Escape,
92 "QuitAll": (*View).QuitAll,
93 "AddTab": (*View).AddTab,
94 "PreviousTab": (*View).PreviousTab,
95 "NextTab": (*View).NextTab,
96 "NextSplit": (*View).NextSplit,
97 "PreviousSplit": (*View).PreviousSplit,
98 "Unsplit": (*View).Unsplit,
99 "VSplit": (*View).VSplitBinding,
100 "HSplit": (*View).HSplitBinding,
101 "ToggleMacro": (*View).ToggleMacro,
102 "PlayMacro": (*View).PlayMacro,
103 "Suspend": (*View).Suspend,
104 "ScrollUp": (*View).ScrollUpAction,
105 "ScrollDown": (*View).ScrollDownAction,
106 "SpawnMultiCursor": (*View).SpawnMultiCursor,
107 "RemoveMultiCursor": (*View).RemoveMultiCursor,
108 "RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
109 "SkipMultiCursor": (*View).SkipMultiCursor,
111 // This was changed to InsertNewline but I don't want to break backwards compatibility
112 "InsertEnter": (*View).InsertNewline,
115 var bindingMouse = map[string]tcell.ButtonMask{
116 "MouseLeft": tcell.Button1,
117 "MouseMiddle": tcell.Button2,
118 "MouseRight": tcell.Button3,
119 "MouseWheelUp": tcell.WheelUp,
120 "MouseWheelDown": tcell.WheelDown,
121 "MouseWheelLeft": tcell.WheelLeft,
122 "MouseWheelRight": tcell.WheelRight,
125 var bindingKeys = map[string]tcell.Key{
127 "Down": tcell.KeyDown,
128 "Right": tcell.KeyRight,
129 "Left": tcell.KeyLeft,
130 "UpLeft": tcell.KeyUpLeft,
131 "UpRight": tcell.KeyUpRight,
132 "DownLeft": tcell.KeyDownLeft,
133 "DownRight": tcell.KeyDownRight,
134 "Center": tcell.KeyCenter,
135 "PageUp": tcell.KeyPgUp,
136 "PageDown": tcell.KeyPgDn,
137 "Home": tcell.KeyHome,
139 "Insert": tcell.KeyInsert,
140 "Delete": tcell.KeyDelete,
141 "Help": tcell.KeyHelp,
142 "Exit": tcell.KeyExit,
143 "Clear": tcell.KeyClear,
144 "Cancel": tcell.KeyCancel,
145 "Print": tcell.KeyPrint,
146 "Pause": tcell.KeyPause,
147 "Backtab": tcell.KeyBacktab,
212 "CtrlSpace": tcell.KeyCtrlSpace,
213 "CtrlA": tcell.KeyCtrlA,
214 "CtrlB": tcell.KeyCtrlB,
215 "CtrlC": tcell.KeyCtrlC,
216 "CtrlD": tcell.KeyCtrlD,
217 "CtrlE": tcell.KeyCtrlE,
218 "CtrlF": tcell.KeyCtrlF,
219 "CtrlG": tcell.KeyCtrlG,
220 "CtrlH": tcell.KeyCtrlH,
221 "CtrlI": tcell.KeyCtrlI,
222 "CtrlJ": tcell.KeyCtrlJ,
223 "CtrlK": tcell.KeyCtrlK,
224 "CtrlL": tcell.KeyCtrlL,
225 "CtrlM": tcell.KeyCtrlM,
226 "CtrlN": tcell.KeyCtrlN,
227 "CtrlO": tcell.KeyCtrlO,
228 "CtrlP": tcell.KeyCtrlP,
229 "CtrlQ": tcell.KeyCtrlQ,
230 "CtrlR": tcell.KeyCtrlR,
231 "CtrlS": tcell.KeyCtrlS,
232 "CtrlT": tcell.KeyCtrlT,
233 "CtrlU": tcell.KeyCtrlU,
234 "CtrlV": tcell.KeyCtrlV,
235 "CtrlW": tcell.KeyCtrlW,
236 "CtrlX": tcell.KeyCtrlX,
237 "CtrlY": tcell.KeyCtrlY,
238 "CtrlZ": tcell.KeyCtrlZ,
239 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
240 "CtrlBackslash": tcell.KeyCtrlBackslash,
241 "CtrlRightSq": tcell.KeyCtrlRightSq,
242 "CtrlCarat": tcell.KeyCtrlCarat,
243 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
244 "CtrlPageUp": tcell.KeyCtrlPgUp,
245 "CtrlPageDown": tcell.KeyCtrlPgDn,
248 "Escape": tcell.KeyEscape,
249 "Enter": tcell.KeyEnter,
250 "Backspace": tcell.KeyBackspace2,
252 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
253 "PgUp": tcell.KeyPgUp,
254 "PgDown": tcell.KeyPgDn,
257 // The Key struct holds the data for a keypress (keycode + modifiers)
260 modifiers tcell.ModMask
261 buttons tcell.ButtonMask
266 // InitBindings initializes the keybindings for micro
267 func InitBindings() {
268 bindings = make(map[Key][]func(*View, bool) bool)
269 mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
271 var parsed map[string]string
272 defaults := DefaultBindings()
274 filename := configDir + "/bindings.json"
275 if _, e := os.Stat(filename); e == nil {
276 input, err := ioutil.ReadFile(filename)
278 TermMessage("Error reading bindings.json file: " + err.Error())
282 err = json5.Unmarshal(input, &parsed)
284 TermMessage("Error reading bindings.json:", err.Error())
288 parseBindings(defaults)
289 parseBindings(parsed)
292 func parseBindings(userBindings map[string]string) {
293 for k, v := range userBindings {
298 // findKey will find binding Key 'b' using string 'k'
299 func findKey(k string) (b Key, ok bool) {
300 modifiers := tcell.ModNone
302 // First, we'll strip off all the modifiers in the name and add them to the
307 case strings.HasPrefix(k, "-"):
308 // We optionally support dashes between modifiers
310 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
311 // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
313 modifiers |= tcell.ModCtrl
314 case strings.HasPrefix(k, "Alt"):
316 modifiers |= tcell.ModAlt
317 case strings.HasPrefix(k, "Shift"):
319 modifiers |= tcell.ModShift
320 case strings.HasPrefix(k, "\x1b"):
323 modifiers: modifiers,
333 // Control is handled specially, since some character codes in bindingKeys
334 // are different when Control is depressed. We should check for Control keys
336 if modifiers&tcell.ModCtrl != 0 {
337 // see if the key is in bindingKeys with the Ctrl prefix.
338 if code, ok := bindingKeys["Ctrl"+k]; ok {
339 // It is, we're done.
342 modifiers: modifiers,
349 // See if we can find the key in bindingKeys
350 if code, ok := bindingKeys[k]; ok {
353 modifiers: modifiers,
359 // See if we can find the key in bindingMouse
360 if code, ok := bindingMouse[k]; ok {
362 modifiers: modifiers,
368 // If we were given one character, then we've got a rune.
371 keyCode: tcell.KeyRune,
372 modifiers: modifiers,
378 // We don't know what happened.
379 return Key{buttons: -1}, false
382 // findAction will find 'action' using string 'v'
383 func findAction(v string) (action func(*View, bool) bool) {
384 action, ok := bindingActions[v]
386 // If the user seems to be binding a function that doesn't exist
387 // We hope that it's a lua function that exists and bind it to that
388 action = LuaFunctionBinding(v)
393 func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
394 action, ok := mouseBindingActions[v]
396 // If the user seems to be binding a function that doesn't exist
397 // We hope that it's a lua function that exists and bind it to that
398 action = LuaFunctionMouseBinding(v)
403 // BindKey takes a key and an action and binds the two together
404 func BindKey(k, v string) {
405 key, ok := findKey(k)
407 TermMessage("Unknown keybinding: " + k)
410 if v == "ToggleHelp" {
413 if v == "ToggleKeyMenu" {
416 if helpBinding == k && v != "ToggleHelp" {
419 if kmenuBinding == k && v != "ToggleKeyMenu" {
423 actionNames := strings.Split(v, ",")
424 if actionNames[0] == "UnbindKey" {
425 delete(bindings, key)
426 delete(mouseBindings, key)
427 if len(actionNames) == 1 {
430 actionNames = append(actionNames[:0], actionNames[1:]...)
432 actions := make([]func(*View, bool) bool, 0, len(actionNames))
433 mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
434 for _, actionName := range actionNames {
435 if strings.HasPrefix(actionName, "Mouse") {
436 mouseActions = append(mouseActions, findMouseAction(actionName))
438 actions = append(actions, findAction(actionName))
442 if len(actions) > 0 {
443 // Can't have a binding be both mouse and normal
444 delete(mouseBindings, key)
445 bindings[key] = actions
446 } else if len(mouseActions) > 0 {
447 // Can't have a binding be both mouse and normal
448 delete(bindings, key)
449 mouseBindings[key] = mouseActions
453 // DefaultBindings returns a map containing micro's default keybindings
454 func DefaultBindings() map[string]string {
455 return map[string]string{
457 "Down": "CursorDown",
458 "Right": "CursorRight",
459 "Left": "CursorLeft",
460 "ShiftUp": "SelectUp",
461 "ShiftDown": "SelectDown",
462 "ShiftLeft": "SelectLeft",
463 "ShiftRight": "SelectRight",
464 "AltLeft": "WordLeft",
465 "AltRight": "WordRight",
466 "AltUp": "MoveLinesUp",
467 "AltDown": "MoveLinesDown",
468 "AltShiftRight": "SelectWordRight",
469 "AltShiftLeft": "SelectWordLeft",
470 "CtrlLeft": "StartOfLine",
471 "CtrlRight": "EndOfLine",
472 "CtrlShiftLeft": "SelectToStartOfLine",
473 "ShiftHome": "SelectToStartOfLine",
474 "CtrlShiftRight": "SelectToEndOfLine",
475 "ShiftEnd": "SelectToEndOfLine",
476 "CtrlUp": "CursorStart",
477 "CtrlDown": "CursorEnd",
478 "CtrlShiftUp": "SelectToStart",
479 "CtrlShiftDown": "SelectToEnd",
480 "Alt-{": "ParagraphPrevious",
481 "Alt-}": "ParagraphNext",
482 "Enter": "InsertNewline",
483 "CtrlH": "Backspace",
484 "Backspace": "Backspace",
485 "Alt-CtrlH": "DeleteWordLeft",
486 "Alt-Backspace": "DeleteWordLeft",
487 "Tab": "IndentSelection,InsertTab",
488 "Backtab": "OutdentSelection,OutdentLine",
493 "CtrlP": "FindPrevious",
499 "CtrlD": "DuplicateLine",
501 "CtrlA": "SelectAll",
503 "Alt,": "PreviousTab",
505 "Home": "StartOfLine",
507 "CtrlHome": "CursorStart",
508 "CtrlEnd": "CursorEnd",
509 "PageUp": "CursorPageUp",
510 "PageDown": "CursorPageDown",
511 "CtrlPageUp": "PreviousTab",
512 "CtrlPageDown": "NextTab",
513 "CtrlG": "ToggleHelp",
514 "Alt-g": "ToggleKeyMenu",
515 "CtrlR": "ToggleRuler",
518 "CtrlB": "ShellMode",
520 "CtrlE": "CommandMode",
521 "CtrlW": "NextSplit",
522 "CtrlU": "ToggleMacro",
523 "CtrlJ": "PlayMacro",
525 // Emacs-style keybindings
526 "Alt-f": "WordRight",
528 "Alt-a": "StartOfLine",
529 "Alt-e": "EndOfLine",
530 // "Alt-p": "CursorUp",
531 // "Alt-n": "CursorDown",
533 // Integration with file managers
542 "MouseWheelUp": "ScrollUp",
543 "MouseWheelDown": "ScrollDown",
544 "MouseLeft": "MousePress",
545 "MouseMiddle": "PastePrimary",
546 "Ctrl-MouseLeft": "MouseMultiCursor",
548 "Alt-n": "SpawnMultiCursor",
549 "Alt-p": "RemoveMultiCursor",
550 "Alt-c": "RemoveAllMultiCursors",
551 "Alt-x": "SkipMultiCursor",