]> git.lizzy.rs Git - micro.git/blob - cmd/micro/bindings.go
fc0c2d3afc3ae8316ed2e38f143c03d89ca09f19
[micro.git] / cmd / micro / bindings.go
1 package main
2
3 import (
4         "io/ioutil"
5         "os"
6         "strings"
7
8         "github.com/flynn/json5"
9         "github.com/zyedidia/tcell"
10 )
11
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
16
17 var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
18         "MousePress":       (*View).MousePress,
19         "MouseMultiCursor": (*View).MouseMultiCursor,
20 }
21
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,
52         "Save":                  (*View).Save,
53         "SaveAll":               (*View).SaveAll,
54         "SaveAs":                (*View).SaveAs,
55         "Find":                  (*View).Find,
56         "FindNext":              (*View).FindNext,
57         "FindPrevious":          (*View).FindPrevious,
58         "Center":                (*View).Center,
59         "Undo":                  (*View).Undo,
60         "Redo":                  (*View).Redo,
61         "Copy":                  (*View).Copy,
62         "Cut":                   (*View).Cut,
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,
76         "End":                   (*View).End,
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,
91         "Quit":                  (*View).Quit,
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,
110
111         // This was changed to InsertNewline but I don't want to break backwards compatibility
112         "InsertEnter": (*View).InsertNewline,
113 }
114
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,
123 }
124
125 var bindingKeys = map[string]tcell.Key{
126         "Up":             tcell.KeyUp,
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,
138         "End":            tcell.KeyEnd,
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,
148         "F1":             tcell.KeyF1,
149         "F2":             tcell.KeyF2,
150         "F3":             tcell.KeyF3,
151         "F4":             tcell.KeyF4,
152         "F5":             tcell.KeyF5,
153         "F6":             tcell.KeyF6,
154         "F7":             tcell.KeyF7,
155         "F8":             tcell.KeyF8,
156         "F9":             tcell.KeyF9,
157         "F10":            tcell.KeyF10,
158         "F11":            tcell.KeyF11,
159         "F12":            tcell.KeyF12,
160         "F13":            tcell.KeyF13,
161         "F14":            tcell.KeyF14,
162         "F15":            tcell.KeyF15,
163         "F16":            tcell.KeyF16,
164         "F17":            tcell.KeyF17,
165         "F18":            tcell.KeyF18,
166         "F19":            tcell.KeyF19,
167         "F20":            tcell.KeyF20,
168         "F21":            tcell.KeyF21,
169         "F22":            tcell.KeyF22,
170         "F23":            tcell.KeyF23,
171         "F24":            tcell.KeyF24,
172         "F25":            tcell.KeyF25,
173         "F26":            tcell.KeyF26,
174         "F27":            tcell.KeyF27,
175         "F28":            tcell.KeyF28,
176         "F29":            tcell.KeyF29,
177         "F30":            tcell.KeyF30,
178         "F31":            tcell.KeyF31,
179         "F32":            tcell.KeyF32,
180         "F33":            tcell.KeyF33,
181         "F34":            tcell.KeyF34,
182         "F35":            tcell.KeyF35,
183         "F36":            tcell.KeyF36,
184         "F37":            tcell.KeyF37,
185         "F38":            tcell.KeyF38,
186         "F39":            tcell.KeyF39,
187         "F40":            tcell.KeyF40,
188         "F41":            tcell.KeyF41,
189         "F42":            tcell.KeyF42,
190         "F43":            tcell.KeyF43,
191         "F44":            tcell.KeyF44,
192         "F45":            tcell.KeyF45,
193         "F46":            tcell.KeyF46,
194         "F47":            tcell.KeyF47,
195         "F48":            tcell.KeyF48,
196         "F49":            tcell.KeyF49,
197         "F50":            tcell.KeyF50,
198         "F51":            tcell.KeyF51,
199         "F52":            tcell.KeyF52,
200         "F53":            tcell.KeyF53,
201         "F54":            tcell.KeyF54,
202         "F55":            tcell.KeyF55,
203         "F56":            tcell.KeyF56,
204         "F57":            tcell.KeyF57,
205         "F58":            tcell.KeyF58,
206         "F59":            tcell.KeyF59,
207         "F60":            tcell.KeyF60,
208         "F61":            tcell.KeyF61,
209         "F62":            tcell.KeyF62,
210         "F63":            tcell.KeyF63,
211         "F64":            tcell.KeyF64,
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,
246         "Tab":            tcell.KeyTab,
247         "Esc":            tcell.KeyEsc,
248         "Escape":         tcell.KeyEscape,
249         "Enter":          tcell.KeyEnter,
250         "Backspace":      tcell.KeyBackspace2,
251
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,
255 }
256
257 // The Key struct holds the data for a keypress (keycode + modifiers)
258 type Key struct {
259         keyCode   tcell.Key
260         modifiers tcell.ModMask
261         buttons   tcell.ButtonMask
262         r         rune
263         escape    string
264 }
265
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)
270
271         var parsed map[string]string
272         defaults := DefaultBindings()
273
274         filename := configDir + "/bindings.json"
275         if _, e := os.Stat(filename); e == nil {
276                 input, err := ioutil.ReadFile(filename)
277                 if err != nil {
278                         TermMessage("Error reading bindings.json file: " + err.Error())
279                         return
280                 }
281
282                 err = json5.Unmarshal(input, &parsed)
283                 if err != nil {
284                         TermMessage("Error reading bindings.json:", err.Error())
285                 }
286         }
287
288         parseBindings(defaults)
289         parseBindings(parsed)
290 }
291
292 func parseBindings(userBindings map[string]string) {
293         for k, v := range userBindings {
294                 BindKey(k, v)
295         }
296 }
297
298 // findKey will find binding Key 'b' using string 'k'
299 func findKey(k string) (b Key, ok bool) {
300         modifiers := tcell.ModNone
301
302         // First, we'll strip off all the modifiers in the name and add them to the
303         // ModMask
304 modSearch:
305         for {
306                 switch {
307                 case strings.HasPrefix(k, "-"):
308                         // We optionally support dashes between modifiers
309                         k = k[1:]
310                 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
311                         // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
312                         k = k[4:]
313                         modifiers |= tcell.ModCtrl
314                 case strings.HasPrefix(k, "Alt"):
315                         k = k[3:]
316                         modifiers |= tcell.ModAlt
317                 case strings.HasPrefix(k, "Shift"):
318                         k = k[5:]
319                         modifiers |= tcell.ModShift
320                 case strings.HasPrefix(k, "\x1b"):
321                         return Key{
322                                 keyCode:   -1,
323                                 modifiers: modifiers,
324                                 buttons:   -1,
325                                 r:         0,
326                                 escape:    k,
327                         }, true
328                 default:
329                         break modSearch
330                 }
331         }
332
333         // Control is handled specially, since some character codes in bindingKeys
334         // are different when Control is depressed. We should check for Control keys
335         // first.
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.
340                         return Key{
341                                 keyCode:   code,
342                                 modifiers: modifiers,
343                                 buttons:   -1,
344                                 r:         0,
345                         }, true
346                 }
347         }
348
349         // See if we can find the key in bindingKeys
350         if code, ok := bindingKeys[k]; ok {
351                 return Key{
352                         keyCode:   code,
353                         modifiers: modifiers,
354                         buttons:   -1,
355                         r:         0,
356                 }, true
357         }
358
359         // See if we can find the key in bindingMouse
360         if code, ok := bindingMouse[k]; ok {
361                 return Key{
362                         modifiers: modifiers,
363                         buttons:   code,
364                         r:         0,
365                 }, true
366         }
367
368         // If we were given one character, then we've got a rune.
369         if len(k) == 1 {
370                 return Key{
371                         keyCode:   tcell.KeyRune,
372                         modifiers: modifiers,
373                         buttons:   -1,
374                         r:         rune(k[0]),
375                 }, true
376         }
377
378         // We don't know what happened.
379         return Key{buttons: -1}, false
380 }
381
382 // findAction will find 'action' using string 'v'
383 func findAction(v string) (action func(*View, bool) bool) {
384         action, ok := bindingActions[v]
385         if !ok {
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)
389         }
390         return action
391 }
392
393 func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
394         action, ok := mouseBindingActions[v]
395         if !ok {
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)
399         }
400         return action
401 }
402
403 // BindKey takes a key and an action and binds the two together
404 func BindKey(k, v string) {
405         key, ok := findKey(k)
406         if !ok {
407                 TermMessage("Unknown keybinding: " + k)
408                 return
409         }
410         if v == "ToggleHelp" {
411                 helpBinding = k
412         }
413         if v == "ToggleKeyMenu" {
414                 kmenuBinding = k
415         }
416         if helpBinding == k && v != "ToggleHelp" {
417                 helpBinding = ""
418         }
419         if kmenuBinding == k && v != "ToggleKeyMenu" {
420                 kmenuBinding = ""
421         }
422
423         actionNames := strings.Split(v, ",")
424         if actionNames[0] == "UnbindKey" {
425                 delete(bindings, key)
426                 delete(mouseBindings, key)
427                 if len(actionNames) == 1 {
428                         return
429                 }
430                 actionNames = append(actionNames[:0], actionNames[1:]...)
431         }
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))
437                 } else {
438                         actions = append(actions, findAction(actionName))
439                 }
440         }
441
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
450         }
451 }
452
453 // DefaultBindings returns a map containing micro's default keybindings
454 func DefaultBindings() map[string]string {
455         return map[string]string{
456                 "Up":             "CursorUp",
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",
489                 "CtrlO":          "OpenFile",
490                 "CtrlS":          "Save",
491                 "CtrlF":          "Find",
492                 "CtrlN":          "FindNext",
493                 "CtrlP":          "FindPrevious",
494                 "CtrlZ":          "Undo",
495                 "CtrlY":          "Redo",
496                 "CtrlC":          "Copy",
497                 "CtrlX":          "Cut",
498                 "CtrlK":          "CutLine",
499                 "CtrlD":          "DuplicateLine",
500                 "CtrlV":          "Paste",
501                 "CtrlA":          "SelectAll",
502                 "CtrlT":          "AddTab",
503                 "Alt,":           "PreviousTab",
504                 "Alt.":           "NextTab",
505                 "Home":           "StartOfLine",
506                 "End":            "EndOfLine",
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",
516                 "CtrlL":          "JumpLine",
517                 "Delete":         "Delete",
518                 "CtrlB":          "ShellMode",
519                 "CtrlQ":          "Quit",
520                 "CtrlE":          "CommandMode",
521                 "CtrlW":          "NextSplit",
522                 "CtrlU":          "ToggleMacro",
523                 "CtrlJ":          "PlayMacro",
524
525                 // Emacs-style keybindings
526                 "Alt-f": "WordRight",
527                 "Alt-b": "WordLeft",
528                 "Alt-a": "StartOfLine",
529                 "Alt-e": "EndOfLine",
530                 // "Alt-p": "CursorUp",
531                 // "Alt-n": "CursorDown",
532
533                 // Integration with file managers
534                 "F2":  "Save",
535                 "F3":  "Find",
536                 "F4":  "Quit",
537                 "F7":  "Find",
538                 "F10": "Quit",
539                 "Esc": "Escape",
540
541                 // Mouse bindings
542                 "MouseWheelUp":   "ScrollUp",
543                 "MouseWheelDown": "ScrollDown",
544                 "MouseLeft":      "MousePress",
545                 "MouseMiddle":    "PastePrimary",
546                 "Ctrl-MouseLeft": "MouseMultiCursor",
547
548                 "Alt-n": "SpawnMultiCursor",
549                 "Alt-p": "RemoveMultiCursor",
550                 "Alt-c": "RemoveAllMultiCursors",
551                 "Alt-x": "SkipMultiCursor",
552         }
553 }