d33c28eeb8
This commit adds support for binding key sequences such as "<Ctrl-x><Ctrl-c>". This commit does not solve the problem of global bindings yet, and therefore the command bar doesn't work properly in this commit.
248 lines
6.2 KiB
Go
248 lines
6.2 KiB
Go
package action
|
|
|
|
import (
|
|
"log"
|
|
|
|
"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
|
|
// actions (the list will be nil if no actions correspond to a certain
|
|
// node)
|
|
type KeyTreeNode struct {
|
|
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 PaneKeyAction
|
|
any PaneKeyAnyAction
|
|
mouse PaneMouseAction
|
|
|
|
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 *tcell.EventMouse
|
|
}
|
|
|
|
// MakeClosure uses the information stored in a key tree cursor to construct
|
|
// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,
|
|
// or AnyAction)
|
|
func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
|
|
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 := NewKeyTreeNode()
|
|
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 PaneKeyAction with an Event.
|
|
func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
|
|
k.registerBinding(e, TreeAction{
|
|
action: a,
|
|
any: nil,
|
|
mouse: nil,
|
|
modes: nil,
|
|
})
|
|
}
|
|
|
|
// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.
|
|
// The event should contain an "any" event.
|
|
func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {
|
|
k.registerBinding(e, TreeAction{
|
|
action: nil,
|
|
any: a,
|
|
mouse: nil,
|
|
modes: nil,
|
|
})
|
|
}
|
|
|
|
// RegisterMouseBinding registers a PaneMouseAction with an Event.
|
|
// The event should contain a mouse event.
|
|
func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {
|
|
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:
|
|
newNode, ok := k.root.children[e]
|
|
if !ok {
|
|
newNode = NewKeyTreeNode()
|
|
k.root.children[e] = newNode
|
|
}
|
|
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()
|
|
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 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.
|
|
// 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, 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 {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// HasMode returns if the given mode is currently active
|
|
func (k *KeyTree) HasMode(mode string) bool {
|
|
return k.modes[mode]
|
|
}
|