Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <errno.h> | ||
2 | #include <fcntl.h> | ||
3 | #include <glob.h> | ||
4 | #include <signal.h> | ||
5 | #include <stdlib.h> | ||
6 | #include <string.h> | ||
7 | #include <sys/stat.h> | ||
8 | #include <unistd.h> | ||
9 | #include "commands.h" | ||
10 | #include "bind.h" | ||
11 | #include "bookmark.h" | ||
12 | #include "buffer.h" | ||
13 | #include "case.h" | ||
14 | #include "change.h" | ||
15 | #include "cmdline.h" | ||
16 | #include "command/alias.h" | ||
17 | #include "command/args.h" | ||
18 | #include "command/error.h" | ||
19 | #include "command/macro.h" | ||
20 | #include "compiler.h" | ||
21 | #include "config.h" | ||
22 | #include "convert.h" | ||
23 | #include "copy.h" | ||
24 | #include "delete.h" | ||
25 | #include "editor.h" | ||
26 | #include "encoding.h" | ||
27 | #include "exec.h" | ||
28 | #include "file-option.h" | ||
29 | #include "filetype.h" | ||
30 | #include "frame.h" | ||
31 | #include "history.h" | ||
32 | #include "insert.h" | ||
33 | #include "join.h" | ||
34 | #include "load-save.h" | ||
35 | #include "lock.h" | ||
36 | #include "mode.h" | ||
37 | #include "move.h" | ||
38 | #include "msg.h" | ||
39 | #include "regexp.h" | ||
40 | #include "replace.h" | ||
41 | #include "search.h" | ||
42 | #include "selection.h" | ||
43 | #include "shift.h" | ||
44 | #include "show.h" | ||
45 | #include "spawn.h" | ||
46 | #include "syntax/color.h" | ||
47 | #include "syntax/syntax.h" | ||
48 | #include "tag.h" | ||
49 | #include "terminal/cursor.h" | ||
50 | #include "terminal/mode.h" | ||
51 | #include "terminal/osc52.h" | ||
52 | #include "terminal/style.h" | ||
53 | #include "terminal/terminal.h" | ||
54 | #include "ui.h" | ||
55 | #include "util/arith.h" | ||
56 | #include "util/array.h" | ||
57 | #include "util/ascii.h" | ||
58 | #include "util/bit.h" | ||
59 | #include "util/bsearch.h" | ||
60 | #include "util/debug.h" | ||
61 | #include "util/intern.h" | ||
62 | #include "util/log.h" | ||
63 | #include "util/path.h" | ||
64 | #include "util/str-array.h" | ||
65 | #include "util/str-util.h" | ||
66 | #include "util/strtonum.h" | ||
67 | #include "util/time-util.h" | ||
68 | #include "util/xmalloc.h" | ||
69 | #include "util/xsnprintf.h" | ||
70 | #include "view.h" | ||
71 | #include "window.h" | ||
72 | #include "wrap.h" | ||
73 | |||
74 | NOINLINE | ||
75 | 16 | static void do_selection_noinline(View *view, SelectionType sel) | |
76 | { | ||
77 | // Should only be called from do_selection() | ||
78 | 16 | BUG_ON(sel == view->selection); | |
79 | |||
80 |
2/2✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→7) taken 15 times.
|
16 | if (sel == SELECT_NONE) { |
81 | 1 | unselect(view); | |
82 | 1 | return; | |
83 | } | ||
84 | |||
85 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→11) taken 15 times.
|
15 | if (view->selection) { |
86 | ✗ | if (view->selection != sel) { | |
87 | ✗ | view->selection = sel; | |
88 | // TODO: be less brute force about this; only the first/last | ||
89 | // line of the selection can change in this case | ||
90 | ✗ | mark_all_lines_changed(view->buffer); | |
91 | } | ||
92 | ✗ | return; | |
93 | } | ||
94 | |||
95 | 15 | view->sel_so = block_iter_get_offset(&view->cursor); | |
96 | 15 | view->sel_eo = SEL_EO_RECALC; | |
97 | 15 | view->selection = sel; | |
98 | |||
99 | // Need to mark current line changed because cursor might | ||
100 | // move up or down before screen is updated | ||
101 | 15 | view_update_cursor_y(view); | |
102 | 15 | buffer_mark_lines_changed(view->buffer, view->cy, view->cy); | |
103 | } | ||
104 | |||
105 | 134 | static void do_selection(View *view, SelectionType sel) | |
106 | { | ||
107 |
2/2✓ Branch 0 (2→3) taken 118 times.
✓ Branch 1 (2→6) taken 16 times.
|
134 | if (likely(sel == view->selection)) { |
108 | // If `sel` is SELECT_NONE here, it's always equal to select_mode | ||
109 | 118 | BUG_ON(!sel && view->select_mode); | |
110 | return; | ||
111 | } | ||
112 | |||
113 | 16 | do_selection_noinline(view, sel); | |
114 | } | ||
115 | |||
116 | 18 | static char last_flag_or_default(const CommandArgs *a, char def) | |
117 | { | ||
118 | 18 | size_t n = a->nr_flags; | |
119 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→4) taken 4 times.
|
18 | return n ? a->flags[n - 1] : def; |
120 | } | ||
121 | |||
122 | 16 | static char last_flag(const CommandArgs *a) | |
123 | { | ||
124 | 16 | return last_flag_or_default(a, 0); | |
125 | } | ||
126 | |||
127 | 6605 | static bool has_flag(const CommandArgs *a, unsigned char flag) | |
128 | { | ||
129 | 6605 | return cmdargs_has_flag(a, flag); | |
130 | } | ||
131 | |||
132 | 131 | static void handle_selection_flags(View *view, const CommandArgs *a) | |
133 | { | ||
134 | 131 | SelectionType sel; | |
135 |
2/2✓ Branch 0 (3→4) taken 116 times.
✓ Branch 1 (3→9) taken 15 times.
|
131 | if (has_flag(a, 'l')) { |
136 | sel = SELECT_LINES; | ||
137 |
2/2✓ Branch 0 (5→6) taken 10 times.
✓ Branch 1 (5→8) taken 106 times.
|
116 | } else if (has_flag(a, 'c')) { |
138 | 10 | static_assert(SELECT_CHARS < SELECT_LINES); | |
139 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 10 times.
|
10 | sel = MAX(SELECT_CHARS, view->select_mode); |
140 | } else { | ||
141 | 106 | sel = view->select_mode; | |
142 | } | ||
143 | 131 | do_selection(view, sel); | |
144 | 131 | } | |
145 | |||
146 | 170 | static bool cmd_alias(EditorState *e, const CommandArgs *a) | |
147 | { | ||
148 | 170 | const char *const name = a->args[0]; | |
149 | 170 | const char *const cmd = a->args[1]; | |
150 | |||
151 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 169 times.
|
170 | if (unlikely(name[0] == '\0')) { |
152 | 1 | return error_msg(&e->err, "Empty alias name not allowed"); | |
153 | } | ||
154 |
2/2✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→11) taken 168 times.
|
169 | if (unlikely(name[0] == '-')) { |
155 | // Disallowing this simplifies auto-completion for "alias " | ||
156 | 1 | return error_msg(&e->err, "Alias name cannot begin with '-'"); | |
157 | } | ||
158 | |||
159 |
2/2✓ Branch 0 (11→6) taken 1282 times.
✓ Branch 1 (11→12) taken 167 times.
|
1449 | for (size_t i = 0; name[i]; i++) { |
160 | 1282 | unsigned char c = name[i]; | |
161 |
5/6✓ Branch 0 (6→7) taken 99 times.
✓ Branch 1 (6→10) taken 1183 times.
✓ Branch 2 (7→8) taken 1 times.
✓ Branch 3 (7→10) taken 98 times.
✓ Branch 4 (8→9) taken 1 times.
✗ Branch 5 (8→10) not taken.
|
1282 | if (unlikely(!(is_word_byte(c) || c == '-' || c == '?' || c == '!'))) { |
162 | 1 | return error_msg(&e->err, "Invalid byte in alias name: %c (0x%02hhX)", c, c); | |
163 | } | ||
164 | } | ||
165 | |||
166 |
2/2✓ Branch 0 (13→14) taken 1 times.
✓ Branch 1 (13→15) taken 166 times.
|
167 | if (unlikely(find_normal_command(name))) { |
167 | 1 | return error_msg(&e->err, "Can't replace existing command %s with an alias", name); | |
168 | } | ||
169 | |||
170 |
2/2✓ Branch 0 (15→16) taken 164 times.
✓ Branch 1 (15→17) taken 2 times.
|
166 | if (likely(cmd)) { |
171 | 164 | add_alias(&e->aliases, name, cmd); | |
172 | } else { | ||
173 | 2 | remove_alias(&e->aliases, name); | |
174 | } | ||
175 | |||
176 | return true; | ||
177 | } | ||
178 | |||
179 | 1221 | static bool cmd_bind(EditorState *e, const CommandArgs *a) | |
180 | { | ||
181 | 1221 | const char *keystr = a->args[a->nr_flag_args]; | |
182 | 1221 | const char *cmd = a->args[a->nr_flag_args + 1]; | |
183 | 1221 | KeyCode key = keycode_from_str(keystr); | |
184 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→9) taken 1220 times.
|
1221 | if (unlikely(key == KEY_NONE)) { |
185 |
1/2✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→8) taken 1 times.
|
1 | if (has_flag(a, 'q')) { |
186 | ✗ | LOG_INFO("bind -q: dropped invalid key string: %s", keystr); | |
187 | ✗ | return false; | |
188 | } | ||
189 | 1 | return error_msg(&e->err, "invalid key string: %s", keystr); | |
190 | } | ||
191 | |||
192 | 1220 | ModeHandler *modes[10]; | |
193 | 1220 | size_t nmodes = 0; | |
194 | 1220 | static_assert(ARRAYLEN(modes) <= ARRAYLEN(a->flags)); | |
195 |
1/2✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 1220 times.
|
1220 | if (has_flag(a, 'n')) { |
196 | ✗ | modes[nmodes++] = e->normal_mode; | |
197 | } | ||
198 |
2/2✓ Branch 0 (13→14) taken 259 times.
✓ Branch 1 (13→15) taken 961 times.
|
1220 | if (has_flag(a, 'c')) { |
199 | 259 | modes[nmodes++] = e->command_mode; | |
200 | } | ||
201 |
2/2✓ Branch 0 (16→17) taken 266 times.
✓ Branch 1 (16→18) taken 954 times.
|
1220 | if (has_flag(a, 's')) { |
202 | 266 | modes[nmodes++] = e->search_mode; | |
203 | } | ||
204 | |||
205 |
1/2✗ Branch 0 (18→19) not taken.
✓ Branch 1 (18→24) taken 1220 times.
|
1220 | if (unlikely(nmodes + a->nr_flag_args > ARRAYLEN(modes))) { |
206 | // This is already prevented by the ARGERR_TOO_MANY_OPTIONS check | ||
207 | // in do_parse_args(), but since that's only incidental, it's still | ||
208 | // checked here | ||
209 | ✗ | return error_msg(&e->err, "too many modes specified"); | |
210 | } | ||
211 | |||
212 | // Gather pointers to modes specified via `-T modename`. This is done | ||
213 | // separately from adding/removing bindings, partly so that either all | ||
214 | // modes are processed or none are (if the error below is triggered). | ||
215 |
2/2✓ Branch 0 (24→20) taken 1 times.
✓ Branch 1 (24→25) taken 1219 times.
|
1220 | for (size_t i = 0, n = a->nr_flag_args; i < n; i++) { |
216 | 1 | const char *name = a->args[i]; | |
217 | 1 | ModeHandler *mode = get_mode_handler(&e->modes, name); | |
218 |
1/2✓ Branch 0 (21→22) taken 1 times.
✗ Branch 1 (21→23) not taken.
|
1 | if (unlikely(!mode)) { |
219 | 1 | return error_msg(&e->err, "can't bind key in unknown mode '%s'", name); | |
220 | } | ||
221 | ✗ | modes[nmodes++] = mode; | |
222 | } | ||
223 | |||
224 |
2/2✓ Branch 0 (25→26) taken 939 times.
✓ Branch 1 (25→27) taken 280 times.
|
1219 | if (nmodes == 0) { |
225 | // No [-cnsT] flags used; default to normal mode | ||
226 | 939 | modes[nmodes++] = e->normal_mode; | |
227 | } | ||
228 | |||
229 |
1/2✗ Branch 0 (27→30) not taken.
✓ Branch 1 (27→31) taken 1219 times.
|
1219 | if (!cmd) { |
230 | ✗ | for (size_t i = 0; i < nmodes; i++) { | |
231 | ✗ | remove_binding(&modes[i]->key_bindings, key); | |
232 | } | ||
233 | return true; | ||
234 | } | ||
235 | |||
236 | 1219 | CommandRunner runner = cmdrunner(e, NULL); | |
237 |
2/2✓ Branch 0 (35→32) taken 1464 times.
✓ Branch 1 (35→36) taken 1219 times.
|
2683 | for (size_t i = 0; i < nmodes; i++) { |
238 | 1464 | runner.cmds = modes[i]->cmds; | |
239 | 1464 | CachedCommand *cc = cached_command_new(&runner, cmd); | |
240 | 1464 | add_binding(&modes[i]->key_bindings, key, cc); | |
241 | } | ||
242 | |||
243 | return true; | ||
244 | } | ||
245 | |||
246 | 4 | static bool cmd_bof(EditorState *e, const CommandArgs *a) | |
247 | { | ||
248 | 4 | handle_selection_flags(e->view, a); | |
249 | 4 | move_bof(e->view); | |
250 | 4 | return true; | |
251 | } | ||
252 | |||
253 | 7 | static bool cmd_bol(EditorState *e, const CommandArgs *a) | |
254 | { | ||
255 | 7 | SmartBolType type = BOL_SIMPLE; | |
256 |
1/4✗ Branch 0 (3→4) not taken.
✗ Branch 1 (3→5) not taken.
✗ Branch 2 (3→6) not taken.
✓ Branch 3 (3→7) taken 7 times.
|
7 | switch (cmdargs_pick_winning_flag(a, "rst")) { |
257 | ✗ | case 'r': type = BOL_TOGGLE_LR; break; | |
258 | ✗ | case 's': type = BOL_INDENT; break; | |
259 | ✗ | case 't': type = BOL_TOGGLE_RL; break; | |
260 | } | ||
261 | |||
262 | 7 | handle_selection_flags(e->view, a); | |
263 | 7 | move_bol(e->view, type); | |
264 | 7 | return true; | |
265 | } | ||
266 | |||
267 | 1 | static bool cmd_bolsf(EditorState *e, const CommandArgs *a) | |
268 | { | ||
269 | 1 | BUG_ON(a->nr_args); | |
270 | 1 | View *view = e->view; | |
271 | 1 | handle_selection_flags(view, a); | |
272 | |||
273 |
1/2✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→11) not taken.
|
1 | if (!block_iter_bol(&view->cursor)) { |
274 | 1 | long top = view->vy + window_get_scroll_margin(e->window); | |
275 |
1/2✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→10) not taken.
|
1 | if (view->cy > top) { |
276 | 1 | move_up(view, view->cy - top); | |
277 | } else { | ||
278 | ✗ | block_iter_bof(&view->cursor); | |
279 | } | ||
280 | } | ||
281 | |||
282 | 1 | view_reset_preferred_x(view); | |
283 | 1 | return true; | |
284 | } | ||
285 | |||
286 | 1 | static bool cmd_bookmark(EditorState *e, const CommandArgs *a) | |
287 | { | ||
288 | 1 | PointerArray *bookmarks = &e->bookmarks; | |
289 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 1 times.
|
1 | if (has_flag(a, 'r')) { |
290 | ✗ | bookmark_pop(bookmarks, e->window); | |
291 | ✗ | return true; | |
292 | } | ||
293 | |||
294 | 1 | bookmark_push(bookmarks, get_current_file_location(e->view)); | |
295 | 1 | return true; | |
296 | } | ||
297 | |||
298 | 2 | static bool cmd_case(EditorState *e, const CommandArgs *a) | |
299 | { | ||
300 | 2 | change_case(e->view, last_flag_or_default(a, 't')); | |
301 | 2 | return true; | |
302 | } | ||
303 | |||
304 | 4 | static void mark_tabbar_changed(Window *window, void* UNUSED_ARG(data)) | |
305 | { | ||
306 | 4 | window->update_tabbar = true; | |
307 | 4 | } | |
308 | |||
309 | 6 | static bool cmd_cd(EditorState *e, const CommandArgs *a) | |
310 | { | ||
311 | 6 | const char *dir = a->args[0]; | |
312 | 6 | ErrorBuffer *ebuf = &e->err; | |
313 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 5 times.
|
6 | if (unlikely(dir[0] == '\0')) { |
314 | 1 | return error_msg(ebuf, "directory argument cannot be empty"); | |
315 | } | ||
316 | |||
317 |
2/2✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→8) taken 3 times.
|
5 | if (streq(dir, "-")) { |
318 | 2 | dir = xgetenv("OLDPWD"); | |
319 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 2 times.
|
2 | if (!dir) { |
320 | ✗ | return error_msg(ebuf, "OLDPWD not set"); | |
321 | } | ||
322 | } | ||
323 | |||
324 | 5 | char buf[8192]; | |
325 | 5 | const char *cwd = getcwd(buf, sizeof(buf)); | |
326 |
2/2✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→12) taken 4 times.
|
5 | if (chdir(dir) != 0) { |
327 | 1 | return error_msg_errno(ebuf, "changing directory failed"); | |
328 | } | ||
329 | |||
330 |
1/2✓ Branch 0 (12→13) taken 4 times.
✗ Branch 1 (12→17) not taken.
|
4 | if (likely(cwd)) { |
331 | 4 | int r = setenv("OLDPWD", cwd, 1); | |
332 |
1/2✗ Branch 0 (14→15) not taken.
✓ Branch 1 (14→17) taken 4 times.
|
4 | if (unlikely(r != 0)) { |
333 | ✗ | LOG_WARNING("failed to set OLDPWD: %s", strerror(errno)); | |
334 | } | ||
335 | } | ||
336 | |||
337 | 4 | cwd = getcwd(buf, sizeof(buf)); | |
338 |
1/2✓ Branch 0 (18→19) taken 4 times.
✗ Branch 1 (18→23) not taken.
|
4 | if (likely(cwd)) { |
339 | 4 | int r = setenv("PWD", cwd, 1); | |
340 |
1/2✗ Branch 0 (20→21) not taken.
✓ Branch 1 (20→23) taken 4 times.
|
4 | if (unlikely(r != 0)) { |
341 | ✗ | LOG_WARNING("failed to set PWD: %s", strerror(errno)); | |
342 | } | ||
343 | } | ||
344 | |||
345 |
2/2✓ Branch 0 (26→24) taken 136 times.
✓ Branch 1 (26→27) taken 4 times.
|
140 | for (size_t i = 0, n = e->buffers.count; i < n; i++) { |
346 | 136 | Buffer *buffer = e->buffers.ptrs[i]; | |
347 | 136 | buffer_update_short_filename_cwd(buffer, &e->home_dir, cwd); | |
348 | } | ||
349 | |||
350 | 4 | frame_for_each_window(e->root_frame, mark_tabbar_changed, NULL); | |
351 | 4 | e->screen_update |= UPDATE_TERM_TITLE; | |
352 | |||
353 | 4 | bool verbose = has_flag(a, 'v'); | |
354 |
1/6✗ Branch 0 (29→30) not taken.
✓ Branch 1 (29→35) taken 4 times.
✗ Branch 2 (30→31) not taken.
✗ Branch 3 (30→32) not taken.
✗ Branch 4 (33→34) not taken.
✗ Branch 5 (33→35) not taken.
|
4 | return !verbose || info_msg(ebuf, "changed directory to: %s", cwd ? cwd : dir); |
355 | } | ||
356 | |||
357 | ✗ | static bool cmd_center_view(EditorState *e, const CommandArgs *a) | |
358 | { | ||
359 | ✗ | BUG_ON(a->nr_args); | |
360 | ✗ | e->view->force_center = true; | |
361 | ✗ | return true; | |
362 | } | ||
363 | |||
364 | 4 | static bool cmd_clear(EditorState *e, const CommandArgs *a) | |
365 | { | ||
366 | 4 | char iflag = cmdargs_pick_winning_flag(a, "iI"); | |
367 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 4 times.
|
4 | bool auto_indent = iflag ? (iflag == 'i') : e->buffer->options.auto_indent; |
368 | 4 | clear_lines(e->view, auto_indent); | |
369 | 4 | return true; | |
370 | } | ||
371 | |||
372 | 25 | static bool cmd_close(EditorState *e, const CommandArgs *a) | |
373 | { | ||
374 | 25 | bool force = has_flag(a, 'f'); | |
375 |
4/4✓ Branch 0 (3→4) taken 18 times.
✓ Branch 1 (3→13) taken 7 times.
✓ Branch 2 (5→6) taken 2 times.
✓ Branch 3 (5→13) taken 16 times.
|
25 | if (!force && !view_can_close(e->view)) { |
376 | 2 | bool prompt = has_flag(a, 'p'); | |
377 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 1 times.
|
2 | if (!prompt) { |
378 | 1 | return error_msg ( | |
379 | &e->err, | ||
380 | "The buffer is modified; " | ||
381 | "save or run 'close -f' to close without saving" | ||
382 | ); | ||
383 | } | ||
384 | |||
385 |
1/2✓ Branch 0 (9→10) taken 1 times.
✗ Branch 1 (9→11) not taken.
|
1 | if (unlikely(e->flags & EFLAG_HEADLESS)) { |
386 | 1 | return error_msg(&e->err, "-p flag unavailable in headless mode"); | |
387 | } | ||
388 | |||
389 | ✗ | static const char str[] = "Close without saving changes? [y/N]"; | |
390 | ✗ | if (dialog_prompt(e, str, "ny") != 'y') { | |
391 | return false; | ||
392 | } | ||
393 | } | ||
394 | |||
395 | 23 | bool allow_quit = has_flag(a, 'q'); | |
396 |
1/6✗ Branch 0 (14→15) not taken.
✓ Branch 1 (14→18) taken 23 times.
✗ Branch 2 (15→16) not taken.
✗ Branch 3 (15→18) not taken.
✗ Branch 4 (16→17) not taken.
✗ Branch 5 (16→18) not taken.
|
23 | if (allow_quit && e->buffers.count == 1 && e->root_frame->frames.count <= 1) { |
397 | ✗ | e->status = EDITOR_EXIT_OK; | |
398 | ✗ | return true; | |
399 | } | ||
400 | |||
401 | 23 | bool allow_wclose = has_flag(a, 'w'); | |
402 |
1/4✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→23) taken 23 times.
✗ Branch 2 (20→21) not taken.
✗ Branch 3 (20→23) not taken.
|
23 | if (allow_wclose && e->window->views.count <= 1) { |
403 | ✗ | window_close(e->window); | |
404 | ✗ | return true; | |
405 | } | ||
406 | |||
407 | 23 | window_close_current_view(e->window); | |
408 | 23 | set_view(e->window->view); | |
409 | 23 | return true; | |
410 | } | ||
411 | |||
412 | ✗ | static bool cmd_command(EditorState *e, const CommandArgs *a) | |
413 | { | ||
414 | ✗ | const char *text = a->args[0]; | |
415 | ✗ | push_input_mode(e, e->command_mode); | |
416 | ✗ | if (text) { | |
417 | ✗ | cmdline_set_text(&e->cmdline, text); | |
418 | } | ||
419 | ✗ | return true; | |
420 | } | ||
421 | |||
422 | 1 | static bool cmd_compile(EditorState *e, const CommandArgs *a) | |
423 | { | ||
424 | 1 | Compiler *compiler = find_compiler(&e->compilers, a->args[0]); | |
425 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (unlikely(!compiler)) { |
426 | 1 | return error_msg(&e->err, "No such error parser %s", a->args[0]); | |
427 | } | ||
428 | |||
429 | ✗ | bool quiet = has_flag(a, 's'); | |
430 | ✗ | if ((e->flags & EFLAG_HEADLESS) && !quiet) { | |
431 | ✗ | LOG_INFO("automatically added -s flag to compile command (headless mode)"); | |
432 | ✗ | quiet = true; | |
433 | } | ||
434 | |||
435 | ✗ | SpawnContext ctx = { | |
436 | ✗ | .argv = (const char **)a->args + 1, | |
437 | ✗ | .ebuf = &e->err, | |
438 | .quiet = quiet, | ||
439 | }; | ||
440 | |||
441 | ✗ | char abc = cmdargs_pick_winning_flag(a, "ABC"); | |
442 | ✗ | size_t idx = abc ? abc - 'A' : e->options.msg_compile; | |
443 | ✗ | MessageArray *messages = &e->messages[idx]; | |
444 | ✗ | clear_messages(messages); | |
445 | |||
446 | ✗ | yield_terminal(e, quiet); | |
447 | ✗ | bool prompt = has_flag(a, 'p'); | |
448 | ✗ | bool read_stdout = has_flag(a, '1'); | |
449 | ✗ | bool spawned = spawn_compiler(&ctx, compiler, messages, read_stdout); | |
450 | ✗ | resume_terminal(e, quiet, spawned && prompt); | |
451 | |||
452 | ✗ | activate_current_message_save(messages, &e->bookmarks, e->view); | |
453 | ✗ | return spawned; | |
454 | } | ||
455 | |||
456 | 4 | static bool cmd_copy(EditorState *e, const CommandArgs *a) | |
457 | { | ||
458 | 4 | const char *text = a->args[0]; | |
459 | 4 | Terminal *term = &e->terminal; | |
460 | 4 | bool clipboard = has_flag(a, 'b'); | |
461 | 4 | bool primary = has_flag(a, 'p'); | |
462 |
2/4✓ Branch 0 (5→6) taken 4 times.
✗ Branch 1 (5→7) not taken.
✗ Branch 2 (6→7) not taken.
✓ Branch 3 (6→10) taken 4 times.
|
4 | bool internal = has_flag(a, 'i') || !(clipboard || primary); |
463 | ✗ | bool osc52 = (clipboard || primary) && term->features & TFLAG_OSC52_COPY; | |
464 | |||
465 |
1/2✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→19) taken 4 times.
|
4 | if (text) { |
466 | ✗ | size_t len = strlen(text); | |
467 | ✗ | if (internal) { | |
468 | ✗ | record_copy(&e->clipboard, xstrdup(text), len, false); | |
469 | } | ||
470 | ✗ | if (osc52) { | |
471 | ✗ | if (!term_osc52_copy(&term->obuf, text, len, clipboard, primary)) { | |
472 | ✗ | error_msg_errno(&e->err, "OSC 52 copy failed"); | |
473 | } | ||
474 | } | ||
475 | ✗ | return true; | |
476 | } | ||
477 | |||
478 | 4 | const View *view = e->view; | |
479 | 4 | BlockIter bi; | |
480 | 4 | size_t size; | |
481 | 4 | bool line_copy; | |
482 |
1/2✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→22) taken 4 times.
|
4 | if (view->selection) { |
483 | ✗ | SelectionInfo info = init_selection(view); | |
484 | ✗ | size = info.eo - info.so; | |
485 | ✗ | bi = info.si; | |
486 | ✗ | line_copy = (view->selection == SELECT_LINES); | |
487 | } else { | ||
488 | 4 | bi = view->cursor; | |
489 | 4 | block_iter_bol(&bi); | |
490 | 4 | BlockIter tmp = bi; | |
491 | 4 | size = block_iter_eat_line(&tmp); | |
492 | 4 | line_copy = true; | |
493 | } | ||
494 | |||
495 |
1/2✓ Branch 0 (25→26) taken 4 times.
✗ Branch 1 (25→40) not taken.
|
4 | if (unlikely(size == 0)) { |
496 | return true; | ||
497 | } | ||
498 | |||
499 | 4 | char *buf = block_iter_get_bytes(&bi, size); | |
500 |
1/2✗ Branch 0 (27→28) not taken.
✓ Branch 1 (27→31) taken 4 times.
|
4 | if (osc52) { |
501 | ✗ | if (!term_osc52_copy(&term->obuf, buf, size, clipboard, primary)) { | |
502 | ✗ | error_msg_errno(&e->err, "OSC 52 copy failed"); | |
503 | } | ||
504 | } | ||
505 | |||
506 |
1/2✓ Branch 0 (31→32) taken 4 times.
✗ Branch 1 (31→33) not taken.
|
4 | if (internal) { |
507 | // Clipboard takes ownership of `buf` | ||
508 | 4 | record_copy(&e->clipboard, buf, size, line_copy); | |
509 | } else { | ||
510 | ✗ | free(buf); | |
511 | } | ||
512 | |||
513 | // TODO: return false if term_osc52_copy() failed? | ||
514 |
2/4✓ Branch 0 (35→36) taken 4 times.
✗ Branch 1 (35→39) not taken.
✓ Branch 2 (37→38) taken 4 times.
✗ Branch 3 (37→39) not taken.
|
8 | return has_flag(a, 'k') || unselect(e->view); |
515 | } | ||
516 | |||
517 | 3 | static bool cmd_cursor(EditorState *e, const CommandArgs *a) | |
518 | { | ||
519 |
1/2✗ Branch 0 (2→4) not taken.
✓ Branch 1 (2→6) taken 3 times.
|
3 | if (unlikely(a->nr_args == 0)) { |
520 | // Reset all cursor styles | ||
521 | ✗ | for (CursorInputMode m = 0; m < ARRAYLEN(e->cursor_styles); m++) { | |
522 | ✗ | e->cursor_styles[m] = get_default_cursor_style(m); | |
523 | } | ||
524 | ✗ | e->screen_update |= UPDATE_CURSOR_STYLE; | |
525 | ✗ | return true; | |
526 | } | ||
527 | |||
528 | 3 | CursorInputMode mode = cursor_mode_from_str(a->args[0]); | |
529 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 2 times.
|
3 | if (unlikely(mode >= NR_CURSOR_MODES)) { |
530 | 1 | return error_msg(&e->err, "invalid mode argument: %s", a->args[0]); | |
531 | } | ||
532 | |||
533 | 2 | TermCursorStyle style = get_default_cursor_style(mode); | |
534 |
1/2✓ Branch 0 (9→10) taken 2 times.
✗ Branch 1 (9→13) not taken.
|
2 | if (a->nr_args >= 2) { |
535 | 2 | style.type = cursor_type_from_str(a->args[1]); | |
536 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 1 times.
|
2 | if (unlikely(style.type == CURSOR_INVALID)) { |
537 | 1 | return error_msg(&e->err, "invalid cursor type: %s", a->args[1]); | |
538 | } | ||
539 | } | ||
540 | |||
541 |
1/2✓ Branch 0 (13→14) taken 1 times.
✗ Branch 1 (13→17) not taken.
|
1 | if (a->nr_args >= 3) { |
542 | 1 | style.color = cursor_color_from_str(a->args[2]); | |
543 |
1/2✓ Branch 0 (15→16) taken 1 times.
✗ Branch 1 (15→17) not taken.
|
1 | if (unlikely(style.color == COLOR_INVALID)) { |
544 | 1 | return error_msg(&e->err, "invalid cursor color: %s", a->args[2]); | |
545 | } | ||
546 | } | ||
547 | |||
548 | ✗ | e->cursor_styles[mode] = style; | |
549 | ✗ | e->screen_update |= UPDATE_CURSOR_STYLE; | |
550 | ✗ | return true; | |
551 | } | ||
552 | |||
553 | 2 | static bool cmd_cut(EditorState *e, const CommandArgs *a) | |
554 | { | ||
555 | 2 | BUG_ON(a->nr_args); | |
556 | 2 | View *view = e->view; | |
557 | 2 | long preferred_x = view_get_preferred_x(view); | |
558 | 2 | size_t size; | |
559 | 2 | bool line_copy; | |
560 | |||
561 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→8) taken 1 times.
|
2 | if (view->selection) { |
562 | 1 | line_copy = (view->selection == SELECT_LINES); | |
563 | 1 | size = prepare_selection(view); | |
564 | 1 | unselect(view); | |
565 | } else { | ||
566 | 1 | line_copy = true; | |
567 | 1 | block_iter_bol(&view->cursor); | |
568 | 1 | BlockIter tmp = view->cursor; | |
569 | 1 | size = block_iter_eat_line(&tmp); | |
570 | } | ||
571 | |||
572 |
1/2✓ Branch 0 (11→12) taken 2 times.
✗ Branch 1 (11→17) not taken.
|
2 | if (size == 0) { |
573 | return true; | ||
574 | } | ||
575 | |||
576 | 2 | char *buf = block_iter_get_bytes(&view->cursor, size); | |
577 | 2 | record_copy(&e->clipboard, buf, size, line_copy); | |
578 | 2 | buffer_delete_bytes(view, size); | |
579 | |||
580 |
2/2✓ Branch 0 (15→16) taken 1 times.
✓ Branch 1 (15→17) taken 1 times.
|
2 | if (line_copy) { |
581 | 1 | move_to_preferred_x(view, preferred_x); | |
582 | } | ||
583 | |||
584 | return true; | ||
585 | } | ||
586 | |||
587 | 9 | static bool cmd_def_mode(EditorState *e, const CommandArgs *a) | |
588 | { | ||
589 | 9 | const char *name = a->args[0]; | |
590 |
2/2✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 7 times.
|
9 | if (name[0] == '\0' || name[0] == '-' ) { |
591 | 2 | return error_msg(&e->err, "mode name can't be empty or start with '-'"); | |
592 | } | ||
593 | |||
594 | 7 | HashMap *modes = &e->modes; | |
595 |
2/2✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→7) taken 3 times.
|
7 | if (hashmap_get(modes, name)) { |
596 | 4 | return error_msg(&e->err, "mode '%s' already exists", name); | |
597 | } | ||
598 | |||
599 | 3 | PointerArray ftmodes = PTR_ARRAY_INIT; | |
600 |
2/2✓ Branch 0 (17→8) taken 2 times.
✓ Branch 1 (17→18) taken 1 times.
|
3 | for (size_t i = 1, n = a->nr_args; i < n; i++) { |
601 | 2 | const char *ftname = a->args[i]; | |
602 | 2 | ModeHandler *mode = get_mode_handler(modes, ftname); | |
603 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→12) taken 1 times.
|
2 | if (unlikely(!mode)) { |
604 | 1 | ptr_array_free_array(&ftmodes); | |
605 | 1 | return error_msg(&e->err, "unknown fallthrough mode '%s'", ftname); | |
606 | } | ||
607 |
1/2✓ Branch 0 (12→13) taken 1 times.
✗ Branch 1 (12→15) not taken.
|
1 | if (unlikely(mode->cmds != &normal_commands)) { |
608 | // TODO: Support "command" and "search" as fallback modes? | ||
609 | // If implemented, all involved modes need to use the same | ||
610 | // `CommandSet`. | ||
611 | 1 | ptr_array_free_array(&ftmodes); | |
612 | 1 | return error_msg(&e->err, "unable to use '%s' as fall-through mode", ftname); | |
613 | } | ||
614 | ✗ | ptr_array_append(&ftmodes, mode); | |
615 | } | ||
616 | |||
617 | 1 | static const FlagMapping map[] = { | |
618 | {'u', MHF_NO_TEXT_INSERTION}, | ||
619 | {'U', MHF_NO_TEXT_INSERTION | MHF_NO_TEXT_INSERTION_RECURSIVE}, | ||
620 | }; | ||
621 | |||
622 | 1 | ModeHandler *mode = new_mode(modes, xstrdup(name), &normal_commands); | |
623 | 1 | mode->flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)); | |
624 | 1 | mode->fallthrough_modes = ftmodes; | |
625 | 1 | return true; | |
626 | } | ||
627 | |||
628 | 9 | static bool cmd_delete(EditorState *e, const CommandArgs *a) | |
629 | { | ||
630 | 9 | BUG_ON(a->nr_args); | |
631 | 9 | delete_ch(e->view); | |
632 | 9 | return true; | |
633 | } | ||
634 | |||
635 | 2 | static bool cmd_delete_eol(EditorState *e, const CommandArgs *a) | |
636 | { | ||
637 | 2 | View *view = e->view; | |
638 |
1/2✓ Branch 0 (2→3) taken 2 times.
✗ Branch 1 (2→14) not taken.
|
2 | if (view->selection) { |
639 | // TODO: return false? | ||
640 | return true; | ||
641 | } | ||
642 | |||
643 | 2 | bool delete_newline_if_at_eol = has_flag(a, 'n'); | |
644 | 2 | BlockIter bi = view->cursor; | |
645 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→11) taken 2 times.
|
2 | if (delete_newline_if_at_eol) { |
646 | ✗ | CodePoint ch; | |
647 | ✗ | if (block_iter_get_char(&view->cursor, &ch) == 1 && ch == '\n') { | |
648 | ✗ | delete_ch(view); | |
649 | ✗ | return true; | |
650 | } | ||
651 | } | ||
652 | |||
653 | 2 | buffer_delete_bytes(view, block_iter_eol(&bi)); | |
654 | 2 | return true; | |
655 | } | ||
656 | |||
657 | ✗ | static bool cmd_delete_line(EditorState *e, const CommandArgs *a) | |
658 | { | ||
659 | ✗ | BUG_ON(a->nr_args); | |
660 | ✗ | View *view = e->view; | |
661 | ✗ | long x = view_get_preferred_x(view); | |
662 | ✗ | bool whole_lines = true; | |
663 | ✗ | size_t del_count; | |
664 | |||
665 | ✗ | if (view->selection) { | |
666 | ✗ | whole_lines = !has_flag(a, 'S'); | |
667 | ✗ | view->selection = whole_lines ? SELECT_LINES : view->selection; | |
668 | ✗ | del_count = prepare_selection(view); | |
669 | ✗ | unselect(view); | |
670 | } else { | ||
671 | ✗ | block_iter_bol(&view->cursor); | |
672 | ✗ | BlockIter tmp = view->cursor; | |
673 | ✗ | del_count = block_iter_eat_line(&tmp); | |
674 | } | ||
675 | |||
676 | ✗ | buffer_delete_bytes(view, del_count); | |
677 | ✗ | if (whole_lines) { | |
678 | ✗ | move_to_preferred_x(view, x); | |
679 | } | ||
680 | |||
681 | ✗ | return true; | |
682 | } | ||
683 | |||
684 | 1 | static bool cmd_delete_word(EditorState *e, const CommandArgs *a) | |
685 | { | ||
686 | 1 | bool skip_non_word = has_flag(a, 's'); | |
687 | 1 | BlockIter bi = e->view->cursor; | |
688 | 1 | buffer_delete_bytes(e->view, word_fwd(&bi, skip_non_word)); | |
689 | 1 | return true; | |
690 | } | ||
691 | |||
692 | 11 | static bool cmd_down(EditorState *e, const CommandArgs *a) | |
693 | { | ||
694 | 11 | handle_selection_flags(e->view, a); | |
695 | 11 | move_down(e->view, 1); | |
696 | 11 | return true; | |
697 | } | ||
698 | |||
699 | 6 | static bool cmd_eof(EditorState *e, const CommandArgs *a) | |
700 | { | ||
701 | 6 | handle_selection_flags(e->view, a); | |
702 | 6 | move_eof(e->view); | |
703 | 6 | return true; | |
704 | } | ||
705 | |||
706 | 6 | static bool cmd_eol(EditorState *e, const CommandArgs *a) | |
707 | { | ||
708 | 6 | handle_selection_flags(e->view, a); | |
709 | 6 | move_eol(e->view); | |
710 | 6 | return true; | |
711 | } | ||
712 | |||
713 | 1 | static bool cmd_eolsf(EditorState *e, const CommandArgs *a) | |
714 | { | ||
715 | 1 | BUG_ON(a->nr_args); | |
716 | 1 | View *view = e->view; | |
717 | 1 | handle_selection_flags(view, a); | |
718 | |||
719 |
1/2✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→11) not taken.
|
1 | if (!block_iter_eol(&view->cursor)) { |
720 | 1 | Window *window = e->window; | |
721 | 1 | long margin = window_get_scroll_margin(window); | |
722 | 1 | long bottom = view->vy + window->edit_h - 1 - margin; | |
723 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 1 times.
|
1 | if (view->cy < bottom) { |
724 | ✗ | move_down(view, bottom - view->cy); | |
725 | } else { | ||
726 | 1 | block_iter_eof(&view->cursor); | |
727 | } | ||
728 | } | ||
729 | |||
730 | 1 | view_reset_preferred_x(view); | |
731 | 1 | return true; | |
732 | } | ||
733 | |||
734 | 6 | static bool cmd_erase(EditorState *e, const CommandArgs *a) | |
735 | { | ||
736 | 6 | BUG_ON(a->nr_args); | |
737 | 6 | erase(e->view); | |
738 | 6 | return true; | |
739 | } | ||
740 | |||
741 | 1 | static bool cmd_erase_bol(EditorState *e, const CommandArgs *a) | |
742 | { | ||
743 | 1 | BUG_ON(a->nr_args); | |
744 | 1 | buffer_erase_bytes(e->view, block_iter_bol(&e->view->cursor)); | |
745 | 1 | return true; | |
746 | } | ||
747 | |||
748 | 2 | static bool cmd_erase_word(EditorState *e, const CommandArgs *a) | |
749 | { | ||
750 | 2 | View *view = e->view; | |
751 | 2 | bool skip_non_word = has_flag(a, 's'); | |
752 | 2 | buffer_erase_bytes(view, word_bwd(&view->cursor, skip_non_word)); | |
753 | 2 | return true; | |
754 | } | ||
755 | |||
756 | 212 | static bool cmd_errorfmt(EditorState *e, const CommandArgs *a) | |
757 | { | ||
758 | 212 | BUG_ON(a->nr_args == 0 || a->nr_args > 2 + ERRORFMT_CAPTURE_MAX); | |
759 | 212 | const char *name = a->args[0]; | |
760 |
2/2✓ Branch 0 (4→5) taken 21 times.
✓ Branch 1 (4→7) taken 191 times.
|
212 | if (a->nr_args == 1) { |
761 | 21 | remove_compiler(&e->compilers, name); | |
762 | 21 | return true; | |
763 | } | ||
764 | |||
765 | 191 | static_assert(NR_ERRFMT_INDICES == 4); | |
766 | 191 | size_t max_idx = 0; | |
767 | 191 | int8_t indices[NR_ERRFMT_INDICES] = { | |
768 | [ERRFMT_FILE] = -1, | ||
769 | [ERRFMT_LINE] = -1, | ||
770 | [ERRFMT_COLUMN] = -1, | ||
771 | [ERRFMT_MESSAGE] = 0, | ||
772 | }; | ||
773 | |||
774 |
2/2✓ Branch 0 (15→8) taken 227 times.
✓ Branch 1 (15→16) taken 190 times.
|
417 | for (size_t i = 0, n = a->nr_args - 2; i < n; i++) { |
775 | 227 | char *cap_name = a->args[i + 2]; | |
776 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 227 times.
|
227 | if (streq(cap_name, "_")) { |
777 | ✗ | continue; | |
778 | } | ||
779 | 227 | ssize_t cap_idx = errorfmt_capture_name_to_index(cap_name); | |
780 |
2/2✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→13) taken 226 times.
|
227 | if (unlikely(cap_idx < 0)) { |
781 | 1 | return error_msg(&e->err, "unknown substring name %s", cap_name); | |
782 | } | ||
783 | 226 | max_idx = i + 1; | |
784 | 226 | indices[cap_idx] = max_idx; | |
785 | } | ||
786 | |||
787 | |||
788 | 190 | const char *pattern = a->args[1]; | |
789 | 190 | regex_t re; | |
790 |
1/2✓ Branch 0 (17→18) taken 190 times.
✗ Branch 1 (17→24) not taken.
|
190 | if (unlikely(!regexp_compile(&e->err, &re, pattern, 0))) { |
791 | return false; | ||
792 | } | ||
793 | |||
794 |
2/2✓ Branch 0 (18→19) taken 1 times.
✓ Branch 1 (18→21) taken 189 times.
|
190 | if (unlikely(max_idx > re.re_nsub)) { |
795 | 1 | regfree(&re); | |
796 | 1 | return error_msg(&e->err, "invalid substring count"); | |
797 | } | ||
798 | |||
799 | 189 | bool ignore = has_flag(a, 'i'); | |
800 | 189 | add_error_fmt(&e->compilers, name, pattern, &re, indices, ignore); | |
801 | 189 | return true; | |
802 | } | ||
803 | |||
804 | 18 | static bool cmd_exec(EditorState *e, const CommandArgs *a) | |
805 | { | ||
806 | 18 | ExecAction actions[3] = {EXEC_TTY, EXEC_TTY, EXEC_TTY}; | |
807 | 18 | ExecFlags exec_flags = 0; | |
808 | 18 | bool lflag = false; | |
809 | 18 | bool move_after_insert = false; | |
810 | |||
811 |
2/2✓ Branch 0 (18→3) taken 36 times.
✓ Branch 1 (18→19) taken 15 times.
|
51 | for (size_t i = 0, n = a->nr_flags, argidx = 0, fd; i < n; i++) { |
812 |
4/10✓ Branch 0 (3→4) taken 4 times.
✓ Branch 1 (3→5) taken 6 times.
✗ Branch 2 (3→6) not taken.
✓ Branch 3 (3→7) taken 18 times.
✗ Branch 4 (3→8) not taken.
✗ Branch 5 (3→9) not taken.
✗ Branch 6 (3→10) not taken.
✗ Branch 7 (3→11) not taken.
✗ Branch 8 (3→12) not taken.
✓ Branch 9 (3→13) taken 8 times.
|
36 | switch (a->flags[i]) { |
813 | case 'e': fd = STDERR_FILENO; break; | ||
814 | 4 | case 'i': fd = STDIN_FILENO; break; | |
815 | 6 | case 'o': fd = STDOUT_FILENO; break; | |
816 | ✗ | case 'p': exec_flags |= EXECFLAG_PROMPT; continue; | |
817 | 18 | case 's': exec_flags |= EXECFLAG_QUIET; continue; | |
818 | ✗ | case 't': exec_flags &= ~EXECFLAG_QUIET; continue; | |
819 | ✗ | case 'l': lflag = true; continue; | |
820 | ✗ | case 'm': move_after_insert = true; continue; | |
821 | ✗ | case 'n': exec_flags |= EXECFLAG_STRIP_NL; continue; | |
822 | ✗ | default: BUG("unexpected flag"); return false; | |
823 | } | ||
824 | 18 | const char *action_name = a->args[argidx++]; | |
825 | 18 | ExecAction action = lookup_exec_action(action_name, fd); | |
826 |
2/2✓ Branch 0 (14→15) taken 3 times.
✓ Branch 1 (14→16) taken 15 times.
|
18 | if (unlikely(action == EXEC_INVALID)) { |
827 | 3 | return error_msg(&e->err, "invalid action for -%c: '%s'", a->flags[i], action_name); | |
828 | } | ||
829 | 15 | actions[fd] = action; | |
830 | } | ||
831 | |||
832 |
1/4✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→22) taken 15 times.
✗ Branch 2 (20→21) not taken.
✗ Branch 3 (20→22) not taken.
|
15 | if (lflag && actions[STDIN_FILENO] == EXEC_BUFFER) { |
833 | // For compat. with old "filter" and "pipe-to" commands | ||
834 | ✗ | actions[STDIN_FILENO] = EXEC_LINE; | |
835 | } | ||
836 | |||
837 |
2/4✓ Branch 0 (22→23) taken 15 times.
✗ Branch 1 (22→26) not taken.
✗ Branch 2 (23→24) not taken.
✓ Branch 3 (23→26) taken 15 times.
|
15 | if ((e->flags & EFLAG_HEADLESS) && !(exec_flags & EXECFLAG_QUIET)) { |
838 | ✗ | LOG_INFO("automatically added -s flag to exec command (headless mode)"); | |
839 | ✗ | exec_flags |= EXECFLAG_QUIET; | |
840 | } | ||
841 | |||
842 | 15 | const char **argv = (const char **)a->args + a->nr_flag_args; | |
843 | 15 | ssize_t outlen = handle_exec(e, argv, actions, exec_flags); | |
844 |
2/2✓ Branch 0 (27→28) taken 10 times.
✓ Branch 1 (27→29) taken 5 times.
|
15 | if (outlen <= 0) { |
845 | 10 | return outlen == 0; | |
846 | } | ||
847 | |||
848 |
1/4✗ Branch 0 (29→30) not taken.
✓ Branch 1 (29→32) taken 5 times.
✗ Branch 2 (30→31) not taken.
✗ Branch 3 (30→32) not taken.
|
5 | if (move_after_insert && actions[STDOUT_FILENO] == EXEC_BUFFER) { |
849 | ✗ | block_iter_skip_bytes(&e->view->cursor, outlen); | |
850 | } | ||
851 | return true; | ||
852 | } | ||
853 | |||
854 | 16 | static bool cmd_ft(EditorState *e, const CommandArgs *a) | |
855 | { | ||
856 | 16 | ErrorBuffer *ebuf = &e->err; | |
857 | 16 | char **args = a->args; | |
858 | 16 | const char *filetype = args[0]; | |
859 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 15 times.
|
16 | if (unlikely(!is_valid_filetype_name(filetype))) { |
860 | 1 | return error_msg(ebuf, "Invalid filetype name: '%s'", filetype); | |
861 | } | ||
862 | |||
863 | 15 | FileDetectionType dt = FT_EXTENSION; | |
864 |
2/5✗ Branch 0 (4→5) not taken.
✗ Branch 1 (4→6) not taken.
✓ Branch 2 (4→7) taken 14 times.
✗ Branch 3 (4→8) not taken.
✓ Branch 4 (4→9) taken 1 times.
|
15 | switch (last_flag(a)) { |
865 | ✗ | case 'b': dt = FT_BASENAME; break; | |
866 | ✗ | case 'c': dt = FT_CONTENT; break; | |
867 | 14 | case 'f': dt = FT_FILENAME; break; | |
868 | ✗ | case 'i': dt = FT_INTERPRETER; break; | |
869 | } | ||
870 | |||
871 | 15 | PointerArray *filetypes = &e->filetypes; | |
872 | 15 | size_t nfailed = 0; | |
873 |
2/2✓ Branch 0 (14→10) taken 15 times.
✓ Branch 1 (14→15) taken 15 times.
|
30 | for (size_t i = 1, n = a->nr_args; i < n; i++) { |
874 |
1/2✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→13) taken 15 times.
|
15 | if (!add_filetype(filetypes, filetype, args[i], dt, ebuf)) { |
875 | ✗ | nfailed++; | |
876 | } | ||
877 | } | ||
878 | |||
879 | 15 | return nfailed == 0; | |
880 | } | ||
881 | |||
882 | 648 | static bool cmd_hi(EditorState *e, const CommandArgs *a) | |
883 | { | ||
884 |
2/2✓ Branch 0 (2→3) taken 7 times.
✓ Branch 1 (2→5) taken 641 times.
|
648 | if (unlikely(a->nr_args == 0)) { |
885 | 7 | exec_builtin_color_reset(e); | |
886 | 7 | goto update; | |
887 | } | ||
888 | |||
889 | 641 | char **strs = a->args + 1; | |
890 | 641 | size_t strs_len = a->nr_args - 1; | |
891 | 641 | TermStyle style; | |
892 | 641 | ssize_t n = parse_term_style(&style, strs, strs_len); | |
893 |
2/2✓ Branch 0 (6→7) taken 2 times.
✓ Branch 1 (6→12) taken 639 times.
|
641 | if (unlikely(n != strs_len)) { |
894 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 1 times.
|
2 | if (n < 0) { |
895 | 1 | return error_msg(&e->err, "too many colors"); | |
896 | } | ||
897 | 1 | BUG_ON(n > strs_len); | |
898 | 1 | return error_msg(&e->err, "invalid color or attribute: '%s'", strs[n]); | |
899 | } | ||
900 | |||
901 | 639 | TermFeatureFlags features = e->terminal.features; | |
902 | 639 | bool true_color = !!(features & TFLAG_TRUE_COLOR); | |
903 |
1/4✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→14) taken 639 times.
✗ Branch 2 (13→14) not taken.
✗ Branch 3 (13→15) not taken.
|
639 | bool optimize = (true_color && e->options.optimize_true_color); |
904 | 639 | int32_t fg = color_to_nearest(style.fg, features, optimize); | |
905 | 639 | int32_t bg = color_to_nearest(style.bg, features, optimize); | |
906 |
7/8✓ Branch 0 (17→18) taken 639 times.
✗ Branch 1 (17→22) not taken.
✓ Branch 2 (19→20) taken 168 times.
✓ Branch 3 (19→22) taken 471 times.
✓ Branch 4 (20→21) taken 44 times.
✓ Branch 5 (20→26) taken 124 times.
✓ Branch 6 (21→22) taken 20 times.
✓ Branch 7 (21→26) taken 24 times.
|
639 | if (!true_color && has_flag(a, 'c') && (fg != style.fg || bg != style.bg)) { |
907 | return true; | ||
908 | } | ||
909 | |||
910 | 491 | style.fg = fg; | |
911 | 491 | style.bg = bg; | |
912 | 491 | set_highlight_style(&e->styles, a->args[0], &style); | |
913 | |||
914 | 498 | update: | |
915 | // Don't call update_all_syntax_styles() needlessly; it's called | ||
916 | // right after config has been loaded | ||
917 |
1/2✗ Branch 0 (23→24) not taken.
✓ Branch 1 (23→26) taken 498 times.
|
498 | if (e->status != EDITOR_INITIALIZING) { |
918 | ✗ | update_all_syntax_styles(&e->syntaxes, &e->styles); | |
919 | ✗ | e->screen_update |= UPDATE_ALL_WINDOWS; | |
920 | } | ||
921 | |||
922 | return true; | ||
923 | } | ||
924 | |||
925 | 96 | static bool cmd_include(EditorState *e, const CommandArgs *a) | |
926 | { | ||
927 | 96 | ConfigFlags flags = has_flag(a, 'q') ? CFG_NOFLAGS : CFG_MUST_EXIST; | |
928 |
2/2✓ Branch 0 (4→5) taken 29 times.
✓ Branch 1 (4→6) taken 67 times.
|
96 | if (has_flag(a, 'b')) { |
929 | 29 | flags |= CFG_BUILTIN; | |
930 | } | ||
931 | |||
932 | 96 | int err = read_normal_config(e, a->args[0], flags); | |
933 | // TODO: Clean up read_normal_config() so this can be simplified to `err == 0` | ||
934 |
5/6✓ Branch 0 (7→8) taken 2 times.
✓ Branch 1 (7→11) taken 94 times.
✓ Branch 2 (8→9) taken 1 times.
✓ Branch 3 (8→10) taken 1 times.
✓ Branch 4 (9→10) taken 1 times.
✗ Branch 5 (9→11) not taken.
|
96 | return err == 0 || (err == ENOENT && !(flags & CFG_MUST_EXIST)); |
935 | } | ||
936 | |||
937 | 84 | static bool cmd_insert(EditorState *e, const CommandArgs *a) | |
938 | { | ||
939 | 84 | const char *str = a->args[0]; | |
940 |
2/2✓ Branch 0 (3→6) taken 20 times.
✓ Branch 1 (3→7) taken 64 times.
|
84 | if (has_flag(a, 'k')) { |
941 |
2/2✓ Branch 0 (6→4) taken 142 times.
✓ Branch 1 (6→10) taken 20 times.
|
162 | for (size_t i = 0; str[i]; i++) { |
942 | 142 | insert_ch(e->view, str[i]); | |
943 | } | ||
944 | return true; | ||
945 | } | ||
946 | |||
947 | 64 | bool move_after = has_flag(a, 'm'); | |
948 | 64 | insert_text(e->view, str, strlen(str), move_after); | |
949 | 64 | return true; | |
950 | } | ||
951 | |||
952 | 16 | static bool cmd_join(EditorState *e, const CommandArgs *a) | |
953 | { | ||
954 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→4) taken 2 times.
|
16 | const char *delim = a->args[0] ? a->args[0] : " "; |
955 | 16 | join_lines(e->view, delim, strlen(delim)); | |
956 | 16 | return true; | |
957 | } | ||
958 | |||
959 | 16 | static bool cmd_left(EditorState *e, const CommandArgs *a) | |
960 | { | ||
961 | 16 | handle_selection_flags(e->view, a); | |
962 | 16 | move_cursor_left(e->view); | |
963 | 16 | return true; | |
964 | } | ||
965 | |||
966 | 4 | static bool cmd_line(EditorState *e, const CommandArgs *a) | |
967 | { | ||
968 | 4 | const char *str = a->args[0]; | |
969 | 4 | size_t line, column; | |
970 |
2/2✓ Branch 0 (3→4) taken 3 times.
✓ Branch 1 (3→5) taken 1 times.
|
4 | if (unlikely(!str_to_xfilepos(str, &line, &column))) { |
971 | 3 | return error_msg(&e->err, "Invalid line number: %s", str); | |
972 | } | ||
973 | |||
974 | 1 | View *view = e->view; | |
975 | 1 | long x = view_get_preferred_x(view); | |
976 | 1 | handle_selection_flags(view, a); | |
977 | |||
978 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 1 times.
|
1 | if (column >= 1) { |
979 | // Column was specified; move to exact position | ||
980 | ✗ | move_to_filepos(view, line, column); | |
981 | } else { | ||
982 | // Column was omitted; move to line while preserving current column | ||
983 | 1 | move_to_line(view, line); | |
984 | 1 | move_to_preferred_x(view, x); | |
985 | } | ||
986 | |||
987 | return true; | ||
988 | } | ||
989 | |||
990 | 2 | static bool cmd_macro(EditorState *e, const CommandArgs *a) | |
991 | { | ||
992 | 2 | MacroRecorder *m = &e->macro; | |
993 | 2 | const char *action = a->args[0]; | |
994 | |||
995 |
3/4✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
✗ Branch 2 (3→4) not taken.
✓ Branch 3 (3→9) taken 1 times.
|
2 | if (streq(action, "play") || streq(action, "run")) { |
996 |
2/2✓ Branch 0 (8→5) taken 9 times.
✓ Branch 1 (8→30) taken 1 times.
|
10 | for (size_t i = 0, n = m->macro.count; i < n; i++) { |
997 | 9 | const char *cmd_str = m->macro.ptrs[i]; | |
998 |
1/2✓ Branch 0 (6→7) taken 9 times.
✗ Branch 1 (6→30) not taken.
|
9 | if (!handle_normal_command(e, cmd_str, false)) { |
999 | return false; | ||
1000 | } | ||
1001 | } | ||
1002 | return true; | ||
1003 | } | ||
1004 | |||
1005 |
1/2✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→12) taken 1 times.
|
1 | if (streq(action, "toggle")) { |
1006 | ✗ | action = m->recording ? "stop" : "record"; | |
1007 | } | ||
1008 | |||
1009 |
1/2✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→17) taken 1 times.
|
1 | if (streq(action, "record")) { |
1010 | ✗ | bool r = macro_record(m); | |
1011 | ✗ | return info_msg(&e->err, "%s", r ? "Recording macro" : "Already recording"); | |
1012 | } | ||
1013 | |||
1014 |
1/2✗ Branch 0 (17→18) not taken.
✓ Branch 1 (17→24) taken 1 times.
|
1 | if (streq(action, "stop")) { |
1015 | ✗ | if (!macro_stop(m)) { | |
1016 | ✗ | return info_msg(&e->err, "Not recording"); | |
1017 | } | ||
1018 | ✗ | size_t count = m->macro.count; | |
1019 | ✗ | const char *plural = (count != 1) ? "s" : ""; | |
1020 | ✗ | return info_msg(&e->err, "Macro recording stopped; %zu command%s saved", count, plural); | |
1021 | } | ||
1022 | |||
1023 |
1/2✗ Branch 0 (24→25) not taken.
✓ Branch 1 (24→29) taken 1 times.
|
1 | if (streq(action, "cancel")) { |
1024 | ✗ | bool r = macro_cancel(m); | |
1025 | ✗ | return info_msg(&e->err, "%s", r ? "Macro recording cancelled" : "Not recording"); | |
1026 | } | ||
1027 | |||
1028 | 1 | return error_msg(&e->err, "Unknown action '%s'", action); | |
1029 | } | ||
1030 | |||
1031 | 5 | static bool cmd_match_bracket(EditorState *e, const CommandArgs *a) | |
1032 | { | ||
1033 | 5 | BUG_ON(a->nr_args); | |
1034 | 5 | View *view = e->view; | |
1035 | 5 | CodePoint cursor_char; | |
1036 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 4 times.
|
5 | if (!block_iter_get_char(&view->cursor, &cursor_char)) { |
1037 | 1 | return error_msg(&e->err, "No character under cursor"); | |
1038 | } | ||
1039 | |||
1040 | 4 | CodePoint target = cursor_char; | |
1041 | 4 | BlockIter bi = view->cursor; | |
1042 | 4 | size_t level = 0; | |
1043 | 4 | CodePoint u = 0; | |
1044 | |||
1045 |
4/5✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 1 times.
✓ Branch 2 (7→11) taken 1 times.
✗ Branch 3 (7→12) not taken.
✓ Branch 4 (7→13) taken 1 times.
|
4 | switch (cursor_char) { |
1046 | 1 | case '<': | |
1047 | case '[': | ||
1048 | case '{': | ||
1049 | 1 | target++; | |
1050 | // Fallthrough | ||
1051 | 2 | case '(': | |
1052 | 2 | target++; | |
1053 | 2 | goto search_fwd; | |
1054 | 1 | case '>': | |
1055 | case ']': | ||
1056 | case '}': | ||
1057 | 1 | target--; | |
1058 | // Fallthrough | ||
1059 | 1 | case ')': | |
1060 | 1 | target--; | |
1061 | 1 | goto search_bwd; | |
1062 | 1 | default: | |
1063 | 1 | return error_msg(&e->err, "Character under cursor not matchable"); | |
1064 | } | ||
1065 | |||
1066 | 2 | search_fwd: | |
1067 | 2 | block_iter_next_char(&bi, &u); | |
1068 | 2 | BUG_ON(u != cursor_char); | |
1069 |
2/2✓ Branch 0 (24→15) taken 46 times.
✓ Branch 1 (24→25) taken 1 times.
|
47 | while (block_iter_next_char(&bi, &u)) { |
1070 |
2/2✓ Branch 0 (15→16) taken 2 times.
✓ Branch 1 (15→20) taken 44 times.
|
46 | if (u == target) { |
1071 |
2/2✓ Branch 0 (16→17) taken 1 times.
✓ Branch 1 (16→19) taken 1 times.
|
2 | if (level == 0) { |
1072 | 1 | block_iter_prev_char(&bi, &u); | |
1073 | 1 | goto found; | |
1074 | } | ||
1075 | 1 | level--; | |
1076 |
2/2✓ Branch 0 (20→21) taken 1 times.
✓ Branch 1 (20→22) taken 43 times.
|
44 | } else if (u == cursor_char) { |
1077 | 1 | level++; | |
1078 | } | ||
1079 | } | ||
1080 | 1 | goto not_found; | |
1081 | |||
1082 | 1 | search_bwd: | |
1083 |
1/2✓ Branch 0 (34→26) taken 38 times.
✗ Branch 1 (34→35) not taken.
|
38 | while (block_iter_prev_char(&bi, &u)) { |
1084 |
2/2✓ Branch 0 (26→27) taken 1 times.
✓ Branch 1 (26→30) taken 37 times.
|
38 | if (u == target) { |
1085 |
1/2✓ Branch 0 (27→28) taken 1 times.
✗ Branch 1 (27→29) not taken.
|
1 | if (level == 0) { |
1086 | 1 | goto found; | |
1087 | } | ||
1088 | ✗ | level--; | |
1089 |
1/2✗ Branch 0 (30→31) not taken.
✓ Branch 1 (30→32) taken 37 times.
|
37 | } else if (u == cursor_char) { |
1090 | ✗ | level++; | |
1091 | } | ||
1092 | } | ||
1093 | |||
1094 | ✗ | not_found: | |
1095 | 1 | return error_msg(&e->err, "No matching bracket found"); | |
1096 | |||
1097 | 2 | found: | |
1098 | 2 | handle_selection_flags(view, a); | |
1099 | 2 | view->cursor = bi; | |
1100 | 2 | return true; | |
1101 | } | ||
1102 | |||
1103 | 1 | static bool cmd_mode(EditorState *e, const CommandArgs *a) | |
1104 | { | ||
1105 | 1 | const char *name = a->args[0]; | |
1106 | 1 | ModeHandler *handler = get_mode_handler(&e->modes, name); | |
1107 |
1/2✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
|
1 | if (!handler) { |
1108 | 1 | return error_msg(&e->err, "unknown mode '%s'", name); | |
1109 | } | ||
1110 | |||
1111 | ✗ | push_input_mode(e, handler); | |
1112 | ✗ | return true; | |
1113 | } | ||
1114 | |||
1115 | 11 | static bool cmd_move_tab(EditorState *e, const CommandArgs *a) | |
1116 | { | ||
1117 | 11 | Window *window = e->window; | |
1118 | 11 | const size_t ntabs = window->views.count; | |
1119 | 11 | const char *str = a->args[0]; | |
1120 | 11 | size_t to, from = ptr_array_xindex(&window->views, e->view); | |
1121 |
2/2✓ Branch 0 (3→4) taken 6 times.
✓ Branch 1 (3→6) taken 5 times.
|
11 | if (streq(str, "left")) { |
1122 | 6 | to = wrapping_decrement(from, ntabs); | |
1123 |
2/2✓ Branch 0 (6→7) taken 3 times.
✓ Branch 1 (6→9) taken 2 times.
|
5 | } else if (streq(str, "right")) { |
1124 | 3 | to = wrapping_increment(from, ntabs); | |
1125 | } else { | ||
1126 |
3/4✓ Branch 0 (10→11) taken 2 times.
✗ Branch 1 (10→12) not taken.
✓ Branch 2 (11→12) taken 1 times.
✓ Branch 3 (11→13) taken 1 times.
|
2 | if (!str_to_size(str, &to) || to == 0) { |
1127 | 1 | return error_msg(&e->err, "Invalid tab position %s", str); | |
1128 | } | ||
1129 | 1 | to = MIN(to, ntabs) - 1; | |
1130 | } | ||
1131 | 10 | ptr_array_move(&window->views, from, to); | |
1132 | 10 | window->update_tabbar = true; | |
1133 | 10 | return true; | |
1134 | } | ||
1135 | |||
1136 | 2 | static bool cmd_msg(EditorState *e, const CommandArgs *a) | |
1137 | { | ||
1138 | 2 | const char *str = a->args[0]; | |
1139 | 2 | CommandFlagSet npflags = cmdargs_flagset_from_str("npNP"); | |
1140 |
3/4✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→6) taken 1 times.
✓ Branch 2 (4→5) taken 1 times.
✗ Branch 3 (4→6) not taken.
|
2 | if (str && (a->flag_set & npflags)) { |
1141 | 1 | return error_msg ( | |
1142 | &e->err, | ||
1143 | "flags [-n|-p|-N|-P] can't be used with [number] argument" | ||
1144 | ); | ||
1145 | } | ||
1146 | |||
1147 | 1 | char abc = cmdargs_pick_winning_flag(a, "ABC"); | |
1148 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 1 times.
|
1 | size_t idx = abc ? abc - 'A' : 0; |
1149 | 1 | MessageArray *msgs = &e->messages[idx]; | |
1150 | 1 | size_t count = msgs->array.count; | |
1151 |
1/2✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→26) taken 1 times.
|
1 | if (count == 0) { |
1152 | return true; | ||
1153 | } | ||
1154 | |||
1155 | ✗ | size_t p = msgs->pos; | |
1156 | ✗ | switch (cmdargs_pick_winning_flag(a, "npNP")) { | |
1157 | ✗ | case 'n': p = saturating_increment(p, count - 1); break; | |
1158 | ✗ | case 'p': p = saturating_decrement(p); break; | |
1159 | ✗ | case 'N': p = wrapping_increment(p, count); break; | |
1160 | ✗ | case 'P': p = wrapping_decrement(p, count); break; | |
1161 | ✗ | case 0: | |
1162 | ✗ | if (str) { | |
1163 | ✗ | if (!str_to_size(str, &p) || p == 0) { | |
1164 | ✗ | return error_msg(&e->err, "invalid message index: %s", str); | |
1165 | } | ||
1166 | ✗ | p = MIN(p - 1, count - 1); | |
1167 | } | ||
1168 | } | ||
1169 | |||
1170 | ✗ | msgs->pos = p; | |
1171 | ✗ | return activate_current_message(msgs, e->window); | |
1172 | } | ||
1173 | |||
1174 | 8 | static bool cmd_new_line(EditorState *e, const CommandArgs *a) | |
1175 | { | ||
1176 | 8 | char iflag = cmdargs_pick_winning_flag(a, "iI"); | |
1177 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 8 times.
|
8 | bool auto_indent = iflag ? (iflag == 'i') : e->buffer->options.auto_indent; |
1178 | 8 | bool above_cursor = has_flag(a, 'a'); | |
1179 | 8 | new_line(e->view, auto_indent, above_cursor); | |
1180 | 8 | return true; | |
1181 | } | ||
1182 | |||
1183 | 1 | static bool cmd_next(EditorState *e, const CommandArgs *a) | |
1184 | { | ||
1185 | 1 | BUG_ON(a->nr_args); | |
1186 | 1 | const PointerArray *views = &e->window->views; | |
1187 | 1 | size_t current = ptr_array_xindex(views, e->view); | |
1188 | 1 | size_t next = wrapping_increment(current, views->count); | |
1189 | 1 | set_view(views->ptrs[next]); | |
1190 | 1 | return true; | |
1191 | } | ||
1192 | |||
1193 | 1 | static bool xglob(ErrorBuffer *ebuf, char **args, glob_t *globbuf) | |
1194 | { | ||
1195 | 1 | BUG_ON(!args); | |
1196 | 1 | BUG_ON(!args[0]); | |
1197 | 1 | int err = glob(*args, GLOB_NOCHECK, NULL, globbuf); | |
1198 |
2/4✓ Branch 0 (9→10) taken 1 times.
✗ Branch 1 (9→11) not taken.
✗ Branch 2 (10→8) not taken.
✓ Branch 3 (10→11) taken 1 times.
|
1 | while (err == 0 && *++args) { |
1199 | ✗ | err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, globbuf); | |
1200 | } | ||
1201 | |||
1202 |
1/2✓ Branch 0 (11→12) taken 1 times.
✗ Branch 1 (11→18) not taken.
|
1 | if (likely(err == 0)) { |
1203 | 1 | BUG_ON(globbuf->gl_pathc == 0); | |
1204 | 1 | BUG_ON(!globbuf->gl_pathv); | |
1205 | 1 | BUG_ON(!globbuf->gl_pathv[0]); | |
1206 | return true; | ||
1207 | } | ||
1208 | |||
1209 | ✗ | BUG_ON(err == GLOB_NOMATCH); | |
1210 | ✗ | globfree(globbuf); | |
1211 | ✗ | return error_msg(ebuf, "glob: %s", (err == GLOB_NOSPACE) ? strerror(ENOMEM) : "failed"); | |
1212 | } | ||
1213 | |||
1214 | 47 | static bool cmd_open(EditorState *e, const CommandArgs *a) | |
1215 | { | ||
1216 | 47 | bool temporary = has_flag(a, 't'); | |
1217 |
3/4✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→6) taken 46 times.
✓ Branch 2 (4→5) taken 1 times.
✗ Branch 3 (4→6) not taken.
|
47 | if (unlikely(temporary && a->nr_args > 0)) { |
1218 | 1 | return error_msg(&e->err, "'open -t' can't be used with filename arguments"); | |
1219 | } | ||
1220 | |||
1221 | 46 | const char *requested_encoding = NULL; | |
1222 | 46 | char **args = a->args; | |
1223 |
2/2✓ Branch 0 (6→7) taken 3 times.
✓ Branch 1 (6→21) taken 43 times.
|
46 | if (unlikely(a->nr_flag_args > 0)) { |
1224 | // The "-e" flag is the only one that takes an argument, so the | ||
1225 | // above condition implies it was used | ||
1226 | 3 | BUG_ON(!has_flag(a, 'e')); | |
1227 | 3 | requested_encoding = args[a->nr_flag_args - 1]; | |
1228 | 3 | args += a->nr_flag_args; | |
1229 | } | ||
1230 | |||
1231 | 3 | const char *encoding = NULL; // (Auto-detect) | |
1232 |
1/2✓ Branch 0 (10→11) taken 3 times.
✗ Branch 1 (10→21) not taken.
|
3 | if (requested_encoding) { |
1233 | 3 | EncodingType enctype = lookup_encoding(requested_encoding); | |
1234 |
1/2✓ Branch 0 (12→13) taken 3 times.
✗ Branch 1 (12→14) not taken.
|
3 | if (enctype == UTF8) { |
1235 | 3 | encoding = encoding_from_type(UTF8); | |
1236 | ✗ | } else if (conversion_supported_by_iconv(requested_encoding, "UTF-8")) { | |
1237 | ✗ | encoding = encoding_normalize(requested_encoding); | |
1238 | } else { | ||
1239 | ✗ | if (errno == EINVAL) { | |
1240 | ✗ | return error_msg(&e->err, "Unsupported encoding '%s'", requested_encoding); | |
1241 | } | ||
1242 | ✗ | return error_msg ( | |
1243 | &e->err, | ||
1244 | "iconv conversion from '%s' failed: %s", | ||
1245 | requested_encoding, | ||
1246 | strerror(errno) | ||
1247 | ); | ||
1248 | } | ||
1249 | } | ||
1250 | |||
1251 | 46 | Window *window = e->window; | |
1252 |
2/2✓ Branch 0 (21→22) taken 41 times.
✓ Branch 1 (21→26) taken 5 times.
|
46 | if (a->nr_args == 0) { |
1253 | 41 | View *view = window_open_new_file(window); | |
1254 | 41 | view->buffer->temporary = temporary; | |
1255 |
2/2✓ Branch 0 (23→24) taken 3 times.
✓ Branch 1 (23→25) taken 38 times.
|
41 | if (requested_encoding) { |
1256 | 3 | buffer_set_encoding(view->buffer, encoding, e->options.utf8_bom); | |
1257 | } | ||
1258 | 41 | return true; | |
1259 | } | ||
1260 | |||
1261 | 5 | char **paths = args; | |
1262 | 5 | glob_t globbuf; | |
1263 | 5 | bool use_glob = has_flag(a, 'g'); | |
1264 |
2/2✓ Branch 0 (27→28) taken 1 times.
✓ Branch 1 (27→31) taken 4 times.
|
5 | if (use_glob) { |
1265 |
1/2✓ Branch 0 (29→30) taken 1 times.
✗ Branch 1 (29→35) not taken.
|
1 | if (!xglob(&e->err, args, &globbuf)) { |
1266 | return false; | ||
1267 | } | ||
1268 | 1 | paths = globbuf.gl_pathv; | |
1269 | } | ||
1270 | |||
1271 | 5 | View *first_opened = window_open_files(window, paths, encoding); | |
1272 | |||
1273 |
2/2✓ Branch 0 (32→33) taken 1 times.
✓ Branch 1 (32→34) taken 4 times.
|
5 | if (use_glob) { |
1274 | 1 | globfree(&globbuf); | |
1275 | } | ||
1276 | |||
1277 | 5 | return !!first_opened; | |
1278 | } | ||
1279 | |||
1280 | 112 | static bool cmd_option(EditorState *e, const CommandArgs *a) | |
1281 | { | ||
1282 | 112 | BUG_ON(a->nr_args < 3); | |
1283 | 112 | const char *arg0 = a->args[0]; | |
1284 | 112 | char **strs = a->args + 1; | |
1285 | 112 | size_t nstrs = a->nr_args - 1; | |
1286 |
2/2✓ Branch 0 (4→5) taken 3 times.
✓ Branch 1 (4→6) taken 109 times.
|
112 | if (unlikely(arg0[0] == '\0')) { |
1287 | 3 | return error_msg(&e->err, "first argument cannot be empty"); | |
1288 | } | ||
1289 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 108 times.
|
109 | if (unlikely(nstrs & 1)) { |
1290 | 1 | return error_msg(&e->err, "missing option value"); | |
1291 | } | ||
1292 |
2/2✓ Branch 0 (9→10) taken 100 times.
✓ Branch 1 (9→28) taken 8 times.
|
108 | if (unlikely(!validate_local_options(&e->err, strs))) { |
1293 | return false; | ||
1294 | } | ||
1295 | |||
1296 | 100 | PointerArray *opts = &e->file_options; | |
1297 |
2/2✓ Branch 0 (11→12) taken 8 times.
✓ Branch 1 (11→17) taken 92 times.
|
100 | if (has_flag(a, 'r')) { |
1298 | 8 | FileTypeOrFileName u = {.filename = regexp_intern(&e->err, arg0)}; | |
1299 |
1/2✓ Branch 0 (13→14) taken 8 times.
✗ Branch 1 (13→16) not taken.
|
8 | if (unlikely(!u.filename)) { |
1300 | return false; | ||
1301 | } | ||
1302 | 8 | add_file_options(opts, FOPTS_FILENAME, u, strs, nstrs); | |
1303 | 8 | return true; | |
1304 | } | ||
1305 | |||
1306 | 92 | size_t errors = 0; | |
1307 |
2/2✓ Branch 0 (26→18) taken 282 times.
✓ Branch 1 (26→27) taken 92 times.
|
374 | for (size_t pos = 0, len = strlen(arg0); pos < len; ) { |
1308 | 282 | const StringView ft = get_delim(arg0, &pos, len, ','); | |
1309 |
2/2✓ Branch 0 (19→20) taken 8 times.
✓ Branch 1 (19→22) taken 274 times.
|
282 | if (unlikely(!is_valid_filetype_name_sv(&ft))) { |
1310 | 8 | error_msg(&e->err, "invalid filetype name: '%.*s'", (int)ft.length, ft.data); | |
1311 | 8 | errors++; | |
1312 | 8 | continue; | |
1313 | } | ||
1314 | 274 | FileTypeOrFileName u = {.filetype = mem_intern(ft.data, ft.length)}; | |
1315 | 274 | add_file_options(opts, FOPTS_FILETYPE, u, strs, nstrs); | |
1316 | } | ||
1317 | |||
1318 | 92 | return !errors; | |
1319 | } | ||
1320 | |||
1321 | 9 | static bool cmd_blkdown(EditorState *e, const CommandArgs *a) | |
1322 | { | ||
1323 | 9 | View *view = e->view; | |
1324 | 9 | handle_selection_flags(view, a); | |
1325 | |||
1326 | // If current line is blank, skip past consecutive blank lines | ||
1327 | 9 | StringView line = get_current_line(&view->cursor); | |
1328 |
2/2✓ Branch 0 (4→7) taken 2 times.
✓ Branch 1 (4→11) taken 7 times.
|
9 | if (strview_isblank(&line)) { |
1329 |
1/2✓ Branch 0 (8→5) taken 3 times.
✗ Branch 1 (8→11) not taken.
|
3 | while (block_iter_next_line(&view->cursor)) { |
1330 | 3 | line = block_iter_get_line(&view->cursor); | |
1331 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→11) taken 2 times.
|
3 | if (!strview_isblank(&line)) { |
1332 | break; | ||
1333 | } | ||
1334 | } | ||
1335 | } | ||
1336 | |||
1337 | // Skip past non-blank lines | ||
1338 |
2/2✓ Branch 0 (13→9) taken 21 times.
✓ Branch 1 (13→14) taken 4 times.
|
25 | while (block_iter_next_line(&view->cursor)) { |
1339 | 21 | line = block_iter_get_line(&view->cursor); | |
1340 |
2/2✓ Branch 0 (10→12) taken 16 times.
✓ Branch 1 (10→14) taken 5 times.
|
21 | if (strview_isblank(&line)) { |
1341 | break; | ||
1342 | } | ||
1343 | } | ||
1344 | |||
1345 | // If we reach the last populated line in the buffer, move down one line | ||
1346 | 9 | BlockIter tmp = view->cursor; | |
1347 | 9 | block_iter_eol(&tmp); | |
1348 | 9 | block_iter_skip_bytes(&tmp, 1); | |
1349 |
2/2✓ Branch 0 (16→17) taken 4 times.
✓ Branch 1 (16→18) taken 5 times.
|
9 | if (block_iter_is_eof(&tmp)) { |
1350 | 4 | view->cursor = tmp; | |
1351 | } | ||
1352 | |||
1353 | 9 | return true; | |
1354 | } | ||
1355 | |||
1356 | 4 | static bool cmd_blkup(EditorState *e, const CommandArgs *a) | |
1357 | { | ||
1358 | 4 | View *view = e->view; | |
1359 | 4 | handle_selection_flags(view, a); | |
1360 | |||
1361 | // If cursor is on the first line, just move to bol | ||
1362 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→6) taken 3 times.
|
4 | if (view->cy == 0) { |
1363 | 1 | block_iter_bol(&view->cursor); | |
1364 | 1 | return true; | |
1365 | } | ||
1366 | |||
1367 | // If current line is blank, skip past consecutive blank lines | ||
1368 | 3 | StringView line = get_current_line(&view->cursor); | |
1369 |
2/2✓ Branch 0 (7→10) taken 1 times.
✓ Branch 1 (7→14) taken 2 times.
|
3 | if (strview_isblank(&line)) { |
1370 |
1/2✓ Branch 0 (11→8) taken 3 times.
✗ Branch 1 (11→14) not taken.
|
3 | while (block_iter_prev_line(&view->cursor)) { |
1371 | 3 | line = block_iter_get_line(&view->cursor); | |
1372 |
2/2✓ Branch 0 (9→10) taken 2 times.
✓ Branch 1 (9→14) taken 1 times.
|
3 | if (!strview_isblank(&line)) { |
1373 | break; | ||
1374 | } | ||
1375 | } | ||
1376 | } | ||
1377 | |||
1378 | // Skip past non-blank lines | ||
1379 |
2/2✓ Branch 0 (16→12) taken 14 times.
✓ Branch 1 (16→17) taken 2 times.
|
16 | while (block_iter_prev_line(&view->cursor)) { |
1380 | 14 | line = block_iter_get_line(&view->cursor); | |
1381 |
2/2✓ Branch 0 (13→15) taken 13 times.
✓ Branch 1 (13→17) taken 1 times.
|
14 | if (strview_isblank(&line)) { |
1382 | break; | ||
1383 | } | ||
1384 | } | ||
1385 | |||
1386 | return true; | ||
1387 | } | ||
1388 | |||
1389 | 6 | static bool cmd_paste(EditorState *e, const CommandArgs *a) | |
1390 | { | ||
1391 | 6 | PasteLinesType type = PASTE_LINES_BELOW_CURSOR; | |
1392 |
2/3✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 2 times.
✓ Branch 2 (3→6) taken 4 times.
|
6 | switch (cmdargs_pick_winning_flag(a, "ac")) { |
1393 | ✗ | case 'a': type = PASTE_LINES_ABOVE_CURSOR; break; | |
1394 | 2 | case 'c': type = PASTE_LINES_INLINE; break; | |
1395 | } | ||
1396 | |||
1397 | 6 | bool move_after = has_flag(a, 'm'); | |
1398 | 6 | paste(&e->clipboard, e->view, type, move_after); | |
1399 | 6 | return true; | |
1400 | } | ||
1401 | |||
1402 | 2 | static bool cmd_pgdown(EditorState *e, const CommandArgs *a) | |
1403 | { | ||
1404 | 2 | View *view = e->view; | |
1405 | 2 | handle_selection_flags(view, a); | |
1406 | |||
1407 | 2 | Window *window = e->window; | |
1408 | 2 | long margin = window_get_scroll_margin(window); | |
1409 | 2 | long bottom = view->vy + window->edit_h - 1 - margin; | |
1410 | 2 | long count; | |
1411 | |||
1412 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 2 times.
|
2 | if (view->cy < bottom) { |
1413 | ✗ | count = bottom - view->cy; | |
1414 | } else { | ||
1415 | 2 | count = window->edit_h - 1 - margin * 2; | |
1416 | } | ||
1417 | |||
1418 | 2 | move_down(view, count); | |
1419 | 2 | return true; | |
1420 | } | ||
1421 | |||
1422 | 1 | static bool cmd_pgup(EditorState *e, const CommandArgs *a) | |
1423 | { | ||
1424 | 1 | View *view = e->view; | |
1425 | 1 | handle_selection_flags(view, a); | |
1426 | |||
1427 | 1 | Window *window = e->window; | |
1428 | 1 | long margin = window_get_scroll_margin(window); | |
1429 | 1 | long top = view->vy + margin; | |
1430 | 1 | long count; | |
1431 | |||
1432 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→6) not taken.
|
1 | if (view->cy > top) { |
1433 | 1 | count = view->cy - top; | |
1434 | } else { | ||
1435 | ✗ | count = window->edit_h - 1 - margin * 2; | |
1436 | } | ||
1437 | |||
1438 | 1 | move_up(view, count); | |
1439 | 1 | return true; | |
1440 | } | ||
1441 | |||
1442 | 1 | static bool cmd_prev(EditorState *e, const CommandArgs *a) | |
1443 | { | ||
1444 | 1 | BUG_ON(a->nr_args); | |
1445 | 1 | const PointerArray *views = &e->window->views; | |
1446 | 1 | size_t current = ptr_array_xindex(views, e->view); | |
1447 | 1 | size_t prev = wrapping_decrement(current, views->count); | |
1448 | 1 | set_view(views->ptrs[prev]); | |
1449 | 1 | return true; | |
1450 | } | ||
1451 | |||
1452 | 3 | static View *window_find_modified_view(Window *window) | |
1453 | { | ||
1454 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→7) taken 3 times.
|
3 | if (buffer_modified(window->view->buffer)) { |
1455 | return window->view; | ||
1456 | } | ||
1457 | ✗ | for (size_t i = 0, n = window->views.count; i < n; i++) { | |
1458 | ✗ | View *view = window->views.ptrs[i]; | |
1459 | ✗ | if (buffer_modified(view->buffer)) { | |
1460 | return view; | ||
1461 | } | ||
1462 | } | ||
1463 | return NULL; | ||
1464 | } | ||
1465 | |||
1466 | 5 | static size_t count_modified_buffers(const PointerArray *buffers, View **first) | |
1467 | { | ||
1468 | 5 | View *modified = NULL; | |
1469 | 5 | size_t nr_modified = 0; | |
1470 |
2/2✓ Branch 0 (8→3) taken 8 times.
✓ Branch 1 (8→9) taken 5 times.
|
13 | for (size_t i = 0, n = buffers->count; i < n; i++) { |
1471 | 8 | Buffer *buffer = buffers->ptrs[i]; | |
1472 |
2/2✓ Branch 0 (3→4) taken 5 times.
✓ Branch 1 (3→5) taken 3 times.
|
8 | if (!buffer_modified(buffer)) { |
1473 | 5 | continue; | |
1474 | } | ||
1475 | 3 | nr_modified++; | |
1476 |
1/2✓ Branch 0 (5→6) taken 3 times.
✗ Branch 1 (5→7) not taken.
|
3 | if (!modified) { |
1477 | 3 | modified = buffer_get_first_view(buffer); | |
1478 | } | ||
1479 | } | ||
1480 | |||
1481 | 5 | BUG_ON(nr_modified > 0 && !modified); | |
1482 | 5 | *first = modified; | |
1483 | 5 | return nr_modified; | |
1484 | } | ||
1485 | |||
1486 | 7 | static bool cmd_quit(EditorState *e, const CommandArgs *a) | |
1487 | { | ||
1488 | 7 | static const FlagMapping fmap[] = { | |
1489 | {'C', EFLAG_CMD_HIST}, | ||
1490 | {'S', EFLAG_SEARCH_HIST}, | ||
1491 | {'F', EFLAG_FILE_HIST}, | ||
1492 | {'H', EFLAG_ALL_HIST}, | ||
1493 | }; | ||
1494 | |||
1495 | 7 | int exit_code = EDITOR_EXIT_OK; | |
1496 |
2/2✓ Branch 0 (2→3) taken 5 times.
✓ Branch 1 (2→9) taken 2 times.
|
7 | if (a->nr_args) { |
1497 |
2/2✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→6) taken 4 times.
|
5 | if (!str_to_int(a->args[0], &exit_code)) { |
1498 | 1 | return error_msg(&e->err, "Not a valid integer argument: '%s'", a->args[0]); | |
1499 | } | ||
1500 | 4 | int max = EDITOR_EXIT_MAX; | |
1501 |
3/4✓ Branch 0 (6→7) taken 4 times.
✗ Branch 1 (6→8) not taken.
✓ Branch 2 (7→8) taken 1 times.
✓ Branch 3 (7→9) taken 3 times.
|
4 | if (exit_code < 0 || exit_code > max) { |
1502 | 1 | return error_msg(&e->err, "Exit code should be between 0 and %d", max); | |
1503 | } | ||
1504 | } | ||
1505 | |||
1506 | 5 | View *first_modified = NULL; | |
1507 | 5 | size_t n = count_modified_buffers(&e->buffers, &first_modified); | |
1508 |
2/2✓ Branch 0 (10→11) taken 2 times.
✓ Branch 1 (10→12) taken 3 times.
|
5 | if (n == 0) { |
1509 | 2 | goto exit; | |
1510 | } | ||
1511 | |||
1512 | 3 | BUG_ON(!first_modified); | |
1513 |
1/2✓ Branch 0 (14→15) taken 3 times.
✗ Branch 1 (14→16) not taken.
|
3 | const char *plural = (n > 1) ? "s" : ""; |
1514 |
1/2✗ Branch 0 (17→18) not taken.
✓ Branch 1 (17→20) taken 3 times.
|
3 | if (has_flag(a, 'f')) { |
1515 | ✗ | LOG_INFO("force quitting with %zu modified buffer%s", n, plural); | |
1516 | ✗ | goto exit; | |
1517 | } | ||
1518 | |||
1519 | // Activate a modified view (giving preference to the current view or | ||
1520 | // a view in the current window) | ||
1521 | 3 | View *view = window_find_modified_view(e->window); | |
1522 |
1/2✗ Branch 0 (20→21) not taken.
✓ Branch 1 (20→22) taken 3 times.
|
3 | set_view(view ? view : first_modified); |
1523 | |||
1524 |
2/2✓ Branch 0 (24→25) taken 1 times.
✓ Branch 1 (24→26) taken 2 times.
|
3 | if (!has_flag(a, 'p')) { |
1525 | 1 | return error_msg(&e->err, "Save modified files or run 'quit -f' to quit without saving"); | |
1526 | } | ||
1527 | |||
1528 |
1/2✓ Branch 0 (26→27) taken 2 times.
✗ Branch 1 (26→28) not taken.
|
2 | if (unlikely(e->flags & EFLAG_HEADLESS)) { |
1529 | 2 | return error_msg(&e->err, "-p flag unavailable in headless mode"); | |
1530 | } | ||
1531 | |||
1532 | ✗ | char question[128]; | |
1533 | ✗ | xsnprintf ( | |
1534 | question, sizeof question, | ||
1535 | "Quit without saving %zu modified buffer%s? [y/N]", | ||
1536 | n, plural | ||
1537 | ); | ||
1538 | |||
1539 | ✗ | if (dialog_prompt(e, question, "ny") != 'y') { | |
1540 | return false; | ||
1541 | } | ||
1542 | ✗ | LOG_INFO("quit prompt accepted with %zu modified buffer%s", n, plural); | |
1543 | |||
1544 | 2 | exit:; | |
1545 | 2 | EditorFlags histflags = cmdargs_convert_flags(a, fmap, ARRAYLEN(fmap)); | |
1546 | 2 | e->flags &= ~histflags; | |
1547 | 2 | e->status = exit_code; | |
1548 | 2 | return true; | |
1549 | } | ||
1550 | |||
1551 | 7 | static bool cmd_redo(EditorState *e, const CommandArgs *a) | |
1552 | { | ||
1553 | 7 | char *arg = a->args[0]; | |
1554 | 7 | unsigned long change_id = 0; | |
1555 |
2/2✓ Branch 0 (2→3) taken 6 times.
✓ Branch 1 (2→7) taken 1 times.
|
7 | if (arg) { |
1556 |
3/4✓ Branch 0 (4→5) taken 5 times.
✓ Branch 1 (4→6) taken 1 times.
✗ Branch 2 (5→6) not taken.
✓ Branch 3 (5→7) taken 5 times.
|
6 | if (!str_to_ulong(arg, &change_id) || change_id == 0) { |
1557 | 1 | return error_msg(&e->err, "Invalid change id: %s", arg); | |
1558 | } | ||
1559 | } | ||
1560 |
2/2✓ Branch 0 (8→9) taken 3 times.
✓ Branch 1 (8→10) taken 3 times.
|
6 | if (!redo(e->view, change_id)) { |
1561 | return false; | ||
1562 | } | ||
1563 | |||
1564 | 3 | return unselect(e->view); | |
1565 | } | ||
1566 | |||
1567 | 1 | static bool cmd_refresh(EditorState *e, const CommandArgs *a) | |
1568 | { | ||
1569 | 1 | BUG_ON(a->nr_args); | |
1570 | 1 | e->screen_update |= UPDATE_ALL; | |
1571 | 1 | return true; | |
1572 | } | ||
1573 | |||
1574 | 8 | static bool repeat_insert(EditorState *e, const char *str, unsigned int count, bool move_after) | |
1575 | { | ||
1576 | 8 | BUG_ON(count < 2); | |
1577 | 8 | size_t str_len = strlen(str); | |
1578 | 8 | size_t bufsize; | |
1579 |
1/2✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 8 times.
|
8 | if (unlikely(size_multiply_overflows(count, str_len, &bufsize))) { |
1580 | ✗ | return error_msg(&e->err, "Repeated insert would overflow"); | |
1581 | } | ||
1582 |
1/2✓ Branch 0 (7→8) taken 8 times.
✗ Branch 1 (7→29) not taken.
|
8 | if (unlikely(bufsize == 0)) { |
1583 | return true; | ||
1584 | } | ||
1585 | |||
1586 | 8 | char *buf = malloc(bufsize); | |
1587 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 8 times.
|
8 | if (unlikely(!buf)) { |
1588 | ✗ | return error_msg_errno(&e->err, "malloc"); | |
1589 | } | ||
1590 | |||
1591 | 8 | char tmp[4096]; | |
1592 |
2/2✓ Branch 0 (10→11) taken 3 times.
✓ Branch 1 (10→13) taken 5 times.
|
8 | if (str_len == 1) { |
1593 | 3 | memset(buf, str[0], bufsize); | |
1594 | 3 | LOG_DEBUG("Optimized %u inserts of 1 byte into 1 memset()", count); | |
1595 | 3 | goto insert; | |
1596 |
3/4✓ Branch 0 (13→14) taken 1 times.
✓ Branch 1 (13→16) taken 4 times.
✗ Branch 2 (14→16) not taken.
✓ Branch 3 (14→19) taken 1 times.
|
5 | } else if (bufsize < 2 * sizeof(tmp) || str_len > sizeof(tmp) / 8) { |
1597 |
2/2✓ Branch 0 (17→15) taken 65 times.
✓ Branch 1 (17→18) taken 4 times.
|
69 | for (size_t i = 0; i < count; i++) { |
1598 | 65 | memcpy(buf + (i * str_len), str, str_len); | |
1599 | } | ||
1600 | 4 | goto insert; | |
1601 | } | ||
1602 | |||
1603 | 1 | size_t strs_per_tmp = sizeof(tmp) / str_len; | |
1604 | 1 | size_t tmp_len = strs_per_tmp * str_len; | |
1605 | 1 | size_t tmps_per_buf = bufsize / tmp_len; | |
1606 | 1 | size_t remainder = bufsize % tmp_len; | |
1607 | |||
1608 | // Create a block of text containing `strs_per_tmp` concatenated strs | ||
1609 |
2/2✓ Branch 0 (21→20) taken 1024 times.
✓ Branch 1 (21→23) taken 1 times.
|
1025 | for (size_t i = 0; i < strs_per_tmp; i++) { |
1610 | 1024 | memcpy(tmp + (i * str_len), str, str_len); | |
1611 | } | ||
1612 | |||
1613 | // Copy `tmps_per_buf` copies of `tmp` into `buf` | ||
1614 |
2/2✓ Branch 0 (23→22) taken 2 times.
✓ Branch 1 (23→24) taken 1 times.
|
3 | for (size_t i = 0; i < tmps_per_buf; i++) { |
1615 | 2 | memcpy(buf + (i * tmp_len), tmp, tmp_len); | |
1616 | } | ||
1617 | |||
1618 | // Copy the remainder into `buf` (if any) | ||
1619 |
1/2✓ Branch 0 (24→25) taken 1 times.
✗ Branch 1 (24→26) not taken.
|
1 | if (remainder) { |
1620 | 1 | memcpy(buf + (tmps_per_buf * tmp_len), tmp, remainder); | |
1621 | } | ||
1622 | |||
1623 | 1 | LOG_DEBUG ( | |
1624 | "Optimized %u inserts of %zu bytes into %zu inserts of %zu bytes", | ||
1625 | count, str_len, | ||
1626 | tmps_per_buf, tmp_len | ||
1627 | ); | ||
1628 | |||
1629 | 8 | insert: | |
1630 | 8 | insert_text(e->view, buf, bufsize, move_after); | |
1631 | 8 | free(buf); | |
1632 | 8 | return true; | |
1633 | } | ||
1634 | |||
1635 | ✗ | static bool cmd_reopen(EditorState *e, const CommandArgs* UNUSED_ARG(a)) | |
1636 | { | ||
1637 | ✗ | for (const FileHistoryEntry *h = e->file_history.last; h; h = h->prev) { | |
1638 | ✗ | const char *path = h->filename; | |
1639 | // The combination of walking the history and doing a linear search | ||
1640 | // (with find_buffer()) here makes this O(m*n) in the worst case, | ||
1641 | // although n (e->buffers.count) will typically be small and m | ||
1642 | // (e->file_history.entries.count) is bounded by FILEHIST_MAX_ENTRIES | ||
1643 | ✗ | if ( | |
1644 | ✗ | !find_buffer(&e->buffers, path) // Not already open in the editor | |
1645 | ✗ | && access(path, R_OK) == 0 // File exists and is readable | |
1646 | ✗ | && window_open_file(e->window, path, NULL) // Reopened successfully | |
1647 | ) { | ||
1648 | return true; | ||
1649 | } | ||
1650 | } | ||
1651 | |||
1652 | ✗ | return error_msg(&e->err, "no reopenable files in history"); | |
1653 | } | ||
1654 | |||
1655 | 39 | static bool cmd_repeat(EditorState *e, const CommandArgs *a) | |
1656 | { | ||
1657 | 39 | ErrorBuffer *ebuf = &e->err; | |
1658 | 39 | unsigned int count; | |
1659 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 38 times.
|
39 | if (unlikely(!str_to_uint(a->args[0], &count))) { |
1660 | 1 | return error_msg(ebuf, "Not a valid repeat count: %s", a->args[0]); | |
1661 | } | ||
1662 |
2/2✓ Branch 0 (5→6) taken 36 times.
✓ Branch 1 (5→20) taken 2 times.
|
38 | if (unlikely(count == 0)) { |
1663 | return true; | ||
1664 | } | ||
1665 | |||
1666 | 36 | const Command *cmd = find_normal_command(a->args[1]); | |
1667 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 35 times.
|
36 | if (unlikely(!cmd)) { |
1668 | 1 | return error_msg(ebuf, "No such command: %s", a->args[1]); | |
1669 | } | ||
1670 | |||
1671 | 35 | CommandArgs a2 = cmdargs_new(a->args + 2); | |
1672 |
1/2✓ Branch 0 (10→11) taken 35 times.
✗ Branch 1 (10→20) not taken.
|
35 | if (unlikely(!parse_args(cmd, &a2, ebuf))) { |
1673 | return false; | ||
1674 | } | ||
1675 | |||
1676 |
6/6✓ Branch 0 (11→12) taken 32 times.
✓ Branch 1 (11→18) taken 3 times.
✓ Branch 2 (12→13) taken 10 times.
✓ Branch 3 (12→18) taken 22 times.
✓ Branch 4 (14→15) taken 8 times.
✓ Branch 5 (14→18) taken 2 times.
|
35 | if (count > 1 && cmd->cmd == cmd_insert && !has_flag(&a2, 'k')) { |
1677 | // Use optimized implementation for repeated "insert" | ||
1678 | 8 | return repeat_insert(e, a2.args[0], count, has_flag(&a2, 'm')); | |
1679 | } | ||
1680 | |||
1681 |
2/2✓ Branch 0 (19→17) taken 1096 times.
✓ Branch 1 (19→20) taken 27 times.
|
1123 | while (count--) { |
1682 | 1096 | command_func_call(e, ebuf, cmd, &a2); | |
1683 | } | ||
1684 | |||
1685 | // TODO: return false if fn() fails? | ||
1686 | return true; | ||
1687 | } | ||
1688 | |||
1689 | 14 | static bool cmd_replace(EditorState *e, const CommandArgs *a) | |
1690 | { | ||
1691 | 14 | static const FlagMapping map[] = { | |
1692 | {'b', REPLACE_BASIC}, | ||
1693 | {'c', REPLACE_CONFIRM}, | ||
1694 | {'g', REPLACE_GLOBAL}, | ||
1695 | {'i', REPLACE_IGNORE_CASE}, | ||
1696 | }; | ||
1697 | |||
1698 | 14 | const char *pattern = a->args[0]; | |
1699 | 14 | ReplaceFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)); | |
1700 | |||
1701 |
3/4✓ Branch 0 (3→4) taken 2 times.
✓ Branch 1 (3→6) taken 12 times.
✓ Branch 2 (4→5) taken 2 times.
✗ Branch 3 (4→6) not taken.
|
14 | if (unlikely((flags & REPLACE_CONFIRM) && (e->flags & EFLAG_HEADLESS))) { |
1702 | 2 | return error_msg(&e->err, "-c flag unavailable in headless mode"); | |
1703 | } | ||
1704 | |||
1705 | 12 | char *alloc = NULL; | |
1706 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→11) taken 12 times.
|
12 | if (has_flag(a, 'e')) { |
1707 | ✗ | size_t len = strlen(pattern); | |
1708 | ✗ | if (strn_contains_ascii_char_type(pattern, len, ASCII_REGEX)) { | |
1709 | ✗ | pattern = alloc = regexp_escape(pattern, len); | |
1710 | } | ||
1711 | ✗ | flags &= ~REPLACE_BASIC; | |
1712 | } | ||
1713 | |||
1714 |
2/2✓ Branch 0 (11→12) taken 10 times.
✓ Branch 1 (11→13) taken 2 times.
|
12 | const char *replacement = a->args[1] ? a->args[1] : ""; |
1715 | 12 | bool r = reg_replace(e->view, pattern, replacement, flags); | |
1716 | 12 | free(alloc); | |
1717 | 12 | return r; | |
1718 | } | ||
1719 | |||
1720 | 31 | static bool cmd_right(EditorState *e, const CommandArgs *a) | |
1721 | { | ||
1722 | 31 | handle_selection_flags(e->view, a); | |
1723 | 31 | move_cursor_right(e->view); | |
1724 | 31 | return true; | |
1725 | } | ||
1726 | |||
1727 | ✗ | static bool stat_changed(const FileInfo *file, const struct stat *st) | |
1728 | { | ||
1729 | // Don't compare st_mode because we allow chmod 755 etc. | ||
1730 | ✗ | return !timespecs_equal(get_stat_mtime(st), &file->mtime) | |
1731 | ✗ | || st->st_dev != file->dev | |
1732 | ✗ | || st->st_ino != file->ino | |
1733 | ✗ | || st->st_size != file->size; | |
1734 | } | ||
1735 | |||
1736 | ✗ | static bool save_unmodified_buffer(Buffer *buffer, const char *filename) | |
1737 | { | ||
1738 | ✗ | SaveUnmodifiedType type = buffer->options.save_unmodified; | |
1739 | ✗ | if (type == SAVE_NONE) { | |
1740 | ✗ | LOG_INFO("buffer unchanged; leaving file untouched"); | |
1741 | ✗ | return true; | |
1742 | } | ||
1743 | |||
1744 | ✗ | BUG_ON(type != SAVE_TOUCH); | |
1745 | ✗ | struct timespec times[2]; | |
1746 | ✗ | if (unlikely(clock_gettime(CLOCK_REALTIME, ×[0]) != 0)) { | |
1747 | ✗ | LOG_ERRNO("aborting partial save; clock_gettime() failed"); | |
1748 | ✗ | return false; | |
1749 | } | ||
1750 | |||
1751 | ✗ | times[1] = times[0]; | |
1752 | ✗ | if (unlikely(utimensat(AT_FDCWD, filename, times, 0) != 0)) { | |
1753 | ✗ | LOG_ERRNO("aborting partial save; utimensat() failed"); | |
1754 | ✗ | return false; | |
1755 | } | ||
1756 | |||
1757 | ✗ | buffer->file.mtime = times[0]; | |
1758 | ✗ | LOG_INFO("buffer unchanged; mtime/atime updated"); | |
1759 | ✗ | return true; | |
1760 | } | ||
1761 | |||
1762 | 24 | static bool cmd_save(EditorState *e, const CommandArgs *a) | |
1763 | { | ||
1764 | 24 | Buffer *buffer = e->buffer; | |
1765 |
1/2✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 24 times.
|
24 | if (unlikely(buffer->stdout_buffer)) { |
1766 | ✗ | const char *f = buffer_filename(buffer); | |
1767 | ✗ | return info_msg(&e->err, "%s can't be saved; it will be piped to stdout on exit", f); | |
1768 | } | ||
1769 | |||
1770 | 24 | char du_flag = cmdargs_pick_winning_flag(a, "du"); | |
1771 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→8) taken 23 times.
|
24 | bool crlf = du_flag ? (du_flag == 'd') : buffer->crlf_newlines; |
1772 | |||
1773 | 24 | const char *requested_encoding = NULL; | |
1774 | 24 | char **args = a->args; | |
1775 |
2/2✓ Branch 0 (9→10) taken 2 times.
✓ Branch 1 (9→14) taken 22 times.
|
24 | if (unlikely(a->nr_flag_args > 0)) { |
1776 | 2 | BUG_ON(!has_flag(a, 'e')); | |
1777 | 2 | requested_encoding = args[a->nr_flag_args - 1]; | |
1778 | 2 | args += a->nr_flag_args; | |
1779 | } | ||
1780 | |||
1781 | 24 | const char *encoding = buffer->encoding; | |
1782 | 24 | bool bom = buffer->bom; | |
1783 |
2/2✓ Branch 0 (14→15) taken 2 times.
✓ Branch 1 (14→32) taken 22 times.
|
24 | if (requested_encoding) { |
1784 | 2 | EncodingType et = lookup_encoding(requested_encoding); | |
1785 |
2/2✓ Branch 0 (16→17) taken 1 times.
✓ Branch 1 (16→21) taken 1 times.
|
2 | if (et == UTF8) { |
1786 |
1/2✗ Branch 0 (18→19) not taken.
✓ Branch 1 (18→32) taken 1 times.
|
1 | if (!encoding_is_utf8(encoding)) { |
1787 | // Encoding changed | ||
1788 | ✗ | encoding = encoding_from_type(UTF8); | |
1789 | ✗ | bom = e->options.utf8_bom; | |
1790 | } | ||
1791 |
1/2✓ Branch 0 (22→23) taken 1 times.
✗ Branch 1 (22→28) not taken.
|
1 | } else if (conversion_supported_by_iconv("UTF-8", requested_encoding)) { |
1792 | 1 | encoding = encoding_normalize(requested_encoding); | |
1793 |
1/2✓ Branch 0 (24→25) taken 1 times.
✗ Branch 1 (24→32) not taken.
|
1 | if (encoding != buffer->encoding) { |
1794 | // Encoding changed | ||
1795 | 1 | bom = !!get_bom_for_encoding(lookup_encoding(encoding)); | |
1796 | } | ||
1797 | } else { | ||
1798 | ✗ | if (errno == EINVAL) { | |
1799 | ✗ | return error_msg(&e->err, "Unsupported encoding '%s'", requested_encoding); | |
1800 | } | ||
1801 | ✗ | return error_msg ( | |
1802 | &e->err, | ||
1803 | "iconv conversion to '%s' failed: %s", | ||
1804 | requested_encoding, | ||
1805 | strerror(errno) | ||
1806 | ); | ||
1807 | } | ||
1808 | } | ||
1809 | |||
1810 | 24 | char b_flag = cmdargs_pick_winning_flag(a, "bB"); | |
1811 |
1/2✗ Branch 0 (33→34) not taken.
✓ Branch 1 (33→35) taken 24 times.
|
24 | bom = b_flag ? (b_flag == 'b') : bom; |
1812 | |||
1813 | 24 | char *absolute = buffer->abs_filename; | |
1814 | 24 | bool force = has_flag(a, 'f'); | |
1815 | 24 | bool new_locked = false; | |
1816 |
2/2✓ Branch 0 (36→37) taken 23 times.
✓ Branch 1 (36→45) taken 1 times.
|
24 | if (a->nr_args > 0) { |
1817 |
2/2✓ Branch 0 (37→38) taken 1 times.
✓ Branch 1 (37→39) taken 22 times.
|
23 | if (args[0][0] == '\0') { |
1818 | 1 | return error_msg(&e->err, "Empty filename not allowed"); | |
1819 | } | ||
1820 | 22 | char *tmp = path_absolute(args[0]); | |
1821 |
1/2✗ Branch 0 (40→41) not taken.
✓ Branch 1 (40→42) taken 22 times.
|
22 | if (!tmp) { |
1822 | ✗ | return error_msg_errno(&e->err, "Failed to make absolute path"); | |
1823 | } | ||
1824 |
3/4✓ Branch 0 (42→43) taken 2 times.
✓ Branch 1 (42→55) taken 20 times.
✗ Branch 2 (43→44) not taken.
✓ Branch 3 (43→55) taken 2 times.
|
22 | if (absolute && streq(tmp, absolute)) { |
1825 | ✗ | free(tmp); | |
1826 | } else { | ||
1827 | absolute = tmp; | ||
1828 | } | ||
1829 | } else { | ||
1830 |
1/2✓ Branch 0 (45→46) taken 1 times.
✗ Branch 1 (45→52) not taken.
|
1 | if (!absolute) { |
1831 |
1/2✓ Branch 0 (47→48) taken 1 times.
✗ Branch 1 (47→49) not taken.
|
1 | if (!has_flag(a, 'p')) { |
1832 | 1 | return error_msg(&e->err, "No filename"); | |
1833 | } | ||
1834 | ✗ | push_input_mode(e, e->command_mode); | |
1835 | ✗ | cmdline_set_text(&e->cmdline, "save "); | |
1836 | ✗ | return true; | |
1837 | } | ||
1838 | ✗ | if (buffer->readonly && !force) { | |
1839 | ✗ | return error_msg(&e->err, "Use -f to force saving read-only file"); | |
1840 | } | ||
1841 | } | ||
1842 | |||
1843 | 22 | mode_t old_mode = buffer->file.mode; | |
1844 | 22 | bool hardlinks = false; | |
1845 | 22 | struct stat st; | |
1846 | 22 | bool stat_ok = !stat(absolute, &st); | |
1847 |
2/2✓ Branch 0 (56→57) taken 21 times.
✓ Branch 1 (56→61) taken 1 times.
|
22 | if (!stat_ok) { |
1848 |
1/2✗ Branch 0 (57→58) not taken.
✓ Branch 1 (57→70) taken 21 times.
|
21 | if (errno != ENOENT) { |
1849 | ✗ | error_msg(&e->err, "stat failed for %s: %s", absolute, strerror(errno)); | |
1850 | ✗ | goto error; | |
1851 | } | ||
1852 | } else { | ||
1853 | 1 | if ( | |
1854 |
1/2✗ Branch 0 (61→62) not taken.
✓ Branch 1 (61→66) taken 1 times.
|
1 | absolute == buffer->abs_filename |
1855 | ✗ | && !force | |
1856 | ✗ | && stat_changed(&buffer->file, &st) | |
1857 | ) { | ||
1858 | ✗ | error_msg ( | |
1859 | &e->err, | ||
1860 | "File has been modified by another process; " | ||
1861 | "use 'save -f' to force overwrite" | ||
1862 | ); | ||
1863 | ✗ | goto error; | |
1864 | } | ||
1865 |
1/2✓ Branch 0 (66→67) taken 1 times.
✗ Branch 1 (66→69) not taken.
|
1 | if (S_ISDIR(st.st_mode)) { |
1866 | 1 | error_msg(&e->err, "Will not overwrite directory %s", absolute); | |
1867 | 1 | goto error; | |
1868 | } | ||
1869 | ✗ | hardlinks = (st.st_nlink >= 2); | |
1870 | } | ||
1871 | |||
1872 |
1/2✗ Branch 0 (70→71) not taken.
✓ Branch 1 (70→84) taken 21 times.
|
21 | if (e->options.lock_files) { |
1873 | ✗ | if (absolute == buffer->abs_filename) { | |
1874 | ✗ | if (!buffer->locked) { | |
1875 | ✗ | if (!lock_file(&e->locks_ctx, &e->err, absolute)) { | |
1876 | ✗ | if (!force) { | |
1877 | ✗ | error_msg(&e->err, "Can't lock file %s", absolute); | |
1878 | ✗ | goto error; | |
1879 | } | ||
1880 | } else { | ||
1881 | ✗ | buffer->locked = true; | |
1882 | } | ||
1883 | } | ||
1884 | } else { | ||
1885 | ✗ | if (!lock_file(&e->locks_ctx, &e->err, absolute)) { | |
1886 | ✗ | if (!force) { | |
1887 | ✗ | error_msg(&e->err, "Can't lock file %s", absolute); | |
1888 | ✗ | goto error; | |
1889 | } | ||
1890 | } else { | ||
1891 | new_locked = true; | ||
1892 | } | ||
1893 | } | ||
1894 | } | ||
1895 | |||
1896 |
1/2✗ Branch 0 (84→85) not taken.
✓ Branch 1 (84→102) taken 21 times.
|
21 | if (stat_ok) { |
1897 | ✗ | if (absolute != buffer->abs_filename && !force) { | |
1898 | ✗ | error_msg(&e->err, "Use -f to overwrite %s", absolute); | |
1899 | ✗ | goto error; | |
1900 | } | ||
1901 | // Allow chmod 755 etc. | ||
1902 | ✗ | buffer->file.mode = st.st_mode; | |
1903 | } | ||
1904 | |||
1905 | ✗ | if ( | |
1906 | stat_ok | ||
1907 | ✗ | && buffer->options.save_unmodified != SAVE_FULL | |
1908 | ✗ | && !stat_changed(&buffer->file, &st) | |
1909 | ✗ | && st.st_uid == buffer->file.uid | |
1910 | ✗ | && st.st_gid == buffer->file.gid | |
1911 | ✗ | && !buffer_modified(buffer) | |
1912 | ✗ | && absolute == buffer->abs_filename | |
1913 | ✗ | && encoding == buffer->encoding | |
1914 | ✗ | && crlf == buffer->crlf_newlines | |
1915 | ✗ | && bom == buffer->bom | |
1916 | ✗ | && save_unmodified_buffer(buffer, absolute) | |
1917 | ) { | ||
1918 | ✗ | BUG_ON(new_locked); | |
1919 | return true; | ||
1920 | } | ||
1921 | |||
1922 | 21 | FileSaveContext ctx = { | |
1923 | 21 | .ebuf = &e->err, | |
1924 | .encoding = encoding, | ||
1925 | 21 | .new_file_mode = e->new_file_mode, | |
1926 | .crlf = crlf, | ||
1927 | .write_bom = bom, | ||
1928 | .hardlinks = hardlinks, | ||
1929 | }; | ||
1930 | |||
1931 |
1/2✗ Branch 0 (103→104) not taken.
✓ Branch 1 (103→105) taken 21 times.
|
21 | if (!save_buffer(buffer, absolute, &ctx)) { |
1932 | ✗ | goto error; | |
1933 | } | ||
1934 | |||
1935 | 21 | buffer->saved_change = buffer->cur_change; | |
1936 | 21 | buffer->readonly = false; | |
1937 | 21 | buffer->temporary = false; | |
1938 | 21 | buffer->crlf_newlines = crlf; | |
1939 | 21 | buffer->bom = bom; | |
1940 |
2/2✓ Branch 0 (105→106) taken 2 times.
✓ Branch 1 (105→107) taken 19 times.
|
21 | if (requested_encoding) { |
1941 | 2 | buffer->encoding = encoding; | |
1942 | } | ||
1943 | |||
1944 |
1/2✓ Branch 0 (107→108) taken 21 times.
✗ Branch 1 (107→112) not taken.
|
21 | if (absolute != buffer->abs_filename) { |
1945 |
1/2✗ Branch 0 (108→109) not taken.
✓ Branch 1 (108→110) taken 21 times.
|
21 | if (buffer->locked) { |
1946 | // Filename changes, release old file lock | ||
1947 | ✗ | unlock_file(&e->locks_ctx, &e->err, buffer->abs_filename); | |
1948 | } | ||
1949 | 21 | buffer->locked = new_locked; | |
1950 | |||
1951 | 21 | free(buffer->abs_filename); | |
1952 | 21 | buffer->abs_filename = absolute; | |
1953 | 21 | buffer_update_short_filename(buffer, &e->home_dir); | |
1954 | 21 | e->screen_update |= UPDATE_TERM_TITLE; | |
1955 | |||
1956 | // Filename change is not detected (only buffer_modified() change) | ||
1957 | 21 | buffer_mark_tabbars_changed(buffer); | |
1958 | } | ||
1959 |
3/4✓ Branch 0 (112→113) taken 19 times.
✓ Branch 1 (112→123) taken 2 times.
✓ Branch 2 (113→114) taken 19 times.
✗ Branch 3 (113→123) not taken.
|
21 | if (!old_mode && streq(buffer->options.filetype, "none")) { |
1960 | // New file and most likely user has not changed the filetype | ||
1961 |
1/2✗ Branch 0 (115→116) not taken.
✓ Branch 1 (115→123) taken 19 times.
|
19 | if (buffer_detect_filetype(buffer, &e->filetypes)) { |
1962 | ✗ | set_file_options(e, buffer); | |
1963 | ✗ | set_editorconfig_options(buffer); | |
1964 | ✗ | buffer_update_syntax(e, buffer); | |
1965 | } | ||
1966 | } | ||
1967 | |||
1968 | return true; | ||
1969 | |||
1970 | ✗ | error: | |
1971 |
0/2✗ Branch 0 (119→120) not taken.
✗ Branch 1 (119→121) not taken.
|
1 | if (new_locked) { |
1972 | ✗ | unlock_file(&e->locks_ctx, &e->err, absolute); | |
1973 | } | ||
1974 |
1/2✓ Branch 0 (121→122) taken 1 times.
✗ Branch 1 (121→123) not taken.
|
1 | if (absolute != buffer->abs_filename) { |
1975 | 1 | free(absolute); | |
1976 | } | ||
1977 | return false; | ||
1978 | } | ||
1979 | |||
1980 | 1 | static bool cmd_scroll_down(EditorState *e, const CommandArgs *a) | |
1981 | { | ||
1982 | 1 | BUG_ON(a->nr_args); | |
1983 | 1 | View *view = e->view; | |
1984 | 1 | handle_selection_flags(view, a); | |
1985 | 1 | view->vy++; | |
1986 | |||
1987 |
1/2✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→10) not taken.
|
1 | if (has_flag(a, 'M')) { |
1988 | return true; // Don't move cursor, even at scroll margin | ||
1989 | } | ||
1990 | |||
1991 | 1 | unsigned int margin = window_get_scroll_margin(e->window); | |
1992 |
1/2✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→10) not taken.
|
1 | if (view->cy < view->vy + margin) { |
1993 | 1 | move_down(view, 1); | |
1994 | } | ||
1995 | |||
1996 | return true; | ||
1997 | } | ||
1998 | |||
1999 | 1 | static bool cmd_scroll_pgdown(EditorState *e, const CommandArgs *a) | |
2000 | { | ||
2001 | 1 | BUG_ON(a->nr_args); | |
2002 | 1 | View *view = e->view; | |
2003 | 1 | handle_selection_flags(view, a); | |
2004 | |||
2005 | 1 | const long edit_h = e->window->edit_h; | |
2006 | 1 | const long max = view->buffer->nl - edit_h + 1; | |
2007 | 1 | long count; | |
2008 | |||
2009 |
2/4✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→11) not taken.
✓ Branch 2 (6→7) taken 1 times.
✗ Branch 3 (6→11) not taken.
|
1 | if (view->vy < max && max > 0) { |
2010 | 1 | bool half = has_flag(a, 'h'); | |
2011 | 1 | unsigned int shift = half & 1; | |
2012 | 1 | count = (edit_h - 1) >> shift; | |
2013 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 1 times.
|
1 | if (view->vy + count > max) { |
2014 | ✗ | count = max - view->vy; | |
2015 | } | ||
2016 | 1 | view->vy += count; | |
2017 | ✗ | } else if (view->cy < view->buffer->nl) { | |
2018 | ✗ | count = view->buffer->nl - view->cy; | |
2019 | } else { | ||
2020 | return true; | ||
2021 | } | ||
2022 | |||
2023 | 1 | move_down(view, count); | |
2024 | 1 | return true; | |
2025 | } | ||
2026 | |||
2027 | 1 | static bool cmd_scroll_pgup(EditorState *e, const CommandArgs *a) | |
2028 | { | ||
2029 | 1 | BUG_ON(a->nr_args); | |
2030 | 1 | View *view = e->view; | |
2031 | 1 | handle_selection_flags(view, a); | |
2032 | |||
2033 | 1 | long count; | |
2034 |
1/2✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→8) taken 1 times.
|
1 | if (view->vy > 0) { |
2035 | ✗ | bool half = has_flag(a, 'h'); | |
2036 | ✗ | unsigned int shift = half & 1; | |
2037 | ✗ | count = MIN((e->window->edit_h - 1) >> shift, view->vy); | |
2038 | ✗ | view->vy -= count; | |
2039 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→11) taken 1 times.
|
1 | } else if (view->cy > 0) { |
2040 | count = view->cy; | ||
2041 | } else { | ||
2042 | return true; | ||
2043 | } | ||
2044 | |||
2045 | ✗ | move_up(view, count); | |
2046 | ✗ | return true; | |
2047 | } | ||
2048 | |||
2049 | 1 | static bool cmd_scroll_up(EditorState *e, const CommandArgs *a) | |
2050 | { | ||
2051 | 1 | BUG_ON(a->nr_args); | |
2052 | 1 | View *view = e->view; | |
2053 | 1 | handle_selection_flags(view, a); | |
2054 | 1 | view->vy -= (view->vy > 0); | |
2055 | |||
2056 |
1/2✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→10) not taken.
|
1 | if (has_flag(a, 'M')) { |
2057 | return true; // Don't move cursor, even at scroll margin | ||
2058 | } | ||
2059 | |||
2060 | 1 | const Window *window = e->window; | |
2061 | 1 | unsigned int margin = window_get_scroll_margin(window); | |
2062 |
1/2✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→10) not taken.
|
1 | if (view->vy + (window->edit_h - margin) <= view->cy) { |
2063 | 1 | move_up(view, 1); | |
2064 | } | ||
2065 | |||
2066 | return true; | ||
2067 | } | ||
2068 | |||
2069 | 7 | static bool cmd_search(EditorState *e, const CommandArgs *a) | |
2070 | { | ||
2071 | 7 | const char *pattern = a->args[0]; | |
2072 | 7 | char npflag = cmdargs_pick_winning_flag(a, "np"); | |
2073 | 7 | bool use_word_under_cursor = has_flag(a, 'w'); | |
2074 | |||
2075 |
2/2✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→6) taken 5 times.
|
7 | if (!!npflag + use_word_under_cursor + !!pattern > 1) { |
2076 | 2 | return error_msg ( | |
2077 | &e->err, | ||
2078 | "flags [-n|-p|-w] and [pattern] argument are mutually exclusive" | ||
2079 | ); | ||
2080 | } | ||
2081 | |||
2082 | 5 | View *view = e->view; | |
2083 | 5 | char pattbuf[4096]; | |
2084 | |||
2085 |
2/2✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→13) taken 4 times.
|
5 | if (use_word_under_cursor) { |
2086 | 1 | StringView word = view_get_word_under_cursor(view); | |
2087 |
1/2✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→12) not taken.
|
1 | if (word.length == 0) { |
2088 | 1 | return false; // Error message wouldn't be very useful here | |
2089 | } | ||
2090 | 1 | const RegexpWordBoundaryTokens *rwbt = &e->regexp_word_tokens; | |
2091 | 1 | const size_t bmax = sizeof(rwbt->start); | |
2092 | 1 | static_assert_compatible_types(rwbt->start, char[8]); | |
2093 |
1/2✓ Branch 0 (9→10) taken 1 times.
✗ Branch 1 (9→11) not taken.
|
1 | if (unlikely(word.length >= sizeof(pattbuf) - (bmax * 2))) { |
2094 | 1 | return error_msg(&e->err, "word under cursor too long"); | |
2095 | } | ||
2096 | ✗ | char *ptr = stpncpy(pattbuf, rwbt->start, bmax); | |
2097 | ✗ | memcpy(ptr, word.data, word.length); | |
2098 | ✗ | memcpy(ptr + word.length, rwbt->end, bmax); | |
2099 | ✗ | pattern = pattbuf; | |
2100 | } | ||
2101 | |||
2102 | 4 | SearchState *search = &e->search; | |
2103 | 4 | search->reverse = has_flag(a, 'r'); | |
2104 | |||
2105 |
1/2✗ Branch 0 (14→15) not taken.
✓ Branch 1 (14→17) taken 4 times.
|
4 | if (!npflag && !pattern) { |
2106 | ✗ | push_input_mode(e, e->search_mode); | |
2107 | ✗ | return true; | |
2108 | } | ||
2109 | |||
2110 | // TODO: Make [-c|-l] selection flags apply to search mode (as handled above) | ||
2111 | // and/or add `[-c|-l]` flags to the search mode `accept` command and bind to | ||
2112 | // Shift+Enter and Ctrl+Shift+Enter | ||
2113 | 4 | handle_selection_flags(view, a); | |
2114 | |||
2115 | 4 | SearchCaseSensitivity cs = e->options.case_sensitive_search; | |
2116 |
1/4✗ Branch 0 (19→20) not taken.
✗ Branch 1 (19→21) not taken.
✗ Branch 2 (19→22) not taken.
✓ Branch 3 (19→23) taken 4 times.
|
4 | switch (cmdargs_pick_winning_flag(a, "ais")) { |
2117 | ✗ | case 'a': cs = CSS_AUTO; break; | |
2118 | ✗ | case 'i': cs = CSS_FALSE; break; | |
2119 | ✗ | case 's': cs = CSS_TRUE; break; | |
2120 | } | ||
2121 | |||
2122 |
3/3✓ Branch 0 (23→24) taken 1 times.
✓ Branch 1 (23→25) taken 1 times.
✓ Branch 2 (23→26) taken 2 times.
|
4 | switch (npflag) { |
2123 | 1 | case 'n': return search_next(view, search, cs); | |
2124 | 1 | case 'p': return search_prev(view, search, cs); | |
2125 | } | ||
2126 | |||
2127 | 2 | BUG_ON(!pattern); | |
2128 | 2 | char *alloc = NULL; | |
2129 | |||
2130 |
2/4✓ Branch 0 (28→29) taken 2 times.
✗ Branch 1 (28→33) not taken.
✗ Branch 2 (30→31) not taken.
✓ Branch 3 (30→33) taken 2 times.
|
2 | if (!use_word_under_cursor && has_flag(a, 'e')) { |
2131 | ✗ | size_t len = strlen(pattern); | |
2132 | ✗ | if (strn_contains_ascii_char_type(pattern, len, ASCII_REGEX)) { | |
2133 | ✗ | pattern = alloc = regexp_escape(pattern, len); | |
2134 | } | ||
2135 | } | ||
2136 | |||
2137 |
2/2✓ Branch 0 (34→35) taken 1 times.
✓ Branch 1 (34→36) taken 1 times.
|
2 | if (!has_flag(a, 'H')) { |
2138 | 1 | history_append(&e->search_history, pattern); | |
2139 | } | ||
2140 | |||
2141 | 2 | search_set_regexp(search, pattern); | |
2142 | 2 | free(alloc); | |
2143 |
3/4✓ Branch 0 (38→39) taken 2 times.
✗ Branch 1 (38→42) not taken.
✓ Branch 2 (40→41) taken 1 times.
✓ Branch 3 (40→42) taken 1 times.
|
3 | return has_flag(a, 'x') || do_search_next(view, search, cs, use_word_under_cursor); |
2144 | } | ||
2145 | |||
2146 | ✗ | static bool cmd_select_block(EditorState *e, const CommandArgs *a) | |
2147 | { | ||
2148 | ✗ | BUG_ON(a->nr_args); | |
2149 | ✗ | select_block(e->view); | |
2150 | |||
2151 | // TODO: return false if select_block() doesn't select anything? | ||
2152 | ✗ | return true; | |
2153 | } | ||
2154 | |||
2155 | 3 | static bool cmd_select(EditorState *e, const CommandArgs *a) | |
2156 | { | ||
2157 | 3 | View *view = e->view; | |
2158 |
1/2✓ Branch 0 (3→4) taken 3 times.
✗ Branch 1 (3→5) not taken.
|
3 | SelectionType sel = has_flag(a, 'l') ? SELECT_LINES : SELECT_CHARS; |
2159 | 3 | bool keep = has_flag(a, 'k'); | |
2160 |
2/6✓ Branch 0 (6→7) taken 3 times.
✗ Branch 1 (6→10) not taken.
✗ Branch 2 (7→8) not taken.
✓ Branch 3 (7→10) taken 3 times.
✗ Branch 4 (8→9) not taken.
✗ Branch 5 (8→10) not taken.
|
3 | if (!keep && view->selection && view->selection == sel) { |
2161 | ✗ | sel = SELECT_NONE; | |
2162 | } | ||
2163 | |||
2164 | 3 | view->select_mode = sel; | |
2165 | 3 | do_selection(view, sel); | |
2166 | 3 | return true; | |
2167 | } | ||
2168 | |||
2169 | 41 | static bool cmd_set(EditorState *e, const CommandArgs *a) | |
2170 | { | ||
2171 | 41 | bool global = has_flag(a, 'g'); | |
2172 | 41 | bool local = has_flag(a, 'l'); | |
2173 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→7) taken 41 times.
|
41 | if (!e->buffer) { |
2174 | ✗ | if (unlikely(local)) { | |
2175 | ✗ | return error_msg(&e->err, "Flag -l makes no sense in config file"); | |
2176 | } | ||
2177 | global = true; | ||
2178 | } | ||
2179 | |||
2180 | 41 | char **args = a->args; | |
2181 | 41 | size_t count = a->nr_args; | |
2182 |
2/2✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 40 times.
|
41 | if (count == 1) { |
2183 | 1 | return set_bool_option(e, args[0], local, global); | |
2184 | } | ||
2185 |
2/2✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→15) taken 39 times.
|
40 | if (count & 1) { |
2186 | 1 | return error_msg(&e->err, "One or even number of arguments expected"); | |
2187 | } | ||
2188 | |||
2189 | size_t errors = 0; | ||
2190 |
2/2✓ Branch 0 (15→11) taken 39 times.
✓ Branch 1 (15→16) taken 39 times.
|
78 | for (size_t i = 0; i < count; i += 2) { |
2191 |
2/2✓ Branch 0 (12→13) taken 22 times.
✓ Branch 1 (12→14) taken 17 times.
|
39 | if (!set_option(e, args[i], args[i + 1], local, global)) { |
2192 | 22 | errors++; | |
2193 | } | ||
2194 | } | ||
2195 | |||
2196 | 39 | return !errors; | |
2197 | } | ||
2198 | |||
2199 | 16 | static bool cmd_setenv(EditorState *e, const CommandArgs *a) | |
2200 | { | ||
2201 | 16 | const char *name = a->args[0]; | |
2202 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 15 times.
|
16 | if (unlikely(streq(name, "DTE_VERSION"))) { |
2203 | 1 | return error_msg(&e->err, "$DTE_VERSION cannot be changed"); | |
2204 | } | ||
2205 | |||
2206 | 15 | const size_t nr_args = a->nr_args; | |
2207 | 15 | int res; | |
2208 |
2/2✓ Branch 0 (4→5) taken 11 times.
✓ Branch 1 (4→6) taken 4 times.
|
15 | if (nr_args == 2) { |
2209 | 11 | res = setenv(name, a->args[1], 1); | |
2210 | } else { | ||
2211 | 4 | BUG_ON(nr_args != 1); | |
2212 | 4 | res = unsetenv(name); | |
2213 | } | ||
2214 | |||
2215 |
1/2✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→15) taken 15 times.
|
15 | if (likely(res == 0)) { |
2216 | return true; | ||
2217 | } | ||
2218 | |||
2219 | ✗ | if (errno == EINVAL) { | |
2220 | ✗ | return error_msg(&e->err, "Invalid environment variable name '%s'", name); | |
2221 | } | ||
2222 | |||
2223 | ✗ | return error_msg_errno(&e->err, nr_args == 2 ? "setenv" : "unsetenv"); | |
2224 | } | ||
2225 | |||
2226 | 12 | static bool cmd_shift(EditorState *e, const CommandArgs *a) | |
2227 | { | ||
2228 | 12 | const char *arg = a->args[0]; | |
2229 | 12 | int count; | |
2230 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 11 times.
|
12 | if (!str_to_int(arg, &count)) { |
2231 | 1 | return error_msg(&e->err, "Invalid number: %s", arg); | |
2232 | } | ||
2233 |
2/2✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 10 times.
|
11 | if (count == 0) { |
2234 | 1 | return error_msg(&e->err, "Count must be non-zero"); | |
2235 | } | ||
2236 | 10 | shift_lines(e->view, count); | |
2237 | 10 | return true; | |
2238 | } | ||
2239 | |||
2240 | 9 | static bool cmd_show(EditorState *e, const CommandArgs *a) | |
2241 | { | ||
2242 | 9 | bool write_to_cmdline = has_flag(a, 'c'); | |
2243 | 9 | size_t nr_args = a->nr_args; | |
2244 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 8 times.
|
9 | if (write_to_cmdline && nr_args < 2) { |
2245 | 1 | return error_msg(&e->err, "\"show -c\" requires 2 arguments"); | |
2246 | } | ||
2247 | |||
2248 |
1/2✓ Branch 0 (5→6) taken 8 times.
✗ Branch 1 (5→8) not taken.
|
8 | const char *type = (nr_args > 0) ? a->args[0] : NULL; |
2249 |
2/2✓ Branch 0 (6→7) taken 6 times.
✓ Branch 1 (6→8) taken 2 times.
|
8 | const char *key = (nr_args > 1) ? a->args[1] : NULL; |
2250 | 8 | return show(e, type, key, write_to_cmdline); | |
2251 | } | ||
2252 | |||
2253 | 2 | static bool cmd_suspend(EditorState *e, const CommandArgs *a) | |
2254 | { | ||
2255 | 2 | BUG_ON(a->nr_args); | |
2256 |
1/2✓ Branch 0 (4→5) taken 2 times.
✗ Branch 1 (4→6) not taken.
|
2 | if (e->flags & EFLAG_HEADLESS) { |
2257 | 2 | return error_msg(&e->err, "unavailable in headless mode"); | |
2258 | } | ||
2259 | |||
2260 | ✗ | if (e->status == EDITOR_INITIALIZING) { | |
2261 | ✗ | LOG_WARNING("suspend request ignored"); | |
2262 | ✗ | return false; | |
2263 | } | ||
2264 | |||
2265 | ✗ | if (e->session_leader) { | |
2266 | ✗ | return error_msg(&e->err, "Session leader can't suspend"); | |
2267 | } | ||
2268 | |||
2269 | ✗ | ui_end(e, false); | |
2270 | ✗ | term_cooked(); | |
2271 | ✗ | LOG_INFO("suspending"); | |
2272 | |||
2273 | ✗ | bool suspended = !kill(0, SIGSTOP); | |
2274 | ✗ | if (suspended) { | |
2275 | ✗ | LOG_INFO("resumed"); | |
2276 | } else { | ||
2277 | ✗ | error_msg_errno(&e->err, "kill"); | |
2278 | } | ||
2279 | |||
2280 | ✗ | term_raw(); | |
2281 | ✗ | ui_start(e); | |
2282 | ✗ | return suspended; | |
2283 | } | ||
2284 | |||
2285 | 2 | static bool cmd_tag(EditorState *e, const CommandArgs *a) | |
2286 | { | ||
2287 |
2/2✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→6) taken 1 times.
|
2 | if (has_flag(a, 'r')) { |
2288 | 1 | bookmark_pop(&e->bookmarks, e->window); | |
2289 | 1 | return true; | |
2290 | } | ||
2291 | |||
2292 | 1 | size_t nargs = a->nr_args; | |
2293 | 1 | StringView word_under_cursor; | |
2294 |
1/2✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 1 times.
|
1 | if (nargs == 0) { |
2295 | ✗ | word_under_cursor = view_get_word_under_cursor(e->view); | |
2296 | ✗ | if (word_under_cursor.length == 0) { | |
2297 | return false; | ||
2298 | } | ||
2299 | } | ||
2300 | |||
2301 | 1 | char abc = cmdargs_pick_winning_flag(a, "ABC"); | |
2302 |
1/2✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→12) taken 1 times.
|
1 | size_t idx = abc ? abc - 'A' : e->options.msg_tag; |
2303 | 1 | MessageArray *msgs = &e->messages[idx]; | |
2304 | 1 | clear_messages(msgs); | |
2305 |
1/2✓ Branch 0 (15→16) taken 1 times.
✗ Branch 1 (15→24) not taken.
|
1 | if (!load_tag_file(&e->tagfile, &e->err)) { |
2306 | return false; | ||
2307 | } | ||
2308 | |||
2309 | 1 | const char *filename = e->buffer->abs_filename; | |
2310 |
1/2✗ Branch 0 (16→17) not taken.
✓ Branch 1 (16→20) taken 1 times.
|
1 | if (nargs == 0) { |
2311 | ✗ | tag_lookup(&e->tagfile, msgs, &e->err, &word_under_cursor, filename); | |
2312 | } | ||
2313 | |||
2314 |
2/2✓ Branch 0 (21→18) taken 1 times.
✓ Branch 1 (21→22) taken 1 times.
|
2 | for (size_t i = 0; i < nargs; i++) { |
2315 | 1 | StringView tagname = strview_from_cstring(a->args[i]); | |
2316 | 1 | tag_lookup(&e->tagfile, msgs, &e->err, &tagname, filename); | |
2317 | } | ||
2318 | |||
2319 | 1 | activate_current_message_save(msgs, &e->bookmarks, e->view); | |
2320 | 1 | return (msgs->array.count > 0); | |
2321 | } | ||
2322 | |||
2323 | ✗ | static bool cmd_title(EditorState *e, const CommandArgs *a) | |
2324 | { | ||
2325 | ✗ | Buffer *buffer = e->buffer; | |
2326 | ✗ | if (buffer->abs_filename) { | |
2327 | ✗ | return error_msg(&e->err, "saved buffers can't be retitled"); | |
2328 | } | ||
2329 | |||
2330 | ✗ | buffer_set_display_filename(buffer, xstrdup(a->args[0])); | |
2331 | ✗ | buffer_mark_tabbars_changed(buffer); | |
2332 | ✗ | e->screen_update |= UPDATE_TERM_TITLE; | |
2333 | ✗ | return true; | |
2334 | } | ||
2335 | |||
2336 | 16 | static bool cmd_toggle(EditorState *e, const CommandArgs *a) | |
2337 | { | ||
2338 | 16 | bool global = has_flag(a, 'g'); | |
2339 | 16 | bool verbose = has_flag(a, 'v'); | |
2340 | 16 | const char *option_name = a->args[0]; | |
2341 | 16 | size_t nr_values = a->nr_args - 1; | |
2342 |
1/2✓ Branch 0 (4→5) taken 16 times.
✗ Branch 1 (4→6) not taken.
|
16 | if (nr_values == 0) { |
2343 | 16 | return toggle_option(e, option_name, global, verbose); | |
2344 | } | ||
2345 | |||
2346 | ✗ | char **values = a->args + 1; | |
2347 | ✗ | return toggle_option_values(e, option_name, global, verbose, values, nr_values); | |
2348 | } | ||
2349 | |||
2350 | 1020 | static bool cmd_undo(EditorState *e, const CommandArgs *a) | |
2351 | { | ||
2352 | 1020 | View *view = e->view; | |
2353 | 1020 | bool move_only = has_flag(a, 'm'); | |
2354 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→8) taken 1020 times.
|
1020 | if (move_only) { |
2355 | ✗ | const Change *change = view->buffer->cur_change; | |
2356 | ✗ | if (!change->next) { | |
2357 | // If there's only 1 change, there's nothing meaningful to move to | ||
2358 | return false; | ||
2359 | } | ||
2360 | ✗ | block_iter_goto_offset(&view->cursor, change->offset); | |
2361 | ✗ | view_reset_preferred_x(view); | |
2362 | ✗ | return true; | |
2363 | } | ||
2364 | |||
2365 |
3/4✓ Branch 0 (9→10) taken 44 times.
✓ Branch 1 (9→13) taken 976 times.
✗ Branch 2 (11→12) not taken.
✓ Branch 3 (11→13) taken 44 times.
|
1020 | return undo(view) && unselect(view); |
2366 | } | ||
2367 | |||
2368 | 1 | static bool cmd_unselect(EditorState *e, const CommandArgs *a) | |
2369 | { | ||
2370 | 1 | BUG_ON(a->nr_args); | |
2371 | 1 | return unselect(e->view); | |
2372 | } | ||
2373 | |||
2374 | 16 | static bool cmd_up(EditorState *e, const CommandArgs *a) | |
2375 | { | ||
2376 | 16 | handle_selection_flags(e->view, a); | |
2377 | 16 | move_up(e->view, 1); | |
2378 | 16 | return true; | |
2379 | } | ||
2380 | |||
2381 | 3 | static bool cmd_view(EditorState *e, const CommandArgs *a) | |
2382 | { | ||
2383 | 3 | Window *window = e->window; | |
2384 | 3 | BUG_ON(window->views.count == 0); | |
2385 | 3 | const char *arg = a->args[0]; | |
2386 | 3 | size_t idx; | |
2387 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 3 times.
|
3 | if (streq(arg, "last")) { |
2388 | ✗ | idx = window->views.count - 1; | |
2389 | } else { | ||
2390 |
4/4✓ Branch 0 (7→8) taken 2 times.
✓ Branch 1 (7→9) taken 1 times.
✓ Branch 2 (8→9) taken 1 times.
✓ Branch 3 (8→10) taken 1 times.
|
3 | if (!str_to_size(arg, &idx) || idx == 0) { |
2391 | 2 | return error_msg(&e->err, "Invalid view index: %s", arg); | |
2392 | } | ||
2393 | 1 | idx = MIN(idx, window->views.count) - 1; | |
2394 | } | ||
2395 | 1 | set_view(window->views.ptrs[idx]); | |
2396 | 1 | return true; | |
2397 | } | ||
2398 | |||
2399 | 4 | static bool cmd_wclose(EditorState *e, const CommandArgs *a) | |
2400 | { | ||
2401 | 4 | View *view = window_find_unclosable_view(e->window); | |
2402 | 4 | bool force = has_flag(a, 'f'); | |
2403 |
2/2✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→6) taken 2 times.
|
4 | if (!view || force) { |
2404 | 2 | goto close; | |
2405 | } | ||
2406 | |||
2407 | 2 | bool prompt = has_flag(a, 'p'); | |
2408 | 2 | set_view(view); | |
2409 |
2/2✓ Branch 0 (8→9) taken 1 times.
✓ Branch 1 (8→10) taken 1 times.
|
2 | if (!prompt) { |
2410 | 1 | return error_msg ( | |
2411 | &e->err, | ||
2412 | "Save modified files or run 'wclose -f' to close " | ||
2413 | "window without saving" | ||
2414 | ); | ||
2415 | } | ||
2416 | |||
2417 |
1/2✓ Branch 0 (10→11) taken 1 times.
✗ Branch 1 (10→12) not taken.
|
1 | if (unlikely(e->flags & EFLAG_HEADLESS)) { |
2418 | 1 | return error_msg(&e->err, "-p flag unavailable in headless mode"); | |
2419 | } | ||
2420 | |||
2421 | ✗ | if (dialog_prompt(e, "Close window without saving? [y/N]", "ny") != 'y') { | |
2422 | return false; | ||
2423 | } | ||
2424 | |||
2425 | ✗ | close: | |
2426 | 2 | window_close(e->window); | |
2427 | 2 | return true; | |
2428 | } | ||
2429 | |||
2430 | 1 | static bool cmd_wflip(EditorState *e, const CommandArgs *a) | |
2431 | { | ||
2432 | 1 | BUG_ON(a->nr_args); | |
2433 | 1 | Frame *frame = e->window->frame; | |
2434 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 1 times.
|
1 | if (!frame->parent) { |
2435 | return false; | ||
2436 | } | ||
2437 | ✗ | frame->parent->vertical ^= 1; | |
2438 | ✗ | e->screen_update |= UPDATE_ALL_WINDOWS; | |
2439 | ✗ | return true; | |
2440 | } | ||
2441 | |||
2442 | 1 | static bool cmd_wnext(EditorState *e, const CommandArgs *a) | |
2443 | { | ||
2444 | 1 | BUG_ON(a->nr_args); | |
2445 | 1 | e->window = window_next(e->window); | |
2446 | 1 | set_view(e->window->view); | |
2447 | 1 | e->screen_update |= UPDATE_ALL; | |
2448 | 1 | frame_debug(e->root_frame); | |
2449 | 1 | return true; | |
2450 | } | ||
2451 | |||
2452 | 1 | static bool cmd_word_bwd(EditorState *e, const CommandArgs *a) | |
2453 | { | ||
2454 | 1 | handle_selection_flags(e->view, a); | |
2455 | 1 | bool skip_non_word = has_flag(a, 's'); | |
2456 | 1 | word_bwd(&e->view->cursor, skip_non_word); | |
2457 | 1 | view_reset_preferred_x(e->view); | |
2458 | 1 | return true; | |
2459 | } | ||
2460 | |||
2461 | 4 | static bool cmd_word_fwd(EditorState *e, const CommandArgs *a) | |
2462 | { | ||
2463 | 4 | handle_selection_flags(e->view, a); | |
2464 | 4 | bool skip_non_word = has_flag(a, 's'); | |
2465 | 4 | word_fwd(&e->view->cursor, skip_non_word); | |
2466 | 4 | view_reset_preferred_x(e->view); | |
2467 | 4 | return true; | |
2468 | } | ||
2469 | |||
2470 | 1 | static bool cmd_wprev(EditorState *e, const CommandArgs *a) | |
2471 | { | ||
2472 | 1 | BUG_ON(a->nr_args); | |
2473 | 1 | e->window = window_prev(e->window); | |
2474 | 1 | set_view(e->window->view); | |
2475 | 1 | e->screen_update |= UPDATE_ALL; | |
2476 | 1 | frame_debug(e->root_frame); | |
2477 | 1 | return true; | |
2478 | } | ||
2479 | |||
2480 | 4 | static bool cmd_wrap_paragraph(EditorState *e, const CommandArgs *a) | |
2481 | { | ||
2482 | 4 | const char *arg = a->args[0]; | |
2483 | 4 | unsigned int width = e->buffer->options.text_width; | |
2484 |
2/2✓ Branch 0 (2→3) taken 3 times.
✓ Branch 1 (2→9) taken 1 times.
|
4 | if (arg) { |
2485 |
2/2✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→6) taken 2 times.
|
3 | if (!str_to_uint(arg, &width)) { |
2486 | 1 | return error_msg(&e->err, "invalid paragraph width: %s", arg); | |
2487 | } | ||
2488 | 2 | unsigned int max = TEXT_WIDTH_MAX; | |
2489 |
3/4✓ Branch 0 (6→7) taken 2 times.
✗ Branch 1 (6→8) not taken.
✓ Branch 2 (7→8) taken 1 times.
✓ Branch 3 (7→9) taken 1 times.
|
2 | if (width < 1 || width > max) { |
2490 | 1 | return error_msg(&e->err, "width must be between 1 and %u", max); | |
2491 | } | ||
2492 | } | ||
2493 | 2 | wrap_paragraph(e->view, width); | |
2494 | 2 | return true; | |
2495 | } | ||
2496 | |||
2497 | 2 | static bool cmd_wresize(EditorState *e, const CommandArgs *a) | |
2498 | { | ||
2499 | 2 | Window *window = e->window; | |
2500 |
2/2✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→18) taken 1 times.
|
2 | if (!window->frame->parent) { |
2501 | // Only window | ||
2502 | return false; | ||
2503 | } | ||
2504 | |||
2505 | 1 | ResizeDirection dir = RESIZE_DIRECTION_AUTO; | |
2506 |
1/3✗ Branch 0 (3→4) not taken.
✗ Branch 1 (3→5) not taken.
✓ Branch 2 (3→6) taken 1 times.
|
1 | switch (last_flag(a)) { |
2507 | ✗ | case 'h': | |
2508 | ✗ | dir = RESIZE_DIRECTION_HORIZONTAL; | |
2509 | ✗ | break; | |
2510 | ✗ | case 'v': | |
2511 | ✗ | dir = RESIZE_DIRECTION_VERTICAL; | |
2512 | ✗ | break; | |
2513 | } | ||
2514 | |||
2515 | 1 | const char *arg = a->args[0]; | |
2516 |
1/2✓ Branch 0 (6→7) taken 1 times.
✗ Branch 1 (6→15) not taken.
|
1 | if (arg) { |
2517 | 1 | int n; | |
2518 |
1/2✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→11) not taken.
|
1 | if (!str_to_int(arg, &n)) { |
2519 | 1 | return error_msg(&e->err, "Invalid resize value: %s", arg); | |
2520 | } | ||
2521 | ✗ | if (arg[0] == '+' || arg[0] == '-') { | |
2522 | ✗ | frame_add_to_size(window->frame, dir, n); | |
2523 | } else { | ||
2524 | ✗ | frame_resize(window->frame, dir, n); | |
2525 | } | ||
2526 | } else { | ||
2527 | ✗ | frame_equalize_sizes(window->frame->parent); | |
2528 | } | ||
2529 | |||
2530 | ✗ | e->screen_update |= UPDATE_ALL_WINDOWS; | |
2531 | ✗ | frame_debug(e->root_frame); | |
2532 | // TODO: return false if resize failed? | ||
2533 | ✗ | return true; | |
2534 | } | ||
2535 | |||
2536 | 3 | static bool cmd_wsplit(EditorState *e, const CommandArgs *a) | |
2537 | { | ||
2538 | 3 | bool before = has_flag(a, 'b'); | |
2539 |
1/4✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 3 times.
✗ Branch 2 (5→6) not taken.
✗ Branch 3 (5→7) not taken.
|
3 | bool use_glob = has_flag(a, 'g') && a->nr_args > 0; |
2540 | 3 | bool vertical = has_flag(a, 'h'); | |
2541 | 3 | bool root = has_flag(a, 'r'); | |
2542 | 3 | bool temporary = has_flag(a, 't'); | |
2543 |
3/4✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→13) taken 2 times.
✗ Branch 2 (12→13) not taken.
✓ Branch 3 (12→15) taken 1 times.
|
3 | bool empty = temporary || has_flag(a, 'n'); |
2544 | |||
2545 |
2/2✓ Branch 0 (13→14) taken 1 times.
✓ Branch 1 (13→15) taken 1 times.
|
2 | if (unlikely(empty && a->nr_args > 0)) { |
2546 | 1 | return error_msg(&e->err, "flags [-n|-t] can't be used with [filename] arguments"); | |
2547 | } | ||
2548 | |||
2549 | 2 | char **paths = a->args; | |
2550 | 2 | glob_t globbuf; | |
2551 |
1/2✗ Branch 0 (15→16) not taken.
✓ Branch 1 (15→19) taken 2 times.
|
2 | if (use_glob) { |
2552 | ✗ | if (!xglob(&e->err, a->args, &globbuf)) { | |
2553 | return false; | ||
2554 | } | ||
2555 | ✗ | paths = globbuf.gl_pathv; | |
2556 | } | ||
2557 | |||
2558 | 2 | Frame *frame; | |
2559 |
1/2✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→21) taken 2 times.
|
2 | if (root) { |
2560 | ✗ | frame = frame_split_root(e, vertical, before); | |
2561 | } else { | ||
2562 | 2 | frame = frame_split(e->window, vertical, before); | |
2563 | } | ||
2564 | |||
2565 | 2 | View *save = e->view; | |
2566 | 2 | e->window = frame->window; | |
2567 | 2 | e->view = NULL; | |
2568 | 2 | e->buffer = NULL; | |
2569 | 2 | e->screen_update |= UPDATE_ALL; | |
2570 | |||
2571 | 2 | View *view; | |
2572 |
2/2✓ Branch 0 (22→23) taken 1 times.
✓ Branch 1 (22→25) taken 1 times.
|
2 | if (empty) { |
2573 | 1 | view = window_open_new_file(e->window); | |
2574 | 1 | view->buffer->temporary = temporary; | |
2575 |
1/2✗ Branch 0 (25→26) not taken.
✓ Branch 1 (25→27) taken 1 times.
|
1 | } else if (paths[0]) { |
2576 | ✗ | view = window_open_files(e->window, paths, NULL); | |
2577 | } else { | ||
2578 | 1 | view = window_add_buffer(e->window, save->buffer); | |
2579 | 1 | view->cursor = save->cursor; | |
2580 | 1 | set_view(view); | |
2581 | } | ||
2582 | |||
2583 |
1/2✗ Branch 0 (29→30) not taken.
✓ Branch 1 (29→31) taken 2 times.
|
2 | if (use_glob) { |
2584 | ✗ | globfree(&globbuf); | |
2585 | } | ||
2586 | |||
2587 |
1/2✗ Branch 0 (31→32) not taken.
✓ Branch 1 (31→34) taken 2 times.
|
2 | if (!view) { |
2588 | // Open failed, remove new window | ||
2589 | ✗ | frame_remove(e, e->window->frame); | |
2590 | ✗ | e->view = save; | |
2591 | ✗ | e->buffer = save->buffer; | |
2592 | ✗ | e->window = save->window; | |
2593 | } | ||
2594 | |||
2595 | 2 | frame_debug(e->root_frame); | |
2596 | 2 | return !!view; | |
2597 | } | ||
2598 | |||
2599 | 1 | static bool cmd_wswap(EditorState *e, const CommandArgs *a) | |
2600 | { | ||
2601 | 1 | BUG_ON(a->nr_args); | |
2602 | 1 | Frame *frame = e->window->frame; | |
2603 | 1 | Frame *parent = frame->parent; | |
2604 |
1/2✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→9) not taken.
|
1 | if (!parent) { |
2605 | return false; | ||
2606 | } | ||
2607 | |||
2608 | 1 | PointerArray *pframes = &parent->frames; | |
2609 | 1 | size_t current = ptr_array_xindex(pframes, frame); | |
2610 | 1 | size_t next = wrapping_increment(current, pframes->count); | |
2611 | 1 | ptr_array_swap(pframes, current, next); | |
2612 | 1 | e->screen_update |= UPDATE_ALL_WINDOWS; | |
2613 | 1 | return true; | |
2614 | } | ||
2615 | |||
2616 | enum { | ||
2617 | // Short aliases for CommandOptions: | ||
2618 | NA = 0, | ||
2619 | RC = CMDOPT_ALLOW_IN_RC, | ||
2620 | NFAA = CMDOPT_NO_FLAGS_AFTER_ARGS, | ||
2621 | }; | ||
2622 | |||
2623 | static const Command cmds[] = { | ||
2624 | {"alias", "", RC | NFAA, 1, 2, cmd_alias}, | ||
2625 | {"bind", "cnqsT=", RC | NFAA, 1, 2, cmd_bind}, | ||
2626 | {"blkdown", "cl", NA, 0, 0, cmd_blkdown}, | ||
2627 | {"blkup", "cl", NA, 0, 0, cmd_blkup}, | ||
2628 | {"bof", "cl", NA, 0, 0, cmd_bof}, | ||
2629 | {"bol", "clrst", NA, 0, 0, cmd_bol}, | ||
2630 | {"bolsf", "cl", NA, 0, 0, cmd_bolsf}, | ||
2631 | {"bookmark", "r", NA, 0, 0, cmd_bookmark}, | ||
2632 | {"case", "lu", NA, 0, 0, cmd_case}, | ||
2633 | {"cd", "v", RC, 1, 1, cmd_cd}, | ||
2634 | {"center-view", "", NA, 0, 0, cmd_center_view}, | ||
2635 | {"clear", "Ii", NA, 0, 0, cmd_clear}, | ||
2636 | {"close", "fpqw", NA, 0, 0, cmd_close}, | ||
2637 | {"command", "", NFAA, 0, 1, cmd_command}, | ||
2638 | {"compile", "1ABCps", NFAA, 2, -1, cmd_compile}, | ||
2639 | {"copy", "bikp", NA, 0, 1, cmd_copy}, | ||
2640 | {"cursor", "", RC, 0, 3, cmd_cursor}, | ||
2641 | {"cut", "", NA, 0, 0, cmd_cut}, | ||
2642 | {"def-mode", "Uu", RC, 1, 16, cmd_def_mode}, | ||
2643 | {"delete", "", NA, 0, 0, cmd_delete}, | ||
2644 | {"delete-eol", "n", NA, 0, 0, cmd_delete_eol}, | ||
2645 | {"delete-line", "S", NA, 0, 0, cmd_delete_line}, | ||
2646 | {"delete-word", "s", NA, 0, 0, cmd_delete_word}, | ||
2647 | {"down", "cl", NA, 0, 0, cmd_down}, | ||
2648 | {"eof", "cl", NA, 0, 0, cmd_eof}, | ||
2649 | {"eol", "cl", NA, 0, 0, cmd_eol}, | ||
2650 | {"eolsf", "cl", NA, 0, 0, cmd_eolsf}, | ||
2651 | {"erase", "", NA, 0, 0, cmd_erase}, | ||
2652 | {"erase-bol", "", NA, 0, 0, cmd_erase_bol}, | ||
2653 | {"erase-word", "s", NA, 0, 0, cmd_erase_word}, | ||
2654 | {"errorfmt", "i", RC, 1, 2 + ERRORFMT_CAPTURE_MAX, cmd_errorfmt}, | ||
2655 | {"exec", "e=i=o=lmnpst", NFAA, 1, -1, cmd_exec}, | ||
2656 | {"ft", "bcfi", RC | NFAA, 2, -1, cmd_ft}, | ||
2657 | {"hi", "c", RC | NFAA, 0, -1, cmd_hi}, | ||
2658 | {"include", "bq", RC, 1, 1, cmd_include}, | ||
2659 | {"insert", "km", NA, 1, 1, cmd_insert}, | ||
2660 | {"join", "", NA, 0, 1, cmd_join}, | ||
2661 | {"left", "cl", NA, 0, 0, cmd_left}, | ||
2662 | {"line", "cl", NA, 1, 1, cmd_line}, | ||
2663 | {"macro", "", NA, 1, 1, cmd_macro}, | ||
2664 | {"match-bracket", "cl", NA, 0, 0, cmd_match_bracket}, | ||
2665 | {"mode", "", RC, 1, 1, cmd_mode}, | ||
2666 | {"move-tab", "", NA, 1, 1, cmd_move_tab}, | ||
2667 | {"msg", "ABCNPnp", NA, 0, 1, cmd_msg}, | ||
2668 | {"new-line", "Iai", NA, 0, 0, cmd_new_line}, | ||
2669 | {"next", "", NA, 0, 0, cmd_next}, | ||
2670 | {"open", "e=gt", NA, 0, -1, cmd_open}, | ||
2671 | {"option", "r", RC | NFAA, 3, -1, cmd_option}, | ||
2672 | {"paste", "acm", NA, 0, 0, cmd_paste}, | ||
2673 | {"pgdown", "cl", NA, 0, 0, cmd_pgdown}, | ||
2674 | {"pgup", "cl", NA, 0, 0, cmd_pgup}, | ||
2675 | {"prev", "", NA, 0, 0, cmd_prev}, | ||
2676 | {"quit", "CFHSfp", NA, 0, 1, cmd_quit}, | ||
2677 | {"redo", "", NA, 0, 1, cmd_redo}, | ||
2678 | {"refresh", "", NA, 0, 0, cmd_refresh}, | ||
2679 | {"reopen", "", NA, 0, 0, cmd_reopen}, | ||
2680 | {"repeat", "", NFAA, 2, -1, cmd_repeat}, | ||
2681 | {"replace", "bcegi", NA, 1, 2, cmd_replace}, | ||
2682 | {"right", "cl", NA, 0, 0, cmd_right}, | ||
2683 | {"save", "Bbde=fpu", NA, 0, 1, cmd_save}, | ||
2684 | {"scroll-down", "Mcl", NA, 0, 0, cmd_scroll_down}, | ||
2685 | {"scroll-pgdown", "chl", NA, 0, 0, cmd_scroll_pgdown}, | ||
2686 | {"scroll-pgup", "chl", NA, 0, 0, cmd_scroll_pgup}, | ||
2687 | {"scroll-up", "Mcl", NA, 0, 0, cmd_scroll_up}, | ||
2688 | {"search", "Haceilnprswx", NA, 0, 1, cmd_search}, | ||
2689 | {"select", "kl", NA, 0, 0, cmd_select}, | ||
2690 | {"select-block", "", NA, 0, 0, cmd_select_block}, | ||
2691 | {"set", "gl", RC, 1, -1, cmd_set}, | ||
2692 | {"setenv", "", RC, 1, 2, cmd_setenv}, | ||
2693 | {"shift", "", NA, 1, 1, cmd_shift}, | ||
2694 | {"show", "c", NA, 0, 2, cmd_show}, | ||
2695 | {"suspend", "", NA, 0, 0, cmd_suspend}, | ||
2696 | {"tag", "ABCr", NA, 0, -1, cmd_tag}, | ||
2697 | {"title", "", NA, 1, 1, cmd_title}, | ||
2698 | {"toggle", "gv", NA, 1, -1, cmd_toggle}, | ||
2699 | {"undo", "m", NA, 0, 0, cmd_undo}, | ||
2700 | {"unselect", "", NA, 0, 0, cmd_unselect}, | ||
2701 | {"up", "cl", NA, 0, 0, cmd_up}, | ||
2702 | {"view", "", NA, 1, 1, cmd_view}, | ||
2703 | {"wclose", "fp", NA, 0, 0, cmd_wclose}, | ||
2704 | {"wflip", "", NA, 0, 0, cmd_wflip}, | ||
2705 | {"wnext", "", NA, 0, 0, cmd_wnext}, | ||
2706 | {"word-bwd", "cls", NA, 0, 0, cmd_word_bwd}, | ||
2707 | {"word-fwd", "cls", NA, 0, 0, cmd_word_fwd}, | ||
2708 | {"wprev", "", NA, 0, 0, cmd_wprev}, | ||
2709 | {"wrap-paragraph", "", NA, 0, 1, cmd_wrap_paragraph}, | ||
2710 | {"wresize", "hv", NA, 0, 1, cmd_wresize}, | ||
2711 | {"wsplit", "bghnrt", NA, 0, -1, cmd_wsplit}, | ||
2712 | {"wswap", "", NA, 0, 0, cmd_wswap}, | ||
2713 | }; | ||
2714 | |||
2715 | 247 | static bool allow_macro_recording(const Command *cmd, char **args) | |
2716 | { | ||
2717 | 247 | const CommandFunc fn = cmd->cmd; | |
2718 |
5/6✓ Branch 0 (2→3) taken 225 times.
✓ Branch 1 (2→15) taken 22 times.
✓ Branch 2 (3→4) taken 203 times.
✓ Branch 3 (3→15) taken 22 times.
✓ Branch 4 (4→5) taken 203 times.
✗ Branch 5 (4→15) not taken.
|
247 | if (fn == cmd_macro || fn == cmd_command || fn == cmd_mode) { |
2719 | return false; | ||
2720 | } | ||
2721 | |||
2722 |
2/2✓ Branch 0 (5→6) taken 154 times.
✓ Branch 1 (5→15) taken 49 times.
|
203 | if (fn == cmd_search) { |
2723 | 154 | char **args_copy = copy_string_array(args, string_array_length(args)); | |
2724 | 154 | CommandArgs a = cmdargs_new(args_copy); | |
2725 | 154 | bool ret = true; | |
2726 |
1/2✓ Branch 0 (8→9) taken 154 times.
✗ Branch 1 (8→13) not taken.
|
154 | if (do_parse_args(cmd, &a) == ARGERR_NONE) { |
2727 | 154 | CommandFlagSet npw = cmdargs_flagset_from_str("npw"); | |
2728 |
4/4✓ Branch 0 (10→11) taken 110 times.
✓ Branch 1 (10→13) taken 44 times.
✓ Branch 2 (11→12) taken 44 times.
✓ Branch 3 (11→13) taken 66 times.
|
154 | if (a.nr_args == 0 && !(a.flag_set & npw)) { |
2729 | // If command is "search" with no [pattern] argument or | ||
2730 | // [-n|-p|-w] flags, the command would put the editor into | ||
2731 | // search mode, which shouldn't be recorded. | ||
2732 | 44 | ret = false; | |
2733 | } | ||
2734 | } | ||
2735 | 154 | free_string_array(args_copy); | |
2736 | 154 | return ret; | |
2737 | } | ||
2738 | |||
2739 | if (fn == cmd_exec) { | ||
2740 | // TODO: don't record -o with open/tag/eval/msg | ||
2741 | } | ||
2742 | |||
2743 | return true; | ||
2744 | } | ||
2745 | |||
2746 | 22 | UNITTEST { | |
2747 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
2748 | 22 | const char *args[4] = {NULL}; | |
2749 | 22 | char **argp = (char**)args; | |
2750 | 22 | const Command *cmd = find_normal_command("left"); | |
2751 | 22 | BUG_ON(!cmd); | |
2752 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2753 | |||
2754 | 22 | cmd = find_normal_command("exec"); | |
2755 | 22 | BUG_ON(!cmd); | |
2756 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2757 | |||
2758 | 22 | cmd = find_normal_command("command"); | |
2759 | 22 | BUG_ON(!cmd); | |
2760 | 22 | BUG_ON(allow_macro_recording(cmd, argp)); | |
2761 | |||
2762 | 22 | cmd = find_normal_command("macro"); | |
2763 | 22 | BUG_ON(!cmd); | |
2764 | 22 | BUG_ON(allow_macro_recording(cmd, argp)); | |
2765 | |||
2766 | 22 | cmd = find_normal_command("search"); | |
2767 | 22 | BUG_ON(!cmd); | |
2768 | 22 | BUG_ON(allow_macro_recording(cmd, argp)); | |
2769 | 22 | args[0] = "xyz"; | |
2770 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2771 | 22 | args[0] = "-n"; | |
2772 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2773 | 22 | args[0] = "-p"; | |
2774 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2775 | 22 | args[0] = "-w"; | |
2776 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2777 | 22 | args[0] = "-Hr"; | |
2778 | 22 | BUG_ON(allow_macro_recording(cmd, argp)); | |
2779 | 22 | args[1] = "str"; | |
2780 | 22 | BUG_ON(!allow_macro_recording(cmd, argp)); | |
2781 | // NOLINTEND(bugprone-assert-side-effect) | ||
2782 | 22 | } | |
2783 | |||
2784 | 5 | static void record_command(EditorState *e, const Command *cmd, char **args) | |
2785 | { | ||
2786 |
1/2✓ Branch 0 (3→4) taken 5 times.
✗ Branch 1 (3→5) not taken.
|
5 | if (!allow_macro_recording(cmd, args)) { |
2787 | return; | ||
2788 | } | ||
2789 | 5 | macro_command_hook(&e->macro, cmd->name, args); | |
2790 | } | ||
2791 | |||
2792 | 5659 | const Command *find_normal_command(const char *name) | |
2793 | { | ||
2794 | 5659 | return BSEARCH(name, cmds, command_cmp); | |
2795 | } | ||
2796 | |||
2797 | const CommandSet normal_commands = { | ||
2798 | .lookup = find_normal_command, | ||
2799 | .macro_record = record_command, | ||
2800 | }; | ||
2801 | |||
2802 | 116 | const char *find_normal_alias(const EditorState *e, const char *name) | |
2803 | { | ||
2804 | 116 | return find_alias(&e->aliases, name); | |
2805 | } | ||
2806 | |||
2807 | 166 | bool handle_normal_command(EditorState *e, const char *cmd, bool allow_recording) | |
2808 | { | ||
2809 | 166 | CommandRunner runner = normal_mode_cmdrunner(e); | |
2810 | 166 | BUG_ON(runner.flags & CMDRUNNER_ALLOW_RECORDING); | |
2811 |
2/2✓ Branch 0 (4→5) taken 164 times.
✓ Branch 1 (4→6) taken 2 times.
|
166 | runner.flags |= allow_recording ? CMDRUNNER_ALLOW_RECORDING : 0; |
2812 | 166 | return handle_command(&runner, cmd); | |
2813 | } | ||
2814 | |||
2815 | 41 | void exec_normal_config(EditorState *e, StringView config) | |
2816 | { | ||
2817 | 41 | CommandRunner runner = normal_mode_cmdrunner(e); | |
2818 | 41 | exec_config(&runner, config); | |
2819 | 41 | } | |
2820 | |||
2821 | 102 | int read_normal_config(EditorState *e, const char *filename, ConfigFlags flags) | |
2822 | { | ||
2823 | 102 | CommandRunner runner = normal_mode_cmdrunner(e); | |
2824 | 102 | return read_config(&runner, filename, flags); | |
2825 | } | ||
2826 | |||
2827 | 5 | void collect_normal_commands(PointerArray *a, const char *prefix) | |
2828 | { | ||
2829 | 5 | COLLECT_STRING_FIELDS(cmds, name, a, prefix); | |
2830 | 5 | } | |
2831 | |||
2832 | 22 | UNITTEST { | |
2833 | 22 | CHECK_BSEARCH_ARRAY(cmds, name); | |
2834 | |||
2835 |
2/2✓ Branch 0 (15→4) taken 1958 times.
✓ Branch 1 (15→16) taken 22 times.
|
1980 | for (size_t i = 0, n = ARRAYLEN(cmds); i < n; i++) { |
2836 | // Check that flags array is null-terminated within bounds | ||
2837 | 1958 | const char *const flags = cmds[i].flags; | |
2838 | 1958 | BUG_ON(flags[ARRAYLEN(cmds[0].flags) - 1] != '\0'); | |
2839 | |||
2840 | // Count number of real flags (i.e. not including '=') | ||
2841 | size_t nr_real_flags = 0; | ||
2842 |
2/2✓ Branch 0 (11→6) taken 3916 times.
✓ Branch 1 (11→12) taken 1958 times.
|
5874 | for (size_t j = 0; flags[j]; j++) { |
2843 | 3916 | unsigned char flag = flags[j]; | |
2844 |
2/2✓ Branch 0 (6→7) taken 3784 times.
✓ Branch 1 (6→8) taken 132 times.
|
3916 | if (ascii_isalnum(flag)) { |
2845 | 3784 | nr_real_flags++; | |
2846 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 132 times.
|
132 | } else if (flag != '=') { |
2847 | − | BUG("invalid command flag: 0x%02hhX", flag); | |
2848 | } | ||
2849 | } | ||
2850 | |||
2851 | // Check that max. number of real flags fits in CommandArgs::flags | ||
2852 | // array (and also leaves 1 byte for null-terminator) | ||
2853 | 1958 | CommandArgs a; | |
2854 | 1958 | BUG_ON(nr_real_flags >= ARRAYLEN(a.flags)); | |
2855 | } | ||
2856 | 22 | } | |
2857 |