10 "github.com/flynn/json5"
11 "github.com/zyedidia/tcell"
14 var bindingsStr map[string]string
15 var bindings map[Key][]func(*View, bool) bool
16 var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
17 var helpBinding string
18 var kmenuBinding string
20 var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
21 "MousePress": (*View).MousePress,
22 "MouseMultiCursor": (*View).MouseMultiCursor,
25 var bindingActions = map[string]func(*View, bool) bool{
26 "CursorUp": (*View).CursorUp,
27 "CursorDown": (*View).CursorDown,
28 "CursorPageUp": (*View).CursorPageUp,
29 "CursorPageDown": (*View).CursorPageDown,
30 "CursorLeft": (*View).CursorLeft,
31 "CursorRight": (*View).CursorRight,
32 "CursorStart": (*View).CursorStart,
33 "CursorEnd": (*View).CursorEnd,
34 "SelectToStart": (*View).SelectToStart,
35 "SelectToEnd": (*View).SelectToEnd,
36 "SelectUp": (*View).SelectUp,
37 "SelectDown": (*View).SelectDown,
38 "SelectLeft": (*View).SelectLeft,
39 "SelectRight": (*View).SelectRight,
40 "WordRight": (*View).WordRight,
41 "WordLeft": (*View).WordLeft,
42 "SelectWordRight": (*View).SelectWordRight,
43 "SelectWordLeft": (*View).SelectWordLeft,
44 "DeleteWordRight": (*View).DeleteWordRight,
45 "DeleteWordLeft": (*View).DeleteWordLeft,
46 "SelectLine": (*View).SelectLine,
47 "SelectToStartOfLine": (*View).SelectToStartOfLine,
48 "SelectToEndOfLine": (*View).SelectToEndOfLine,
49 "ParagraphPrevious": (*View).ParagraphPrevious,
50 "ParagraphNext": (*View).ParagraphNext,
51 "InsertNewline": (*View).InsertNewline,
52 "InsertSpace": (*View).InsertSpace,
53 "Backspace": (*View).Backspace,
54 "Delete": (*View).Delete,
55 "InsertTab": (*View).InsertTab,
57 "SaveAll": (*View).SaveAll,
58 "SaveAs": (*View).SaveAs,
60 "FindNext": (*View).FindNext,
61 "FindPrevious": (*View).FindPrevious,
62 "Center": (*View).Center,
67 "CutLine": (*View).CutLine,
68 "DuplicateLine": (*View).DuplicateLine,
69 "DeleteLine": (*View).DeleteLine,
70 "MoveLinesUp": (*View).MoveLinesUp,
71 "MoveLinesDown": (*View).MoveLinesDown,
72 "IndentSelection": (*View).IndentSelection,
73 "OutdentSelection": (*View).OutdentSelection,
74 "OutdentLine": (*View).OutdentLine,
75 "Paste": (*View).Paste,
76 "PastePrimary": (*View).PastePrimary,
77 "SelectAll": (*View).SelectAll,
78 "OpenFile": (*View).OpenFile,
79 "Start": (*View).Start,
81 "PageUp": (*View).PageUp,
82 "PageDown": (*View).PageDown,
83 "SelectPageUp": (*View).SelectPageUp,
84 "SelectPageDown": (*View).SelectPageDown,
85 "HalfPageUp": (*View).HalfPageUp,
86 "HalfPageDown": (*View).HalfPageDown,
87 "StartOfLine": (*View).StartOfLine,
88 "EndOfLine": (*View).EndOfLine,
89 "ToggleHelp": (*View).ToggleHelp,
90 "ToggleKeyMenu": (*View).ToggleKeyMenu,
91 "ToggleRuler": (*View).ToggleRuler,
92 "JumpLine": (*View).JumpLine,
93 "ClearStatus": (*View).ClearStatus,
94 "ShellMode": (*View).ShellMode,
95 "CommandMode": (*View).CommandMode,
96 "ToggleOverwriteMode": (*View).ToggleOverwriteMode,
97 "Escape": (*View).Escape,
99 "QuitAll": (*View).QuitAll,
100 "AddTab": (*View).AddTab,
101 "PreviousTab": (*View).PreviousTab,
102 "NextTab": (*View).NextTab,
103 "NextSplit": (*View).NextSplit,
104 "PreviousSplit": (*View).PreviousSplit,
105 "Unsplit": (*View).Unsplit,
106 "VSplit": (*View).VSplitBinding,
107 "HSplit": (*View).HSplitBinding,
108 "ToggleMacro": (*View).ToggleMacro,
109 "PlayMacro": (*View).PlayMacro,
110 "Suspend": (*View).Suspend,
111 "ScrollUp": (*View).ScrollUpAction,
112 "ScrollDown": (*View).ScrollDownAction,
113 "SpawnMultiCursor": (*View).SpawnMultiCursor,
114 "SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
115 "RemoveMultiCursor": (*View).RemoveMultiCursor,
116 "RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
117 "SkipMultiCursor": (*View).SkipMultiCursor,
118 "JumpToMatchingBrace": (*View).JumpToMatchingBrace,
120 // This was changed to InsertNewline but I don't want to break backwards compatibility
121 "InsertEnter": (*View).InsertNewline,
124 var bindingMouse = map[string]tcell.ButtonMask{
125 "MouseLeft": tcell.Button1,
126 "MouseMiddle": tcell.Button2,
127 "MouseRight": tcell.Button3,
128 "MouseWheelUp": tcell.WheelUp,
129 "MouseWheelDown": tcell.WheelDown,
130 "MouseWheelLeft": tcell.WheelLeft,
131 "MouseWheelRight": tcell.WheelRight,
134 var bindingKeys = map[string]tcell.Key{
136 "Down": tcell.KeyDown,
137 "Right": tcell.KeyRight,
138 "Left": tcell.KeyLeft,
139 "UpLeft": tcell.KeyUpLeft,
140 "UpRight": tcell.KeyUpRight,
141 "DownLeft": tcell.KeyDownLeft,
142 "DownRight": tcell.KeyDownRight,
143 "Center": tcell.KeyCenter,
144 "PageUp": tcell.KeyPgUp,
145 "PageDown": tcell.KeyPgDn,
146 "Home": tcell.KeyHome,
148 "Insert": tcell.KeyInsert,
149 "Delete": tcell.KeyDelete,
150 "Help": tcell.KeyHelp,
151 "Exit": tcell.KeyExit,
152 "Clear": tcell.KeyClear,
153 "Cancel": tcell.KeyCancel,
154 "Print": tcell.KeyPrint,
155 "Pause": tcell.KeyPause,
156 "Backtab": tcell.KeyBacktab,
221 "CtrlSpace": tcell.KeyCtrlSpace,
222 "CtrlA": tcell.KeyCtrlA,
223 "CtrlB": tcell.KeyCtrlB,
224 "CtrlC": tcell.KeyCtrlC,
225 "CtrlD": tcell.KeyCtrlD,
226 "CtrlE": tcell.KeyCtrlE,
227 "CtrlF": tcell.KeyCtrlF,
228 "CtrlG": tcell.KeyCtrlG,
229 "CtrlH": tcell.KeyCtrlH,
230 "CtrlI": tcell.KeyCtrlI,
231 "CtrlJ": tcell.KeyCtrlJ,
232 "CtrlK": tcell.KeyCtrlK,
233 "CtrlL": tcell.KeyCtrlL,
234 "CtrlM": tcell.KeyCtrlM,
235 "CtrlN": tcell.KeyCtrlN,
236 "CtrlO": tcell.KeyCtrlO,
237 "CtrlP": tcell.KeyCtrlP,
238 "CtrlQ": tcell.KeyCtrlQ,
239 "CtrlR": tcell.KeyCtrlR,
240 "CtrlS": tcell.KeyCtrlS,
241 "CtrlT": tcell.KeyCtrlT,
242 "CtrlU": tcell.KeyCtrlU,
243 "CtrlV": tcell.KeyCtrlV,
244 "CtrlW": tcell.KeyCtrlW,
245 "CtrlX": tcell.KeyCtrlX,
246 "CtrlY": tcell.KeyCtrlY,
247 "CtrlZ": tcell.KeyCtrlZ,
248 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
249 "CtrlBackslash": tcell.KeyCtrlBackslash,
250 "CtrlRightSq": tcell.KeyCtrlRightSq,
251 "CtrlCarat": tcell.KeyCtrlCarat,
252 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
253 "CtrlPageUp": tcell.KeyCtrlPgUp,
254 "CtrlPageDown": tcell.KeyCtrlPgDn,
257 "Escape": tcell.KeyEscape,
258 "Enter": tcell.KeyEnter,
259 "Backspace": tcell.KeyBackspace2,
260 "OldBackspace": tcell.KeyBackspace,
262 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
263 "PgUp": tcell.KeyPgUp,
264 "PgDown": tcell.KeyPgDn,
267 // The Key struct holds the data for a keypress (keycode + modifiers)
270 modifiers tcell.ModMask
271 buttons tcell.ButtonMask
276 // InitBindings initializes the keybindings for micro
277 func InitBindings() {
278 bindings = make(map[Key][]func(*View, bool) bool)
279 bindingsStr = make(map[string]string)
280 mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
282 var parsed map[string]string
283 defaults := DefaultBindings()
285 filename := configDir + "/bindings.json"
286 if _, e := os.Stat(filename); e == nil {
287 input, err := ioutil.ReadFile(filename)
289 TermMessage("Error reading bindings.json file: " + err.Error())
293 err = json5.Unmarshal(input, &parsed)
295 TermMessage("Error reading bindings.json:", err.Error())
299 parseBindings(defaults)
300 parseBindings(parsed)
303 func parseBindings(userBindings map[string]string) {
304 for k, v := range userBindings {
309 // findKey will find binding Key 'b' using string 'k'
310 func findKey(k string) (b Key, ok bool) {
311 modifiers := tcell.ModNone
313 // First, we'll strip off all the modifiers in the name and add them to the
318 case strings.HasPrefix(k, "-"):
319 // We optionally support dashes between modifiers
321 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
322 // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
324 modifiers |= tcell.ModCtrl
325 case strings.HasPrefix(k, "Alt"):
327 modifiers |= tcell.ModAlt
328 case strings.HasPrefix(k, "Shift"):
330 modifiers |= tcell.ModShift
331 case strings.HasPrefix(k, "\x1b"):
334 modifiers: modifiers,
345 return Key{buttons: -1}, false
348 // Control is handled specially, since some character codes in bindingKeys
349 // are different when Control is depressed. We should check for Control keys
351 if modifiers&tcell.ModCtrl != 0 {
352 // see if the key is in bindingKeys with the Ctrl prefix.
353 k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
354 if code, ok := bindingKeys["Ctrl"+k]; ok {
355 // It is, we're done.
358 modifiers: modifiers,
365 // See if we can find the key in bindingKeys
366 if code, ok := bindingKeys[k]; ok {
369 modifiers: modifiers,
375 // See if we can find the key in bindingMouse
376 if code, ok := bindingMouse[k]; ok {
378 modifiers: modifiers,
384 // If we were given one character, then we've got a rune.
387 keyCode: tcell.KeyRune,
388 modifiers: modifiers,
394 // We don't know what happened.
395 return Key{buttons: -1}, false
398 // findAction will find 'action' using string 'v'
399 func findAction(v string) (action func(*View, bool) bool) {
400 action, ok := bindingActions[v]
402 // If the user seems to be binding a function that doesn't exist
403 // We hope that it's a lua function that exists and bind it to that
404 action = LuaFunctionBinding(v)
409 func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
410 action, ok := mouseBindingActions[v]
412 // If the user seems to be binding a function that doesn't exist
413 // We hope that it's a lua function that exists and bind it to that
414 action = LuaFunctionMouseBinding(v)
419 // TryBindKey tries to bind a key by writing to configDir/bindings.json
420 // This function is unused for now
421 func TryBindKey(k, v string) {
422 filename := configDir + "/bindings.json"
423 if _, e := os.Stat(filename); e == nil {
424 input, err := ioutil.ReadFile(filename)
426 TermMessage("Error reading bindings.json file: " + err.Error())
431 lines := strings.Split(string(input), "\n")
432 for i, l := range lines {
433 parts := strings.Split(l, ":")
435 if strings.Contains(parts[0], k) {
437 TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
442 binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
444 lines = append([]string{lines[0], binding}, lines[conflict:]...)
446 lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
448 txt := strings.Join(lines, "\n")
449 err = ioutil.WriteFile(filename, []byte(txt), 0644)
456 // BindKey takes a key and an action and binds the two together
457 func BindKey(k, v string) {
458 key, ok := findKey(k)
460 TermMessage("Unknown keybinding: " + k)
463 if v == "ToggleHelp" {
466 if v == "ToggleKeyMenu" {
469 if helpBinding == k && v != "ToggleHelp" {
472 if kmenuBinding == k && v != "ToggleKeyMenu" {
476 actionNames := strings.Split(v, ",")
477 if actionNames[0] == "UnbindKey" {
478 delete(bindings, key)
479 delete(mouseBindings, key)
480 delete(bindingsStr, k)
481 if len(actionNames) == 1 {
484 actionNames = append(actionNames[:0], actionNames[1:]...)
486 actions := make([]func(*View, bool) bool, 0, len(actionNames))
487 mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
488 for _, actionName := range actionNames {
489 if strings.HasPrefix(actionName, "Mouse") {
490 mouseActions = append(mouseActions, findMouseAction(actionName))
491 } else if strings.HasPrefix(actionName, "command:") {
492 cmd := strings.SplitN(actionName, ":", 2)[1]
493 actions = append(actions, CommandAction(cmd))
494 } else if strings.HasPrefix(actionName, "command-edit:") {
495 cmd := strings.SplitN(actionName, ":", 2)[1]
496 actions = append(actions, CommandEditAction(cmd))
498 actions = append(actions, findAction(actionName))
502 if len(actions) > 0 {
503 // Can't have a binding be both mouse and normal
504 delete(mouseBindings, key)
505 bindings[key] = actions
507 } else if len(mouseActions) > 0 {
508 // Can't have a binding be both mouse and normal
509 delete(bindings, key)
510 mouseBindings[key] = mouseActions
514 // DefaultBindings returns a map containing micro's default keybindings
515 func DefaultBindings() map[string]string {
516 return map[string]string{
518 "Down": "CursorDown",
519 "Right": "CursorRight",
520 "Left": "CursorLeft",
521 "ShiftUp": "SelectUp",
522 "ShiftDown": "SelectDown",
523 "ShiftLeft": "SelectLeft",
524 "ShiftRight": "SelectRight",
525 "AltLeft": "WordLeft",
526 "AltRight": "WordRight",
527 "AltUp": "MoveLinesUp",
528 "AltDown": "MoveLinesDown",
529 "AltShiftRight": "SelectWordRight",
530 "AltShiftLeft": "SelectWordLeft",
531 "CtrlLeft": "StartOfLine",
532 "CtrlRight": "EndOfLine",
533 "CtrlShiftLeft": "SelectToStartOfLine",
534 "ShiftHome": "SelectToStartOfLine",
535 "CtrlShiftRight": "SelectToEndOfLine",
536 "ShiftEnd": "SelectToEndOfLine",
537 "CtrlUp": "CursorStart",
538 "CtrlDown": "CursorEnd",
539 "CtrlShiftUp": "SelectToStart",
540 "CtrlShiftDown": "SelectToEnd",
541 "Alt-{": "ParagraphPrevious",
542 "Alt-}": "ParagraphNext",
543 "Enter": "InsertNewline",
544 "CtrlH": "Backspace",
545 "Backspace": "Backspace",
546 "Alt-CtrlH": "DeleteWordLeft",
547 "Alt-Backspace": "DeleteWordLeft",
548 "Tab": "IndentSelection,InsertTab",
549 "Backtab": "OutdentSelection,OutdentLine",
554 "CtrlP": "FindPrevious",
560 "CtrlD": "DuplicateLine",
562 "CtrlA": "SelectAll",
564 "Alt,": "PreviousTab",
566 "Home": "StartOfLine",
568 "CtrlHome": "CursorStart",
569 "CtrlEnd": "CursorEnd",
570 "PageUp": "CursorPageUp",
571 "PageDown": "CursorPageDown",
572 "CtrlPageUp": "PreviousTab",
573 "CtrlPageDown": "NextTab",
574 "CtrlG": "ToggleHelp",
575 "Alt-g": "ToggleKeyMenu",
576 "CtrlR": "ToggleRuler",
579 "CtrlB": "ShellMode",
581 "CtrlE": "CommandMode",
582 "CtrlW": "NextSplit",
583 "CtrlU": "ToggleMacro",
584 "CtrlJ": "PlayMacro",
585 "Insert": "ToggleOverwriteMode",
587 // Emacs-style keybindings
588 "Alt-f": "WordRight",
590 "Alt-a": "StartOfLine",
591 "Alt-e": "EndOfLine",
592 // "Alt-p": "CursorUp",
593 // "Alt-n": "CursorDown",
595 // Integration with file managers
604 "MouseWheelUp": "ScrollUp",
605 "MouseWheelDown": "ScrollDown",
606 "MouseLeft": "MousePress",
607 "MouseMiddle": "PastePrimary",
608 "Ctrl-MouseLeft": "MouseMultiCursor",
610 "Alt-n": "SpawnMultiCursor",
611 "Alt-m": "SpawnMultiCursorSelect",
612 "Alt-p": "RemoveMultiCursor",
613 "Alt-c": "RemoveAllMultiCursors",
614 "Alt-x": "SkipMultiCursor",