Skip to content

Commit 6a824da

Browse files
committed
port from dive
1 parent cff3994 commit 6a824da

File tree

9 files changed

+374
-1
lines changed

9 files changed

+374
-1
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13+
14+
.idea

.travis.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
language: go
2+
3+
go:
4+
- '1.8'
5+
- '1.9'
6+
- '1.10'
7+
- '1.11'
8+
- 'master'
9+
10+
# Skip the install step. Don't `go get` dependencies. Only build with the
11+
# code in vendor/
12+
install: true
13+
14+
matrix:
15+
# It's ok if our code fails on unstable development versions of Go.
16+
allow_failures:
17+
- go: master
18+
# Don't wait for tip tests to finish. Mark the test run green if the
19+
# tests pass on the stable versions of Go.
20+
fast_finish: true
21+
22+
notifications:
23+
email: false
24+
25+
before_script:
26+
- go get -t ./...
27+
28+
# Note: scripts always run to completion
29+
script:
30+
- make validate
31+
- make test
32+

Makefile

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
all: clean build
2+
3+
build:
4+
go build
5+
6+
test: build
7+
go test -cover -v ./...
8+
9+
validate:
10+
@! gofmt -s -d -l . 2>&1 | grep -vE '^\.git/'
11+
go vet ./...
12+
13+
lint: build
14+
golint -set_exit_status $$(go list ./...)
15+
16+
clean:
17+
rm -rf build
18+
rm -rf vendor
19+
go clean
20+
21+
.PHONY: build install test lint clean release validate
22+

README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
# keybinding
2-
a wrapper for parsing gocui keybindings
2+
3+
A golang wrapper for parsing gocui keybindings.
4+
5+
```go
6+
// get a single keybinding
7+
keybinding.Parse("ctrl+c")
8+
9+
// get a list of keybindings
10+
keybinding.ParseAll("ctrl+A, ctrl+B, CTRL+ALT+C")
11+
```

examples/main.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/wagoodman/keybinding"
6+
)
7+
8+
func show(keyStr string) {
9+
keys, err := keybinding.ParseAll(keyStr)
10+
if err != nil {
11+
fmt.Println("Error parsing", keyStr, ":", err)
12+
} else {
13+
fmt.Println("Key: ", keyStr, "=", keys)
14+
}
15+
}
16+
17+
func main() {
18+
show("ctrl+a")
19+
show("ctrl+b")
20+
show("ctrl+/, tab")
21+
show("ctrl+ alt +/")
22+
}

go.mod

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module github.com/wagoodman/keybinding
2+
3+
require (
4+
github.com/jroimartin/gocui v0.4.0
5+
github.com/mattn/go-runewidth v0.0.3 // indirect
6+
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed // indirect
7+
)

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
2+
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
3+
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
4+
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
5+
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed h1:bAVGG6B+R5qpSylrrA+BAMrzYkdAoiTaKPVxRB+4cyM=
6+
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=

keybinding.go

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package keybinding
2+
3+
import (
4+
"fmt"
5+
"github.com/jroimartin/gocui"
6+
"strings"
7+
"unicode"
8+
)
9+
10+
var translate = map[string]string{
11+
"/": "Slash",
12+
"\\": "Backslash",
13+
"[": "LsqBracket",
14+
"]": "RsqBracket",
15+
"_": "Underscore",
16+
"escape": "Esc",
17+
"~": "Tilde",
18+
"pageup": "Pgup",
19+
"pagedown": "Pgdn",
20+
"pgup": "Pgup",
21+
"pgdown": "Pgdn",
22+
"up": "ArrowUp",
23+
"down": "ArrowDown",
24+
"right": "ArrowRight",
25+
"left": "ArrowLeft",
26+
"ctl": "Ctrl",
27+
}
28+
29+
var display = map[string]string{
30+
"Slash": "/",
31+
"Backslash": "\\",
32+
"LsqBracket": "[",
33+
"RsqBracket": "]",
34+
"Underscore": "_",
35+
"Tilde": "~",
36+
"Ctrl": "^",
37+
}
38+
39+
var supportedKeybindings = map[string]gocui.Key{
40+
"KeyF1": gocui.KeyF1,
41+
"KeyF2": gocui.KeyF2,
42+
"KeyF3": gocui.KeyF3,
43+
"KeyF4": gocui.KeyF4,
44+
"KeyF5": gocui.KeyF5,
45+
"KeyF6": gocui.KeyF6,
46+
"KeyF7": gocui.KeyF7,
47+
"KeyF8": gocui.KeyF8,
48+
"KeyF9": gocui.KeyF9,
49+
"KeyF10": gocui.KeyF10,
50+
"KeyF11": gocui.KeyF11,
51+
"KeyF12": gocui.KeyF12,
52+
"KeyInsert": gocui.KeyInsert,
53+
"KeyDelete": gocui.KeyDelete,
54+
"KeyHome": gocui.KeyHome,
55+
"KeyEnd": gocui.KeyEnd,
56+
"KeyPgup": gocui.KeyPgup,
57+
"KeyPgdn": gocui.KeyPgdn,
58+
"KeyArrowUp": gocui.KeyArrowUp,
59+
"KeyArrowDown": gocui.KeyArrowDown,
60+
"KeyArrowLeft": gocui.KeyArrowLeft,
61+
"KeyArrowRight": gocui.KeyArrowRight,
62+
"KeyCtrlTilde": gocui.KeyCtrlTilde,
63+
"KeyCtrl2": gocui.KeyCtrl2,
64+
"KeyCtrlSpace": gocui.KeyCtrlSpace,
65+
"KeyCtrlA": gocui.KeyCtrlA,
66+
"KeyCtrlB": gocui.KeyCtrlB,
67+
"KeyCtrlC": gocui.KeyCtrlC,
68+
"KeyCtrlD": gocui.KeyCtrlD,
69+
"KeyCtrlE": gocui.KeyCtrlE,
70+
"KeyCtrlF": gocui.KeyCtrlF,
71+
"KeyCtrlG": gocui.KeyCtrlG,
72+
"KeyBackspace": gocui.KeyBackspace,
73+
"KeyCtrlH": gocui.KeyCtrlH,
74+
"KeyTab": gocui.KeyTab,
75+
"KeyCtrlI": gocui.KeyCtrlI,
76+
"KeyCtrlJ": gocui.KeyCtrlJ,
77+
"KeyCtrlK": gocui.KeyCtrlK,
78+
"KeyCtrlL": gocui.KeyCtrlL,
79+
"KeyEnter": gocui.KeyEnter,
80+
"KeyCtrlM": gocui.KeyCtrlM,
81+
"KeyCtrlN": gocui.KeyCtrlN,
82+
"KeyCtrlO": gocui.KeyCtrlO,
83+
"KeyCtrlP": gocui.KeyCtrlP,
84+
"KeyCtrlQ": gocui.KeyCtrlQ,
85+
"KeyCtrlR": gocui.KeyCtrlR,
86+
"KeyCtrlS": gocui.KeyCtrlS,
87+
"KeyCtrlT": gocui.KeyCtrlT,
88+
"KeyCtrlU": gocui.KeyCtrlU,
89+
"KeyCtrlV": gocui.KeyCtrlV,
90+
"KeyCtrlW": gocui.KeyCtrlW,
91+
"KeyCtrlX": gocui.KeyCtrlX,
92+
"KeyCtrlY": gocui.KeyCtrlY,
93+
"KeyCtrlZ": gocui.KeyCtrlZ,
94+
"KeyEsc": gocui.KeyEsc,
95+
"KeyCtrlLsqBracket": gocui.KeyCtrlLsqBracket,
96+
"KeyCtrl3": gocui.KeyCtrl3,
97+
"KeyCtrl4": gocui.KeyCtrl4,
98+
"KeyCtrlBackslash": gocui.KeyCtrlBackslash,
99+
"KeyCtrl5": gocui.KeyCtrl5,
100+
"KeyCtrlRsqBracket": gocui.KeyCtrlRsqBracket,
101+
"KeyCtrl6": gocui.KeyCtrl6,
102+
"KeyCtrl7": gocui.KeyCtrl7,
103+
"KeyCtrlSlash": gocui.KeyCtrlSlash,
104+
"KeyCtrlUnderscore": gocui.KeyCtrlUnderscore,
105+
"KeySpace": gocui.KeySpace,
106+
"KeyBackspace2": gocui.KeyBackspace2,
107+
"KeyCtrl8": gocui.KeyCtrl8,
108+
}
109+
110+
type Key struct {
111+
Value gocui.Key
112+
Modifier gocui.Modifier
113+
Tokens []string
114+
}
115+
116+
func Parse(input string) (Key, error) {
117+
f := func(c rune) bool { return unicode.IsSpace(c) || c == '+' }
118+
tokens := strings.FieldsFunc(input, f)
119+
var normalizedTokens []string
120+
var modifier = gocui.ModNone
121+
122+
for _, token := range tokens {
123+
normalized := strings.ToLower(token)
124+
125+
if value, exists := translate[normalized]; exists {
126+
normalized = value
127+
} else {
128+
normalized = strings.Title(normalized)
129+
}
130+
131+
if normalized == "Alt" {
132+
modifier = gocui.ModAlt
133+
continue
134+
}
135+
136+
if len(normalized) == 1 {
137+
normalizedTokens = append(normalizedTokens, strings.ToUpper(normalized))
138+
continue
139+
}
140+
141+
normalizedTokens = append(normalizedTokens, normalized)
142+
}
143+
144+
lookup := "Key" + strings.Join(normalizedTokens, "")
145+
146+
if key, exists := supportedKeybindings[lookup]; exists {
147+
return Key{key, modifier, normalizedTokens}, nil
148+
}
149+
150+
if modifier != gocui.ModNone {
151+
return Key{0, modifier, normalizedTokens}, fmt.Errorf("unsupported keybinding: %s (+%+v)", lookup, modifier)
152+
}
153+
return Key{0, modifier, normalizedTokens}, fmt.Errorf("unsupported keybinding: %s", lookup)
154+
}
155+
156+
func ParseAll(input string) ([]Key, error) {
157+
ret := make([]Key, 0)
158+
for _, value := range strings.Split(input, ",") {
159+
key, err := Parse(value)
160+
if err != nil {
161+
return nil, fmt.Errorf("could not parse keybinding '%s' from request '%s': %+v", value, input, err)
162+
}
163+
ret = append(ret, key)
164+
}
165+
if len(ret) == 0 {
166+
return nil, fmt.Errorf("must have at least one keybinding")
167+
}
168+
return ret, nil
169+
}
170+
171+
func (key Key) String() string {
172+
displayTokens := make([]string, 0)
173+
prefix := ""
174+
for _, token := range key.Tokens {
175+
if token == "Ctrl" {
176+
prefix = "^"
177+
continue
178+
}
179+
if value, exists := display[token]; exists {
180+
token = value
181+
}
182+
displayTokens = append(displayTokens, token)
183+
}
184+
return prefix + strings.Join(displayTokens, "+")
185+
}

keybinding_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package keybinding
2+
3+
import (
4+
"github.com/jroimartin/gocui"
5+
"testing"
6+
)
7+
8+
func TestParse(t *testing.T) {
9+
var table = []struct {
10+
input string
11+
key gocui.Key
12+
modifier gocui.Modifier
13+
errStr string
14+
}{
15+
{"ctrl + A", gocui.KeyCtrlA, gocui.ModNone, ""},
16+
{"Ctrl + a", gocui.KeyCtrlA, gocui.ModNone, ""},
17+
{"Ctl + a", gocui.KeyCtrlA, gocui.ModNone, ""},
18+
{"ctl + A", gocui.KeyCtrlA, gocui.ModNone, ""},
19+
{"f2", gocui.KeyF2, gocui.ModNone, ""},
20+
{"ctrl + [", gocui.KeyCtrlLsqBracket, gocui.ModNone, ""},
21+
{" ctrl + ] ", gocui.KeyCtrlRsqBracket, gocui.ModNone, ""},
22+
{"ctrl + /", gocui.KeyCtrlSlash, gocui.ModNone, ""},
23+
{"ctrl + \\", gocui.KeyCtrlBackslash, gocui.ModNone, ""},
24+
// {"left", gocui.KeyArrowLeft, gocui.ModNone, ""},
25+
{"PageUp", gocui.KeyPgup, gocui.ModNone, ""},
26+
{"PgUp", gocui.KeyPgup, gocui.ModNone, ""},
27+
{"pageup", gocui.KeyPgup, gocui.ModNone, ""},
28+
{"pgup", gocui.KeyPgup, gocui.ModNone, ""},
29+
{"tab", gocui.KeyTab, gocui.ModNone, ""},
30+
{"escape", gocui.KeyEsc, gocui.ModNone, ""},
31+
{"enter", gocui.KeyEnter, gocui.ModNone, ""},
32+
{"space", gocui.KeySpace, gocui.ModNone, ""},
33+
{"ctrl + alt + z", gocui.KeyCtrlZ, gocui.ModAlt, ""},
34+
{"f22", 0, gocui.ModNone, "unsupported keybinding: KeyF22"},
35+
{"ctrl + alt + !", 0, gocui.ModAlt, "unsupported keybinding: KeyCtrl! (+1)"},
36+
}
37+
38+
for idx, trial := range table {
39+
actualKey, actualErr := Parse(trial.input)
40+
41+
if actualKey.Value != trial.key {
42+
t.Errorf("Expected key '%+v' but got '%+v' (trial %d)", trial.key, actualKey, idx)
43+
}
44+
45+
if actualKey.Modifier != trial.modifier {
46+
t.Errorf("Expected modifier '%+v' but got '%+v' (trial %d)", trial.modifier, actualKey, idx)
47+
}
48+
49+
if actualErr == nil && trial.errStr != "" {
50+
t.Errorf("Expected error message of '%s' but got no message (trial %d)", trial.errStr, idx)
51+
} else if actualErr != nil && actualErr.Error() != trial.errStr {
52+
t.Errorf("Expected error message '%s' but got '%s' (trial %d)", trial.errStr, actualErr.Error(), idx)
53+
}
54+
}
55+
}
56+
57+
//
58+
// func TestParseAll(t *testing.T) {
59+
// var table = []struct {
60+
// input string
61+
// key Key
62+
// errStr string
63+
// }{
64+
// {"ctrl + A, ctrl + B", Key{Value: gocui.KeyCtrlA, Modifier:gocui.ModNone}, ""},
65+
// // {"Ctrl + a", gocui.KeyCtrlA, gocui.ModNone, ""},
66+
// // {"Ctl + a", gocui.KeyCtrlA, gocui.ModNone, ""},
67+
// // {"ctl + A", gocui.KeyCtrlA, gocui.ModNone, ""},
68+
// }
69+
//
70+
// for idx, trial := range table {
71+
// actualKey, actualErr := Parse(trial.input)
72+
//
73+
// if actualKey.Value != trial.key {
74+
// t.Errorf("Expected key '%+v' but got '%+v' (trial %d)", trial.key, actualKey, idx)
75+
// }
76+
//
77+
// if actualKey.Modifier != trial.modifier {
78+
// t.Errorf("Expected modifier '%+v' but got '%+v' (trial %d)", trial.modifier, actualKey, idx)
79+
// }
80+
//
81+
// if actualErr == nil && trial.errStr != "" {
82+
// t.Errorf("Expected error message of '%s' but got no message (trial %d)", trial.errStr, idx)
83+
// } else if actualErr != nil && actualErr.Error() != trial.errStr {
84+
// t.Errorf("Expected error message '%s' but got '%s' (trial %d)", trial.errStr, actualErr.Error(), idx)
85+
// }
86+
// }
87+
// }
88+
//

0 commit comments

Comments
 (0)