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