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