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