neovim

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

file_search.c (66691B)


      1 // File searching functions for 'path', 'tags' and 'cdpath' options.
      2 //
      3 // External visible functions:
      4 //   vim_findfile_init()          creates/initialises the search context
      5 //   vim_findfile_free_visited()  free list of visited files/dirs of search
      6 //                                context
      7 //   vim_findfile()               find a file in the search context
      8 //   vim_findfile_cleanup()       cleanup/free search context created by
      9 //                                vim_findfile_init()
     10 //
     11 // All static functions and variables start with 'ff_'
     12 //
     13 // In general it works like this:
     14 // First you create yourself a search context by calling vim_findfile_init().
     15 // It is possible to give a search context from a previous call to
     16 // vim_findfile_init(), so it can be reused. After this you call vim_findfile()
     17 // until you are satisfied with the result or it returns NULL. On every call it
     18 // returns the next file which matches the conditions given to
     19 // vim_findfile_init(). If it doesn't find a next file it returns NULL.
     20 //
     21 // It is possible to call vim_findfile_init() again to reinitialise your search
     22 // with some new parameters. Don't forget to pass your old search context to
     23 // it, so it can reuse it and especially reuse the list of already visited
     24 // directories. If you want to delete the list of already visited directories
     25 // simply call vim_findfile_free_visited().
     26 //
     27 // When you are done call vim_findfile_cleanup() to free the search context.
     28 //
     29 // The function vim_findfile_init() has a long comment, which describes the
     30 // needed parameters.
     31 //
     32 //
     33 //
     34 // ATTENTION:
     35 // ==========
     36 // We use an allocated search context, these functions are NOT thread-safe!!!!!
     37 //
     38 // To minimize parameter passing (or because I'm too lazy), only the
     39 // external visible functions get a search context as a parameter. This is
     40 // then assigned to a static global, which is used throughout the local
     41 // functions.
     42 
     43 #include <assert.h>
     44 #include <ctype.h>
     45 #include <inttypes.h>
     46 #include <limits.h>
     47 #include <stdbool.h>
     48 #include <stddef.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 
     53 #include "nvim/api/private/defs.h"
     54 #include "nvim/api/private/helpers.h"
     55 #include "nvim/ascii_defs.h"
     56 #include "nvim/autocmd.h"
     57 #include "nvim/autocmd_defs.h"
     58 #include "nvim/buffer_defs.h"
     59 #include "nvim/charset.h"
     60 #include "nvim/cursor.h"
     61 #include "nvim/errors.h"
     62 #include "nvim/eval.h"
     63 #include "nvim/eval/typval.h"
     64 #include "nvim/eval/typval_defs.h"
     65 #include "nvim/eval/vars.h"
     66 #include "nvim/file_search.h"
     67 #include "nvim/gettext_defs.h"
     68 #include "nvim/globals.h"
     69 #include "nvim/macros_defs.h"
     70 #include "nvim/mbyte.h"
     71 #include "nvim/memory.h"
     72 #include "nvim/message.h"
     73 #include "nvim/normal.h"
     74 #include "nvim/option.h"
     75 #include "nvim/option_defs.h"
     76 #include "nvim/option_vars.h"
     77 #include "nvim/os/fs.h"
     78 #include "nvim/os/fs_defs.h"
     79 #include "nvim/os/input.h"
     80 #include "nvim/os/os.h"
     81 #include "nvim/os/os_defs.h"
     82 #include "nvim/path.h"
     83 #include "nvim/strings.h"
     84 #include "nvim/vim_defs.h"
     85 
     86 static String ff_expand_buffer = STRING_INIT;  // used for expanding filenames
     87 
     88 // type for the directory search stack
     89 typedef struct ff_stack {
     90  struct ff_stack *ffs_prev;
     91 
     92  // the fix part (no wildcards) and the part containing the wildcards
     93  // of the search path
     94  String ffs_fix_path;
     95  String ffs_wc_path;
     96 
     97  // files/dirs found in the above directory, matched by the first wildcard
     98  // of wc_part
     99  char **ffs_filearray;
    100  int ffs_filearray_size;
    101  int ffs_filearray_cur;                  // needed for partly handled dirs
    102 
    103  // to store status of partly handled directories
    104  // 0: we work on this directory for the first time
    105  // 1: this directory was partly searched in an earlier step
    106  int ffs_stage;
    107 
    108  // How deep are we in the directory tree?
    109  // Counts backward from value of level parameter to vim_findfile_init
    110  int ffs_level;
    111 
    112  // Did we already expand '**' to an empty string?
    113  int ffs_star_star_empty;
    114 } ff_stack_T;
    115 
    116 // type for already visited directories or files.
    117 typedef struct ff_visited {
    118  struct ff_visited *ffv_next;
    119 
    120  // Visited directories are different if the wildcard string are
    121  // different. So we have to save it.
    122  char *ffv_wc_path;
    123 
    124  // use FileID for comparison (needed because of links), else use filename.
    125  bool file_id_valid;
    126  FileID file_id;
    127  // The memory for this struct is allocated according to the length of
    128  // ffv_fname.
    129  char ffv_fname[];
    130 } ff_visited_T;
    131 
    132 // We might have to manage several visited lists during a search.
    133 // This is especially needed for the tags option. If tags is set to:
    134 //      "./++/tags,./++/TAGS,++/tags"  (replace + with *)
    135 // So we have to do 3 searches:
    136 //   1) search from the current files directory downward for the file "tags"
    137 //   2) search from the current files directory downward for the file "TAGS"
    138 //   3) search from Vims current directory downwards for the file "tags"
    139 // As you can see, the first and the third search are for the same file, so for
    140 // the third search we can use the visited list of the first search. For the
    141 // second search we must start from an empty visited list.
    142 // The struct ff_visited_list_hdr is used to manage a linked list of already
    143 // visited lists.
    144 typedef struct ff_visited_list_hdr {
    145  struct ff_visited_list_hdr *ffvl_next;
    146 
    147  // the filename the attached visited list is for
    148  char *ffvl_filename;
    149 
    150  ff_visited_T *ffvl_visited_list;
    151 } ff_visited_list_hdr_T;
    152 
    153 // '**' can be expanded to several directory levels.
    154 // Set the default maximum depth.
    155 #define FF_MAX_STAR_STAR_EXPAND 30
    156 
    157 // The search context:
    158 //   ffsc_stack_ptr:    the stack for the dirs to search
    159 //   ffsc_visited_list: the currently active visited list
    160 //   ffsc_dir_visited_list: the currently active visited list for search dirs
    161 //   ffsc_visited_lists_list: the list of all visited lists
    162 //   ffsc_dir_visited_lists_list: the list of all visited lists for search dirs
    163 //   ffsc_file_to_search:     the file to search for
    164 //   ffsc_start_dir:    the starting directory, if search path was relative
    165 //   ffsc_fix_path:     the fix part of the given path (without wildcards)
    166 //                      Needed for upward search.
    167 //   ffsc_wc_path:      the part of the given path containing wildcards
    168 //   ffsc_level:        how many levels of dirs to search downwards
    169 //   ffsc_stopdirs_v:   array of stop directories for upward search
    170 //   ffsc_find_what:    FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE
    171 //   ffsc_tagfile:      searching for tags file, don't use 'suffixesadd'
    172 typedef struct {
    173  ff_stack_T *ffsc_stack_ptr;
    174  ff_visited_list_hdr_T *ffsc_visited_list;
    175  ff_visited_list_hdr_T *ffsc_dir_visited_list;
    176  ff_visited_list_hdr_T *ffsc_visited_lists_list;
    177  ff_visited_list_hdr_T *ffsc_dir_visited_lists_list;
    178  String ffsc_file_to_search;
    179  String ffsc_start_dir;
    180  String ffsc_fix_path;
    181  String ffsc_wc_path;
    182  int ffsc_level;
    183  String *ffsc_stopdirs_v;
    184  int ffsc_find_what;
    185  int ffsc_tagfile;
    186 } ff_search_ctx_T;
    187 
    188 // locally needed functions
    189 
    190 #include "file_search.c.generated.h"
    191 
    192 static const char e_path_too_long_for_completion[]
    193  = N_("E854: Path too long for completion");
    194 
    195 /// Initialization routine for vim_findfile().
    196 ///
    197 /// Returns the newly allocated search context or NULL if an error occurred.
    198 ///
    199 /// Don't forget to clean up by calling vim_findfile_cleanup() if you are done
    200 /// with the search context.
    201 ///
    202 /// Find the file 'filename' in the directory 'path'.
    203 /// The parameter 'path' may contain wildcards. If so only search 'level'
    204 /// directories deep. The parameter 'level' is the absolute maximum and is
    205 /// not related to restricts given to the '**' wildcard. If 'level' is 100
    206 /// and you use '**200' vim_findfile() will stop after 100 levels.
    207 ///
    208 /// 'filename' cannot contain wildcards!  It is used as-is, no backslashes to
    209 /// escape special characters.
    210 ///
    211 /// If 'stopdirs' is not NULL and nothing is found downward, the search is
    212 /// restarted on the next higher directory level. This is repeated until the
    213 /// start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the
    214 /// format ";*<dirname>*\(;<dirname>\)*;\=$".
    215 ///
    216 /// If the 'path' is relative, the starting dir for the search is either VIM's
    217 /// current dir or if the path starts with "./" the current files dir.
    218 /// If the 'path' is absolute, the starting dir is that part of the path before
    219 /// the first wildcard.
    220 ///
    221 /// Upward search is only done on the starting dir.
    222 ///
    223 /// If 'free_visited' is true the list of already visited files/directories is
    224 /// cleared. Set this to false if you just want to search from another
    225 /// directory, but want to be sure that no directory from a previous search is
    226 /// searched again. This is useful if you search for a file at different places.
    227 /// The list of visited files/dirs can also be cleared with the function
    228 /// vim_findfile_free_visited().
    229 ///
    230 /// Set the parameter 'find_what' to FINDFILE_DIR if you want to search for
    231 /// directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both.
    232 ///
    233 /// A search context returned by a previous call to vim_findfile_init() can be
    234 /// passed in the parameter "search_ctx_arg".  This context is reused and
    235 /// reinitialized with the new parameters.  The list of already visited
    236 /// directories from this context is only deleted if the parameter
    237 /// "free_visited" is true.  Be aware that the passed "search_ctx_arg" is freed
    238 /// if the reinitialization fails.
    239 ///
    240 /// If you don't have a search context from a previous call "search_ctx_arg"
    241 /// must be NULL.
    242 ///
    243 /// This function silently ignores a few errors, vim_findfile() will have
    244 /// limited functionality then.
    245 ///
    246 /// @param tagfile  expanding names of tags files
    247 /// @param rel_fname  file name to use for "."
    248 void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *stopdirs, int level,
    249                        int free_visited, int find_what, void *search_ctx_arg, int tagfile,
    250                        char *rel_fname)
    251 {
    252  ff_stack_T *sptr;
    253  ff_search_ctx_T *search_ctx;
    254 
    255  // If a search context is given by the caller, reuse it, else allocate a
    256  // new one.
    257  if (search_ctx_arg != NULL) {
    258    search_ctx = search_ctx_arg;
    259  } else {
    260    search_ctx = xcalloc(1, sizeof(ff_search_ctx_T));
    261  }
    262  search_ctx->ffsc_find_what = find_what;
    263  search_ctx->ffsc_tagfile = tagfile;
    264 
    265  // clear the search context, but NOT the visited lists
    266  ff_clear(search_ctx);
    267 
    268  // clear visited list if wanted
    269  if (free_visited == true) {
    270    vim_findfile_free_visited(search_ctx);
    271  } else {
    272    // Reuse old visited lists. Get the visited list for the given
    273    // filename. If no list for the current filename exists, creates a new
    274    // one.
    275    search_ctx->ffsc_visited_list
    276      = ff_get_visited_list(filename, filenamelen,
    277                            &search_ctx->ffsc_visited_lists_list);
    278    if (search_ctx->ffsc_visited_list == NULL) {
    279      goto error_return;
    280    }
    281    search_ctx->ffsc_dir_visited_list
    282      = ff_get_visited_list(filename, filenamelen,
    283                            &search_ctx->ffsc_dir_visited_lists_list);
    284    if (search_ctx->ffsc_dir_visited_list == NULL) {
    285      goto error_return;
    286    }
    287  }
    288 
    289  if (ff_expand_buffer.data == NULL) {
    290    ff_expand_buffer.size = 0;
    291    ff_expand_buffer.data = xmalloc(MAXPATHL);
    292  }
    293 
    294  // Store information on starting dir now if path is relative.
    295  // If path is absolute, we do that later.
    296  if (path[0] == '.'
    297      && (vim_ispathsep(path[1]) || path[1] == NUL)
    298      && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
    299      && rel_fname != NULL) {
    300    size_t len = (size_t)(path_tail(rel_fname) - rel_fname);
    301 
    302    if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) {
    303      // Make the start dir an absolute path name.
    304      xmemcpyz(ff_expand_buffer.data, rel_fname, len);
    305      ff_expand_buffer.size = len;
    306      search_ctx->ffsc_start_dir = cstr_as_string(FullName_save(ff_expand_buffer.data, false));
    307    } else {
    308      search_ctx->ffsc_start_dir = cbuf_to_string(rel_fname, len);
    309    }
    310    if (*++path != NUL) {
    311      path++;
    312    }
    313  } else if (*path == NUL || !vim_isAbsName(path)) {
    314 #ifdef BACKSLASH_IN_FILENAME
    315    // "c:dir" needs "c:" to be expanded, otherwise use current dir
    316    if (*path != NUL && path[1] == ':') {
    317      char drive[3];
    318 
    319      drive[0] = path[0];
    320      drive[1] = ':';
    321      drive[2] = NUL;
    322      if (vim_FullName(drive, ff_expand_buffer.data, MAXPATHL, true) == FAIL) {
    323        goto error_return;
    324      }
    325      path += 2;
    326    } else
    327 #endif
    328    if (os_dirname(ff_expand_buffer.data, MAXPATHL) == FAIL) {
    329      goto error_return;
    330    }
    331    ff_expand_buffer.size = strlen(ff_expand_buffer.data);
    332 
    333    search_ctx->ffsc_start_dir = copy_string(ff_expand_buffer, NULL);
    334  }
    335 
    336  // If stopdirs are given, split them into an array of pointers.
    337  // If this fails (mem allocation), there is no upward search at all or a
    338  // stop directory is not recognized -> continue silently.
    339  // If stopdirs just contains a ";" or is empty,
    340  // search_ctx->ffsc_stopdirs_v will only contain a  NULL pointer. This
    341  // is handled as unlimited upward search.  See function
    342  // ff_path_in_stoplist() for details.
    343  if (stopdirs != NULL) {
    344    char *walker = stopdirs;
    345 
    346    while (*walker == ';') {
    347      walker++;
    348    }
    349 
    350    size_t dircount = 1;
    351    search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(String));
    352 
    353    do {
    354      char *helper = walker;
    355      void *ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
    356                           (dircount + 1) * sizeof(String));
    357      search_ctx->ffsc_stopdirs_v = ptr;
    358      walker = vim_strchr(walker, ';');
    359      assert(!walker || walker - helper >= 0);
    360      size_t len = walker ? (size_t)(walker - helper) : strlen(helper);
    361      // "" means ascent till top of directory tree.
    362      if (*helper != NUL && !vim_isAbsName(helper) && len + 1 < MAXPATHL) {
    363        // Make the stop dir an absolute path name.
    364        xmemcpyz(ff_expand_buffer.data, helper, len);
    365        ff_expand_buffer.size = len;
    366        search_ctx->ffsc_stopdirs_v[dircount - 1] = cstr_as_string(FullName_save(helper, len));
    367      } else {
    368        search_ctx->ffsc_stopdirs_v[dircount - 1] = cbuf_to_string(helper, len);
    369      }
    370      if (walker) {
    371        walker++;
    372      }
    373      dircount++;
    374    } while (walker != NULL);
    375 
    376    search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL_STRING;
    377  }
    378 
    379  search_ctx->ffsc_level = level;
    380 
    381  // split into:
    382  //  -fix path
    383  //  -wildcard_stuff (might be NULL)
    384  char *wc_part = vim_strchr(path, '*');
    385  if (wc_part != NULL) {
    386    int64_t llevel;
    387    char *errpt;
    388 
    389    // save the fix part of the path
    390    assert(wc_part - path >= 0);
    391    search_ctx->ffsc_fix_path = cbuf_to_string(path, (size_t)(wc_part - path));
    392 
    393    // copy wc_path and add restricts to the '**' wildcard.
    394    // The octet after a '**' is used as a (binary) counter.
    395    // So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
    396    // or '**76' is transposed to '**N'( 'N' is ASCII value 76).
    397    // If no restrict is given after '**' the default is used.
    398    // Due to this technique the path looks awful if you print it as a
    399    // string.
    400    ff_expand_buffer.size = 0;
    401    while (*wc_part != NUL) {
    402      if (ff_expand_buffer.size + 5 >= MAXPATHL) {
    403        emsg(_(e_path_too_long_for_completion));
    404        break;
    405      }
    406      if (strncmp(wc_part, "**", 2) == 0) {
    407        ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++;
    408        ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++;
    409 
    410        llevel = strtol(wc_part, &errpt, 10);
    411        if (errpt != wc_part && llevel > 0 && llevel < 255) {
    412          ff_expand_buffer.data[ff_expand_buffer.size++] = (char)llevel;
    413        } else if (errpt != wc_part && llevel == 0) {
    414          // restrict is 0 -> remove already added '**'
    415          ff_expand_buffer.size -= 2;
    416        } else {
    417          ff_expand_buffer.data[ff_expand_buffer.size++] = FF_MAX_STAR_STAR_EXPAND;
    418        }
    419        wc_part = errpt;
    420        if (*wc_part != NUL && !vim_ispathsep(*wc_part)) {
    421          semsg(_(
    422                 "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."),
    423                PATHSEPSTR);
    424          goto error_return;
    425        }
    426      } else {
    427        ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++;
    428      }
    429    }
    430    ff_expand_buffer.data[ff_expand_buffer.size] = NUL;
    431    search_ctx->ffsc_wc_path = copy_string(ff_expand_buffer, false);
    432  } else {
    433    search_ctx->ffsc_fix_path = cstr_to_string(path);
    434  }
    435 
    436  if (search_ctx->ffsc_start_dir.data == NULL) {
    437    // store the fix part as startdir.
    438    // This is needed if the parameter path is fully qualified.
    439    search_ctx->ffsc_start_dir = copy_string(search_ctx->ffsc_fix_path, false);
    440    search_ctx->ffsc_fix_path.data[0] = NUL;
    441    search_ctx->ffsc_fix_path.size = 0;
    442  }
    443 
    444  // create an absolute path
    445  if (search_ctx->ffsc_start_dir.size
    446      + search_ctx->ffsc_fix_path.size + 3 >= MAXPATHL) {
    447    emsg(_(e_path_too_long_for_completion));
    448    goto error_return;
    449  }
    450 
    451  bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data,
    452                                search_ctx->ffsc_start_dir.data + search_ctx->ffsc_start_dir.size);
    453  ff_expand_buffer.size = (size_t)vim_snprintf(ff_expand_buffer.data,
    454                                               MAXPATHL,
    455                                               "%s%s",
    456                                               search_ctx->ffsc_start_dir.data,
    457                                               add_sep ? PATHSEPSTR : "");
    458  assert(ff_expand_buffer.size < MAXPATHL);
    459 
    460  {
    461    size_t bufsize = ff_expand_buffer.size + search_ctx->ffsc_fix_path.size + 1;
    462    char *buf = xmalloc(bufsize);
    463 
    464    vim_snprintf(buf,
    465                 bufsize,
    466                 "%s%s",
    467                 ff_expand_buffer.data,
    468                 search_ctx->ffsc_fix_path.data);
    469    if (os_isdir(buf)) {
    470      if (search_ctx->ffsc_fix_path.size > 0) {
    471        add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data,
    472                                 search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size);
    473        ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size,
    474                                                      MAXPATHL - ff_expand_buffer.size,
    475                                                      "%s%s",
    476                                                      search_ctx->ffsc_fix_path.data,
    477                                                      add_sep ? PATHSEPSTR : "");
    478        assert(ff_expand_buffer.size < MAXPATHL);
    479      }
    480    } else {
    481      char *p = path_tail(search_ctx->ffsc_fix_path.data);
    482      int len = (int)search_ctx->ffsc_fix_path.size;
    483 
    484      if (p > search_ctx->ffsc_fix_path.data) {
    485        // do not add '..' to the path and start upwards searching
    486        len = (int)(p - search_ctx->ffsc_fix_path.data) - 1;
    487        if ((len >= 2 && strncmp(search_ctx->ffsc_fix_path.data, "..", 2) == 0)
    488            && (len == 2 || search_ctx->ffsc_fix_path.data[2] == PATHSEP)) {
    489          xfree(buf);
    490          goto error_return;
    491        }
    492 
    493        add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data,
    494                                 search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size);
    495        ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size,
    496                                                      MAXPATHL - ff_expand_buffer.size,
    497                                                      "%.*s%s",
    498                                                      len,
    499                                                      search_ctx->ffsc_fix_path.data,
    500                                                      add_sep ? PATHSEPSTR : "");
    501        assert(ff_expand_buffer.size < MAXPATHL);
    502      }
    503 
    504      if (search_ctx->ffsc_wc_path.data != NULL) {
    505        size_t tempsize = (search_ctx->ffsc_fix_path.size - (size_t)len)
    506                          + search_ctx->ffsc_wc_path.size + 1;
    507        char *temp = xmalloc(tempsize);
    508        search_ctx->ffsc_wc_path.size = (size_t)vim_snprintf(temp,
    509                                                             tempsize,
    510                                                             "%s%s",
    511                                                             search_ctx->ffsc_fix_path.data + len,
    512                                                             search_ctx->ffsc_wc_path.data);
    513        assert(search_ctx->ffsc_wc_path.size < tempsize);
    514        xfree(search_ctx->ffsc_wc_path.data);
    515        search_ctx->ffsc_wc_path.data = temp;
    516      }
    517    }
    518    xfree(buf);
    519  }
    520 
    521  sptr = ff_create_stack_element(ff_expand_buffer.data,
    522                                 ff_expand_buffer.size,
    523                                 search_ctx->ffsc_wc_path.data,
    524                                 search_ctx->ffsc_wc_path.size,
    525                                 level, 0);
    526 
    527  ff_push(search_ctx, sptr);
    528  search_ctx->ffsc_file_to_search = cbuf_to_string(filename, filenamelen);
    529  return search_ctx;
    530 
    531 error_return:
    532  // We clear the search context now!
    533  // Even when the caller gave us a (perhaps valid) context we free it here,
    534  // as we might have already destroyed it.
    535  vim_findfile_cleanup(search_ctx);
    536  return NULL;
    537 }
    538 
    539 /// @return  the stopdir string.  Check that ';' is not escaped.
    540 char *vim_findfile_stopdir(char *buf)
    541 {
    542  for (; *buf != NUL && *buf != ';' && (buf[0] != '\\' || buf[1] != ';'); buf++) {}
    543  char *dst = buf;
    544  if (*buf == ';') {
    545    goto is_semicolon;
    546  }
    547  if (*buf == NUL) {
    548    goto is_nul;
    549  }
    550  goto start;
    551  while (*buf != NUL && *buf != ';') {
    552    if (buf[0] == '\\' && buf[1] == ';') {
    553 start:
    554      // Overwrite the escape char.
    555      *dst++ = ';';
    556      buf += 2;
    557    } else {
    558      *dst++ = *buf++;
    559    }
    560  }
    561  assert(dst < buf);
    562  *dst = NUL;
    563  if (*buf == ';') {
    564 is_semicolon:
    565    *buf = NUL;
    566    buf++;
    567  } else {  // if (*buf == NUL)
    568 is_nul:
    569    buf = NULL;
    570  }
    571  return buf;
    572 }
    573 
    574 /// Clean up the given search context. Can handle a NULL pointer.
    575 void vim_findfile_cleanup(void *ctx)
    576 {
    577  if (ctx == NULL) {
    578    return;
    579  }
    580 
    581  vim_findfile_free_visited(ctx);
    582  ff_clear(ctx);
    583  xfree(ctx);
    584 }
    585 
    586 /// Find a file in a search context.
    587 /// The search context was created with vim_findfile_init() above.
    588 ///
    589 /// To get all matching files call this function until you get NULL.
    590 ///
    591 /// If the passed search_context is NULL, NULL is returned.
    592 ///
    593 /// The search algorithm is depth first. To change this replace the
    594 /// stack with a list (don't forget to leave partly searched directories on the
    595 /// top of the list).
    596 ///
    597 /// @return  a pointer to an allocated file name or,
    598 ///          NULL if nothing found.
    599 char *vim_findfile(void *search_ctx_arg)
    600 {
    601  String rest_of_wildcards;
    602  char *path_end = NULL;
    603  ff_stack_T *stackp = NULL;
    604 
    605  if (search_ctx_arg == NULL) {
    606    return NULL;
    607  }
    608 
    609  ff_search_ctx_T *search_ctx = (ff_search_ctx_T *)search_ctx_arg;
    610 
    611  // filepath is used as buffer for various actions and as the storage to
    612  // return a found filename.
    613  String file_path = { .data = xmalloc(MAXPATHL) };
    614 
    615  // store the end of the start dir -- needed for upward search
    616  if (search_ctx->ffsc_start_dir.data != NULL) {
    617    path_end = &search_ctx->ffsc_start_dir.data[search_ctx->ffsc_start_dir.size];
    618  }
    619 
    620  // upward search loop
    621  while (true) {
    622    // downward search loop
    623    while (true) {
    624      // check if user wants to stop the search
    625      os_breakcheck();
    626      if (got_int) {
    627        break;
    628      }
    629 
    630      // get directory to work on from stack
    631      stackp = ff_pop(search_ctx);
    632      if (stackp == NULL) {
    633        break;
    634      }
    635 
    636      // TODO(vim): decide if we leave this test in
    637      //
    638      // GOOD: don't search a directory(-tree) twice.
    639      // BAD:  - check linked list for every new directory entered.
    640      //       - check for double files also done below
    641      //
    642      // Here we check if we already searched this directory.
    643      // We already searched a directory if:
    644      // 1) The directory is the same.
    645      // 2) We would use the same wildcard string.
    646      //
    647      // Good if you have links on same directory via several ways
    648      //  or you have selfreferences in directories (e.g. SuSE Linux 6.3:
    649      //  /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop)
    650      //
    651      // This check is only needed for directories we work on for the
    652      // first time (hence stackp->ff_filearray == NULL)
    653      if (stackp->ffs_filearray == NULL
    654          && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list,
    655                              stackp->ffs_fix_path.data, stackp->ffs_fix_path.size,
    656                              stackp->ffs_wc_path.data, stackp->ffs_wc_path.size) == FAIL) {
    657 #ifdef FF_VERBOSE
    658        if (p_verbose >= 5) {
    659          verbose_enter_scroll();
    660          smsg(0, "Already Searched: %s (%s)",
    661               stackp->ffs_fix_path.data, stackp->ffs_wc_path.data);
    662          msg_puts("\n");  // don't overwrite this either
    663          verbose_leave_scroll();
    664        }
    665 #endif
    666        ff_free_stack_element(stackp);
    667        continue;
    668 #ifdef FF_VERBOSE
    669      } else if (p_verbose >= 5) {
    670        verbose_enter_scroll();
    671        smsg(0, "Searching: %s (%s)",
    672             stackp->ffs_fix_path.data, stackp->ffs_wc_path.data);
    673        msg_puts("\n");  // don't overwrite this either
    674        verbose_leave_scroll();
    675 #endif
    676      }
    677 
    678      // check depth
    679      if (stackp->ffs_level <= 0) {
    680        ff_free_stack_element(stackp);
    681        continue;
    682      }
    683 
    684      file_path.data[0] = NUL;
    685      file_path.size = 0;
    686 
    687      // If no filearray till now expand wildcards
    688      // The function expand_wildcards() can handle an array of paths
    689      // and all possible expands are returned in one array. We use this
    690      // to handle the expansion of '**' into an empty string.
    691      if (stackp->ffs_filearray == NULL) {
    692        char *dirptrs[2];
    693 
    694        // we use filepath to build the path expand_wildcards() should expand.
    695        dirptrs[0] = file_path.data;
    696        dirptrs[1] = NULL;
    697 
    698        // if we have a start dir copy it in
    699        if (!vim_isAbsName(stackp->ffs_fix_path.data)
    700            && search_ctx->ffsc_start_dir.data) {
    701          if (search_ctx->ffsc_start_dir.size + 1 >= MAXPATHL) {
    702            ff_free_stack_element(stackp);
    703            goto fail;
    704          }
    705          bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data,
    706                                        search_ctx->ffsc_start_dir.data
    707                                        + search_ctx->ffsc_start_dir.size);
    708          file_path.size = (size_t)vim_snprintf(file_path.data,
    709                                                MAXPATHL,
    710                                                "%s%s",
    711                                                search_ctx->ffsc_start_dir.data,
    712                                                add_sep ? PATHSEPSTR : "");
    713          if (file_path.size >= MAXPATHL) {
    714            ff_free_stack_element(stackp);
    715            goto fail;
    716          }
    717        }
    718 
    719        // append the fix part of the search path
    720        if (file_path.size + stackp->ffs_fix_path.size + 1 >= MAXPATHL) {
    721          ff_free_stack_element(stackp);
    722          goto fail;
    723        }
    724        bool add_sep = !after_pathsep(stackp->ffs_fix_path.data,
    725                                      stackp->ffs_fix_path.data + stackp->ffs_fix_path.size);
    726        file_path.size += (size_t)vim_snprintf(file_path.data + file_path.size,
    727                                               MAXPATHL - file_path.size,
    728                                               "%s%s",
    729                                               stackp->ffs_fix_path.data,
    730                                               add_sep ? PATHSEPSTR : "");
    731        if (file_path.size >= MAXPATHL) {
    732          ff_free_stack_element(stackp);
    733          goto fail;
    734        }
    735 
    736        rest_of_wildcards = stackp->ffs_wc_path;
    737        if (*rest_of_wildcards.data != NUL) {
    738          if (strncmp(rest_of_wildcards.data, "**", 2) == 0) {
    739            // pointer to the restrict byte
    740            // The restrict byte is not a character!
    741            char *p = rest_of_wildcards.data + 2;
    742 
    743            if (*p > 0) {
    744              (*p)--;
    745              if (file_path.size + 1 >= MAXPATHL) {
    746                ff_free_stack_element(stackp);
    747                goto fail;
    748              }
    749              file_path.data[file_path.size++] = '*';
    750            }
    751 
    752            if (*p == 0) {
    753              // remove '**<numb> from wildcards
    754              memmove(rest_of_wildcards.data,
    755                      rest_of_wildcards.data + 3,
    756                      (rest_of_wildcards.size - 3) + 1);    // +1 for NUL
    757              rest_of_wildcards.size -= 3;
    758              stackp->ffs_wc_path.size = rest_of_wildcards.size;
    759            } else {
    760              rest_of_wildcards.data += 3;
    761              rest_of_wildcards.size -= 3;
    762            }
    763 
    764            if (stackp->ffs_star_star_empty == 0) {
    765              // if not done before, expand '**' to empty
    766              stackp->ffs_star_star_empty = 1;
    767              dirptrs[1] = stackp->ffs_fix_path.data;
    768            }
    769          }
    770 
    771          // Here we copy until the next path separator or the end of
    772          // the path. If we stop at a path separator, there is
    773          // still something else left. This is handled below by
    774          // pushing every directory returned from expand_wildcards()
    775          // on the stack again for further search.
    776          while (*rest_of_wildcards.data
    777                 && !vim_ispathsep(*rest_of_wildcards.data)) {
    778            if (file_path.size + 1 >= MAXPATHL) {
    779              ff_free_stack_element(stackp);
    780              goto fail;
    781            }
    782            file_path.data[file_path.size++] = *rest_of_wildcards.data++;
    783            rest_of_wildcards.size--;
    784          }
    785 
    786          file_path.data[file_path.size] = NUL;
    787          if (vim_ispathsep(*rest_of_wildcards.data)) {
    788            rest_of_wildcards.data++;
    789            rest_of_wildcards.size--;
    790          }
    791        }
    792 
    793        // Expand wildcards like "*" and "$VAR".
    794        // If the path is a URL don't try this.
    795        if (path_with_url(dirptrs[0])) {
    796          stackp->ffs_filearray = xmalloc(sizeof(char *));
    797          stackp->ffs_filearray[0] = xmemdupz(dirptrs[0], file_path.size);
    798          stackp->ffs_filearray_size = 1;
    799        } else {
    800          // Add EW_NOTWILD because the expanded path may contain
    801          // wildcard characters that are to be taken literally.
    802          // This is a bit of a hack.
    803          expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs,
    804                           &stackp->ffs_filearray_size,
    805                           &stackp->ffs_filearray,
    806                           EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD);
    807        }
    808 
    809        stackp->ffs_filearray_cur = 0;
    810        stackp->ffs_stage = 0;
    811      } else {
    812        rest_of_wildcards.data = &stackp->ffs_wc_path.data[stackp->ffs_wc_path.size];
    813        rest_of_wildcards.size = 0;
    814      }
    815 
    816      if (stackp->ffs_stage == 0) {
    817        // this is the first time we work on this directory
    818        if (*rest_of_wildcards.data == NUL) {
    819          // We don't have further wildcards to expand, so we have to
    820          // check for the final file now.
    821          for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) {
    822            if (!path_with_url(stackp->ffs_filearray[i])
    823                && !os_isdir(stackp->ffs_filearray[i])) {
    824              continue;                 // not a directory
    825            }
    826            // prepare the filename to be checked for existence below
    827            size_t len = strlen(stackp->ffs_filearray[i]);
    828            if (len + 1 + search_ctx->ffsc_file_to_search.size >= MAXPATHL) {
    829              ff_free_stack_element(stackp);
    830              goto fail;
    831            }
    832            bool add_sep = !after_pathsep(stackp->ffs_filearray[i],
    833                                          stackp->ffs_filearray[i] + len);
    834            file_path.size = (size_t)vim_snprintf(file_path.data,
    835                                                  MAXPATHL,
    836                                                  "%s%s%s",
    837                                                  stackp->ffs_filearray[i],
    838                                                  add_sep ? PATHSEPSTR : "",
    839                                                  search_ctx->ffsc_file_to_search.data);
    840            if (file_path.size >= MAXPATHL) {
    841              ff_free_stack_element(stackp);
    842              goto fail;
    843            }
    844 
    845            // Try without extra suffix and then with suffixes
    846            // from 'suffixesadd'.
    847            len = file_path.size;
    848            char *suf = search_ctx->ffsc_tagfile ? "" : curbuf->b_p_sua;
    849            while (true) {
    850              // if file exists and we didn't already find it
    851              if ((path_with_url(file_path.data)
    852                   || (os_path_exists(file_path.data)
    853                       && (search_ctx->ffsc_find_what == FINDFILE_BOTH
    854                           || ((search_ctx->ffsc_find_what == FINDFILE_DIR)
    855                               == os_isdir(file_path.data)))))
    856 #ifndef FF_VERBOSE
    857                  && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list,
    858                                       file_path.data, file_path.size, "", 0) == OK)
    859 #endif
    860                  ) {
    861 #ifdef FF_VERBOSE
    862                if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list,
    863                                     file_path.data, file_path.size, "", 0) == FAIL) {
    864                  if (p_verbose >= 5) {
    865                    verbose_enter_scroll();
    866                    smsg(0, "Already: %s", file_path.data);
    867                    msg_puts("\n");  // don't overwrite this either
    868                    verbose_leave_scroll();
    869                  }
    870                  continue;
    871                }
    872 #endif
    873 
    874                // push dir to examine rest of subdirs later
    875                assert(i < INT_MAX);
    876                stackp->ffs_filearray_cur = i + 1;
    877                ff_push(search_ctx, stackp);
    878 
    879                if (!path_with_url(file_path.data)) {
    880                  file_path.size = simplify_filename(file_path.data);
    881                }
    882 
    883                if (os_dirname(ff_expand_buffer.data, MAXPATHL) == OK) {
    884                  ff_expand_buffer.size = strlen(ff_expand_buffer.data);
    885                  char *p = path_shorten_fname(file_path.data, ff_expand_buffer.data);
    886                  if (p != NULL) {
    887                    memmove(file_path.data, p,
    888                            (size_t)((file_path.data + file_path.size) - p) + 1);  // +1 for NUL
    889                    file_path.size -= (size_t)(p - file_path.data);
    890                  }
    891                }
    892 #ifdef FF_VERBOSE
    893                if (p_verbose >= 5) {
    894                  verbose_enter_scroll();
    895                  smsg(0, "HIT: %s", file_path.data);
    896                  msg_puts("\n");  // don't overwrite this either
    897                  verbose_leave_scroll();
    898                }
    899 #endif
    900                return file_path.data;
    901              }
    902 
    903              // Not found or found already, try next suffix.
    904              if (*suf == NUL) {
    905                break;
    906              }
    907              assert(MAXPATHL >= file_path.size);
    908              file_path.size = len + copy_option_part(&suf, file_path.data + len,
    909                                                      MAXPATHL - len, ",");
    910            }
    911          }
    912        } else {
    913          // still wildcards left, push the directories for further search
    914          for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) {
    915            if (!os_isdir(stackp->ffs_filearray[i])) {
    916              continue;                 // not a directory
    917            }
    918            ff_push(search_ctx,
    919                    ff_create_stack_element(stackp->ffs_filearray[i],
    920                                            strlen(stackp->ffs_filearray[i]),
    921                                            rest_of_wildcards.data,
    922                                            rest_of_wildcards.size,
    923                                            stackp->ffs_level - 1, 0));
    924          }
    925        }
    926        stackp->ffs_filearray_cur = 0;
    927        stackp->ffs_stage = 1;
    928      }
    929 
    930      // if wildcards contains '**' we have to descent till we reach the
    931      // leaves of the directory tree.
    932      if (strncmp(stackp->ffs_wc_path.data, "**", 2) == 0) {
    933        for (int i = stackp->ffs_filearray_cur;
    934             i < stackp->ffs_filearray_size; i++) {
    935          if (path_fnamecmp(stackp->ffs_filearray[i],
    936                            stackp->ffs_fix_path.data) == 0) {
    937            continue;             // don't repush same directory
    938          }
    939          if (!os_isdir(stackp->ffs_filearray[i])) {
    940            continue;               // not a directory
    941          }
    942          ff_push(search_ctx,
    943                  ff_create_stack_element(stackp->ffs_filearray[i],
    944                                          strlen(stackp->ffs_filearray[i]),
    945                                          stackp->ffs_wc_path.data,
    946                                          stackp->ffs_wc_path.size,
    947                                          stackp->ffs_level - 1, 1));
    948        }
    949      }
    950 
    951      // we are done with the current directory
    952      ff_free_stack_element(stackp);
    953    }
    954 
    955    // If we reached this, we didn't find anything downwards.
    956    // Let's check if we should do an upward search.
    957    if (search_ctx->ffsc_start_dir.data
    958        && search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
    959      ff_stack_T *sptr;
    960      // path_end may point to the NUL or the previous path separator
    961      ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir.data) + (*path_end != NUL);
    962 
    963      // is the last starting directory in the stop list?
    964      if (ff_path_in_stoplist(search_ctx->ffsc_start_dir.data,
    965                              (size_t)plen, search_ctx->ffsc_stopdirs_v)) {
    966        break;
    967      }
    968 
    969      // cut of last dir
    970      while (path_end > search_ctx->ffsc_start_dir.data && vim_ispathsep(*path_end)) {
    971        path_end--;
    972      }
    973      while (path_end > search_ctx->ffsc_start_dir.data && !vim_ispathsep(path_end[-1])) {
    974        path_end--;
    975      }
    976      *path_end = NUL;
    977 
    978      // we may have shortened search_ctx->ffsc_start_dir, so update it's length
    979      search_ctx->ffsc_start_dir.size = (size_t)(path_end - search_ctx->ffsc_start_dir.data);
    980      path_end--;
    981 
    982      if (*search_ctx->ffsc_start_dir.data == NUL) {
    983        break;
    984      }
    985 
    986      if (search_ctx->ffsc_start_dir.size + 1
    987          + search_ctx->ffsc_fix_path.size >= MAXPATHL) {
    988        goto fail;
    989      }
    990      bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data,
    991                                    search_ctx->ffsc_start_dir.data
    992                                    + search_ctx->ffsc_start_dir.size);
    993      file_path.size = (size_t)vim_snprintf(file_path.data,
    994                                            MAXPATHL,
    995                                            "%s%s%s",
    996                                            search_ctx->ffsc_start_dir.data,
    997                                            add_sep ? PATHSEPSTR : "",
    998                                            search_ctx->ffsc_fix_path.data);
    999      if (file_path.size >= MAXPATHL) {
   1000        goto fail;
   1001      }
   1002 
   1003      // create a new stack entry
   1004      sptr = ff_create_stack_element(file_path.data,
   1005                                     file_path.size,
   1006                                     search_ctx->ffsc_wc_path.data,
   1007                                     search_ctx->ffsc_wc_path.size,
   1008                                     search_ctx->ffsc_level, 0);
   1009      ff_push(search_ctx, sptr);
   1010    } else {
   1011      break;
   1012    }
   1013  }
   1014 
   1015 fail:
   1016  xfree(file_path.data);
   1017  return NULL;
   1018 }
   1019 
   1020 /// Free the list of lists of visited files and directories
   1021 /// Can handle it if the passed search_context is NULL;
   1022 static void vim_findfile_free_visited(void *search_ctx_arg)
   1023 {
   1024  if (search_ctx_arg == NULL) {
   1025    return;
   1026  }
   1027 
   1028  ff_search_ctx_T *search_ctx = (ff_search_ctx_T *)search_ctx_arg;
   1029  vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list);
   1030  vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list);
   1031 }
   1032 
   1033 static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
   1034 {
   1035  ff_visited_list_hdr_T *vp;
   1036 
   1037  while (*list_headp != NULL) {
   1038    vp = (*list_headp)->ffvl_next;
   1039    ff_free_visited_list((*list_headp)->ffvl_visited_list);
   1040 
   1041    xfree((*list_headp)->ffvl_filename);
   1042    xfree(*list_headp);
   1043    *list_headp = vp;
   1044  }
   1045  *list_headp = NULL;
   1046 }
   1047 
   1048 static void ff_free_visited_list(ff_visited_T *vl)
   1049 {
   1050  ff_visited_T *vp;
   1051 
   1052  while (vl != NULL) {
   1053    vp = vl->ffv_next;
   1054    xfree(vl->ffv_wc_path);
   1055    xfree(vl);
   1056    vl = vp;
   1057  }
   1058  vl = NULL;
   1059 }
   1060 
   1061 /// @return  the already visited list for the given filename. If none is found it
   1062 ///          allocates a new one.
   1063 static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, size_t filenamelen,
   1064                                                  ff_visited_list_hdr_T **list_headp)
   1065 {
   1066  ff_visited_list_hdr_T *retptr = NULL;
   1067 
   1068  // check if a visited list for the given filename exists
   1069  if (*list_headp != NULL) {
   1070    retptr = *list_headp;
   1071    while (retptr != NULL) {
   1072      if (path_fnamecmp(filename, retptr->ffvl_filename) == 0) {
   1073 #ifdef FF_VERBOSE
   1074        if (p_verbose >= 5) {
   1075          verbose_enter_scroll();
   1076          smsg(0, "ff_get_visited_list: FOUND list for %s", filename);
   1077          msg_puts("\n");  // don't overwrite this either
   1078          verbose_leave_scroll();
   1079        }
   1080 #endif
   1081        return retptr;
   1082      }
   1083      retptr = retptr->ffvl_next;
   1084    }
   1085  }
   1086 
   1087 #ifdef FF_VERBOSE
   1088  if (p_verbose >= 5) {
   1089    verbose_enter_scroll();
   1090    smsg(0, "ff_get_visited_list: new list for %s", filename);
   1091    msg_puts("\n");  // don't overwrite this either
   1092    verbose_leave_scroll();
   1093  }
   1094 #endif
   1095 
   1096  // if we reach this we didn't find a list and we have to allocate new list
   1097  retptr = xmalloc(sizeof(*retptr));
   1098 
   1099  retptr->ffvl_visited_list = NULL;
   1100  retptr->ffvl_filename = xmemdupz(filename, filenamelen);
   1101  retptr->ffvl_next = *list_headp;
   1102  *list_headp = retptr;
   1103 
   1104  return retptr;
   1105 }
   1106 
   1107 /// Check if two wildcard paths are equal.
   1108 /// They are equal if:
   1109 ///  - both paths are NULL
   1110 ///  - they have the same length
   1111 ///  - char by char comparison is OK
   1112 ///  - the only differences are in the counters behind a '**', so
   1113 ///    '**\20' is equal to '**\24'
   1114 static bool ff_wc_equal(char *s1, char *s2)
   1115 {
   1116  int i, j;
   1117  int prev1 = NUL;
   1118  int prev2 = NUL;
   1119 
   1120  if (s1 == s2) {
   1121    return true;
   1122  }
   1123 
   1124  if (s1 == NULL || s2 == NULL) {
   1125    return false;
   1126  }
   1127 
   1128  for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) {
   1129    int c1 = utf_ptr2char(s1 + i);
   1130    int c2 = utf_ptr2char(s2 + j);
   1131 
   1132    if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2)
   1133        && (prev1 != '*' || prev2 != '*')) {
   1134      return false;
   1135    }
   1136    prev2 = prev1;
   1137    prev1 = c1;
   1138 
   1139    i += utfc_ptr2len(s1 + i);
   1140    j += utfc_ptr2len(s2 + j);
   1141  }
   1142  return s1[i] == s2[j];
   1143 }
   1144 
   1145 /// maintains the list of already visited files and dirs
   1146 ///
   1147 /// @return  FAIL if the given file/dir is already in the list or,
   1148 ///          OK if it is newly added
   1149 static int ff_check_visited(ff_visited_T **visited_list, char *fname, size_t fnamelen,
   1150                            char *wc_path, size_t wc_pathlen)
   1151 {
   1152  ff_visited_T *vp;
   1153  bool url = false;
   1154 
   1155  FileID file_id;
   1156  // For a URL we only compare the name, otherwise we compare the
   1157  // device/inode.
   1158  if (path_with_url(fname)) {
   1159    xmemcpyz(ff_expand_buffer.data, fname, fnamelen);
   1160    ff_expand_buffer.size = fnamelen;
   1161    url = true;
   1162  } else {
   1163    ff_expand_buffer.data[0] = NUL;
   1164    ff_expand_buffer.size = 0;
   1165    if (!os_fileid(fname, &file_id)) {
   1166      return FAIL;
   1167    }
   1168  }
   1169 
   1170  // check against list of already visited files
   1171  for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) {
   1172    if ((url && path_fnamecmp(vp->ffv_fname, ff_expand_buffer.data) == 0)
   1173        || (!url && vp->file_id_valid
   1174            && os_fileid_equal(&(vp->file_id), &file_id))) {
   1175      // are the wildcard parts equal
   1176      if (ff_wc_equal(vp->ffv_wc_path, wc_path)) {
   1177        // already visited
   1178        return FAIL;
   1179      }
   1180    }
   1181  }
   1182 
   1183  // New file/dir.  Add it to the list of visited files/dirs.
   1184  vp = xmalloc(offsetof(ff_visited_T, ffv_fname) + ff_expand_buffer.size + 1);
   1185 
   1186  if (!url) {
   1187    vp->file_id_valid = true;
   1188    vp->file_id = file_id;
   1189    vp->ffv_fname[0] = NUL;
   1190  } else {
   1191    vp->file_id_valid = false;
   1192    STRCPY(vp->ffv_fname, ff_expand_buffer.data);
   1193  }
   1194 
   1195  if (wc_path != NULL) {
   1196    vp->ffv_wc_path = xmemdupz(wc_path, wc_pathlen);
   1197  } else {
   1198    vp->ffv_wc_path = NULL;
   1199  }
   1200 
   1201  vp->ffv_next = *visited_list;
   1202  *visited_list = vp;
   1203 
   1204  return OK;
   1205 }
   1206 
   1207 /// create stack element from given path pieces
   1208 static ff_stack_T *ff_create_stack_element(char *fix_part, size_t fix_partlen, char *wc_part,
   1209                                           size_t wc_partlen, int level, int star_star_empty)
   1210 {
   1211  ff_stack_T *stack = xmalloc(sizeof(ff_stack_T));
   1212 
   1213  stack->ffs_prev = NULL;
   1214  stack->ffs_filearray = NULL;
   1215  stack->ffs_filearray_size = 0;
   1216  stack->ffs_filearray_cur = 0;
   1217  stack->ffs_stage = 0;
   1218  stack->ffs_level = level;
   1219  stack->ffs_star_star_empty = star_star_empty;
   1220 
   1221  // the following saves NULL pointer checks in vim_findfile
   1222  if (fix_part == NULL) {
   1223    fix_part = "";
   1224    fix_partlen = 0;
   1225  }
   1226  stack->ffs_fix_path = cbuf_to_string(fix_part, fix_partlen);
   1227 
   1228  if (wc_part == NULL) {
   1229    wc_part = "";
   1230    wc_partlen = 0;
   1231  }
   1232  stack->ffs_wc_path = cbuf_to_string(wc_part, wc_partlen);
   1233 
   1234  return stack;
   1235 }
   1236 
   1237 /// Push a dir on the directory stack.
   1238 static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
   1239 {
   1240  // check for NULL pointer, not to return an error to the user, but
   1241  // to prevent a crash
   1242  if (stack_ptr == NULL) {
   1243    return;
   1244  }
   1245 
   1246  stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
   1247  search_ctx->ffsc_stack_ptr = stack_ptr;
   1248 }
   1249 
   1250 /// Pop a dir from the directory stack.
   1251 ///
   1252 /// @return  NULL if stack is empty.
   1253 static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
   1254 {
   1255  ff_stack_T *sptr = search_ctx->ffsc_stack_ptr;
   1256  if (search_ctx->ffsc_stack_ptr != NULL) {
   1257    search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev;
   1258  }
   1259 
   1260  return sptr;
   1261 }
   1262 
   1263 /// free the given stack element
   1264 static void ff_free_stack_element(ff_stack_T *const stack_ptr)
   1265 {
   1266  if (stack_ptr == NULL) {
   1267    return;
   1268  }
   1269 
   1270  // API_CLEAR_STRING handles possible NULL pointers
   1271  API_CLEAR_STRING(stack_ptr->ffs_fix_path);
   1272  API_CLEAR_STRING(stack_ptr->ffs_wc_path);
   1273 
   1274  if (stack_ptr->ffs_filearray != NULL) {
   1275    FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray);
   1276  }
   1277 
   1278  xfree(stack_ptr);
   1279 }
   1280 
   1281 /// Clear the search context, but NOT the visited list.
   1282 static void ff_clear(ff_search_ctx_T *search_ctx)
   1283 {
   1284  ff_stack_T *sptr;
   1285 
   1286  // clear up stack
   1287  while ((sptr = ff_pop(search_ctx)) != NULL) {
   1288    ff_free_stack_element(sptr);
   1289  }
   1290 
   1291  if (search_ctx->ffsc_stopdirs_v != NULL) {
   1292    int i = 0;
   1293 
   1294    while (search_ctx->ffsc_stopdirs_v[i].data != NULL) {
   1295      xfree(search_ctx->ffsc_stopdirs_v[i].data);
   1296      i++;
   1297    }
   1298    XFREE_CLEAR(search_ctx->ffsc_stopdirs_v);
   1299  }
   1300 
   1301  // reset everything
   1302  API_CLEAR_STRING(search_ctx->ffsc_file_to_search);
   1303  API_CLEAR_STRING(search_ctx->ffsc_start_dir);
   1304  API_CLEAR_STRING(search_ctx->ffsc_fix_path);
   1305  API_CLEAR_STRING(search_ctx->ffsc_wc_path);
   1306  search_ctx->ffsc_level = 0;
   1307 }
   1308 
   1309 /// check if the given path is in the stopdirs
   1310 ///
   1311 /// @return  true if yes else false
   1312 static bool ff_path_in_stoplist(char *path, size_t path_len, String *stopdirs_v)
   1313 {
   1314  // eat up trailing path separators, except the first
   1315  while (path_len > 1 && vim_ispathsep(path[path_len - 1])) {
   1316    path_len--;
   1317  }
   1318 
   1319  // if no path consider it as match
   1320  if (path_len == 0) {
   1321    return true;
   1322  }
   1323 
   1324  for (int i = 0; stopdirs_v[i].data != NULL; i++) {
   1325    // match for parent directory. So '/home' also matches
   1326    // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
   1327    // '/home/r' would also match '/home/rks'
   1328    if (path_fnamencmp(stopdirs_v[i].data, path, path_len) == 0
   1329        && (stopdirs_v[i].size <= path_len
   1330            || vim_ispathsep(stopdirs_v[i].data[path_len]))) {
   1331      return true;
   1332    }
   1333  }
   1334 
   1335  return false;
   1336 }
   1337 
   1338 /// Find the file name "ptr[len]" in the path.  Also finds directory names.
   1339 ///
   1340 /// On the first call set the parameter 'first' to true to initialize
   1341 /// the search.  For repeating calls to false.
   1342 ///
   1343 /// Repeating calls will return other files called 'ptr[len]' from the path.
   1344 ///
   1345 /// Only on the first call 'ptr' and 'len' are used.  For repeating calls they
   1346 /// don't need valid values.
   1347 ///
   1348 /// If nothing found on the first call the option FNAME_MESS will issue the
   1349 /// message:
   1350 ///          'Can't find file "<file>" in path'
   1351 /// On repeating calls:
   1352 ///          'No more file "<file>" found in path'
   1353 ///
   1354 /// options:
   1355 /// FNAME_MESS       give error message when not found
   1356 ///
   1357 /// Uses NameBuff[]!
   1358 ///
   1359 /// @param ptr  file name
   1360 /// @param len  length of file name
   1361 /// @param first  use count'th matching file name
   1362 /// @param rel_fname  file name searching relative to
   1363 /// @param[in,out] file_to_find  modified copy of file name
   1364 /// @param[in,out] search_ctx  state of the search
   1365 ///
   1366 /// @return  an allocated string for the file name.  NULL for error.
   1367 char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname,
   1368                        char **file_to_find, char **search_ctx)
   1369 {
   1370  return find_file_in_path_option(ptr, len, options, first,
   1371                                  (*curbuf->b_p_path == NUL
   1372                                   ? p_path
   1373                                   : curbuf->b_p_path),
   1374                                  FINDFILE_BOTH, rel_fname, curbuf->b_p_sua,
   1375                                  file_to_find, search_ctx);
   1376 }
   1377 
   1378 #if defined(EXITFREE)
   1379 void free_findfile(void)
   1380 {
   1381  API_CLEAR_STRING(ff_expand_buffer);
   1382 }
   1383 #endif
   1384 
   1385 /// Find the directory name "ptr[len]" in the path.
   1386 ///
   1387 /// options:
   1388 /// FNAME_MESS       give error message when not found
   1389 /// FNAME_UNESC      unescape backslashes
   1390 ///
   1391 /// Uses NameBuff[]!
   1392 ///
   1393 /// @param ptr  file name
   1394 /// @param len  length of file name
   1395 /// @param rel_fname  file name searching relative to
   1396 /// @param[in,out] file_to_find  modified copy of file name
   1397 /// @param[in,out] search_ctx  state of the search
   1398 ///
   1399 /// @return  an allocated string for the file name.  NULL for error.
   1400 char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname,
   1401                             char **file_to_find, char **search_ctx)
   1402 {
   1403  return find_file_in_path_option(ptr, len, options, true, p_cdpath,
   1404                                  FINDFILE_DIR, rel_fname, "",
   1405                                  file_to_find, search_ctx);
   1406 }
   1407 
   1408 /// @param ptr  file name
   1409 /// @param len  length of file name
   1410 /// @param first  use count'th matching file name
   1411 /// @param path_option  p_path or p_cdpath
   1412 /// @param find_what  FINDFILE_FILE, _DIR or _BOTH
   1413 /// @param rel_fname  file name we are looking relative to.
   1414 /// @param suffixes  list of suffixes, 'suffixesadd' option
   1415 /// @param[in,out] file_to_find  modified copy of file name
   1416 /// @param[in,out] search_ctx_arg  state of the search
   1417 char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option,
   1418                               int find_what, char *rel_fname, char *suffixes, char **file_to_find,
   1419                               char **search_ctx_arg)
   1420 {
   1421  ff_search_ctx_T **search_ctx = (ff_search_ctx_T **)search_ctx_arg;
   1422  static char *dir;
   1423  static bool did_findfile_init = false;
   1424  char *file_name = NULL;
   1425  static size_t file_to_findlen = 0;
   1426 
   1427  if (rel_fname != NULL && path_with_url(rel_fname)) {
   1428    // Do not attempt to search "relative" to a URL. #6009
   1429    rel_fname = NULL;
   1430  }
   1431 
   1432  if (first == true) {
   1433    if (len == 0) {
   1434      return NULL;
   1435    }
   1436 
   1437    // copy file name into NameBuff, expanding environment variables
   1438    char save_char = ptr[len];
   1439    ptr[len] = NUL;
   1440    file_to_findlen = expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL);
   1441    ptr[len] = save_char;
   1442 
   1443    xfree(*file_to_find);
   1444    *file_to_find = xmemdupz(NameBuff, file_to_findlen);
   1445    if (options & FNAME_UNESC) {
   1446      // Change all "\ " to " ".
   1447      for (ptr = *file_to_find; *ptr != NUL; ptr++) {
   1448        if (ptr[0] == '\\' && ptr[1] == ' ') {
   1449          memmove(ptr, ptr + 1,
   1450                  (size_t)((*file_to_find + file_to_findlen) - (ptr + 1)) + 1);
   1451          file_to_findlen--;
   1452        }
   1453      }
   1454    }
   1455  }
   1456 
   1457  bool rel_to_curdir = ((*file_to_find)[0] == '.'
   1458                        && ((*file_to_find)[1] == NUL
   1459                            || vim_ispathsep((*file_to_find)[1])
   1460                            || ((*file_to_find)[1] == '.'
   1461                                && ((*file_to_find)[2] == NUL
   1462                                    || vim_ispathsep((*file_to_find)[2])))));
   1463  if (vim_isAbsName(*file_to_find)
   1464      // "..", "../path", "." and "./path": don't use the path_option
   1465      || rel_to_curdir
   1466 #if defined(MSWIN)
   1467      // handle "\tmp" as absolute path
   1468      || vim_ispathsep((*file_to_find)[0])
   1469      // handle "c:name" as absolute path
   1470      || ((*file_to_find)[0] != NUL && (*file_to_find)[1] == ':')
   1471 #endif
   1472      ) {
   1473    // Absolute path, no need to use "path_option".
   1474    // If this is not a first call, return NULL.  We already returned a
   1475    // filename on the first call.
   1476    if (first == true) {
   1477      if (path_with_url(*file_to_find)) {
   1478        file_name = xmemdupz(*file_to_find, file_to_findlen);
   1479        goto theend;
   1480      }
   1481 
   1482      size_t rel_fnamelen = rel_fname != NULL ? strlen(rel_fname) : 0;
   1483 
   1484      // When FNAME_REL flag given first use the directory of the file.
   1485      // Otherwise or when this fails use the current directory.
   1486      for (int run = 1; run <= 2; run++) {
   1487        size_t l = file_to_findlen;
   1488        if (run == 1
   1489            && rel_to_curdir
   1490            && (options & FNAME_REL)
   1491            && rel_fname != NULL
   1492            && rel_fnamelen + l < MAXPATHL) {
   1493          l = (size_t)vim_snprintf(NameBuff,
   1494                                   MAXPATHL,
   1495                                   "%.*s%s",
   1496                                   (int)(path_tail(rel_fname) - rel_fname),
   1497                                   rel_fname,
   1498                                   *file_to_find);
   1499          assert(l < MAXPATHL);
   1500        } else {
   1501          STRCPY(NameBuff, *file_to_find);
   1502          run = 2;
   1503        }
   1504 
   1505        // When the file doesn't exist, try adding parts of 'suffixesadd'.
   1506        size_t NameBufflen = l;
   1507        char *suffix = suffixes;
   1508        while (true) {
   1509          if ((os_path_exists(NameBuff)
   1510               && (find_what == FINDFILE_BOTH
   1511                   || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) {
   1512            file_name = xmemdupz(NameBuff, NameBufflen);
   1513            goto theend;
   1514          }
   1515          if (*suffix == NUL) {
   1516            break;
   1517          }
   1518          assert(MAXPATHL >= l);
   1519          NameBufflen = l + copy_option_part(&suffix, NameBuff + l, MAXPATHL - l, ",");
   1520        }
   1521      }
   1522    }
   1523  } else {
   1524    // Loop over all paths in the 'path' or 'cdpath' option.
   1525    // When "first" is set, first setup to the start of the option.
   1526    // Otherwise continue to find the next match.
   1527    if (first == true) {
   1528      // vim_findfile_free_visited can handle a possible NULL pointer
   1529      vim_findfile_free_visited(*search_ctx);
   1530      dir = path_option;
   1531      did_findfile_init = false;
   1532    }
   1533 
   1534    while (true) {
   1535      if (did_findfile_init) {
   1536        file_name = vim_findfile(*search_ctx);
   1537        if (file_name != NULL) {
   1538          break;
   1539        }
   1540 
   1541        did_findfile_init = false;
   1542      } else {
   1543        char *r_ptr;
   1544 
   1545        if (dir == NULL || *dir == NUL) {
   1546          // We searched all paths of the option, now we can free the search context.
   1547          vim_findfile_cleanup(*search_ctx);
   1548          *search_ctx = NULL;
   1549          break;
   1550        }
   1551 
   1552        char *buf = xmalloc(MAXPATHL);
   1553 
   1554        // copy next path
   1555        buf[0] = NUL;
   1556        copy_option_part(&dir, buf, MAXPATHL, " ,");
   1557 
   1558        // get the stopdir string
   1559        r_ptr = vim_findfile_stopdir(buf);
   1560        *search_ctx = vim_findfile_init(buf, *file_to_find, file_to_findlen,
   1561                                        r_ptr, 100, false, find_what,
   1562                                        *search_ctx, false, rel_fname);
   1563        if (*search_ctx != NULL) {
   1564          did_findfile_init = true;
   1565        }
   1566        xfree(buf);
   1567      }
   1568    }
   1569  }
   1570  if (file_name == NULL && (options & FNAME_MESS)) {
   1571    if (first == true) {
   1572      if (find_what == FINDFILE_DIR) {
   1573        semsg(_(e_cant_find_directory_str_in_cdpath), *file_to_find);
   1574      } else {
   1575        semsg(_(e_cant_find_file_str_in_path), *file_to_find);
   1576      }
   1577    } else {
   1578      if (find_what == FINDFILE_DIR) {
   1579        semsg(_(e_no_more_directory_str_found_in_cdpath), *file_to_find);
   1580      } else {
   1581        semsg(_(e_no_more_file_str_found_in_path), *file_to_find);
   1582      }
   1583    }
   1584  }
   1585 
   1586 theend:
   1587  return file_name;
   1588 }
   1589 
   1590 /// Get the file name at the cursor.
   1591 /// If Visual mode is active, use the selected text if it's in one line.
   1592 /// Returns the name in allocated memory, NULL for failure.
   1593 char *grab_file_name(int count, linenr_T *file_lnum)
   1594 {
   1595  int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
   1596  if (VIsual_active) {
   1597    size_t len;
   1598    char *ptr;
   1599    if (get_visual_text(NULL, &ptr, &len) == FAIL) {
   1600      return NULL;
   1601    }
   1602    // Only recognize ":123" here
   1603    if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) {
   1604      char *p = ptr + len + 1;
   1605 
   1606      *file_lnum = getdigits_int32(&p, false, 0);
   1607    }
   1608    return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
   1609  }
   1610  return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
   1611 }
   1612 
   1613 /// Return the file name under or after the cursor.
   1614 ///
   1615 /// The 'path' option is searched if the file name is not absolute.
   1616 /// The string returned has been alloc'ed and should be freed by the caller.
   1617 /// NULL is returned if the file name or file is not found.
   1618 ///
   1619 /// options:
   1620 /// FNAME_MESS       give error messages
   1621 /// FNAME_EXP        expand to path
   1622 /// FNAME_HYP        check for hypertext link
   1623 /// FNAME_INCL       apply "includeexpr"
   1624 char *file_name_at_cursor(int options, int count, linenr_T *file_lnum)
   1625 {
   1626  return file_name_in_line(get_cursor_line_ptr(),
   1627                           curwin->w_cursor.col, options, count, curbuf->b_ffname,
   1628                           file_lnum);
   1629 }
   1630 
   1631 /// @param rel_fname  file we are searching relative to
   1632 /// @param file_lnum  line number after the file name
   1633 ///
   1634 /// @return  the name of the file under or after ptr[col].
   1635 ///
   1636 /// Otherwise like file_name_at_cursor().
   1637 char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname,
   1638                        linenr_T *file_lnum)
   1639 {
   1640  // search forward for what could be the start of a file name
   1641  char *ptr = line + col;
   1642  while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) {
   1643    MB_PTR_ADV(ptr);
   1644  }
   1645  if (*ptr == NUL) {            // nothing found
   1646    if (options & FNAME_MESS) {
   1647      emsg(_("E446: No file name under cursor"));
   1648    }
   1649    return NULL;
   1650  }
   1651 
   1652  size_t len;
   1653  bool in_type = true;
   1654  bool is_url = false;
   1655 
   1656  // Search backward for first char of the file name.
   1657  // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":"
   1658  // is not in 'isfname').
   1659  while (ptr > line) {
   1660    if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) {
   1661      ptr -= len + 1;
   1662    } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
   1663      ptr--;
   1664    } else {
   1665      break;
   1666    }
   1667  }
   1668 
   1669  // Search forward for the last char of the file name.
   1670  // Also allow ":/" when ':' is not in 'isfname'.
   1671  len = path_has_drive_letter(ptr, strlen(ptr)) ? 2 : 0;
   1672  while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
   1673         || ((options & FNAME_HYP) && path_is_url(ptr + len))
   1674         || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) {
   1675    // After type:// we also include :, ?, & and = as valid characters, so that
   1676    // http://google.com:8080?q=this&that=ok works.
   1677    if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
   1678      if (in_type && path_is_url(ptr + len + 1)) {
   1679        is_url = true;
   1680      }
   1681    } else {
   1682      in_type = false;
   1683    }
   1684 
   1685    if (ptr[len] == '\\' && ptr[len + 1] == ' ') {
   1686      // Skip over the "\" in "\ ".
   1687      len++;
   1688    }
   1689    len += (size_t)(utfc_ptr2len(ptr + len));
   1690  }
   1691 
   1692  // If there is trailing punctuation, remove it.
   1693  // But don't remove "..", could be a directory name.
   1694  if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL
   1695      && ptr[len - 2] != '.') {
   1696    len--;
   1697  }
   1698 
   1699  if (file_lnum != NULL) {
   1700    const char *match_text = " line ";  // english
   1701    size_t match_textlen = 6;
   1702 
   1703    // Get the number after the file name and a separator character.
   1704    // Also accept " line 999" with and without the same translation as
   1705    // used in last_set_msg().
   1706    char *p = ptr + len;
   1707    if (strncmp(p, match_text, match_textlen) == 0) {
   1708      p += match_textlen;
   1709    } else {
   1710      // no match with english, try localized
   1711      match_text = _(line_msg);
   1712      match_textlen = strlen(match_text);
   1713      if (strncmp(p, match_text, match_textlen) == 0) {
   1714        p += match_textlen;
   1715      } else {
   1716        p = skipwhite(p);
   1717      }
   1718    }
   1719    if (*p != NUL) {
   1720      if (!isdigit((uint8_t)(*p))) {
   1721        p++;                        // skip the separator
   1722      }
   1723      p = skipwhite(p);
   1724      if (isdigit((uint8_t)(*p))) {
   1725        *file_lnum = (linenr_T)getdigits_long(&p, false, 0);
   1726      }
   1727    }
   1728  }
   1729 
   1730  return find_file_name_in_path(ptr, len, options, count, rel_fname);
   1731 }
   1732 
   1733 static char *eval_includeexpr(const char *const ptr, const size_t len)
   1734 {
   1735  const sctx_T save_sctx = current_sctx;
   1736  set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
   1737  current_sctx = curbuf->b_p_script_ctx[kBufOptIncludeexpr];
   1738 
   1739  char *res = eval_to_string_safe(curbuf->b_p_inex,
   1740                                  was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL),
   1741                                  true);
   1742 
   1743  set_vim_var_string(VV_FNAME, NULL, 0);
   1744  current_sctx = save_sctx;
   1745  return res;
   1746 }
   1747 
   1748 /// Return the name of the file ptr[len] in 'path'.
   1749 /// Otherwise like file_name_at_cursor().
   1750 ///
   1751 /// @param rel_fname  file we are searching relative to
   1752 char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname)
   1753 {
   1754  char *file_name;
   1755  char *tofree = NULL;
   1756 
   1757  if (len == 0) {
   1758    return NULL;
   1759  }
   1760 
   1761  if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
   1762    tofree = eval_includeexpr(ptr, len);
   1763    if (tofree != NULL) {
   1764      ptr = tofree;
   1765      len = strlen(ptr);
   1766    }
   1767  }
   1768 
   1769  if (options & FNAME_EXP) {
   1770    char *file_to_find = NULL;
   1771    char *search_ctx = NULL;
   1772 
   1773    file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
   1774                                  true, rel_fname, &file_to_find, &search_ctx);
   1775 
   1776    // If the file could not be found in a normal way, try applying
   1777    // 'includeexpr' (unless done already).
   1778    if (file_name == NULL
   1779        && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
   1780      tofree = eval_includeexpr(ptr, len);
   1781      if (tofree != NULL) {
   1782        ptr = tofree;
   1783        len = strlen(ptr);
   1784        file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
   1785                                      true, rel_fname, &file_to_find, &search_ctx);
   1786      }
   1787    }
   1788    if (file_name == NULL && (options & FNAME_MESS)) {
   1789      char c = ptr[len];
   1790      ptr[len] = NUL;
   1791      semsg(_("E447: Can't find file \"%s\" in path"), ptr);
   1792      ptr[len] = c;
   1793    }
   1794 
   1795    // Repeat finding the file "count" times.  This matters when it
   1796    // appears several times in the path.
   1797    while (file_name != NULL && --count > 0) {
   1798      xfree(file_name);
   1799      file_name = find_file_in_path(ptr, len, options, false, rel_fname,
   1800                                    &file_to_find, &search_ctx);
   1801    }
   1802 
   1803    xfree(file_to_find);
   1804    vim_findfile_cleanup(search_ctx);
   1805  } else {
   1806    file_name = xstrnsave(ptr, len);
   1807  }
   1808 
   1809  xfree(tofree);
   1810 
   1811  return file_name;
   1812 }
   1813 
   1814 void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre)
   1815 {
   1816  static bool recursive = false;
   1817 
   1818  event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED;
   1819 
   1820  if (recursive || !has_event(event)) {
   1821    // No autocommand was defined or we changed
   1822    // the directory from this autocommand.
   1823    return;
   1824  }
   1825 
   1826  recursive = true;
   1827 
   1828  save_v_event_T save_v_event;
   1829  dict_T *dict = get_v_event(&save_v_event);
   1830  char buf[8];
   1831 
   1832  switch (scope) {
   1833  case kCdScopeGlobal:
   1834    snprintf(buf, sizeof(buf), "global");
   1835    break;
   1836  case kCdScopeTabpage:
   1837    snprintf(buf, sizeof(buf), "tabpage");
   1838    break;
   1839  case kCdScopeWindow:
   1840    snprintf(buf, sizeof(buf), "window");
   1841    break;
   1842  case kCdScopeInvalid:
   1843    // Should never happen.
   1844    abort();
   1845  }
   1846 
   1847 #ifdef BACKSLASH_IN_FILENAME
   1848  char new_dir_buf[MAXPATHL];
   1849  STRCPY(new_dir_buf, new_dir);
   1850  slash_adjust(new_dir_buf);
   1851  new_dir = new_dir_buf;
   1852 #endif
   1853 
   1854  if (pre) {
   1855    tv_dict_add_str(dict, S_LEN("directory"), new_dir);
   1856  } else {
   1857    tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
   1858  }
   1859  tv_dict_add_str(dict, S_LEN("scope"), buf);
   1860  tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
   1861  tv_dict_set_keys_readonly(dict);
   1862 
   1863  switch (cause) {
   1864  case kCdCauseManual:
   1865  case kCdCauseWindow:
   1866    break;
   1867  case kCdCauseAuto:
   1868    snprintf(buf, sizeof(buf), "auto");
   1869    break;
   1870  case kCdCauseOther:
   1871    // Should never happen.
   1872    abort();
   1873  }
   1874 
   1875  apply_autocmds(event, buf, new_dir, false, curbuf);
   1876 
   1877  restore_v_event(dict, &save_v_event);
   1878 
   1879  recursive = false;
   1880 }
   1881 
   1882 /// Change to a file's directory.
   1883 /// Caller must call shorten_fnames()!
   1884 ///
   1885 /// @return  OK or FAIL
   1886 int vim_chdirfile(char *fname, CdCause cause)
   1887 {
   1888  char dir[MAXPATHL];
   1889 
   1890  xstrlcpy(dir, fname, MAXPATHL);
   1891  *path_tail_with_sep(dir) = NUL;
   1892 
   1893  if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) {
   1894    NameBuff[0] = NUL;
   1895  }
   1896 
   1897  if (pathcmp(dir, NameBuff, -1) == 0) {
   1898    // nothing to do
   1899    return OK;
   1900  }
   1901 
   1902  if (cause != kCdCauseOther) {
   1903    do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true);
   1904  }
   1905 
   1906  if (os_chdir(dir) != 0) {
   1907    return FAIL;
   1908  }
   1909 
   1910  if (cause != kCdCauseOther) {
   1911    do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false);
   1912  }
   1913 
   1914  return OK;
   1915 }
   1916 
   1917 /// Change directory to "new_dir". Search 'cdpath' for relative directory names.
   1918 int vim_chdir(char *new_dir)
   1919 {
   1920  char *file_to_find = NULL;
   1921  char *search_ctx = NULL;
   1922  char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), FNAME_MESS,
   1923                                          curbuf->b_ffname, &file_to_find, &search_ctx);
   1924  xfree(file_to_find);
   1925  vim_findfile_cleanup(search_ctx);
   1926  if (dir_name == NULL) {
   1927    return -1;
   1928  }
   1929 
   1930  int r = os_chdir(dir_name);
   1931  xfree(dir_name);
   1932  return r;
   1933 }