]> git.lizzy.rs Git - micro.git/blob - cmd/micro/action/bindings.go
Action subpackage
[micro.git] / cmd / micro / 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/flynn/json5"
12         "github.com/zyedidia/micro/cmd/micro/config"
13         "github.com/zyedidia/micro/cmd/micro/util"
14         "github.com/zyedidia/tcell"
15 )
16
17 var Bindings = DefaultBindings()
18
19 func InitBindings() {
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                         util.TermMessage("Error reading bindings.json file: " + err.Error())
28                         return
29                 }
30
31                 err = json5.Unmarshal(input, &parsed)
32                 if err != nil {
33                         util.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                 util.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                 util.TermMessage("Raw events not supported yet")
58         }
59
60         Bindings[k] = v
61 }
62
63 // findKeyEvent 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                         return RawEvent{
87                                 esc: k,
88                         }, true
89                 default:
90                         break modSearch
91                 }
92         }
93
94         if len(k) == 0 {
95                 return KeyEvent{}, false
96         }
97
98         // Control is handled in a special way, since the terminal sends explicitly
99         // marked escape sequences for control keys
100         // We should check for Control keys first
101         if modifiers&tcell.ModCtrl != 0 {
102                 // see if the key is in bindingKeys with the Ctrl prefix.
103                 k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
104                 if code, ok := keyEvents["Ctrl"+k]; ok {
105                         // It is, we're done.
106                         return KeyEvent{
107                                 code: code,
108                                 mod:  modifiers,
109                                 r:    rune(code),
110                         }, true
111                 }
112         }
113
114         // See if we can find the key in bindingKeys
115         if code, ok := keyEvents[k]; ok {
116                 return KeyEvent{
117                         code: code,
118                         mod:  modifiers,
119                         r:    0,
120                 }, true
121         }
122
123         // See if we can find the key in bindingMouse
124         if code, ok := mouseEvents[k]; ok {
125                 return MouseEvent{
126                         btn: code,
127                         mod: modifiers,
128                 }, true
129         }
130
131         // If we were given one character, then we've got a rune.
132         if len(k) == 1 {
133                 return KeyEvent{
134                         code: tcell.KeyRune,
135                         mod:  modifiers,
136                         r:    rune(k[0]),
137                 }, true
138         }
139
140         // We don't know what happened.
141         return KeyEvent{}, false
142 }
143
144 // TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
145 // Returns true if the keybinding already existed and a possible error
146 func TryBindKey(k, v string, overwrite bool) (bool, error) {
147         var e error
148         var parsed map[string]string
149
150         filename := config.ConfigDir + "/bindings.json"
151         if _, e = os.Stat(filename); e == nil {
152                 input, err := ioutil.ReadFile(filename)
153                 if err != nil {
154                         return false, errors.New("Error reading bindings.json file: " + err.Error())
155                 }
156
157                 err = json5.Unmarshal(input, &parsed)
158                 if err != nil {
159                         return false, errors.New("Error reading bindings.json: " + err.Error())
160                 }
161
162                 key, ok := findEvent(k)
163                 if !ok {
164                         return false, errors.New("Invalid event " + k)
165                 }
166
167                 found := false
168                 for ev := range parsed {
169                         if e, ok := findEvent(ev); ok {
170                                 if e == key {
171                                         if overwrite {
172                                                 parsed[ev] = v
173                                         }
174                                         found = true
175                                         break
176                                 }
177                         }
178                 }
179
180                 if found && !overwrite {
181                         return true, nil
182                 } else if !found {
183                         parsed[k] = v
184                 }
185
186                 BindKey(k, v)
187
188                 txt, _ := json.MarshalIndent(parsed, "", "    ")
189                 return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
190         }
191         return false, e
192 }
193
194 var mouseEvents = map[string]tcell.ButtonMask{
195         "MouseLeft":       tcell.Button1,
196         "MouseMiddle":     tcell.Button2,
197         "MouseRight":      tcell.Button3,
198         "MouseWheelUp":    tcell.WheelUp,
199         "MouseWheelDown":  tcell.WheelDown,
200         "MouseWheelLeft":  tcell.WheelLeft,
201         "MouseWheelRight": tcell.WheelRight,
202 }
203
204 var keyEvents = map[string]tcell.Key{
205         "Up":             tcell.KeyUp,
206         "Down":           tcell.KeyDown,
207         "Right":          tcell.KeyRight,
208         "Left":           tcell.KeyLeft,
209         "UpLeft":         tcell.KeyUpLeft,
210         "UpRight":        tcell.KeyUpRight,
211         "DownLeft":       tcell.KeyDownLeft,
212         "DownRight":      tcell.KeyDownRight,
213         "Center":         tcell.KeyCenter,
214         "PageUp":         tcell.KeyPgUp,
215         "PageDown":       tcell.KeyPgDn,
216         "Home":           tcell.KeyHome,
217         "End":            tcell.KeyEnd,
218         "Insert":         tcell.KeyInsert,
219         "Delete":         tcell.KeyDelete,
220         "Help":           tcell.KeyHelp,
221         "Exit":           tcell.KeyExit,
222         "Clear":          tcell.KeyClear,
223         "Cancel":         tcell.KeyCancel,
224         "Print":          tcell.KeyPrint,
225         "Pause":          tcell.KeyPause,
226         "Backtab":        tcell.KeyBacktab,
227         "F1":             tcell.KeyF1,
228         "F2":             tcell.KeyF2,
229         "F3":             tcell.KeyF3,
230         "F4":             tcell.KeyF4,
231         "F5":             tcell.KeyF5,
232         "F6":             tcell.KeyF6,
233         "F7":             tcell.KeyF7,
234         "F8":             tcell.KeyF8,
235         "F9":             tcell.KeyF9,
236         "F10":            tcell.KeyF10,
237         "F11":            tcell.KeyF11,
238         "F12":            tcell.KeyF12,
239         "F13":            tcell.KeyF13,
240         "F14":            tcell.KeyF14,
241         "F15":            tcell.KeyF15,
242         "F16":            tcell.KeyF16,
243         "F17":            tcell.KeyF17,
244         "F18":            tcell.KeyF18,
245         "F19":            tcell.KeyF19,
246         "F20":            tcell.KeyF20,
247         "F21":            tcell.KeyF21,
248         "F22":            tcell.KeyF22,
249         "F23":            tcell.KeyF23,
250         "F24":            tcell.KeyF24,
251         "F25":            tcell.KeyF25,
252         "F26":            tcell.KeyF26,
253         "F27":            tcell.KeyF27,
254         "F28":            tcell.KeyF28,
255         "F29":            tcell.KeyF29,
256         "F30":            tcell.KeyF30,
257         "F31":            tcell.KeyF31,
258         "F32":            tcell.KeyF32,
259         "F33":            tcell.KeyF33,
260         "F34":            tcell.KeyF34,
261         "F35":            tcell.KeyF35,
262         "F36":            tcell.KeyF36,
263         "F37":            tcell.KeyF37,
264         "F38":            tcell.KeyF38,
265         "F39":            tcell.KeyF39,
266         "F40":            tcell.KeyF40,
267         "F41":            tcell.KeyF41,
268         "F42":            tcell.KeyF42,
269         "F43":            tcell.KeyF43,
270         "F44":            tcell.KeyF44,
271         "F45":            tcell.KeyF45,
272         "F46":            tcell.KeyF46,
273         "F47":            tcell.KeyF47,
274         "F48":            tcell.KeyF48,
275         "F49":            tcell.KeyF49,
276         "F50":            tcell.KeyF50,
277         "F51":            tcell.KeyF51,
278         "F52":            tcell.KeyF52,
279         "F53":            tcell.KeyF53,
280         "F54":            tcell.KeyF54,
281         "F55":            tcell.KeyF55,
282         "F56":            tcell.KeyF56,
283         "F57":            tcell.KeyF57,
284         "F58":            tcell.KeyF58,
285         "F59":            tcell.KeyF59,
286         "F60":            tcell.KeyF60,
287         "F61":            tcell.KeyF61,
288         "F62":            tcell.KeyF62,
289         "F63":            tcell.KeyF63,
290         "F64":            tcell.KeyF64,
291         "CtrlSpace":      tcell.KeyCtrlSpace,
292         "CtrlA":          tcell.KeyCtrlA,
293         "CtrlB":          tcell.KeyCtrlB,
294         "CtrlC":          tcell.KeyCtrlC,
295         "CtrlD":          tcell.KeyCtrlD,
296         "CtrlE":          tcell.KeyCtrlE,
297         "CtrlF":          tcell.KeyCtrlF,
298         "CtrlG":          tcell.KeyCtrlG,
299         "CtrlH":          tcell.KeyCtrlH,
300         "CtrlI":          tcell.KeyCtrlI,
301         "CtrlJ":          tcell.KeyCtrlJ,
302         "CtrlK":          tcell.KeyCtrlK,
303         "CtrlL":          tcell.KeyCtrlL,
304         "CtrlM":          tcell.KeyCtrlM,
305         "CtrlN":          tcell.KeyCtrlN,
306         "CtrlO":          tcell.KeyCtrlO,
307         "CtrlP":          tcell.KeyCtrlP,
308         "CtrlQ":          tcell.KeyCtrlQ,
309         "CtrlR":          tcell.KeyCtrlR,
310         "CtrlS":          tcell.KeyCtrlS,
311         "CtrlT":          tcell.KeyCtrlT,
312         "CtrlU":          tcell.KeyCtrlU,
313         "CtrlV":          tcell.KeyCtrlV,
314         "CtrlW":          tcell.KeyCtrlW,
315         "CtrlX":          tcell.KeyCtrlX,
316         "CtrlY":          tcell.KeyCtrlY,
317         "CtrlZ":          tcell.KeyCtrlZ,
318         "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
319         "CtrlBackslash":  tcell.KeyCtrlBackslash,
320         "CtrlRightSq":    tcell.KeyCtrlRightSq,
321         "CtrlCarat":      tcell.KeyCtrlCarat,
322         "CtrlUnderscore": tcell.KeyCtrlUnderscore,
323         "CtrlPageUp":     tcell.KeyCtrlPgUp,
324         "CtrlPageDown":   tcell.KeyCtrlPgDn,
325         "Tab":            tcell.KeyTab,
326         "Esc":            tcell.KeyEsc,
327         "Escape":         tcell.KeyEscape,
328         "Enter":          tcell.KeyEnter,
329         "Backspace":      tcell.KeyBackspace2,
330         "OldBackspace":   tcell.KeyBackspace,
331
332         // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
333         "PgUp":   tcell.KeyPgUp,
334         "PgDown": tcell.KeyPgDn,
335 }
336
337 // DefaultBindings returns a map containing micro's default keybindings
338 func DefaultBindings() map[string]string {
339         return map[string]string{
340                 "Up":             "CursorUp",
341                 "Down":           "CursorDown",
342                 "Right":          "CursorRight",
343                 "Left":           "CursorLeft",
344                 "ShiftUp":        "SelectUp",
345                 "ShiftDown":      "SelectDown",
346                 "ShiftLeft":      "SelectLeft",
347                 "ShiftRight":     "SelectRight",
348                 "AltLeft":        "WordLeft",
349                 "AltRight":       "WordRight",
350                 "AltUp":          "MoveLinesUp",
351                 "AltDown":        "MoveLinesDown",
352                 "AltShiftRight":  "SelectWordRight",
353                 "AltShiftLeft":   "SelectWordLeft",
354                 "CtrlLeft":       "StartOfLine",
355                 "CtrlRight":      "EndOfLine",
356                 "CtrlShiftLeft":  "SelectToStartOfLine",
357                 "ShiftHome":      "SelectToStartOfLine",
358                 "CtrlShiftRight": "SelectToEndOfLine",
359                 "ShiftEnd":       "SelectToEndOfLine",
360                 "CtrlUp":         "CursorStart",
361                 "CtrlDown":       "CursorEnd",
362                 "CtrlShiftUp":    "SelectToStart",
363                 "CtrlShiftDown":  "SelectToEnd",
364                 "Alt-{":          "ParagraphPrevious",
365                 "Alt-}":          "ParagraphNext",
366                 "Enter":          "InsertNewline",
367                 "CtrlH":          "Backspace",
368                 "Backspace":      "Backspace",
369                 "Alt-CtrlH":      "DeleteWordLeft",
370                 "Alt-Backspace":  "DeleteWordLeft",
371                 "Tab":            "IndentSelection,InsertTab",
372                 "Backtab":        "OutdentSelection,OutdentLine",
373                 "CtrlO":          "OpenFile",
374                 "CtrlS":          "Save",
375                 "CtrlF":          "Find",
376                 "CtrlN":          "FindNext",
377                 "CtrlP":          "FindPrevious",
378                 "CtrlZ":          "Undo",
379                 "CtrlY":          "Redo",
380                 "CtrlC":          "Copy",
381                 "CtrlX":          "Cut",
382                 "CtrlK":          "CutLine",
383                 "CtrlD":          "DuplicateLine",
384                 "CtrlV":          "Paste",
385                 "CtrlA":          "SelectAll",
386                 "CtrlT":          "AddTab",
387                 "Alt,":           "PreviousTab",
388                 "Alt.":           "NextTab",
389                 "Home":           "StartOfLine",
390                 "End":            "EndOfLine",
391                 "CtrlHome":       "CursorStart",
392                 "CtrlEnd":        "CursorEnd",
393                 "PageUp":         "CursorPageUp",
394                 "PageDown":       "CursorPageDown",
395                 "CtrlPageUp":     "PreviousTab",
396                 "CtrlPageDown":   "NextTab",
397                 "CtrlG":          "ToggleHelp",
398                 "Alt-g":          "ToggleKeyMenu",
399                 "CtrlR":          "ToggleRuler",
400                 "CtrlL":          "JumpLine",
401                 "Delete":         "Delete",
402                 "CtrlB":          "ShellMode",
403                 "CtrlQ":          "Quit",
404                 "CtrlE":          "CommandMode",
405                 "CtrlW":          "NextSplit",
406                 "CtrlU":          "ToggleMacro",
407                 "CtrlJ":          "PlayMacro",
408                 "Insert":         "ToggleOverwriteMode",
409
410                 // Emacs-style keybindings
411                 "Alt-f": "WordRight",
412                 "Alt-b": "WordLeft",
413                 "Alt-a": "StartOfLine",
414                 "Alt-e": "EndOfLine",
415                 // "Alt-p": "CursorUp",
416                 // "Alt-n": "CursorDown",
417
418                 // Integration with file managers
419                 "F2":  "Save",
420                 "F3":  "Find",
421                 "F4":  "Quit",
422                 "F7":  "Find",
423                 "F10": "Quit",
424                 "Esc": "Escape",
425
426                 // Mouse bindings
427                 "MouseWheelUp":   "ScrollUp",
428                 "MouseWheelDown": "ScrollDown",
429                 "MouseLeft":      "MousePress",
430                 "MouseMiddle":    "PastePrimary",
431                 "Ctrl-MouseLeft": "MouseMultiCursor",
432
433                 "Alt-n": "SpawnMultiCursor",
434                 "Alt-m": "SpawnMultiCursorSelect",
435                 "Alt-p": "RemoveMultiCursor",
436                 "Alt-c": "RemoveAllMultiCursors",
437                 "Alt-x": "SkipMultiCursor",
438         }
439 }