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