-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkey.go
More file actions
289 lines (256 loc) · 8.16 KB
/
Copy pathkey.go
File metadata and controls
289 lines (256 loc) · 8.16 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package wui
import "strings"
// Binding is a named keyboard shortcut: one or more key names, a short
// label for help output, and an optional enabled predicate.
//
// Bindings are declarative — they describe intent ("q quits") rather
// than hard-coding a switch in Update, which lets wui derive help text
// automatically and lets both platforms share one definition.
//
// var quit = wui.NewBinding("quit", wui.Keys("q", "ctrl+c"), wui.Help("q", "quit"))
//
// func (m model) Update(msg wui.Msg) (wui.Model, wui.Cmd) {
// switch v := msg.(type) {
// case wui.KeyMsg:
// if quit.Matches(v) {
// return m, wui.Quit()
// }
// }
// return m, nil
// }
type Binding struct {
// Name identifies the binding. It is the ID reported by KeyMap
// lookups and is handy as a Msg payload.
Name string
// keys are normalized key names ("q", "ctrl+c", "up").
keys []string
// helpKey is the key shown in help output ("↑/k"); when empty the
// first entry of keys is shown instead.
helpKey string
// helpDesc is the human description shown in help output.
helpDesc string
// disabled suppresses matching and hides the binding from help.
disabled bool
}
// BindingOption configures a Binding at construction time.
type BindingOption func(*Binding)
// Keys sets the key names a Binding matches. Names use the same
// normalized vocabulary as KeyMsg.Key ("enter", "ctrl+c", "up", "q").
func Keys(keys ...string) BindingOption {
return func(b *Binding) { b.keys = keys }
}
// Help sets the key text and description shown in help output. key may
// be a display form covering several bindings ("↑/k"); pass "" to show
// the binding's first key instead.
func Help(key, desc string) BindingOption {
return func(b *Binding) {
b.helpKey = key
b.helpDesc = desc
}
}
// BindingDisabled constructs the Binding in a disabled state: it
// matches nothing and is hidden from help until SetEnabled(true).
func BindingDisabled() BindingOption {
return func(b *Binding) { b.disabled = true }
}
// NewBinding creates a named Binding.
func NewBinding(name string, opts ...BindingOption) Binding {
b := Binding{Name: name}
for _, opt := range opts {
opt(&b)
}
return b
}
// Matches reports whether msg is one of the binding's keys. A disabled
// binding matches nothing, so a single `if b.Matches(k)` in Update
// respects context-sensitive availability without extra conditionals.
func (b Binding) Matches(msg KeyMsg) bool {
if b.disabled {
return false
}
for _, k := range b.keys {
if k == msg.Key {
return true
}
}
return false
}
// MatchesKey reports whether the binding contains the given key name.
func (b Binding) MatchesKey(key string) bool {
return b.Matches(KeyMsg{Key: key})
}
// Keys returns the key names the binding matches.
func (b Binding) KeyNames() []string {
out := make([]string, len(b.keys))
copy(out, b.keys)
return out
}
// Enabled reports whether the binding is active.
func (b Binding) Enabled() bool { return !b.disabled && len(b.keys) > 0 }
// SetEnabled returns a copy of the binding with its enabled state set.
// Bindings are values, so context-sensitive availability is expressed
// by storing the result — typically rebuilt in View or Update:
//
// km.Undo = km.Undo.SetEnabled(len(m.history) > 0)
func (b Binding) SetEnabled(v bool) Binding {
b.disabled = !v
return b
}
// HelpKey returns the key text for help output: the explicit Help key
// when set, else the binding's first key name.
func (b Binding) HelpKey() string {
if b.helpKey != "" {
return b.helpKey
}
if len(b.keys) > 0 {
return b.keys[0]
}
return ""
}
// HelpDesc returns the description for help output.
func (b Binding) HelpDesc() string { return b.helpDesc }
// KeyMap is an ordered set of Bindings. It gives an app one place to
// declare its shortcuts, one call to dispatch them, and automatic help
// rendering.
//
// km := wui.NewKeyMap(
// wui.NewBinding("inc", wui.Keys("+", "k"), wui.Help("+/k", "increment")),
// wui.NewBinding("quit", wui.Keys("q"), wui.Help("q", "quit")),
// )
type KeyMap struct {
bindings []Binding
}
// NewKeyMap creates a KeyMap from bindings, preserving their order —
// help output lists them as given.
func NewKeyMap(bindings ...Binding) KeyMap {
return KeyMap{bindings: bindings}
}
// Add returns a copy of the KeyMap with additional bindings appended.
func (k KeyMap) Add(bindings ...Binding) KeyMap {
merged := make([]Binding, 0, len(k.bindings)+len(bindings))
merged = append(merged, k.bindings...)
merged = append(merged, bindings...)
return KeyMap{bindings: merged}
}
// Bindings returns the bindings in declaration order.
func (k KeyMap) Bindings() []Binding {
out := make([]Binding, len(k.bindings))
copy(out, k.bindings)
return out
}
// Get returns the binding with the given name.
func (k KeyMap) Get(name string) (Binding, bool) {
for _, b := range k.bindings {
if b.Name == name {
return b, true
}
}
return Binding{}, false
}
// SetEnabled returns a copy of the KeyMap with the named binding's
// enabled state set. Unknown names are ignored.
func (k KeyMap) SetEnabled(name string, enabled bool) KeyMap {
out := make([]Binding, len(k.bindings))
copy(out, k.bindings)
for i := range out {
if out[i].Name == name {
out[i] = out[i].SetEnabled(enabled)
}
}
return KeyMap{bindings: out}
}
// Match returns the name of the first enabled binding matching msg.
// Bindings are tested in declaration order, so earlier entries win when
// two bindings share a key.
func (k KeyMap) Match(msg KeyMsg) (string, bool) {
for _, b := range k.bindings {
if b.Matches(msg) {
return b.Name, true
}
}
return "", false
}
// Handler pairs a Binding with the action it triggers, for apps that
// prefer table-driven dispatch over a switch on binding names.
type Handler struct {
Binding Binding
Do func() Msg
}
// Bind is shorthand for a Handler.
func Bind(b Binding, do func() Msg) Handler {
return Handler{Binding: b, Do: do}
}
// Dispatch runs the first handler whose binding matches msg and returns
// its Msg. handled is false when nothing matched, so the caller can
// fall through to its own key handling:
//
// case wui.KeyMsg:
// if out, ok := wui.Dispatch(v, handlers...); ok {
// return m, func() wui.Msg { return out }
// }
func Dispatch(msg KeyMsg, handlers ...Handler) (Msg, bool) {
for _, h := range handlers {
if h.Binding.Matches(msg) && h.Do != nil {
return h.Do(), true
}
}
return nil, false
}
// KeyMapFrom builds a KeyMap from handlers, preserving order — so one
// handler table can drive both dispatch and help output.
func KeyMapFrom(handlers ...Handler) KeyMap {
bindings := make([]Binding, 0, len(handlers))
for _, h := range handlers {
bindings = append(bindings, h.Binding)
}
return KeyMap{bindings: bindings}
}
// HelpEntry is one row of rendered help: the key text and what it does.
type HelpEntry struct {
Key string
Desc string
}
// HelpEntries returns a help row for every enabled binding that has a
// description. Bindings without Help text are skipped — they are
// implementation details, not user-facing shortcuts.
func (k KeyMap) HelpEntries() []HelpEntry {
var out []HelpEntry
for _, b := range k.bindings {
if !b.Enabled() || b.HelpDesc() == "" {
continue
}
out = append(out, HelpEntry{Key: b.HelpKey(), Desc: b.HelpDesc()})
}
return out
}
// ShortHelp renders the key map as a single line — "+/k increment •
// q quit" — suitable for a status bar.
func (k KeyMap) ShortHelp() string {
entries := k.HelpEntries()
parts := make([]string, 0, len(entries))
for _, e := range entries {
parts = append(parts, e.Key+" "+e.Desc)
}
return strings.Join(parts, " • ")
}
// HelpView renders the key map as an Element: one dim "key desc" pair
// per binding, laid out along dir. Row gives a status-bar strip,
// Column a help panel.
func (k KeyMap) HelpView(dir Direction) Element {
entries := k.HelpEntries()
if len(entries) == 0 {
return Box(dir)
}
items := make([]Element, 0, len(entries))
for _, e := range entries {
items = append(items, Box(Row,
Text(e.Key, WithTextStyle(Style{Bold: true})),
Text(" "+e.Desc, WithTextStyle(Style{Faint: true})),
))
}
gap := 1
if dir == Column {
gap = 0
}
return BoxStyled(dir, gap, Style{}, items...)
}