11 "github.com/flynn/json5"
12 "github.com/zyedidia/micro/internal/config"
13 "github.com/zyedidia/micro/internal/screen"
14 "github.com/zyedidia/tcell"
18 config.Bindings = DefaultBindings()
20 var parsed map[string]string
21 defaults := DefaultBindings()
23 filename := config.ConfigDir + "/bindings.json"
24 if _, e := os.Stat(filename); e == nil {
25 input, err := ioutil.ReadFile(filename)
27 screen.TermMessage("Error reading bindings.json file: " + err.Error())
31 err = json5.Unmarshal(input, &parsed)
33 screen.TermMessage("Error reading bindings.json:", err.Error())
37 for k, v := range defaults {
40 for k, v := range parsed {
45 func BindKey(k, v string) {
46 event, ok := findEvent(k)
48 screen.TermMessage(k, "is not a bindable event")
51 switch e := event.(type) {
60 config.Bindings[k] = v
63 // findEvent will find binding Key 'b' using string 'k'
64 func findEvent(k string) (b Event, ok bool) {
65 modifiers := tcell.ModNone
67 // First, we'll strip off all the modifiers in the name and add them to the
72 case strings.HasPrefix(k, "-"):
73 // We optionally support dashes between modifiers
75 case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
76 // CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
78 modifiers |= tcell.ModCtrl
79 case strings.HasPrefix(k, "Alt"):
81 modifiers |= tcell.ModAlt
82 case strings.HasPrefix(k, "Shift"):
84 modifiers |= tcell.ModShift
85 case strings.HasPrefix(k, "\x1b"):
86 screen.Screen.RegisterRawSeq(k)
96 return KeyEvent{}, false
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 {
107 // Special case for escape, for some reason tcell doesn't send it with the esc character
108 if code < 256 && code != 27 {
111 // It is, we're done.
120 // See if we can find the key in bindingKeys
121 if code, ok := keyEvents[k]; ok {
123 // Special case for escape, for some reason tcell doesn't send it with the esc character
124 if code < 256 && code != 27 {
134 // See if we can find the key in bindingMouse
135 if code, ok := mouseEvents[k]; ok {
142 // If we were given one character, then we've got a rune.
151 // We don't know what happened.
152 return KeyEvent{}, false
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) {
159 var parsed map[string]string
161 filename := config.ConfigDir + "/bindings.json"
162 if _, e = os.Stat(filename); e == nil {
163 input, err := ioutil.ReadFile(filename)
165 return false, errors.New("Error reading bindings.json file: " + err.Error())
168 err = json5.Unmarshal(input, &parsed)
170 return false, errors.New("Error reading bindings.json: " + err.Error())
173 key, ok := findEvent(k)
175 return false, errors.New("Invalid event " + k)
179 for ev := range parsed {
180 if e, ok := findEvent(ev); ok {
191 if found && !overwrite {
199 txt, _ := json.MarshalIndent(parsed, "", " ")
200 return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
205 // UnbindKey removes the binding for a key from the bindings.json file
206 func UnbindKey(k string) error {
208 var parsed map[string]string
210 filename := config.ConfigDir + "/bindings.json"
211 if _, e = os.Stat(filename); e == nil {
212 input, err := ioutil.ReadFile(filename)
214 return errors.New("Error reading bindings.json file: " + err.Error())
217 err = json5.Unmarshal(input, &parsed)
219 return errors.New("Error reading bindings.json: " + err.Error())
222 key, ok := findEvent(k)
224 return errors.New("Invalid event " + k)
227 for ev := range parsed {
228 if e, ok := findEvent(ev); ok {
236 defaults := DefaultBindings()
237 if a, ok := defaults[k]; ok {
239 } else if _, ok := config.Bindings[k]; ok {
240 delete(config.Bindings, k)
243 txt, _ := json.MarshalIndent(parsed, "", " ")
244 return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
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,
259 var keyEvents = map[string]tcell.Key{
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,
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,
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,
380 "Escape": tcell.KeyEscape,
381 "Enter": tcell.KeyEnter,
382 "Backspace": tcell.KeyBackspace2,
383 "OldBackspace": tcell.KeyBackspace,
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,
390 // DefaultBindings returns a map containing micro's default keybindings
391 func DefaultBindings() map[string]string {
392 return map[string]string{
394 "Down": "CursorDown",
395 "Right": "CursorRight",
396 "Left": "CursorLeft",
397 "ShiftUp": "SelectUp",
398 "ShiftDown": "SelectDown",
399 "ShiftLeft": "SelectLeft",
400 "ShiftRight": "SelectRight",
401 "AltLeft": "WordLeft",
402 "AltRight": "WordRight",
403 "AltUp": "MoveLinesUp",
404 "AltDown": "MoveLinesDown",
405 "AltShiftRight": "SelectWordRight",
406 "AltShiftLeft": "SelectWordLeft",
407 "CtrlLeft": "StartOfLine",
408 "CtrlRight": "EndOfLine",
409 "CtrlShiftLeft": "SelectToStartOfLine",
410 "ShiftHome": "SelectToStartOfLine",
411 "CtrlShiftRight": "SelectToEndOfLine",
412 "ShiftEnd": "SelectToEndOfLine",
413 "CtrlUp": "CursorStart",
414 "CtrlDown": "CursorEnd",
415 "CtrlShiftUp": "SelectToStart",
416 "CtrlShiftDown": "SelectToEnd",
417 "Alt-{": "ParagraphPrevious",
418 "Alt-}": "ParagraphNext",
419 "Enter": "InsertNewline",
420 "CtrlH": "Backspace",
421 "Backspace": "Backspace",
422 "Alt-CtrlH": "DeleteWordLeft",
423 "Alt-Backspace": "DeleteWordLeft",
424 "Tab": "Autocomplete|IndentSelection|InsertTab",
425 "Backtab": "OutdentSelection|OutdentLine",
430 "CtrlP": "FindPrevious",
436 "CtrlD": "DuplicateLine",
438 "CtrlA": "SelectAll",
440 "Alt,": "PreviousTab",
442 "Home": "StartOfLine",
444 "CtrlHome": "CursorStart",
445 "CtrlEnd": "CursorEnd",
446 "PageUp": "CursorPageUp",
447 "PageDown": "CursorPageDown",
448 "CtrlPageUp": "PreviousTab",
449 "CtrlPageDown": "NextTab",
450 "CtrlG": "ToggleHelp",
451 "Alt-g": "ToggleKeyMenu",
452 "CtrlR": "ToggleRuler",
453 "CtrlL": "command-edit:goto ",
455 "CtrlB": "ShellMode",
457 "CtrlE": "CommandMode",
458 "CtrlW": "NextSplit",
459 "CtrlU": "ToggleMacro",
460 "CtrlJ": "PlayMacro",
461 "Insert": "ToggleOverwriteMode",
463 // Emacs-style keybindings
464 "Alt-f": "WordRight",
466 "Alt-a": "StartOfLine",
467 "Alt-e": "EndOfLine",
468 // "Alt-p": "CursorUp",
469 // "Alt-n": "CursorDown",
471 // Integration with file managers
480 "MouseWheelUp": "ScrollUp",
481 "MouseWheelDown": "ScrollDown",
482 "MouseLeft": "MousePress",
483 "MouseMiddle": "PastePrimary",
484 "Ctrl-MouseLeft": "MouseMultiCursor",
486 "Alt-n": "SpawnMultiCursor",
487 "Alt-m": "SpawnMultiCursorSelect",
488 "Alt-p": "RemoveMultiCursor",
489 "Alt-c": "RemoveAllMultiCursors",
490 "Alt-x": "SkipMultiCursor",