Preliminary support for key sequences

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.
This commit is contained in:
Zachary Yedidia
2020-06-30 21:25:54 -04:00
parent 5ff8b3791d
commit d33c28eeb8
5 changed files with 183 additions and 67 deletions

View File

@@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"unicode" "unicode"
@@ -53,14 +54,16 @@ func InitBindings() {
} }
func BindKey(k, v string) { func BindKey(k, v string) {
event, ok := findEvent(k) event, err := findEvent(k)
if !ok { if err != nil {
screen.TermMessage(k, "is not a bindable event") screen.TermMessage(err)
} }
switch e := event.(type) { switch e := event.(type) {
case KeyEvent: case KeyEvent:
BufMapKey(e, v) BufMapKey(e, v)
case KeySequenceEvent:
BufMapKey(e, v)
case MouseEvent: case MouseEvent:
BufMapMouse(e, v) BufMapMouse(e, v)
case RawEvent: case RawEvent:
@@ -70,8 +73,36 @@ func BindKey(k, v string) {
config.Bindings[k] = v config.Bindings[k] = v
} }
// findEvent will find binding Key 'b' using string 'k' var r = regexp.MustCompile("<(.+?)>")
func findEvent(k string) (b Event, ok bool) {
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 modifiers := tcell.ModNone
// First, we'll strip off all the modifiers in the name and add them to the // First, we'll strip off all the modifiers in the name and add them to the
@@ -162,6 +193,23 @@ modSearch:
return KeyEvent{}, false 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 // TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
// Returns true if the keybinding already existed and a possible error // Returns true if the keybinding already existed and a possible error
func TryBindKey(k, v string, overwrite bool) (bool, 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()) return false, errors.New("Error reading bindings.json: " + err.Error())
} }
key, ok := findEvent(k) key, err := findEvent(k)
if !ok { if err != nil {
return false, errors.New("Invalid event " + k) return false, err
} }
found := false found := false
for ev := range parsed { for ev := range parsed {
if e, ok := findEvent(ev); ok { if e, err := findEvent(ev); err == nil {
if e == key { if e == key {
if overwrite { if overwrite {
parsed[ev] = v parsed[ev] = v
@@ -231,13 +279,13 @@ func UnbindKey(k string) error {
return errors.New("Error reading bindings.json: " + err.Error()) return errors.New("Error reading bindings.json: " + err.Error())
} }
key, ok := findEvent(k) key, err := findEvent(k)
if !ok { if err != nil {
return errors.New("Invalid event " + k) return err
} }
for ev := range parsed { for ev := range parsed {
if e, ok := findEvent(ev); ok { if e, err := findEvent(ev); err == nil {
if e == key { if e == key {
delete(parsed, ev) delete(parsed, ev)
break break

View File

@@ -1,6 +1,7 @@
package action package action
import ( import (
"log"
"strings" "strings"
"time" "time"
@@ -19,14 +20,29 @@ import (
type BufKeyAction func(*BufPane) bool type BufKeyAction func(*BufPane) bool
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
var BufBindings *KeyTree
var BufKeyBindings map[Event]BufKeyAction var BufKeyBindings map[Event]BufKeyAction
var BufKeyStrings map[Event]string var BufKeyStrings map[Event]string
var BufMouseBindings map[MouseEvent]BufMouseAction 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() { func init() {
BufKeyBindings = make(map[Event]BufKeyAction) BufKeyBindings = make(map[Event]BufKeyAction)
BufKeyStrings = make(map[Event]string) BufKeyStrings = make(map[Event]string)
BufMouseBindings = make(map[MouseEvent]BufMouseAction) BufMouseBindings = make(map[MouseEvent]BufMouseAction)
BufBindings = NewKeyTree()
} }
func LuaAction(fn string) func(*BufPane) bool { 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 // BufMapKey maps a key event to an action
func BufMapKey(k Event, action string) { func BufMapKey(k Event, action string) {
BufKeyStrings[k] = action // BufKeyStrings[k] = action
var actionfns []func(*BufPane) bool var actionfns []func(*BufPane) bool
var names []string var names []string
var types []byte var types []byte
@@ -109,7 +125,7 @@ func BufMapKey(k Event, action string) {
} }
actionfns = append(actionfns, afn) actionfns = append(actionfns, afn)
} }
BufKeyBindings[k] = func(h *BufPane) bool { bufAction := func(h *BufPane) bool {
cursors := h.Buf.GetCursors() cursors := h.Buf.GetCursors()
success := true success := true
for i, a := range actionfns { for i, a := range actionfns {
@@ -132,27 +148,33 @@ func BufMapKey(k Event, action string) {
} }
return true return true
} }
BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
} }
// BufMapMouse maps a mouse event to an action // BufMapMouse maps a mouse event to an action
func BufMapMouse(k MouseEvent, action string) { func BufMapMouse(k MouseEvent, action string) {
if f, ok := BufMouseActions[action]; ok { if f, ok := BufMouseActions[action]; ok {
BufMouseBindings[k] = f BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
// BufMouseBindings[k] = f
} else { } else {
delete(BufMouseBindings, k) // TODO
// delete(BufMouseBindings, k)
// BufMapKey(k, action)
BufMapKey(k, action) BufMapKey(k, action)
} }
} }
// BufUnmap unmaps a key or mouse event from any action // BufUnmap unmaps a key or mouse event from any action
func BufUnmap(k Event) { func BufUnmap(k Event) {
delete(BufKeyBindings, k) // TODO
delete(BufKeyStrings, k) // delete(BufKeyBindings, k)
// delete(BufKeyStrings, k)
switch e := k.(type) { //
case MouseEvent: // switch e := k.(type) {
delete(BufMouseBindings, e) // case MouseEvent:
} // delete(BufMouseBindings, e)
// }
} }
// The BufPane connects the buffer and the window // The BufPane connects the buffer and the window
@@ -163,9 +185,13 @@ func BufUnmap(k Event) {
type BufPane struct { type BufPane struct {
display.BWindow display.BWindow
// Buf is the buffer this BufPane views
Buf *buffer.Buffer 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 // Since tcell doesn't differentiate between a mouse release event
// and a mouse move event with no keys pressed, we need to keep // 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 // DoKeyEvent executes a key event by finding the action it is bound
// to and executing it (possibly multiple times for multiple cursors) // to and executing it (possibly multiple times for multiple cursors)
func (h *BufPane) DoKeyEvent(e Event) bool { func (h *BufPane) DoKeyEvent(e Event) bool {
if action, ok := BufKeyBindings[e]; ok { action, more := BufBindings.NextEvent(e, nil)
return action(h) 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 return false
} }
@@ -436,22 +470,36 @@ func (h *BufPane) completeAction(action string) {
} }
func (h *BufPane) HasKeyEvent(e Event) bool { func (h *BufPane) HasKeyEvent(e Event) bool {
_, ok := BufKeyBindings[e] // TODO
return ok return true
// _, ok := BufKeyBindings[e]
// return ok
} }
// DoMouseEvent executes a mouse event by finding the action it is bound // DoMouseEvent executes a mouse event by finding the action it is bound
// to and executing it // to and executing it
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
if action, ok := BufMouseBindings[e]; ok { log.Println("DOMOUSEEVENT")
if action(h, te) { action, _ := BufBindings.NextEvent(e, te)
if action != nil {
if action(h) {
h.Relocate() h.Relocate()
} }
BufBindings.ResetEvents()
return true return true
} else if h.HasKeyEvent(e) {
return h.DoKeyEvent(e)
} }
// TODO
return false 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 // DoRuneInsert inserts a given rune into the current buffer

View File

@@ -10,7 +10,7 @@ import (
) )
type Event interface { type Event interface {
String() string Name() string
} }
// RawEvent is simply an escape code // RawEvent is simply an escape code
@@ -20,7 +20,7 @@ type RawEvent struct {
esc string esc string
} }
func (r RawEvent) String() string { func (r RawEvent) Name() string {
return r.esc return r.esc
} }
@@ -36,7 +36,7 @@ type KeyEvent struct {
any bool any bool
} }
func (k KeyEvent) String() string { func (k KeyEvent) Name() string {
if k.any { if k.any {
return "<any>" return "<any>"
} }
@@ -82,10 +82,12 @@ type KeySequenceEvent struct {
keys []Event keys []Event
} }
func (k KeySequenceEvent) String() string { func (k KeySequenceEvent) Name() string {
buf := bytes.Buffer{} buf := bytes.Buffer{}
for _, e := range k.keys { for _, e := range k.keys {
buf.WriteString(e.String()) buf.WriteByte('<')
buf.WriteString(e.Name())
buf.WriteByte('>')
} }
return buf.String() return buf.String()
} }
@@ -97,7 +99,7 @@ type MouseEvent struct {
mod tcell.ModMask mod tcell.ModMask
} }
func (m MouseEvent) String() string { func (m MouseEvent) Name() string {
mod := "" mod := ""
if m.mod&tcell.ModShift != 0 { if m.mod&tcell.ModShift != 0 {
mod = "Shift-" mod = "Shift-"

View File

@@ -1,9 +1,14 @@
package action package action
type KeyAction func(Pane) bool import (
type MouseAction func(Pane, *MouseEvent) bool "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 // 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 // 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. // the action to be active.
type TreeAction struct { type TreeAction struct {
// only one of these can be non-nil // only one of these can be non-nil
action KeyAction action PaneKeyAction
any KeyAnyAction any PaneKeyAnyAction
mouse MouseAction mouse PaneMouseAction
modes []ModeConstraint modes []ModeConstraint
} }
@@ -56,13 +61,13 @@ type KeyTreeCursor struct {
node *KeyTreeNode node *KeyTreeNode
wildcards []KeyEvent wildcards []KeyEvent
mouseInfo *MouseEvent mouseInfo *tcell.EventMouse
} }
// MakeClosure uses the information stored in a key tree cursor to construct // 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) // or AnyAction)
func (k *KeyTreeCursor) MakeClosure(a TreeAction) KeyAction { func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
if a.action != nil { if a.action != nil {
return a.action return a.action
} else if a.any != nil { } else if a.any != nil {
@@ -80,7 +85,7 @@ func (k *KeyTreeCursor) MakeClosure(a TreeAction) KeyAction {
// NewKeyTree allocates and returns an empty key tree // NewKeyTree allocates and returns an empty key tree
func NewKeyTree() *KeyTree { func NewKeyTree() *KeyTree {
root := new(KeyTreeNode) root := NewKeyTreeNode()
tree := new(KeyTree) tree := new(KeyTree)
tree.root = root tree.root = root
@@ -101,8 +106,8 @@ type ModeConstraint struct {
disabled bool disabled bool
} }
// RegisterKeyBinding registers a KeyAction with an Event. // RegisterKeyBinding registers a PaneKeyAction with an Event.
func (k *KeyTree) RegisterKeyBinding(e Event, a KeyAction) { func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
k.registerBinding(e, TreeAction{ k.registerBinding(e, TreeAction{
action: a, action: a,
any: nil, 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. // 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{ k.registerBinding(e, TreeAction{
action: nil, action: nil,
any: a, 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. // 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{ k.registerBinding(e, TreeAction{
action: nil, action: nil,
any: nil, any: nil,
@@ -135,19 +140,19 @@ func (k *KeyTree) RegisterMouseBinding(e Event, a MouseAction) {
func (k *KeyTree) registerBinding(e Event, a TreeAction) { func (k *KeyTree) registerBinding(e Event, a TreeAction) {
switch ev := e.(type) { switch ev := e.(type) {
case *KeyEvent, *MouseEvent: case KeyEvent, MouseEvent:
n, ok := k.root.children[e] newNode, ok := k.root.children[e]
if !ok { if !ok {
newNode := NewKeyTreeNode() newNode = NewKeyTreeNode()
k.root.children[e] = newNode k.root.children[e] = newNode
} }
n.actions = append(n.actions, a) newNode.actions = append(newNode.actions, a)
case *KeySequenceEvent: case KeySequenceEvent:
n := k.root n := k.root
for _, key := range ev.keys { for _, key := range ev.keys {
newNode, ok := n.children[key] newNode, ok := n.children[key]
if !ok { if !ok {
newNode := NewKeyTreeNode() newNode = NewKeyTreeNode()
n.children[key] = newNode 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 // 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, // event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,
// it will be returned as a KeyAction closure where the appropriate arguments // it will be returned as a PaneKeyAction closure where the appropriate arguments
// have been provided. // have been provided.
// If no action is associated with the given Event, or mode constraints are not // If no action is associated with the given Event, or mode constraints are not
// met for that action, nil is returned. // 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 // bindings associated with further sequences starting with this event. The
// calling function can decide what to do about the conflict (e.g. use a // calling function can decide what to do about the conflict (e.g. use a
// timeout). // timeout).
func (k *KeyTree) NextEvent(e Event) (KeyAction, bool) { func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {
n := k.cursor.node n := k.cursor.node
c, ok := n.children[e] c, ok := n.children[e]
log.Println("NEXT EVENT", e, len(n.children), ok)
if !ok { if !ok {
return nil, false return nil, false
} }
more := len(c.children) > 0 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 { if len(c.actions) > 0 {
// check if actions are active // check if actions are active
for _, a := range c.actions { for _, a := range c.actions {
@@ -199,8 +217,8 @@ func (k *KeyTree) NextEvent(e Event) (KeyAction, bool) {
return nil, more return nil, more
} }
// Reset sets the current sequence back to the initial value. // ResetEvents sets the current sequence back to the initial value.
func (k *KeyTree) Reset() { func (k *KeyTree) ResetEvents() {
k.cursor.node = k.root k.cursor.node = k.root
k.cursor.wildcards = []KeyEvent{} k.cursor.wildcards = []KeyEvent{}
k.cursor.mouseInfo = nil k.cursor.mouseInfo = nil

View File

@@ -38,7 +38,7 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
e, err := ConstructEvent(event) e, err := ConstructEvent(event)
if err == nil { 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())) h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))