dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 51.9% 138 / 0 / 266
Functions: 56.8% 21 / 0 / 37
Branches: 39.4% 41 / 46 / 150

src/cmdline.c
Line Branch Exec Source
1 #include <stdlib.h>
2 #include <string.h>
3 #include "cmdline.h"
4 #include "command/args.h"
5 #include "command/macro.h"
6 #include "commands.h"
7 #include "completion.h"
8 #include "copy.h"
9 #include "editor.h"
10 #include "history.h"
11 #include "options.h"
12 #include "regexp.h"
13 #include "search.h"
14 #include "selection.h"
15 #include "terminal/osc52.h"
16 #include "util/ascii.h"
17 #include "util/bsearch.h"
18 #include "util/debug.h"
19 #include "util/log.h"
20 #include "util/utf8.h"
21
22 4 static void cmdline_delete(CommandLine *c)
23 {
24 4 size_t pos = c->pos;
25
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 3 times.
4 if (pos == c->buf.len) {
26 1 return;
27 }
28
29 3 u_get_char(c->buf.buffer, c->buf.len, &pos);
30 3 string_remove(&c->buf, c->pos, pos - c->pos);
31 }
32
33 13 void cmdline_clear(CommandLine *c)
34 {
35 13 string_clear(&c->buf);
36 13 c->pos = 0;
37 13 c->search_pos = NULL;
38 13 }
39
40 11 void cmdline_free(CommandLine *c)
41 {
42 11 cmdline_clear(c);
43 11 string_free(&c->buf);
44 11 free(c->search_text);
45 11 reset_completion(c);
46 11 }
47
48 180 static void set_text(CommandLine *c, StringView text)
49 {
50 180 string_clear(&c->buf);
51 180 c->pos = string_append_strview(&c->buf, text);
52 180 }
53
54 180 void cmdline_set_text(CommandLine *c, const char *text)
55 {
56 180 c->search_pos = NULL;
57 180 set_text(c, strview(text));
58 180 }
59
60 // Reset command completion and history search state (after cursor
61 // position or buffer is changed)
62 20 static bool cmdline_soft_reset(CommandLine *c)
63 {
64 20 c->search_pos = NULL;
65 20 maybe_reset_completion(c);
66 20 return true;
67 }
68
69 2 static bool cmd_bol(EditorState *e, const CommandArgs *a)
70 {
71 2 BUG_ON(a->nr_args);
72 2 e->cmdline.pos = 0;
73 2 return cmdline_soft_reset(&e->cmdline);
74 }
75
76 1 static bool cmd_cancel(EditorState *e, const CommandArgs *a)
77 {
78 1 BUG_ON(a->nr_args);
79 1 CommandLine *c = &e->cmdline;
80 1 cmdline_clear(c);
81 1 pop_input_mode(e);
82 1 reset_completion(c);
83 1 return true;
84 }
85
86 static bool cmd_clear(EditorState *e, const CommandArgs *a)
87 {
88 BUG_ON(a->nr_args);
89 cmdline_clear(&e->cmdline);
90 return true;
91 }
92
93 static bool cmd_copy(EditorState *e, const CommandArgs *a)
94 {
95 static const FlagMapping map[] = {
96 {'b', TCOPY_CLIPBOARD},
97 {'p', TCOPY_PRIMARY},
98 };
99
100 Terminal *term = &e->terminal;
101 TermCopyFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map));
102 bool internal = cmdargs_has_flag(a, 'i') || flags == 0;
103 bool osc52 = flags && (term->features & TFLAG_OSC52_COPY);
104 String *buf = &e->cmdline.buf;
105 size_t len = buf->len;
106
107 if (internal) {
108 record_copy(&e->clipboard, string_clone_cstring(buf), len, false);
109 }
110
111 if (osc52) {
112 bool ok = term_osc52_copy(&term->obuf, strview_from_string(buf), flags);
113 LOG_ERRNO_ON(!ok, "term_osc52_copy");
114 }
115
116 return true;
117 }
118
119 2 static bool cmd_delete(EditorState *e, const CommandArgs *a)
120 {
121 2 BUG_ON(a->nr_args);
122 2 CommandLine *c = &e->cmdline;
123 2 cmdline_delete(c);
124 2 return cmdline_soft_reset(c);
125 }
126
127 1 static bool cmd_delete_eol(EditorState *e, const CommandArgs *a)
128 {
129 1 BUG_ON(a->nr_args);
130 1 CommandLine *c = &e->cmdline;
131 1 c->buf.len = c->pos;
132 1 return cmdline_soft_reset(c);
133 }
134
135 1 static bool cmd_delete_word(EditorState *e, const CommandArgs *a)
136 {
137 1 BUG_ON(a->nr_args);
138 1 CommandLine *c = &e->cmdline;
139 1 const char *buf = c->buf.buffer;
140 1 const size_t len = c->buf.len;
141 1 size_t i = c->pos;
142
143
1/2
✓ Branch 4 → 6 taken 1 time.
✗ Branch 4 → 14 not taken.
1 if (i == len) {
144 return true;
145 }
146
147
3/4
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 5 taken 5 times.
✓ Branch 7 → 9 taken 1 time.
6 while (i < len && is_word_byte(buf[i])) {
148 5 i++;
149 }
150
151
3/4
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 12 not taken.
✓ Branch 11 → 8 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
2 while (i < len && !is_word_byte(buf[i])) {
152 1 i++;
153 }
154
155 1 string_remove(&c->buf, c->pos, i - c->pos);
156 1 return cmdline_soft_reset(c);
157 }
158
159 2 static bool cmd_eol(EditorState *e, const CommandArgs *a)
160 {
161 2 BUG_ON(a->nr_args);
162 2 CommandLine *c = &e->cmdline;
163 2 c->pos = c->buf.len;
164 2 return cmdline_soft_reset(c);
165 }
166
167 2 static bool cmd_erase(EditorState *e, const CommandArgs *a)
168 {
169 2 BUG_ON(a->nr_args);
170 2 CommandLine *c = &e->cmdline;
171
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 7 not taken.
2 if (c->pos > 0) {
172 2 u_prev_char(c->buf.buffer, &c->pos);
173 2 cmdline_delete(c);
174 }
175 2 return cmdline_soft_reset(c);
176 }
177
178 1 static bool cmd_erase_bol(EditorState *e, const CommandArgs *a)
179 {
180 1 BUG_ON(a->nr_args);
181 1 CommandLine *c = &e->cmdline;
182 1 string_remove(&c->buf, 0, c->pos);
183 1 c->pos = 0;
184 1 return cmdline_soft_reset(c);
185 }
186
187 2 static bool cmd_erase_word(EditorState *e, const CommandArgs *a)
188 {
189 2 BUG_ON(a->nr_args);
190 2 CommandLine *c = &e->cmdline;
191 2 size_t i = c->pos;
192
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 15 not taken.
2 if (i == 0) {
193 return true;
194 }
195
196 // open /path/to/file^W => open /path/to/
197
198 // erase whitespace
199
3/4
✓ Branch 5 → 6 taken 3 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 5 taken 1 time.
✓ Branch 6 → 7 taken 2 times.
3 while (i && ascii_isspace(c->buf.buffer[i - 1])) {
200 i--;
201 }
202
203 // erase non-word bytes
204
2/4
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 10 not taken.
✗ Branch 9 → 8 not taken.
✓ Branch 9 → 10 taken 2 times.
2 while (i && !is_word_byte(c->buf.buffer[i - 1])) {
205 i--;
206 }
207
208 // erase word bytes
209
3/4
✓ Branch 11 → 12 taken 12 times.
✗ Branch 11 → 13 not taken.
✓ Branch 12 → 11 taken 10 times.
✓ Branch 12 → 13 taken 2 times.
12 while (i && is_word_byte(c->buf.buffer[i - 1])) {
210 i--;
211 }
212
213 2 string_remove(&c->buf, i, c->pos - i);
214 2 c->pos = i;
215 2 return cmdline_soft_reset(c);
216 }
217
218 static bool cmd_insert(EditorState *e, const CommandArgs *a)
219 {
220 const char *str = a->args[0];
221 size_t len = strlen(str);
222 CommandLine *c = &e->cmdline;
223 string_insert_buf(&c->buf, c->pos, str, len);
224 c->pos += cmdargs_has_flag(a, 'm') ? len : 0;
225 c->search_pos = NULL;
226 return true;
227 }
228
229 static bool do_history_prev(const History *hist, CommandLine *c, bool prefix_search)
230 {
231 if (!c->search_pos) {
232 free(c->search_text);
233 c->search_text = string_clone_cstring(&c->buf);
234 }
235
236 StringView prefix = strview(prefix_search ? c->search_text : "");
237 if (history_search_forward(hist, &c->search_pos, prefix)) {
238 BUG_ON(!c->search_pos);
239 set_text(c, strview(c->search_pos->text));
240 }
241
242 maybe_reset_completion(c);
243 return true;
244 }
245
246 static bool do_history_next(const History *hist, CommandLine *c, bool prefix_search)
247 {
248 if (!c->search_pos) {
249 goto out;
250 }
251
252 StringView prefix = strview(prefix_search ? c->search_text : "");
253 if (history_search_backward(hist, &c->search_pos, prefix)) {
254 BUG_ON(!c->search_pos);
255 set_text(c, strview(c->search_pos->text));
256 } else {
257 set_text(c, strview(c->search_text));
258 c->search_pos = NULL;
259 }
260
261 out:
262 maybe_reset_completion(c);
263 return true;
264 }
265
266 static bool cmd_search_history_next(EditorState *e, const CommandArgs *a)
267 {
268 BUG_ON(a->nr_args);
269 bool prefix_search = !cmdargs_has_flag(a, 'S');
270 return do_history_next(&e->search_history, &e->cmdline, prefix_search);
271 }
272
273 static bool cmd_search_history_prev(EditorState *e, const CommandArgs *a)
274 {
275 BUG_ON(a->nr_args);
276 bool prefix_search = !cmdargs_has_flag(a, 'S');
277 return do_history_prev(&e->search_history, &e->cmdline, prefix_search);
278 }
279
280 static bool cmd_command_history_next(EditorState *e, const CommandArgs *a)
281 {
282 BUG_ON(a->nr_args);
283 bool prefix_search = !cmdargs_has_flag(a, 'S');
284 return do_history_next(&e->command_history, &e->cmdline, prefix_search);
285 }
286
287 static bool cmd_command_history_prev(EditorState *e, const CommandArgs *a)
288 {
289 BUG_ON(a->nr_args);
290 bool prefix_search = !cmdargs_has_flag(a, 'S');
291 return do_history_prev(&e->command_history, &e->cmdline, prefix_search);
292 }
293
294 2 static bool cmd_left(EditorState *e, const CommandArgs *a)
295 {
296 2 BUG_ON(a->nr_args);
297 2 CommandLine *c = &e->cmdline;
298
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1 time.
2 if (c->pos) {
299 1 u_prev_char(c->buf.buffer, &c->pos);
300 }
301 2 return cmdline_soft_reset(c);
302 }
303
304 static bool cmd_paste(EditorState *e, const CommandArgs *a)
305 {
306 const Clipboard *clip = &e->clipboard;
307 const size_t len = clip->len;
308 if (len == 0) {
309 return true;
310 }
311
312 CommandLine *c = &e->cmdline;
313 char newline_replacement = cmdargs_has_flag(a, 'n') ? ';' : ' ';
314 string_insert_buf(&c->buf, c->pos, clip->buf, len);
315 strn_replace_byte(c->buf.buffer + c->pos, len, '\n', newline_replacement);
316 c->pos += cmdargs_has_flag(a, 'm') ? len : 0;
317 return cmdline_soft_reset(c);
318 }
319
320 2 static bool cmd_right(EditorState *e, const CommandArgs *a)
321 {
322 2 BUG_ON(a->nr_args);
323 2 CommandLine *c = &e->cmdline;
324
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
2 if (c->pos < c->buf.len) {
325 2 u_get_char(c->buf.buffer, c->buf.len, &c->pos);
326 }
327 2 return cmdline_soft_reset(c);
328 }
329
330 static bool cmd_toggle(EditorState *e, const CommandArgs *a)
331 {
332 const char *option_name = a->args[0];
333 bool global = cmdargs_has_flag(a, 'g');
334 size_t nr_values = a->nr_args - 1;
335 if (nr_values == 0) {
336 return toggle_option(e, option_name, global, false);
337 }
338
339 char **values = a->args + 1;
340 return toggle_option_values(e, option_name, global, false, values, nr_values);
341 }
342
343 2 static bool cmd_word_bwd(EditorState *e, const CommandArgs *a)
344 {
345 2 BUG_ON(a->nr_args);
346 2 CommandLine *c = &e->cmdline;
347
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1 time.
2 if (c->pos <= 1) {
348 1 c->pos = 0;
349 1 return cmdline_soft_reset(c);
350 }
351
352 1 const char *const buf = c->buf.buffer;
353 1 size_t i = c->pos - 1;
354
355
3/4
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 11 not taken.
✓ Branch 9 → 7 taken 1 time.
✓ Branch 9 → 11 taken 1 time.
2 while (i > 0 && !is_word_byte(buf[i])) {
356 1 i--;
357 }
358
359
3/4
✓ Branch 12 → 13 taken 6 times.
✗ Branch 12 → 14 not taken.
✓ Branch 13 → 10 taken 5 times.
✓ Branch 13 → 14 taken 1 time.
6 while (i > 0 && is_word_byte(buf[i])) {
360 5 i--;
361 }
362
363
1/2
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
1 if (i > 0) {
364 1 i++;
365 }
366
367 1 c->pos = i;
368 1 return cmdline_soft_reset(c);
369 }
370
371 1 static bool cmd_word_fwd(EditorState *e, const CommandArgs *a)
372 {
373 1 BUG_ON(a->nr_args);
374 1 CommandLine *c = &e->cmdline;
375 1 const char *buf = c->buf.buffer;
376 1 const size_t len = c->buf.len;
377 1 size_t i = c->pos;
378
379
3/4
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 5 taken 5 times.
✓ Branch 7 → 9 taken 1 time.
6 while (i < len && is_word_byte(buf[i])) {
380 5 i++;
381 }
382
383
3/4
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 11 → 8 taken 1 time.
✗ Branch 11 → 12 not taken.
2 while (i < len && !is_word_byte(buf[i])) {
384 1 i++;
385 }
386
387 1 c->pos = i;
388 1 return cmdline_soft_reset(c);
389 }
390
391 static bool cmd_complete_next(EditorState *e, const CommandArgs *a)
392 {
393 BUG_ON(a->nr_args);
394 complete_command_next(e);
395 return true;
396 }
397
398 static bool cmd_complete_prev(EditorState *e, const CommandArgs *a)
399 {
400 BUG_ON(a->nr_args);
401 complete_command_prev(e);
402 return true;
403 }
404
405 static bool cmd_direction(EditorState *e, const CommandArgs *a)
406 {
407 BUG_ON(a->nr_args);
408 toggle_search_direction(&e->search);
409 return true;
410 }
411
412 static bool cmd_command_mode_accept(EditorState *e, const CommandArgs *a)
413 {
414 CommandLine *c = &e->cmdline;
415 reset_completion(c);
416 pop_input_mode(e);
417
418 const char *str = string_borrow_cstring(&c->buf);
419 cmdline_clear(c);
420 if (!cmdargs_has_flag(a, 'H') && str[0] != ' ') {
421 // This is done before handle_command() because "command [text]"
422 // can modify the contents of the command-line
423 history_append(&e->command_history, str);
424 }
425
426 e->err.command_name = NULL;
427 return handle_normal_command(e, str, true);
428 }
429
430 static bool cmd_search_mode_accept(EditorState *e, const CommandArgs *a)
431 {
432 CommandLine *c = &e->cmdline;
433 bool add_to_history = !cmdargs_has_flag(a, 'H');
434 const char *pat = NULL;
435
436 if (c->buf.len > 0) {
437 String *s = &c->buf;
438 if (cmdargs_has_flag(a, 'e')) {
439 // Escape the regex; to match as plain text
440 char *original = string_clone_cstring(s);
441 size_t origlen = string_clear(s);
442 string_append_escaped_regex(s, string_view(original, origlen));
443 free(original);
444 }
445
446 pat = string_borrow_cstring(s);
447 search_set_regexp(&e->search, pat);
448 if (add_to_history) {
449 history_append(&e->search_history, pat);
450 }
451 }
452
453 if (e->macro.recording) {
454 macro_search_hook(&e->macro, pat, e->search.reverse, add_to_history);
455 }
456
457 // Unselect, unless selection mode is active
458 view_set_selection_type(e->view, e->view->select_mode);
459
460 SearchCaseSensitivity cs = e->options.case_sensitive_search;
461 e->err.command_name = NULL;
462 bool found = search_next(e->view, &e->search, &e->err, cs);
463 cmdline_clear(c);
464 pop_input_mode(e);
465 return found;
466 }
467
468 // Note that some of the `Command::flags` entries here aren't actually
469 // used in the `cmd` handler and are only included to mirror commands
470 // of the same name in normal mode. This is done as a convenience for
471 // allowing key binding commands like e.g. `bind -cns C-M-c 'copy -bk'`
472 // to be used, instead of needing 2 different commands (with and without
473 // the `-k` flag for normal vs. command/search modes).
474
475 #define CMD(name, flags, min, max, func) \
476 {name, flags, 0, min, max, func}
477
478 static const Command common_cmds[] = {
479 CMD("bol", "st", 0, 0, cmd_bol), // Ignored flags: s, t
480 CMD("cancel", "", 0, 0, cmd_cancel),
481 CMD("clear", "Ii", 0, 0, cmd_clear), // Ignored flags: I, i
482 CMD("copy", "bikp", 0, 0, cmd_copy), // Ignored flag: k
483 CMD("delete", "", 0, 0, cmd_delete),
484 CMD("delete-eol", "n", 0, 0, cmd_delete_eol), // Ignored flag: n
485 CMD("delete-word", "s", 0, 0, cmd_delete_word), // Ignored flag: s
486 CMD("eol", "", 0, 0, cmd_eol),
487 CMD("erase", "", 0, 0, cmd_erase),
488 CMD("erase-bol", "", 0, 0, cmd_erase_bol),
489 CMD("erase-word", "s", 0, 0, cmd_erase_word), // Ignored flag: s
490 CMD("insert", "km", 1, 1, cmd_insert), // Ignored flag: k
491 CMD("left", "", 0, 0, cmd_left),
492 CMD("paste", "acmn", 0, 0, cmd_paste), // Ignored flags: a, c
493 CMD("right", "", 0, 0, cmd_right),
494 CMD("toggle", "gv", 1, -1, cmd_toggle), // Ignored flag: v
495 CMD("word-bwd", "s", 0, 0, cmd_word_bwd), // Ignored flag: s
496 CMD("word-fwd", "s", 0, 0, cmd_word_fwd), // Ignored flag: s
497 };
498
499 static const Command search_cmds[] = {
500 CMD("accept", "eH", 0, 0, cmd_search_mode_accept),
501 CMD("direction", "", 0, 0, cmd_direction),
502 CMD("history-next", "S", 0, 0, cmd_search_history_next),
503 CMD("history-prev", "S", 0, 0, cmd_search_history_prev),
504 };
505
506 static const Command command_cmds[] = {
507 CMD("accept", "H", 0, 0, cmd_command_mode_accept),
508 CMD("complete-next", "", 0, 0, cmd_complete_next),
509 CMD("complete-prev", "", 0, 0, cmd_complete_prev),
510 CMD("history-next", "S", 0, 0, cmd_command_history_next),
511 CMD("history-prev", "S", 0, 0, cmd_command_history_prev),
512 };
513
514 373 static const Command *find_cmd_mode_command(const char *name)
515 {
516 373 const Command *cmd = BSEARCH(name, common_cmds, command_cmp);
517
2/2
✓ Branch 3 → 4 taken 64 times.
✓ Branch 3 → 5 taken 309 times.
373 return cmd ? cmd : BSEARCH(name, command_cmds, command_cmp);
518 }
519
520 360 static const Command *find_search_mode_command(const char *name)
521 {
522 360 const Command *cmd = BSEARCH(name, common_cmds, command_cmp);
523
2/2
✓ Branch 3 → 4 taken 63 times.
✓ Branch 3 → 5 taken 297 times.
360 return cmd ? cmd : BSEARCH(name, search_cmds, command_cmp);
524 }
525
526 const CommandSet cmd_mode_commands = {
527 .lookup = find_cmd_mode_command
528 };
529
530 const CommandSet search_mode_commands = {
531 .lookup = find_search_mode_command
532 };
533