]> git.lizzy.rs Git - micro.git/blob - internal/action/bindings.go
Merge pull request #1361 from Lisiadito/master
[micro.git] / internal / action / bindings.go
1 package action
2
3 import (
4         "encoding/json"
5         "errors"
6         "io/ioutil"
7         "os"
8         "strings"
9         "unicode"
10
11         "github.com/flynn/json5"
12         "github.com/zyedidia/micro/internal/config"
13         "github.com/zyedidia/micro/internal/screen"
14         "github.com/zyedidia/tcell"
15 )
16
17 func InitBindings() {
18         config.Bindings = DefaultBindings()
19
20         var parsed map[string]string
21         defaults := DefaultBindings()
22
23         filename := config.ConfigDir + "/bindings.json"
24         if _, e := os.Stat(filename); e == nil {
25                 input, err := ioutil.ReadFile(filename)
26                 if err != nil {
27                         screen.TermMessage("Error reading bindings.json file: " + err.Error())
28                         return
29                 }
30
31                 err = json5.Unmarshal(input, &parsed)
32                 if err != nil {
33                         screen.TermMessage("Error reading bindings.json:", err.Error())
34                 }
35         }
36
37         for k, v := range defaults {
38                 BindKey(k, v)
39         }
40         for k, v := range parsed {
41                 BindKey(k, v)
42         }
43 }
44
45 func BindKey(k, v string) {
46         event, ok := findEvent(k)
47         if !ok {
48                 screen.TermMessage(k, "is not a bindable event")
49         }
50
51         switch e := event.(type) {
52         case KeyEvent:
53                 BufMapKey(e, v)
54         case MouseEvent:
55                 BufMapMouse(e, v)
56         case RawEvent:
57                 BufMapKey(e, v)
58         }
59
60         config.Bindings[k] = v
61 }
62
63 // findEvent will find binding Key 'b' using string 'k'
64 func findEvent(k string) (b Event, ok bool) {
65         modifiers := tcell.ModNone
66
67         // First, we'll strip off all the modifiers in the name and add them to the
68         // ModMask
69 modSearch:
70         for {
71                 switch {
72                 case strings.HasPrefix(k, "-"):
73                         // We optionally support dashes between modifiers
74                         k = k[1:]
75                 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
76                         // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
77                         k = k[4:]
78                         modifiers |= tcell.ModCtrl
79                 case strings.HasPrefix(k, "Alt"):
80                         k = k[3:]
81                         modifiers |= tcell.ModAlt
82                 case strings.HasPrefix(k, "Shift"):
83                         k = k[5:]
84                         modifiers |= tcell.ModShift
85                 case strings.HasPrefix(k, "\x1b"):
86                         return RawEvent{
87                                 esc: k,
88                         }, true
89                 default:
90                         break modSearch
91                 }
92         }
93
94         if len(k) == 0 {
95                 return KeyEvent{}, false
96         }
97
98         // Control is handled in a special way, since the terminal sends explicitly
99         // marked escape sequences for control keys
100         // We should check for Control keys first
101         if modifiers&tcell.ModCtrl != 0 {
102                 // see if the key is in bindingKeys with the Ctrl prefix.
103                 k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
104                 if code, ok := keyEvents["Ctrl"+k]; ok {
105                         var r tcell.Key
106                         // Special case for escape, for some reason tcell doesn't send it with the esc character
107                         if code < 256 && code != 27 {
108                                 r = code
109                         }
110                         // It is, we're done.
111                         return KeyEvent{
112                                 code: code,
113                                 mod:  modifiers,
114                                 r:    rune(r),
115                         }, true
116                 }
117         }
118
119         // See if we can find the key in bindingKeys
120         if code, ok := keyEvents[k]; ok {
121                 var r tcell.Key
122                 // Special case for escape, for some reason tcell doesn't send it with the esc character
123                 if code < 256 && code != 27 {
124                         r = code
125                 }
126                 return KeyEvent{
127                         code: code,
128                         mod:  modifiers,
129                         r:    rune(r),
130                 }, true
131         }
132
133         // See if we can find the key in bindingMouse
134         if code, ok := mouseEvents[k]; ok {
135                 return MouseEvent{
136                         btn: code,
137                         mod: modifiers,
138                 }, true
139         }
140
141         // If we were given one character, then we've got a rune.
142         if len(k) == 1 {
143                 return KeyEvent{
144                         code: tcell.KeyRune,
145                         mod:  modifiers,
146                         r:    rune(k[0]),
147                 }, true
148         }
149
150         // We don't know what happened.
151         return KeyEvent{}, false
152 }
153
154 // TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
155 // Returns true if the keybinding already existed and a possible error
156 func TryBindKey(k, v string, overwrite bool) (bool, error) {
157         var e error
158         var parsed map[string]string
159
160         filename := config.ConfigDir + "/bindings.json"
161         if _, e = os.Stat(filename); e == nil {
162                 input, err := ioutil.ReadFile(filename)
163                 if err != nil {
164                         return false, errors.New("Error reading bindings.json file: " + err.Error())
165                 }
166
167                 err = json5.Unmarshal(input, &parsed)
168                 if err != nil {
169                         return false, errors.New("Error reading bindings.json: " + err.Error())
170                 }
171
172                 key, ok := findEvent(k)
173                 if !ok {
174                         return false, errors.New("Invalid event " + k)
175                 }
176
177                 found := false
178                 for ev := range parsed {
179                         if e, ok := findEvent(ev); ok {
180                                 if e == key {
181                                         if overwrite {
182                                                 parsed[ev] = v
183                                         }
184                                         found = true
185                                         break
186                                 }
187                         }
188                 }
189
190                 if found && !overwrite {
191                         return true, nil
192                 } else if !found {
193                         parsed[k] = v
194                 }
195
196                 BindKey(k, v)
197
198                 txt, _ := json.MarshalIndent(parsed, "", "    ")
199                 return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
200         }
201         return false, e
202 }
203
204 // UnbindKey removes the binding for a key from the bindings.json file
205 func UnbindKey(k string) error {
206         var e error
207         var parsed map[string]string
208
209         filename := config.ConfigDir + "/bindings.json"
210         if _, e = os.Stat(filename); e == nil {
211                 input, err := ioutil.ReadFile(filename)
212                 if err != nil {
213                         return errors.New("Error reading bindings.json file: " + err.Error())
214                 }
215
216                 err = json5.Unmarshal(input, &parsed)
217                 if err != nil {
218                         return errors.New("Error reading bindings.json: " + err.Error())
219                 }
220
221                 key, ok := findEvent(k)
222                 if !ok {
223                         return errors.New("Invalid event " + k)
224                 }
225
226                 for ev := range parsed {
227                         if e, ok := findEvent(ev); ok {
228                                 if e == key {
229                                         delete(parsed, ev)
230                                         break
231                                 }
232                         }
233                 }
234
235                 defaults := DefaultBindings()
236                 if a, ok := defaults[k]; ok {
237                         BindKey(k, a)
238                 } else if _, ok := config.Bindings[k]; ok {
239                         delete(config.Bindings, k)
240                 }
241
242                 txt, _ := json.MarshalIndent(parsed, "", "    ")
243                 return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
244         }
245         return e
246 }
247
248 var mouseEvents = map[string]tcell.ButtonMask{
249         "MouseLeft":       tcell.Button1,
250         "MouseMiddle":     tcell.Button2,
251         "MouseRight":      tcell.Button3,
252         "MouseWheelUp":    tcell.WheelUp,
253         "MouseWheelDown":  tcell.WheelDown,
254         "MouseWheelLeft":  tcell.WheelLeft,
255         "MouseWheelRight": tcell.WheelRight,
256 }
257
258 var keyEvents = map[string]tcell.Key{
259         "Up":             tcell.KeyUp,
260         "Down":           tcell.KeyDown,
261         "Right":          tcell.KeyRight,
262         "Left":           tcell.KeyLeft,
263         "UpLeft":         tcell.KeyUpLeft,
264         "UpRight":        tcell.KeyUpRight,
265         "DownLeft":       tcell.KeyDownLeft,
266         "DownRight":      tcell.KeyDownRight,
267         "Center":         tcell.KeyCenter,
268         "PageUp":         tcell.KeyPgUp,
269         "PageDown":       tcell.KeyPgDn,
270         "Home":           tcell.KeyHome,
271         "End":            tcell.KeyEnd,
272         "Insert":         tcell.KeyInsert,
273         "Delete":         tcell.KeyDelete,
274         "Help":           tcell.KeyHelp,
275         "Exit":           tcell.KeyExit,
276         "Clear":          tcell.KeyClear,
277         "Cancel":         tcell.KeyCancel,
278         "Print":          tcell.KeyPrint,
279         "Pause":          tcell.KeyPause,
280         "Backtab":        tcell.KeyBacktab,
281         "F1":             tcell.KeyF1,
282         "F2":             tcell.KeyF2,
283         "F3":             tcell.KeyF3,
284         "F4":             tcell.KeyF4,
285         "F5":             tcell.KeyF5,
286         "F6":             tcell.KeyF6,
287         "F7":             tcell.KeyF7,
288         "F8":             tcell.KeyF8,
289         "F9":             tcell.KeyF9,
290         "F10":            tcell.KeyF10,
291         "F11":            tcell.KeyF11,
292         "F12":            tcell.KeyF12,
293         "F13":            tcell.KeyF13,
294         "F14":            tcell.KeyF14,
295         "F15":            tcell.KeyF15,
296         "F16":            tcell.KeyF16,
297         "F17":            tcell.KeyF17,
298         "F18":            tcell.KeyF18,
299         "F19":            tcell.KeyF19,
300         "F20":            tcell.KeyF20,
301         "F21":            tcell.KeyF21,
302         "F22":            tcell.KeyF22,
303         "F23":            tcell.KeyF23,
304         "F24":            tcell.KeyF24,
305         "F25":            tcell.KeyF25,
306         "F26":            tcell.KeyF26,
307         "F27":            tcell.KeyF27,
308         "F28":            tcell.KeyF28,
309         "F29":            tcell.KeyF29,
310         "F30":            tcell.KeyF30,
311         "F31":            tcell.KeyF31,
312         "F32":            tcell.KeyF32,
313         "F33":            tcell.KeyF33,
314         "F34":            tcell.KeyF34,
315         "F35":            tcell.KeyF35,
316         "F36":            tcell.KeyF36,
317         "F37":            tcell.KeyF37,
318         "F38":            tcell.KeyF38,
319         "F39":            tcell.KeyF39,
320         "F40":            tcell.KeyF40,
321         "F41":            tcell.KeyF41,
322         "F42":            tcell.KeyF42,
323         "F43":            tcell.KeyF43,
324         "F44":            tcell.KeyF44,
325         "F45":            tcell.KeyF45,
326         "F46":            tcell.KeyF46,
327         "F47":            tcell.KeyF47,
328         "F48":            tcell.KeyF48,
329         "F49":            tcell.KeyF49,
330         "F50":            tcell.KeyF50,
331         "F51":            tcell.KeyF51,
332         "F52":            tcell.KeyF52,
333         "F53":            tcell.KeyF53,
334         "F54":            tcell.KeyF54,
335         "F55":            tcell.KeyF55,
336         "F56":            tcell.KeyF56,
337         "F57":            tcell.KeyF57,
338         "F58":            tcell.KeyF58,
339         "F59":            tcell.KeyF59,
340         "F60":            tcell.KeyF60,
341         "F61":            tcell.KeyF61,
342         "F62":            tcell.KeyF62,
343         "F63":            tcell.KeyF63,
344         "F64":            tcell.KeyF64,
345         "CtrlSpace":      tcell.KeyCtrlSpace,
346         "CtrlA":          tcell.KeyCtrlA,
347         "CtrlB":          tcell.KeyCtrlB,
348         "CtrlC":          tcell.KeyCtrlC,
349         "CtrlD":          tcell.KeyCtrlD,
350         "CtrlE":          tcell.KeyCtrlE,
351         "CtrlF":          tcell.KeyCtrlF,
352         "CtrlG":          tcell.KeyCtrlG,
353         "CtrlH":          tcell.KeyCtrlH,
354         "CtrlI":          tcell.KeyCtrlI,
355         "CtrlJ":          tcell.KeyCtrlJ,
356         "CtrlK":          tcell.KeyCtrlK,
357         "CtrlL":          tcell.KeyCtrlL,
358         "CtrlM":          tcell.KeyCtrlM,
359         "CtrlN":          tcell.KeyCtrlN,
360         "CtrlO":          tcell.KeyCtrlO,
361         "CtrlP":          tcell.KeyCtrlP,
362         "CtrlQ":          tcell.KeyCtrlQ,
363         "CtrlR":          tcell.KeyCtrlR,
364         "CtrlS":          tcell.KeyCtrlS,
365         "CtrlT":          tcell.KeyCtrlT,
366         "CtrlU":          tcell.KeyCtrlU,
367         "CtrlV":          tcell.KeyCtrlV,
368         "CtrlW":          tcell.KeyCtrlW,
369         "CtrlX":          tcell.KeyCtrlX,
370         "CtrlY":          tcell.KeyCtrlY,
371         "CtrlZ":          tcell.KeyCtrlZ,
372         "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
373         "CtrlBackslash":  tcell.KeyCtrlBackslash,
374         "CtrlRightSq":    tcell.KeyCtrlRightSq,
375         "CtrlCarat":      tcell.KeyCtrlCarat,
376         "CtrlUnderscore": tcell.KeyCtrlUnderscore,
377         "CtrlPageUp":     tcell.KeyCtrlPgUp,
378         "CtrlPageDown":   tcell.KeyCtrlPgDn,
379         "Tab":            tcell.KeyTab,
380         "Esc":            tcell.KeyEsc,
381         "Escape":         tcell.KeyEscape,
382         "Enter":          tcell.KeyEnter,
383         "Backspace":      tcell.KeyBackspace2,
384         "OldBackspace":   tcell.KeyBackspace,
385
386         // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
387         "PgUp":   tcell.KeyPgUp,
388         "PgDown": tcell.KeyPgDn,
389 }
390
391 // DefaultBindings returns a map containing micro's default keybindings
392 func DefaultBindings() map[string]string {
393         return map[string]string{
394                 "Up":             "CursorUp",
395                 "Down":           "CursorDown",
396                 "Right":          "CursorRight",
397                 "Left":           "CursorLeft",
398                 "ShiftUp":        "SelectUp",
399                 "ShiftDown":      "SelectDown",
400                 "ShiftLeft":      "SelectLeft",
401                 "ShiftRight":     "SelectRight",
402                 "AltLeft":        "WordLeft",
403                 "AltRight":       "WordRight",
404                 "AltUp":          "MoveLinesUp",
405                 "AltDown":        "MoveLinesDown",
406                 "AltShiftRight":  "SelectWordRight",
407                 "AltShiftLeft":   "SelectWordLeft",
408                 "CtrlLeft":       "StartOfLine",
409                 "CtrlRight":      "EndOfLine",
410                 "CtrlShiftLeft":  "SelectToStartOfLine",
411                 "ShiftHome":      "SelectToStartOfLine",
412                 "CtrlShiftRight": "SelectToEndOfLine",
413                 "ShiftEnd":       "SelectToEndOfLine",
414                 "CtrlUp":         "CursorStart",
415                 "CtrlDown":       "CursorEnd",
416                 "CtrlShiftUp":    "SelectToStart",
417                 "CtrlShiftDown":  "SelectToEnd",
418                 "Alt-{":          "ParagraphPrevious",
419                 "Alt-}":          "ParagraphNext",
420                 "Enter":          "InsertNewline",
421                 "CtrlH":          "Backspace",
422                 "Backspace":      "Backspace",
423                 "Alt-CtrlH":      "DeleteWordLeft",
424                 "Alt-Backspace":  "DeleteWordLeft",
425                 "Tab":            "Autocomplete|IndentSelection|InsertTab",
426                 "Backtab":        "OutdentSelection|OutdentLine",
427                 "CtrlO":          "OpenFile",
428                 "CtrlS":          "Save",
429                 "CtrlF":          "Find",
430                 "CtrlN":          "FindNext",
431                 "CtrlP":          "FindPrevious",
432                 "CtrlZ":          "Undo",
433                 "CtrlY":          "Redo",
434                 "CtrlC":          "Copy",
435                 "CtrlX":          "Cut",
436                 "CtrlK":          "CutLine",
437                 "CtrlD":          "DuplicateLine",
438                 "CtrlV":          "Paste",
439                 "CtrlA":          "SelectAll",
440                 "CtrlT":          "AddTab",
441                 "Alt,":           "PreviousTab",
442                 "Alt.":           "NextTab",
443                 "Home":           "StartOfLine",
444                 "End":            "EndOfLine",
445                 "CtrlHome":       "CursorStart",
446                 "CtrlEnd":        "CursorEnd",
447                 "PageUp":         "CursorPageUp",
448                 "PageDown":       "CursorPageDown",
449                 "CtrlPageUp":     "PreviousTab",
450                 "CtrlPageDown":   "NextTab",
451                 "CtrlG":          "ToggleHelp",
452                 "Alt-g":          "ToggleKeyMenu",
453                 "CtrlR":          "ToggleRuler",
454                 "CtrlL":          "command-edit:goto ",
455                 "Delete":         "Delete",
456                 "CtrlB":          "ShellMode",
457                 "CtrlQ":          "Quit",
458                 "CtrlE":          "CommandMode",
459                 "CtrlW":          "NextSplit",
460                 "CtrlU":          "ToggleMacro",
461                 "CtrlJ":          "PlayMacro",
462                 "Insert":         "ToggleOverwriteMode",
463
464                 // Emacs-style keybindings
465                 "Alt-f": "WordRight",
466                 "Alt-b": "WordLeft",
467                 "Alt-a": "StartOfLine",
468                 "Alt-e": "EndOfLine",
469                 // "Alt-p": "CursorUp",
470                 // "Alt-n": "CursorDown",
471
472                 // Integration with file managers
473                 "F2":  "Save",
474                 "F3":  "Find",
475                 "F4":  "Quit",
476                 "F7":  "Find",
477                 "F10": "Quit",
478                 "Esc": "Escape",
479
480                 // Mouse bindings
481                 "MouseWheelUp":   "ScrollUp",
482                 "MouseWheelDown": "ScrollDown",
483                 "MouseLeft":      "MousePress",
484                 "MouseMiddle":    "PastePrimary",
485                 "Ctrl-MouseLeft": "MouseMultiCursor",
486
487                 "Alt-n": "SpawnMultiCursor",
488                 "Alt-m": "SpawnMultiCursorSelect",
489                 "Alt-p": "RemoveMultiCursor",
490                 "Alt-c": "RemoveAllMultiCursors",
491                 "Alt-x": "SkipMultiCursor",
492         }
493 }