aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelipe Oliveira Carvalho <felipekde@gmail.com>2014-02-28 02:54:47 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2014-02-28 18:37:01 -0300
commite18c075b703f85a6b30866851a02b682debf041e (patch)
treeae5739cfe94f9ea436b4cdccb9c19268545f4ad1
parent007a19c4971e42d1bb7fdafc8fc7d6d8885120f2 (diff)
downloadrneovim-e18c075b703f85a6b30866851a02b682debf041e.tar.gz
rneovim-e18c075b703f85a6b30866851a02b682debf041e.tar.bz2
rneovim-e18c075b703f85a6b30866851a02b682debf041e.zip
Extract file_search.c from misc2.c
Continue to split misc2.c in many other files (see #209). The only changed I made to the moved code was adding `vim_free(ff_expand_buffer)` to `free_finfile()`. This is was needed because `ff_expand_buffer` was moved from `misc2.c` to `file_search.c`.
-rw-r--r--src/eval.c1
-rw-r--r--src/ex_docmd.c1
-rw-r--r--src/file_search.c1599
-rw-r--r--src/file_search.h23
-rw-r--r--src/misc2.c1590
-rw-r--r--src/misc2.h17
-rw-r--r--src/tag.c1
-rw-r--r--src/window.c1
8 files changed, 1627 insertions, 1606 deletions
diff --git a/src/eval.c b/src/eval.c
index 7c54513e00..5b8c43fc8d 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -33,6 +33,7 @@
#include "message.h"
#include "misc1.h"
#include "misc2.h"
+#include "file_search.h"
#include "garray.h"
#include "move.h"
#include "normal.h"
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index cb731c17d3..2f4a50580a 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -37,6 +37,7 @@
#include "message.h"
#include "misc1.h"
#include "misc2.h"
+#include "file_search.h"
#include "garray.h"
#include "move.h"
#include "normal.h"
diff --git a/src/file_search.c b/src/file_search.c
new file mode 100644
index 0000000000..c9d3f3c234
--- /dev/null
+++ b/src/file_search.c
@@ -0,0 +1,1599 @@
+/* TODO: make some #ifdef for this */
+/*--------[ file searching ]-------------------------------------------------*/
+/*
+ * File searching functions for 'path', 'tags' and 'cdpath' options.
+ * External visible functions:
+ * vim_findfile_init() creates/initialises the search context
+ * vim_findfile_free_visited() free list of visited files/dirs of search
+ * context
+ * vim_findfile() find a file in the search context
+ * vim_findfile_cleanup() cleanup/free search context created by
+ * vim_findfile_init()
+ *
+ * All static functions and variables start with 'ff_'
+ *
+ * In general it works like this:
+ * First you create yourself a search context by calling vim_findfile_init().
+ * It is possible to give a search context from a previous call to
+ * vim_findfile_init(), so it can be reused. After this you call vim_findfile()
+ * until you are satisfied with the result or it returns NULL. On every call it
+ * returns the next file which matches the conditions given to
+ * vim_findfile_init(). If it doesn't find a next file it returns NULL.
+ *
+ * It is possible to call vim_findfile_init() again to reinitialise your search
+ * with some new parameters. Don't forget to pass your old search context to
+ * it, so it can reuse it and especially reuse the list of already visited
+ * directories. If you want to delete the list of already visited directories
+ * simply call vim_findfile_free_visited().
+ *
+ * When you are done call vim_findfile_cleanup() to free the search context.
+ *
+ * The function vim_findfile_init() has a long comment, which describes the
+ * needed parameters.
+ *
+ *
+ *
+ * ATTENTION:
+ * ==========
+ * Also we use an allocated search context here, this functions are NOT
+ * thread-safe!!!!!
+ *
+ * To minimize parameter passing (or because I'm to lazy), only the
+ * external visible functions get a search context as a parameter. This is
+ * then assigned to a static global, which is used throughout the local
+ * functions.
+ */
+
+#include "vim.h"
+#include "file_search.h"
+#include "charset.h"
+#include "fileio.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "os_unix.h"
+#include "tag.h"
+#include "ui.h"
+#include "window.h"
+#include "os/os.h"
+
+static char_u *ff_expand_buffer = NULL; /* used for expanding filenames */
+
+/*
+ * type for the directory search stack
+ */
+typedef struct ff_stack {
+ struct ff_stack *ffs_prev;
+
+ /* the fix part (no wildcards) and the part containing the wildcards
+ * of the search path
+ */
+ char_u *ffs_fix_path;
+ char_u *ffs_wc_path;
+
+ /* files/dirs found in the above directory, matched by the first wildcard
+ * of wc_part
+ */
+ char_u **ffs_filearray;
+ int ffs_filearray_size;
+ char_u ffs_filearray_cur; /* needed for partly handled dirs */
+
+ /* to store status of partly handled directories
+ * 0: we work on this directory for the first time
+ * 1: this directory was partly searched in an earlier step
+ */
+ int ffs_stage;
+
+ /* How deep are we in the directory tree?
+ * Counts backward from value of level parameter to vim_findfile_init
+ */
+ int ffs_level;
+
+ /* Did we already expand '**' to an empty string? */
+ int ffs_star_star_empty;
+} ff_stack_T;
+
+/*
+ * type for already visited directories or files.
+ */
+typedef struct ff_visited {
+ struct ff_visited *ffv_next;
+
+ /* Visited directories are different if the wildcard string are
+ * different. So we have to save it.
+ */
+ char_u *ffv_wc_path;
+ /* for unix use inode etc for comparison (needed because of links), else
+ * use filename.
+ */
+#ifdef UNIX
+ int ffv_dev_valid; /* ffv_dev and ffv_ino were set */
+ dev_t ffv_dev; /* device number */
+ ino_t ffv_ino; /* inode number */
+#endif
+ /* The memory for this struct is allocated according to the length of
+ * ffv_fname.
+ */
+ char_u ffv_fname[1]; /* actually longer */
+} ff_visited_T;
+
+/*
+ * We might have to manage several visited lists during a search.
+ * This is especially needed for the tags option. If tags is set to:
+ * "./++/tags,./++/TAGS,++/tags" (replace + with *)
+ * So we have to do 3 searches:
+ * 1) search from the current files directory downward for the file "tags"
+ * 2) search from the current files directory downward for the file "TAGS"
+ * 3) search from Vims current directory downwards for the file "tags"
+ * As you can see, the first and the third search are for the same file, so for
+ * the third search we can use the visited list of the first search. For the
+ * second search we must start from a empty visited list.
+ * The struct ff_visited_list_hdr is used to manage a linked list of already
+ * visited lists.
+ */
+typedef struct ff_visited_list_hdr {
+ struct ff_visited_list_hdr *ffvl_next;
+
+ /* the filename the attached visited list is for */
+ char_u *ffvl_filename;
+
+ ff_visited_T *ffvl_visited_list;
+
+} ff_visited_list_hdr_T;
+
+
+/*
+ * '**' can be expanded to several directory levels.
+ * Set the default maximum depth.
+ */
+#define FF_MAX_STAR_STAR_EXPAND ((char_u)30)
+
+/*
+ * The search context:
+ * ffsc_stack_ptr: the stack for the dirs to search
+ * ffsc_visited_list: the currently active visited list
+ * ffsc_dir_visited_list: the currently active visited list for search dirs
+ * ffsc_visited_lists_list: the list of all visited lists
+ * ffsc_dir_visited_lists_list: the list of all visited lists for search dirs
+ * ffsc_file_to_search: the file to search for
+ * ffsc_start_dir: the starting directory, if search path was relative
+ * ffsc_fix_path: the fix part of the given path (without wildcards)
+ * Needed for upward search.
+ * ffsc_wc_path: the part of the given path containing wildcards
+ * ffsc_level: how many levels of dirs to search downwards
+ * ffsc_stopdirs_v: array of stop directories for upward search
+ * ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE
+ * ffsc_tagfile: searching for tags file, don't use 'suffixesadd'
+ */
+typedef struct ff_search_ctx_T {
+ ff_stack_T *ffsc_stack_ptr;
+ ff_visited_list_hdr_T *ffsc_visited_list;
+ ff_visited_list_hdr_T *ffsc_dir_visited_list;
+ ff_visited_list_hdr_T *ffsc_visited_lists_list;
+ ff_visited_list_hdr_T *ffsc_dir_visited_lists_list;
+ char_u *ffsc_file_to_search;
+ char_u *ffsc_start_dir;
+ char_u *ffsc_fix_path;
+ char_u *ffsc_wc_path;
+ int ffsc_level;
+ char_u **ffsc_stopdirs_v;
+ int ffsc_find_what;
+ int ffsc_tagfile;
+} ff_search_ctx_T;
+
+/* locally needed functions */
+static int ff_check_visited __ARGS((ff_visited_T **, char_u *, char_u *));
+static void vim_findfile_free_visited_list __ARGS(
+ (ff_visited_list_hdr_T **list_headp));
+static void ff_free_visited_list __ARGS((ff_visited_T *vl));
+static ff_visited_list_hdr_T* ff_get_visited_list __ARGS(
+ (char_u *, ff_visited_list_hdr_T **list_headp));
+static int ff_wc_equal __ARGS((char_u *s1, char_u *s2));
+
+static void ff_push __ARGS((ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr));
+static ff_stack_T *ff_pop __ARGS((ff_search_ctx_T *search_ctx));
+static void ff_clear __ARGS((ff_search_ctx_T *search_ctx));
+static void ff_free_stack_element __ARGS((ff_stack_T *stack_ptr));
+static ff_stack_T *ff_create_stack_element __ARGS((char_u *, char_u *, int, int));
+static int ff_path_in_stoplist __ARGS((char_u *, int, char_u **));
+
+static char_u e_pathtoolong[] = N_("E854: path too long for completion");
+
+
+/*
+ * Initialization routine for vim_findfile().
+ *
+ * Returns the newly allocated search context or NULL if an error occurred.
+ *
+ * Don't forget to clean up by calling vim_findfile_cleanup() if you are done
+ * with the search context.
+ *
+ * Find the file 'filename' in the directory 'path'.
+ * The parameter 'path' may contain wildcards. If so only search 'level'
+ * directories deep. The parameter 'level' is the absolute maximum and is
+ * not related to restricts given to the '**' wildcard. If 'level' is 100
+ * and you use '**200' vim_findfile() will stop after 100 levels.
+ *
+ * 'filename' cannot contain wildcards! It is used as-is, no backslashes to
+ * escape special characters.
+ *
+ * If 'stopdirs' is not NULL and nothing is found downward, the search is
+ * restarted on the next higher directory level. This is repeated until the
+ * start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the
+ * format ";*<dirname>*\(;<dirname>\)*;\=$".
+ *
+ * If the 'path' is relative, the starting dir for the search is either VIM's
+ * current dir or if the path starts with "./" the current files dir.
+ * If the 'path' is absolute, the starting dir is that part of the path before
+ * the first wildcard.
+ *
+ * Upward search is only done on the starting dir.
+ *
+ * If 'free_visited' is TRUE the list of already visited files/directories is
+ * cleared. Set this to FALSE if you just want to search from another
+ * directory, but want to be sure that no directory from a previous search is
+ * searched again. This is useful if you search for a file at different places.
+ * The list of visited files/dirs can also be cleared with the function
+ * vim_findfile_free_visited().
+ *
+ * Set the parameter 'find_what' to FINDFILE_DIR if you want to search for
+ * directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both.
+ *
+ * A search context returned by a previous call to vim_findfile_init() can be
+ * passed in the parameter "search_ctx_arg". This context is reused and
+ * reinitialized with the new parameters. The list of already visited
+ * directories from this context is only deleted if the parameter
+ * "free_visited" is true. Be aware that the passed "search_ctx_arg" is freed
+ * if the reinitialization fails.
+ *
+ * If you don't have a search context from a previous call "search_ctx_arg"
+ * must be NULL.
+ *
+ * This function silently ignores a few errors, vim_findfile() will have
+ * limited functionality then.
+ */
+void *
+vim_findfile_init (
+ char_u *path,
+ char_u *filename,
+ char_u *stopdirs,
+ int level,
+ int free_visited,
+ int find_what,
+ void *search_ctx_arg,
+ int tagfile, /* expanding names of tags files */
+ char_u *rel_fname /* file name to use for "." */
+)
+{
+ char_u *wc_part;
+ ff_stack_T *sptr;
+ ff_search_ctx_T *search_ctx;
+
+ /* If a search context is given by the caller, reuse it, else allocate a
+ * new one.
+ */
+ if (search_ctx_arg != NULL)
+ search_ctx = search_ctx_arg;
+ else {
+ search_ctx = (ff_search_ctx_T*)alloc((unsigned)sizeof(ff_search_ctx_T));
+ if (search_ctx == NULL)
+ goto error_return;
+ vim_memset(search_ctx, 0, sizeof(ff_search_ctx_T));
+ }
+ search_ctx->ffsc_find_what = find_what;
+ search_ctx->ffsc_tagfile = tagfile;
+
+ /* clear the search context, but NOT the visited lists */
+ ff_clear(search_ctx);
+
+ /* clear visited list if wanted */
+ if (free_visited == TRUE)
+ vim_findfile_free_visited(search_ctx);
+ else {
+ /* Reuse old visited lists. Get the visited list for the given
+ * filename. If no list for the current filename exists, creates a new
+ * one. */
+ search_ctx->ffsc_visited_list = ff_get_visited_list(filename,
+ &search_ctx->ffsc_visited_lists_list);
+ if (search_ctx->ffsc_visited_list == NULL)
+ goto error_return;
+ search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename,
+ &search_ctx->ffsc_dir_visited_lists_list);
+ if (search_ctx->ffsc_dir_visited_list == NULL)
+ goto error_return;
+ }
+
+ if (ff_expand_buffer == NULL) {
+ ff_expand_buffer = (char_u*)alloc(MAXPATHL);
+ if (ff_expand_buffer == NULL)
+ goto error_return;
+ }
+
+ /* Store information on starting dir now if path is relative.
+ * If path is absolute, we do that later. */
+ if (path[0] == '.'
+ && (vim_ispathsep(path[1]) || path[1] == NUL)
+ && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
+ && rel_fname != NULL) {
+ int len = (int)(gettail(rel_fname) - rel_fname);
+
+ if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) {
+ /* Make the start dir an absolute path name. */
+ vim_strncpy(ff_expand_buffer, rel_fname, len);
+ search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, FALSE);
+ } else
+ search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
+ if (search_ctx->ffsc_start_dir == NULL)
+ goto error_return;
+ if (*++path != NUL)
+ ++path;
+ } else if (*path == NUL || !vim_isAbsName(path)) {
+#ifdef BACKSLASH_IN_FILENAME
+ /* "c:dir" needs "c:" to be expanded, otherwise use current dir */
+ if (*path != NUL && path[1] == ':') {
+ char_u drive[3];
+
+ drive[0] = path[0];
+ drive[1] = ':';
+ drive[2] = NUL;
+ if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
+ goto error_return;
+ path += 2;
+ } else
+#endif
+ if (mch_dirname(ff_expand_buffer, MAXPATHL) == FAIL)
+ goto error_return;
+
+ search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer);
+ if (search_ctx->ffsc_start_dir == NULL)
+ goto error_return;
+
+#ifdef BACKSLASH_IN_FILENAME
+ /* A path that starts with "/dir" is relative to the drive, not to the
+ * directory (but not for "//machine/dir"). Only use the drive name. */
+ if ((*path == '/' || *path == '\\')
+ && path[1] != path[0]
+ && search_ctx->ffsc_start_dir[1] == ':')
+ search_ctx->ffsc_start_dir[2] = NUL;
+#endif
+ }
+
+ /*
+ * If stopdirs are given, split them into an array of pointers.
+ * If this fails (mem allocation), there is no upward search at all or a
+ * stop directory is not recognized -> continue silently.
+ * If stopdirs just contains a ";" or is empty,
+ * search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This
+ * is handled as unlimited upward search. See function
+ * ff_path_in_stoplist() for details.
+ */
+ if (stopdirs != NULL) {
+ char_u *walker = stopdirs;
+ int dircount;
+
+ while (*walker == ';')
+ walker++;
+
+ dircount = 1;
+ search_ctx->ffsc_stopdirs_v =
+ (char_u **)alloc((unsigned)sizeof(char_u *));
+
+ if (search_ctx->ffsc_stopdirs_v != NULL) {
+ do {
+ char_u *helper;
+ void *ptr;
+
+ helper = walker;
+ ptr = vim_realloc(search_ctx->ffsc_stopdirs_v,
+ (dircount + 1) * sizeof(char_u *));
+ if (ptr)
+ search_ctx->ffsc_stopdirs_v = ptr;
+ else
+ /* ignore, keep what we have and continue */
+ break;
+ walker = vim_strchr(walker, ';');
+ if (walker) {
+ search_ctx->ffsc_stopdirs_v[dircount-1] =
+ vim_strnsave(helper, (int)(walker - helper));
+ walker++;
+ } else
+ /* this might be "", which means ascent till top
+ * of directory tree.
+ */
+ search_ctx->ffsc_stopdirs_v[dircount-1] =
+ vim_strsave(helper);
+
+ dircount++;
+
+ } while (walker != NULL);
+ search_ctx->ffsc_stopdirs_v[dircount-1] = NULL;
+ }
+ }
+
+ search_ctx->ffsc_level = level;
+
+ /* split into:
+ * -fix path
+ * -wildcard_stuff (might be NULL)
+ */
+ wc_part = vim_strchr(path, '*');
+ if (wc_part != NULL) {
+ int llevel;
+ int len;
+ char *errpt;
+
+ /* save the fix part of the path */
+ search_ctx->ffsc_fix_path = vim_strnsave(path, (int)(wc_part - path));
+
+ /*
+ * copy wc_path and add restricts to the '**' wildcard.
+ * The octet after a '**' is used as a (binary) counter.
+ * So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
+ * or '**76' is transposed to '**N'( 'N' is ASCII value 76).
+ * For EBCDIC you get different character values.
+ * If no restrict is given after '**' the default is used.
+ * Due to this technique the path looks awful if you print it as a
+ * string.
+ */
+ len = 0;
+ while (*wc_part != NUL) {
+ if (len + 5 >= MAXPATHL) {
+ EMSG(_(e_pathtoolong));
+ break;
+ }
+ if (STRNCMP(wc_part, "**", 2) == 0) {
+ ff_expand_buffer[len++] = *wc_part++;
+ ff_expand_buffer[len++] = *wc_part++;
+
+ llevel = strtol((char *)wc_part, &errpt, 10);
+ if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255)
+ ff_expand_buffer[len++] = llevel;
+ else if ((char_u *)errpt != wc_part && llevel == 0)
+ /* restrict is 0 -> remove already added '**' */
+ len -= 2;
+ else
+ ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND;
+ wc_part = (char_u *)errpt;
+ if (*wc_part != NUL && !vim_ispathsep(*wc_part)) {
+ EMSG2(_(
+ "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."),
+ PATHSEPSTR);
+ goto error_return;
+ }
+ } else
+ ff_expand_buffer[len++] = *wc_part++;
+ }
+ ff_expand_buffer[len] = NUL;
+ search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer);
+
+ if (search_ctx->ffsc_wc_path == NULL)
+ goto error_return;
+ } else
+ search_ctx->ffsc_fix_path = vim_strsave(path);
+
+ if (search_ctx->ffsc_start_dir == NULL) {
+ /* store the fix part as startdir.
+ * This is needed if the parameter path is fully qualified.
+ */
+ search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path);
+ if (search_ctx->ffsc_start_dir == NULL)
+ goto error_return;
+ search_ctx->ffsc_fix_path[0] = NUL;
+ }
+
+ /* create an absolute path */
+ if (STRLEN(search_ctx->ffsc_start_dir)
+ + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) {
+ EMSG(_(e_pathtoolong));
+ goto error_return;
+ }
+ STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
+ add_pathsep(ff_expand_buffer);
+ {
+ int eb_len = (int)STRLEN(ff_expand_buffer);
+ char_u *buf = alloc(eb_len
+ + (int)STRLEN(search_ctx->ffsc_fix_path) + 1);
+
+ STRCPY(buf, ff_expand_buffer);
+ STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
+ if (mch_isdir(buf)) {
+ STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
+ add_pathsep(ff_expand_buffer);
+ } else {
+ char_u *p = gettail(search_ctx->ffsc_fix_path);
+ char_u *wc_path = NULL;
+ char_u *temp = NULL;
+ int len = 0;
+
+ if (p > search_ctx->ffsc_fix_path) {
+ len = (int)(p - search_ctx->ffsc_fix_path) - 1;
+ STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
+ add_pathsep(ff_expand_buffer);
+ } else
+ len = (int)STRLEN(search_ctx->ffsc_fix_path);
+
+ if (search_ctx->ffsc_wc_path != NULL) {
+ wc_path = vim_strsave(search_ctx->ffsc_wc_path);
+ temp = alloc((int)(STRLEN(search_ctx->ffsc_wc_path)
+ + STRLEN(search_ctx->ffsc_fix_path + len)
+ + 1));
+ }
+
+ if (temp == NULL || wc_path == NULL) {
+ vim_free(buf);
+ vim_free(temp);
+ vim_free(wc_path);
+ goto error_return;
+ }
+
+ STRCPY(temp, search_ctx->ffsc_fix_path + len);
+ STRCAT(temp, search_ctx->ffsc_wc_path);
+ vim_free(search_ctx->ffsc_wc_path);
+ vim_free(wc_path);
+ search_ctx->ffsc_wc_path = temp;
+ }
+ vim_free(buf);
+ }
+
+ sptr = ff_create_stack_element(ff_expand_buffer,
+ search_ctx->ffsc_wc_path,
+ level, 0);
+
+ if (sptr == NULL)
+ goto error_return;
+
+ ff_push(search_ctx, sptr);
+
+ search_ctx->ffsc_file_to_search = vim_strsave(filename);
+ if (search_ctx->ffsc_file_to_search == NULL)
+ goto error_return;
+
+ return search_ctx;
+
+error_return:
+ /*
+ * We clear the search context now!
+ * Even when the caller gave us a (perhaps valid) context we free it here,
+ * as we might have already destroyed it.
+ */
+ vim_findfile_cleanup(search_ctx);
+ return NULL;
+}
+
+/*
+ * Get the stopdir string. Check that ';' is not escaped.
+ */
+char_u *vim_findfile_stopdir(char_u *buf)
+{
+ char_u *r_ptr = buf;
+
+ while (*r_ptr != NUL && *r_ptr != ';') {
+ if (r_ptr[0] == '\\' && r_ptr[1] == ';') {
+ /* Overwrite the escape char,
+ * use STRLEN(r_ptr) to move the trailing '\0'. */
+ STRMOVE(r_ptr, r_ptr + 1);
+ r_ptr++;
+ }
+ r_ptr++;
+ }
+ if (*r_ptr == ';') {
+ *r_ptr = 0;
+ r_ptr++;
+ } else if (*r_ptr == NUL)
+ r_ptr = NULL;
+ return r_ptr;
+}
+
+/*
+ * Clean up the given search context. Can handle a NULL pointer.
+ */
+void vim_findfile_cleanup(void *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ vim_findfile_free_visited(ctx);
+ ff_clear(ctx);
+ vim_free(ctx);
+}
+
+/*
+ * Find a file in a search context.
+ * The search context was created with vim_findfile_init() above.
+ * Return a pointer to an allocated file name or NULL if nothing found.
+ * To get all matching files call this function until you get NULL.
+ *
+ * If the passed search_context is NULL, NULL is returned.
+ *
+ * The search algorithm is depth first. To change this replace the
+ * stack with a list (don't forget to leave partly searched directories on the
+ * top of the list).
+ */
+char_u *vim_findfile(void *search_ctx_arg)
+{
+ char_u *file_path;
+ char_u *rest_of_wildcards;
+ char_u *path_end = NULL;
+ ff_stack_T *stackp;
+ int len;
+ int i;
+ char_u *p;
+ char_u *suf;
+ ff_search_ctx_T *search_ctx;
+
+ if (search_ctx_arg == NULL)
+ return NULL;
+
+ search_ctx = (ff_search_ctx_T *)search_ctx_arg;
+
+ /*
+ * filepath is used as buffer for various actions and as the storage to
+ * return a found filename.
+ */
+ if ((file_path = alloc((int)MAXPATHL)) == NULL)
+ return NULL;
+
+ /* store the end of the start dir -- needed for upward search */
+ if (search_ctx->ffsc_start_dir != NULL)
+ path_end = &search_ctx->ffsc_start_dir[
+ STRLEN(search_ctx->ffsc_start_dir)];
+
+ /* upward search loop */
+ for (;; ) {
+ /* downward search loop */
+ for (;; ) {
+ /* check if user user wants to stop the search*/
+ ui_breakcheck();
+ if (got_int)
+ break;
+
+ /* get directory to work on from stack */
+ stackp = ff_pop(search_ctx);
+ if (stackp == NULL)
+ break;
+
+ /*
+ * TODO: decide if we leave this test in
+ *
+ * GOOD: don't search a directory(-tree) twice.
+ * BAD: - check linked list for every new directory entered.
+ * - check for double files also done below
+ *
+ * Here we check if we already searched this directory.
+ * We already searched a directory if:
+ * 1) The directory is the same.
+ * 2) We would use the same wildcard string.
+ *
+ * Good if you have links on same directory via several ways
+ * or you have selfreferences in directories (e.g. SuSE Linux 6.3:
+ * /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop)
+ *
+ * This check is only needed for directories we work on for the
+ * first time (hence stackp->ff_filearray == NULL)
+ */
+ if (stackp->ffs_filearray == NULL
+ && ff_check_visited(&search_ctx->ffsc_dir_visited_list
+ ->ffvl_visited_list,
+ stackp->ffs_fix_path
+ , stackp->ffs_wc_path
+ ) == FAIL) {
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"Already Searched: %s (%s)",
+ stackp->ffs_fix_path, stackp->ffs_wc_path);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+#endif
+ ff_free_stack_element(stackp);
+ continue;
+ }
+#ifdef FF_VERBOSE
+ else if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"Searching: %s (%s)",
+ stackp->ffs_fix_path, stackp->ffs_wc_path);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+#endif
+
+ /* check depth */
+ if (stackp->ffs_level <= 0) {
+ ff_free_stack_element(stackp);
+ continue;
+ }
+
+ file_path[0] = NUL;
+
+ /*
+ * If no filearray till now expand wildcards
+ * The function expand_wildcards() can handle an array of paths
+ * and all possible expands are returned in one array. We use this
+ * to handle the expansion of '**' into an empty string.
+ */
+ if (stackp->ffs_filearray == NULL) {
+ char_u *dirptrs[2];
+
+ /* we use filepath to build the path expand_wildcards() should
+ * expand.
+ */
+ dirptrs[0] = file_path;
+ dirptrs[1] = NULL;
+
+ /* if we have a start dir copy it in */
+ if (!vim_isAbsName(stackp->ffs_fix_path)
+ && search_ctx->ffsc_start_dir) {
+ STRCPY(file_path, search_ctx->ffsc_start_dir);
+ add_pathsep(file_path);
+ }
+
+ /* append the fix part of the search path */
+ STRCAT(file_path, stackp->ffs_fix_path);
+ add_pathsep(file_path);
+
+ rest_of_wildcards = stackp->ffs_wc_path;
+ if (*rest_of_wildcards != NUL) {
+ len = (int)STRLEN(file_path);
+ if (STRNCMP(rest_of_wildcards, "**", 2) == 0) {
+ /* pointer to the restrict byte
+ * The restrict byte is not a character!
+ */
+ p = rest_of_wildcards + 2;
+
+ if (*p > 0) {
+ (*p)--;
+ file_path[len++] = '*';
+ }
+
+ if (*p == 0) {
+ /* remove '**<numb> from wildcards */
+ STRMOVE(rest_of_wildcards, rest_of_wildcards + 3);
+ } else
+ rest_of_wildcards += 3;
+
+ if (stackp->ffs_star_star_empty == 0) {
+ /* if not done before, expand '**' to empty */
+ stackp->ffs_star_star_empty = 1;
+ dirptrs[1] = stackp->ffs_fix_path;
+ }
+ }
+
+ /*
+ * Here we copy until the next path separator or the end of
+ * the path. If we stop at a path separator, there is
+ * still something else left. This is handled below by
+ * pushing every directory returned from expand_wildcards()
+ * on the stack again for further search.
+ */
+ while (*rest_of_wildcards
+ && !vim_ispathsep(*rest_of_wildcards))
+ file_path[len++] = *rest_of_wildcards++;
+
+ file_path[len] = NUL;
+ if (vim_ispathsep(*rest_of_wildcards))
+ rest_of_wildcards++;
+ }
+
+ /*
+ * Expand wildcards like "*" and "$VAR".
+ * If the path is a URL don't try this.
+ */
+ if (path_with_url(dirptrs[0])) {
+ stackp->ffs_filearray = (char_u **)
+ alloc((unsigned)sizeof(char *));
+ if (stackp->ffs_filearray != NULL
+ && (stackp->ffs_filearray[0]
+ = vim_strsave(dirptrs[0])) != NULL)
+ stackp->ffs_filearray_size = 1;
+ else
+ stackp->ffs_filearray_size = 0;
+ } else
+ /* Add EW_NOTWILD because the expanded path may contain
+ * wildcard characters that are to be taken literally.
+ * This is a bit of a hack. */
+ expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs,
+ &stackp->ffs_filearray_size,
+ &stackp->ffs_filearray,
+ EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD);
+
+ stackp->ffs_filearray_cur = 0;
+ stackp->ffs_stage = 0;
+ } else
+ rest_of_wildcards = &stackp->ffs_wc_path[
+ STRLEN(stackp->ffs_wc_path)];
+
+ if (stackp->ffs_stage == 0) {
+ /* this is the first time we work on this directory */
+ if (*rest_of_wildcards == NUL) {
+ /*
+ * We don't have further wildcards to expand, so we have to
+ * check for the final file now.
+ */
+ for (i = stackp->ffs_filearray_cur;
+ i < stackp->ffs_filearray_size; ++i) {
+ if (!path_with_url(stackp->ffs_filearray[i])
+ && !mch_isdir(stackp->ffs_filearray[i]))
+ continue; /* not a directory */
+
+ /* prepare the filename to be checked for existence
+ * below */
+ STRCPY(file_path, stackp->ffs_filearray[i]);
+ add_pathsep(file_path);
+ STRCAT(file_path, search_ctx->ffsc_file_to_search);
+
+ /*
+ * Try without extra suffix and then with suffixes
+ * from 'suffixesadd'.
+ */
+ len = (int)STRLEN(file_path);
+ if (search_ctx->ffsc_tagfile)
+ suf = (char_u *)"";
+ else
+ suf = curbuf->b_p_sua;
+ for (;; ) {
+ /* if file exists and we didn't already find it */
+ if ((path_with_url(file_path)
+ || (mch_getperm(file_path) >= 0
+ && (search_ctx->ffsc_find_what
+ == FINDFILE_BOTH
+ || ((search_ctx->ffsc_find_what
+ == FINDFILE_DIR)
+ == mch_isdir(file_path)))))
+#ifndef FF_VERBOSE
+ && (ff_check_visited(
+ &search_ctx->ffsc_visited_list->ffvl_visited_list,
+ file_path
+ , (char_u *)""
+ ) == OK)
+#endif
+ ) {
+#ifdef FF_VERBOSE
+ if (ff_check_visited(
+ &search_ctx->ffsc_visited_list->ffvl_visited_list,
+ file_path
+ , (char_u *)""
+ ) == FAIL) {
+ if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"Already: %s",
+ file_path);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+ continue;
+ }
+#endif
+
+ /* push dir to examine rest of subdirs later */
+ stackp->ffs_filearray_cur = i + 1;
+ ff_push(search_ctx, stackp);
+
+ if (!path_with_url(file_path))
+ simplify_filename(file_path);
+ if (mch_dirname(ff_expand_buffer, MAXPATHL)
+ == OK) {
+ p = shorten_fname(file_path,
+ ff_expand_buffer);
+ if (p != NULL)
+ STRMOVE(file_path, p);
+ }
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"HIT: %s", file_path);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+#endif
+ return file_path;
+ }
+
+ /* Not found or found already, try next suffix. */
+ if (*suf == NUL)
+ break;
+ copy_option_part(&suf, file_path + len,
+ MAXPATHL - len, ",");
+ }
+ }
+ } else {
+ /*
+ * still wildcards left, push the directories for further
+ * search
+ */
+ for (i = stackp->ffs_filearray_cur;
+ i < stackp->ffs_filearray_size; ++i) {
+ if (!mch_isdir(stackp->ffs_filearray[i]))
+ continue; /* not a directory */
+
+ ff_push(search_ctx,
+ ff_create_stack_element(
+ stackp->ffs_filearray[i],
+ rest_of_wildcards,
+ stackp->ffs_level - 1, 0));
+ }
+ }
+ stackp->ffs_filearray_cur = 0;
+ stackp->ffs_stage = 1;
+ }
+
+ /*
+ * if wildcards contains '**' we have to descent till we reach the
+ * leaves of the directory tree.
+ */
+ if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) {
+ for (i = stackp->ffs_filearray_cur;
+ i < stackp->ffs_filearray_size; ++i) {
+ if (fnamecmp(stackp->ffs_filearray[i],
+ stackp->ffs_fix_path) == 0)
+ continue; /* don't repush same directory */
+ if (!mch_isdir(stackp->ffs_filearray[i]))
+ continue; /* not a directory */
+ ff_push(search_ctx,
+ ff_create_stack_element(stackp->ffs_filearray[i],
+ stackp->ffs_wc_path, stackp->ffs_level - 1, 1));
+ }
+ }
+
+ /* we are done with the current directory */
+ ff_free_stack_element(stackp);
+
+ }
+
+ /* If we reached this, we didn't find anything downwards.
+ * Let's check if we should do an upward search.
+ */
+ if (search_ctx->ffsc_start_dir
+ && search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
+ ff_stack_T *sptr;
+
+ /* is the last starting directory in the stop list? */
+ if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
+ (int)(path_end - search_ctx->ffsc_start_dir),
+ search_ctx->ffsc_stopdirs_v) == TRUE)
+ break;
+
+ /* cut of last dir */
+ while (path_end > search_ctx->ffsc_start_dir
+ && vim_ispathsep(*path_end))
+ path_end--;
+ while (path_end > search_ctx->ffsc_start_dir
+ && !vim_ispathsep(path_end[-1]))
+ path_end--;
+ *path_end = 0;
+ path_end--;
+
+ if (*search_ctx->ffsc_start_dir == 0)
+ break;
+
+ STRCPY(file_path, search_ctx->ffsc_start_dir);
+ add_pathsep(file_path);
+ STRCAT(file_path, search_ctx->ffsc_fix_path);
+
+ /* create a new stack entry */
+ sptr = ff_create_stack_element(file_path,
+ search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0);
+ if (sptr == NULL)
+ break;
+ ff_push(search_ctx, sptr);
+ } else
+ break;
+ }
+
+ vim_free(file_path);
+ return NULL;
+}
+
+/*
+ * Free the list of lists of visited files and directories
+ * Can handle it if the passed search_context is NULL;
+ */
+void vim_findfile_free_visited(void *search_ctx_arg)
+{
+ ff_search_ctx_T *search_ctx;
+
+ if (search_ctx_arg == NULL)
+ return;
+
+ search_ctx = (ff_search_ctx_T *)search_ctx_arg;
+ vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list);
+ vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list);
+}
+
+static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
+{
+ ff_visited_list_hdr_T *vp;
+
+ while (*list_headp != NULL) {
+ vp = (*list_headp)->ffvl_next;
+ ff_free_visited_list((*list_headp)->ffvl_visited_list);
+
+ vim_free((*list_headp)->ffvl_filename);
+ vim_free(*list_headp);
+ *list_headp = vp;
+ }
+ *list_headp = NULL;
+}
+
+static void ff_free_visited_list(ff_visited_T *vl)
+{
+ ff_visited_T *vp;
+
+ while (vl != NULL) {
+ vp = vl->ffv_next;
+ vim_free(vl->ffv_wc_path);
+ vim_free(vl);
+ vl = vp;
+ }
+ vl = NULL;
+}
+
+/*
+ * Returns the already visited list for the given filename. If none is found it
+ * allocates a new one.
+ */
+static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_list_hdr_T **list_headp)
+{
+ ff_visited_list_hdr_T *retptr = NULL;
+
+ /* check if a visited list for the given filename exists */
+ if (*list_headp != NULL) {
+ retptr = *list_headp;
+ while (retptr != NULL) {
+ if (fnamecmp(filename, retptr->ffvl_filename) == 0) {
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"ff_get_visited_list: FOUND list for %s",
+ filename);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+#endif
+ return retptr;
+ }
+ retptr = retptr->ffvl_next;
+ }
+ }
+
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5) {
+ verbose_enter_scroll();
+ smsg((char_u *)"ff_get_visited_list: new list for %s", filename);
+ /* don't overwrite this either */
+ msg_puts((char_u *)"\n");
+ verbose_leave_scroll();
+ }
+#endif
+
+ /*
+ * if we reach this we didn't find a list and we have to allocate new list
+ */
+ retptr = (ff_visited_list_hdr_T*)alloc((unsigned)sizeof(*retptr));
+ if (retptr == NULL)
+ return NULL;
+
+ retptr->ffvl_visited_list = NULL;
+ retptr->ffvl_filename = vim_strsave(filename);
+ if (retptr->ffvl_filename == NULL) {
+ vim_free(retptr);
+ return NULL;
+ }
+ retptr->ffvl_next = *list_headp;
+ *list_headp = retptr;
+
+ return retptr;
+}
+
+/*
+ * check if two wildcard paths are equal. Returns TRUE or FALSE.
+ * They are equal if:
+ * - both paths are NULL
+ * - they have the same length
+ * - char by char comparison is OK
+ * - the only differences are in the counters behind a '**', so
+ * '**\20' is equal to '**\24'
+ */
+static int ff_wc_equal(char_u *s1, char_u *s2)
+{
+ int i;
+ int prev1 = NUL;
+ int prev2 = NUL;
+
+ if (s1 == s2)
+ return TRUE;
+
+ if (s1 == NULL || s2 == NULL)
+ return FALSE;
+
+ if (STRLEN(s1) != STRLEN(s2))
+ return FAIL;
+
+ for (i = 0; s1[i] != NUL && s2[i] != NUL; i += MB_PTR2LEN(s1 + i)) {
+ int c1 = PTR2CHAR(s1 + i);
+ int c2 = PTR2CHAR(s2 + i);
+
+ if ((p_fic ? MB_TOLOWER(c1) != MB_TOLOWER(c2) : c1 != c2)
+ && (prev1 != '*' || prev2 != '*'))
+ return FAIL;
+ prev2 = prev1;
+ prev1 = c1;
+ }
+ return TRUE;
+}
+
+/*
+ * maintains the list of already visited files and dirs
+ * returns FAIL if the given file/dir is already in the list
+ * returns OK if it is newly added
+ *
+ * TODO: What to do on memory allocation problems?
+ * -> return TRUE - Better the file is found several times instead of
+ * never.
+ */
+static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path)
+{
+ ff_visited_T *vp;
+#ifdef UNIX
+ struct stat st;
+ int url = FALSE;
+#endif
+
+ /* For an URL we only compare the name, otherwise we compare the
+ * device/inode (unix) or the full path name (not Unix). */
+ if (path_with_url(fname)) {
+ vim_strncpy(ff_expand_buffer, fname, MAXPATHL - 1);
+#ifdef UNIX
+ url = TRUE;
+#endif
+ } else {
+ ff_expand_buffer[0] = NUL;
+#ifdef UNIX
+ if (mch_stat((char *)fname, &st) < 0)
+#else
+ if (vim_FullName(fname, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
+#endif
+ return FAIL;
+ }
+
+ /* check against list of already visited files */
+ for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) {
+ if (
+#ifdef UNIX
+ !url ? (vp->ffv_dev_valid && vp->ffv_dev == st.st_dev
+ && vp->ffv_ino == st.st_ino)
+ :
+#endif
+ fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0
+ ) {
+ /* are the wildcard parts equal */
+ if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE)
+ /* already visited */
+ return FAIL;
+ }
+ }
+
+ /*
+ * New file/dir. Add it to the list of visited files/dirs.
+ */
+ vp = (ff_visited_T *)alloc((unsigned)(sizeof(ff_visited_T)
+ + STRLEN(ff_expand_buffer)));
+
+ if (vp != NULL) {
+#ifdef UNIX
+ if (!url) {
+ vp->ffv_dev_valid = TRUE;
+ vp->ffv_ino = st.st_ino;
+ vp->ffv_dev = st.st_dev;
+ vp->ffv_fname[0] = NUL;
+ } else {
+ vp->ffv_dev_valid = FALSE;
+#endif
+ STRCPY(vp->ffv_fname, ff_expand_buffer);
+#ifdef UNIX
+ }
+#endif
+ if (wc_path != NULL)
+ vp->ffv_wc_path = vim_strsave(wc_path);
+ else
+ vp->ffv_wc_path = NULL;
+
+ vp->ffv_next = *visited_list;
+ *visited_list = vp;
+ }
+
+ return OK;
+}
+
+/*
+ * create stack element from given path pieces
+ */
+static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, int star_star_empty)
+{
+ ff_stack_T *new;
+
+ new = (ff_stack_T *)alloc((unsigned)sizeof(ff_stack_T));
+ if (new == NULL)
+ return NULL;
+
+ new->ffs_prev = NULL;
+ new->ffs_filearray = NULL;
+ new->ffs_filearray_size = 0;
+ new->ffs_filearray_cur = 0;
+ new->ffs_stage = 0;
+ new->ffs_level = level;
+ new->ffs_star_star_empty = star_star_empty;;
+
+ /* the following saves NULL pointer checks in vim_findfile */
+ if (fix_part == NULL)
+ fix_part = (char_u *)"";
+ new->ffs_fix_path = vim_strsave(fix_part);
+
+ if (wc_part == NULL)
+ wc_part = (char_u *)"";
+ new->ffs_wc_path = vim_strsave(wc_part);
+
+ if (new->ffs_fix_path == NULL
+ || new->ffs_wc_path == NULL
+ ) {
+ ff_free_stack_element(new);
+ new = NULL;
+ }
+
+ return new;
+}
+
+/*
+ * Push a dir on the directory stack.
+ */
+static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
+{
+ /* check for NULL pointer, not to return an error to the user, but
+ * to prevent a crash */
+ if (stack_ptr != NULL) {
+ stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
+ search_ctx->ffsc_stack_ptr = stack_ptr;
+ }
+}
+
+/*
+ * Pop a dir from the directory stack.
+ * Returns NULL if stack is empty.
+ */
+static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
+{
+ ff_stack_T *sptr;
+
+ sptr = search_ctx->ffsc_stack_ptr;
+ if (search_ctx->ffsc_stack_ptr != NULL)
+ search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev;
+
+ return sptr;
+}
+
+/*
+ * free the given stack element
+ */
+static void ff_free_stack_element(ff_stack_T *stack_ptr)
+{
+ /* vim_free handles possible NULL pointers */
+ vim_free(stack_ptr->ffs_fix_path);
+ vim_free(stack_ptr->ffs_wc_path);
+
+ if (stack_ptr->ffs_filearray != NULL)
+ FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray);
+
+ vim_free(stack_ptr);
+}
+
+/*
+ * Clear the search context, but NOT the visited list.
+ */
+static void ff_clear(ff_search_ctx_T *search_ctx)
+{
+ ff_stack_T *sptr;
+
+ /* clear up stack */
+ while ((sptr = ff_pop(search_ctx)) != NULL)
+ ff_free_stack_element(sptr);
+
+ vim_free(search_ctx->ffsc_file_to_search);
+ vim_free(search_ctx->ffsc_start_dir);
+ vim_free(search_ctx->ffsc_fix_path);
+ vim_free(search_ctx->ffsc_wc_path);
+
+ if (search_ctx->ffsc_stopdirs_v != NULL) {
+ int i = 0;
+
+ while (search_ctx->ffsc_stopdirs_v[i] != NULL) {
+ vim_free(search_ctx->ffsc_stopdirs_v[i]);
+ i++;
+ }
+ vim_free(search_ctx->ffsc_stopdirs_v);
+ }
+ search_ctx->ffsc_stopdirs_v = NULL;
+
+ /* reset everything */
+ search_ctx->ffsc_file_to_search = NULL;
+ search_ctx->ffsc_start_dir = NULL;
+ search_ctx->ffsc_fix_path = NULL;
+ search_ctx->ffsc_wc_path = NULL;
+ search_ctx->ffsc_level = 0;
+}
+
+/*
+ * check if the given path is in the stopdirs
+ * returns TRUE if yes else FALSE
+ */
+static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v)
+{
+ int i = 0;
+
+ /* eat up trailing path separators, except the first */
+ while (path_len > 1 && vim_ispathsep(path[path_len - 1]))
+ path_len--;
+
+ /* if no path consider it as match */
+ if (path_len == 0)
+ return TRUE;
+
+ for (i = 0; stopdirs_v[i] != NULL; i++) {
+ if ((int)STRLEN(stopdirs_v[i]) > path_len) {
+ /* match for parent directory. So '/home' also matches
+ * '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
+ * '/home/r' would also match '/home/rks'
+ */
+ if (fnamencmp(stopdirs_v[i], path, path_len) == 0
+ && vim_ispathsep(stopdirs_v[i][path_len]))
+ return TRUE;
+ } else {
+ if (fnamecmp(stopdirs_v[i], path) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Find the file name "ptr[len]" in the path. Also finds directory names.
+ *
+ * On the first call set the parameter 'first' to TRUE to initialize
+ * the search. For repeating calls to FALSE.
+ *
+ * Repeating calls will return other files called 'ptr[len]' from the path.
+ *
+ * Only on the first call 'ptr' and 'len' are used. For repeating calls they
+ * don't need valid values.
+ *
+ * If nothing found on the first call the option FNAME_MESS will issue the
+ * message:
+ * 'Can't find file "<file>" in path'
+ * On repeating calls:
+ * 'No more file "<file>" found in path'
+ *
+ * options:
+ * FNAME_MESS give error message when not found
+ *
+ * Uses NameBuff[]!
+ *
+ * Returns an allocated string for the file name. NULL for error.
+ *
+ */
+char_u *
+find_file_in_path (
+ char_u *ptr, /* file name */
+ int len, /* length of file name */
+ int options,
+ int first, /* use count'th matching file name */
+ char_u *rel_fname /* file name searching relative to */
+)
+{
+ return find_file_in_path_option(ptr, len, options, first,
+ *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path,
+ FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
+}
+
+static char_u *ff_file_to_find = NULL;
+static void *fdip_search_ctx = NULL;
+
+#if defined(EXITFREE)
+void free_findfile(void) {
+ vim_free(ff_file_to_find);
+ vim_findfile_cleanup(fdip_search_ctx);
+ vim_free(ff_expand_buffer);
+}
+
+#endif
+
+/*
+ * Find the directory name "ptr[len]" in the path.
+ *
+ * options:
+ * FNAME_MESS give error message when not found
+ *
+ * Uses NameBuff[]!
+ *
+ * Returns an allocated string for the file name. NULL for error.
+ */
+char_u *
+find_directory_in_path (
+ char_u *ptr, /* file name */
+ int len, /* length of file name */
+ int options,
+ char_u *rel_fname /* file name searching relative to */
+)
+{
+ return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath,
+ FINDFILE_DIR, rel_fname, (char_u *)"");
+}
+
+char_u *
+find_file_in_path_option (
+ char_u *ptr, /* file name */
+ int len, /* length of file name */
+ int options,
+ int first, /* use count'th matching file name */
+ char_u *path_option, /* p_path or p_cdpath */
+ int find_what, /* FINDFILE_FILE, _DIR or _BOTH */
+ char_u *rel_fname, /* file name we are looking relative to. */
+ char_u *suffixes /* list of suffixes, 'suffixesadd' option */
+)
+{
+ static char_u *dir;
+ static int did_findfile_init = FALSE;
+ char_u save_char;
+ char_u *file_name = NULL;
+ char_u *buf = NULL;
+ int rel_to_curdir;
+
+ if (first == TRUE) {
+ /* copy file name into NameBuff, expanding environment variables */
+ save_char = ptr[len];
+ ptr[len] = NUL;
+ expand_env(ptr, NameBuff, MAXPATHL);
+ ptr[len] = save_char;
+
+ vim_free(ff_file_to_find);
+ ff_file_to_find = vim_strsave(NameBuff);
+ if (ff_file_to_find == NULL) { /* out of memory */
+ file_name = NULL;
+ goto theend;
+ }
+ }
+
+ rel_to_curdir = (ff_file_to_find[0] == '.'
+ && (ff_file_to_find[1] == NUL
+ || vim_ispathsep(ff_file_to_find[1])
+ || (ff_file_to_find[1] == '.'
+ && (ff_file_to_find[2] == NUL
+ || vim_ispathsep(ff_file_to_find[2])))));
+ if (vim_isAbsName(ff_file_to_find)
+ /* "..", "../path", "." and "./path": don't use the path_option */
+ || rel_to_curdir
+ ) {
+ /*
+ * Absolute path, no need to use "path_option".
+ * If this is not a first call, return NULL. We already returned a
+ * filename on the first call.
+ */
+ if (first == TRUE) {
+ int l;
+ int run;
+
+ if (path_with_url(ff_file_to_find)) {
+ file_name = vim_strsave(ff_file_to_find);
+ goto theend;
+ }
+
+ /* When FNAME_REL flag given first use the directory of the file.
+ * Otherwise or when this fails use the current directory. */
+ for (run = 1; run <= 2; ++run) {
+ l = (int)STRLEN(ff_file_to_find);
+ if (run == 1
+ && rel_to_curdir
+ && (options & FNAME_REL)
+ && rel_fname != NULL
+ && STRLEN(rel_fname) + l < MAXPATHL) {
+ STRCPY(NameBuff, rel_fname);
+ STRCPY(gettail(NameBuff), ff_file_to_find);
+ l = (int)STRLEN(NameBuff);
+ } else {
+ STRCPY(NameBuff, ff_file_to_find);
+ run = 2;
+ }
+
+ /* When the file doesn't exist, try adding parts of
+ * 'suffixesadd'. */
+ buf = suffixes;
+ for (;; ) {
+ if (
+ (mch_getperm(NameBuff) >= 0
+ && (find_what == FINDFILE_BOTH
+ || ((find_what == FINDFILE_DIR)
+ == mch_isdir(NameBuff))))) {
+ file_name = vim_strsave(NameBuff);
+ goto theend;
+ }
+ if (*buf == NUL)
+ break;
+ copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ",");
+ }
+ }
+ }
+ } else {
+ /*
+ * Loop over all paths in the 'path' or 'cdpath' option.
+ * When "first" is set, first setup to the start of the option.
+ * Otherwise continue to find the next match.
+ */
+ if (first == TRUE) {
+ /* vim_findfile_free_visited can handle a possible NULL pointer */
+ vim_findfile_free_visited(fdip_search_ctx);
+ dir = path_option;
+ did_findfile_init = FALSE;
+ }
+
+ for (;; ) {
+ if (did_findfile_init) {
+ file_name = vim_findfile(fdip_search_ctx);
+ if (file_name != NULL)
+ break;
+
+ did_findfile_init = FALSE;
+ } else {
+ char_u *r_ptr;
+
+ if (dir == NULL || *dir == NUL) {
+ /* We searched all paths of the option, now we can
+ * free the search context. */
+ vim_findfile_cleanup(fdip_search_ctx);
+ fdip_search_ctx = NULL;
+ break;
+ }
+
+ if ((buf = alloc((int)(MAXPATHL))) == NULL)
+ break;
+
+ /* copy next path */
+ buf[0] = 0;
+ copy_option_part(&dir, buf, MAXPATHL, " ,");
+
+ /* get the stopdir string */
+ r_ptr = vim_findfile_stopdir(buf);
+ fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
+ r_ptr, 100, FALSE, find_what,
+ fdip_search_ctx, FALSE, rel_fname);
+ if (fdip_search_ctx != NULL)
+ did_findfile_init = TRUE;
+ vim_free(buf);
+ }
+ }
+ }
+ if (file_name == NULL && (options & FNAME_MESS)) {
+ if (first == TRUE) {
+ if (find_what == FINDFILE_DIR)
+ EMSG2(_("E344: Can't find directory \"%s\" in cdpath"),
+ ff_file_to_find);
+ else
+ EMSG2(_("E345: Can't find file \"%s\" in path"),
+ ff_file_to_find);
+ } else {
+ if (find_what == FINDFILE_DIR)
+ EMSG2(_("E346: No more directory \"%s\" found in cdpath"),
+ ff_file_to_find);
+ else
+ EMSG2(_("E347: No more file \"%s\" found in path"),
+ ff_file_to_find);
+ }
+ }
+
+theend:
+ return file_name;
+}
+
diff --git a/src/file_search.h b/src/file_search.h
new file mode 100644
index 0000000000..3ea1d84a1b
--- /dev/null
+++ b/src/file_search.h
@@ -0,0 +1,23 @@
+#ifndef NEOVIM_FIND_FILE_H
+#define NEOVIM_FIND_FILE_H
+
+void *vim_findfile_init __ARGS((char_u *path, char_u *filename, char_u *
+ stopdirs, int level, int free_visited,
+ int find_what, void *search_ctx_arg,
+ int tagfile,
+ char_u *rel_fname));
+char_u *vim_findfile_stopdir __ARGS((char_u *buf));
+void vim_findfile_cleanup __ARGS((void *ctx));
+char_u *vim_findfile __ARGS((void *search_ctx_arg));
+void vim_findfile_free_visited __ARGS((void *search_ctx_arg));
+char_u *find_file_in_path __ARGS((char_u *ptr, int len, int options, int first,
+ char_u *rel_fname));
+void free_findfile __ARGS((void));
+char_u *find_directory_in_path __ARGS((char_u *ptr, int len, int options,
+ char_u *rel_fname));
+char_u *find_file_in_path_option __ARGS((char_u *ptr, int len, int options,
+ int first, char_u *path_option,
+ int find_what, char_u *rel_fname,
+ char_u *suffixes));
+
+#endif /* NEOVIM_FIND_FILE_H */
diff --git a/src/misc2.c b/src/misc2.c
index 910ff5150c..d82c45f8ec 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -12,6 +12,7 @@
*/
#include "vim.h"
#include "misc2.h"
+#include "file_search.h"
#include "blowfish.h"
#include "charset.h"
#include "edit.h"
@@ -37,8 +38,6 @@
static char_u *username = NULL; /* cached result of mch_get_user_name() */
-static char_u *ff_expand_buffer = NULL; /* used for expanding filenames */
-
static int coladvance2 __ARGS((pos_T *pos, int addspaces, int finetune,
colnr_T wcol));
@@ -856,8 +855,6 @@ void do_outofmem_msg(long_u size)
#if defined(EXITFREE) || defined(PROTO)
-static void free_findfile __ARGS((void));
-
/*
* Free everything that we allocated.
* Can be used to detect memory leaks, e.g., with ccmalloc.
@@ -931,7 +928,6 @@ void free_all_mem(void) {
vim_free(last_cmdline);
vim_free(new_last_cmdline);
set_keep_msg(NULL, 0);
- vim_free(ff_expand_buffer);
/* Clear cmdline history. */
p_hi = 0;
@@ -3142,1590 +3138,6 @@ get_crypt_key (
}
-/* TODO: make some #ifdef for this */
-/*--------[ file searching ]-------------------------------------------------*/
-/*
- * File searching functions for 'path', 'tags' and 'cdpath' options.
- * External visible functions:
- * vim_findfile_init() creates/initialises the search context
- * vim_findfile_free_visited() free list of visited files/dirs of search
- * context
- * vim_findfile() find a file in the search context
- * vim_findfile_cleanup() cleanup/free search context created by
- * vim_findfile_init()
- *
- * All static functions and variables start with 'ff_'
- *
- * In general it works like this:
- * First you create yourself a search context by calling vim_findfile_init().
- * It is possible to give a search context from a previous call to
- * vim_findfile_init(), so it can be reused. After this you call vim_findfile()
- * until you are satisfied with the result or it returns NULL. On every call it
- * returns the next file which matches the conditions given to
- * vim_findfile_init(). If it doesn't find a next file it returns NULL.
- *
- * It is possible to call vim_findfile_init() again to reinitialise your search
- * with some new parameters. Don't forget to pass your old search context to
- * it, so it can reuse it and especially reuse the list of already visited
- * directories. If you want to delete the list of already visited directories
- * simply call vim_findfile_free_visited().
- *
- * When you are done call vim_findfile_cleanup() to free the search context.
- *
- * The function vim_findfile_init() has a long comment, which describes the
- * needed parameters.
- *
- *
- *
- * ATTENTION:
- * ==========
- * Also we use an allocated search context here, this functions are NOT
- * thread-safe!!!!!
- *
- * To minimize parameter passing (or because I'm to lazy), only the
- * external visible functions get a search context as a parameter. This is
- * then assigned to a static global, which is used throughout the local
- * functions.
- */
-
-/*
- * type for the directory search stack
- */
-typedef struct ff_stack {
- struct ff_stack *ffs_prev;
-
- /* the fix part (no wildcards) and the part containing the wildcards
- * of the search path
- */
- char_u *ffs_fix_path;
- char_u *ffs_wc_path;
-
- /* files/dirs found in the above directory, matched by the first wildcard
- * of wc_part
- */
- char_u **ffs_filearray;
- int ffs_filearray_size;
- char_u ffs_filearray_cur; /* needed for partly handled dirs */
-
- /* to store status of partly handled directories
- * 0: we work on this directory for the first time
- * 1: this directory was partly searched in an earlier step
- */
- int ffs_stage;
-
- /* How deep are we in the directory tree?
- * Counts backward from value of level parameter to vim_findfile_init
- */
- int ffs_level;
-
- /* Did we already expand '**' to an empty string? */
- int ffs_star_star_empty;
-} ff_stack_T;
-
-/*
- * type for already visited directories or files.
- */
-typedef struct ff_visited {
- struct ff_visited *ffv_next;
-
- /* Visited directories are different if the wildcard string are
- * different. So we have to save it.
- */
- char_u *ffv_wc_path;
- /* for unix use inode etc for comparison (needed because of links), else
- * use filename.
- */
-#ifdef UNIX
- int ffv_dev_valid; /* ffv_dev and ffv_ino were set */
- dev_t ffv_dev; /* device number */
- ino_t ffv_ino; /* inode number */
-#endif
- /* The memory for this struct is allocated according to the length of
- * ffv_fname.
- */
- char_u ffv_fname[1]; /* actually longer */
-} ff_visited_T;
-
-/*
- * We might have to manage several visited lists during a search.
- * This is especially needed for the tags option. If tags is set to:
- * "./++/tags,./++/TAGS,++/tags" (replace + with *)
- * So we have to do 3 searches:
- * 1) search from the current files directory downward for the file "tags"
- * 2) search from the current files directory downward for the file "TAGS"
- * 3) search from Vims current directory downwards for the file "tags"
- * As you can see, the first and the third search are for the same file, so for
- * the third search we can use the visited list of the first search. For the
- * second search we must start from a empty visited list.
- * The struct ff_visited_list_hdr is used to manage a linked list of already
- * visited lists.
- */
-typedef struct ff_visited_list_hdr {
- struct ff_visited_list_hdr *ffvl_next;
-
- /* the filename the attached visited list is for */
- char_u *ffvl_filename;
-
- ff_visited_T *ffvl_visited_list;
-
-} ff_visited_list_hdr_T;
-
-
-/*
- * '**' can be expanded to several directory levels.
- * Set the default maximum depth.
- */
-#define FF_MAX_STAR_STAR_EXPAND ((char_u)30)
-
-/*
- * The search context:
- * ffsc_stack_ptr: the stack for the dirs to search
- * ffsc_visited_list: the currently active visited list
- * ffsc_dir_visited_list: the currently active visited list for search dirs
- * ffsc_visited_lists_list: the list of all visited lists
- * ffsc_dir_visited_lists_list: the list of all visited lists for search dirs
- * ffsc_file_to_search: the file to search for
- * ffsc_start_dir: the starting directory, if search path was relative
- * ffsc_fix_path: the fix part of the given path (without wildcards)
- * Needed for upward search.
- * ffsc_wc_path: the part of the given path containing wildcards
- * ffsc_level: how many levels of dirs to search downwards
- * ffsc_stopdirs_v: array of stop directories for upward search
- * ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE
- * ffsc_tagfile: searching for tags file, don't use 'suffixesadd'
- */
-typedef struct ff_search_ctx_T {
- ff_stack_T *ffsc_stack_ptr;
- ff_visited_list_hdr_T *ffsc_visited_list;
- ff_visited_list_hdr_T *ffsc_dir_visited_list;
- ff_visited_list_hdr_T *ffsc_visited_lists_list;
- ff_visited_list_hdr_T *ffsc_dir_visited_lists_list;
- char_u *ffsc_file_to_search;
- char_u *ffsc_start_dir;
- char_u *ffsc_fix_path;
- char_u *ffsc_wc_path;
- int ffsc_level;
- char_u **ffsc_stopdirs_v;
- int ffsc_find_what;
- int ffsc_tagfile;
-} ff_search_ctx_T;
-
-/* locally needed functions */
-static int ff_check_visited __ARGS((ff_visited_T **, char_u *, char_u *));
-static void vim_findfile_free_visited_list __ARGS(
- (ff_visited_list_hdr_T **list_headp));
-static void ff_free_visited_list __ARGS((ff_visited_T *vl));
-static ff_visited_list_hdr_T* ff_get_visited_list __ARGS(
- (char_u *, ff_visited_list_hdr_T **list_headp));
-static int ff_wc_equal __ARGS((char_u *s1, char_u *s2));
-
-static void ff_push __ARGS((ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr));
-static ff_stack_T *ff_pop __ARGS((ff_search_ctx_T *search_ctx));
-static void ff_clear __ARGS((ff_search_ctx_T *search_ctx));
-static void ff_free_stack_element __ARGS((ff_stack_T *stack_ptr));
-static ff_stack_T *ff_create_stack_element __ARGS((char_u *, char_u *, int, int));
-static int ff_path_in_stoplist __ARGS((char_u *, int, char_u **));
-
-static char_u e_pathtoolong[] = N_("E854: path too long for completion");
-
-
-/*
- * Initialization routine for vim_findfile().
- *
- * Returns the newly allocated search context or NULL if an error occurred.
- *
- * Don't forget to clean up by calling vim_findfile_cleanup() if you are done
- * with the search context.
- *
- * Find the file 'filename' in the directory 'path'.
- * The parameter 'path' may contain wildcards. If so only search 'level'
- * directories deep. The parameter 'level' is the absolute maximum and is
- * not related to restricts given to the '**' wildcard. If 'level' is 100
- * and you use '**200' vim_findfile() will stop after 100 levels.
- *
- * 'filename' cannot contain wildcards! It is used as-is, no backslashes to
- * escape special characters.
- *
- * If 'stopdirs' is not NULL and nothing is found downward, the search is
- * restarted on the next higher directory level. This is repeated until the
- * start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the
- * format ";*<dirname>*\(;<dirname>\)*;\=$".
- *
- * If the 'path' is relative, the starting dir for the search is either VIM's
- * current dir or if the path starts with "./" the current files dir.
- * If the 'path' is absolute, the starting dir is that part of the path before
- * the first wildcard.
- *
- * Upward search is only done on the starting dir.
- *
- * If 'free_visited' is TRUE the list of already visited files/directories is
- * cleared. Set this to FALSE if you just want to search from another
- * directory, but want to be sure that no directory from a previous search is
- * searched again. This is useful if you search for a file at different places.
- * The list of visited files/dirs can also be cleared with the function
- * vim_findfile_free_visited().
- *
- * Set the parameter 'find_what' to FINDFILE_DIR if you want to search for
- * directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both.
- *
- * A search context returned by a previous call to vim_findfile_init() can be
- * passed in the parameter "search_ctx_arg". This context is reused and
- * reinitialized with the new parameters. The list of already visited
- * directories from this context is only deleted if the parameter
- * "free_visited" is true. Be aware that the passed "search_ctx_arg" is freed
- * if the reinitialization fails.
- *
- * If you don't have a search context from a previous call "search_ctx_arg"
- * must be NULL.
- *
- * This function silently ignores a few errors, vim_findfile() will have
- * limited functionality then.
- */
-void *
-vim_findfile_init (
- char_u *path,
- char_u *filename,
- char_u *stopdirs,
- int level,
- int free_visited,
- int find_what,
- void *search_ctx_arg,
- int tagfile, /* expanding names of tags files */
- char_u *rel_fname /* file name to use for "." */
-)
-{
- char_u *wc_part;
- ff_stack_T *sptr;
- ff_search_ctx_T *search_ctx;
-
- /* If a search context is given by the caller, reuse it, else allocate a
- * new one.
- */
- if (search_ctx_arg != NULL)
- search_ctx = search_ctx_arg;
- else {
- search_ctx = (ff_search_ctx_T*)alloc((unsigned)sizeof(ff_search_ctx_T));
- if (search_ctx == NULL)
- goto error_return;
- vim_memset(search_ctx, 0, sizeof(ff_search_ctx_T));
- }
- search_ctx->ffsc_find_what = find_what;
- search_ctx->ffsc_tagfile = tagfile;
-
- /* clear the search context, but NOT the visited lists */
- ff_clear(search_ctx);
-
- /* clear visited list if wanted */
- if (free_visited == TRUE)
- vim_findfile_free_visited(search_ctx);
- else {
- /* Reuse old visited lists. Get the visited list for the given
- * filename. If no list for the current filename exists, creates a new
- * one. */
- search_ctx->ffsc_visited_list = ff_get_visited_list(filename,
- &search_ctx->ffsc_visited_lists_list);
- if (search_ctx->ffsc_visited_list == NULL)
- goto error_return;
- search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename,
- &search_ctx->ffsc_dir_visited_lists_list);
- if (search_ctx->ffsc_dir_visited_list == NULL)
- goto error_return;
- }
-
- if (ff_expand_buffer == NULL) {
- ff_expand_buffer = (char_u*)alloc(MAXPATHL);
- if (ff_expand_buffer == NULL)
- goto error_return;
- }
-
- /* Store information on starting dir now if path is relative.
- * If path is absolute, we do that later. */
- if (path[0] == '.'
- && (vim_ispathsep(path[1]) || path[1] == NUL)
- && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
- && rel_fname != NULL) {
- int len = (int)(gettail(rel_fname) - rel_fname);
-
- if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) {
- /* Make the start dir an absolute path name. */
- vim_strncpy(ff_expand_buffer, rel_fname, len);
- search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, FALSE);
- } else
- search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
- if (search_ctx->ffsc_start_dir == NULL)
- goto error_return;
- if (*++path != NUL)
- ++path;
- } else if (*path == NUL || !vim_isAbsName(path)) {
-#ifdef BACKSLASH_IN_FILENAME
- /* "c:dir" needs "c:" to be expanded, otherwise use current dir */
- if (*path != NUL && path[1] == ':') {
- char_u drive[3];
-
- drive[0] = path[0];
- drive[1] = ':';
- drive[2] = NUL;
- if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
- goto error_return;
- path += 2;
- } else
-#endif
- if (mch_dirname(ff_expand_buffer, MAXPATHL) == FAIL)
- goto error_return;
-
- search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer);
- if (search_ctx->ffsc_start_dir == NULL)
- goto error_return;
-
-#ifdef BACKSLASH_IN_FILENAME
- /* A path that starts with "/dir" is relative to the drive, not to the
- * directory (but not for "//machine/dir"). Only use the drive name. */
- if ((*path == '/' || *path == '\\')
- && path[1] != path[0]
- && search_ctx->ffsc_start_dir[1] == ':')
- search_ctx->ffsc_start_dir[2] = NUL;
-#endif
- }
-
- /*
- * If stopdirs are given, split them into an array of pointers.
- * If this fails (mem allocation), there is no upward search at all or a
- * stop directory is not recognized -> continue silently.
- * If stopdirs just contains a ";" or is empty,
- * search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This
- * is handled as unlimited upward search. See function
- * ff_path_in_stoplist() for details.
- */
- if (stopdirs != NULL) {
- char_u *walker = stopdirs;
- int dircount;
-
- while (*walker == ';')
- walker++;
-
- dircount = 1;
- search_ctx->ffsc_stopdirs_v =
- (char_u **)alloc((unsigned)sizeof(char_u *));
-
- if (search_ctx->ffsc_stopdirs_v != NULL) {
- do {
- char_u *helper;
- void *ptr;
-
- helper = walker;
- ptr = vim_realloc(search_ctx->ffsc_stopdirs_v,
- (dircount + 1) * sizeof(char_u *));
- if (ptr)
- search_ctx->ffsc_stopdirs_v = ptr;
- else
- /* ignore, keep what we have and continue */
- break;
- walker = vim_strchr(walker, ';');
- if (walker) {
- search_ctx->ffsc_stopdirs_v[dircount-1] =
- vim_strnsave(helper, (int)(walker - helper));
- walker++;
- } else
- /* this might be "", which means ascent till top
- * of directory tree.
- */
- search_ctx->ffsc_stopdirs_v[dircount-1] =
- vim_strsave(helper);
-
- dircount++;
-
- } while (walker != NULL);
- search_ctx->ffsc_stopdirs_v[dircount-1] = NULL;
- }
- }
-
- search_ctx->ffsc_level = level;
-
- /* split into:
- * -fix path
- * -wildcard_stuff (might be NULL)
- */
- wc_part = vim_strchr(path, '*');
- if (wc_part != NULL) {
- int llevel;
- int len;
- char *errpt;
-
- /* save the fix part of the path */
- search_ctx->ffsc_fix_path = vim_strnsave(path, (int)(wc_part - path));
-
- /*
- * copy wc_path and add restricts to the '**' wildcard.
- * The octet after a '**' is used as a (binary) counter.
- * So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
- * or '**76' is transposed to '**N'( 'N' is ASCII value 76).
- * For EBCDIC you get different character values.
- * If no restrict is given after '**' the default is used.
- * Due to this technique the path looks awful if you print it as a
- * string.
- */
- len = 0;
- while (*wc_part != NUL) {
- if (len + 5 >= MAXPATHL) {
- EMSG(_(e_pathtoolong));
- break;
- }
- if (STRNCMP(wc_part, "**", 2) == 0) {
- ff_expand_buffer[len++] = *wc_part++;
- ff_expand_buffer[len++] = *wc_part++;
-
- llevel = strtol((char *)wc_part, &errpt, 10);
- if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255)
- ff_expand_buffer[len++] = llevel;
- else if ((char_u *)errpt != wc_part && llevel == 0)
- /* restrict is 0 -> remove already added '**' */
- len -= 2;
- else
- ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND;
- wc_part = (char_u *)errpt;
- if (*wc_part != NUL && !vim_ispathsep(*wc_part)) {
- EMSG2(_(
- "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."),
- PATHSEPSTR);
- goto error_return;
- }
- } else
- ff_expand_buffer[len++] = *wc_part++;
- }
- ff_expand_buffer[len] = NUL;
- search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer);
-
- if (search_ctx->ffsc_wc_path == NULL)
- goto error_return;
- } else
- search_ctx->ffsc_fix_path = vim_strsave(path);
-
- if (search_ctx->ffsc_start_dir == NULL) {
- /* store the fix part as startdir.
- * This is needed if the parameter path is fully qualified.
- */
- search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path);
- if (search_ctx->ffsc_start_dir == NULL)
- goto error_return;
- search_ctx->ffsc_fix_path[0] = NUL;
- }
-
- /* create an absolute path */
- if (STRLEN(search_ctx->ffsc_start_dir)
- + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) {
- EMSG(_(e_pathtoolong));
- goto error_return;
- }
- STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
- add_pathsep(ff_expand_buffer);
- {
- int eb_len = (int)STRLEN(ff_expand_buffer);
- char_u *buf = alloc(eb_len
- + (int)STRLEN(search_ctx->ffsc_fix_path) + 1);
-
- STRCPY(buf, ff_expand_buffer);
- STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
- if (mch_isdir(buf)) {
- STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
- add_pathsep(ff_expand_buffer);
- } else {
- char_u *p = gettail(search_ctx->ffsc_fix_path);
- char_u *wc_path = NULL;
- char_u *temp = NULL;
- int len = 0;
-
- if (p > search_ctx->ffsc_fix_path) {
- len = (int)(p - search_ctx->ffsc_fix_path) - 1;
- STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
- add_pathsep(ff_expand_buffer);
- } else
- len = (int)STRLEN(search_ctx->ffsc_fix_path);
-
- if (search_ctx->ffsc_wc_path != NULL) {
- wc_path = vim_strsave(search_ctx->ffsc_wc_path);
- temp = alloc((int)(STRLEN(search_ctx->ffsc_wc_path)
- + STRLEN(search_ctx->ffsc_fix_path + len)
- + 1));
- }
-
- if (temp == NULL || wc_path == NULL) {
- vim_free(buf);
- vim_free(temp);
- vim_free(wc_path);
- goto error_return;
- }
-
- STRCPY(temp, search_ctx->ffsc_fix_path + len);
- STRCAT(temp, search_ctx->ffsc_wc_path);
- vim_free(search_ctx->ffsc_wc_path);
- vim_free(wc_path);
- search_ctx->ffsc_wc_path = temp;
- }
- vim_free(buf);
- }
-
- sptr = ff_create_stack_element(ff_expand_buffer,
- search_ctx->ffsc_wc_path,
- level, 0);
-
- if (sptr == NULL)
- goto error_return;
-
- ff_push(search_ctx, sptr);
-
- search_ctx->ffsc_file_to_search = vim_strsave(filename);
- if (search_ctx->ffsc_file_to_search == NULL)
- goto error_return;
-
- return search_ctx;
-
-error_return:
- /*
- * We clear the search context now!
- * Even when the caller gave us a (perhaps valid) context we free it here,
- * as we might have already destroyed it.
- */
- vim_findfile_cleanup(search_ctx);
- return NULL;
-}
-
-/*
- * Get the stopdir string. Check that ';' is not escaped.
- */
-char_u *vim_findfile_stopdir(char_u *buf)
-{
- char_u *r_ptr = buf;
-
- while (*r_ptr != NUL && *r_ptr != ';') {
- if (r_ptr[0] == '\\' && r_ptr[1] == ';') {
- /* Overwrite the escape char,
- * use STRLEN(r_ptr) to move the trailing '\0'. */
- STRMOVE(r_ptr, r_ptr + 1);
- r_ptr++;
- }
- r_ptr++;
- }
- if (*r_ptr == ';') {
- *r_ptr = 0;
- r_ptr++;
- } else if (*r_ptr == NUL)
- r_ptr = NULL;
- return r_ptr;
-}
-
-/*
- * Clean up the given search context. Can handle a NULL pointer.
- */
-void vim_findfile_cleanup(void *ctx)
-{
- if (ctx == NULL)
- return;
-
- vim_findfile_free_visited(ctx);
- ff_clear(ctx);
- vim_free(ctx);
-}
-
-/*
- * Find a file in a search context.
- * The search context was created with vim_findfile_init() above.
- * Return a pointer to an allocated file name or NULL if nothing found.
- * To get all matching files call this function until you get NULL.
- *
- * If the passed search_context is NULL, NULL is returned.
- *
- * The search algorithm is depth first. To change this replace the
- * stack with a list (don't forget to leave partly searched directories on the
- * top of the list).
- */
-char_u *vim_findfile(void *search_ctx_arg)
-{
- char_u *file_path;
- char_u *rest_of_wildcards;
- char_u *path_end = NULL;
- ff_stack_T *stackp;
- int len;
- int i;
- char_u *p;
- char_u *suf;
- ff_search_ctx_T *search_ctx;
-
- if (search_ctx_arg == NULL)
- return NULL;
-
- search_ctx = (ff_search_ctx_T *)search_ctx_arg;
-
- /*
- * filepath is used as buffer for various actions and as the storage to
- * return a found filename.
- */
- if ((file_path = alloc((int)MAXPATHL)) == NULL)
- return NULL;
-
- /* store the end of the start dir -- needed for upward search */
- if (search_ctx->ffsc_start_dir != NULL)
- path_end = &search_ctx->ffsc_start_dir[
- STRLEN(search_ctx->ffsc_start_dir)];
-
- /* upward search loop */
- for (;; ) {
- /* downward search loop */
- for (;; ) {
- /* check if user user wants to stop the search*/
- ui_breakcheck();
- if (got_int)
- break;
-
- /* get directory to work on from stack */
- stackp = ff_pop(search_ctx);
- if (stackp == NULL)
- break;
-
- /*
- * TODO: decide if we leave this test in
- *
- * GOOD: don't search a directory(-tree) twice.
- * BAD: - check linked list for every new directory entered.
- * - check for double files also done below
- *
- * Here we check if we already searched this directory.
- * We already searched a directory if:
- * 1) The directory is the same.
- * 2) We would use the same wildcard string.
- *
- * Good if you have links on same directory via several ways
- * or you have selfreferences in directories (e.g. SuSE Linux 6.3:
- * /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop)
- *
- * This check is only needed for directories we work on for the
- * first time (hence stackp->ff_filearray == NULL)
- */
- if (stackp->ffs_filearray == NULL
- && ff_check_visited(&search_ctx->ffsc_dir_visited_list
- ->ffvl_visited_list,
- stackp->ffs_fix_path
- , stackp->ffs_wc_path
- ) == FAIL) {
-#ifdef FF_VERBOSE
- if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"Already Searched: %s (%s)",
- stackp->ffs_fix_path, stackp->ffs_wc_path);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
-#endif
- ff_free_stack_element(stackp);
- continue;
- }
-#ifdef FF_VERBOSE
- else if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"Searching: %s (%s)",
- stackp->ffs_fix_path, stackp->ffs_wc_path);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
-#endif
-
- /* check depth */
- if (stackp->ffs_level <= 0) {
- ff_free_stack_element(stackp);
- continue;
- }
-
- file_path[0] = NUL;
-
- /*
- * If no filearray till now expand wildcards
- * The function expand_wildcards() can handle an array of paths
- * and all possible expands are returned in one array. We use this
- * to handle the expansion of '**' into an empty string.
- */
- if (stackp->ffs_filearray == NULL) {
- char_u *dirptrs[2];
-
- /* we use filepath to build the path expand_wildcards() should
- * expand.
- */
- dirptrs[0] = file_path;
- dirptrs[1] = NULL;
-
- /* if we have a start dir copy it in */
- if (!vim_isAbsName(stackp->ffs_fix_path)
- && search_ctx->ffsc_start_dir) {
- STRCPY(file_path, search_ctx->ffsc_start_dir);
- add_pathsep(file_path);
- }
-
- /* append the fix part of the search path */
- STRCAT(file_path, stackp->ffs_fix_path);
- add_pathsep(file_path);
-
- rest_of_wildcards = stackp->ffs_wc_path;
- if (*rest_of_wildcards != NUL) {
- len = (int)STRLEN(file_path);
- if (STRNCMP(rest_of_wildcards, "**", 2) == 0) {
- /* pointer to the restrict byte
- * The restrict byte is not a character!
- */
- p = rest_of_wildcards + 2;
-
- if (*p > 0) {
- (*p)--;
- file_path[len++] = '*';
- }
-
- if (*p == 0) {
- /* remove '**<numb> from wildcards */
- STRMOVE(rest_of_wildcards, rest_of_wildcards + 3);
- } else
- rest_of_wildcards += 3;
-
- if (stackp->ffs_star_star_empty == 0) {
- /* if not done before, expand '**' to empty */
- stackp->ffs_star_star_empty = 1;
- dirptrs[1] = stackp->ffs_fix_path;
- }
- }
-
- /*
- * Here we copy until the next path separator or the end of
- * the path. If we stop at a path separator, there is
- * still something else left. This is handled below by
- * pushing every directory returned from expand_wildcards()
- * on the stack again for further search.
- */
- while (*rest_of_wildcards
- && !vim_ispathsep(*rest_of_wildcards))
- file_path[len++] = *rest_of_wildcards++;
-
- file_path[len] = NUL;
- if (vim_ispathsep(*rest_of_wildcards))
- rest_of_wildcards++;
- }
-
- /*
- * Expand wildcards like "*" and "$VAR".
- * If the path is a URL don't try this.
- */
- if (path_with_url(dirptrs[0])) {
- stackp->ffs_filearray = (char_u **)
- alloc((unsigned)sizeof(char *));
- if (stackp->ffs_filearray != NULL
- && (stackp->ffs_filearray[0]
- = vim_strsave(dirptrs[0])) != NULL)
- stackp->ffs_filearray_size = 1;
- else
- stackp->ffs_filearray_size = 0;
- } else
- /* Add EW_NOTWILD because the expanded path may contain
- * wildcard characters that are to be taken literally.
- * This is a bit of a hack. */
- expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs,
- &stackp->ffs_filearray_size,
- &stackp->ffs_filearray,
- EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD);
-
- stackp->ffs_filearray_cur = 0;
- stackp->ffs_stage = 0;
- } else
- rest_of_wildcards = &stackp->ffs_wc_path[
- STRLEN(stackp->ffs_wc_path)];
-
- if (stackp->ffs_stage == 0) {
- /* this is the first time we work on this directory */
- if (*rest_of_wildcards == NUL) {
- /*
- * We don't have further wildcards to expand, so we have to
- * check for the final file now.
- */
- for (i = stackp->ffs_filearray_cur;
- i < stackp->ffs_filearray_size; ++i) {
- if (!path_with_url(stackp->ffs_filearray[i])
- && !mch_isdir(stackp->ffs_filearray[i]))
- continue; /* not a directory */
-
- /* prepare the filename to be checked for existence
- * below */
- STRCPY(file_path, stackp->ffs_filearray[i]);
- add_pathsep(file_path);
- STRCAT(file_path, search_ctx->ffsc_file_to_search);
-
- /*
- * Try without extra suffix and then with suffixes
- * from 'suffixesadd'.
- */
- len = (int)STRLEN(file_path);
- if (search_ctx->ffsc_tagfile)
- suf = (char_u *)"";
- else
- suf = curbuf->b_p_sua;
- for (;; ) {
- /* if file exists and we didn't already find it */
- if ((path_with_url(file_path)
- || (mch_getperm(file_path) >= 0
- && (search_ctx->ffsc_find_what
- == FINDFILE_BOTH
- || ((search_ctx->ffsc_find_what
- == FINDFILE_DIR)
- == mch_isdir(file_path)))))
-#ifndef FF_VERBOSE
- && (ff_check_visited(
- &search_ctx->ffsc_visited_list->ffvl_visited_list,
- file_path
- , (char_u *)""
- ) == OK)
-#endif
- ) {
-#ifdef FF_VERBOSE
- if (ff_check_visited(
- &search_ctx->ffsc_visited_list->ffvl_visited_list,
- file_path
- , (char_u *)""
- ) == FAIL) {
- if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"Already: %s",
- file_path);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
- continue;
- }
-#endif
-
- /* push dir to examine rest of subdirs later */
- stackp->ffs_filearray_cur = i + 1;
- ff_push(search_ctx, stackp);
-
- if (!path_with_url(file_path))
- simplify_filename(file_path);
- if (mch_dirname(ff_expand_buffer, MAXPATHL)
- == OK) {
- p = shorten_fname(file_path,
- ff_expand_buffer);
- if (p != NULL)
- STRMOVE(file_path, p);
- }
-#ifdef FF_VERBOSE
- if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"HIT: %s", file_path);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
-#endif
- return file_path;
- }
-
- /* Not found or found already, try next suffix. */
- if (*suf == NUL)
- break;
- copy_option_part(&suf, file_path + len,
- MAXPATHL - len, ",");
- }
- }
- } else {
- /*
- * still wildcards left, push the directories for further
- * search
- */
- for (i = stackp->ffs_filearray_cur;
- i < stackp->ffs_filearray_size; ++i) {
- if (!mch_isdir(stackp->ffs_filearray[i]))
- continue; /* not a directory */
-
- ff_push(search_ctx,
- ff_create_stack_element(
- stackp->ffs_filearray[i],
- rest_of_wildcards,
- stackp->ffs_level - 1, 0));
- }
- }
- stackp->ffs_filearray_cur = 0;
- stackp->ffs_stage = 1;
- }
-
- /*
- * if wildcards contains '**' we have to descent till we reach the
- * leaves of the directory tree.
- */
- if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) {
- for (i = stackp->ffs_filearray_cur;
- i < stackp->ffs_filearray_size; ++i) {
- if (fnamecmp(stackp->ffs_filearray[i],
- stackp->ffs_fix_path) == 0)
- continue; /* don't repush same directory */
- if (!mch_isdir(stackp->ffs_filearray[i]))
- continue; /* not a directory */
- ff_push(search_ctx,
- ff_create_stack_element(stackp->ffs_filearray[i],
- stackp->ffs_wc_path, stackp->ffs_level - 1, 1));
- }
- }
-
- /* we are done with the current directory */
- ff_free_stack_element(stackp);
-
- }
-
- /* If we reached this, we didn't find anything downwards.
- * Let's check if we should do an upward search.
- */
- if (search_ctx->ffsc_start_dir
- && search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
- ff_stack_T *sptr;
-
- /* is the last starting directory in the stop list? */
- if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
- (int)(path_end - search_ctx->ffsc_start_dir),
- search_ctx->ffsc_stopdirs_v) == TRUE)
- break;
-
- /* cut of last dir */
- while (path_end > search_ctx->ffsc_start_dir
- && vim_ispathsep(*path_end))
- path_end--;
- while (path_end > search_ctx->ffsc_start_dir
- && !vim_ispathsep(path_end[-1]))
- path_end--;
- *path_end = 0;
- path_end--;
-
- if (*search_ctx->ffsc_start_dir == 0)
- break;
-
- STRCPY(file_path, search_ctx->ffsc_start_dir);
- add_pathsep(file_path);
- STRCAT(file_path, search_ctx->ffsc_fix_path);
-
- /* create a new stack entry */
- sptr = ff_create_stack_element(file_path,
- search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0);
- if (sptr == NULL)
- break;
- ff_push(search_ctx, sptr);
- } else
- break;
- }
-
- vim_free(file_path);
- return NULL;
-}
-
-/*
- * Free the list of lists of visited files and directories
- * Can handle it if the passed search_context is NULL;
- */
-void vim_findfile_free_visited(void *search_ctx_arg)
-{
- ff_search_ctx_T *search_ctx;
-
- if (search_ctx_arg == NULL)
- return;
-
- search_ctx = (ff_search_ctx_T *)search_ctx_arg;
- vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list);
- vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list);
-}
-
-static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
-{
- ff_visited_list_hdr_T *vp;
-
- while (*list_headp != NULL) {
- vp = (*list_headp)->ffvl_next;
- ff_free_visited_list((*list_headp)->ffvl_visited_list);
-
- vim_free((*list_headp)->ffvl_filename);
- vim_free(*list_headp);
- *list_headp = vp;
- }
- *list_headp = NULL;
-}
-
-static void ff_free_visited_list(ff_visited_T *vl)
-{
- ff_visited_T *vp;
-
- while (vl != NULL) {
- vp = vl->ffv_next;
- vim_free(vl->ffv_wc_path);
- vim_free(vl);
- vl = vp;
- }
- vl = NULL;
-}
-
-/*
- * Returns the already visited list for the given filename. If none is found it
- * allocates a new one.
- */
-static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_list_hdr_T **list_headp)
-{
- ff_visited_list_hdr_T *retptr = NULL;
-
- /* check if a visited list for the given filename exists */
- if (*list_headp != NULL) {
- retptr = *list_headp;
- while (retptr != NULL) {
- if (fnamecmp(filename, retptr->ffvl_filename) == 0) {
-#ifdef FF_VERBOSE
- if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"ff_get_visited_list: FOUND list for %s",
- filename);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
-#endif
- return retptr;
- }
- retptr = retptr->ffvl_next;
- }
- }
-
-#ifdef FF_VERBOSE
- if (p_verbose >= 5) {
- verbose_enter_scroll();
- smsg((char_u *)"ff_get_visited_list: new list for %s", filename);
- /* don't overwrite this either */
- msg_puts((char_u *)"\n");
- verbose_leave_scroll();
- }
-#endif
-
- /*
- * if we reach this we didn't find a list and we have to allocate new list
- */
- retptr = (ff_visited_list_hdr_T*)alloc((unsigned)sizeof(*retptr));
- if (retptr == NULL)
- return NULL;
-
- retptr->ffvl_visited_list = NULL;
- retptr->ffvl_filename = vim_strsave(filename);
- if (retptr->ffvl_filename == NULL) {
- vim_free(retptr);
- return NULL;
- }
- retptr->ffvl_next = *list_headp;
- *list_headp = retptr;
-
- return retptr;
-}
-
-/*
- * check if two wildcard paths are equal. Returns TRUE or FALSE.
- * They are equal if:
- * - both paths are NULL
- * - they have the same length
- * - char by char comparison is OK
- * - the only differences are in the counters behind a '**', so
- * '**\20' is equal to '**\24'
- */
-static int ff_wc_equal(char_u *s1, char_u *s2)
-{
- int i;
- int prev1 = NUL;
- int prev2 = NUL;
-
- if (s1 == s2)
- return TRUE;
-
- if (s1 == NULL || s2 == NULL)
- return FALSE;
-
- if (STRLEN(s1) != STRLEN(s2))
- return FAIL;
-
- for (i = 0; s1[i] != NUL && s2[i] != NUL; i += MB_PTR2LEN(s1 + i)) {
- int c1 = PTR2CHAR(s1 + i);
- int c2 = PTR2CHAR(s2 + i);
-
- if ((p_fic ? MB_TOLOWER(c1) != MB_TOLOWER(c2) : c1 != c2)
- && (prev1 != '*' || prev2 != '*'))
- return FAIL;
- prev2 = prev1;
- prev1 = c1;
- }
- return TRUE;
-}
-
-/*
- * maintains the list of already visited files and dirs
- * returns FAIL if the given file/dir is already in the list
- * returns OK if it is newly added
- *
- * TODO: What to do on memory allocation problems?
- * -> return TRUE - Better the file is found several times instead of
- * never.
- */
-static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path)
-{
- ff_visited_T *vp;
-#ifdef UNIX
- struct stat st;
- int url = FALSE;
-#endif
-
- /* For an URL we only compare the name, otherwise we compare the
- * device/inode (unix) or the full path name (not Unix). */
- if (path_with_url(fname)) {
- vim_strncpy(ff_expand_buffer, fname, MAXPATHL - 1);
-#ifdef UNIX
- url = TRUE;
-#endif
- } else {
- ff_expand_buffer[0] = NUL;
-#ifdef UNIX
- if (mch_stat((char *)fname, &st) < 0)
-#else
- if (vim_FullName(fname, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
-#endif
- return FAIL;
- }
-
- /* check against list of already visited files */
- for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) {
- if (
-#ifdef UNIX
- !url ? (vp->ffv_dev_valid && vp->ffv_dev == st.st_dev
- && vp->ffv_ino == st.st_ino)
- :
-#endif
- fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0
- ) {
- /* are the wildcard parts equal */
- if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE)
- /* already visited */
- return FAIL;
- }
- }
-
- /*
- * New file/dir. Add it to the list of visited files/dirs.
- */
- vp = (ff_visited_T *)alloc((unsigned)(sizeof(ff_visited_T)
- + STRLEN(ff_expand_buffer)));
-
- if (vp != NULL) {
-#ifdef UNIX
- if (!url) {
- vp->ffv_dev_valid = TRUE;
- vp->ffv_ino = st.st_ino;
- vp->ffv_dev = st.st_dev;
- vp->ffv_fname[0] = NUL;
- } else {
- vp->ffv_dev_valid = FALSE;
-#endif
- STRCPY(vp->ffv_fname, ff_expand_buffer);
-#ifdef UNIX
- }
-#endif
- if (wc_path != NULL)
- vp->ffv_wc_path = vim_strsave(wc_path);
- else
- vp->ffv_wc_path = NULL;
-
- vp->ffv_next = *visited_list;
- *visited_list = vp;
- }
-
- return OK;
-}
-
-/*
- * create stack element from given path pieces
- */
-static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, int star_star_empty)
-{
- ff_stack_T *new;
-
- new = (ff_stack_T *)alloc((unsigned)sizeof(ff_stack_T));
- if (new == NULL)
- return NULL;
-
- new->ffs_prev = NULL;
- new->ffs_filearray = NULL;
- new->ffs_filearray_size = 0;
- new->ffs_filearray_cur = 0;
- new->ffs_stage = 0;
- new->ffs_level = level;
- new->ffs_star_star_empty = star_star_empty;;
-
- /* the following saves NULL pointer checks in vim_findfile */
- if (fix_part == NULL)
- fix_part = (char_u *)"";
- new->ffs_fix_path = vim_strsave(fix_part);
-
- if (wc_part == NULL)
- wc_part = (char_u *)"";
- new->ffs_wc_path = vim_strsave(wc_part);
-
- if (new->ffs_fix_path == NULL
- || new->ffs_wc_path == NULL
- ) {
- ff_free_stack_element(new);
- new = NULL;
- }
-
- return new;
-}
-
-/*
- * Push a dir on the directory stack.
- */
-static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
-{
- /* check for NULL pointer, not to return an error to the user, but
- * to prevent a crash */
- if (stack_ptr != NULL) {
- stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
- search_ctx->ffsc_stack_ptr = stack_ptr;
- }
-}
-
-/*
- * Pop a dir from the directory stack.
- * Returns NULL if stack is empty.
- */
-static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
-{
- ff_stack_T *sptr;
-
- sptr = search_ctx->ffsc_stack_ptr;
- if (search_ctx->ffsc_stack_ptr != NULL)
- search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev;
-
- return sptr;
-}
-
-/*
- * free the given stack element
- */
-static void ff_free_stack_element(ff_stack_T *stack_ptr)
-{
- /* vim_free handles possible NULL pointers */
- vim_free(stack_ptr->ffs_fix_path);
- vim_free(stack_ptr->ffs_wc_path);
-
- if (stack_ptr->ffs_filearray != NULL)
- FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray);
-
- vim_free(stack_ptr);
-}
-
-/*
- * Clear the search context, but NOT the visited list.
- */
-static void ff_clear(ff_search_ctx_T *search_ctx)
-{
- ff_stack_T *sptr;
-
- /* clear up stack */
- while ((sptr = ff_pop(search_ctx)) != NULL)
- ff_free_stack_element(sptr);
-
- vim_free(search_ctx->ffsc_file_to_search);
- vim_free(search_ctx->ffsc_start_dir);
- vim_free(search_ctx->ffsc_fix_path);
- vim_free(search_ctx->ffsc_wc_path);
-
- if (search_ctx->ffsc_stopdirs_v != NULL) {
- int i = 0;
-
- while (search_ctx->ffsc_stopdirs_v[i] != NULL) {
- vim_free(search_ctx->ffsc_stopdirs_v[i]);
- i++;
- }
- vim_free(search_ctx->ffsc_stopdirs_v);
- }
- search_ctx->ffsc_stopdirs_v = NULL;
-
- /* reset everything */
- search_ctx->ffsc_file_to_search = NULL;
- search_ctx->ffsc_start_dir = NULL;
- search_ctx->ffsc_fix_path = NULL;
- search_ctx->ffsc_wc_path = NULL;
- search_ctx->ffsc_level = 0;
-}
-
-/*
- * check if the given path is in the stopdirs
- * returns TRUE if yes else FALSE
- */
-static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v)
-{
- int i = 0;
-
- /* eat up trailing path separators, except the first */
- while (path_len > 1 && vim_ispathsep(path[path_len - 1]))
- path_len--;
-
- /* if no path consider it as match */
- if (path_len == 0)
- return TRUE;
-
- for (i = 0; stopdirs_v[i] != NULL; i++) {
- if ((int)STRLEN(stopdirs_v[i]) > path_len) {
- /* match for parent directory. So '/home' also matches
- * '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
- * '/home/r' would also match '/home/rks'
- */
- if (fnamencmp(stopdirs_v[i], path, path_len) == 0
- && vim_ispathsep(stopdirs_v[i][path_len]))
- return TRUE;
- } else {
- if (fnamecmp(stopdirs_v[i], path) == 0)
- return TRUE;
- }
- }
- return FALSE;
-}
-
-/*
- * Find the file name "ptr[len]" in the path. Also finds directory names.
- *
- * On the first call set the parameter 'first' to TRUE to initialize
- * the search. For repeating calls to FALSE.
- *
- * Repeating calls will return other files called 'ptr[len]' from the path.
- *
- * Only on the first call 'ptr' and 'len' are used. For repeating calls they
- * don't need valid values.
- *
- * If nothing found on the first call the option FNAME_MESS will issue the
- * message:
- * 'Can't find file "<file>" in path'
- * On repeating calls:
- * 'No more file "<file>" found in path'
- *
- * options:
- * FNAME_MESS give error message when not found
- *
- * Uses NameBuff[]!
- *
- * Returns an allocated string for the file name. NULL for error.
- *
- */
-char_u *
-find_file_in_path (
- char_u *ptr, /* file name */
- int len, /* length of file name */
- int options,
- int first, /* use count'th matching file name */
- char_u *rel_fname /* file name searching relative to */
-)
-{
- return find_file_in_path_option(ptr, len, options, first,
- *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path,
- FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
-}
-
-static char_u *ff_file_to_find = NULL;
-static void *fdip_search_ctx = NULL;
-
-#if defined(EXITFREE)
-static void free_findfile(void) {
- vim_free(ff_file_to_find);
- vim_findfile_cleanup(fdip_search_ctx);
-}
-
-#endif
-
-/*
- * Find the directory name "ptr[len]" in the path.
- *
- * options:
- * FNAME_MESS give error message when not found
- *
- * Uses NameBuff[]!
- *
- * Returns an allocated string for the file name. NULL for error.
- */
-char_u *
-find_directory_in_path (
- char_u *ptr, /* file name */
- int len, /* length of file name */
- int options,
- char_u *rel_fname /* file name searching relative to */
-)
-{
- return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath,
- FINDFILE_DIR, rel_fname, (char_u *)"");
-}
-
-char_u *
-find_file_in_path_option (
- char_u *ptr, /* file name */
- int len, /* length of file name */
- int options,
- int first, /* use count'th matching file name */
- char_u *path_option, /* p_path or p_cdpath */
- int find_what, /* FINDFILE_FILE, _DIR or _BOTH */
- char_u *rel_fname, /* file name we are looking relative to. */
- char_u *suffixes /* list of suffixes, 'suffixesadd' option */
-)
-{
- static char_u *dir;
- static int did_findfile_init = FALSE;
- char_u save_char;
- char_u *file_name = NULL;
- char_u *buf = NULL;
- int rel_to_curdir;
-
- if (first == TRUE) {
- /* copy file name into NameBuff, expanding environment variables */
- save_char = ptr[len];
- ptr[len] = NUL;
- expand_env(ptr, NameBuff, MAXPATHL);
- ptr[len] = save_char;
-
- vim_free(ff_file_to_find);
- ff_file_to_find = vim_strsave(NameBuff);
- if (ff_file_to_find == NULL) { /* out of memory */
- file_name = NULL;
- goto theend;
- }
- }
-
- rel_to_curdir = (ff_file_to_find[0] == '.'
- && (ff_file_to_find[1] == NUL
- || vim_ispathsep(ff_file_to_find[1])
- || (ff_file_to_find[1] == '.'
- && (ff_file_to_find[2] == NUL
- || vim_ispathsep(ff_file_to_find[2])))));
- if (vim_isAbsName(ff_file_to_find)
- /* "..", "../path", "." and "./path": don't use the path_option */
- || rel_to_curdir
- ) {
- /*
- * Absolute path, no need to use "path_option".
- * If this is not a first call, return NULL. We already returned a
- * filename on the first call.
- */
- if (first == TRUE) {
- int l;
- int run;
-
- if (path_with_url(ff_file_to_find)) {
- file_name = vim_strsave(ff_file_to_find);
- goto theend;
- }
-
- /* When FNAME_REL flag given first use the directory of the file.
- * Otherwise or when this fails use the current directory. */
- for (run = 1; run <= 2; ++run) {
- l = (int)STRLEN(ff_file_to_find);
- if (run == 1
- && rel_to_curdir
- && (options & FNAME_REL)
- && rel_fname != NULL
- && STRLEN(rel_fname) + l < MAXPATHL) {
- STRCPY(NameBuff, rel_fname);
- STRCPY(gettail(NameBuff), ff_file_to_find);
- l = (int)STRLEN(NameBuff);
- } else {
- STRCPY(NameBuff, ff_file_to_find);
- run = 2;
- }
-
- /* When the file doesn't exist, try adding parts of
- * 'suffixesadd'. */
- buf = suffixes;
- for (;; ) {
- if (
- (mch_getperm(NameBuff) >= 0
- && (find_what == FINDFILE_BOTH
- || ((find_what == FINDFILE_DIR)
- == mch_isdir(NameBuff))))) {
- file_name = vim_strsave(NameBuff);
- goto theend;
- }
- if (*buf == NUL)
- break;
- copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ",");
- }
- }
- }
- } else {
- /*
- * Loop over all paths in the 'path' or 'cdpath' option.
- * When "first" is set, first setup to the start of the option.
- * Otherwise continue to find the next match.
- */
- if (first == TRUE) {
- /* vim_findfile_free_visited can handle a possible NULL pointer */
- vim_findfile_free_visited(fdip_search_ctx);
- dir = path_option;
- did_findfile_init = FALSE;
- }
-
- for (;; ) {
- if (did_findfile_init) {
- file_name = vim_findfile(fdip_search_ctx);
- if (file_name != NULL)
- break;
-
- did_findfile_init = FALSE;
- } else {
- char_u *r_ptr;
-
- if (dir == NULL || *dir == NUL) {
- /* We searched all paths of the option, now we can
- * free the search context. */
- vim_findfile_cleanup(fdip_search_ctx);
- fdip_search_ctx = NULL;
- break;
- }
-
- if ((buf = alloc((int)(MAXPATHL))) == NULL)
- break;
-
- /* copy next path */
- buf[0] = 0;
- copy_option_part(&dir, buf, MAXPATHL, " ,");
-
- /* get the stopdir string */
- r_ptr = vim_findfile_stopdir(buf);
- fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
- r_ptr, 100, FALSE, find_what,
- fdip_search_ctx, FALSE, rel_fname);
- if (fdip_search_ctx != NULL)
- did_findfile_init = TRUE;
- vim_free(buf);
- }
- }
- }
- if (file_name == NULL && (options & FNAME_MESS)) {
- if (first == TRUE) {
- if (find_what == FINDFILE_DIR)
- EMSG2(_("E344: Can't find directory \"%s\" in cdpath"),
- ff_file_to_find);
- else
- EMSG2(_("E345: Can't find file \"%s\" in path"),
- ff_file_to_find);
- } else {
- if (find_what == FINDFILE_DIR)
- EMSG2(_("E346: No more directory \"%s\" found in cdpath"),
- ff_file_to_find);
- else
- EMSG2(_("E347: No more file \"%s\" found in path"),
- ff_file_to_find);
- }
- }
-
-theend:
- return file_name;
-}
-
-
/*
* Change directory to "new_dir". If FEAT_SEARCHPATH is defined, search
* 'cdpath' for relative directory names, otherwise just mch_chdir().
diff --git a/src/misc2.h b/src/misc2.h
index 4fabd49001..20046b69fb 100644
--- a/src/misc2.h
+++ b/src/misc2.h
@@ -91,23 +91,6 @@ void crypt_decode __ARGS((char_u *ptr, long len));
void crypt_init_keys __ARGS((char_u *passwd));
void free_crypt_key __ARGS((char_u *key));
char_u *get_crypt_key __ARGS((int store, int twice));
-void *vim_findfile_init __ARGS((char_u *path, char_u *filename, char_u *
- stopdirs, int level, int free_visited,
- int find_what, void *search_ctx_arg,
- int tagfile,
- char_u *rel_fname));
-char_u *vim_findfile_stopdir __ARGS((char_u *buf));
-void vim_findfile_cleanup __ARGS((void *ctx));
-char_u *vim_findfile __ARGS((void *search_ctx_arg));
-void vim_findfile_free_visited __ARGS((void *search_ctx_arg));
-char_u *find_file_in_path __ARGS((char_u *ptr, int len, int options, int first,
- char_u *rel_fname));
-char_u *find_directory_in_path __ARGS((char_u *ptr, int len, int options,
- char_u *rel_fname));
-char_u *find_file_in_path_option __ARGS((char_u *ptr, int len, int options,
- int first, char_u *path_option,
- int find_what, char_u *rel_fname,
- char_u *suffixes));
int vim_chdir __ARGS((char_u *new_dir));
int get_user_name __ARGS((char_u *buf, int len));
void sort_strings __ARGS((char_u **files, int count));
diff --git a/src/tag.c b/src/tag.c
index 5fb80724bb..3eed09031d 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -29,6 +29,7 @@
#include "message.h"
#include "misc1.h"
#include "misc2.h"
+#include "file_search.h"
#include "garray.h"
#include "move.h"
#include "option.h"
diff --git a/src/window.c b/src/window.c
index a3347ed0f1..2e906d633b 100644
--- a/src/window.c
+++ b/src/window.c
@@ -29,6 +29,7 @@
#include "message.h"
#include "misc1.h"
#include "misc2.h"
+#include "file_search.h"
#include "garray.h"
#include "move.h"
#include "normal.h"