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"
"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

View File

@@ -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

View File

@@ -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 "<any>"
}
@@ -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-"

View File

@@ -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

View File

@@ -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()))