-
Notifications
You must be signed in to change notification settings - Fork 1
/
bindings.go
157 lines (145 loc) · 4.9 KB
/
bindings.go
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
package catwalk
import (
"fmt"
"reflect"
"strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
)
// KeyMapUpdater defines an updater which supports the "keybind" and
// "keyhelp" commands to change a KeyMap struct. You can add this to
// a test using WithUpdater(). It is possible to add multiple keymap
// updaters to the same test.
//
// A KeyMap struct is any go struct containing exported fields of type
// key.Binding. For example, using:
//
// KeyMapUpdater("mymodel",
// func(m tea.Model, changeKeyMap func(interface{})) (tea.Model, err) {
// myModel := m.(mymodel)
// if err := changeKeyMap(&myModel.KeyMap); err != nil {
// return m, err
// }
// return myModel, nil
// })
//
// and mymodel.KeyMap containing a CursorUp binding,
// it becomes possible to use "keybind mymodel.CursorUp ctrl+c" to
// define a new keybinding during a test.
//
// If your model implements tea.Model by reference (i.e. its address
// does not change through Update calls), you can simplify
// the call as follows:
//
// KeyMapUpdater("...", SimpleKeyMapApplier(&yourmodel.KeyMap)).
func KeyMapUpdater(prefix string, apply KeyMapApplier) Updater {
return func(m tea.Model, inputCmd string, args ...string) (bool, tea.Model, tea.Cmd, error) {
return handleKeyMapUpdate(prefix+".", apply, m, inputCmd, args...)
}
}
// KeyMapApplier is the type of a function which applies the
// changeKeyMap callback on a KeyMap struct inside the model, then
// returns the resulting model.
//
// Example implementation:
// func(m tea.Model, changeKeyMap func(interface{}) error) (tea.Model, err) {
// myModel := m.(mymodel)
// if err := changeKeyMap(&myModel.KeyMap); err != nil {
// return m, err
// }
// return myModel, nil
// }
type KeyMapApplier func(m tea.Model, changeKeyMap func(interface{}) error) (tea.Model, error)
// SimpleKeyMapApplier is a helper to simplify the definition of the
// function argument to KeyMapUpdater, in the case the model is
// implemented by reference -- i.e. the address of the KeyMap does not
// change from one call to Update to the next.
func SimpleKeyMapApplier(keymap interface{}) KeyMapApplier {
return func(m tea.Model, changeKeyMap func(interface{}) error) (tea.Model, error) {
return m, changeKeyMap(keymap)
}
}
func handleKeyMapUpdate(
prefix string, apply KeyMapApplier, m tea.Model, inputcmd string, args ...string,
) (bool, tea.Model, tea.Cmd, error) {
switch inputcmd {
case "keybind":
if len(args) < 2 {
return false, m, nil, fmt.Errorf("syntax: keybind <bindingname> <newkey...>")
}
if !strings.HasPrefix(args[0], prefix) {
// This keybind is meant for another updater. Not us.
return false, m, nil, nil
}
bindingName := strings.TrimPrefix(args[0], prefix)
newM, err := apply(m, func(km interface{}) error {
return applyKeyRebind(km, bindingName, args[1:]...)
})
return true, newM, nil, err
case "keyhelp":
if len(args) < 3 {
return false, m, nil, fmt.Errorf("syntax: keyhelp <bindingname> <helpkey> <helptext...>")
}
if !strings.HasPrefix(args[0], prefix) {
// This keybind is meant for another updater. Not us.
return false, m, nil, nil
}
bindingName := strings.TrimPrefix(args[0], prefix)
newM, err := apply(m, func(km interface{}) error {
return applyKeyNewHelp(km, bindingName, args[1], strings.Join(args[2:], " "))
})
return true, newM, nil, err
default:
// Command not supported.
return false, m, nil, nil
}
}
func applyKeyRebind(km interface{}, bindingName string, newKeys ...string) error {
kb, err := getBinding(km, bindingName)
if err != nil {
return err
}
if len(newKeys) == 1 {
switch newKeys[0] {
case "enable":
kb.SetEnabled(true)
return nil
case "disable":
kb.SetEnabled(false)
return nil
case "unbind":
kb.Unbind()
return nil
}
}
kb.SetKeys(newKeys...)
return nil
}
func applyKeyNewHelp(km interface{}, bindingName, helpKey, helpText string) error {
kb, err := getBinding(km, bindingName)
if err != nil {
return err
}
kb.SetHelp(helpKey, helpText)
return nil
}
func getBinding(km interface{}, bindingName string) (*key.Binding, error) {
v := reflect.ValueOf(km)
if v.Type().Kind() != reflect.Ptr {
return nil, fmt.Errorf("keymap type %T is not a pointer to struct", km)
}
v = v.Elem()
if v.Type().Kind() != reflect.Struct {
return nil, fmt.Errorf("keymap type %T is not a pointer to struct", km)
}
var zv reflect.Value
fv := v.FieldByName(bindingName)
if fv == zv {
return nil, fmt.Errorf("keymap struct %T does not contain a field named %q", km, bindingName)
}
if fv.Type() != keyBindingType {
return nil, fmt.Errorf("field %q of struct %T does not have type key.Binding", bindingName, km)
}
return fv.Addr().Interface().(*key.Binding), nil
}
var keyBindingType = reflect.TypeOf(key.Binding{})