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