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