1 #include <string.h>
2 #include "test.h"
3 #include "../src/debug.h"
4 #include "../src/terminal/color.h"
5 #include "../src/terminal/key.h"
6 #include "../src/terminal/xterm.h"
7 #include "../src/util/unicode.h"
8
test_parse_term_color(void)9 static void test_parse_term_color(void)
10 {
11 static const struct {
12 const char *const strs[4];
13 const TermColor expected_color;
14 } tests[] = {
15 {{"bold", "red", "yellow"}, {COLOR_RED, COLOR_YELLOW, ATTR_BOLD}},
16 {{"#ff0000"}, {COLOR_RGB(0xff0000), -1, 0}},
17 {{"#f00a9c", "reverse"}, {COLOR_RGB(0xf00a9c), -1, ATTR_REVERSE}},
18 {{"black", "#00ffff"}, {COLOR_BLACK, COLOR_RGB(0x00ffff), 0}},
19 {{"#123456", "#abcdef"}, {COLOR_RGB(0x123456), COLOR_RGB(0xabcdef), 0}},
20 {{"red", "strikethrough"}, {COLOR_RED, -1, ATTR_STRIKETHROUGH}},
21 {{"5/5/5"}, {231, COLOR_DEFAULT, 0}},
22 {{"1/3/0", "0/5/2", "italic"}, {70, 48, ATTR_ITALIC}},
23 {{"-1", "-2"}, {COLOR_DEFAULT, COLOR_KEEP, 0}},
24 {{"keep", "red", "keep"}, {-2, COLOR_RED, ATTR_KEEP}},
25 {{"bold", "blink"}, {-1, -1, ATTR_BOLD | ATTR_BLINK}},
26 {{"0", "255", "underline"}, {COLOR_BLACK, 255, ATTR_UNDERLINE}},
27 {{"white", "green", "dim"}, {COLOR_WHITE, COLOR_GREEN, ATTR_DIM}},
28 {{"lightred", "lightyellow"}, {COLOR_LIGHTRED, COLOR_LIGHTYELLOW, 0}},
29 {{"darkgray", "lightgreen"}, {COLOR_DARKGRAY, COLOR_LIGHTGREEN, 0}},
30 {{"lightblue", "lightcyan"}, {COLOR_LIGHTBLUE, COLOR_LIGHTCYAN, 0}},
31 {{"lightmagenta"}, {COLOR_LIGHTMAGENTA, COLOR_DEFAULT, 0}},
32 {{"keep", "254", "keep"}, {COLOR_KEEP, 254, ATTR_KEEP}},
33 {{"red", "green", "keep"}, {COLOR_RED, COLOR_GREEN, ATTR_KEEP}},
34 {{"1", "2", "invisible"}, {COLOR_RED, COLOR_GREEN, ATTR_INVIS}},
35 };
36 FOR_EACH_I(i, tests) {
37 TermColor parsed;
38 bool ok = parse_term_color(&parsed, (char**)tests[i].strs);
39 IEXPECT_TRUE(ok);
40 if (ok) {
41 const TermColor expected = tests[i].expected_color;
42 IEXPECT_EQ(parsed.fg, expected.fg);
43 IEXPECT_EQ(parsed.bg, expected.bg);
44 IEXPECT_EQ(parsed.attr, expected.attr);
45 }
46 }
47 }
48
test_color_to_nearest(void)49 static void test_color_to_nearest(void)
50 {
51 static const struct {
52 int32_t input;
53 int32_t expected_rgb;
54 int32_t expected_256;
55 int32_t expected_16;
56 } tests[] = {
57 // ECMA-48 colors
58 {0, 0, 0, 0},
59 {5, 5, 5, 5},
60 {7, 7, 7, 7},
61
62 // aixterm-style colors
63 {8, 8, 8, 8},
64 {10, 10, 10, 10},
65 {15, 15, 15, 15},
66
67 // xterm 256 palette colors
68 {25, 25, 25, COLOR_BLUE},
69 {87, 87, 87, COLOR_LIGHTCYAN},
70 {88, 88, 88, COLOR_RED},
71 {90, 90, 90, COLOR_MAGENTA},
72 {96, 96, 96, COLOR_MAGENTA},
73 {178, 178, 178, COLOR_YELLOW},
74 {179, 179, 179, COLOR_YELLOW},
75
76 // RGB colors with exact xterm 6x6x6 cube equivalents
77 {COLOR_RGB(0x000000), 16, 16, COLOR_BLACK},
78 {COLOR_RGB(0x000087), 18, 18, COLOR_BLUE},
79 {COLOR_RGB(0x0000FF), 21, 21, COLOR_LIGHTBLUE},
80 {COLOR_RGB(0x00AF87), 36, 36, COLOR_GREEN},
81 {COLOR_RGB(0x00FF00), 46, 46, COLOR_LIGHTGREEN},
82 {COLOR_RGB(0x870000), 88, 88, COLOR_RED},
83 {COLOR_RGB(0xFF0000), 196, 196, COLOR_LIGHTRED},
84 {COLOR_RGB(0xFFD700), 220, 220, COLOR_YELLOW},
85 {COLOR_RGB(0xFFFF5F), 227, 227, COLOR_LIGHTYELLOW},
86 {COLOR_RGB(0xFFFFFF), 231, 231, COLOR_WHITE},
87
88 // RGB colors with exact xterm grayscale equivalents
89 {COLOR_RGB(0x080808), 232, 232, COLOR_BLACK},
90 {COLOR_RGB(0x121212), 233, 233, COLOR_BLACK},
91 {COLOR_RGB(0x6C6C6C), 242, 242, COLOR_DARKGRAY},
92 {COLOR_RGB(0xA8A8A8), 248, 248, COLOR_GRAY},
93 {COLOR_RGB(0xB2B2B2), 249, 249, COLOR_GRAY},
94 {COLOR_RGB(0xBCBCBC), 250, 250, COLOR_WHITE},
95 {COLOR_RGB(0xEEEEEE), 255, 255, COLOR_WHITE},
96
97 // RGB colors with NO exact xterm equivalents
98 {COLOR_RGB(0x00FF88), COLOR_RGB(0x00FF88), 48, COLOR_LIGHTGREEN},
99 {COLOR_RGB(0xFF0001), COLOR_RGB(0xFF0001), 196, COLOR_LIGHTRED},
100 {COLOR_RGB(0xAABBCC), COLOR_RGB(0xAABBCC), 146, COLOR_LIGHTBLUE},
101 {COLOR_RGB(0x080809), COLOR_RGB(0x080809), 232, COLOR_BLACK},
102 {COLOR_RGB(0xBABABA), COLOR_RGB(0xBABABA), 250, COLOR_WHITE},
103 {COLOR_RGB(0xEEEEED), COLOR_RGB(0xEEEEED), 255, COLOR_WHITE},
104 };
105 FOR_EACH_I(i, tests) {
106 const int32_t c = tests[i].input;
107 IEXPECT_EQ(color_to_nearest(c, TERM_TRUE_COLOR), tests[i].expected_rgb);
108 IEXPECT_EQ(color_to_nearest(c, TERM_256_COLOR), tests[i].expected_256);
109 IEXPECT_EQ(color_to_nearest(c, TERM_16_COLOR), tests[i].expected_16);
110 IEXPECT_EQ(color_to_nearest(c, TERM_8_COLOR), tests[i].expected_16 & 7);
111 IEXPECT_EQ(color_to_nearest(c, TERM_0_COLOR), COLOR_DEFAULT);
112 }
113 }
114
test_xterm_parse_key(void)115 static void test_xterm_parse_key(void)
116 {
117 static const struct xterm_key_test {
118 const char *escape_sequence;
119 ssize_t expected_length;
120 KeyCode expected_key;
121 } tests[] = {
122 {"\033[Z", 3, MOD_SHIFT | '\t'},
123 {"\033[1;2A", 6, MOD_SHIFT | KEY_UP},
124 {"\033[1;2B", 6, MOD_SHIFT | KEY_DOWN},
125 {"\033[1;2C", 6, MOD_SHIFT | KEY_RIGHT},
126 {"\033[1;2D", 6, MOD_SHIFT | KEY_LEFT},
127 {"\033[1;2F", 6, MOD_SHIFT | KEY_END},
128 {"\033[1;2H", 6, MOD_SHIFT | KEY_HOME},
129 {"\033[1;8H", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME},
130 {"\033[1;8H~", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME},
131 {"\033[1;8H~_", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME},
132 {"\033", -1, 0},
133 {"\033[", -1, 0},
134 {"\033]", 0, 0},
135 {"\033[1", -1, 0},
136 {"\033[9", -1, 0},
137 {"\033[1;", -1, 0},
138 {"\033[1[", 0, 0},
139 {"\033[1;2", -1, 0},
140 {"\033[1;8", -1, 0},
141 {"\033[1;9", -1, 0},
142 {"\033[1;_", 0, 0},
143 {"\033[1;8Z", 0, 0},
144 {"\033O", -1, 0},
145 {"\033[\033", 0, 0},
146 {"\033[A", 3, KEY_UP},
147 {"\033[B", 3, KEY_DOWN},
148 {"\033[C", 3, KEY_RIGHT},
149 {"\033[D", 3, KEY_LEFT},
150 {"\033[F", 3, KEY_END},
151 {"\033[H", 3, KEY_HOME},
152 {"\033[L", 3, KEY_INSERT},
153 {"\033[1~", 4, KEY_HOME},
154 {"\033[2~", 4, KEY_INSERT},
155 {"\033[3~", 4, KEY_DELETE},
156 {"\033[4~", 4, KEY_END},
157 {"\033[5~", 4, KEY_PAGE_UP},
158 {"\033[6~", 4, KEY_PAGE_DOWN},
159 {"\033O ", 3, ' '},
160 {"\033OA", 3, KEY_UP},
161 {"\033OB", 3, KEY_DOWN},
162 {"\033OC", 3, KEY_RIGHT},
163 {"\033OD", 3, KEY_LEFT},
164 {"\033OF", 3, KEY_END},
165 {"\033OH", 3, KEY_HOME},
166 {"\033OI", 3, '\t'},
167 {"\033OM", 3, KEY_ENTER},
168 {"\033OP", 3, KEY_F1},
169 {"\033OQ", 3, KEY_F2},
170 {"\033OR", 3, KEY_F3},
171 {"\033OS", 3, KEY_F4},
172 {"\033OX", 3, '='},
173 {"\033Oj", 3, '*'},
174 {"\033Ok", 3, '+'},
175 {"\033Ol", 3, ','},
176 {"\033Om", 3, '-'},
177 {"\033On", 3, '.'},
178 {"\033Oo", 3, '/'},
179 {"\033Op", 3, '0'},
180 {"\033Oq", 3, '1'},
181 {"\033Or", 3, '2'},
182 {"\033Os", 3, '3'},
183 {"\033Ot", 3, '4'},
184 {"\033Ou", 3, '5'},
185 {"\033Ov", 3, '6'},
186 {"\033Ow", 3, '7'},
187 {"\033Ox", 3, '8'},
188 {"\033Oy", 3, '9'},
189 {"\033[10~", 0, 0},
190 {"\033[11~", 5, KEY_F1},
191 {"\033[12~", 5, KEY_F2},
192 {"\033[13~", 5, KEY_F3},
193 {"\033[14~", 5, KEY_F4},
194 {"\033[15~", 5, KEY_F5},
195 {"\033[16~", 0, 0},
196 {"\033[17~", 5, KEY_F6},
197 {"\033[18~", 5, KEY_F7},
198 {"\033[19~", 5, KEY_F8},
199 {"\033[20~", 5, KEY_F9},
200 {"\033[21~", 5, KEY_F10},
201 {"\033[22~", 0, 0},
202 {"\033[23~", 5, KEY_F11},
203 {"\033[24~", 5, KEY_F12},
204 {"\033[25~", 0, 0},
205 {"\033[6;3~", 6, MOD_META | KEY_PAGE_DOWN},
206 {"\033[6;5~", 6, MOD_CTRL | KEY_PAGE_DOWN},
207 {"\033[6;8~", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_PAGE_DOWN},
208 // Linux console
209 {"\033[[A", 4, KEY_F1},
210 {"\033[[B", 4, KEY_F2},
211 {"\033[[C", 4, KEY_F3},
212 {"\033[[D", 4, KEY_F4},
213 {"\033[[E", 4, KEY_F5},
214 // rxvt
215 {"\033Oa", 3, MOD_CTRL | KEY_UP},
216 {"\033Ob", 3, MOD_CTRL | KEY_DOWN},
217 {"\033Oc", 3, MOD_CTRL | KEY_RIGHT},
218 {"\033Od", 3, MOD_CTRL | KEY_LEFT},
219 {"\033[a", 3, MOD_SHIFT | KEY_UP},
220 {"\033[b", 3, MOD_SHIFT | KEY_DOWN},
221 {"\033[c", 3, MOD_SHIFT | KEY_RIGHT},
222 {"\033[d", 3, MOD_SHIFT | KEY_LEFT},
223 // xterm + `modifyOtherKeys` option
224 {"\033[27;5;9~", 9, MOD_CTRL | '\t'},
225 {"\033[27;5;13~", 10, MOD_CTRL | KEY_ENTER},
226 {"\033[27;6;13~", 10, MOD_CTRL | MOD_SHIFT |KEY_ENTER},
227 {"\033[27;2;127~", 11, MOD_CTRL | '?'},
228 {"\033[27;6;127~", 11, MOD_CTRL | '?'},
229 {"\033[27;8;127~", 11, MOD_CTRL | MOD_META | '?'},
230 {"\033[27;6;82~", 10, MOD_CTRL | 'R'},
231 {"\033[27;5;114~", 11, MOD_CTRL | 'R'},
232 {"\033[27;3;82~", 10, MOD_META | 'R'},
233 {"\033[27;3;114~", 11, MOD_META | 'r'},
234 {"\033[27;4;62~", 10, MOD_META | '>'},
235 {"\033[27;5;46~", 10, MOD_CTRL | '.'},
236 {"\033[27;3;1114111~", 15, MOD_META | UNICODE_MAX_VALID_CODEPOINT},
237 {"\033[27;3;1114112~", 0, 0},
238 {"\033[27;999999999999999999999;123~", 0, 0},
239 {"\033[27;123;99999999999999999~", 0, 0},
240 // www.leonerd.org.uk/hacks/fixterms/
241 {"\033[13;3u", 7, MOD_META | KEY_ENTER},
242 {"\033[9;5u", 6, MOD_CTRL | '\t'},
243 {"\033[65;3u", 7, MOD_META | 'A'},
244 {"\033[108;5u", 8, MOD_CTRL | 'L'},
245 {"\033[127765;3u", 11, MOD_META | 127765ul},
246 {"\033[1114111;3u", 12, MOD_META | UNICODE_MAX_VALID_CODEPOINT},
247 {"\033[1114112;3u", 0, 0},
248 {"\033[11141110;3u", 0, 0},
249 {"\033[11141111;3u", 0, 0},
250 {"\033[2147483647;3u", 0, 0}, // INT32_MAX
251 {"\033[2147483648;3u", 0, 0}, // INT32_MAX + 1
252 {"\033[4294967295;3u", 0, 0}, // UINT32_MAX
253 {"\033[4294967296;3u", 0, 0}, // UINT32_MAX + 1
254 {"\033[-1;3u", 0, 0},
255 {"\033[-2;3u", 0, 0},
256 };
257 FOR_EACH_I(i, tests) {
258 const char *seq = tests[i].escape_sequence;
259 const size_t seq_length = strlen(seq);
260 BUG_ON(seq_length == 0);
261 KeyCode key = 0x18;
262 ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key);
263 ssize_t expected_length = tests[i].expected_length;
264 IEXPECT_EQ(parsed_length, expected_length);
265 if (parsed_length <= 0) {
266 // If nothing was parsed, key should be unmodified
267 IEXPECT_EQ(key, 0x18);
268 continue;
269 }
270 IEXPECT_EQ(key, tests[i].expected_key);
271 // Ensure that parsing any truncated sequence returns -1:
272 key = 0x18;
273 for (size_t n = expected_length - 1; n != 0; n--) {
274 parsed_length = xterm_parse_key(seq, n, &key);
275 IEXPECT_EQ(parsed_length, -1);
276 IEXPECT_EQ(key, 0x18);
277 }
278 }
279 }
280
test_xterm_parse_key_combo(void)281 static void test_xterm_parse_key_combo(void)
282 {
283 static const struct {
284 char escape_sequence[8];
285 KeyCode key;
286 } templates[] = {
287 {"\033[1;_A", KEY_UP},
288 {"\033[1;_B", KEY_DOWN},
289 {"\033[1;_C", KEY_RIGHT},
290 {"\033[1;_D", KEY_LEFT},
291 {"\033[1;_F", KEY_END},
292 {"\033[1;_H", KEY_HOME},
293 {"\033[2;_~", KEY_INSERT},
294 {"\033[3;_~", KEY_DELETE},
295 {"\033[5;_~", KEY_PAGE_UP},
296 {"\033[6;_~", KEY_PAGE_DOWN},
297 {"\033[1;_P", KEY_F1},
298 {"\033[1;_Q", KEY_F2},
299 {"\033[1;_R", KEY_F3},
300 {"\033[1;_S", KEY_F4},
301 {"\033[15;_~", KEY_F5},
302 {"\033[17;_~", KEY_F6},
303 {"\033[18;_~", KEY_F7},
304 {"\033[19;_~", KEY_F8},
305 {"\033[20;_~", KEY_F9},
306 {"\033[21;_~", KEY_F10},
307 {"\033[23;_~", KEY_F11},
308 {"\033[24;_~", KEY_F12},
309 };
310
311 static const struct {
312 char ch;
313 KeyCode mask;
314 } modifiers[] = {
315 {'2', MOD_SHIFT},
316 {'3', MOD_META},
317 {'4', MOD_SHIFT | MOD_META},
318 {'5', MOD_CTRL},
319 {'6', MOD_SHIFT | MOD_CTRL},
320 {'7', MOD_META | MOD_CTRL},
321 {'8', MOD_SHIFT | MOD_META | MOD_CTRL}
322 };
323
324 FOR_EACH_I(i, templates) {
325 FOR_EACH_I(j, modifiers) {
326 char seq[8];
327 memcpy(seq, templates[i].escape_sequence, 8);
328 BUG_ON(seq[7] != '\0');
329 char *underscore = strchr(seq, '_');
330 ASSERT_NONNULL(underscore);
331 *underscore = modifiers[j].ch;
332 size_t seq_length = strlen(seq);
333 KeyCode key;
334 ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key);
335 EXPECT_EQ(parsed_length, seq_length);
336 EXPECT_EQ(key, modifiers[j].mask | templates[i].key);
337 // Truncated
338 key = 0x18;
339 for (size_t n = seq_length - 1; n != 0; n--) {
340 parsed_length = xterm_parse_key(seq, n, &key);
341 EXPECT_EQ(parsed_length, -1);
342 EXPECT_EQ(key, 0x18);
343 }
344 // Overlength
345 key = 0x18;
346 seq[seq_length] = '~';
347 parsed_length = xterm_parse_key(seq, seq_length + 1, &key);
348 EXPECT_EQ(parsed_length, seq_length);
349 EXPECT_EQ(key, modifiers[j].mask | templates[i].key);
350 }
351 }
352 }
353
test_xterm_parse_key_combo_rxvt(void)354 static void test_xterm_parse_key_combo_rxvt(void)
355 {
356 static const struct {
357 char escape_sequence[8];
358 KeyCode key;
359 } templates[] = {
360 {"\033[3_", KEY_DELETE},
361 {"\033[5_", KEY_PAGE_UP},
362 {"\033[6_", KEY_PAGE_DOWN},
363 {"\033[7_", KEY_HOME},
364 {"\033[8_", KEY_END},
365 };
366
367 static const struct {
368 char ch;
369 KeyCode mask;
370 } modifiers[] = {
371 {'~', 0},
372 /*
373 Note: the tests for these non-standard rxvt modifiers have been
374 disabled, because using '$' as a final byte is a violation of the
375 ECMA-48 spec:
376
377 {'^', MOD_CTRL},
378 {'$', MOD_SHIFT},
379 {'@', MOD_SHIFT | MOD_CTRL},
380
381 For the rxvt developers to invent a new key encoding schemes where
382 a perfectly good, de facto standard (xterm) already existed was
383 foolish. To then violate the spec in the process was pure stupidity.
384 */
385 };
386
387 FOR_EACH_I(i, templates) {
388 FOR_EACH_I(j, modifiers) {
389 char seq[8];
390 memcpy(seq, templates[i].escape_sequence, 8);
391 BUG_ON(seq[7] != '\0');
392 char *underscore = strchr(seq, '_');
393 ASSERT_NONNULL(underscore);
394 *underscore = modifiers[j].ch;
395 size_t seq_length = strlen(seq);
396 KeyCode key;
397 ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key);
398 EXPECT_EQ(parsed_length, seq_length);
399 EXPECT_EQ(key, modifiers[j].mask | templates[i].key);
400 }
401 }
402 }
403
test_key_to_string(void)404 static void test_key_to_string(void)
405 {
406 static const struct key_to_string_test {
407 const char *str;
408 KeyCode key;
409 } tests[] = {
410 {"a", 'a'},
411 {"Z", 'Z'},
412 {"0", '0'},
413 {"{", '{'},
414 {"space", ' '},
415 {"enter", KEY_ENTER},
416 {"tab", '\t'},
417 {"insert", KEY_INSERT},
418 {"delete", KEY_DELETE},
419 {"home", KEY_HOME},
420 {"end", KEY_END},
421 {"pgup", KEY_PAGE_UP},
422 {"pgdown", KEY_PAGE_DOWN},
423 {"left", KEY_LEFT},
424 {"right", KEY_RIGHT},
425 {"up", KEY_UP},
426 {"down", KEY_DOWN},
427 {"C-A", MOD_CTRL | 'A'},
428 {"M-S-{", MOD_META | MOD_SHIFT | '{'},
429 {"C-S-A", MOD_CTRL | MOD_SHIFT | 'A'},
430 {"F1", KEY_F1},
431 {"F12", KEY_F12},
432 {"M-enter", MOD_META | KEY_ENTER},
433 {"M-space", MOD_META | ' '},
434 {"S-tab", MOD_SHIFT | '\t'},
435 {"C-M-S-F12", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_F12},
436 {"C-M-S-up", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_UP},
437 {"C-M-delete", MOD_CTRL | MOD_META | KEY_DELETE},
438 {"C-home", MOD_CTRL | KEY_HOME},
439 };
440 FOR_EACH_I(i, tests) {
441 const char *str = key_to_string(tests[i].key);
442 IEXPECT_STREQ(str, tests[i].str);
443 }
444 }
445
446 DISABLE_WARNING("-Wmissing-prototypes")
447
test_terminal(void)448 void test_terminal(void)
449 {
450 test_parse_term_color();
451 test_color_to_nearest();
452 test_xterm_parse_key();
453 test_xterm_parse_key_combo();
454 test_xterm_parse_key_combo_rxvt();
455 test_key_to_string();
456 }
457