dte test coverage


Directory: ./
File: src/terminal/key.c
Date: 2025-12-11 10:43:49
Coverage Exec Excl Total
Lines: 100.0% 87 0 87
Functions: 100.0% 7 0 7
Branches: 96.3% 52 0 54

Line Branch Exec Source
1 #include <string.h>
2 #include "key.h"
3 #include "util/array.h"
4 #include "util/ascii.h"
5 #include "util/debug.h"
6 #include "util/numtostr.h"
7 #include "util/str-util.h"
8 #include "util/unicode.h"
9 #include "util/utf8.h"
10
11 #define KEY(keyname) [KEY_ ## keyname - KEY_SPECIAL_MIN]
12
13 static const char special_names[][10] = {
14 KEY(ESCAPE) = "escape",
15 KEY(BACKSPACE) = "backspace",
16 KEY(INSERT) = "insert",
17 KEY(DELETE) = "delete",
18 KEY(HOME) = "home",
19 KEY(END) = "end",
20 KEY(PAGE_UP) = "pgup",
21 KEY(PAGE_DOWN) = "pgdown",
22 KEY(UP) = "up",
23 KEY(DOWN) = "down",
24 KEY(RIGHT) = "right",
25 KEY(LEFT) = "left",
26 KEY(BEGIN) = "begin",
27 KEY(SCROLL_LOCK) = "scrlock",
28 KEY(PRINT_SCREEN) = "print",
29 KEY(PAUSE) = "pause",
30 KEY(MENU) = "menu",
31 KEY(F1) = "F1",
32 KEY(F2) = "F2",
33 KEY(F3) = "F3",
34 KEY(F4) = "F4",
35 KEY(F5) = "F5",
36 KEY(F6) = "F6",
37 KEY(F7) = "F7",
38 KEY(F8) = "F8",
39 KEY(F9) = "F9",
40 KEY(F10) = "F10",
41 KEY(F11) = "F11",
42 KEY(F12) = "F12",
43 KEY(F13) = "F13",
44 KEY(F14) = "F14",
45 KEY(F15) = "F15",
46 KEY(F16) = "F16",
47 KEY(F17) = "F17",
48 KEY(F18) = "F18",
49 KEY(F19) = "F19",
50 KEY(F20) = "F20",
51 KEY(F21) = "F21",
52 KEY(F22) = "F22",
53 KEY(F23) = "F23",
54 KEY(F24) = "F24",
55 };
56
57 static const struct {
58 char name[8];
59 KeyCode key;
60 } other_keys[] = {
61 {"tab", KEY_TAB},
62 {"enter", KEY_ENTER},
63 {"space", KEY_SPACE},
64 };
65
66 24 UNITTEST {
67 24 static_assert(ARRAYLEN(special_names) == NR_SPECIAL_KEYS);
68 24 CHECK_STRING_ARRAY(special_names);
69 24 CHECK_STRUCT_ARRAY(other_keys, name);
70
71 24 static_assert(KEY_UNICODE_MAX == UNICODE_MAX_VALID_CODEPOINT);
72 24 static_assert(KEY_MASK + 1 == 1u << 21);
73 24 static_assert((KEY_MASK & MOD_MASK) == 0);
74 24 static_assert(((KEY_MASK | MOD_MASK) & KEYCODE_QUERY_REPLY_BIT) == 0);
75 24 static_assert((KEY_MASK & KEY_SPECIAL_MIN) == KEY_SPECIAL_MIN);
76 24 static_assert((KEY_MASK & KEY_SPECIAL_MAX) == KEY_SPECIAL_MAX);
77 24 static_assert((KEY_MASK & KEY_IGNORE) == KEY_IGNORE);
78 24 static_assert((KEY_MASK & KEYCODE_DETECTED_PASTE) == KEYCODE_DETECTED_PASTE);
79 24 static_assert((KEY_MASK & KEYCODE_BRACKETED_PASTE) == KEYCODE_BRACKETED_PASTE);
80 24 }
81
82 // https://www.gnu.org/software/emacs/manual/html_node/efaq/Binding-combinations-of-modifiers-and-function-keys.html
83 3668 static KeyCode modifier_from_char(char c)
84 {
85
6/6
✓ Branch 2 → 3 taken 775 times.
✓ Branch 2 → 4 taken 511 times.
✓ Branch 2 → 5 taken 20 times.
✓ Branch 2 → 6 taken 6 times.
✓ Branch 2 → 7 taken 1736 times.
✓ Branch 2 → 8 taken 620 times.
3668 switch (c) {
86 case 'C': return MOD_CTRL;
87 775 case 'M': return MOD_META;
88 511 case 'S': return MOD_SHIFT;
89 20 case 's': return MOD_SUPER;
90 6 case 'H': return MOD_HYPER;
91 }
92 1736 return 0;
93 }
94
95 1760 static size_t parse_modifiers(const char *str, KeyCode *modifiersp)
96 {
97
3/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 5 taken 1758 times.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
1760 if (str[0] == '^' && str[1] != '\0') {
98 2 *modifiersp = MOD_CTRL;
99 2 return 1;
100 }
101
102 KeyCode modifiers = 0;
103 size_t i = 0;
104
105 5578 while (1) {
106 3668 KeyCode tmp = modifier_from_char(str[i]);
107
6/6
✓ Branch 6 → 7 taken 1932 times.
✓ Branch 6 → 10 taken 1736 times.
✓ Branch 7 → 8 taken 1912 times.
✓ Branch 7 → 10 taken 20 times.
✓ Branch 8 → 9 taken 1910 times.
✓ Branch 8 → 10 taken 2 times.
3668 if (tmp == 0 || str[i + 1] != '-' || modifiers & tmp) {
108 break;
109 }
110 1910 modifiers |= tmp;
111 1910 i += 2;
112 }
113
114 1758 *modifiersp = modifiers;
115 1758 return i;
116 }
117
118 1760 KeyCode keycode_from_str(const char *str)
119 {
120 1760 KeyCode modifiers;
121 1760 str += parse_modifiers(str, &modifiers);
122 1760 size_t len = strlen(str);
123 1760 size_t pos = 0;
124 1760 KeyCode ch = u_get_char(str, len, &pos);
125
126
3/4
✓ Branch 4 → 5 taken 1760 times.
✗ Branch 4 → 17 not taken.
✓ Branch 5 → 6 taken 814 times.
✓ Branch 5 → 17 taken 946 times.
1760 if (u_is_unicode(ch) && pos == len) {
127
2/2
✓ Branch 6 → 7 taken 806 times.
✓ Branch 6 → 24 taken 8 times.
814 if (unlikely(u_is_cntrl(ch))) {
128 return KEY_NONE;
129 }
130
2/2
✓ Branch 7 → 8 taken 10 times.
✓ Branch 7 → 12 taken 796 times.
806 if (u_is_ascii_upper(ch)) {
131
2/2
✓ Branch 8 → 9 taken 8 times.
✓ Branch 8 → 10 taken 2 times.
10 if (modifiers & MOD_CTRL) {
132 // Convert C-A to C-a
133 8 ch = ascii_tolower(ch);
134
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
2 } else if (modifiers & MOD_META) {
135 // Convert M-A to M-S-a
136 1 modifiers |= MOD_SHIFT;
137 1 ch = ascii_tolower(ch);
138 }
139 }
140 806 return modifiers | ch;
141 }
142
143
2/2
✓ Branch 18 → 13 taken 2737 times.
✓ Branch 18 → 23 taken 881 times.
3618 for (size_t i = 0; i < ARRAYLEN(other_keys); i++) {
144
2/2
✓ Branch 14 → 15 taken 65 times.
✓ Branch 14 → 16 taken 2672 times.
2737 if (ascii_streq_icase(str, other_keys[i].name)) {
145 65 return modifiers | other_keys[i].key;
146 }
147 }
148
149
2/2
✓ Branch 23 → 19 taken 8402 times.
✓ Branch 23 → 24 taken 9 times.
8411 for (size_t i = 0; i < ARRAYLEN(special_names); i++) {
150
2/2
✓ Branch 20 → 21 taken 872 times.
✓ Branch 20 → 22 taken 7530 times.
8402 if (ascii_streq_icase(str, special_names[i])) {
151 872 return modifiers | (KEY_SPECIAL_MIN + i);
152 }
153 }
154
155 return KEY_NONE;
156 }
157
158 292 static const char *lookup_other_key(KeyCode key)
159 {
160
2/2
✓ Branch 6 → 3 taken 849 times.
✓ Branch 6 → 7 taken 272 times.
1121 for (size_t i = 0; i < ARRAYLEN(other_keys); i++) {
161
2/2
✓ Branch 3 → 4 taken 20 times.
✓ Branch 3 → 5 taken 829 times.
849 if (key == other_keys[i].key) {
162 20 return other_keys[i].name;
163 }
164 }
165 return NULL;
166 }
167
168 591 static size_t modifiers_to_str(KeyCode k, char *buf)
169 {
170 591 static const struct {
171 char str[2];
172 KeyCode code;
173 } mods[] = {
174 {"C-", MOD_CTRL},
175 {"M-", MOD_META},
176 {"S-", MOD_SHIFT},
177 {"s-", MOD_SUPER},
178 {"H-", MOD_HYPER},
179 };
180
181 591 size_t pos = 0;
182
2/2
✓ Branch 7 → 3 taken 2955 times.
✓ Branch 7 → 8 taken 591 times.
3546 for (size_t i = 0; i < ARRAYLEN(mods); i++) {
183
2/2
✓ Branch 3 → 4 taken 649 times.
✓ Branch 3 → 6 taken 2306 times.
2955 if (k & mods[i].code) {
184 649 pos += copystrn(buf + pos, mods[i].str, sizeof(mods[0].str));
185 }
186 }
187
188 591 return pos;
189 }
190
191 // Writes the string representation of `k` into `buf` (which must
192 // have at least `KEYCODE_STR_BUFSIZE` bytes available) and returns
193 // the length of the written string.
194 595 size_t keycode_to_str(KeyCode k, char buf[static KEYCODE_STR_BUFSIZE])
195 {
196 595 KeyCode key = keycode_get_key(k);
197 595 KeyCode mask = MOD_MASK | KEY_MASK;
198
4/4
✓ Branch 2 → 3 taken 594 times.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 3 → 4 taken 591 times.
✓ Branch 3 → 6 taken 3 times.
595 bool is_key_combo = (k & mask) == k && key <= KEY_SPECIAL_MAX;
199
200 595 if (unlikely(!is_key_combo)) {
201 4 size_t n = (k & KEYCODE_QUERY_REPLY_BIT)
202 1 ? copyliteral(buf, "QUERY REPLY; 0x")
203
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 3 times.
4 : copyliteral(buf, "INVALID; 0x")
204 ;
205 4 return n + buf_umax_to_hex_str(k, buf + n, 8);
206 }
207
208 591 const char *name;
209 591 size_t pos = modifiers_to_str(k, buf);
210
211
2/2
✓ Branch 5 → 11 taken 299 times.
✓ Branch 5 → 12 taken 292 times.
591 if (key >= KEY_SPECIAL_MIN) {
212 299 name = special_names[key - KEY_SPECIAL_MIN];
213 } else {
214 292 name = lookup_other_key(key);
215
2/2
✓ Branch 12 → 13 taken 272 times.
✓ Branch 12 → 15 taken 20 times.
292 if (!name) {
216 272 pos += u_set_char(buf + pos, key);
217 272 buf[pos] = '\0';
218 272 return pos;
219 }
220 }
221
222 319 BUG_ON(name[0] == '\0');
223 319 char *end = memccpy(buf + pos, name, '\0', KEYCODE_STR_BUFSIZE - pos);
224 319 BUG_ON(!end);
225 319 BUG_ON(end <= buf);
226 319 return (size_t)(end - buf) - 1;
227 }
228