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