1package tui
2
3import "testing"
4
5func TestReaderParsesCSIUShiftEnter(t *testing.T) {
6 k := readKey(t, "\x1b[13;2u")
7 if k.Kind != KeyEnter || !k.Shift || k.Alt {
8 t.Fatalf("Read kind=%v shift=%v alt=%v, want shift+enter", k.Kind, k.Shift, k.Alt)
9 }
10}
11
12func TestReaderParsesModifyOtherKeysShiftEnter(t *testing.T) {
13 k := readKey(t, "\x1b[27;2;13~")
14 if k.Kind != KeyEnter || !k.Shift || k.Alt {
15 t.Fatalf("Read kind=%v shift=%v alt=%v, want shift+enter", k.Kind, k.Shift, k.Alt)
16 }
17}
18
19func TestReaderParsesCSIUCtrlC(t *testing.T) {
20 k := readKey(t, "\x1b[99;5u")
21 if k.Kind != KeyCtrlC || !k.Ctrl {
22 t.Fatalf("Read kind=%v ctrl=%v, want ctrl+c", k.Kind, k.Ctrl)
23 }
24}
25
26func TestReaderParsesCSIUCtrlNumber(t *testing.T) {
27 k := readKey(t, "\x1b[49;5u")
28 if k.Kind != KeyRune || k.Rune != '1' || !k.Ctrl {
29 t.Fatalf("Read kind=%v rune=%q ctrl=%v, want ctrl+1", k.Kind, k.Rune, k.Ctrl)
30 }
31}
32
33func TestReaderParsesCSIUSuperNumber(t *testing.T) {
34 k := readKey(t, "\x1b[50;9u")
35 if k.Kind != KeyRune || k.Rune != '2' || !k.Super {
36 t.Fatalf("Read kind=%v rune=%q super=%v, want super+2", k.Kind, k.Rune, k.Super)
37 }
38}
39
40func TestReaderParsesCSIUSuperNumberWithEventType(t *testing.T) {
41 k := readKey(t, "\x1b[51;9:3u")
42 if k.Kind != KeyRune || k.Rune != '3' || !k.Super {
43 t.Fatalf("Read kind=%v rune=%q super=%v, want super+3", k.Kind, k.Rune, k.Super)
44 }
45}
46
47func TestReaderParsesCSIUHyperNumberAsSuper(t *testing.T) {
48 k := readKey(t, "\x1b[52;33u")
49 if k.Kind != KeyRune || k.Rune != '4' || !k.Super {
50 t.Fatalf("Read kind=%v rune=%q super=%v, want hyper+4 as super", k.Kind, k.Rune, k.Super)
51 }
52}
53
54func TestReaderParsesRawCtrlGAsModifiedRune(t *testing.T) {
55 k := readKey(t, "\x07")
56 if k.Kind != KeyRune || k.Rune != 'g' || !k.Ctrl {
57 t.Fatalf("Read kind=%v rune=%q ctrl=%v, want ctrl+g", k.Kind, k.Rune, k.Ctrl)
58 }
59}
60
61func TestReaderParsesRawCtrlSAsModifiedRune(t *testing.T) {
62 k := readKey(t, "\x13")
63 if k.Kind != KeyRune || k.Rune != 's' || !k.Ctrl {
64 t.Fatalf("Read kind=%v rune=%q ctrl=%v, want ctrl+s", k.Kind, k.Rune, k.Ctrl)
65 }
66}
67
68func TestReaderParsesRawCtrlBAsModifiedRune(t *testing.T) {
69 k := readKey(t, "\x02")
70 if k.Kind != KeyRune || k.Rune != 'b' || !k.Ctrl {
71 t.Fatalf("Read kind=%v rune=%q ctrl=%v, want ctrl+b", k.Kind, k.Rune, k.Ctrl)
72 }
73}
74
75func TestReaderKeepsDedicatedControlKinds(t *testing.T) {
76 for in, want := range map[string]KeyKind{
77 "\x01": KeyCtrlA, "\x03": KeyCtrlC, "\x04": KeyCtrlD, "\x05": KeyCtrlE,
78 "\x0b": KeyCtrlK, "\x0c": KeyCtrlL, "\x0f": KeyCtrlO, "\x15": KeyCtrlU,
79 "\x17": KeyCtrlW, "\x16": KeyPasteClipboard, "\t": KeyTab, "\r": KeyEnter, "\x08": KeyBackspace,
80 } {
81 if k := readKey(t, in); k.Kind != want {
82 t.Fatalf("Read(%q) kind=%v, want %v", in, k.Kind, want)
83 }
84 }
85}
86
87func TestReaderParsesCSIUCtrlLetterAsModifiedRune(t *testing.T) {
88 k := readKey(t, "\x1b[115;5u")
89 if k.Kind != KeyRune || k.Rune != 's' || !k.Ctrl || k.Shift {
90 t.Fatalf("Read = %+v, want ctrl+s", k)
91 }
92 k = readKey(t, "\x1b[71;6u")
93 if k.Kind != KeyRune || k.Rune != 'G' || !k.Ctrl || !k.Shift {
94 t.Fatalf("Read = %+v, want ctrl+shift+G", k)
95 }
96}
97
98func TestReaderParsesRawCtrlVAsClipboardPaste(t *testing.T) {
99 k := readKey(t, "\x16")
100 if k.Kind != KeyPasteClipboard || !k.Ctrl {
101 t.Fatalf("Read kind=%v ctrl=%v, want ctrl+v clipboard paste", k.Kind, k.Ctrl)
102 }
103}
104
105func TestReaderParsesCSIUCtrlVAsClipboardPaste(t *testing.T) {
106 k := readKey(t, "\x1b[118;5u")
107 if k.Kind != KeyPasteClipboard || !k.Ctrl {
108 t.Fatalf("Read kind=%v ctrl=%v, want enhanced ctrl+v clipboard paste", k.Kind, k.Ctrl)
109 }
110}
111
112func TestReaderParsesModifyOtherKeysCtrlC(t *testing.T) {
113 k := readKey(t, "\x1b[27;5;99~")
114 if k.Kind != KeyCtrlC || !k.Ctrl {
115 t.Fatalf("Read kind=%v ctrl=%v, want ctrl+c", k.Kind, k.Ctrl)
116 }
117}
118
119func TestReaderParsesCSIUEsc(t *testing.T) {
120 k := readKey(t, "\x1b[27u")
121 if k.Kind != KeyEsc {
122 t.Fatalf("Read kind=%v, want esc", k.Kind)
123 }
124}
125
126func TestReaderParsesCSIUTabAndBackspace(t *testing.T) {
127 if k := readKey(t, "\x1b[9u"); k.Kind != KeyTab {
128 t.Fatalf("Read kind=%v, want tab", k.Kind)
129 }
130 if k := readKey(t, "\x1b[9;2u"); k.Kind != KeyShiftTab {
131 t.Fatalf("Read kind=%v, want shift-tab", k.Kind)
132 }
133 if k := readKey(t, "\x1b[127u"); k.Kind != KeyBackspace {
134 t.Fatalf("Read kind=%v, want backspace", k.Kind)
135 }
136}
137
138func TestReaderParsesSS3CursorKeys(t *testing.T) {
139 cases := []struct {
140 seq string
141 want KeyKind
142 }{
143 {"\x1bOA", KeyUp},
144 {"\x1bOB", KeyDown},
145 {"\x1bOC", KeyRight},
146 {"\x1bOD", KeyLeft},
147 }
148 for _, tc := range cases {
149 if k := readKey(t, tc.seq); k.Kind != tc.want {
150 t.Fatalf("Read(%q) kind=%v, want %v", tc.seq, k.Kind, tc.want)
151 }
152 }
153}
154
155func TestReaderParsesSGRMouseWheel(t *testing.T) {
156 cases := []struct {
157 seq string
158 want KeyKind
159 }{
160 {"\x1b[<64;10;20M", KeyMouseWheelUp},
161 {"\x1b[<65;10;20M", KeyMouseWheelDown},
162 }
163 for _, tc := range cases {
164 k := readKey(t, tc.seq)
165 if k.Kind != tc.want {
166 t.Fatalf("Read(%q) kind=%v, want %v", tc.seq, k.Kind, tc.want)
167 }
168 }
169}
170
171func readKey(t *testing.T, seq string) Key {
172 t.Helper()
173 idx := 0
174 r := NewReader(func() (byte, error) {
175 b := seq[idx]
176 idx++
177 return b, nil
178 })
179 k, err := r.Read()
180 if err != nil {
181 t.Fatalf("Read(%q): %v", seq, err)
182 }
183 return k
184}