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