From d33c28eeb8e1864554203a0f6411f3b6ed60f9dd Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Tue, 30 Jun 2020 21:25:54 -0400 Subject: [PATCH] Preliminary support for key sequences This commit adds support for binding key sequences such as "". This commit does not solve the problem of global bindings yet, and therefore the command bar doesn't work properly in this commit. --- internal/action/bindings.go | 74 +++++++++++++++++++++++++------ internal/action/bufpane.go | 88 ++++++++++++++++++++++++++++--------- internal/action/events.go | 14 +++--- internal/action/keytree.go | 72 ++++++++++++++++++------------ internal/action/rawpane.go | 2 +- 5 files changed, 183 insertions(+), 67 deletions(-) diff --git a/internal/action/bindings.go b/internal/action/bindings.go index fd57eb9c..92715f8b 100644 --- a/internal/action/bindings.go +++ b/internal/action/bindings.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "unicode" @@ -53,14 +54,16 @@ func InitBindings() { } func BindKey(k, v string) { - event, ok := findEvent(k) - if !ok { - screen.TermMessage(k, "is not a bindable event") + event, err := findEvent(k) + if err != nil { + screen.TermMessage(err) } switch e := event.(type) { case KeyEvent: BufMapKey(e, v) + case KeySequenceEvent: + BufMapKey(e, v) case MouseEvent: BufMapMouse(e, v) case RawEvent: @@ -70,8 +73,36 @@ func BindKey(k, v string) { config.Bindings[k] = v } -// findEvent will find binding Key 'b' using string 'k' -func findEvent(k string) (b Event, ok bool) { +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 + } + } + + return KeySequenceEvent{events}, true, nil +} + +// 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 @@ -162,6 +193,23 @@ modSearch: 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) { @@ -181,14 +229,14 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) { 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 @@ -231,13 +279,13 @@ func UnbindKey(k string) error { 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 diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 203a5b3a..1e18da0f 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -1,6 +1,7 @@ package action import ( + "log" "strings" "time" @@ -19,14 +20,29 @@ import ( type BufKeyAction func(*BufPane) bool type BufMouseAction func(*BufPane, *tcell.EventMouse) bool +var BufBindings *KeyTree var BufKeyBindings map[Event]BufKeyAction var BufKeyStrings map[Event]string var BufMouseBindings map[MouseEvent]BufMouseAction +func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction { + return func(p Pane) bool { + return a(p.(*BufPane)) + } +} + +func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction { + return func(p Pane, me *tcell.EventMouse) bool { + return a(p.(*BufPane), me) + } +} + func init() { BufKeyBindings = make(map[Event]BufKeyAction) BufKeyStrings = make(map[Event]string) BufMouseBindings = make(map[MouseEvent]BufMouseAction) + + BufBindings = NewKeyTree() } func LuaAction(fn string) func(*BufPane) bool { @@ -54,7 +70,7 @@ func LuaAction(fn string) func(*BufPane) bool { // BufMapKey maps a key event to an action func BufMapKey(k Event, action string) { - BufKeyStrings[k] = action + // BufKeyStrings[k] = action var actionfns []func(*BufPane) bool var names []string var types []byte @@ -109,7 +125,7 @@ func BufMapKey(k Event, action string) { } actionfns = append(actionfns, afn) } - BufKeyBindings[k] = func(h *BufPane) bool { + bufAction := func(h *BufPane) bool { cursors := h.Buf.GetCursors() success := true for i, a := range actionfns { @@ -132,27 +148,33 @@ func BufMapKey(k Event, action string) { } return true } + + BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction)) } // BufMapMouse maps a mouse event to an action func BufMapMouse(k MouseEvent, action string) { if f, ok := BufMouseActions[action]; ok { - BufMouseBindings[k] = f + BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f)) + // BufMouseBindings[k] = f } else { - delete(BufMouseBindings, k) + // TODO + // delete(BufMouseBindings, k) + // BufMapKey(k, action) BufMapKey(k, action) } } // BufUnmap unmaps a key or mouse event from any action func BufUnmap(k Event) { - delete(BufKeyBindings, k) - delete(BufKeyStrings, k) - - switch e := k.(type) { - case MouseEvent: - delete(BufMouseBindings, e) - } + // TODO + // delete(BufKeyBindings, k) + // delete(BufKeyStrings, k) + // + // switch e := k.(type) { + // case MouseEvent: + // delete(BufMouseBindings, e) + // } } // The BufPane connects the buffer and the window @@ -163,9 +185,13 @@ func BufUnmap(k Event) { type BufPane struct { display.BWindow + // Buf is the buffer this BufPane views Buf *buffer.Buffer + // Bindings stores the association of key events and actions + Bindings *KeyTree - Cursor *buffer.Cursor // the active cursor + // Cursor is the currently active buffer cursor + Cursor *buffer.Cursor // Since tcell doesn't differentiate between a mouse release event // and a mouse move event with no keys pressed, we need to keep @@ -399,9 +425,17 @@ func (h *BufPane) HandleEvent(event tcell.Event) { // DoKeyEvent executes a key event by finding the action it is bound // to and executing it (possibly multiple times for multiple cursors) func (h *BufPane) DoKeyEvent(e Event) bool { - if action, ok := BufKeyBindings[e]; ok { - return action(h) + action, more := BufBindings.NextEvent(e, nil) + log.Println("Next event", e, more) + if action != nil && !more { + action(h) + BufBindings.ResetEvents() + } else if action == nil && !more { + BufBindings.ResetEvents() } + // if action, ok := BufKeyBindings[e]; ok { + // return action(h) + // } return false } @@ -436,22 +470,36 @@ func (h *BufPane) completeAction(action string) { } func (h *BufPane) HasKeyEvent(e Event) bool { - _, ok := BufKeyBindings[e] - return ok + // TODO + return true + // _, ok := BufKeyBindings[e] + // return ok } // DoMouseEvent executes a mouse event by finding the action it is bound // to and executing it func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { - if action, ok := BufMouseBindings[e]; ok { - if action(h, te) { + log.Println("DOMOUSEEVENT") + action, _ := BufBindings.NextEvent(e, te) + if action != nil { + if action(h) { h.Relocate() } + BufBindings.ResetEvents() return true - } else if h.HasKeyEvent(e) { - return h.DoKeyEvent(e) } + // TODO return false + + // if action, ok := BufMouseBindings[e]; ok { + // if action(h, te) { + // h.Relocate() + // } + // return true + // } else if h.HasKeyEvent(e) { + // return h.DoKeyEvent(e) + // } + // return false } // DoRuneInsert inserts a given rune into the current buffer diff --git a/internal/action/events.go b/internal/action/events.go index 037ba271..3378f5cc 100644 --- a/internal/action/events.go +++ b/internal/action/events.go @@ -10,7 +10,7 @@ import ( ) type Event interface { - String() string + Name() string } // RawEvent is simply an escape code @@ -20,7 +20,7 @@ type RawEvent struct { esc string } -func (r RawEvent) String() string { +func (r RawEvent) Name() string { return r.esc } @@ -36,7 +36,7 @@ type KeyEvent struct { any bool } -func (k KeyEvent) String() string { +func (k KeyEvent) Name() string { if k.any { return "" } @@ -82,10 +82,12 @@ type KeySequenceEvent struct { keys []Event } -func (k KeySequenceEvent) String() string { +func (k KeySequenceEvent) Name() string { buf := bytes.Buffer{} for _, e := range k.keys { - buf.WriteString(e.String()) + buf.WriteByte('<') + buf.WriteString(e.Name()) + buf.WriteByte('>') } return buf.String() } @@ -97,7 +99,7 @@ type MouseEvent struct { mod tcell.ModMask } -func (m MouseEvent) String() string { +func (m MouseEvent) Name() string { mod := "" if m.mod&tcell.ModShift != 0 { mod = "Shift-" diff --git a/internal/action/keytree.go b/internal/action/keytree.go index ffef5a34..8bd0078a 100644 --- a/internal/action/keytree.go +++ b/internal/action/keytree.go @@ -1,9 +1,14 @@ package action -type KeyAction func(Pane) bool -type MouseAction func(Pane, *MouseEvent) bool +import ( + "log" -type KeyAnyAction func(Pane, []KeyEvent) bool + "github.com/zyedidia/tcell" +) + +type PaneKeyAction func(Pane) bool +type PaneMouseAction func(Pane, *tcell.EventMouse) bool +type PaneKeyAnyAction func(Pane, []KeyEvent) bool // A KeyTreeNode stores a single node in the KeyTree (trie). The // children are stored as a map, and any node may store a list of @@ -30,9 +35,9 @@ func NewKeyTreeNode() *KeyTreeNode { // the action to be active. type TreeAction struct { // only one of these can be non-nil - action KeyAction - any KeyAnyAction - mouse MouseAction + action PaneKeyAction + any PaneKeyAnyAction + mouse PaneMouseAction modes []ModeConstraint } @@ -56,13 +61,13 @@ type KeyTreeCursor struct { node *KeyTreeNode wildcards []KeyEvent - mouseInfo *MouseEvent + mouseInfo *tcell.EventMouse } // MakeClosure uses the information stored in a key tree cursor to construct -// a KeyAction from a TreeAction (which may have a KeyAction, MouseAction, +// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction, // or AnyAction) -func (k *KeyTreeCursor) MakeClosure(a TreeAction) KeyAction { +func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction { if a.action != nil { return a.action } else if a.any != nil { @@ -80,7 +85,7 @@ func (k *KeyTreeCursor) MakeClosure(a TreeAction) KeyAction { // NewKeyTree allocates and returns an empty key tree func NewKeyTree() *KeyTree { - root := new(KeyTreeNode) + root := NewKeyTreeNode() tree := new(KeyTree) tree.root = root @@ -101,8 +106,8 @@ type ModeConstraint struct { disabled bool } -// RegisterKeyBinding registers a KeyAction with an Event. -func (k *KeyTree) RegisterKeyBinding(e Event, a KeyAction) { +// RegisterKeyBinding registers a PaneKeyAction with an Event. +func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) { k.registerBinding(e, TreeAction{ action: a, any: nil, @@ -111,9 +116,9 @@ func (k *KeyTree) RegisterKeyBinding(e Event, a KeyAction) { }) } -// RegisterKeyAnyBinding registers a KeyAnyAction with an Event. +// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event. // The event should contain an "any" event. -func (k *KeyTree) RegisterKeyAnyBinding(e Event, a KeyAnyAction) { +func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) { k.registerBinding(e, TreeAction{ action: nil, any: a, @@ -122,9 +127,9 @@ func (k *KeyTree) RegisterKeyAnyBinding(e Event, a KeyAnyAction) { }) } -// RegisterMouseBinding registers a MouseAction with an Event. +// RegisterMouseBinding registers a PaneMouseAction with an Event. // The event should contain a mouse event. -func (k *KeyTree) RegisterMouseBinding(e Event, a MouseAction) { +func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) { k.registerBinding(e, TreeAction{ action: nil, any: nil, @@ -135,19 +140,19 @@ func (k *KeyTree) RegisterMouseBinding(e Event, a MouseAction) { func (k *KeyTree) registerBinding(e Event, a TreeAction) { switch ev := e.(type) { - case *KeyEvent, *MouseEvent: - n, ok := k.root.children[e] + case KeyEvent, MouseEvent: + newNode, ok := k.root.children[e] if !ok { - newNode := NewKeyTreeNode() + newNode = NewKeyTreeNode() k.root.children[e] = newNode } - n.actions = append(n.actions, a) - case *KeySequenceEvent: + newNode.actions = append(newNode.actions, a) + case KeySequenceEvent: n := k.root for _, key := range ev.keys { newNode, ok := n.children[key] if !ok { - newNode := NewKeyTreeNode() + newNode = NewKeyTreeNode() n.children[key] = newNode } @@ -158,8 +163,8 @@ func (k *KeyTree) registerBinding(e Event, a TreeAction) { } // NextEvent returns the action for the current sequence where e is the next -// event. Even if the action was registered as a KeyAnyAction or MouseAction, -// it will be returned as a KeyAction closure where the appropriate arguments +// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction, +// it will be returned as a PaneKeyAction closure where the appropriate arguments // have been provided. // If no action is associated with the given Event, or mode constraints are not // met for that action, nil is returned. @@ -168,15 +173,28 @@ func (k *KeyTree) registerBinding(e Event, a TreeAction) { // bindings associated with further sequences starting with this event. The // calling function can decide what to do about the conflict (e.g. use a // timeout). -func (k *KeyTree) NextEvent(e Event) (KeyAction, bool) { +func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) { n := k.cursor.node c, ok := n.children[e] + log.Println("NEXT EVENT", e, len(n.children), ok) + if !ok { return nil, false } more := len(c.children) > 0 + k.cursor.node = c + + switch ev := e.(type) { + case KeyEvent: + if ev.any { + k.cursor.wildcards = append(k.cursor.wildcards, ev) + } + case MouseEvent: + k.cursor.mouseInfo = mouse + } + if len(c.actions) > 0 { // check if actions are active for _, a := range c.actions { @@ -199,8 +217,8 @@ func (k *KeyTree) NextEvent(e Event) (KeyAction, bool) { return nil, more } -// Reset sets the current sequence back to the initial value. -func (k *KeyTree) Reset() { +// ResetEvents sets the current sequence back to the initial value. +func (k *KeyTree) ResetEvents() { k.cursor.node = k.root k.cursor.wildcards = []KeyEvent{} k.cursor.mouseInfo = nil diff --git a/internal/action/rawpane.go b/internal/action/rawpane.go index 7541c288..bc8ade2a 100644 --- a/internal/action/rawpane.go +++ b/internal/action/rawpane.go @@ -38,7 +38,7 @@ func (h *RawPane) HandleEvent(event tcell.Event) { e, err := ConstructEvent(event) if err == nil { - h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.String())) + h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name())) } h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))