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