xref: /vim-8.2.3635/src/arglist.c (revision 35578168)
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
check_arglist_locked(void)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
alist_clear(alist_T * al)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
alist_init(alist_T * al)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
alist_unlink(alist_T * al)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
alist_new(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
alist_expand(int * fnum_list,int fnum_len)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
alist_set(alist_T * al,int count,char_u ** files,int use_curbuf,int * fnum_list,int fnum_len)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
alist_add(alist_T * al,char_u * fname,int set_fnum)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
alist_slash_adjust(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 *
do_one_arg(char_u * str)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
get_arglist(garray_T * gap,char_u * str,int escaped)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
get_arglist_exp(char_u * str,int * fcountp,char_u *** fnamesp,int wig)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
alist_check_arg_idx(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
alist_add_list(int count,char_u ** files,int after,int will_edit)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
do_arglist(char_u * str,int what,int after UNUSED,int will_edit)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(&regmatch, 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
set_arglist(char_u * str)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
editing_arg_idx(win_T * win)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
check_arg_idx(win_T * win)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
ex_args(exarg_T * eap)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
ex_previous(exarg_T * eap)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
ex_rewind(exarg_T * eap)632 ex_rewind(exarg_T *eap)
633 {
634     do_argfile(eap, 0);
635 }
636 
637 /*
638  * ":last" and ":slast".
639  */
640     void
ex_last(exarg_T * eap)641 ex_last(exarg_T *eap)
642 {
643     do_argfile(eap, ARGCOUNT - 1);
644 }
645 
646 /*
647  * ":argument" and ":sargument".
648  */
649     void
ex_argument(exarg_T * eap)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
do_argfile(exarg_T * eap,int argn)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
ex_next(exarg_T * eap)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
ex_argedit(exarg_T * eap)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
ex_argadd(exarg_T * eap)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
ex_argdelete(exarg_T * eap)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 *
get_arglist_name(expand_T * xp UNUSED,int idx)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 *
alist_name(aentry_T * aep)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
do_arg_all(int count,int forceit,int keep_tabs)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
ex_all(exarg_T * eap)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 *
arg_all(void)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
f_argc(typval_T * argvars,typval_T * rettv)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
f_argidx(typval_T * argvars UNUSED,typval_T * rettv)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
f_arglistid(typval_T * argvars,typval_T * rettv)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
get_arglist_as_rettv(aentry_T * arglist,int argcount,typval_T * rettv)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
f_argv(typval_T * argvars,typval_T * rettv)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