-
-
Notifications
You must be signed in to change notification settings - Fork 233
Expand file tree
/
Copy pathkeymap.go
More file actions
204 lines (176 loc) · 4.7 KB
/
keymap.go
File metadata and controls
204 lines (176 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package peco
import (
"context"
"errors"
"fmt"
"maps"
"sort"
"strings"
"time"
"github.com/lestrrat-go/pdebug"
"github.com/peco/peco/internal/keyseq"
)
// Keyseq is the interface for key sequence matching engines.
type Keyseq interface {
Add(keyseq.KeyList, any)
AcceptKey(keyseq.Key) (any, error)
CancelChain()
Clear()
Compile() error
InMiddleOfChain() bool
}
// Keymap holds all the key sequence to action map
type Keymap struct {
Config map[string]string
Action map[string][]string // custom actions
seq Keyseq
}
// NewKeymap creates a new Keymap struct
func NewKeymap(config map[string]string, actions map[string][]string) Keymap {
return Keymap{
Config: config,
Action: actions,
seq: keyseq.New(),
}
}
func (km Keymap) Sequence() Keyseq {
return km.seq
}
// ExecuteAction looks up and executes the action(s) bound to the given key event.
func (km Keymap) ExecuteAction(ctx context.Context, state *Peco, ev Event) (err error) {
if pdebug.Enabled {
g := pdebug.Marker("Keymap.ExecuteAction %v", ev).BindError(&err)
defer g.End()
}
a := km.LookupAction(ev)
if a == nil {
return errors.New("action not found")
}
a.Execute(ctx, state, ev)
return nil
}
// LookupAction returns the appropriate action for the given event
func (km Keymap) LookupAction(ev Event) Action {
key := keyseq.Key{
Modifier: ev.Mod,
Key: ev.Key,
Ch: ev.Ch,
}
action, err := km.seq.AcceptKey(key)
switch err {
case nil:
// Found an action!
if pdebug.Enabled {
pdebug.Printf("Keymap.Handler: Fetched action")
}
a, ok := action.(Action)
if !ok {
return ActionFunc(doNothing)
}
return wrapClearSequence(a)
case keyseq.ErrInSequence:
if pdebug.Enabled {
pdebug.Printf("Keymap.Handler: Waiting for more commands...")
}
return wrapRememberSequence(ActionFunc(doNothing))
default:
if pdebug.Enabled {
pdebug.Printf("Keymap.Handler: Defaulting to doAcceptChar")
}
return wrapClearSequence(ActionFunc(doAcceptChar))
}
}
// wrapRememberSequence wraps an action to record the key press as part of a multi-key sequence.
func wrapRememberSequence(a Action) Action {
return ActionFunc(func(ctx context.Context, state *Peco, ev Event) {
if s, err := keyseq.KeyEventToString(ev.Key, ev.Ch, ev.Mod); err == nil {
seq := state.Inputseq()
seq.Add(s)
state.Hub().SendStatusMsg(ctx, strings.Join(seq.KeyNames(), " "), 0)
}
a.Execute(ctx, state, ev)
})
}
// wrapClearSequence wraps an action to clear the accumulated key sequence after execution.
func wrapClearSequence(a Action) Action {
return ActionFunc(func(ctx context.Context, state *Peco, ev Event) {
seq := state.Inputseq()
if s, err := keyseq.KeyEventToString(ev.Key, ev.Ch, ev.Mod); err == nil {
seq.Add(s)
}
if seq.Len() > 0 {
msg := strings.Join(seq.KeyNames(), " ")
state.Hub().SendStatusMsg(ctx, msg, 500*time.Millisecond)
seq.Reset()
}
a.Execute(ctx, state, ev)
})
}
const maxResolveActionDepth = 100
// resolveActionName maps a string action name from config to the corresponding action function.
func (km Keymap) resolveActionName(name string, depth int) (Action, error) {
if depth >= maxResolveActionDepth {
return nil, fmt.Errorf("could not resolve %s: deep recursion", name)
}
// Can it be resolved via regular nameToActions ?
v, ok := nameToActions[name]
if ok {
return v, nil
}
// Can it be resolved via combined actions?
l, ok := km.Action[name]
if ok {
actions := []Action{}
for _, actionName := range l {
child, err := km.resolveActionName(actionName, depth+1)
if err != nil {
return nil, err
}
actions = append(actions, child)
}
v = makeCombinedAction(actions...)
return v, nil
}
return nil, fmt.Errorf("could not resolve %s: no such action", name)
}
// ApplyKeybinding applies all of the custom key bindings on top of
// the default key bindings
func (km *Keymap) ApplyKeybinding() error {
k := km.seq
k.Clear()
// Copy the map
kb := map[string]Action{}
maps.Copy(kb, defaultKeyBinding)
// munge the map using config
for s, as := range km.Config {
if as == "-" {
delete(kb, s)
continue
}
v, err := km.resolveActionName(as, 0)
if err != nil {
return fmt.Errorf("failed to resolve action name %s: %w", as, err)
}
kb[s] = v
}
// now compile using kb
// there's no need to do this, but we sort keys here just to make
// debugging easier
keys := make([]string, 0, len(kb))
for s := range kb {
keys = append(keys, s)
}
sort.Strings(keys)
for _, s := range keys {
a := kb[s]
list, err := keyseq.ToKeyList(s)
if err != nil {
return fmt.Errorf("unknown key %s: %w", s, err)
}
k.Add(list, a)
}
if err := k.Compile(); err != nil {
return fmt.Errorf("failed to compile key binding patterns: %w", err)
}
return nil
}