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"):
95 return KeyEvent{}, false
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 {
106 // Special case for escape, for some reason tcell doesn't send it with the esc character
107 if code < 256 && code != 27 {
110 // It is, we're done.
119 // See if we can find the key in bindingKeys
120 if code, ok := keyEvents[k]; ok {
122 // Special case for escape, for some reason tcell doesn't send it with the esc character
123 if code < 256 && code != 27 {
133 // See if we can find the key in bindingMouse
134 if code, ok := mouseEvents[k]; ok {
141 // If we were given one character, then we've got a rune.
150 // We don't know what happened.
151 return KeyEvent{}, false
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) {
158 var parsed map[string]string
160 filename := config.ConfigDir + "/bindings.json"
161 if _, e = os.Stat(filename); e == nil {
162 input, err := ioutil.ReadFile(filename)
164 return false, errors.New("Error reading bindings.json file: " + err.Error())
167 err = json5.Unmarshal(input, &parsed)
169 return false, errors.New("Error reading bindings.json: " + err.Error())
172 key, ok := findEvent(k)
174 return false, errors.New("Invalid event " + k)
178 for ev := range parsed {
179 if e, ok := findEvent(ev); ok {
190 if found && !overwrite {
198 txt, _ := json.MarshalIndent(parsed, "", " ")
199 return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
204 // UnbindKey removes the binding for a key from the bindings.json file
205 func UnbindKey(k string) error {
207 var parsed map[string]string
209 filename := config.ConfigDir + "/bindings.json"
210 if _, e = os.Stat(filename); e == nil {
211 input, err := ioutil.ReadFile(filename)
213 return errors.New("Error reading bindings.json file: " + err.Error())
216 err = json5.Unmarshal(input, &parsed)
218 return errors.New("Error reading bindings.json: " + err.Error())
221 key, ok := findEvent(k)
223 return errors.New("Invalid event " + k)
226 for ev := range parsed {
227 if e, ok := findEvent(ev); ok {
235 defaults := DefaultBindings()
236 if a, ok := defaults[k]; ok {
238 } else if _, ok := config.Bindings[k]; ok {
239 delete(config.Bindings, k)
242 txt, _ := json.MarshalIndent(parsed, "", " ")
243 return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
248 var mouseEvents = map[string]tcell.ButtonMask{
249 "MouseLeft": tcell.Button1,
250 "MouseMiddle": tcell.Button2,
251 "MouseRight": tcell.Button3,
252 "MouseWheelUp": tcell.WheelUp,
253 "MouseWheelDown": tcell.WheelDown,
254 "MouseWheelLeft": tcell.WheelLeft,
255 "MouseWheelRight": tcell.WheelRight,
258 var keyEvents = map[string]tcell.Key{
260 "Down": tcell.KeyDown,
261 "Right": tcell.KeyRight,
262 "Left": tcell.KeyLeft,
263 "UpLeft": tcell.KeyUpLeft,
264 "UpRight": tcell.KeyUpRight,
265 "DownLeft": tcell.KeyDownLeft,
266 "DownRight": tcell.KeyDownRight,
267 "Center": tcell.KeyCenter,
268 "PageUp": tcell.KeyPgUp,
269 "PageDown": tcell.KeyPgDn,
270 "Home": tcell.KeyHome,
272 "Insert": tcell.KeyInsert,
273 "Delete": tcell.KeyDelete,
274 "Help": tcell.KeyHelp,
275 "Exit": tcell.KeyExit,
276 "Clear": tcell.KeyClear,
277 "Cancel": tcell.KeyCancel,
278 "Print": tcell.KeyPrint,
279 "Pause": tcell.KeyPause,
280 "Backtab": tcell.KeyBacktab,
345 "CtrlSpace": tcell.KeyCtrlSpace,
346 "CtrlA": tcell.KeyCtrlA,
347 "CtrlB": tcell.KeyCtrlB,
348 "CtrlC": tcell.KeyCtrlC,
349 "CtrlD": tcell.KeyCtrlD,
350 "CtrlE": tcell.KeyCtrlE,
351 "CtrlF": tcell.KeyCtrlF,
352 "CtrlG": tcell.KeyCtrlG,
353 "CtrlH": tcell.KeyCtrlH,
354 "CtrlI": tcell.KeyCtrlI,
355 "CtrlJ": tcell.KeyCtrlJ,
356 "CtrlK": tcell.KeyCtrlK,
357 "CtrlL": tcell.KeyCtrlL,
358 "CtrlM": tcell.KeyCtrlM,
359 "CtrlN": tcell.KeyCtrlN,
360 "CtrlO": tcell.KeyCtrlO,
361 "CtrlP": tcell.KeyCtrlP,
362 "CtrlQ": tcell.KeyCtrlQ,
363 "CtrlR": tcell.KeyCtrlR,
364 "CtrlS": tcell.KeyCtrlS,
365 "CtrlT": tcell.KeyCtrlT,
366 "CtrlU": tcell.KeyCtrlU,
367 "CtrlV": tcell.KeyCtrlV,
368 "CtrlW": tcell.KeyCtrlW,
369 "CtrlX": tcell.KeyCtrlX,
370 "CtrlY": tcell.KeyCtrlY,
371 "CtrlZ": tcell.KeyCtrlZ,
372 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
373 "CtrlBackslash": tcell.KeyCtrlBackslash,
374 "CtrlRightSq": tcell.KeyCtrlRightSq,
375 "CtrlCarat": tcell.KeyCtrlCarat,
376 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
377 "CtrlPageUp": tcell.KeyCtrlPgUp,
378 "CtrlPageDown": tcell.KeyCtrlPgDn,
381 "Escape": tcell.KeyEscape,
382 "Enter": tcell.KeyEnter,
383 "Backspace": tcell.KeyBackspace2,
384 "OldBackspace": tcell.KeyBackspace,
386 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
387 "PgUp": tcell.KeyPgUp,
388 "PgDown": tcell.KeyPgDn,
391 // DefaultBindings returns a map containing micro's default keybindings
392 func DefaultBindings() map[string]string {
393 return map[string]string{
395 "Down": "CursorDown",
396 "Right": "CursorRight",
397 "Left": "CursorLeft",
398 "ShiftUp": "SelectUp",
399 "ShiftDown": "SelectDown",
400 "ShiftLeft": "SelectLeft",
401 "ShiftRight": "SelectRight",
402 "AltLeft": "WordLeft",
403 "AltRight": "WordRight",
404 "AltUp": "MoveLinesUp",
405 "AltDown": "MoveLinesDown",
406 "AltShiftRight": "SelectWordRight",
407 "AltShiftLeft": "SelectWordLeft",
408 "CtrlLeft": "StartOfLine",
409 "CtrlRight": "EndOfLine",
410 "CtrlShiftLeft": "SelectToStartOfLine",
411 "ShiftHome": "SelectToStartOfLine",
412 "CtrlShiftRight": "SelectToEndOfLine",
413 "ShiftEnd": "SelectToEndOfLine",
414 "CtrlUp": "CursorStart",
415 "CtrlDown": "CursorEnd",
416 "CtrlShiftUp": "SelectToStart",
417 "CtrlShiftDown": "SelectToEnd",
418 "Alt-{": "ParagraphPrevious",
419 "Alt-}": "ParagraphNext",
420 "Enter": "InsertNewline",
421 "CtrlH": "Backspace",
422 "Backspace": "Backspace",
423 "Alt-CtrlH": "DeleteWordLeft",
424 "Alt-Backspace": "DeleteWordLeft",
425 "Tab": "Autocomplete|IndentSelection|InsertTab",
426 "Backtab": "OutdentSelection|OutdentLine",
431 "CtrlP": "FindPrevious",
437 "CtrlD": "DuplicateLine",
439 "CtrlA": "SelectAll",
441 "Alt,": "PreviousTab",
443 "Home": "StartOfLine",
445 "CtrlHome": "CursorStart",
446 "CtrlEnd": "CursorEnd",
447 "PageUp": "CursorPageUp",
448 "PageDown": "CursorPageDown",
449 "CtrlPageUp": "PreviousTab",
450 "CtrlPageDown": "NextTab",
451 "CtrlG": "ToggleHelp",
452 "Alt-g": "ToggleKeyMenu",
453 "CtrlR": "ToggleRuler",
454 "CtrlL": "command-edit:goto ",
456 "CtrlB": "ShellMode",
458 "CtrlE": "CommandMode",
459 "CtrlW": "NextSplit",
460 "CtrlU": "ToggleMacro",
461 "CtrlJ": "PlayMacro",
462 "Insert": "ToggleOverwriteMode",
464 // Emacs-style keybindings
465 "Alt-f": "WordRight",
467 "Alt-a": "StartOfLine",
468 "Alt-e": "EndOfLine",
469 // "Alt-p": "CursorUp",
470 // "Alt-n": "CursorDown",
472 // Integration with file managers
481 "MouseWheelUp": "ScrollUp",
482 "MouseWheelDown": "ScrollDown",
483 "MouseLeft": "MousePress",
484 "MouseMiddle": "PastePrimary",
485 "Ctrl-MouseLeft": "MouseMultiCursor",
487 "Alt-n": "SpawnMultiCursor",
488 "Alt-m": "SpawnMultiCursorSelect",
489 "Alt-p": "RemoveMultiCursor",
490 "Alt-c": "RemoveAllMultiCursors",
491 "Alt-x": "SkipMultiCursor",