dte test coverage


Directory: ./
File: src/completion.c
Date: 2025-07-13 15:27:15
Exec Total Coverage
Lines: 462 519 89.0%
Functions: 44 48 91.7%
Branches: 218 306 71.2%

Line Branch Exec Source
1 #include <fcntl.h>
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/stat.h>
6 #include <unistd.h>
7 #include "completion.h"
8 #include "bind.h"
9 #include "command/alias.h"
10 #include "command/args.h"
11 #include "command/parse.h"
12 #include "command/run.h"
13 #include "command/serialize.h"
14 #include "commands.h"
15 #include "compiler.h"
16 #include "config.h"
17 #include "exec.h"
18 #include "filetype.h"
19 #include "mode.h"
20 #include "options.h"
21 #include "show.h"
22 #include "syntax/color.h"
23 #include "tag.h"
24 #include "terminal/cursor.h"
25 #include "terminal/key.h"
26 #include "terminal/style.h"
27 #include "util/arith.h"
28 #include "util/array.h"
29 #include "util/ascii.h"
30 #include "util/bit.h"
31 #include "util/bsearch.h"
32 #include "util/environ.h"
33 #include "util/intmap.h"
34 #include "util/log.h"
35 #include "util/numtostr.h"
36 #include "util/path.h"
37 #include "util/str-array.h"
38 #include "util/str-util.h"
39 #include "util/string-view.h"
40 #include "util/string.h"
41 #include "util/xdirent.h"
42 #include "util/xmalloc.h"
43 #include "vars.h"
44
45 typedef enum {
46 COLLECT_ALL, // (directories and files)
47 COLLECT_EXECUTABLES, // (directories and executable files)
48 COLLECT_DIRS_ONLY,
49 } FileCollectionType;
50
51 static bool is_executable(int dir_fd, const char *filename)
52 {
53 return faccessat(dir_fd, filename, X_OK, 0) == 0;
54 }
55
56 10 static bool do_collect_files (
57 PointerArray *array,
58 const char *dirname,
59 const char *dirprefix,
60 const char *fileprefix,
61 FileCollectionType type
62 ) {
63 10 DIR *const dir = xopendir(dirname);
64
1/2
✓ Branch 0 (3→4) taken 10 times.
✗ Branch 1 (3→45) not taken.
10 if (!dir) {
65 return false;
66 }
67
68 10 const int dir_fd = dirfd(dir);
69
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→10) taken 10 times.
10 if (unlikely(dir_fd < 0)) {
70 LOG_ERRNO("dirfd");
71 xclosedir(dir);
72 return false;
73 }
74
75 10 size_t dlen = strlen(dirprefix);
76 10 size_t flen = strlen(fileprefix);
77 10 const struct dirent *de;
78
79
2/2
✓ Branch 0 (42→11) taken 296 times.
✓ Branch 1 (42→43) taken 10 times.
306 while ((de = xreaddir(dir))) {
80 296 const char *name = de->d_name;
81
5/6
✓ Branch 0 (11→12) taken 286 times.
✓ Branch 1 (11→14) taken 10 times.
✓ Branch 2 (12→13) taken 276 times.
✓ Branch 3 (12→14) taken 10 times.
✗ Branch 4 (13→14) not taken.
✓ Branch 5 (13→15) taken 276 times.
296 if (streq(name, ".") || streq(name, "..") || unlikely(streq(name, ""))) {
82 245 continue;
83 }
84
85 // TODO: add a global option to allow dotfiles to be included
86 // even when there's no prefix
87
4/4
✓ Branch 0 (15→16) taken 227 times.
✓ Branch 1 (15→17) taken 49 times.
✓ Branch 2 (18→19) taken 225 times.
✓ Branch 3 (18→20) taken 51 times.
276 if (flen ? strncmp(name, fileprefix, flen) != 0 : name[0] == '.') {
88 225 continue;
89 }
90
91 51 struct stat st;
92
1/2
✗ Branch 0 (21→22) not taken.
✓ Branch 1 (21→23) taken 51 times.
51 if (fstatat(dir_fd, name, &st, AT_SYMLINK_NOFOLLOW)) {
93 continue;
94 }
95
96 51 bool is_dir = S_ISDIR(st.st_mode);
97
1/2
✗ Branch 0 (23→24) not taken.
✓ Branch 1 (23→27) taken 51 times.
51 if (S_ISLNK(st.st_mode)) {
98 if (!fstatat(dir_fd, name, &st, 0)) {
99 is_dir = S_ISDIR(st.st_mode);
100 }
101 }
102
103
2/2
✓ Branch 0 (27→28) taken 50 times.
✓ Branch 1 (27→36) taken 1 times.
51 if (!is_dir) {
104
1/4
✗ Branch 0 (28→29) not taken.
✗ Branch 1 (28→30) not taken.
✗ Branch 2 (28→35) not taken.
✓ Branch 3 (28→36) taken 50 times.
50 switch (type) {
105 case COLLECT_DIRS_ONLY:
106 continue;
107 case COLLECT_ALL:
108 break;
109 case COLLECT_EXECUTABLES:
110 if (!is_executable(dir_fd, name)) {
111 continue;
112 }
113 if (!dlen) {
114 dirprefix = "./";
115 dlen = 2;
116 }
117 break;
118 default:
119 BUG("unhandled FileCollectionType value");
120 }
121 }
122
123 51 ptr_array_append(array, path_joinx(dirprefix, name, is_dir));
124 }
125
126 10 xclosedir(dir);
127 10 return true;
128 }
129
130 10 static void collect_files(EditorState *e, CompletionState *cs, FileCollectionType type)
131 {
132 10 StringView esc = cs->escaped;
133
2/2
✓ Branch 0 (3→4) taken 2 times.
✓ Branch 1 (3→10) taken 8 times.
10 if (strview_has_prefix(&esc, "~/")) {
134 2 CommandRunner runner = normal_mode_cmdrunner(e);
135 2 runner.flags &= ~CMDRUNNER_EXPAND_TILDE_SLASH;
136 2 char *str = parse_command_arg(&runner, esc.data, esc.length);
137 2 const char *slash = xstrrchr(str, '/');
138 2 cs->tilde_expanded = true;
139 2 char *dir = path_dirname(cs->parsed);
140 2 char *dirprefix = path_dirname(str);
141 2 do_collect_files(&cs->completions, dir, dirprefix, slash + 1, type);
142 2 free(dirprefix);
143 2 free(dir);
144 2 free(str);
145 } else {
146 8 const char *slash = strrchr(cs->parsed, '/');
147
2/2
✓ Branch 0 (10→11) taken 5 times.
✓ Branch 1 (10→12) taken 3 times.
8 if (!slash) {
148 5 do_collect_files(&cs->completions, ".", "", cs->parsed, type);
149 } else {
150 3 char *dir = path_dirname(cs->parsed);
151 3 do_collect_files(&cs->completions, dir, dir, slash + 1, type);
152 3 free(dir);
153 }
154 }
155
156
2/2
✓ Branch 0 (15→16) taken 4 times.
✓ Branch 1 (15→18) taken 6 times.
10 if (cs->completions.count == 1) {
157 // Add space if completed string is not a directory
158 4 const char *s = cs->completions.ptrs[0];
159 4 size_t len = strlen(s);
160
1/2
✓ Branch 0 (16→17) taken 4 times.
✗ Branch 1 (16→18) not taken.
4 if (len > 0) {
161 4 cs->add_space_after_single_match = s[len - 1] != '/';
162 }
163 }
164 10 }
165
166 5 void collect_normal_aliases(EditorState *e, PointerArray *a, const char *prefix)
167 {
168 5 collect_hashmap_keys(&e->aliases, a, prefix);
169 5 }
170
171 5 static void collect_bound_keys(const IntMap *bindings, PointerArray *a, const char *prefix)
172 {
173 5 size_t prefix_len = strlen(prefix);
174 5 char keystr[KEYCODE_STR_BUFSIZE];
175
2/2
✓ Branch 0 (9→3) taken 308 times.
✓ Branch 1 (9→10) taken 5 times.
318 for (IntMapIter it = intmap_iter(bindings); intmap_next(&it); ) {
176 308 size_t keylen = keycode_to_str(it.entry->key, keystr);
177
2/2
✓ Branch 0 (4→5) taken 137 times.
✓ Branch 1 (4→8) taken 171 times.
308 if (str_has_strn_prefix(keystr, prefix, prefix_len)) {
178 137 ptr_array_append(a, xmemdup(keystr, keylen + 1));
179 }
180 }
181 5 }
182
183 1 void collect_bound_normal_keys(EditorState *e, PointerArray *a, const char *prefix)
184 {
185 1 collect_bound_keys(&e->normal_mode->key_bindings, a, prefix);
186 1 }
187
188 1 void collect_hl_styles(EditorState *e, PointerArray *a, const char *prefix)
189 {
190 1 const char *dot = strchr(prefix, '.');
191
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1 times.
✗ Branch 2 (3→4) not taken.
✗ Branch 3 (3→6) not taken.
1 if (!dot || dot == prefix || (dot - prefix) > FILETYPE_NAME_MAX) {
192 // No dot found in prefix, or found at offset 0, or buffer too small;
193 // just collect matching highlight names added by the `hi` command
194 1 collect_builtin_styles(a, prefix);
195 1 collect_hashmap_keys(&e->styles.other, a, prefix);
196 2 return;
197 }
198
199 // Copy and null-terminate the filetype part of `prefix` (before the dot)
200 char filetype[FILETYPE_NAME_MAX + 1];
201 size_t ftlen = dot - prefix;
202 memcpy(filetype, prefix, ftlen);
203 filetype[ftlen] = '\0';
204
205 // Find or load the Syntax for `filetype`
206 const Syntax *syn = find_syntax(&e->syntaxes, filetype);
207 if (!syn) {
208 syn = load_syntax_by_filetype(e, filetype);
209 if (!syn) {
210 return;
211 }
212 }
213
214 // Collect all emit names from `syn` that start with the string after
215 // the dot
216 collect_syntax_emit_names(syn, a, dot + 1);
217 }
218
219 3 void collect_compilers(EditorState *e, PointerArray *a, const char *prefix)
220 {
221 3 collect_hashmap_keys(&e->compilers, a, prefix);
222 3 }
223
224 4 void collect_env (
225 PointerArray *a,
226 StringView prefix, // Prefix to match against
227 const char *suffix // Suffix to append to collected strings
228 ) {
229
1/2
✓ Branch 0 (2→3) taken 4 times.
✗ Branch 1 (2→11) not taken.
4 if (strview_memchr(&prefix, '=')) {
230 return;
231 }
232
233 4 size_t sfxlen = strlen(suffix) + 1;
234
2/2
✓ Branch 0 (10→4) taken 666 times.
✓ Branch 1 (10→11) taken 4 times.
670 for (size_t i = 0; environ[i]; i++) {
235 666 const char *var = environ[i];
236 666 size_t varlen = strlen(var);
237
2/2
✓ Branch 0 (5→6) taken 2 times.
✓ Branch 1 (5→9) taken 664 times.
666 if (strn_has_strview_prefix(var, varlen, &prefix)) {
238 2 const char *delim = memchr(var, '=', varlen);
239
1/2
✓ Branch 0 (6→7) taken 2 times.
✗ Branch 1 (6→9) not taken.
2 if (likely(delim && delim != var)) {
240 2 ptr_array_append(a, xmemjoin(var, delim - var, suffix, sfxlen));
241 }
242 }
243 }
244 }
245
246 2 static void complete_alias(EditorState *e, const CommandArgs *a)
247 {
248 2 CompletionState *cs = &e->cmdline.completion;
249
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
2 if (a->nr_args == 0) {
250 1 collect_normal_aliases(e, &cs->completions, cs->parsed);
251
2/4
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→10) not taken.
✓ Branch 2 (5→6) taken 1 times.
✗ Branch 3 (5→10) not taken.
1 } else if (a->nr_args == 1 && cs->parsed[0] == '\0') {
252 1 const char *cmd = find_alias(&e->aliases, a->args[0]);
253
1/2
✓ Branch 0 (7→8) taken 1 times.
✗ Branch 1 (7→10) not taken.
1 if (cmd) {
254 1 ptr_array_append(&cs->completions, xstrdup(cmd));
255 }
256 }
257 2 }
258
259 // Note: `-T` arguments are generated by collect_command_flag_args()
260 // and completed by collect_completions()
261 10 static void complete_bind(EditorState *e, const CommandArgs *a)
262 {
263 // Mask of flags that determine modes (excludes -q)
264 10 CommandFlagSet modemask = cmdargs_flagset_from_str("cnsT");
265
266
3/4
✓ Branch 0 (3→4) taken 8 times.
✓ Branch 1 (3→31) taken 2 times.
✓ Branch 2 (4→5) taken 8 times.
✗ Branch 3 (4→31) not taken.
10 if (u64_popcount(a->flag_set & modemask) > 1 || a->nr_flag_args > 1) {
267 // Don't complete bindings for multiple modes
268 return;
269 }
270
271 8 const ModeHandler *mode;
272
2/2
✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 7 times.
8 if (cmdargs_has_flag(a, 'c')) {
273 1 mode = e->command_mode;
274
2/2
✓ Branch 0 (9→10) taken 3 times.
✓ Branch 1 (9→11) taken 4 times.
7 } else if (cmdargs_has_flag(a, 's')) {
275 3 mode = e->search_mode;
276
2/2
✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→19) taken 3 times.
4 } else if (cmdargs_has_flag(a, 'T')) {
277 1 BUG_ON(a->nr_flag_args != 1);
278 1 BUG_ON(a->flags[0] != 'T');
279 1 mode = get_mode_handler(&e->modes, a->args[0]);
280
1/2
✓ Branch 0 (18→20) taken 1 times.
✗ Branch 1 (18→31) not taken.
1 if (!mode) {
281 return;
282 }
283 } else {
284 3 mode = e->normal_mode;
285 }
286
287 8 const IntMap *key_bindings = &mode->key_bindings;
288 8 CompletionState *cs = &e->cmdline.completion;
289
2/2
✓ Branch 0 (20→21) taken 4 times.
✓ Branch 1 (20→23) taken 4 times.
8 if (a->nr_args == 0) {
290 4 collect_bound_keys(key_bindings, &cs->completions, cs->parsed);
291 4 return;
292 }
293
294
2/4
✓ Branch 0 (23→24) taken 4 times.
✗ Branch 1 (23→31) not taken.
✓ Branch 2 (24→25) taken 4 times.
✗ Branch 3 (24→31) not taken.
4 if (a->nr_args != 1 || cs->parsed[0] != '\0') {
295 return;
296 }
297
298 4 KeyCode key = keycode_from_str(a->args[a->nr_flag_args]);
299
1/2
✓ Branch 0 (26→27) taken 4 times.
✗ Branch 1 (26→31) not taken.
4 if (key == KEY_NONE) {
300 return;
301 }
302
303 4 const CachedCommand *cmd = lookup_binding(key_bindings, key);
304
1/2
✓ Branch 0 (28→29) taken 4 times.
✗ Branch 1 (28→31) not taken.
4 if (!cmd) {
305 return;
306 }
307
308 4 ptr_array_append(&cs->completions, xstrdup(cmd->cmd_str));
309 }
310
311 static void complete_cd(EditorState *e, const CommandArgs* UNUSED_ARG(a))
312 {
313 CompletionState *cs = &e->cmdline.completion;
314 collect_files(e, cs, COLLECT_DIRS_ONLY);
315 if (str_has_prefix("-", cs->parsed)) {
316 if (likely(xgetenv("OLDPWD"))) {
317 ptr_array_append(&cs->completions, xstrdup("-"));
318 }
319 }
320 }
321
322 // Note: `[-ioe]` arguments are generated by collect_command_flag_args()
323 // and completed by collect_completions()
324 4 static void complete_exec(EditorState *e, const CommandArgs *a)
325 {
326 4 CompletionState *cs = &e->cmdline.completion;
327 4 size_t n = a->nr_args;
328 4 collect_files(e, cs, n == 0 ? COLLECT_EXECUTABLES : COLLECT_ALL);
329 4 }
330
331 1 static void complete_compile(EditorState *e, const CommandArgs *a)
332 {
333 1 CompletionState *cs = &e->cmdline.completion;
334 1 size_t n = a->nr_args;
335
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
1 if (n == 0) {
336 1 collect_compilers(e, &cs->completions, cs->parsed);
337 } else {
338 collect_files(e, cs, n == 1 ? COLLECT_EXECUTABLES : COLLECT_ALL);
339 }
340 1 }
341
342 4 static void complete_cursor(EditorState *e, const CommandArgs *a)
343 {
344 4 CompletionState *cs = &e->cmdline.completion;
345 4 size_t n = a->nr_args;
346
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 3 times.
4 if (n == 0) {
347 1 collect_cursor_modes(&cs->completions, cs->parsed);
348
2/2
✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→6) taken 1 times.
3 } else if (n == 1) {
349 2 collect_cursor_types(&cs->completions, cs->parsed);
350
1/2
✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→11) not taken.
1 } else if (n == 2) {
351 1 collect_cursor_colors(&cs->completions, cs->parsed);
352 // Add an example #rrggbb color, to make things more discoverable
353 1 static const char rgb_example[] = "#22AABB";
354
1/2
✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→11) not taken.
1 if (str_has_prefix(rgb_example, cs->parsed)) {
355 1 ptr_array_append(&cs->completions, xstrdup(rgb_example));
356 }
357 }
358 4 }
359
360 3 static void complete_def_mode(EditorState *e, const CommandArgs *a)
361 {
362
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→16) taken 1 times.
3 if (a->nr_args == 0) {
363 return;
364 }
365
366 2 CompletionState *cs = &e->cmdline.completion;
367 2 PointerArray *completions = &cs->completions;
368 2 const char *prefix = cs->parsed;
369 2 size_t prefix_len = strlen(prefix);
370
371
2/2
✓ Branch 0 (14→4) taken 6 times.
✓ Branch 1 (14→15) taken 2 times.
8 for (HashMapIter it = hashmap_iter(&e->modes); hashmap_next(&it); ) {
372 6 const char *name = it.entry->key;
373 6 const ModeHandler *mode = it.entry->value;
374
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 6 times.
6 if (!str_has_strn_prefix(name, prefix, prefix_len)) {
375 continue;
376 }
377
2/2
✓ Branch 0 (6→7) taken 4 times.
✓ Branch 1 (6→8) taken 2 times.
6 if (mode->cmds != &normal_commands) {
378 // Exclude command/search mode
379 4 continue;
380 }
381
2/2
✓ Branch 0 (8→9) taken 1 times.
✓ Branch 1 (8→10) taken 1 times.
2 if (string_array_contains_str(a->args + 1 + a->nr_flag_args, name)) {
382 // Exclude modes already specified in a previous argument
383 1 continue;
384 }
385 1 ptr_array_append(completions, xstrdup(name));
386 }
387 }
388
389 3 static void complete_errorfmt(EditorState *e, const CommandArgs *a)
390 {
391 3 CompletionState *cs = &e->cmdline.completion;
392
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 2 times.
3 if (a->nr_args == 0) {
393 1 collect_compilers(e, &cs->completions, cs->parsed);
394
2/4
✓ Branch 0 (4→5) taken 2 times.
✗ Branch 1 (4→8) not taken.
✓ Branch 2 (6→7) taken 2 times.
✗ Branch 3 (6→8) not taken.
2 } else if (a->nr_args >= 2 && !cmdargs_has_flag(a, 'i')) {
395 2 collect_errorfmt_capture_names(&cs->completions, cs->parsed);
396 }
397 3 }
398
399 1 static void complete_ft(EditorState *e, const CommandArgs *a)
400 {
401 1 CompletionState *cs = &e->cmdline.completion;
402
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
1 if (a->nr_args == 0) {
403 1 collect_ft(&e->filetypes, &cs->completions, cs->parsed);
404 }
405 1 }
406
407 2 static void complete_hi(EditorState *e, const CommandArgs *a)
408 {
409 2 CompletionState *cs = &e->cmdline.completion;
410
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
2 if (a->nr_args == 0) {
411 1 collect_hl_styles(e, &cs->completions, cs->parsed);
412 } else {
413 // TODO: Take into account previous arguments and don't
414 // suggest repeat attributes or excess colors
415 1 collect_colors_and_attributes(&cs->completions, cs->parsed);
416 }
417 2 }
418
419 1 static void complete_include(EditorState *e, const CommandArgs *a)
420 {
421 1 CompletionState *cs = &e->cmdline.completion;
422
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→7) not taken.
1 if (a->nr_args == 0) {
423
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→6) not taken.
1 if (cmdargs_has_flag(a, 'b')) {
424 1 collect_builtin_includes(&cs->completions, cs->parsed);
425 } else {
426 collect_files(e, cs, COLLECT_ALL);
427 }
428 }
429 1 }
430
431 1 static void complete_macro(EditorState *e, const CommandArgs *a)
432 {
433 1 static const char verbs[][8] = {
434 "cancel",
435 "play",
436 "record",
437 "stop",
438 "toggle",
439 };
440
441
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
1 if (a->nr_args != 0) {
442 return;
443 }
444
445 1 CompletionState *cs = &e->cmdline.completion;
446 1 COLLECT_STRINGS(verbs, &cs->completions, cs->parsed);
447 }
448
449 1 static void complete_mode(EditorState *e, const CommandArgs *a)
450 {
451
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
1 if (a->nr_args != 0) {
452 return;
453 }
454
455 1 CompletionState *cs = &e->cmdline.completion;
456 1 collect_hashmap_keys(&e->modes, &cs->completions, cs->parsed);
457 }
458
459 1 static void complete_move_tab(EditorState *e, const CommandArgs *a)
460 {
461
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→4) not taken.
1 if (a->nr_args != 0) {
462 return;
463 }
464
465 1 static const char words[][8] = {"left", "right"};
466 1 CompletionState *cs = &e->cmdline.completion;
467 1 COLLECT_STRINGS(words, &cs->completions, cs->parsed);
468 }
469
470 4 static void complete_open(EditorState *e, const CommandArgs *a)
471 {
472
1/2
✓ Branch 0 (3→4) taken 4 times.
✗ Branch 1 (3→5) not taken.
4 if (!cmdargs_has_flag(a, 't')) {
473 4 collect_files(e, &e->cmdline.completion, COLLECT_ALL);
474 }
475 4 }
476
477 3 static void complete_option(EditorState *e, const CommandArgs *a)
478 {
479 3 CompletionState *cs = &e->cmdline.completion;
480
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→6) taken 2 times.
3 if (a->nr_args == 0) {
481
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→9) not taken.
1 if (!cmdargs_has_flag(a, 'r')) {
482 1 collect_ft(&e->filetypes, &cs->completions, cs->parsed);
483 }
484
2/2
✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 1 times.
2 } else if (a->nr_args & 1) {
485 1 collect_auto_options(&cs->completions, cs->parsed);
486 } else {
487 1 collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed);
488 }
489 3 }
490
491 1 static void complete_save(EditorState *e, const CommandArgs* UNUSED_ARG(a))
492 {
493 1 collect_files(e, &e->cmdline.completion, COLLECT_ALL);
494 1 }
495
496 1 static void complete_quit(EditorState *e, const CommandArgs* UNUSED_ARG(a))
497 {
498 1 CompletionState *cs = &e->cmdline.completion;
499
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→5) not taken.
1 if (str_has_prefix("0", cs->parsed)) {
500 1 ptr_array_append(&cs->completions, xstrdup("0"));
501 }
502
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→8) not taken.
1 if (str_has_prefix("1", cs->parsed)) {
503 1 ptr_array_append(&cs->completions, xstrdup("1"));
504 }
505 1 }
506
507 static void complete_redo(EditorState *e, const CommandArgs* UNUSED_ARG(a))
508 {
509 const Change *change = e->buffer->cur_change;
510 CompletionState *cs = &e->cmdline.completion;
511 for (unsigned long i = 1, n = change->nr_prev; i <= n; i++) {
512 ptr_array_append(&cs->completions, xstrdup(ulong_to_str(i)));
513 }
514 }
515
516 7 static void complete_set(EditorState *e, const CommandArgs *a)
517 {
518 7 CompletionState *cs = &e->cmdline.completion;
519
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→6) taken 6 times.
7 if ((a->nr_args + 1) & 1) {
520 1 bool local = cmdargs_has_flag(a, 'l');
521 1 bool global = cmdargs_has_flag(a, 'g');
522 1 collect_options(&cs->completions, cs->parsed, local, global);
523 } else {
524 6 collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed);
525 }
526 7 }
527
528 2 static void complete_setenv(EditorState *e, const CommandArgs *a)
529 {
530 2 CompletionState *cs = &e->cmdline.completion;
531
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
2 if (a->nr_args == 0) {
532 1 collect_env(&cs->completions, strview_from_cstring(cs->parsed), "");
533
2/4
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→12) not taken.
✓ Branch 2 (5→6) taken 1 times.
✗ Branch 3 (5→12) not taken.
1 } else if (a->nr_args == 1 && cs->parsed[0] == '\0') {
534 1 BUG_ON(!a->args[0]);
535 1 const char *value = getenv(a->args[0]);
536
1/2
✓ Branch 0 (9→10) taken 1 times.
✗ Branch 1 (9→12) not taken.
1 if (value) {
537 1 ptr_array_append(&cs->completions, xstrdup(value));
538 }
539 }
540 2 }
541
542 6 static void complete_show(EditorState *e, const CommandArgs *a)
543 {
544 6 CompletionState *cs = &e->cmdline.completion;
545
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 4 times.
6 if (a->nr_args == 0) {
546 2 collect_show_subcommands(&cs->completions, cs->parsed);
547
1/2
✓ Branch 0 (4→5) taken 4 times.
✗ Branch 1 (4→8) not taken.
4 } else if (a->nr_args == 1) {
548 4 BUG_ON(!a->args[0]);
549 4 collect_show_subcommand_args(e, &cs->completions, a->args[0], cs->parsed);
550 }
551 6 }
552
553 static void complete_tag(EditorState *e, const CommandArgs *a)
554 {
555 CompletionState *cs = &e->cmdline.completion;
556 if (!cmdargs_has_flag(a, 'r')) {
557 BUG_ON(!cs->parsed);
558 StringView prefix = strview_from_cstring(cs->parsed);
559 collect_tags(&e->tagfile, &cs->completions, &prefix);
560 }
561 }
562
563 1 static void complete_toggle(EditorState *e, const CommandArgs *a)
564 {
565 1 CompletionState *cs = &e->cmdline.completion;
566
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→5) not taken.
1 if (a->nr_args == 0) {
567 1 bool global = cmdargs_has_flag(a, 'g');
568 1 collect_toggleable_options(&cs->completions, cs->parsed, global);
569 }
570 1 }
571
572 1 static void complete_wsplit(EditorState *e, const CommandArgs *a)
573 {
574 1 CompletionState *cs = &e->cmdline.completion;
575
2/4
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→7) not taken.
✓ Branch 2 (5→6) taken 1 times.
✗ Branch 3 (5→7) not taken.
1 if (!cmdargs_has_flag(a, 't') && !cmdargs_has_flag(a, 'n')) {
576 1 collect_files(e, cs, COLLECT_ALL);
577 }
578 1 }
579
580 typedef struct {
581 char cmd_name[12];
582 void (*complete)(EditorState *e, const CommandArgs *a);
583 } CompletionHandler;
584
585 static const CompletionHandler completion_handlers[] = {
586 {"alias", complete_alias},
587 {"bind", complete_bind},
588 {"cd", complete_cd},
589 {"compile", complete_compile},
590 {"cursor", complete_cursor},
591 {"def-mode", complete_def_mode},
592 {"errorfmt", complete_errorfmt},
593 {"exec", complete_exec},
594 {"ft", complete_ft},
595 {"hi", complete_hi},
596 {"include", complete_include},
597 {"macro", complete_macro},
598 {"mode", complete_mode},
599 {"move-tab", complete_move_tab},
600 {"open", complete_open},
601 {"option", complete_option},
602 {"quit", complete_quit},
603 {"redo", complete_redo},
604 {"save", complete_save},
605 {"set", complete_set},
606 {"setenv", complete_setenv},
607 {"show", complete_show},
608 {"tag", complete_tag},
609 {"toggle", complete_toggle},
610 {"wsplit", complete_wsplit},
611 };
612
613 24 UNITTEST {
614 24 CHECK_BSEARCH_ARRAY(completion_handlers, cmd_name);
615 // Ensure handlers are kept in sync with renamed/removed commands
616
2/2
✓ Branch 0 (8→4) taken 600 times.
✓ Branch 1 (8→9) taken 24 times.
624 for (size_t i = 0; i < ARRAYLEN(completion_handlers); i++) {
617 600 const char *name = completion_handlers[i].cmd_name;
618
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 600 times.
600 if (!find_normal_command(name)) {
619 BUG("completion handler for non-existent command: \"%s\"", name);
620 }
621 }
622 24 }
623
624 20 static bool can_collect_flags (
625 char **args,
626 size_t argc,
627 size_t nr_flag_args,
628 bool allow_flags_after_nonflags
629 ) {
630
2/2
✓ Branch 0 (2→5) taken 12 times.
✓ Branch 1 (2→11) taken 8 times.
20 if (allow_flags_after_nonflags) {
631
2/2
✓ Branch 0 (5→3) taken 6 times.
✓ Branch 1 (5→12) taken 10 times.
16 for (size_t i = 0; i < argc; i++) {
632
2/2
✓ Branch 0 (3→4) taken 4 times.
✓ Branch 1 (3→12) taken 2 times.
6 if (streq(args[i], "--")) {
633 return false;
634 }
635 }
636 return true;
637 }
638
639
2/2
✓ Branch 0 (11→6) taken 14 times.
✓ Branch 1 (11→12) taken 4 times.
18 for (size_t i = 0, nonflag = 0; i < argc; i++) {
640
2/2
✓ Branch 0 (6→7) taken 6 times.
✓ Branch 1 (6→9) taken 8 times.
14 if (args[i][0] != '-') {
641
2/2
✓ Branch 0 (7→8) taken 3 times.
✓ Branch 1 (7→12) taken 3 times.
6 if (++nonflag > nr_flag_args) {
642 return false;
643 }
644 3 continue;
645 }
646
2/2
✓ Branch 0 (9→10) taken 7 times.
✓ Branch 1 (9→12) taken 1 times.
8 if (streq(args[i], "--")) {
647 return false;
648 }
649 }
650
651 return true;
652 }
653
654 20 static bool collect_command_flags (
655 PointerArray *array,
656 char **args,
657 size_t argc,
658 const Command *cmd,
659 const CommandArgs *a,
660 const char *prefix
661 ) {
662 20 BUG_ON(prefix[0] != '-');
663 20 bool flags_after_nonflags = !(cmd->cmdopts & CMDOPT_NO_FLAGS_AFTER_ARGS);
664
2/2
✓ Branch 0 (4→5) taken 14 times.
✓ Branch 1 (4→21) taken 6 times.
20 if (!can_collect_flags(args, argc, a->nr_flag_args, flags_after_nonflags)) {
665 return false;
666 }
667
668 14 const char *flags = cmd->flags;
669
4/4
✓ Branch 0 (5→6) taken 3 times.
✓ Branch 1 (5→11) taken 11 times.
✓ Branch 2 (6→7) taken 2 times.
✓ Branch 3 (6→11) taken 1 times.
14 if (ascii_isalnum(prefix[1]) && prefix[2] == '\0') {
670
1/2
✓ Branch 0 (7→8) taken 2 times.
✗ Branch 1 (7→10) not taken.
2 if (strchr(flags, prefix[1])) {
671 2 ptr_array_append(array, xmemdup(prefix, 3));
672 }
673 2 return true;
674 }
675
676
2/2
✓ Branch 0 (11→12) taken 11 times.
✓ Branch 1 (11→21) taken 1 times.
12 if (prefix[1] != '\0') {
677 return true;
678 }
679
680 11 char buf[3] = "-";
681
2/2
✓ Branch 0 (20→13) taken 53 times.
✓ Branch 1 (20→21) taken 11 times.
64 for (size_t i = 0; flags[i]; i++) {
682
4/4
✓ Branch 0 (13→14) taken 47 times.
✓ Branch 1 (13→16) taken 6 times.
✓ Branch 2 (15→16) taken 13 times.
✓ Branch 3 (15→17) taken 34 times.
53 if (!ascii_isalnum(flags[i]) || cmdargs_has_flag(a, flags[i])) {
683 19 continue;
684 }
685 34 buf[1] = flags[i];
686 34 ptr_array_append(array, xmemdup(buf, 3));
687 }
688
689 return true;
690 }
691
692 3 static void collect_command_flag_args (
693 EditorState *e,
694 PointerArray *array,
695 const char *prefix,
696 const char *cmd,
697 const CommandArgs *a
698 ) {
699 3 char flag = a->flags[0];
700
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→6) taken 1 times.
3 if (streq(cmd, "bind")) {
701
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 2 times.
2 WARN_ON(flag != 'T');
702 2 collect_modes(&e->modes, array, prefix);
703
1/2
✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→13) not taken.
1 } else if (streq(cmd, "exec")) {
704
2/4
✓ Branch 0 (7→8) taken 1 times.
✗ Branch 1 (7→10) not taken.
✗ Branch 2 (8→9) not taken.
✓ Branch 3 (8→10) taken 1 times.
1 int fd = (flag == 'i') ? 0 : (flag == 'o' ? 1 : 2);
705
1/2
✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 1 times.
1 WARN_ON(fd == 2 && flag != 'e');
706 1 collect_exec_actions(array, prefix, fd);
707 }
708 // TODO: Completions for `open -e` and `save -e`
709 3 }
710
711 // Only recurses for cmdname="repeat" and typically not more than once
712 // NOLINTNEXTLINE(misc-no-recursion)
713 87 static void collect_completions(EditorState *e, char **args, size_t argc)
714 {
715 87 CompletionState *cs = &e->cmdline.completion;
716 87 PointerArray *arr = &cs->completions;
717 87 const char *prefix = cs->parsed;
718
2/2
✓ Branch 0 (2→3) taken 4 times.
✓ Branch 1 (2→7) taken 83 times.
87 if (!argc) {
719 4 collect_normal_commands(arr, prefix);
720 4 collect_normal_aliases(e, arr, prefix);
721 8 return;
722 }
723
724
2/2
✓ Branch 0 (7→5) taken 160 times.
✓ Branch 1 (7→8) taken 83 times.
243 for (size_t i = 0; i < argc; i++) {
725
1/2
✓ Branch 0 (5→6) taken 160 times.
✗ Branch 1 (5→39) not taken.
160 if (!args[i]) {
726 // Embedded NULLs indicate there are multiple commands.
727 // Just return early here and avoid handling this case.
728 return;
729 }
730 }
731
732 83 const char *cmdname = args[0];
733 83 const Command *cmd = find_normal_command(cmdname);
734
1/2
✓ Branch 0 (9→10) taken 83 times.
✗ Branch 1 (9→39) not taken.
83 if (!cmd) {
735 return;
736 }
737
738 83 char **args_copy = copy_string_array(args + 1, argc - 1);
739 83 CommandArgs a = cmdargs_new(args_copy);
740 83 ArgParseError err = do_parse_args(cmd, &a);
741
742
2/2
✓ Branch 0 (12→13) taken 3 times.
✓ Branch 1 (12→15) taken 80 times.
83 if (err == ARGERR_OPTION_ARGUMENT_MISSING) {
743 3 collect_command_flag_args(e, arr, prefix, cmdname, &a);
744 3 goto out;
745 }
746
747 80 bool dash = (prefix[0] == '-');
748 80 if (
749
2/2
✓ Branch 0 (15→16) taken 79 times.
✓ Branch 1 (15→19) taken 1 times.
80 (err != ARGERR_NONE && err != ARGERR_TOO_FEW_ARGUMENTS)
750
5/6
✓ Branch 0 (16→17) taken 6 times.
✓ Branch 1 (16→20) taken 73 times.
✓ Branch 2 (17→18) taken 6 times.
✗ Branch 3 (17→20) not taken.
✓ Branch 4 (18→19) taken 1 times.
✓ Branch 5 (18→21) taken 5 times.
79 || (a.nr_args >= cmd->max_args && cmd->max_args != 0xFF && !dash)
751 ) {
752 2 goto out;
753 }
754
755
4/4
✓ Branch 0 (20→21) taken 15 times.
✓ Branch 1 (20→24) taken 58 times.
✓ Branch 2 (22→23) taken 14 times.
✓ Branch 3 (22→24) taken 6 times.
78 if (dash && collect_command_flags(arr, args + 1, argc - 1, cmd, &a, prefix)) {
756 14 goto out;
757 }
758
759
2/2
✓ Branch 0 (24→25) taken 1 times.
✓ Branch 1 (24→26) taken 63 times.
64 if (cmd->max_args == 0) {
760 1 goto out;
761 }
762
763 63 const CompletionHandler *h = BSEARCH(cmdname, completion_handlers, vstrcmp);
764
2/2
✓ Branch 0 (27→28) taken 60 times.
✓ Branch 1 (27→29) taken 3 times.
63 if (h) {
765 60 h->complete(e, &a);
766
2/2
✓ Branch 0 (29→30) taken 1 times.
✓ Branch 1 (29→31) taken 2 times.
3 } else if (streq(cmdname, "repeat")) {
767
2/2
✓ Branch 0 (31→32) taken 1 times.
✓ Branch 1 (31→33) taken 1 times.
2 if (a.nr_args == 1) {
768 1 collect_normal_commands(arr, prefix);
769
1/2
✗ Branch 0 (33→34) not taken.
✓ Branch 1 (33→35) taken 1 times.
1 } else if (a.nr_args >= 2) {
770 1 collect_completions(e, args + 2, argc - 2);
771 }
772 }
773
774 1 out:
775 83 free_string_array(args_copy);
776 }
777
778 1 static bool is_valid_nonbracketed_var_name(StringView name)
779 {
780 1 AsciiCharType mask = ASCII_ALNUM | ASCII_UNDERSCORE;
781 1 size_t len = name.length;
782
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→6) not taken.
1 return len == 0 || (
783
1/2
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→6) not taken.
1 is_alpha_or_underscore(name.data[0])
784
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→6) not taken.
1 && ascii_type_prefix_length(name.data, len, mask) == len
785 );
786 }
787
788 2439 static int strptrcmp(const void *v1, const void *v2)
789 {
790 2439 const char *const *s1 = v1;
791 2439 const char *const *s2 = v2;
792 2439 return strcmp(*s1, *s2);
793 }
794
795 3 static size_t collect_vars(PointerArray *a, StringView name)
796 {
797 3 const char *suffix = "";
798 3 size_t pos = STRLEN("$");
799 3 strview_remove_prefix(&name, pos);
800
801
2/2
✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→7) taken 1 times.
3 if (strview_remove_matching_prefix(&name, "{")) {
802
1/2
✓ Branch 0 (5→6) taken 2 times.
✗ Branch 1 (5→11) not taken.
2 if (strview_memchr(&name, '}')) {
803 return 0;
804 }
805 2 collect_builtin_config_variables(a, name);
806 2 pos += STRLEN("{");
807 2 suffix = "}";
808
1/2
✓ Branch 0 (7→8) taken 1 times.
✗ Branch 1 (7→11) not taken.
1 } else if (!is_valid_nonbracketed_var_name(name)) {
809 return 0;
810 }
811
812 3 collect_normal_vars(a, name, suffix);
813 3 collect_env(a, name, suffix);
814 3 return pos;
815 }
816
817 89 static void init_completion(EditorState *e, const CommandLine *cmdline)
818 {
819 89 CompletionState *cs = &e->cmdline.completion;
820 89 const CommandRunner runner = normal_mode_cmdrunner(e);
821 89 BUG_ON(cs->orig);
822 89 BUG_ON(runner.e != e);
823 89 BUG_ON(!runner.lookup_alias);
824
825 89 const size_t cmdline_pos = cmdline->pos;
826 89 char *const cmd = string_clone_cstring(&cmdline->buf);
827 89 PointerArray array = PTR_ARRAY_INIT;
828 89 ssize_t semicolon = -1;
829 89 ssize_t completion_pos = -1;
830
831 89 for (size_t pos = 0; true; ) {
832
2/2
✓ Branch 0 (12→10) taken 166 times.
✓ Branch 1 (12→13) taken 258 times.
424 while (ascii_isspace(cmd[pos])) {
833 166 pos++;
834 }
835
836
2/2
✓ Branch 0 (13→14) taken 32 times.
✓ Branch 1 (13→15) taken 226 times.
258 if (pos >= cmdline_pos) {
837 32 completion_pos = cmdline_pos;
838 32 break;
839 }
840
841
1/2
✓ Branch 0 (15→16) taken 226 times.
✗ Branch 1 (15→41) not taken.
226 if (!cmd[pos]) {
842 break;
843 }
844
845
2/2
✓ Branch 0 (16→17) taken 3 times.
✓ Branch 1 (16→19) taken 223 times.
226 if (cmd[pos] == ';') {
846 3 semicolon = array.count;
847 3 ptr_array_append(&array, NULL);
848 3 pos++;
849 3 continue;
850 }
851
852 223 CommandParseError err;
853 223 size_t end = find_end(cmd, pos, &err);
854
3/4
✓ Branch 0 (20→21) taken 223 times.
✗ Branch 1 (20→22) not taken.
✓ Branch 2 (21→22) taken 57 times.
✓ Branch 3 (21→23) taken 166 times.
223 if (err != CMDERR_NONE || end >= cmdline_pos) {
855 57 completion_pos = pos;
856 57 break;
857 }
858
859
2/2
✓ Branch 0 (23→24) taken 88 times.
✓ Branch 1 (23→38) taken 78 times.
166 if (semicolon + 1 == array.count) {
860 88 char *name = xstrslice(cmd, pos, end);
861 88 const char *value = runner.lookup_alias(runner.e, name);
862
1/2
✗ Branch 0 (26→27) not taken.
✓ Branch 1 (26→35) taken 88 times.
88 if (value) {
863 size_t save = array.count;
864 if (parse_commands(&runner, &array, value) != CMDERR_NONE) {
865 for (size_t i = save, n = array.count; i < n; i++) {
866 free(array.ptrs[i]);
867 array.ptrs[i] = NULL;
868 }
869 array.count = save;
870 ptr_array_append(&array, parse_command_arg(&runner, name, end - pos));
871 } else {
872 // Remove NULL
873 array.count--;
874 }
875 } else {
876 88 ptr_array_append(&array, parse_command_arg(&runner, name, end - pos));
877 }
878 88 free(name);
879 } else {
880 78 ptr_array_append(&array, parse_command_arg(&runner, cmd + pos, end - pos));
881 }
882 166 pos = end;
883 }
884
885 89 StringView text = string_view(cmd, cmdline_pos); // Text to be completed
886 89 strview_remove_prefix(&text, completion_pos);
887
888
2/2
✓ Branch 0 (43→44) taken 3 times.
✓ Branch 1 (43→46) taken 86 times.
89 if (strview_has_prefix(&text, "$")) {
889 3 completion_pos += collect_vars(&cs->completions, text);
890 } else {
891 86 cs->escaped = text;
892 86 cs->parsed = parse_command_arg(&runner, text.data, text.length);
893 86 cs->add_space_after_single_match = true;
894 86 size_t count = array.count;
895
2/2
✓ Branch 0 (47→48) taken 83 times.
✓ Branch 1 (47→49) taken 3 times.
86 char **args = count ? (char**)array.ptrs + 1 + semicolon : NULL;
896 83 size_t argc = count ? count - semicolon - 1 : 0;
897 86 collect_completions(e, args, argc);
898 }
899
900 89 ptr_array_free(&array);
901 89 ptr_array_sort(&cs->completions, strptrcmp);
902 89 cs->orig = cmd; // (takes ownership)
903 89 cs->tail = strview_from_cstring(cmd + cmdline_pos);
904 89 cs->head_len = completion_pos;
905 89 }
906
907 95 static void do_complete_command(CommandLine *cmdline)
908 {
909 95 const CompletionState *cs = &cmdline->completion;
910 95 const PointerArray *arr = &cs->completions;
911 95 const StringView middle = strview_from_cstring(arr->ptrs[cs->idx]);
912 95 const StringView tail = cs->tail;
913 95 const size_t head_length = cs->head_len;
914
915 95 String buf = string_new(head_length + tail.length + middle.length + 16);
916 95 string_append_buf(&buf, cs->orig, head_length);
917 95 string_append_escaped_arg_sv(&buf, middle, !cs->tilde_expanded);
918
919 95 bool single_completion = (arr->count == 1);
920
4/4
✓ Branch 0 (5→6) taken 42 times.
✓ Branch 1 (5→8) taken 53 times.
✓ Branch 2 (6→7) taken 41 times.
✓ Branch 3 (6→8) taken 1 times.
95 if (single_completion && cs->add_space_after_single_match) {
921 41 string_append_byte(&buf, ' ');
922 }
923
924 95 size_t pos = buf.len;
925 95 string_append_strview(&buf, &tail);
926 95 cmdline_set_text(cmdline, string_borrow_cstring(&buf));
927 95 cmdline->pos = pos;
928 95 string_free(&buf);
929
930
2/2
✓ Branch 0 (12→13) taken 42 times.
✓ Branch 1 (12→14) taken 53 times.
95 if (single_completion) {
931 42 reset_completion(cmdline);
932 }
933 95 }
934
935 108 void complete_command_next(EditorState *e)
936 {
937 108 CompletionState *cs = &e->cmdline.completion;
938 108 const bool init = !cs->orig;
939
2/2
✓ Branch 0 (2→3) taken 88 times.
✓ Branch 1 (2→4) taken 20 times.
108 if (init) {
940 88 init_completion(e, &e->cmdline);
941 }
942 108 size_t count = cs->completions.count;
943
2/2
✓ Branch 0 (4→5) taken 90 times.
✓ Branch 1 (4→9) taken 18 times.
108 if (!count) {
944 return;
945 }
946
2/2
✓ Branch 0 (5→6) taken 20 times.
✓ Branch 1 (5→8) taken 70 times.
90 if (!init) {
947 20 cs->idx = wrapping_increment(cs->idx, count);
948 }
949 90 do_complete_command(&e->cmdline);
950 }
951
952 5 void complete_command_prev(EditorState *e)
953 {
954 5 CompletionState *cs = &e->cmdline.completion;
955 5 const bool init = !cs->orig;
956
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 4 times.
5 if (init) {
957 1 init_completion(e, &e->cmdline);
958 }
959 5 size_t count = cs->completions.count;
960
1/2
✓ Branch 0 (4→5) taken 5 times.
✗ Branch 1 (4→9) not taken.
5 if (!count) {
961 return;
962 }
963
2/2
✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→8) taken 1 times.
5 if (!init) {
964 4 cs->idx = wrapping_decrement(cs->idx, count);
965 }
966 5 do_complete_command(&e->cmdline);
967 }
968
969 131 void reset_completion(CommandLine *cmdline)
970 {
971 131 CompletionState *cs = &cmdline->completion;
972 131 free(cs->parsed);
973 131 free(cs->orig);
974 131 ptr_array_free(&cs->completions);
975 131 *cs = (CompletionState){.orig = NULL};
976 131 }
977
978 12 void collect_hashmap_keys(const HashMap *map, PointerArray *a, const char *prefix)
979 {
980 12 size_t prefix_len = strlen(prefix);
981
2/2
✓ Branch 0 (8→3) taken 42 times.
✓ Branch 1 (8→9) taken 12 times.
66 for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); ) {
982 42 const char *name = it.entry->key;
983
2/2
✓ Branch 0 (3→4) taken 11 times.
✓ Branch 1 (3→7) taken 31 times.
42 if (str_has_strn_prefix(name, prefix, prefix_len)) {
984 11 ptr_array_append(a, xstrdup(name));
985 }
986 }
987 12 }
988