dte test coverage


Directory: ./
File: src/completion.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 466 522 89.3%
Functions: 45 49 91.8%
Branches: 219 310 70.6%

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