neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

arglist.c (37174B)


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