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