dte test coverage


Directory: ./
File: src/cmdline.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 139 262 53.1%
Functions: 21 36 58.3%
Branches: 40 100 40.0%

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