arglist.c (37174B)
1 // arglist.c: functions for dealing with the argument list 2 3 #include <assert.h> 4 #include <stdbool.h> 5 #include <stdint.h> 6 #include <string.h> 7 8 #include "auto/config.h" 9 #include "nvim/arglist.h" 10 #include "nvim/ascii_defs.h" 11 #include "nvim/autocmd.h" 12 #include "nvim/buffer.h" 13 #include "nvim/buffer_defs.h" 14 #include "nvim/charset.h" 15 #include "nvim/cmdexpand_defs.h" 16 #include "nvim/errors.h" 17 #include "nvim/eval/typval.h" 18 #include "nvim/eval/typval_defs.h" 19 #include "nvim/eval/window.h" 20 #include "nvim/ex_cmds.h" 21 #include "nvim/ex_cmds2.h" 22 #include "nvim/ex_cmds_defs.h" 23 #include "nvim/ex_getln.h" 24 #include "nvim/fileio.h" 25 #include "nvim/garray.h" 26 #include "nvim/garray_defs.h" 27 #include "nvim/gettext_defs.h" 28 #include "nvim/globals.h" 29 #include "nvim/macros_defs.h" 30 #include "nvim/mark.h" 31 #include "nvim/memline_defs.h" 32 #include "nvim/memory.h" 33 #include "nvim/message.h" 34 #include "nvim/normal.h" 35 #include "nvim/option.h" 36 #include "nvim/option_vars.h" 37 #include "nvim/os/input.h" 38 #include "nvim/path.h" 39 #include "nvim/pos_defs.h" 40 #include "nvim/regexp.h" 41 #include "nvim/regexp_defs.h" 42 #include "nvim/types_defs.h" 43 #include "nvim/undo.h" 44 #include "nvim/version.h" 45 #include "nvim/vim_defs.h" 46 #include "nvim/window.h" 47 48 /// State used by the :all command to open all the files in the argument list in 49 /// separate windows. 50 typedef struct { 51 alist_T *alist; ///< argument list to be used 52 int had_tab; 53 bool keep_tabs; 54 bool forceit; 55 56 bool use_firstwin; ///< use first window for arglist 57 uint8_t *opened; ///< Array of weight for which args are open: 58 ///< 0: not opened 59 ///< 1: opened in other tab 60 ///< 2: opened in curtab 61 ///< 3: opened in curtab and curwin 62 int opened_len; ///< length of opened[] 63 win_T *new_curwin; 64 tabpage_T *new_curtab; 65 } arg_all_state_T; 66 67 #include "arglist.c.generated.h" 68 69 static const char e_window_layout_changed_unexpectedly[] 70 = N_("E249: Window layout changed unexpectedly"); 71 72 enum { 73 AL_SET = 1, 74 AL_ADD = 2, 75 AL_DEL = 3, 76 }; 77 78 /// This flag is set whenever the argument list is being changed and calling a 79 /// function that might trigger an autocommand. 80 static bool arglist_locked = false; 81 82 static int check_arglist_locked(void) 83 { 84 if (arglist_locked) { 85 emsg(_(e_cannot_change_arglist_recursively)); 86 return FAIL; 87 } 88 return OK; 89 } 90 91 /// Clear an argument list: free all file names and reset it to zero entries. 92 void alist_clear(alist_T *al) 93 { 94 if (check_arglist_locked() == FAIL) { 95 return; 96 } 97 #define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) 98 GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); 99 } 100 101 /// Init an argument list. 102 void alist_init(alist_T *al) 103 { 104 ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); 105 } 106 107 /// Remove a reference from an argument list. 108 /// Ignored when the argument list is the global one. 109 /// If the argument list is no longer used by any window, free it. 110 void alist_unlink(alist_T *al) 111 { 112 if (al != &global_alist && --al->al_refcount <= 0) { 113 alist_clear(al); 114 xfree(al); 115 } 116 } 117 118 /// Create a new argument list and use it for the current window. 119 void alist_new(void) 120 { 121 curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); 122 curwin->w_alist->al_refcount = 1; 123 curwin->w_alist->id = ++max_alist_id; 124 alist_init(curwin->w_alist); 125 } 126 127 #if !defined(UNIX) 128 129 /// Expand the file names in the global argument list. 130 /// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer 131 /// numbers to be re-used. 132 void alist_expand(int *fnum_list, int fnum_len) 133 { 134 char *save_p_su = p_su; 135 136 char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); 137 138 // Don't use 'suffixes' here. This should work like the shell did the 139 // expansion. Also, the vimrc file isn't read yet, thus the user 140 // can't set the options. 141 p_su = empty_string_option; 142 for (int i = 0; i < GARGCOUNT; i++) { 143 old_arg_files[i] = xstrdup(GARGLIST[i].ae_fname); 144 } 145 int old_arg_count = GARGCOUNT; 146 char **new_arg_files; 147 int new_arg_file_count; 148 if (expand_wildcards(old_arg_count, old_arg_files, 149 &new_arg_file_count, &new_arg_files, 150 EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK 151 && new_arg_file_count > 0) { 152 alist_set(&global_alist, new_arg_file_count, new_arg_files, 153 true, fnum_list, fnum_len); 154 FreeWild(old_arg_count, old_arg_files); 155 } 156 p_su = save_p_su; 157 } 158 #endif 159 160 /// Set the argument list for the current window. 161 /// Takes over the allocated files[] and the allocated fnames in it. 162 void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) 163 { 164 if (check_arglist_locked() == FAIL) { 165 return; 166 } 167 168 alist_clear(al); 169 ga_grow(&al->al_ga, count); 170 { 171 for (int i = 0; i < count; i++) { 172 if (got_int) { 173 // When adding many buffers this can take a long time. Allow 174 // interrupting here. 175 while (i < count) { 176 xfree(files[i++]); 177 } 178 break; 179 } 180 181 // May set buffer name of a buffer previously used for the 182 // argument list, so that it's re-used by alist_add. 183 if (fnum_list != NULL && i < fnum_len) { 184 arglist_locked = true; 185 buf_set_name(fnum_list[i], files[i]); 186 arglist_locked = false; 187 } 188 189 alist_add(al, files[i], use_curbuf ? 2 : 1); 190 os_breakcheck(); 191 } 192 xfree(files); 193 } 194 195 if (al == &global_alist) { 196 arg_had_last = false; 197 } 198 } 199 200 /// Add file "fname" to argument list "al". 201 /// "fname" must have been allocated and "al" must have been checked for room. 202 /// 203 /// May trigger Buf* autocommands 204 /// 205 /// @param set_fnum 1: set buffer number; 2: re-use curbuf 206 void alist_add(alist_T *al, char *fname, int set_fnum) 207 { 208 if (fname == NULL) { // don't add NULL file names 209 return; 210 } 211 if (check_arglist_locked() == FAIL) { 212 return; 213 } 214 arglist_locked = true; 215 curwin->w_locked = true; 216 217 #ifdef BACKSLASH_IN_FILENAME 218 slash_adjust(fname); 219 #endif 220 AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; 221 if (set_fnum > 0) { 222 AARGLIST(al)[al->al_ga.ga_len].ae_fnum = 223 buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); 224 } 225 al->al_ga.ga_len++; 226 227 arglist_locked = false; 228 curwin->w_locked = false; 229 } 230 231 #if defined(BACKSLASH_IN_FILENAME) 232 233 /// Adjust slashes in file names. Called after 'shellslash' was set. 234 void alist_slash_adjust(void) 235 { 236 for (int i = 0; i < GARGCOUNT; i++) { 237 if (GARGLIST[i].ae_fname != NULL) { 238 slash_adjust(GARGLIST[i].ae_fname); 239 } 240 } 241 242 FOR_ALL_TAB_WINDOWS(tp, wp) { 243 if (wp->w_alist != &global_alist) { 244 for (int i = 0; i < WARGCOUNT(wp); i++) { 245 if (WARGLIST(wp)[i].ae_fname != NULL) { 246 slash_adjust(WARGLIST(wp)[i].ae_fname); 247 } 248 } 249 } 250 } 251 } 252 253 #endif 254 255 /// Isolate one argument, taking backticks. 256 /// Changes the argument in-place, puts a NUL after it. Backticks remain. 257 /// 258 /// @return a pointer to the start of the next argument. 259 static char *do_one_arg(char *str) 260 { 261 char *p; 262 263 bool inbacktick = false; 264 for (p = str; *str; str++) { 265 // When the backslash is used for escaping the special meaning of a 266 // character we need to keep it until wildcard expansion. 267 if (rem_backslash(str)) { 268 *p++ = *str++; 269 *p++ = *str; 270 } else { 271 // An item ends at a space not in backticks 272 if (!inbacktick && ascii_isspace(*str)) { 273 break; 274 } 275 if (*str == '`') { 276 inbacktick ^= true; 277 } 278 *p++ = *str; 279 } 280 } 281 str = skipwhite(str); 282 *p = NUL; 283 284 return str; 285 } 286 287 /// Separate the arguments in "str" and return a list of pointers in the 288 /// growarray "gap". 289 static void get_arglist(garray_T *gap, char *str, bool escaped) 290 { 291 ga_init(gap, (int)sizeof(char *), 20); 292 while (*str != NUL) { 293 GA_APPEND(char *, gap, str); 294 295 // If str is escaped, don't handle backslashes or spaces 296 if (!escaped) { 297 return; 298 } 299 300 // Isolate one argument, change it in-place, put a NUL after it. 301 str = do_one_arg(str); 302 } 303 } 304 305 /// Parse a list of arguments (file names), expand them and return in 306 /// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. 307 /// 308 /// @return FAIL or OK. 309 int get_arglist_exp(char *str, int *fcountp, char ***fnamesp, bool wig) 310 { 311 garray_T ga; 312 int i; 313 314 get_arglist(&ga, str, true); 315 316 if (wig) { 317 i = expand_wildcards(ga.ga_len, ga.ga_data, 318 fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); 319 } else { 320 i = gen_expand_wildcards(ga.ga_len, ga.ga_data, 321 fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); 322 } 323 324 ga_clear(&ga); 325 return i; 326 } 327 328 /// Check the validity of the arg_idx for each other window. 329 static void alist_check_arg_idx(void) 330 { 331 FOR_ALL_TAB_WINDOWS(tp, win) { 332 if (win->w_alist == curwin->w_alist) { 333 check_arg_idx(win); 334 } 335 } 336 } 337 338 /// Add files[count] to the arglist of the current window after arg "after". 339 /// The file names in files[count] must have been allocated and are taken over. 340 /// Files[] itself is not taken over. 341 /// 342 /// @param after: where to add: 0 = before first one 343 /// @param will_edit will edit adding argument 344 static void alist_add_list(int count, char **files, int after, bool will_edit) 345 FUNC_ATTR_NONNULL_ALL 346 { 347 int old_argcount = ARGCOUNT; 348 ga_grow(&ALIST(curwin)->al_ga, count); 349 if (check_arglist_locked() != FAIL) { 350 after = MIN(MAX(after, 0), ARGCOUNT); 351 if (after < ARGCOUNT) { 352 memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), 353 (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); 354 } 355 arglist_locked = true; 356 curwin->w_locked = true; 357 for (int i = 0; i < count; i++) { 358 const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); 359 ARGLIST[after + i].ae_fname = files[i]; 360 ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); 361 } 362 arglist_locked = false; 363 curwin->w_locked = false; 364 ALIST(curwin)->al_ga.ga_len += count; 365 if (old_argcount > 0 && curwin->w_arg_idx >= after) { 366 curwin->w_arg_idx += count; 367 } 368 return; 369 } 370 } 371 372 /// Delete the file names in "alist_ga" from the argument list. 373 static void arglist_del_files(garray_T *alist_ga) 374 { 375 regmatch_T regmatch; 376 377 // Delete the items: use each item as a regexp and find a match in the 378 // argument list. 379 regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set 380 for (int i = 0; i < alist_ga->ga_len && !got_int; i++) { 381 char *p = ((char **)alist_ga->ga_data)[i]; 382 p = file_pat_to_reg_pat(p, NULL, NULL, false); 383 if (p == NULL) { 384 break; 385 } 386 regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); 387 if (regmatch.regprog == NULL) { 388 xfree(p); 389 break; 390 } 391 392 bool didone = false; 393 for (int match = 0; match < ARGCOUNT; match++) { 394 if (vim_regexec(®match, alist_name(&ARGLIST[match]), 0)) { 395 didone = true; 396 xfree(ARGLIST[match].ae_fname); 397 memmove(ARGLIST + match, ARGLIST + match + 1, 398 (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); 399 ALIST(curwin)->al_ga.ga_len--; 400 if (curwin->w_arg_idx > match) { 401 curwin->w_arg_idx--; 402 } 403 match--; 404 } 405 } 406 407 vim_regfree(regmatch.regprog); 408 xfree(p); 409 if (!didone) { 410 semsg(_(e_nomatch2), ((char **)alist_ga->ga_data)[i]); 411 } 412 } 413 ga_clear(alist_ga); 414 } 415 416 /// @param str 417 /// @param what 418 /// AL_SET: Redefine the argument list to 'str'. 419 /// AL_ADD: add files in 'str' to the argument list after "after". 420 /// AL_DEL: remove files in 'str' from the argument list. 421 /// @param after 422 /// 0 means before first one 423 /// @param will_edit will edit added argument 424 /// 425 /// @return FAIL for failure, OK otherwise. 426 static int do_arglist(char *str, int what, int after, bool will_edit) 427 FUNC_ATTR_NONNULL_ALL 428 { 429 garray_T new_ga; 430 int exp_count; 431 char **exp_files; 432 bool arg_escaped = true; 433 434 if (check_arglist_locked() == FAIL) { 435 return FAIL; 436 } 437 438 // Set default argument for ":argadd" command. 439 if (what == AL_ADD && *str == NUL) { 440 if (curbuf->b_ffname == NULL) { 441 return FAIL; 442 } 443 str = curbuf->b_fname; 444 arg_escaped = false; 445 } 446 447 // Collect all file name arguments in "new_ga". 448 get_arglist(&new_ga, str, arg_escaped); 449 450 if (what == AL_DEL) { 451 arglist_del_files(&new_ga); 452 } else { 453 int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, 454 &exp_count, &exp_files, 455 EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); 456 ga_clear(&new_ga); 457 if (i == FAIL || exp_count == 0) { 458 emsg(_(e_nomatch)); 459 return FAIL; 460 } 461 462 if (what == AL_ADD) { 463 alist_add_list(exp_count, exp_files, after, will_edit); 464 xfree(exp_files); 465 } else { 466 assert(what == AL_SET); 467 alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); 468 } 469 } 470 471 alist_check_arg_idx(); 472 473 return OK; 474 } 475 476 /// Redefine the argument list. 477 void set_arglist(char *str) 478 { 479 do_arglist(str, AL_SET, 0, true); 480 } 481 482 /// @return true if window "win" is editing the file at the current argument 483 /// index. 484 bool editing_arg_idx(win_T *win) 485 { 486 return !(win->w_arg_idx >= WARGCOUNT(win) 487 || (win->w_buffer->b_fnum 488 != WARGLIST(win)[win->w_arg_idx].ae_fnum 489 && (win->w_buffer->b_ffname == NULL 490 || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), 491 win->w_buffer->b_ffname, true, 492 true) & kEqualFiles)))); 493 } 494 495 /// Check if window "win" is editing the w_arg_idx file in its argument list. 496 void check_arg_idx(win_T *win) 497 { 498 if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { 499 // We are not editing the current entry in the argument list. 500 // Set "arg_had_last" if we are editing the last one. 501 win->w_arg_idx_invalid = true; 502 if (win->w_arg_idx != WARGCOUNT(win) - 1 503 && arg_had_last == false 504 && ALIST(win) == &global_alist 505 && GARGCOUNT > 0 506 && win->w_arg_idx < GARGCOUNT 507 && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum 508 || (win->w_buffer->b_ffname != NULL 509 && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), 510 win->w_buffer->b_ffname, true, true) 511 & kEqualFiles)))) { 512 arg_had_last = true; 513 } 514 } else { 515 // We are editing the current entry in the argument list. 516 // Set "arg_had_last" if it's also the last one 517 win->w_arg_idx_invalid = false; 518 if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { 519 arg_had_last = true; 520 } 521 } 522 } 523 524 /// ":args", ":arglocal" and ":argglobal". 525 void ex_args(exarg_T *eap) 526 { 527 if (eap->cmdidx != CMD_args) { 528 if (check_arglist_locked() == FAIL) { 529 return; 530 } 531 alist_unlink(ALIST(curwin)); 532 if (eap->cmdidx == CMD_argglobal) { 533 ALIST(curwin) = &global_alist; 534 } else { // eap->cmdidx == CMD_arglocal 535 alist_new(); 536 } 537 } 538 539 // ":args file ..": define new argument list, handle like ":next" 540 // Also for ":argslocal file .." and ":argsglobal file ..". 541 if (*eap->arg != NUL) { 542 if (check_arglist_locked() == FAIL) { 543 return; 544 } 545 ex_next(eap); 546 return; 547 } 548 549 // ":args": list arguments. 550 if (eap->cmdidx == CMD_args) { 551 if (ARGCOUNT <= 0) { 552 return; // empty argument list 553 } 554 555 char **items = xmalloc(sizeof(char *) * (size_t)ARGCOUNT); 556 557 // Overwrite the command, for a short list there is no scrolling 558 // required and no wait_return(). 559 gotocmdline(true); 560 561 for (int i = 0; i < ARGCOUNT; i++) { 562 items[i] = alist_name(&ARGLIST[i]); 563 } 564 list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); 565 xfree(items); 566 567 return; 568 } 569 570 // ":argslocal": make a local copy of the global argument list. 571 if (eap->cmdidx == CMD_arglocal) { 572 garray_T *gap = &curwin->w_alist->al_ga; 573 574 ga_grow(gap, GARGCOUNT); 575 576 for (int i = 0; i < GARGCOUNT; i++) { 577 if (GARGLIST[i].ae_fname != NULL) { 578 AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = xstrdup(GARGLIST[i].ae_fname); 579 AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = GARGLIST[i].ae_fnum; 580 gap->ga_len++; 581 } 582 } 583 } 584 } 585 586 /// ":previous", ":sprevious", ":Next" and ":sNext". 587 void ex_previous(exarg_T *eap) 588 { 589 // If past the last one already, go to the last one. 590 if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) { 591 do_argfile(eap, ARGCOUNT - 1); 592 } else { 593 do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); 594 } 595 } 596 597 /// ":rewind", ":first", ":sfirst" and ":srewind". 598 void ex_rewind(exarg_T *eap) 599 { 600 do_argfile(eap, 0); 601 } 602 603 /// ":last" and ":slast". 604 void ex_last(exarg_T *eap) 605 { 606 do_argfile(eap, ARGCOUNT - 1); 607 } 608 609 /// ":argument" and ":sargument". 610 void ex_argument(exarg_T *eap) 611 { 612 int i; 613 614 if (eap->addr_count > 0) { 615 i = (int)eap->line2 - 1; 616 } else { 617 i = curwin->w_arg_idx; 618 } 619 do_argfile(eap, i); 620 } 621 622 /// Edit file "argn" of the argument lists. 623 void do_argfile(exarg_T *eap, int argn) 624 { 625 bool is_split_cmd = *eap->cmd == 's'; 626 627 int old_arg_idx = curwin->w_arg_idx; 628 629 if (argn < 0 || argn >= ARGCOUNT) { 630 if (ARGCOUNT <= 1) { 631 emsg(_("E163: There is only one file to edit")); 632 } else if (argn < 0) { 633 emsg(_("E164: Cannot go before first file")); 634 } else { 635 emsg(_("E165: Cannot go beyond last file")); 636 } 637 638 return; 639 } 640 641 if (!is_split_cmd 642 && (&ARGLIST[argn])->ae_fnum != curbuf->b_fnum 643 && !check_can_set_curbuf_forceit(eap->forceit)) { 644 return; 645 } 646 647 setpcmark(); 648 649 // split window or create new tab page first 650 if (is_split_cmd || cmdmod.cmod_tab != 0) { 651 if (win_split(0, 0) == FAIL) { 652 return; 653 } 654 RESET_BINDING(curwin); 655 } else { 656 // if 'hidden' set, only check for changed file when re-editing 657 // the same buffer 658 int other = true; 659 if (buf_hide(curbuf)) { 660 char *p = fix_fname(alist_name(&ARGLIST[argn])); 661 other = otherfile(p); 662 xfree(p); 663 } 664 if ((!buf_hide(curbuf) || !other) 665 && check_changed(curbuf, CCGD_AW 666 | (other ? 0 : CCGD_MULTWIN) 667 | (eap->forceit ? CCGD_FORCEIT : 0) 668 | CCGD_EXCMD)) { 669 return; 670 } 671 } 672 673 curwin->w_arg_idx = argn; 674 if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { 675 arg_had_last = true; 676 } 677 678 // Edit the file; always use the last known line number. 679 // When it fails (e.g. Abort for already edited file) restore the 680 // argument index. 681 if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, 682 eap, ECMD_LAST, 683 (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) 684 + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { 685 curwin->w_arg_idx = old_arg_idx; 686 } else if (eap->cmdidx != CMD_argdo) { 687 // like Vi: set the mark where the cursor is in the file. 688 setmark('\''); 689 } 690 } 691 692 /// ":next", and commands that behave like it. 693 void ex_next(exarg_T *eap) 694 { 695 // check for changed buffer now, if this fails the argument list is not 696 // redefined. 697 if (buf_hide(curbuf) 698 || eap->cmdidx == CMD_snext 699 || !check_changed(curbuf, CCGD_AW 700 | (eap->forceit ? CCGD_FORCEIT : 0) 701 | CCGD_EXCMD)) { 702 int i; 703 if (*eap->arg != NUL) { // redefine file list 704 if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) { 705 return; 706 } 707 i = 0; 708 } else { 709 i = curwin->w_arg_idx + (int)eap->line2; 710 } 711 do_argfile(eap, i); 712 } 713 } 714 715 /// ":argdedupe" 716 void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) 717 { 718 for (int i = 0; i < ARGCOUNT; i++) { 719 // Expand each argument to a full path to catch different paths leading 720 // to the same file. 721 char *firstFullname = FullName_save(ARGLIST[i].ae_fname, false); 722 723 for (int j = i + 1; j < ARGCOUNT; j++) { 724 char *secondFullname = FullName_save(ARGLIST[j].ae_fname, false); 725 bool areNamesDuplicate = path_fnamecmp(firstFullname, secondFullname) == 0; 726 xfree(secondFullname); 727 728 if (areNamesDuplicate) { 729 // remove one duplicate argument 730 xfree(ARGLIST[j].ae_fname); 731 memmove(ARGLIST + j, ARGLIST + j + 1, 732 (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T)); 733 ARGCOUNT--; 734 735 if (curwin->w_arg_idx == j) { 736 curwin->w_arg_idx = i; 737 } else if (curwin->w_arg_idx > j) { 738 curwin->w_arg_idx--; 739 } 740 741 j--; 742 } 743 } 744 745 xfree(firstFullname); 746 } 747 } 748 749 /// ":argedit" 750 void ex_argedit(exarg_T *eap) 751 { 752 int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; 753 // Whether curbuf will be reused, curbuf->b_ffname will be set. 754 bool curbuf_is_reusable = curbuf_reusable(); 755 756 if (do_arglist(eap->arg, AL_ADD, i, true) == FAIL) { 757 return; 758 } 759 maketitle(); 760 761 if (curwin->w_arg_idx == 0 762 && (curbuf->b_ml.ml_flags & ML_EMPTY) 763 && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { 764 i = 0; 765 } 766 // Edit the argument. 767 if (i < ARGCOUNT) { 768 do_argfile(eap, i); 769 } 770 } 771 772 /// ":argadd" 773 void ex_argadd(exarg_T *eap) 774 { 775 do_arglist(eap->arg, AL_ADD, 776 eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, 777 false); 778 maketitle(); 779 } 780 781 /// ":argdelete" 782 void ex_argdelete(exarg_T *eap) 783 { 784 if (check_arglist_locked() == FAIL) { 785 return; 786 } 787 788 if (eap->addr_count > 0 || *eap->arg == NUL) { 789 // ":argdel" works like ":.argdel" 790 if (eap->addr_count == 0) { 791 if (curwin->w_arg_idx >= ARGCOUNT) { 792 emsg(_("E610: No argument to delete")); 793 return; 794 } 795 eap->line1 = eap->line2 = curwin->w_arg_idx + 1; 796 } else if (eap->line2 > ARGCOUNT) { 797 // ":1,4argdel": Delete all arguments in the range. 798 eap->line2 = ARGCOUNT; 799 } 800 linenr_T n = eap->line2 - eap->line1 + 1; 801 if (*eap->arg != NUL) { 802 // Can't have both a range and an argument. 803 emsg(_(e_invarg)); 804 } else if (n <= 0) { 805 // Don't give an error for ":%argdel" if the list is empty. 806 if (eap->line1 != 1 || eap->line2 != 0) { 807 emsg(_(e_invrange)); 808 } 809 } else { 810 for (linenr_T i = eap->line1; i <= eap->line2; i++) { 811 xfree(ARGLIST[i - 1].ae_fname); 812 } 813 memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, 814 (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); 815 ALIST(curwin)->al_ga.ga_len -= (int)n; 816 if (curwin->w_arg_idx >= eap->line2) { 817 curwin->w_arg_idx -= (int)n; 818 } else if (curwin->w_arg_idx > eap->line1) { 819 curwin->w_arg_idx = (int)eap->line1; 820 } 821 if (ARGCOUNT == 0) { 822 curwin->w_arg_idx = 0; 823 } else if (curwin->w_arg_idx >= ARGCOUNT) { 824 curwin->w_arg_idx = ARGCOUNT - 1; 825 } 826 } 827 } else { 828 do_arglist(eap->arg, AL_DEL, 0, false); 829 } 830 maketitle(); 831 } 832 833 /// Function given to ExpandGeneric() to obtain the possible arguments of the 834 /// argedit and argdelete commands. 835 char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) 836 { 837 if (idx >= ARGCOUNT) { 838 return NULL; 839 } 840 return alist_name(&ARGLIST[idx]); 841 } 842 843 /// Get the file name for an argument list entry. 844 char *alist_name(aentry_T *aep) 845 { 846 // Use the name from the associated buffer if it exists. 847 buf_T *bp = buflist_findnr(aep->ae_fnum); 848 if (bp == NULL || bp->b_fname == NULL) { 849 return aep->ae_fname; 850 } 851 return bp->b_fname; 852 } 853 854 /// Close all the windows containing files which are not in the argument list. 855 /// Used by the ":all" command. 856 static void arg_all_close_unused_windows(arg_all_state_T *aall) 857 { 858 win_T *old_curwin = curwin; 859 tabpage_T *old_curtab = curtab; 860 861 if (aall->had_tab > 0) { 862 goto_tabpage_tp(first_tabpage, true, true); 863 } 864 865 // moving tabpages around in an autocommand may cause an endless loop 866 tabpage_move_disallowed++; 867 while (true) { 868 win_T *wpnext = NULL; 869 tabpage_T *tpnext = curtab->tp_next; 870 // Try to close floating windows first 871 for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) { 872 int i; 873 wpnext = wp->w_floating 874 ? wp->w_prev->w_floating ? wp->w_prev : firstwin 875 : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next; 876 buf_T *buf = wp->w_buffer; 877 if (buf->b_ffname == NULL 878 || (!aall->keep_tabs 879 && (buf->b_nwindows > 1 || wp->w_width != Columns 880 || (wp->w_floating && !is_aucmd_win(wp))))) { 881 i = aall->opened_len; 882 } else { 883 // check if the buffer in this window is in the arglist 884 for (i = 0; i < aall->opened_len; i++) { 885 if (i < aall->alist->al_ga.ga_len 886 && (AARGLIST(aall->alist)[i].ae_fnum == buf->b_fnum 887 || path_full_compare(alist_name(&AARGLIST(aall->alist)[i]), 888 buf->b_ffname, 889 true, true) & kEqualFiles)) { 890 int weight = 1; 891 892 if (old_curtab == curtab) { 893 weight++; 894 if (old_curwin == wp) { 895 weight++; 896 } 897 } 898 899 if (weight > (int)aall->opened[i]) { 900 aall->opened[i] = (uint8_t)weight; 901 if (i == 0) { 902 if (aall->new_curwin != NULL) { 903 aall->new_curwin->w_arg_idx = aall->opened_len; 904 } 905 aall->new_curwin = wp; 906 aall->new_curtab = curtab; 907 } 908 } else if (aall->keep_tabs) { 909 i = aall->opened_len; 910 } 911 912 if (wp->w_alist != aall->alist) { 913 // Use the current argument list for all windows 914 // containing a file from it. 915 alist_unlink(wp->w_alist); 916 wp->w_alist = aall->alist; 917 wp->w_alist->al_refcount++; 918 } 919 break; 920 } 921 } 922 } 923 wp->w_arg_idx = i; 924 925 if (i == aall->opened_len && !aall->keep_tabs) { // close this window 926 if (buf_hide(buf) || aall->forceit || buf->b_nwindows > 1 927 || !bufIsChanged(buf)) { 928 // If the buffer was changed, and we would like to hide it, try autowriting. 929 if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { 930 bufref_T bufref; 931 set_bufref(&bufref, buf); 932 autowrite(buf, false); 933 // Check if autocommands removed the window. 934 if (!win_valid(wp) || !bufref_valid(&bufref)) { 935 wpnext = lastwin->w_floating ? lastwin : firstwin; // Start all over... 936 continue; 937 } 938 } 939 // don't close last window 940 if (ONE_WINDOW 941 && (first_tabpage->tp_next == NULL || !aall->had_tab)) { 942 aall->use_firstwin = true; 943 } else { 944 win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); 945 // check if autocommands removed the next window 946 if (!win_valid(wpnext)) { 947 // start all over... 948 wpnext = lastwin->w_floating ? lastwin : firstwin; 949 } 950 } 951 } 952 } 953 } 954 955 // Without the ":tab" modifier only do the current tab page. 956 if (aall->had_tab == 0 || tpnext == NULL) { 957 break; 958 } 959 960 // check if autocommands removed the next tab page 961 if (!valid_tabpage(tpnext)) { 962 tpnext = first_tabpage; // start all over... 963 } 964 goto_tabpage_tp(tpnext, true, true); 965 } 966 tabpage_move_disallowed--; 967 } 968 969 /// Open up to "count" windows for the files in the argument list "aall->alist". 970 static void arg_all_open_windows(arg_all_state_T *aall, int count) 971 { 972 bool tab_drop_empty_window = false; 973 974 // ":tab drop file" should re-use an empty window to avoid "--remote-tab" 975 // leaving an empty tab page when executed locally. 976 if (aall->keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 977 && curbuf->b_ffname == NULL && !curbuf->b_changed) { 978 aall->use_firstwin = true; 979 tab_drop_empty_window = true; 980 } 981 982 int split_ret = OK; 983 984 for (int i = 0; i < count && !got_int; i++) { 985 if (aall->alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { 986 arg_had_last = true; 987 } 988 if (aall->opened[i] > 0) { 989 // Move the already present window to below the current window 990 if (curwin->w_arg_idx != i) { 991 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 992 if (wp->w_arg_idx == i) { 993 if (aall->keep_tabs) { 994 aall->new_curwin = wp; 995 aall->new_curtab = curtab; 996 } else if (wp->w_floating) { 997 break; 998 } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { 999 emsg(_(e_window_layout_changed_unexpectedly)); 1000 i = count; 1001 break; 1002 } else { 1003 win_move_after(wp, curwin); 1004 } 1005 break; 1006 } 1007 } 1008 } 1009 } else if (split_ret == OK) { 1010 // trigger events for tab drop 1011 if (tab_drop_empty_window && i == count - 1) { 1012 autocmd_no_enter--; 1013 } 1014 if (!aall->use_firstwin) { // split current window 1015 bool p_ea_save = p_ea; 1016 p_ea = true; // use space from all windows 1017 split_ret = win_split(0, WSP_ROOM | WSP_BELOW); 1018 p_ea = p_ea_save; 1019 if (split_ret == FAIL) { 1020 continue; 1021 } 1022 } else { // first window: do autocmd for leaving this buffer 1023 autocmd_no_leave--; 1024 } 1025 1026 // edit file "i" 1027 curwin->w_arg_idx = i; 1028 if (i == 0) { 1029 aall->new_curwin = curwin; 1030 aall->new_curtab = curtab; 1031 } 1032 do_ecmd(0, alist_name(&AARGLIST(aall->alist)[i]), NULL, NULL, ECMD_ONE, 1033 ((buf_hide(curwin->w_buffer) 1034 || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) + ECMD_OLDBUF, 1035 curwin); 1036 if (tab_drop_empty_window && i == count - 1) { 1037 autocmd_no_enter++; 1038 } 1039 if (aall->use_firstwin) { 1040 autocmd_no_leave++; 1041 } 1042 aall->use_firstwin = false; 1043 } 1044 os_breakcheck(); 1045 1046 // When ":tab" was used open a new tab for a new window repeatedly. 1047 if (aall->had_tab > 0 && tabpage_index(NULL) <= p_tpm) { 1048 cmdmod.cmod_tab = 9999; 1049 } 1050 } 1051 } 1052 1053 /// do_arg_all(): Open up to 'count' windows, one for each argument. 1054 /// 1055 /// @param forceit hide buffers in current windows 1056 /// @param keep_tabs keep current tabs, for ":tab drop file" 1057 static void do_arg_all(int count, int forceit, int keep_tabs) 1058 { 1059 win_T *last_curwin; 1060 tabpage_T *last_curtab; 1061 bool prev_arglist_locked = arglist_locked; 1062 1063 assert(firstwin != NULL); // satisfy coverity 1064 1065 if (cmdwin_type != 0) { 1066 emsg(_(e_cmdwin)); 1067 return; 1068 } 1069 if (ARGCOUNT <= 0) { 1070 // Don't give an error message. We don't want it when the ":all" 1071 // command is in the .vimrc. 1072 return; 1073 } 1074 setpcmark(); 1075 1076 arg_all_state_T aall = { 1077 .use_firstwin = false, 1078 .had_tab = cmdmod.cmod_tab, 1079 .new_curwin = NULL, 1080 .new_curtab = NULL, 1081 .forceit = forceit, 1082 .keep_tabs = keep_tabs, 1083 .opened_len = ARGCOUNT, 1084 .opened = xcalloc((size_t)ARGCOUNT, 1), 1085 }; 1086 1087 // Autocommands may do anything to the argument list. Make sure it's not 1088 // freed while we are working here by "locking" it. We still have to 1089 // watch out for its size to be changed. 1090 aall.alist = curwin->w_alist; 1091 aall.alist->al_refcount++; 1092 arglist_locked = true; 1093 1094 tabpage_T *const new_lu_tp = curtab; 1095 1096 // Stop Visual mode, the cursor and "VIsual" may very well be invalid after 1097 // switching to another buffer. 1098 reset_VIsual_and_resel(); 1099 1100 // Try closing all windows that are not in the argument list. 1101 // Also close windows that are not full width; 1102 // When 'hidden' or "forceit" set the buffer becomes hidden. 1103 // Windows that have a changed buffer and can't be hidden won't be closed. 1104 // When the ":tab" modifier was used do this for all tab pages. 1105 arg_all_close_unused_windows(&aall); 1106 1107 // Open a window for files in the argument list that don't have one. 1108 // ARGCOUNT may change while doing this, because of autocommands. 1109 if (count > aall.opened_len || count <= 0) { 1110 count = aall.opened_len; 1111 } 1112 1113 // Don't execute Win/Buf Enter/Leave autocommands here. 1114 autocmd_no_enter++; 1115 autocmd_no_leave++; 1116 last_curwin = curwin; 1117 last_curtab = curtab; 1118 // lastwin may be aucmd_win 1119 win_enter(lastwin_nofloating(), false); 1120 1121 // Open up to "count" windows. 1122 arg_all_open_windows(&aall, count); 1123 1124 // Remove the "lock" on the argument list. 1125 alist_unlink(aall.alist); 1126 arglist_locked = prev_arglist_locked; 1127 1128 autocmd_no_enter--; 1129 1130 // restore last referenced tabpage's curwin 1131 if (last_curtab != aall.new_curtab) { 1132 if (valid_tabpage(last_curtab)) { 1133 goto_tabpage_tp(last_curtab, true, true); 1134 } 1135 if (win_valid(last_curwin)) { 1136 win_enter(last_curwin, false); 1137 } 1138 } 1139 // to window with first arg 1140 if (valid_tabpage(aall.new_curtab)) { 1141 goto_tabpage_tp(aall.new_curtab, true, true); 1142 } 1143 1144 // Now set the last used tabpage to where we started. 1145 if (valid_tabpage(new_lu_tp)) { 1146 lastused_tabpage = new_lu_tp; 1147 } 1148 1149 if (win_valid(aall.new_curwin)) { 1150 win_enter(aall.new_curwin, false); 1151 } 1152 1153 autocmd_no_leave--; 1154 xfree(aall.opened); 1155 } 1156 1157 /// ":all" and ":sall". 1158 /// Also used for ":tab drop file ..." after setting the argument list. 1159 void ex_all(exarg_T *eap) 1160 { 1161 if (eap->addr_count == 0) { 1162 eap->line2 = 9999; 1163 } 1164 do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); 1165 } 1166 1167 /// Concatenate all files in the argument list, separated by spaces, and return 1168 /// it in one allocated string. 1169 /// Spaces and backslashes in the file names are escaped with a backslash. 1170 char *arg_all(void) 1171 { 1172 char *retval = NULL; 1173 1174 // Do this loop two times: 1175 // first time: compute the total length 1176 // second time: concatenate the names 1177 while (true) { 1178 int len = 0; 1179 for (int idx = 0; idx < ARGCOUNT; idx++) { 1180 char *p = alist_name(&ARGLIST[idx]); 1181 if (p == NULL) { 1182 continue; 1183 } 1184 if (len > 0) { 1185 // insert a space in between names 1186 if (retval != NULL) { 1187 retval[len] = ' '; 1188 } 1189 len++; 1190 } 1191 for (; *p != NUL; p++) { 1192 if (*p == ' ' 1193 #ifndef BACKSLASH_IN_FILENAME 1194 || *p == '\\' 1195 #endif 1196 || *p == '`') { 1197 // insert a backslash 1198 if (retval != NULL) { 1199 retval[len] = '\\'; 1200 } 1201 len++; 1202 } 1203 if (retval != NULL) { 1204 retval[len] = *p; 1205 } 1206 len++; 1207 } 1208 } 1209 1210 // second time: break here 1211 if (retval != NULL) { 1212 retval[len] = NUL; 1213 break; 1214 } 1215 1216 // allocate memory 1217 retval = xmalloc((size_t)len + 1); 1218 } 1219 1220 return retval; 1221 } 1222 1223 /// "argc([window id])" function 1224 void f_argc(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1225 { 1226 if (argvars[0].v_type == VAR_UNKNOWN) { 1227 // use the current window 1228 rettv->vval.v_number = ARGCOUNT; 1229 } else if (argvars[0].v_type == VAR_NUMBER 1230 && tv_get_number(&argvars[0]) == -1) { 1231 // use the global argument list 1232 rettv->vval.v_number = GARGCOUNT; 1233 } else { 1234 // use the argument list of the specified window 1235 win_T *wp = find_win_by_nr_or_id(&argvars[0]); 1236 if (wp != NULL) { 1237 rettv->vval.v_number = WARGCOUNT(wp); 1238 } else { 1239 rettv->vval.v_number = -1; 1240 } 1241 } 1242 } 1243 1244 /// "argidx()" function 1245 void f_argidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1246 { 1247 rettv->vval.v_number = curwin->w_arg_idx; 1248 } 1249 1250 /// "arglistid()" function 1251 void f_arglistid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1252 { 1253 rettv->vval.v_number = -1; 1254 win_T *wp = find_tabwin(&argvars[0], &argvars[1]); 1255 if (wp != NULL) { 1256 rettv->vval.v_number = wp->w_alist->id; 1257 } 1258 } 1259 1260 /// Get the argument list for a given window 1261 static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) 1262 { 1263 tv_list_alloc_ret(rettv, argcount); 1264 if (arglist != NULL) { 1265 for (int idx = 0; idx < argcount; idx++) { 1266 tv_list_append_string(rettv->vval.v_list, alist_name(&arglist[idx]), -1); 1267 } 1268 } 1269 } 1270 1271 /// "argv(nr)" function 1272 void f_argv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1273 { 1274 aentry_T *arglist = NULL; 1275 int argcount = -1; 1276 1277 if (argvars[0].v_type == VAR_UNKNOWN) { 1278 get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); 1279 return; 1280 } 1281 1282 if (argvars[1].v_type == VAR_UNKNOWN) { 1283 arglist = ARGLIST; 1284 argcount = ARGCOUNT; 1285 } else if (argvars[1].v_type == VAR_NUMBER 1286 && tv_get_number(&argvars[1]) == -1) { 1287 arglist = GARGLIST; 1288 argcount = GARGCOUNT; 1289 } else { 1290 win_T *wp = find_win_by_nr_or_id(&argvars[1]); 1291 if (wp != NULL) { 1292 // Use the argument list of the specified window 1293 arglist = WARGLIST(wp); 1294 argcount = WARGCOUNT(wp); 1295 } 1296 } 1297 1298 rettv->v_type = VAR_STRING; 1299 rettv->vval.v_string = NULL; 1300 int idx = (int)tv_get_number_chk(&argvars[0], NULL); 1301 if (arglist != NULL && idx >= 0 && idx < argcount) { 1302 rettv->vval.v_string = xstrdup(alist_name(&arglist[idx])); 1303 } else if (idx == -1) { 1304 get_arglist_as_rettv(arglist, argcount, rettv); 1305 } 1306 }