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