]> git.lizzy.rs Git - micro.git/commitdiff
Basic implementation of KeyTree
authorZachary Yedidia <zyedidia@gmail.com>
Mon, 29 Jun 2020 21:13:22 +0000 (17:13 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Sun, 9 Aug 2020 20:42:03 +0000 (16:42 -0400)
internal/action/events.go
internal/action/keytree.go

index 7daeca52ce5d6dcea8fe93d76aee13900d4d051f..037ba2711545e9c93a2530af35c3cafa32ea336c 100644 (file)
@@ -76,9 +76,10 @@ func (k KeyEvent) String() string {
 }
 
 // A KeySequence defines a list of consecutive
-// key events
+// events. All events in the sequence must be KeyEvents
+// or MouseEvents.
 type KeySequenceEvent struct {
-       keys []KeyEvent
+       keys []Event
 }
 
 func (k KeySequenceEvent) String() string {
index 4b7fec17a277f6e22444bfe8b963f3b14679e43f..ffef5a348d7c7112d5df9ffc62a8b76720a9a093 100644 (file)
@@ -2,11 +2,228 @@ package action
 
 type KeyAction func(Pane) bool
 type MouseAction func(Pane, *MouseEvent) bool
-type KeyAnyAction func(Pane, keys []KeyEvent)
 
+type KeyAnyAction 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
+// actions (the list will be nil if no actions correspond to a certain
+// node)
 type KeyTreeNode struct {
-       children map[Event]KeyTreeNode
+       children map[Event]*KeyTreeNode
+
+       // Only one of these actions may be active in the current
+       // mode, and only one will be returned. If multiple actions
+       // are active, it is undefined which one will be the one
+       // returned.
+       actions []TreeAction
+}
+
+func NewKeyTreeNode() *KeyTreeNode {
+       n := new(KeyTreeNode)
+       n.children = make(map[Event]*KeyTreeNode)
+       n.actions = []TreeAction{}
+       return n
+}
+
+// A TreeAction stores an action, and a set of mode constraints for
+// the action to be active.
+type TreeAction struct {
+       // only one of these can be non-nil
+       action KeyAction
+       any    KeyAnyAction
+       mouse  MouseAction
+
+       modes []ModeConstraint
+}
+
+// A KeyTree is a data structure for storing keybindings. It maps
+// key events to actions, and maintains a set of currently enabled
+// modes, which affects the action that is returned for a key event.
+// The tree acts like a Trie for Events to handle sequence events.
+type KeyTree struct {
+       root  *KeyTreeNode
+       modes map[string]bool
+
+       cursor KeyTreeCursor
+}
+
+// A KeyTreeCursor keeps track of the current location within the
+// tree, and stores any information from previous events that may
+// be needed to execute the action (values of wildcard events or
+// mouse events)
+type KeyTreeCursor struct {
+       node *KeyTreeNode
+
+       wildcards []KeyEvent
+       mouseInfo *MouseEvent
+}
+
+// MakeClosure uses the information stored in a key tree cursor to construct
+// a KeyAction from a TreeAction (which may have a KeyAction, MouseAction,
+// or AnyAction)
+func (k *KeyTreeCursor) MakeClosure(a TreeAction) KeyAction {
+       if a.action != nil {
+               return a.action
+       } else if a.any != nil {
+               return func(p Pane) bool {
+                       return a.any(p, k.wildcards)
+               }
+       } else if a.mouse != nil {
+               return func(p Pane) bool {
+                       return a.mouse(p, k.mouseInfo)
+               }
+       }
+
+       return nil
+}
+
+// NewKeyTree allocates and returns an empty key tree
+func NewKeyTree() *KeyTree {
+       root := new(KeyTreeNode)
+       tree := new(KeyTree)
+
+       tree.root = root
+       tree.modes = make(map[string]bool)
+       tree.cursor = KeyTreeCursor{
+               node:      root,
+               wildcards: []KeyEvent{},
+               mouseInfo: nil,
+       }
+
+       return tree
+}
+
+// A ModeConstraint specifies that an action can only be executed
+// while a certain mode is enabled or disabled.
+type ModeConstraint struct {
+       mode     string
+       disabled bool
+}
+
+// RegisterKeyBinding registers a KeyAction with an Event.
+func (k *KeyTree) RegisterKeyBinding(e Event, a KeyAction) {
+       k.registerBinding(e, TreeAction{
+               action: a,
+               any:    nil,
+               mouse:  nil,
+               modes:  nil,
+       })
+}
+
+// RegisterKeyAnyBinding registers a KeyAnyAction with an Event.
+// The event should contain an "any" event.
+func (k *KeyTree) RegisterKeyAnyBinding(e Event, a KeyAnyAction) {
+       k.registerBinding(e, TreeAction{
+               action: nil,
+               any:    a,
+               mouse:  nil,
+               modes:  nil,
+       })
+}
+
+// RegisterMouseBinding registers a MouseAction with an Event.
+// The event should contain a mouse event.
+func (k *KeyTree) RegisterMouseBinding(e Event, a MouseAction) {
+       k.registerBinding(e, TreeAction{
+               action: nil,
+               any:    nil,
+               mouse:  a,
+               modes:  nil,
+       })
+}
+
+func (k *KeyTree) registerBinding(e Event, a TreeAction) {
+       switch ev := e.(type) {
+       case *KeyEvent, *MouseEvent:
+               n, ok := k.root.children[e]
+               if !ok {
+                       newNode := NewKeyTreeNode()
+                       k.root.children[e] = newNode
+               }
+               n.actions = append(n.actions, a)
+       case *KeySequenceEvent:
+               n := k.root
+               for _, key := range ev.keys {
+                       newNode, ok := n.children[key]
+                       if !ok {
+                               newNode := NewKeyTreeNode()
+                               n.children[key] = newNode
+                       }
+
+                       n = newNode
+               }
+               n.actions = append(n.actions, a)
+       }
+}
+
+// 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
+// have been provided.
+// If no action is associated with the given Event, or mode constraints are not
+// met for that action, nil is returned.
+// A boolean is returned to indicate if there is a conflict with this action. A
+// conflict occurs when there is an active action for this event but there are
+// 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) {
+       n := k.cursor.node
+       c, ok := n.children[e]
+       if !ok {
+               return nil, false
+       }
+
+       more := len(c.children) > 0
+
+       if len(c.actions) > 0 {
+               // check if actions are active
+               for _, a := range c.actions {
+                       active := true
+                       for _, mc := range a.modes {
+                               // if any mode constraint is not met, the action is not active
+                               hasMode := k.modes[mc.mode]
+                               if hasMode != mc.disabled {
+                                       active = false
+                               }
+                       }
+
+                       if active {
+                               // the first active action to be found is returned
+                               return k.cursor.MakeClosure(a), more
+                       }
+               }
+       }
+
+       return nil, more
+}
+
+// Reset sets the current sequence back to the initial value.
+func (k *KeyTree) Reset() {
+       k.cursor.node = k.root
+       k.cursor.wildcards = []KeyEvent{}
+       k.cursor.mouseInfo = nil
+}
+
+// DeleteBinding removes any currently active actions associated with the
+// given event.
+func (k *KeyTree) DeleteBinding(e Event) {
+
+}
+
+// DeleteAllBindings removes all actions associated with the given event,
+// regardless of whether they are active or not.
+func (k *KeyTree) DeleteAllBindings(e Event) {
+
+}
+
+// SetMode enables or disabled a given mode
+func (k *KeyTree) SetMode(mode string, en bool) {
+       k.modes[mode] = en
+}
 
-       // action KeyAction
-       // any KeyAnyAction
+// HasMode returns if the given mode is currently active
+func (k *KeyTree) HasMode(mode string) bool {
+       return k.modes[mode]
 }