"errors"
"io/ioutil"
"os"
+ "path/filepath"
+ "regexp"
"strings"
"unicode"
- "github.com/flynn/json5"
- "github.com/zyedidia/micro/internal/config"
- "github.com/zyedidia/micro/internal/screen"
- "github.com/zyedidia/tcell"
+ "github.com/zyedidia/json5"
+ "github.com/zyedidia/micro/v2/internal/config"
+ "github.com/zyedidia/micro/v2/internal/screen"
+ "github.com/zyedidia/tcell/v2"
)
+var Binder = map[string]func(e Event, action string){
+ "command": InfoMapEvent,
+ "buffer": BufMapEvent,
+ "terminal": TermMapEvent,
+}
+
+func createBindingsIfNotExist(fname string) {
+ if _, e := os.Stat(fname); os.IsNotExist(e) {
+ ioutil.WriteFile(fname, []byte("{}"), 0644)
+ }
+}
+
+// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
- config.Bindings = DefaultBindings()
+ var parsed map[string]interface{}
- var parsed map[string]string
- defaults := DefaultBindings()
+ filename := filepath.Join(config.ConfigDir, "bindings.json")
+ createBindingsIfNotExist(filename)
- filename := config.ConfigDir + "/bindings.json"
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
}
}
- for k, v := range defaults {
- BindKey(k, v)
+ for p, bind := range Binder {
+ defaults := DefaultBindings(p)
+
+ for k, v := range defaults {
+ BindKey(k, v, bind)
+ }
}
+
for k, v := range parsed {
- BindKey(k, v)
+ switch val := v.(type) {
+ case string:
+ BindKey(k, val, Binder["buffer"])
+ case map[string]interface{}:
+ bind := Binder[k]
+ for e, a := range val {
+ s, ok := a.(string)
+ if !ok {
+ screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
+ } else {
+ BindKey(e, s, bind)
+ }
+ }
+ default:
+ screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
+ }
}
}
-func BindKey(k, v string) {
- event, ok := findEvent(k)
- if !ok {
- screen.TermMessage(k, "is not a bindable event")
+func BindKey(k, v string, bind func(e Event, a string)) {
+ event, err := findEvent(k)
+ if err != nil {
+ screen.TermMessage(err)
+ return
}
- switch e := event.(type) {
- case KeyEvent:
- BufMapKey(e, v)
- case MouseEvent:
- BufMapMouse(e, v)
- case RawEvent:
- BufMapKey(e, v)
+ config.Bindings[event.Name()] = v
+
+ bind(event, v)
+
+ // switch e := event.(type) {
+ // case KeyEvent:
+ // InfoMapKey(e, v)
+ // case KeySequenceEvent:
+ // InfoMapKey(e, v)
+ // case MouseEvent:
+ // InfoMapMouse(e, v)
+ // case RawEvent:
+ // InfoMapKey(e, v)
+ // }
+}
+
+var r = regexp.MustCompile("<(.+?)>")
+
+func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
+ var events []Event = nil
+ for len(k) > 0 {
+ groups := r.FindStringSubmatchIndex(k)
+
+ if len(groups) > 3 {
+ if events == nil {
+ events = make([]Event, 0, 3)
+ }
+
+ e, ok := findSingleEvent(k[groups[2]:groups[3]])
+ if !ok {
+ return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
+ }
+
+ events = append(events, e)
+
+ k = k[groups[3]+1:]
+ } else {
+ return KeySequenceEvent{}, false, nil
+ }
}
- config.Bindings[k] = v
+ return KeySequenceEvent{events}, true, nil
}
-// findEvent will find binding Key 'b' using string 'k'
-func findEvent(k string) (b Event, ok bool) {
+// findSingleEvent will find binding Key 'b' using string 'k'
+func findSingleEvent(k string) (b Event, ok bool) {
modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the
return KeyEvent{}, false
}
+func findEvent(k string) (Event, error) {
+ var event Event
+ event, ok, err := findEvents(k)
+ if err != nil {
+ return nil, err
+ }
+
+ if !ok {
+ event, ok = findSingleEvent(k)
+ if !ok {
+ return nil, errors.New(k + " is not a bindable event")
+ }
+ }
+
+ return event, nil
+}
+
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, error) {
var e error
var parsed map[string]string
- filename := config.ConfigDir + "/bindings.json"
+ filename := filepath.Join(config.ConfigDir, "bindings.json")
+ createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json: " + err.Error())
}
- key, ok := findEvent(k)
- if !ok {
- return false, errors.New("Invalid event " + k)
+ key, err := findEvent(k)
+ if err != nil {
+ return false, err
}
found := false
for ev := range parsed {
- if e, ok := findEvent(ev); ok {
+ if e, err := findEvent(ev); err == nil {
if e == key {
if overwrite {
parsed[ev] = v
parsed[k] = v
}
- BindKey(k, v)
+ BindKey(k, v, Binder["buffer"])
txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
var e error
var parsed map[string]string
- filename := config.ConfigDir + "/bindings.json"
+ filename := filepath.Join(config.ConfigDir, "bindings.json")
+ createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json: " + err.Error())
}
- key, ok := findEvent(k)
- if !ok {
- return errors.New("Invalid event " + k)
+ key, err := findEvent(k)
+ if err != nil {
+ return err
}
for ev := range parsed {
- if e, ok := findEvent(ev); ok {
+ if e, err := findEvent(ev); err == nil {
if e == key {
delete(parsed, ev)
break
}
}
- defaults := DefaultBindings()
+ defaults := DefaultBindings("buffer")
if a, ok := defaults[k]; ok {
- BindKey(k, a)
+ BindKey(k, a, Binder["buffer"])
} else if _, ok := config.Bindings[k]; ok {
+ BufUnmap(key)
delete(config.Bindings, k)
}
}
var mouseEvents = map[string]tcell.ButtonMask{
- "MouseLeft": tcell.Button1,
- "MouseMiddle": tcell.Button2,
- "MouseRight": tcell.Button3,
+ "MouseLeft": tcell.ButtonPrimary,
+ "MouseMiddle": tcell.ButtonMiddle,
+ "MouseRight": tcell.ButtonSecondary,
"MouseWheelUp": tcell.WheelUp,
"MouseWheelDown": tcell.WheelDown,
"MouseWheelLeft": tcell.WheelLeft,
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}
-
-// DefaultBindings returns a map containing micro's default keybindings
-func DefaultBindings() map[string]string {
- return map[string]string{
- "Up": "CursorUp",
- "Down": "CursorDown",
- "Right": "CursorRight",
- "Left": "CursorLeft",
- "ShiftUp": "SelectUp",
- "ShiftDown": "SelectDown",
- "ShiftLeft": "SelectLeft",
- "ShiftRight": "SelectRight",
- "AltLeft": "WordLeft",
- "AltRight": "WordRight",
- "AltUp": "MoveLinesUp",
- "AltDown": "MoveLinesDown",
- "AltShiftRight": "SelectWordRight",
- "AltShiftLeft": "SelectWordLeft",
- "CtrlLeft": "StartOfLine",
- "CtrlRight": "EndOfLine",
- "CtrlShiftLeft": "SelectToStartOfLine",
- "ShiftHome": "SelectToStartOfLine",
- "CtrlShiftRight": "SelectToEndOfLine",
- "ShiftEnd": "SelectToEndOfLine",
- "CtrlUp": "CursorStart",
- "CtrlDown": "CursorEnd",
- "CtrlShiftUp": "SelectToStart",
- "CtrlShiftDown": "SelectToEnd",
- "Alt-{": "ParagraphPrevious",
- "Alt-}": "ParagraphNext",
- "Enter": "InsertNewline",
- "CtrlH": "Backspace",
- "Backspace": "Backspace",
- "Alt-CtrlH": "DeleteWordLeft",
- "Alt-Backspace": "DeleteWordLeft",
- "Tab": "Autocomplete|IndentSelection|InsertTab",
- "Backtab": "OutdentSelection|OutdentLine",
- "CtrlO": "OpenFile",
- "CtrlS": "Save",
- "CtrlF": "Find",
- "CtrlN": "FindNext",
- "CtrlP": "FindPrevious",
- "CtrlZ": "Undo",
- "CtrlY": "Redo",
- "CtrlC": "Copy",
- "CtrlX": "Cut",
- "CtrlK": "CutLine",
- "CtrlD": "DuplicateLine",
- "CtrlV": "Paste",
- "CtrlA": "SelectAll",
- "CtrlT": "AddTab",
- "Alt,": "PreviousTab",
- "Alt.": "NextTab",
- "Home": "StartOfLine",
- "End": "EndOfLine",
- "CtrlHome": "CursorStart",
- "CtrlEnd": "CursorEnd",
- "PageUp": "CursorPageUp",
- "PageDown": "CursorPageDown",
- "CtrlPageUp": "PreviousTab",
- "CtrlPageDown": "NextTab",
- "CtrlG": "ToggleHelp",
- "Alt-g": "ToggleKeyMenu",
- "CtrlR": "ToggleRuler",
- "CtrlL": "command-edit:goto ",
- "Delete": "Delete",
- "CtrlB": "ShellMode",
- "CtrlQ": "Quit",
- "CtrlE": "CommandMode",
- "CtrlW": "NextSplit",
- "CtrlU": "ToggleMacro",
- "CtrlJ": "PlayMacro",
- "Insert": "ToggleOverwriteMode",
-
- // Emacs-style keybindings
- "Alt-f": "WordRight",
- "Alt-b": "WordLeft",
- "Alt-a": "StartOfLine",
- "Alt-e": "EndOfLine",
- // "Alt-p": "CursorUp",
- // "Alt-n": "CursorDown",
-
- // Integration with file managers
- "F2": "Save",
- "F3": "Find",
- "F4": "Quit",
- "F7": "Find",
- "F10": "Quit",
- "Esc": "Escape",
-
- // Mouse bindings
- "MouseWheelUp": "ScrollUp",
- "MouseWheelDown": "ScrollDown",
- "MouseLeft": "MousePress",
- "MouseMiddle": "PastePrimary",
- "Ctrl-MouseLeft": "MouseMultiCursor",
-
- "Alt-n": "SpawnMultiCursor",
- "Alt-m": "SpawnMultiCursorSelect",
- "Alt-p": "RemoveMultiCursor",
- "Alt-c": "RemoveAllMultiCursors",
- "Alt-x": "SkipMultiCursor",
- }
-}