summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-02-13 23:13:28 +0100
committerBram Moolenaar <Bram@vim.org>2019-02-13 23:13:28 +0100
commit5fd0f5052f9a312bb4cfe7b4176b1211d45127ee (patch)
treeb82e59bfdcf65662e44803dabbfa77c11010bb70
parent688b3983d8b321e0d32dd51914fa474a0988daf6 (diff)
downloadvim-git-8.1.0914.tar.gz
patch 8.1.0914: code related to findfile() is spread outv8.1.0914
Problem: Code related to findfile() is spread out. Solution: Put findfile() related code into a new source file. (Yegappan Lakshmanan, closes #3934)
-rw-r--r--Filelist2
-rw-r--r--src/Make_bc5.mak1
-rw-r--r--src/Make_cyg_ming.mak1
-rw-r--r--src/Make_dice.mak4
-rw-r--r--src/Make_ivc.mak5
-rw-r--r--src/Make_manx.mak6
-rw-r--r--src/Make_morph.mak1
-rw-r--r--src/Make_mvc.mak4
-rw-r--r--src/Make_sas.mak5
-rw-r--r--src/Make_vms.mms40
-rw-r--r--src/Makefile11
-rw-r--r--src/README.txt2
-rw-r--r--src/findfile.c2607
-rw-r--r--src/misc1.c528
-rw-r--r--src/misc2.c1926
-rw-r--r--src/proto.h1
-rw-r--r--src/proto/findfile.pro18
-rw-r--r--src/proto/misc1.pro5
-rw-r--r--src/proto/misc2.pro8
-rw-r--r--src/proto/window.pro7
-rw-r--r--src/version.c2
-rw-r--r--src/window.c315
22 files changed, 2775 insertions, 2724 deletions
diff --git a/Filelist b/Filelist
index cd9cbf311..211b48767 100644
--- a/Filelist
+++ b/Filelist
@@ -41,6 +41,7 @@ SRC_ALL = \
src/farsi.h \
src/feature.h \
src/fileio.c \
+ src/findfile.c \
src/fold.c \
src/getchar.c \
src/globals.h \
@@ -170,6 +171,7 @@ SRC_ALL = \
src/proto/ex_getln.pro \
src/proto/farsi.pro \
src/proto/fileio.pro \
+ src/proto/findfile.pro \
src/proto/fold.pro \
src/proto/getchar.pro \
src/proto/gui.pro \
diff --git a/src/Make_bc5.mak b/src/Make_bc5.mak
index b2977e700..eb38dd7cc 100644
--- a/src/Make_bc5.mak
+++ b/src/Make_bc5.mak
@@ -544,6 +544,7 @@ vimobj = \
$(OBJDIR)\ex_getln.obj \
$(OBJDIR)\farsi.obj \
$(OBJDIR)\fileio.obj \
+ $(OBJDIR)\findfile.obj \
$(OBJDIR)\fold.obj \
$(OBJDIR)\getchar.obj \
$(OBJDIR)\hardcopy.obj \
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index 1e26adda5..48ce3974d 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -716,6 +716,7 @@ OBJ = \
$(OUTDIR)/ex_getln.o \
$(OUTDIR)/farsi.o \
$(OUTDIR)/fileio.o \
+ $(OUTDIR)/findfile.o \
$(OUTDIR)/fold.o \
$(OUTDIR)/getchar.o \
$(OUTDIR)/hardcopy.o \
diff --git a/src/Make_dice.mak b/src/Make_dice.mak
index 57aed477b..ffce805d3 100644
--- a/src/Make_dice.mak
+++ b/src/Make_dice.mak
@@ -46,6 +46,7 @@ SRC = \
ex_getln.c \
farsi.c \
fileio.c \
+ findfile.c \
fold.c \
getchar.c \
hardcopy.c \
@@ -105,6 +106,7 @@ OBJ = o/arabic.o \
o/ex_getln.o \
o/farsi.o \
o/fileio.o \
+ o/findfile.o \
o/fold.o \
o/getchar.o \
o/hardcopy.o \
@@ -203,6 +205,8 @@ o/farsi.o: farsi.c $(SYMS)
o/fileio.o: fileio.c $(SYMS)
+o/findfile.o: findfile.c $(SYMS)
+
o/fold.o: fold.c $(SYMS)
o/getchar.o: getchar.c $(SYMS)
diff --git a/src/Make_ivc.mak b/src/Make_ivc.mak
index 96d6a47b5..a02ba960f 100644
--- a/src/Make_ivc.mak
+++ b/src/Make_ivc.mak
@@ -230,6 +230,7 @@ LINK32_OBJS= \
"$(INTDIR)/ex_getln.obj" \
"$(INTDIR)/farsi.obj" \
"$(INTDIR)/fileio.obj" \
+ "$(INTDIR)/findfile.obj" \
"$(INTDIR)/fold.obj" \
"$(INTDIR)/getchar.obj" \
"$(INTDIR)/hardcopy.obj" \
@@ -419,6 +420,10 @@ SOURCE=.\farsi.c
SOURCE=.\fileio.c
# End Source File
# Begin Source File
+#
+SOURCE=.\findfile.c
+# End Source File
+# Begin Source File
SOURCE=.\fold.c
# End Source File
diff --git a/src/Make_manx.mak b/src/Make_manx.mak
index a44ad6558..5314a86a1 100644
--- a/src/Make_manx.mak
+++ b/src/Make_manx.mak
@@ -56,6 +56,7 @@ SRC = arabic.c \
ex_getln.c \
farsi.c \
fileio.c \
+ findfile.c \
fold.c \
getchar.c \
hardcopy.c \
@@ -117,6 +118,7 @@ OBJ = obj/arabic.o \
obj/ex_getln.o \
obj/farsi.o \
obj/fileio.o \
+ obj/findfile.o \
obj/fold.o \
obj/getchar.o \
obj/hardcopy.o \
@@ -176,6 +178,7 @@ PRO = proto/arabic.pro \
proto/ex_getln.pro \
proto/farsi.pro \
proto/fileio.pro \
+ proto/findfile.pro \
proto/fold.pro \
proto/getchar.pro \
proto/hardcopy.pro \
@@ -320,6 +323,9 @@ obj/farsi.o: farsi.c
obj/fileio.o: fileio.c
$(CCSYM) $@ fileio.c
+obj/findfile.o: findfile.c
+ $(CCSYM) $@ findfile.c
+
obj/fold.o: fold.c
$(CCSYM) $@ fold.c
diff --git a/src/Make_morph.mak b/src/Make_morph.mak
index 6bcae1a0b..a6e0dae17 100644
--- a/src/Make_morph.mak
+++ b/src/Make_morph.mak
@@ -44,6 +44,7 @@ SRC = arabic.c \
ex_getln.c \
farsi.c \
fileio.c \
+ findfile.c \
fold.c \
getchar.c \
hardcopy.c \
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index 52deb549c..c948f3771 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -721,6 +721,7 @@ OBJ = \
$(OUTDIR)\ex_getln.obj \
$(OUTDIR)\farsi.obj \
$(OUTDIR)\fileio.obj \
+ $(OUTDIR)\findfile.obj \
$(OUTDIR)\fold.obj \
$(OUTDIR)\getchar.obj \
$(OUTDIR)\hardcopy.obj \
@@ -1407,6 +1408,8 @@ $(OUTDIR)/farsi.obj: $(OUTDIR) farsi.c $(INCL)
$(OUTDIR)/fileio.obj: $(OUTDIR) fileio.c $(INCL)
+$(OUTDIR)/findfile.obj: $(OUTDIR) findfile.c $(INCL)
+
$(OUTDIR)/fold.obj: $(OUTDIR) fold.c $(INCL)
$(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL)
@@ -1645,6 +1648,7 @@ proto.h: \
proto/ex_getln.pro \
proto/farsi.pro \
proto/fileio.pro \
+ proto/findfile.pro \
proto/getchar.pro \
proto/hardcopy.pro \
proto/hashtab.pro \
diff --git a/src/Make_sas.mak b/src/Make_sas.mak
index e7faf5677..deaa5eb5d 100644
--- a/src/Make_sas.mak
+++ b/src/Make_sas.mak
@@ -109,6 +109,7 @@ SRC = \
ex_getln.c \
farsi.c \
fileio.c \
+ findfile.c \
fold.c \
getchar.c \
hardcopy.c \
@@ -169,6 +170,7 @@ OBJ = \
ex_getln.o \
farsi.o \
fileio.o \
+ findfile.o \
fold.o \
getchar.o \
hardcopy.o \
@@ -229,6 +231,7 @@ PRO = \
proto/ex_getln.pro \
proto/farsi.pro \
proto/fileio.pro \
+ proto/findfile.pro \
proto/fold.pro \
proto/getchar.pro \
proto/hardcopy.pro \
@@ -363,6 +366,8 @@ farsi.o: farsi.c
proto/farsi.pro: farsi.c
fileio.o: fileio.c
proto/fileio.pro: fileio.c
+findfile.o: findfile.c
+proto/findfile.pro: findfile.c
fold.o: fold.c
proto/fold.pro: fold.c
getchar.o: getchar.c
diff --git a/src/Make_vms.mms b/src/Make_vms.mms
index 1a21d15fb..92f5ab12e 100644
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -312,23 +312,31 @@ ALL_CFLAGS_VER = /def=($(MODEL_DEF)$(DEFS)$(DEBUG_DEF)$(PERL_DEF)$(PYTHON_DEF) -
ALL_LIBS = $(LIBS) $(GUI_LIB_DIR) $(GUI_LIB) \
$(PERL_LIB) $(PYTHON_LIB) $(TCL_LIB) $(RUBY_LIB)
-SRC = arabic.c autocmd.c beval.c blob.c blowfish.c buffer.c charset.c crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c \
- evalfunc.c ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c if_cscope.c if_xcmdsrv.c farsi.c fileio.c fold.c \
- getchar.c hardcopy.c hashtab.c indent.c json.c list.c main.c mark.c menu.c mbyte.c memfile.c memline.c message.c misc1.c \
- misc2.c move.c normal.c ops.c option.c popupmnu.c quickfix.c regexp.c search.c sha256.c sign.c \
- spell.c spellfile.c syntax.c tag.c term.c termlib.c textprop.c ui.c undo.c userfunc.c version.c screen.c \
- window.c os_unix.c os_vms.c pathdef.c \
+SRC = arabic.c autocmd.c beval.c blob.c blowfish.c buffer.c charset.c \
+ crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c evalfunc.c \
+ ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c if_cscope.c \
+ if_xcmdsrv.c farsi.c fileio.c findfile.c fold.c getchar.c hardcopy.c \
+ hashtab.c indent.c json.c list.c main.c mark.c menu.c mbyte.c \
+ memfile.c memline.c message.c misc1.c misc2.c move.c normal.c ops.c \
+ option.c popupmnu.c quickfix.c regexp.c search.c sha256.c sign.c \
+ spell.c spellfile.c syntax.c tag.c term.c termlib.c textprop.c ui.c \
+ undo.c userfunc.c version.c screen.c window.c os_unix.c os_vms.c \
+ pathdef.c
$(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \
$(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC)
-OBJ = arabic.obj autocmd.obj beval.obj blob.obj blowfish.obj buffer.obj charset.obj crypt.obj crypt_zip.obj dict.obj diff.obj digraph.obj \
- edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj ex_eval.obj ex_getln.obj if_cscope.obj \
- if_xcmdsrv.obj farsi.obj fileio.obj fold.obj getchar.obj hardcopy.obj hashtab.obj indent.obj json.obj list.obj main.obj mark.obj \
- menu.obj memfile.obj memline.obj message.obj misc1.obj misc2.obj \
- move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj quickfix.obj \
- regexp.obj search.obj sha256.obj sign.obj spell.obj spellfile.obj syntax.obj tag.obj term.obj termlib.obj textprop.obj \
- ui.obj undo.obj userfunc.obj screen.obj version.obj window.obj os_unix.obj \
- os_vms.obj pathdef.obj if_mzsch.obj\
+OBJ = arabic.obj autocmd.obj beval.obj blob.obj blowfish.obj buffer.obj \
+ charset.obj crypt.obj crypt_zip.obj dict.obj diff.obj digraph.obj \
+ edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj \
+ ex_eval.obj ex_getln.obj if_cscope.obj if_xcmdsrv.obj farsi.obj \
+ fileio.obj findfile.obj fold.obj getchar.obj hardcopy.obj hashtab.obj \
+ indent.obj json.obj list.obj main.obj mark.obj menu.obj memfile.obj \
+ memline.obj message.obj misc1.obj misc2.obj move.obj mbyte.obj \
+ normal.obj ops.obj option.obj popupmnu.obj quickfix.obj regexp.obj \
+ search.obj sha256.obj sign.obj spell.obj spellfile.obj syntax.obj \
+ tag.obj term.obj termlib.obj textprop.obj ui.obj undo.obj \
+ userfunc.obj screen.obj version.obj window.obj os_unix.obj os_vms.obj \
+ pathdef.obj if_mzsch.obj \
$(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \
$(RUBY_OBJ) $(HANGULIN_OBJ) $(MZSCH_OBJ) $(XDIFF_OBJ)
@@ -568,6 +576,10 @@ fileio.obj : fileio.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h term.h macros.h structs.h regexp.h \
gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
globals.h farsi.h arabic.h
+findfile.obj : findfile.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h term.h macros.h structs.h regexp.h \
+ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
+ globals.h farsi.h arabic.h
fold.obj : fold.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
[.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h farsi.h \
diff --git a/src/Makefile b/src/Makefile
index 8b83e9540..60828ad16 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1592,6 +1592,7 @@ BASIC_SRC = \
ex_getln.c \
farsi.c \
fileio.c \
+ findfile.c \
fold.c \
getchar.c \
hardcopy.c \
@@ -1705,6 +1706,7 @@ OBJ_COMMON = \
objects/ex_getln.o \
objects/farsi.o \
objects/fileio.o \
+ objects/findfile.o \
objects/fold.o \
objects/getchar.o \
objects/hardcopy.o \
@@ -1831,6 +1833,7 @@ PRO_AUTO = \
ex_getln.pro \
farsi.pro \
fileio.pro \
+ findfile.pro \
fold.pro \
getchar.pro \
hardcopy.pro \
@@ -2999,6 +3002,9 @@ objects/farsi.o: farsi.c
objects/fileio.o: fileio.c
$(CCC) -o $@ fileio.c
+objects/findfile.o: findfile.c
+ $(CCC) -o $@ findfile.c
+
objects/fold.o: fold.c
$(CCC) -o $@ fold.c
@@ -3471,6 +3477,11 @@ objects/fileio.o: fileio.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h
+objects/findfile.o: findfile.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h farsi.h arabic.h libvterm/include/vterm.h \
+ libvterm/include/vterm_keycodes.h
objects/fold.o: fold.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
diff --git a/src/README.txt b/src/README.txt
index e1dc36f69..52edeb73e 100644
--- a/src/README.txt
+++ b/src/README.txt
@@ -22,8 +22,10 @@ Most code can be found in a file with an obvious name (incomplete list):
diff.c diff mode (vimdiff)
eval.c expression evaluation
fileio.c reading and writing files
+ findfile.c search for files in 'path'
fold.c folding
getchar.c getting characters and key mapping
+ indent.c C and Lisp indentation
mark.c marks
mbyte.c multi-byte character handling
memfile.c storing lines for buffers in a swapfile
diff --git a/src/findfile.c b/src/findfile.c
new file mode 100644
index 000000000..9730b35a4
--- /dev/null
+++ b/src/findfile.c
@@ -0,0 +1,2607 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * findfile.c: Search for files in directories listed in 'path'
+ */
+
+#include "vim.h"
+
+/*
+ * 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;
+#ifdef FEAT_PATH_EXTRA
+ char_u *ffs_wc_path;
+#endif
+
+ // 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;
+
+#ifdef FEAT_PATH_EXTRA
+ // Visited directories are different if the wildcard string are
+ // different. So we have to save it.
+ char_u *ffv_wc_path;
+#endif
+ // 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;
+#ifdef FEAT_PATH_EXTRA
+ char_u *ffsc_wc_path;
+ int ffsc_level;
+ char_u **ffsc_stopdirs_v;
+#endif
+ int ffsc_find_what;
+ int ffsc_tagfile;
+} ff_search_ctx_T;
+
+// locally needed functions
+#ifdef FEAT_PATH_EXTRA
+static int ff_check_visited(ff_visited_T **, char_u *, char_u *);
+#else
+static int ff_check_visited(ff_visited_T **, char_u *);
+#endif
+static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp);
+static void ff_free_visited_list(ff_visited_T *vl);
+static ff_visited_list_hdr_T* ff_get_visited_list(char_u *, ff_visited_list_hdr_T **list_headp);
+
+static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr);
+static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx);
+static void ff_clear(ff_search_ctx_T *search_ctx);
+static void ff_free_stack_element(ff_stack_T *stack_ptr);
+#ifdef FEAT_PATH_EXTRA
+static ff_stack_T *ff_create_stack_element(char_u *, char_u *, int, int);
+#else
+static ff_stack_T *ff_create_stack_element(char_u *, int, int);
+#endif
+#ifdef FEAT_PATH_EXTRA
+static int ff_path_in_stoplist(char_u *, int, char_u **);
+#endif
+
+static char_u e_pathtoolong[] = N_("E854: path too long for completion");
+
+static char_u *ff_expand_buffer = NULL; // used for expanding filenames
+
+#if 0
+/*
+ * if someone likes findfirst/findnext, here are the functions
+ * NOT TESTED!!
+ */
+
+static void *ff_fn_search_context = NULL;
+
+ char_u *
+vim_findfirst(char_u *path, char_u *filename, int level)
+{
+ ff_fn_search_context =
+ vim_findfile_init(path, filename, NULL, level, TRUE, FALSE,
+ ff_fn_search_context, rel_fname);
+ if (NULL == ff_fn_search_context)
+ return NULL;
+ else
+ return vim_findnext()
+}
+
+ char_u *
+vim_findnext(void)
+{
+ char_u *ret = vim_findfile(ff_fn_search_context);
+
+ if (NULL == ret)
+ {
+ vim_findfile_cleanup(ff_fn_search_context);
+ ff_fn_search_context = NULL;
+ }
+ return ret;
+}
+#endif
+
+/*
+ * 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 UNUSED,
+ 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 "."
+{
+#ifdef FEAT_PATH_EXTRA
+ char_u *wc_part;
+#endif
+ 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
+ }
+
+#ifdef FEAT_PATH_EXTRA
+ /*
+ * 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;
+ }
+ }
+#endif
+
+#ifdef FEAT_PATH_EXTRA
+ 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))
+ {
+ semsg(_("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
+#endif
+ 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);
+ }
+#ifdef FEAT_PATH_EXTRA
+ 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;
+ }
+ }
+#endif
+ vim_free(buf);
+ }
+
+ sptr = ff_create_stack_element(ff_expand_buffer,
+#ifdef FEAT_PATH_EXTRA
+ search_ctx->ffsc_wc_path,
+#endif
+ 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;
+}
+
+#if defined(FEAT_PATH_EXTRA) || defined(PROTO)
+/*
+ * 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;
+}
+#endif
+
+/*
+ * 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;
+#ifdef FEAT_PATH_EXTRA
+ char_u *rest_of_wildcards;
+ char_u *path_end = NULL;
+#endif
+ ff_stack_T *stackp;
+#if defined(FEAT_SEARCHPATH) || defined(FEAT_PATH_EXTRA)
+ int len;
+#endif
+ int i;
+ char_u *p;
+#ifdef FEAT_SEARCHPATH
+ char_u *suf;
+#endif
+ 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;
+
+#ifdef FEAT_PATH_EXTRA
+ // 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)];
+#endif
+
+#ifdef FEAT_PATH_EXTRA
+ // upward search loop
+ for (;;)
+ {
+#endif
+ // 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
+#ifdef FEAT_PATH_EXTRA
+ , stackp->ffs_wc_path
+#endif
+ ) == FAIL)
+ {
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5)
+ {
+ verbose_enter_scroll();
+ smsg("Already Searched: %s (%s)",
+ stackp->ffs_fix_path, stackp->ffs_wc_path);
+ // don't overwrite this either
+ msg_puts("\n");
+ verbose_leave_scroll();
+ }
+#endif
+ ff_free_stack_element(stackp);
+ continue;
+ }
+#ifdef FF_VERBOSE
+ else if (p_verbose >= 5)
+ {
+ verbose_enter_scroll();
+ smsg("Searching: %s (%s)",
+ stackp->ffs_fix_path, stackp->ffs_wc_path);
+ // don't overwrite this either
+ msg_puts("\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)
+ {
+ if (STRLEN(search_ctx->ffsc_start_dir) + 1 < MAXPATHL)
+ {
+ STRCPY(file_path, search_ctx->ffsc_start_dir);
+ add_pathsep(file_path);
+ }
+ else
+ {
+ ff_free_stack_element(stackp);
+ goto fail;
+ }
+ }
+
+ // append the fix part of the search path
+ if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1
+ < MAXPATHL)
+ {
+ STRCAT(file_path, stackp->ffs_fix_path);
+ add_pathsep(file_path);
+ }
+ else
+ {
+ ff_free_stack_element(stackp);
+ goto fail;
+ }
+
+#ifdef FEAT_PATH_EXTRA
+ 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)--;
+ if (len + 1 < MAXPATHL)
+ file_path[len++] = '*';
+ else
+ {
+ ff_free_stack_element(stackp);
+ goto fail;
+ }
+ }
+
+ 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))
+ if (len + 1 < MAXPATHL)
+ file_path[len++] = *rest_of_wildcards++;
+ else
+ {
+ ff_free_stack_element(stackp);
+ goto fail;
+ }
+
+ file_path[len] = NUL;
+ if (vim_ispathsep(*rest_of_wildcards))
+ rest_of_wildcards++;
+ }
+#endif
+
+ /*
+ * 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;
+ }
+#ifdef FEAT_PATH_EXTRA
+ else
+ rest_of_wildcards = &stackp->ffs_wc_path[
+ STRLEN(stackp->ffs_wc_path)];
+#endif
+
+ if (stackp->ffs_stage == 0)
+ {
+ // this is the first time we work on this directory
+#ifdef FEAT_PATH_EXTRA
+ if (*rest_of_wildcards == NUL)
+#endif
+ {
+ /*
+ * 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
+ if (STRLEN(stackp->ffs_filearray[i]) + 1
+ + STRLEN(search_ctx->ffsc_file_to_search)
+ < MAXPATHL)
+ {
+ STRCPY(file_path, stackp->ffs_filearray[i]);
+ add_pathsep(file_path);
+ STRCAT(file_path, search_ctx->ffsc_file_to_search);
+ }
+ else
+ {
+ ff_free_stack_element(stackp);
+ goto fail;
+ }
+
+ /*
+ * Try without extra suffix and then with suffixes
+ * from 'suffixesadd'.
+ */
+#ifdef FEAT_SEARCHPATH
+ len = (int)STRLEN(file_path);
+ if (search_ctx->ffsc_tagfile)
+ suf = (char_u *)"";
+ else
+ suf = curbuf->b_p_sua;
+ for (;;)
+#endif
+ {
+ // 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
+#ifdef FEAT_PATH_EXTRA
+ , (char_u *)""
+#endif
+ ) == OK)
+#endif
+ )
+ {
+#ifdef FF_VERBOSE
+ if (ff_check_visited(
+ &search_ctx->ffsc_visited_list->ffvl_visited_list,
+ file_path
+#ifdef FEAT_PATH_EXTRA
+ , (char_u *)""
+#endif
+ ) == FAIL)
+ {
+ if (p_verbose >= 5)
+ {
+ verbose_enter_scroll();
+ smsg("Already: %s",
+ file_path);
+ // don't overwrite this either
+ msg_puts("\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("HIT: %s", file_path);
+ // don't overwrite this either
+ msg_puts("\n");
+ verbose_leave_scroll();
+ }
+#endif
+ return file_path;
+ }
+
+#ifdef FEAT_SEARCHPATH
+ // Not found or found already, try next suffix.
+ if (*suf == NUL)
+ break;
+ copy_option_part(&suf, file_path + len,
+ MAXPATHL - len, ",");
+#endif
+ }
+ }
+ }
+#ifdef FEAT_PATH_EXTRA
+ 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));
+ }
+ }
+#endif
+ stackp->ffs_filearray_cur = 0;
+ stackp->ffs_stage = 1;
+ }
+
+#ifdef FEAT_PATH_EXTRA
+ /*
+ * 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));
+ }
+ }
+#endif
+
+ // we are done with the current directory
+ ff_free_stack_element(stackp);
+
+ }
+
+#ifdef FEAT_PATH_EXTRA
+ // 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;
+
+ if (STRLEN(search_ctx->ffsc_start_dir) + 1
+ + STRLEN(search_ctx->ffsc_fix_path) < MAXPATHL)
+ {
+ STRCPY(file_path, search_ctx->ffsc_start_dir);
+ add_pathsep(file_path);
+ STRCAT(file_path, search_ctx->ffsc_fix_path);
+ }
+ else
+ goto fail;
+
+ // 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;
+ }
+#endif
+
+fail:
+ 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;
+#ifdef FEAT_PATH_EXTRA
+ vim_free(vl->ffv_wc_path);
+#endif
+ 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("ff_get_visited_list: FOUND list for %s",
+ filename);
+ // don't overwrite this either
+ msg_puts("\n");
+ verbose_leave_scroll();
+ }
+#endif
+ return retptr;
+ }
+ retptr = retptr->ffvl_next;
+ }
+ }
+
+#ifdef FF_VERBOSE
+ if (p_verbose >= 5)
+ {
+ verbose_enter_scroll();
+ smsg("ff_get_visited_list: new list for %s", filename);
+ // don't overwrite this either
+ msg_puts("\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;
+}
+
+#ifdef FEAT_PATH_EXTRA
+/*
+ * 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, j;
+ int c1 = NUL;
+ int c2 = NUL;
+ int prev1 = NUL;
+ int prev2 = NUL;
+
+ if (s1 == s2)
+ return TRUE;
+
+ if (s1 == NULL || s2 == NULL)
+ return FALSE;
+
+ for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;)
+ {
+ c1 = PTR2CHAR(s1 + i);
+ c2 = PTR2CHAR(s2 + j);
+
+ if ((p_fic ? MB_TOLOWER(c1) != MB_TOLOWER(c2) : c1 != c2)
+ && (prev1 != '*' || prev2 != '*'))
+ return FALSE;
+ prev2 = prev1;
+ prev1 = c1;
+
+ i += MB_PTR2LEN(s1 + i);
+ j += MB_PTR2LEN(s2 + j);
+ }
+ return s1[i] == s2[j];
+}
+#endif
+
+/*
+ * 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
+#ifdef FEAT_PATH_EXTRA
+ , char_u *wc_path
+#endif
+ )
+{
+ ff_visited_T *vp;
+#ifdef UNIX
+ stat_T 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
+ )
+ {
+#ifdef FEAT_PATH_EXTRA
+ // are the wildcard parts equal
+ if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE)
+#endif
+ // 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
+#ifdef FEAT_PATH_EXTRA
+ if (wc_path != NULL)
+ vp->ffv_wc_path = vim_strsave(wc_path);
+ else
+ vp->ffv_wc_path = NULL;
+#endif
+
+ 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,
+#ifdef FEAT_PATH_EXTRA
+ char_u *wc_part,
+#endif
+ 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);
+
+#ifdef FEAT_PATH_EXTRA
+ if (wc_part == NULL)
+ wc_part = (char_u *)"";
+ new->ffs_wc_path = vim_strsave(wc_part);
+#endif
+
+ if (new->ffs_fix_path == NULL
+#ifdef FEAT_PATH_EXTRA
+ || new->ffs_wc_path == NULL
+#endif
+ )
+ {
+ 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);
+#ifdef FEAT_PATH_EXTRA
+ vim_free(stack_ptr->ffs_wc_path);
+#endif
+
+ 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);
+#ifdef FEAT_PATH_EXTRA
+ vim_free(search_ctx->ffsc_wc_path);
+#endif
+
+#ifdef FEAT_PATH_EXTRA
+ 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;
+#endif
+
+ // reset everything
+ search_ctx->ffsc_file_to_search = NULL;
+ search_ctx->ffsc_start_dir = NULL;
+ search_ctx->ffsc_fix_path = NULL;
+#ifdef FEAT_PATH_EXTRA
+ search_ctx->ffsc_wc_path = NULL;
+ search_ctx->ffsc_level = 0;
+#endif
+}
+
+#ifdef FEAT_PATH_EXTRA
+/*
+ * 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;
+}
+#endif
+
+#if defined(FEAT_SEARCHPATH) || defined(PROTO)
+/*
+ * 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) || defined(PROTO)
+ 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
+ * FNAME_UNESC unescape backslashes.
+ *
+ * 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;
+# ifdef AMIGA
+ struct Process *proc = (struct Process *)FindTask(0L);
+ APTR save_winptr = proc->pr_WindowPtr;
+
+ // Avoid a requester here for a volume that doesn't exist.
+ proc->pr_WindowPtr = (APTR)-1L;
+# endif
+
+ if (first == TRUE)
+ {
+ // copy file name into NameBuff, expanding environment variables
+ save_char = ptr[len];
+ ptr[len] = NUL;
+ expand_env_esc(ptr, NameBuff, MAXPATHL, FALSE, TRUE, NULL);
+ 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;
+ }
+ if (options & FNAME_UNESC)
+ {
+ // Change all "\ " to " ".
+ for (ptr = ff_file_to_find; *ptr != NUL; ++ptr)
+ if (ptr[0] == '\\' && ptr[1] == ' ')
+ mch_memmove(ptr, ptr + 1, STRLEN(ptr));
+ }
+ }
+
+ 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
+# if defined(MSWIN)
+ // handle "\tmp" as absolute path
+ || vim_ispathsep(ff_file_to_find[0])
+ // handle "c:name" as absolute path
+ || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
+# endif
+# ifdef AMIGA
+ // handle ":tmp" as absolute path
+ || ff_file_to_find[0] == ':'
+# endif
+ )
+ {
+ /*
+ * 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, " ,");
+
+# ifdef FEAT_PATH_EXTRA
+ // get the stopdir string
+ r_ptr = vim_findfile_stopdir(buf);
+# else
+ r_ptr = NULL;
+# endif
+ 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)
+ semsg(_("E344: Can't find directory \"%s\" in cdpath"),
+ ff_file_to_find);
+ else
+ semsg(_("E345: Can't find file \"%s\" in path"),
+ ff_file_to_find);
+ }
+ else
+ {
+ if (find_what == FINDFILE_DIR)
+ semsg(_("E346: No more directory \"%s\" found in cdpath"),
+ ff_file_to_find);
+ else
+ semsg(_("E347: No more file \"%s\" found in path"),
+ ff_file_to_find);
+ }
+ }
+
+theend:
+# ifdef AMIGA
+ proc->pr_WindowPtr = save_winptr;
+# endif
+ return file_name;
+}
+
+/*
+ * Get the file name at the cursor.
+ * If Visual mode is active, use the selected text if it's in one line.
+ * Returns the name in allocated memory, NULL for failure.
+ */
+ char_u *
+grab_file_name(long count, linenr_T *file_lnum)
+{
+ int options = FNAME_MESS|FNAME_EXP|FNAME_REL|FNAME_UNESC;
+
+ if (VIsual_active)
+ {
+ int len;
+ char_u *ptr;
+
+ if (get_visual_text(NULL, &ptr, &len) == FAIL)
+ return NULL;
+ return find_file_name_in_path(ptr, len, options,
+ count, curbuf->b_ffname);
+ }
+ return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
+}
+
+/*
+ * Return the file name under or after the cursor.
+ *
+ * The 'path' option is searched if the file name is not absolute.
+ * The string returned has been alloc'ed and should be freed by the caller.
+ * NULL is returned if the file name or file is not found.
+ *
+ * options:
+ * FNAME_MESS give error messages
+ * FNAME_EXP expand to path
+ * FNAME_HYP check for hypertext link
+ * FNAME_INCL apply "includeexpr"
+ */
+ char_u *
+file_name_at_cursor(int options, long count, linenr_T *file_lnum)
+{
+ return file_name_in_line(ml_get_curline(),
+ curwin->w_cursor.col, options, count, curbuf->b_ffname,
+ file_lnum);
+}
+
+/*
+ * Return the name of the file under or after ptr[col].
+ * Otherwise like file_name_at_cursor().
+ */
+ char_u *
+file_name_in_line(
+ char_u *line,
+ int col,
+ int options,
+ long count,
+ char_u *rel_fname, // file we are searching relative to
+ linenr_T *file_lnum) // line number after the file name
+{
+ char_u *ptr;
+ int len;
+ int in_type = TRUE;
+ int is_url = FALSE;
+
+ /*
+ * search forward for what could be the start of a file name
+ */
+ ptr = line + col;
+ while (*ptr != NUL && !vim_isfilec(*ptr))
+ MB_PTR_ADV(ptr);
+ if (*ptr == NUL) // nothing found
+ {
+ if (options & FNAME_MESS)
+ emsg(_("E446: No file name under cursor"));
+ return NULL;
+ }
+
+ /*
+ * Search backward for first char of the file name.
+ * Go one char back to ":" before "//" even when ':' is not in 'isfname'.
+ */
+ while (ptr > line)
+ {
+ if (has_mbyte && (len = (*mb_head_off)(line, ptr - 1)) > 0)
+ ptr -= len + 1;
+ else if (vim_isfilec(ptr[-1])
+ || ((options & FNAME_HYP) && path_is_url(ptr - 1)))
+ --ptr;
+ else
+ break;
+ }
+
+ /*
+ * Search forward for the last char of the file name.
+ * Also allow "://" when ':' is not in 'isfname'.
+ */
+ len = 0;
+ while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
+ || ((options & FNAME_HYP) && path_is_url(ptr + len))
+ || (is_url && vim_strchr((char_u *)"?&=", ptr[len]) != NULL))
+ {
+ // After type:// we also include ?, & and = as valid characters, so that
+ // http://google.com?q=this&that=ok works.
+ if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z'))
+ {
+ if (in_type && path_is_url(ptr + len + 1))
+ is_url = TRUE;
+ }
+ else
+ in_type = FALSE;
+
+ if (ptr[len] == '\\')
+ // Skip over the "\" in "\ ".
+ ++len;
+ if (has_mbyte)
+ len += (*mb_ptr2len)(ptr + len);
+ else
+ ++len;
+ }
+
+ /*
+ * If there is trailing punctuation, remove it.
+ * But don't remove "..", could be a directory name.
+ */
+ if (len > 2 && vim_strchr((char_u *)".,:;!", ptr[len - 1]) != NULL
+ && ptr[len - 2] != '.')
+ --len;
+
+ if (file_lnum != NULL)
+ {
+ char_u *p;
+
+ // Get the number after the file name and a separator character
+ p = ptr + len;
+ p = skipwhite(p);
+ if (*p != NUL)
+ {
+ if (!isdigit(*p))
+ ++p; // skip the separator
+ p = skipwhite(p);
+ if (isdigit(*p))
+ *file_lnum = (int)getdigits(&p);
+ }
+ }
+
+ return find_file_name_in_path(ptr, len, options, count, rel_fname);
+}
+
+# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
+ static char_u *
+eval_includeexpr(char_u *ptr, int len)
+{
+ char_u *res;
+
+ set_vim_var_string(VV_FNAME, ptr, len);
+ res = eval_to_string_safe(curbuf->b_p_inex, NULL,
+ was_set_insecurely((char_u *)"includeexpr", OPT_LOCAL));
+ set_vim_var_string(VV_FNAME, NULL, 0);
+ return res;
+}
+# endif
+
+/*
+ * Return the name of the file ptr[len] in 'path'.
+ * Otherwise like file_name_at_cursor().
+ */
+ char_u *
+find_file_name_in_path(
+ char_u *ptr,
+ int len,
+ int options,
+ long count,
+ char_u *rel_fname) // file we are searching relative to
+{
+ char_u *file_name;
+ int c;
+# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
+ char_u *tofree = NULL;
+
+ if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
+ {
+ tofree = eval_includeexpr(ptr, len);
+ if (tofree != NULL)
+ {
+ ptr = tofree;
+ len = (int)STRLEN(ptr);
+ }
+ }
+# endif
+
+ if (options & FNAME_EXP)
+ {
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ TRUE, rel_fname);
+
+# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
+ /*
+ * If the file could not be found in a normal way, try applying
+ * 'includeexpr' (unless done already).
+ */
+ if (file_name == NULL
+ && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
+ {
+ tofree = eval_includeexpr(ptr, len);
+ if (tofree != NULL)
+ {
+ ptr = tofree;
+ len = (int)STRLEN(ptr);
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ TRUE, rel_fname);
+ }
+ }
+# endif
+ if (file_name == NULL && (options & FNAME_MESS))
+ {
+ c = ptr[len];
+ ptr[len] = NUL;
+ semsg(_("E447: Can't find file \"%s\" in path"), ptr);
+ ptr[len] = c;
+ }
+
+ // Repeat finding the file "count" times. This matters when it
+ // appears several times in the path.
+ while (file_name != NULL && --count > 0)
+ {
+ vim_free(file_name);
+ file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname);
+ }
+ }
+ else
+ file_name = vim_strnsave(ptr, len);
+
+# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
+ vim_free(tofree);
+# endif
+
+ return file_name;
+}
+
+/*
+ * Return the end of the directory name, on the first path
+ * separator:
+ * "/path/file", "/path/dir/", "/path//dir", "/file"
+ * ^ ^ ^ ^
+ */
+ static char_u *
+gettail_dir(char_u *fname)
+{
+ char_u *dir_end = fname;
+ char_u *next_dir_end = fname;
+ int look_for_sep = TRUE;
+ char_u *p;
+
+ for (p = fname; *p != NUL; )
+ {
+ if (vim_ispathsep(*p))
+ {
+ if (look_for_sep)
+ {
+ next_dir_end = p;
+ look_for_sep = FALSE;
+ }
+ }
+ else
+ {
+ if (!look_for_sep)
+ dir_end = next_dir_end;
+ look_for_sep = TRUE;
+ }
+ MB_PTR_ADV(p);
+ }
+ return dir_end;
+}
+
+/*
+ * return TRUE if 'c' is a path list separator.
+ */
+ int
+vim_ispathlistsep(int c)
+{
+# ifdef UNIX
+ return (c == ':');
+# else
+ return (c == ';'); // might not be right for every system...
+# endif
+}
+
+/*
+ * Moves "*psep" back to the previous path separator in "path".
+ * Returns FAIL is "*psep" ends up at the beginning of "path".
+ */
+ static int
+find_previous_pathsep(char_u *path, char_u **psep)
+{
+ // skip the current separator
+ if (*psep > path && vim_ispathsep(**psep))
+ --*psep;
+
+ // find the previous separator
+ while (*psep > path)
+ {
+ if (vim_ispathsep(**psep))
+ return OK;
+ MB_PTR_BACK(path, *psep);
+ }
+
+ return FAIL;
+}
+
+/*
+ * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap".
+ * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
+ */
+ static int
+is_unique(char_u *maybe_unique, garray_T *gap, int i)
+{
+ int j;
+ int candidate_len;
+ int other_path_len;
+ char_u **other_paths = (char_u **)gap->ga_data;
+ char_u *rival;
+
+ for (j = 0; j < gap->ga_len; j++)
+ {
+ if (j == i)
+ continue; // don't compare it with itself
+
+ candidate_len = (int)STRLEN(maybe_unique);
+ other_path_len = (int)STRLEN(other_paths[j]);
+ if (other_path_len < candidate_len)
+ continue; // it's different when it's shorter
+
+ rival = other_paths[j] + other_path_len - candidate_len;
+ if (fnamecmp(maybe_unique, rival) == 0
+ && (rival == other_paths[j] || vim_ispathsep(*(rival - 1))))
+ return FALSE; // match
+ }
+
+ return TRUE; // no match found
+}
+
+/*
+ * Split the 'path' option into an array of strings in garray_T. Relative
+ * paths are expanded to their equivalent fullpath. This includes the "."
+ * (relative to current buffer directory) and empty path (relative to current
+ * directory) notations.
+ *
+ * TODO: handle upward search (;) and path limiter (**N) notations by
+ * expanding each into their equivalent path(s).
+ */
+ static void
+expand_path_option(char_u *curdir, garray_T *gap)
+{
+ char_u *path_option = *curbuf->b_p_path == NUL
+ ? p_path : curbuf->b_p_path;
+ char_u *buf;
+ char_u *p;
+ int len;
+
+ if ((buf = alloc((int)MAXPATHL)) == NULL)
+ return;
+
+ while (*path_option != NUL)
+ {
+ copy_option_part(&path_option, buf, MAXPATHL, " ,");
+
+ if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1])))
+ {
+ // Relative to current buffer:
+ // "/path/file" + "." -> "/path/"
+ // "/path/file" + "./subdir" -> "/path/subdir"
+ if (curbuf->b_ffname == NULL)
+ continue;
+ p = gettail(curbuf->b_ffname);
+ len = (int)(p - curbuf->b_ffname);
+ if (len + (int)STRLEN(buf) >= MAXPATHL)
+ continue;
+ if (buf[1] == NUL)
+ buf[len] = NUL;
+ else
+ STRMOVE(buf + len, buf + 2);
+ mch_memmove(buf, curbuf->b_ffname, len);
+ simplify_filename(buf);
+ }
+ else if (buf[0] == NUL)
+ // relative to current directory
+ STRCPY(buf, curdir);
+ else if (path_with_url(buf))
+ // URL can't be used here
+ continue;
+ else if (!mch_isFullName(buf))
+ {
+ // Expand relative path to their full path equivalent
+ len = (int)STRLEN(curdir);
+ if (len + (int)STRLEN(buf) + 3 > MAXPATHL)
+ continue;
+ STRMOVE(buf + len + 1, buf);
+ STRCPY(buf, curdir);
+ buf[len] = PATHSEP;
+ simplify_filename(buf);
+ }
+
+ if (ga_grow(gap, 1) == FAIL)
+ break;
+
+# if defined(MSWIN)
+ // Avoid the path ending in a backslash, it fails when a comma is
+ // appended.
+ len = (int)STRLEN(buf);
+ if (buf[len - 1] == '\\')
+ buf[len - 1] = '/';
+# endif
+
+ p = vim_strsave(buf);
+ if (p == NULL)
+ break;
+ ((char_u **)gap->ga_data)[gap->ga_len++] = p;
+ }
+
+ vim_free(buf);
+}
+
+/*
+ * Returns a pointer to the file or directory name in "fname" that matches the
+ * longest path in "ga"p, or NULL if there is no match. For example:
+ *
+ * path: /foo/bar/baz
+ * fname: /foo/bar/baz/quux.txt
+ * returns: ^this
+ */
+ static char_u *
+get_path_cutoff(char_u *fname, garray_T *gap)
+{
+ int i;
+ int maxlen = 0;
+ char_u **path_part = (char_u **)gap->ga_data;
+ char_u *cutoff = NULL;
+
+ for (i = 0; i < gap->ga_len; i++)
+ {
+ int j = 0;
+
+ while ((fname[j] == path_part[i][j]
+# if defined(MSWIN)
+ || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j]))
+# endif
+ ) && fname[j] != NUL && path_part[i][j] != NUL)
+ j++;
+ if (j > maxlen)
+ {
+ maxlen = j;
+ cutoff = &fname[j];
+ }
+ }
+
+ // skip to the file or directory name
+ if (cutoff != NULL)
+ while (vim_ispathsep(*cutoff))
+ MB_PTR_ADV(cutoff);
+
+ return cutoff;
+}
+
+/*
+ * Sorts, removes duplicates and modifies all the fullpath names in "gap" so
+ * that they are unique with respect to each other while conserving the part
+ * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len".
+ */
+ void
+uniquefy_paths(garray_T *gap, char_u *pattern)
+{
+ int i;
+ int len;
+ char_u **fnames = (char_u **)gap->ga_data;
+ int sort_again = FALSE;
+ char_u *pat;
+ char_u *file_pattern;
+ char_u *curdir;
+ regmatch_T regmatch;
+ garray_T path_ga;
+ char_u **in_curdir = NULL;
+ char_u *short_name;
+
+ remove_duplicates(gap);
+ ga_init2(&path_ga, (int)sizeof(char_u *), 1);
+
+ /*
+ * We need to prepend a '*' at the beginning of file_pattern so that the
+ * regex matches anywhere in the path. FIXME: is this valid for all
+ * possible patterns?
+ */
+ len = (int)STRLEN(pattern);
+ file_pattern = alloc(len + 2);
+ if (file_pattern == NULL)
+ return;
+ file_pattern[0] = '*';
+ file_pattern[1] = NUL;
+ STRCAT(file_pattern, pattern);
+ pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, TRUE);
+ vim_free(file_pattern);
+ if (pat == NULL)
+ return;
+
+ regmatch.rm_ic = TRUE; // always ignore case
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ vim_free(pat);
+ if (regmatch.regprog == NULL)
+ return;
+
+ if ((curdir = alloc((int)(MAXPATHL))) == NULL)
+ goto theend;
+ mch_dirname(curdir, MAXPATHL);
+ expand_path_option(curdir, &path_ga);
+
+ in_curdir = (char_u **)alloc_clear(gap->ga_len * sizeof(char_u *));
+ if (in_curdir == NULL)
+ goto theend;
+
+ for (i = 0; i < gap->ga_len && !got_int; i++)
+ {
+ char_u *path = fnames[i];
+ int is_in_curdir;
+ char_u *dir_end = gettail_dir(path);
+ char_u *pathsep_p;
+ char_u *path_cutoff;
+
+ len = (int)STRLEN(path);
+ is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0
+ && curdir[dir_end - path] == NUL;
+ if (is_in_curdir)
+ in_curdir[i] = vim_strsave(path);
+
+ // Shorten the filename while maintaining its uniqueness
+ path_cutoff = get_path_cutoff(path, &path_ga);
+
+ // Don't assume all files can be reached without path when search
+ // pattern starts with star star slash, so only remove path_cutoff
+ // when possible.
+ if (pattern[0] == '*' && pattern[1] == '*'
+ && vim_ispathsep_nocolon(pattern[2])
+ && path_cutoff != NULL
+ && vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
+ && is_unique(path_cutoff, gap, i))
+ {
+ sort_again = TRUE;
+ mch_memmove(path, path_cutoff, STRLEN(path_cutoff) + 1);
+ }
+ else
+ {
+ // Here all files can be reached without path, so get shortest
+ // unique path. We start at the end of the path.
+ pathsep_p = path + len - 1;
+
+ while (find_previous_pathsep(path, &pathsep_p))
+ if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
+ && is_unique(pathsep_p + 1, gap, i)
+ && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff)
+ {
+ sort_again = TRUE;
+ mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
+ break;
+ }
+ }
+
+ if (mch_isFullName(path))
+ {
+ /*
+ * Last resort: shorten relative to curdir if possible.
+ * 'possible' means:
+ * 1. It is under the current directory.
+ * 2. The result is actually shorter than the original.
+ *
+ * Before curdir After
+ * /foo/bar/file.txt /foo/bar ./file.txt
+ * c:\foo\bar\file.txt c:\foo\bar .\file.txt
+ * /file.txt / /file.txt
+ * c:\file.txt c:\ .\file.txt
+ */
+ short_name = shorten_fname(path, curdir);
+ if (short_name != NULL && short_name > path + 1
+# if defined(MSWIN)
+ // On windows,
+ // shorten_fname("c:\a\a.txt", "c:\a\b")
+ // returns "\a\a.txt", which is not really the short
+ // name, hence:
+ && !vim_ispathsep(*short_name)
+# endif
+ )
+ {
+ STRCPY(path, ".");
+ add_pathsep(path);
+ STRMOVE(path + STRLEN(path), short_name);
+ }
+ }
+ ui_breakcheck();
+ }
+
+ // Shorten filenames in /in/current/directory/{filename}
+ for (i = 0; i < gap->ga_len && !got_int; i++)
+ {
+ char_u *rel_path;
+ char_u *path = in_curdir[i];
+
+ if (path == NULL)
+ continue;
+
+ // If the {filename} is not unique, change it to ./{filename}.
+ // Else reduce it to {filename}
+ short_name = shorten_fname(path, curdir);
+ if (short_name == NULL)
+ short_name = path;
+ if (is_unique(short_name, gap, i))
+ {
+ STRCPY(fnames[i], short_name);
+ continue;
+ }
+
+ rel_path = alloc((int)(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2));
+ if (rel_path == NULL)
+ goto theend;
+ STRCPY(rel_path, ".");
+ add_pathsep(rel_path);
+ STRCAT(rel_path, short_name);
+
+ vim_free(fnames[i]);
+ fnames[i] = rel_path;
+ sort_again = TRUE;
+ ui_breakcheck();
+ }
+
+theend:
+ vim_free(curdir);
+ if (in_curdir != NULL)
+ {
+ for (i = 0; i < gap->ga_len; i++)
+ vim_free(in_curdir[i]);
+ vim_free(in_curdir);
+ }
+ ga_clear_strings(&path_ga);
+ vim_regfree(regmatch.regprog);
+
+ if (sort_again)
+ remove_duplicates(gap);
+}
+
+/*
+ * Calls globpath() with 'path' values for the given pattern and stores the
+ * result in "gap".
+ * Returns the total number of matches.
+ */
+ int
+expand_in_path(
+ garray_T *gap,
+ char_u *pattern,
+ int flags) // EW_* flags
+{
+ char_u *curdir;
+ garray_T path_ga;
+ char_u *paths = NULL;
+ int glob_flags = 0;
+
+ if ((curdir = alloc((unsigned)MAXPATHL)) == NULL)
+ return 0;
+ mch_dirname(curdir, MAXPATHL);
+
+ ga_init2(&path_ga, (int)sizeof(char_u *), 1);
+ expand_path_option(curdir, &path_ga);
+ vim_free(curdir);
+ if (path_ga.ga_len == 0)
+ return 0;
+
+ paths = ga_concat_strings(&path_ga, ",");
+ ga_clear_strings(&path_ga);
+ if (paths == NULL)
+ return 0;
+
+ if (flags & EW_ICASE)
+ glob_flags |= WILD_ICASE;
+ if (flags & EW_ADDSLASH)
+ glob_flags |= WILD_ADD_SLASH;
+ globpath(paths, pattern, gap, glob_flags);
+ vim_free(paths);
+
+ return gap->ga_len;
+}
+
+#endif // FEAT_SEARCHPATH
diff --git a/src/misc1.c b/src/misc1.c
index 26b570bcb..1d957908e 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -21,6 +21,9 @@
static char_u *vim_version_dir(char_u *vimdir);
static char_u *remove_tail(char_u *p, char_u *pend, char_u *name);
+#define URL_SLASH 1 /* path_is_url() has found "://" */
+#define URL_BACKSLASH 2 /* path_is_url() has found ":\\" */
+
/* All user names (for ~user completion as done by shell). */
#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
static garray_T ga_users;
@@ -5023,43 +5026,6 @@ gettail(char_u *fname)
return p1;
}
-#if defined(FEAT_SEARCHPATH)
-/*
- * Return the end of the directory name, on the first path
- * separator:
- * "/path/file", "/path/dir/", "/path//dir", "/file"
- * ^ ^ ^ ^
- */
- static char_u *
-gettail_dir(char_u *fname)
-{
- char_u *dir_end = fname;
- char_u *next_dir_end = fname;
- int look_for_sep = TRUE;
- char_u *p;
-
- for (p = fname; *p != NUL; )
- {
- if (vim_ispathsep(*p))
- {
- if (look_for_sep)
- {
- next_dir_end = p;
- look_for_sep = FALSE;
- }
- }
- else
- {
- if (!look_for_sep)
- dir_end = next_dir_end;
- look_for_sep = TRUE;
- }
- MB_PTR_ADV(p);
- }
- return dir_end;
-}
-#endif
-
/*
* Get pointer to tail of "fname", including path separators. Putting a NUL
* here leaves the directory name. Takes care of "c:/" and "//".
@@ -5165,21 +5131,6 @@ vim_ispathsep_nocolon(int c)
;
}
-#if defined(FEAT_SEARCHPATH) || defined(PROTO)
-/*
- * return TRUE if 'c' is a path list separator.
- */
- int
-vim_ispathlistsep(int c)
-{
-#ifdef UNIX
- return (c == ':');
-#else
- return (c == ';'); /* might not be right for every system... */
-#endif
-}
-#endif
-
/*
* Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
* It's done in-place.
@@ -6183,407 +6134,6 @@ unix_expandpath(
}
#endif
-#if defined(FEAT_SEARCHPATH)
-/*
- * Moves "*psep" back to the previous path separator in "path".
- * Returns FAIL is "*psep" ends up at the beginning of "path".
- */
- static int
-find_previous_pathsep(char_u *path, char_u **psep)
-{
- /* skip the current separator */
- if (*psep > path && vim_ispathsep(**psep))
- --*psep;
-
- /* find the previous separator */
- while (*psep > path)
- {
- if (vim_ispathsep(**psep))
- return OK;
- MB_PTR_BACK(path, *psep);
- }
-
- return FAIL;
-}
-
-/*
- * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap".
- * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
- */
- static int
-is_unique(char_u *maybe_unique, garray_T *gap, int i)
-{
- int j;
- int candidate_len;
- int other_path_len;
- char_u **other_paths = (char_u **)gap->ga_data;
- char_u *rival;
-
- for (j = 0; j < gap->ga_len; j++)
- {
- if (j == i)
- continue; /* don't compare it with itself */
-
- candidate_len = (int)STRLEN(maybe_unique);
- other_path_len = (int)STRLEN(other_paths[j]);
- if (other_path_len < candidate_len)
- continue; /* it's different when it's shorter */
-
- rival = other_paths[j] + other_path_len - candidate_len;
- if (fnamecmp(maybe_unique, rival) == 0
- && (rival == other_paths[j] || vim_ispathsep(*(rival - 1))))
- return FALSE; /* match */
- }
-
- return TRUE; /* no match found */
-}
-
-/*
- * Split the 'path' option into an array of strings in garray_T. Relative
- * paths are expanded to their equivalent fullpath. This includes the "."
- * (relative to current buffer directory) and empty path (relative to current
- * directory) notations.
- *
- * TODO: handle upward search (;) and path limiter (**N) notations by
- * expanding each into their equivalent path(s).
- */
- static void
-expand_path_option(char_u *curdir, garray_T *gap)
-{
- char_u *path_option = *curbuf->b_p_path == NUL
- ? p_path : curbuf->b_p_path;
- char_u *buf;
- char_u *p;
- int len;
-
- if ((buf = alloc((int)MAXPATHL)) == NULL)
- return;
-
- while (*path_option != NUL)
- {
- copy_option_part(&path_option, buf, MAXPATHL, " ,");
-
- if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1])))
- {
- /* Relative to current buffer:
- * "/path/file" + "." -> "/path/"
- * "/path/file" + "./subdir" -> "/path/subdir" */
- if (curbuf->b_ffname == NULL)
- continue;
- p = gettail(curbuf->b_ffname);
- len = (int)(p - curbuf->b_ffname);
- if (len + (int)STRLEN(buf) >= MAXPATHL)
- continue;
- if (buf[1] == NUL)
- buf[len] = NUL;
- else
- STRMOVE(buf + len, buf + 2);
- mch_memmove(buf, curbuf->b_ffname, len);
- simplify_filename(buf);
- }
- else if (buf[0] == NUL)
- /* relative to current directory */
- STRCPY(buf, curdir);
- else if (path_with_url(buf))
- /* URL can't be used here */
- continue;
- else if (!mch_isFullName(buf))
- {
- /* Expand relative path to their full path equivalent */
- len = (int)STRLEN(curdir);
- if (len + (int)STRLEN(buf) + 3 > MAXPATHL)
- continue;
- STRMOVE(buf + len + 1, buf);
- STRCPY(buf, curdir);
- buf[len] = PATHSEP;
- simplify_filename(buf);
- }
-
- if (ga_grow(gap, 1) == FAIL)
- break;
-
-# if defined(MSWIN)
- /* Avoid the path ending in a backslash, it fails when a comma is
- * appended. */
- len = (int)STRLEN(buf);
- if (buf[len - 1] == '\\')
- buf[len - 1] = '/';
-# endif
-
- p = vim_strsave(buf);
- if (p == NULL)
- break;
- ((char_u **)gap->ga_data)[gap->ga_len++] = p;
- }
-
- vim_free(buf);
-}
-
-/*
- * Returns a pointer to the file or directory name in "fname" that matches the
- * longest path in "ga"p, or NULL if there is no match. For example:
- *
- * path: /foo/bar/baz
- * fname: /foo/bar/baz/quux.txt
- * returns: ^this
- */
- static char_u *
-get_path_cutoff(char_u *fname, garray_T *gap)
-{
- int i;
- int maxlen = 0;
- char_u **path_part = (char_u **)gap->ga_data;
- char_u *cutoff = NULL;
-
- for (i = 0; i < gap->ga_len; i++)
- {
- int j = 0;
-
- while ((fname[j] == path_part[i][j]
-# if defined(MSWIN)
- || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j]))
-#endif
- ) && fname[j] != NUL && path_part[i][j] != NUL)
- j++;
- if (j > maxlen)
- {
- maxlen = j;
- cutoff = &fname[j];
- }
- }
-
- /* skip to the file or directory name */
- if (cutoff != NULL)
- while (vim_ispathsep(*cutoff))
- MB_PTR_ADV(cutoff);
-
- return cutoff;
-}
-
-/*
- * Sorts, removes duplicates and modifies all the fullpath names in "gap" so
- * that they are unique with respect to each other while conserving the part
- * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len".
- */
- static void
-uniquefy_paths(garray_T *gap, char_u *pattern)
-{
- int i;
- int len;
- char_u **fnames = (char_u **)gap->ga_data;
- int sort_again = FALSE;
- char_u *pat;
- char_u *file_pattern;
- char_u *curdir;
- regmatch_T regmatch;
- garray_T path_ga;
- char_u **in_curdir = NULL;
- char_u *short_name;
-
- remove_duplicates(gap);
- ga_init2(&path_ga, (int)sizeof(char_u *), 1);
-
- /*
- * We need to prepend a '*' at the beginning of file_pattern so that the
- * regex matches anywhere in the path. FIXME: is this valid for all
- * possible patterns?
- */
- len = (int)STRLEN(pattern);
- file_pattern = alloc(len + 2);
- if (file_pattern == NULL)
- return;
- file_pattern[0] = '*';
- file_pattern[1] = NUL;
- STRCAT(file_pattern, pattern);
- pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, TRUE);
- vim_free(file_pattern);
- if (pat == NULL)
- return;
-
- regmatch.rm_ic = TRUE; /* always ignore case */
- regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
- vim_free(pat);
- if (regmatch.regprog == NULL)
- return;
-
- if ((curdir = alloc((int)(MAXPATHL))) == NULL)
- goto theend;
- mch_dirname(curdir, MAXPATHL);
- expand_path_option(curdir, &path_ga);
-
- in_curdir = (char_u **)alloc_clear(gap->ga_len * sizeof(char_u *));
- if (in_curdir == NULL)
- goto theend;
-
- for (i = 0; i < gap->ga_len && !got_int; i++)
- {
- char_u *path = fnames[i];
- int is_in_curdir;
- char_u *dir_end = gettail_dir(path);
- char_u *pathsep_p;
- char_u *path_cutoff;
-
- len = (int)STRLEN(path);
- is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0
- && curdir[dir_end - path] == NUL;
- if (is_in_curdir)
- in_curdir[i] = vim_strsave(path);
-
- /* Shorten the filename while maintaining its uniqueness */
- path_cutoff = get_path_cutoff(path, &path_ga);
-
- /* Don't assume all files can be reached without path when search
- * pattern starts with star star slash, so only remove path_cutoff
- * when possible. */
- if (pattern[0] == '*' && pattern[1] == '*'
- && vim_ispathsep_nocolon(pattern[2])
- && path_cutoff != NULL
- && vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
- && is_unique(path_cutoff, gap, i))
- {
- sort_again = TRUE;
- mch_memmove(path, path_cutoff, STRLEN(path_cutoff) + 1);
- }
- else
- {
- /* Here all files can be reached without path, so get shortest
- * unique path. We start at the end of the path. */
- pathsep_p = path + len - 1;
-
- while (find_previous_pathsep(path, &pathsep_p))
- if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
- && is_unique(pathsep_p + 1, gap, i)
- && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff)
- {
- sort_again = TRUE;
- mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
- break;
- }
- }
-
- if (mch_isFullName(path))
- {
- /*
- * Last resort: shorten relative to curdir if possible.
- * 'possible' means:
- * 1. It is under the current directory.
- * 2. The result is actually shorter than the original.
- *
- * Before curdir After
- * /foo/bar/file.txt /foo/bar ./file.txt
- * c:\foo\bar\file.txt c:\foo\bar .\file.txt
- * /file.txt / /file.txt
- * c:\file.txt c:\ .\file.txt
- */
- short_name = shorten_fname(path, curdir);
- if (short_name != NULL && short_name > path + 1
-#if defined(MSWIN)
- /* On windows,
- * shorten_fname("c:\a\a.txt", "c:\a\b")
- * returns "\a\a.txt", which is not really the short
- * name, hence: */
- && !vim_ispathsep(*short_name)
-#endif
- )
- {
- STRCPY(path, ".");
- add_pathsep(path);
- STRMOVE(path + STRLEN(path), short_name);
- }
- }
- ui_breakcheck();
- }
-
- /* Shorten filenames in /in/current/directory/{filename} */
- for (i = 0; i < gap->ga_len && !got_int; i++)
- {
- char_u *rel_path;
- char_u *path = in_curdir[i];
-
- if (path == NULL)
- continue;
-
- /* If the {filename} is not unique, change it to ./{filename}.
- * Else reduce it to {filename} */
- short_name = shorten_fname(path, curdir);
- if (short_name == NULL)
- short_name = path;
- if (is_unique(short_name, gap, i))
- {
- STRCPY(fnames[i], short_name);
- continue;
- }
-
- rel_path = alloc((int)(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2));
- if (rel_path == NULL)
- goto theend;
- STRCPY(rel_path, ".");
- add_pathsep(rel_path);
- STRCAT(rel_path, short_name);
-
- vim_free(fnames[i]);
- fnames[i] = rel_path;
- sort_again = TRUE;
- ui_breakcheck();
- }
-
-theend:
- vim_free(curdir);
- if (in_curdir != NULL)
- {
- for (i = 0; i < gap->ga_len; i++)
- vim_free(in_curdir[i]);
- vim_free(in_curdir);
- }
- ga_clear_strings(&path_ga);
- vim_regfree(regmatch.regprog);
-
- if (sort_again)
- remove_duplicates(gap);
-}
-
-/*
- * Calls globpath() with 'path' values for the given pattern and stores the
- * result in "gap".
- * Returns the total number of matches.
- */
- static int
-expand_in_path(
- garray_T *gap,
- char_u *pattern,
- int flags) /* EW_* flags */
-{
- char_u *curdir;
- garray_T path_ga;
- char_u *paths = NULL;
- int glob_flags = 0;
-
- if ((curdir = alloc((unsigned)MAXPATHL)) == NULL)
- return 0;
- mch_dirname(curdir, MAXPATHL);
-
- ga_init2(&path_ga, (int)sizeof(char_u *), 1);
- expand_path_option(curdir, &path_ga);
- vim_free(curdir);
- if (path_ga.ga_len == 0)
- return 0;
-
- paths = ga_concat_strings(&path_ga, ",");
- ga_clear_strings(&path_ga);
- if (paths == NULL)
- return 0;
-
- if (flags & EW_ICASE)
- glob_flags |= WILD_ICASE;
- if (flags & EW_ADDSLASH)
- glob_flags |= WILD_ADD_SLASH;
- globpath(paths, pattern, gap, glob_flags);
- vim_free(paths);
-
- return gap->ga_len;
-}
-#endif
-
#if defined(FEAT_SEARCHPATH) || defined(FEAT_CMDL_COMPL) || defined(PROTO)
/*
* Sort "gap" and remove duplicate entries. "gap" is expected to contain a
@@ -7120,3 +6670,75 @@ get_isolated_shell_name(void)
#endif
return p;
}
+
+/*
+ * Check if the "://" of a URL is at the pointer, return URL_SLASH.
+ * Also check for ":\\", which MS Internet Explorer accepts, return
+ * URL_BACKSLASH.
+ */
+ int
+path_is_url(char_u *p)
+{
+ if (STRNCMP(p, "://", (size_t)3) == 0)
+ return URL_SLASH;
+ else if (STRNCMP(p, ":\\\\", (size_t)3) == 0)
+ return URL_BACKSLASH;
+ return 0;
+}
+
+/*
+ * Check if "fname" starts with "name://". Return URL_SLASH if it does.
+ * Return URL_BACKSLASH for "name:\\".
+ * Return zero otherwise.
+ */
+ int
+path_with_url(char_u *fname)
+{
+ char_u *p;
+
+ for (p = fname; isalpha(*p); ++p)
+ ;
+ return path_is_url(p);
+}
+
+/*
+ * Return TRUE if "name" is a full (absolute) path name or URL.
+ */
+ int
+vim_isAbsName(char_u *name)
+{
+ return (path_with_url(name) != 0 || mch_isFullName(name));
+}
+
+/*
+ * Get absolute file name into buffer "buf[len]".
+ *
+ * return FAIL for failure, OK otherwise
+ */
+ int
+vim_FullName(
+ char_u *fname,
+ char_u *buf,
+ int len,
+ int force) /* force expansion even when already absolute */
+{
+ int retval = OK;
+ int url;
+
+ *buf = NUL;
+ if (fname == NULL)
+ return FAIL;
+
+ url = path_with_url(fname);
+ if (!url)
+ retval = mch_FullName(fname, buf, len, force);
+ if (url || retval == FAIL)
+ {
+ /* something failed; use the file name (truncate when too long) */
+ vim_strncpy(buf, fname, len - 1);
+ }
+#if defined(MSWIN)
+ slash_adjust(buf);
+#endif
+ return retval;
+}
diff --git a/src/misc2.c b/src/misc2.c
index 278cc330c..d323fe512 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -14,8 +14,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(pos_T *pos, int addspaces, int finetune, colnr_T wcol);
/*
@@ -1047,10 +1045,6 @@ do_outofmem_msg(long_u size)
#if defined(EXITFREE) || defined(PROTO)
-# if defined(FEAT_SEARCHPATH)
-static void free_findfile(void);
-# endif
-
/*
* Free everything that we allocated.
* Can be used to detect memory leaks, e.g., with ccmalloc.
@@ -1161,7 +1155,6 @@ free_all_mem(void)
vim_free(new_last_cmdline);
# endif
set_keep_msg(NULL, 0);
- vim_free(ff_expand_buffer);
/* Clear cmdline history. */
p_hi = 0;
@@ -3822,1925 +3815,6 @@ update_mouseshape(int shape_idx)
#endif /* CURSOR_SHAPE */
-/* 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;
-#ifdef FEAT_PATH_EXTRA
- char_u *ffs_wc_path;
-#endif
-
- /* 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;
-
-#ifdef FEAT_PATH_EXTRA
- /* Visited directories are different if the wildcard string are
- * different. So we have to save it.
- */
- char_u *ffv_wc_path;
-#endif
- /* 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;
-#ifdef FEAT_PATH_EXTRA
- char_u *ffsc_wc_path;
- int ffsc_level;
- char_u **ffsc_stopdirs_v;
-#endif
- int ffsc_find_what;
- int ffsc_tagfile;
-} ff_search_ctx_T;
-
-/* locally needed functions */
-#ifdef FEAT_PATH_EXTRA
-static int ff_check_visited(ff_visited_T **, char_u *, char_u *);
-#else
-static int ff_check_visited(ff_visited_T **, char_u *);
-#endif
-static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp);
-static void ff_free_visited_list(ff_visited_T *vl);
-static ff_visited_list_hdr_T* ff_get_visited_list(char_u *, ff_visited_list_hdr_T **list_headp);
-
-static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr);
-static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx);
-static void ff_clear(ff_search_ctx_T *search_ctx);
-static void ff_free_stack_element(ff_stack_T *stack_ptr);
-#ifdef FEAT_PATH_EXTRA
-static ff_stack_T *ff_create_stack_element(char_u *, char_u *, int, int);
-#else
-static ff_stack_T *ff_create_stack_element(char_u *, int, int);
-#endif
-#ifdef FEAT_PATH_EXTRA
-static int ff_path_in_stoplist(char_u *, int, char_u **);
-#endif
-
-static char_u e_pathtoolong[] = N_("E854: path too long for completion");
-
-#if 0
-/*
- * if someone likes findfirst/findnext, here are the functions
- * NOT TESTED!!
- */
-
-static void *ff_fn_search_context = NULL;
-
- char_u *
-vim_findfirst(char_u *path, char_u *filename, int level)
-{
- ff_fn_search_context =
- vim_findfile_init(path, filename, NULL, level, TRUE, FALSE,
- ff_fn_search_context, rel_fname);
- if (NULL == ff_fn_search_context)
- return NULL;
- else
- return vim_findnext()
-}
-
- char_u *
-vim_findnext(void)
-{
- char_u *ret = vim_findfile(ff_fn_search_context);
-
- if (NULL == ret)
- {
- vim_findfile_cleanup(ff_fn_search_context);
- ff_fn_search_context = NULL;
- }
- return ret;
-}
-#endif
-
-/*
- * 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 UNUSED,
- 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 "." */
-{
-#ifdef FEAT_PATH_EXTRA
- char_u *wc_part;
-#endif
- 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
- }
-
-#ifdef FEAT_PATH_EXTRA
- /*
- * 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;
- }
- }
-#endif
-
-#ifdef FEAT_PATH_EXTRA
- 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))
- {
- semsg(_("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
-#endif
- 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);
- }
-#ifdef FEAT_PATH_EXTRA
- 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;
- }
- }
-#endif
- vim_free(buf);
- }
-
- sptr = ff_create_stack_element(ff_expand_buffer,
-#ifdef FEAT_PATH_EXTRA
- search_ctx->ffsc_wc_path,
-#endif
- 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;
-}
-
-#if defined(FEAT_PATH_EXTRA) || defined(PROTO)
-/*
- * 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;
-}
-#endif
-
-/*
- * 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;
-#ifdef FEAT_PATH_EXTRA
- char_u *rest_of_wildcards;
- char_u *path_end = NULL;
-#endif
- ff_stack_T *stackp;
-#if defined(FEAT_SEARCHPATH) || defined(FEAT_PATH_EXTRA)
- int len;
-#endif
- int i;
- char_u *p;
-#ifdef FEAT_SEARCHPATH
- char_u *suf;
-#endif
- 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;
-
-#ifdef FEAT_PATH_EXTRA
- /* 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)];
-#endif
-
-#ifdef FEAT_PATH_EXTRA
- /* upward search loop */
- for (;;)
- {
-#endif
- /* 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
-#ifdef FEAT_PATH_EXTRA
- , stackp->ffs_wc_path
-#endif
- ) == FAIL)
- {
-#ifdef FF_VERBOSE
- if (p_verbose >= 5)
- {
- verbose_enter_scroll();
- smsg("Already Searched: %s (%s)",
- stackp->ffs_fix_path, stackp->ffs_wc_path);
- /* don't overwrite this either */
- msg_puts("\n");
- verbose_leave_scroll();
- }
-#endif
- ff_free_stack_element(stackp);
- continue;
- }
-#ifdef FF_VERBOSE
- else if (p_verbose >= 5)
- {
- verbose_enter_scroll();
- smsg("Searching: %s (%s)",
- stackp->ffs_fix_path, stackp->ffs_wc_path);
- /* don't overwrite this either */
- msg_puts("\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)
- {
- if (STRLEN(search_ctx->ffsc_start_dir) + 1 < MAXPATHL)
- {
- STRCPY(file_path, search_ctx->ffsc_start_dir);
- add_pathsep(file_path);
- }
- else
- {
- ff_free_stack_element(stackp);
- goto fail;
- }
- }
-
- /* append the fix part of the search path */
- if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 < MAXPATHL)
- {
- STRCAT(file_path, stackp->ffs_fix_path);
- add_pathsep(file_path);
- }
- else
- {
- ff_free_stack_element(stackp);
- goto fail;
- }
-
-#ifdef FEAT_PATH_EXTRA
- 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)--;
- if (len + 1 < MAXPATHL)
- file_path[len++] = '*';
- else
- {
- ff_free_stack_element(stackp);
- goto fail;
- }
- }
-
- 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))
- if (len + 1 < MAXPATHL)
- file_path[len++] = *rest_of_wildcards++;
- else
- {
- ff_free_stack_element(stackp);
- goto fail;
- }
-
- file_path[len] = NUL;
- if (vim_ispathsep(*rest_of_wildcards))
- rest_of_wildcards++;
- }
-#endif
-
- /*
- * 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;
- }
-#ifdef FEAT_PATH_EXTRA
- else
- rest_of_wildcards = &stackp->ffs_wc_path[
- STRLEN(stackp->ffs_wc_path)];
-#endif
-
- if (stackp->ffs_stage == 0)
- {
- /* this is the first time we work on this directory */
-#ifdef FEAT_PATH_EXTRA
- if (*rest_of_wildcards == NUL)
-#endif
- {
- /*
- * 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 */
- if (STRLEN(stackp->ffs_filearray[i]) + 1
- + STRLEN(search_ctx->ffsc_file_to_search) < MAXPATHL)
- {
- STRCPY(file_path, stackp->ffs_filearray[i]);
- add_pathsep(file_path);
- STRCAT(file_path, search_ctx->ffsc_file_to_search);
- }
- else
- {
- ff_free_stack_element(stackp);
- goto fail;
- }
-
- /*
- * Try without extra suffix and then with suffixes
- * from 'suffixesadd'.
- */
-#ifdef FEAT_SEARCHPATH
- len = (int)STRLEN(file_path);
- if (search_ctx->ffsc_tagfile)
- suf = (char_u *)"";
- else
- suf = curbuf->b_p_sua;
- for (;;)
-#endif
- {
- /* 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
-#ifdef FEAT_PATH_EXTRA
- , (char_u *)""
-#endif
- ) == OK)
-#endif
- )
- {
-#ifdef FF_VERBOSE
- if (ff_check_visited(
- &search_ctx->ffsc_visited_list->ffvl_visited_list,
- file_path
-#ifdef FEAT_PATH_EXTRA
- , (char_u *)""
-#endif
- ) == FAIL)
- {
- if (p_verbose >= 5)
- {
- verbose_enter_scroll();
- smsg("Already: %s",
- file_path);
- /* don't overwrite this either */
- msg_puts("\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("HIT: %s", file_path);
- /* don't overwrite this either */
- msg_puts("\n");
- verbose_leave_scroll();
- }
-#endif
- return file_path;
- }
-
-#ifdef FEAT_SEARCHPATH
- /* Not found or found already, try next suffix. */
- if (*suf == NUL)
- break;
- copy_option_part(&suf, file_path + len,
- MAXPATHL - len, ",");
-#endif
- }
- }
- }
-#ifdef FEAT_PATH_EXTRA
- 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));
- }
- }
-#endif
- stackp->ffs_filearray_cur = 0;
- stackp->ffs_stage = 1;
- }
-
-#ifdef FEAT_PATH_EXTRA
- /*
- * 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));
- }
- }
-#endif
-
- /* we are done with the current directory */
- ff_free_stack_element(stackp);
-
- }
-
-#ifdef FEAT_PATH_EXTRA
- /* 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;
-
- if (STRLEN(search_ctx->ffsc_start_dir) + 1
- + STRLEN(search_ctx->ffsc_fix_path) < MAXPATHL)
- {
- STRCPY(file_path, search_ctx->ffsc_start_dir);
- add_pathsep(file_path);
- STRCAT(file_path, search_ctx->ffsc_fix_path);
- }
- else
- goto fail;
-
- /* 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;
- }
-#endif
-
-fail:
- 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;
-#ifdef FEAT_PATH_EXTRA
- vim_free(vl->ffv_wc_path);
-#endif
- 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("ff_get_visited_list: FOUND list for %s",
- filename);
- /* don't overwrite this either */
- msg_puts("\n");
- verbose_leave_scroll();
- }
-#endif
- return retptr;
- }
- retptr = retptr->ffvl_next;
- }
- }
-
-#ifdef FF_VERBOSE
- if (p_verbose >= 5)
- {
- verbose_enter_scroll();
- smsg("ff_get_visited_list: new list for %s", filename);
- /* don't overwrite this either */
- msg_puts("\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;
-}
-
-#ifdef FEAT_PATH_EXTRA
-/*
- * 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, j;
- int c1 = NUL;
- int c2 = NUL;
- int prev1 = NUL;
- int prev2 = NUL;
-
- if (s1 == s2)
- return TRUE;
-
- if (s1 == NULL || s2 == NULL)
- return FALSE;
-
- for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;)
- {
- c1 = PTR2CHAR(s1 + i);
- c2 = PTR2CHAR(s2 + j);
-
- if ((p_fic ? MB_TOLOWER(c1) != MB_TOLOWER(c2) : c1 != c2)
- && (prev1 != '*' || prev2 != '*'))
- return FALSE;
- prev2 = prev1;
- prev1 = c1;
-
- i += MB_PTR2LEN(s1 + i);
- j += MB_PTR2LEN(s2 + j);
- }
- return s1[i] == s2[j];
-}
-#endif
-
-/*
- * 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
-#ifdef FEAT_PATH_EXTRA
- , char_u *wc_path
-#endif
- )
-{
- ff_visited_T *vp;
-#ifdef UNIX
- stat_T 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
- )
- {
-#ifdef FEAT_PATH_EXTRA
- /* are the wildcard parts equal */
- if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE)
-#endif
- /* 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
-#ifdef FEAT_PATH_EXTRA
- if (wc_path != NULL)
- vp->ffv_wc_path = vim_strsave(wc_path);
- else
- vp->ffv_wc_path = NULL;
-#endif
-
- 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,
-#ifdef FEAT_PATH_EXTRA
- char_u *wc_part,
-#endif
- 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);
-
-#ifdef FEAT_PATH_EXTRA
- if (wc_part == NULL)
- wc_part = (char_u *)"";
- new->ffs_wc_path = vim_strsave(wc_part);
-#endif
-
- if (new->ffs_fix_path == NULL
-#ifdef FEAT_PATH_EXTRA
- || new->ffs_wc_path == NULL
-#endif
- )
- {
- 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);
-#ifdef FEAT_PATH_EXTRA
- vim_free(stack_ptr->ffs_wc_path);
-#endif
-
- 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);
-#ifdef FEAT_PATH_EXTRA
- vim_free(search_ctx->ffsc_wc_path);
-#endif
-
-#ifdef FEAT_PATH_EXTRA
- 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;
-#endif
-
- /* reset everything */
- search_ctx->ffsc_file_to_search = NULL;
- search_ctx->ffsc_start_dir = NULL;
- search_ctx->ffsc_fix_path = NULL;
-#ifdef FEAT_PATH_EXTRA
- search_ctx->ffsc_wc_path = NULL;
- search_ctx->ffsc_level = 0;
-#endif
-}
-
-#ifdef FEAT_PATH_EXTRA
-/*
- * 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;
-}
-#endif
-
-#if defined(FEAT_SEARCHPATH) || defined(PROTO)
-/*
- * 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
- * FNAME_UNESC unescape backslashes.
- *
- * 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;
-#ifdef AMIGA
- struct Process *proc = (struct Process *)FindTask(0L);
- APTR save_winptr = proc->pr_WindowPtr;
-
- /* Avoid a requester here for a volume that doesn't exist. */
- proc->pr_WindowPtr = (APTR)-1L;
-#endif
-
- if (first == TRUE)
- {
- /* copy file name into NameBuff, expanding environment variables */
- save_char = ptr[len];
- ptr[len] = NUL;
- expand_env_esc(ptr, NameBuff, MAXPATHL, FALSE, TRUE, NULL);
- 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;
- }
- if (options & FNAME_UNESC)
- {
- /* Change all "\ " to " ". */
- for (ptr = ff_file_to_find; *ptr != NUL; ++ptr)
- if (ptr[0] == '\\' && ptr[1] == ' ')
- mch_memmove(ptr, ptr + 1, STRLEN(ptr));
- }
- }
-
- 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
-#if defined(MSWIN)
- /* handle "\tmp" as absolute path */
- || vim_ispathsep(ff_file_to_find[0])
- /* handle "c:name" as absolute path */
- || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
-#endif
-#ifdef AMIGA
- /* handle ":tmp" as absolute path */
- || ff_file_to_find[0] == ':'
-#endif
- )
- {
- /*
- * 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, " ,");
-
-#ifdef FEAT_PATH_EXTRA
- /* get the stopdir string */
- r_ptr = vim_findfile_stopdir(buf);
-#else
- r_ptr = NULL;
-#endif
- 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)
- semsg(_("E344: Can't find directory \"%s\" in cdpath"),
- ff_file_to_find);
- else
- semsg(_("E345: Can't find file \"%s\" in path"),
- ff_file_to_find);
- }
- else
- {
- if (find_what == FINDFILE_DIR)
- semsg(_("E346: No more directory \"%s\" found in cdpath"),
- ff_file_to_find);
- else
- semsg(_("E347: No more file \"%s\" found in path"),
- ff_file_to_find);
- }
- }
-
-theend:
-#ifdef AMIGA
- proc->pr_WindowPtr = save_winptr;
-#endif
- return file_name;
-}
-
-#endif /* FEAT_SEARCHPATH */
-
/*
* Change directory to "new_dir". If FEAT_SEARCHPATH is defined, search
* 'cdpath' for relative directory names, otherwise just mch_chdir().
diff --git a/src/proto.h b/src/proto.h
index d7e63168e..1ab5cf78c 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -80,6 +80,7 @@ extern int _stricoll(char *a, char *b);
# include "ex_eval.pro"
# include "ex_getln.pro"
# include "fileio.pro"
+# include "findfile.pro"
# include "fold.pro"
# include "getchar.pro"
# ifdef FEAT_HANGULIN
diff --git a/src/proto/findfile.pro b/src/proto/findfile.pro
new file mode 100644
index 000000000..5b80b83c6
--- /dev/null
+++ b/src/proto/findfile.pro
@@ -0,0 +1,18 @@
+/* findfile.c */
+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, char_u *rel_fname);
+char_u *vim_findfile_stopdir(char_u *buf);
+void vim_findfile_cleanup(void *ctx);
+char_u *vim_findfile(void *search_ctx_arg);
+void vim_findfile_free_visited(void *search_ctx_arg);
+char_u *find_file_in_path(char_u *ptr, int len, int options, int first, char_u *rel_fname);
+void free_findfile(void);
+char_u *find_directory_in_path(char_u *ptr, int len, int options, char_u *rel_fname);
+char_u *find_file_in_path_option(char_u *ptr, int len, int options, int first, char_u *path_option, int find_what, char_u *rel_fname, char_u *suffixes);
+char_u *grab_file_name(long count, linenr_T *file_lnum);
+char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum);
+char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u *rel_fname, linenr_T *file_lnum);
+char_u *find_file_name_in_path(char_u *ptr, int len, int options, long count, char_u *rel_fname);
+int vim_ispathlistsep(int c);
+void uniquefy_paths(garray_T *gap, char_u *pattern);
+int expand_in_path(garray_T *gap, char_u *pattern, int flags);
+/* vim: set ft=c : */
diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro
index 1e50f8acd..6a2a5f114 100644
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -74,7 +74,6 @@ char_u *getnextcomp(char_u *fname);
char_u *get_past_head(char_u *path);
int vim_ispathsep(int c);
int vim_ispathsep_nocolon(int c);
-int vim_ispathlistsep(int c);
void shorten_dir(char_u *str);
int dir_of_file_exists(char_u *fname);
int vim_fnamecmp(char_u *x, char_u *y);
@@ -99,4 +98,8 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, int flags, int *ret_len);
void FreeWild(int count, char_u **files);
int goto_im(void);
char_u *get_isolated_shell_name(void);
+int path_is_url(char_u *p);
+int path_with_url(char_u *fname);
+int vim_isAbsName(char_u *name);
+int vim_FullName(char_u *fname, char_u *buf, int len, int force);
/* vim: set ft=c : */
diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro
index 7d33de8b6..3c88efb3a 100644
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -90,14 +90,6 @@ int vim_stat(const char *name, stat_T *stp);
char *parse_shape_opt(int what);
int get_shape_idx(int mouse);
void update_mouseshape(int shape_idx);
-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, char_u *rel_fname);
-char_u *vim_findfile_stopdir(char_u *buf);
-void vim_findfile_cleanup(void *ctx);
-char_u *vim_findfile(void *search_ctx_arg);
-void vim_findfile_free_visited(void *search_ctx_arg);
-char_u *find_file_in_path(char_u *ptr, int len, int options, int first, char_u *rel_fname);
-char_u *find_directory_in_path(char_u *ptr, int len, int options, char_u *rel_fname);
-char_u *find_file_in_path_option(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(char_u *new_dir);
int get_user_name(char_u *buf, int len);
void sort_strings(char_u **files, int count);
diff --git a/src/proto/window.pro b/src/proto/window.pro
index 27f77c475..0efff64eb 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -65,13 +65,6 @@ void win_comp_scroll(win_T *wp);
void command_height(void);
void last_status(int morewin);
int tabline_height(void);
-char_u *grab_file_name(long count, linenr_T *file_lnum);
-char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum);
-char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u *rel_fname, linenr_T *file_lnum);
-char_u *find_file_name_in_path(char_u *ptr, int len, int options, long count, char_u *rel_fname);
-int path_with_url(char_u *fname);
-int vim_isAbsName(char_u *name);
-int vim_FullName(char_u *fname, char_u *buf, int len, int force);
int min_rows(void);
int only_one_window(void);
void check_lnums(int do_curwin);
diff --git a/src/version.c b/src/version.c
index f60b9d62b..ed224a454 100644
--- a/src/version.c
+++ b/src/version.c
@@ -784,6 +784,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 914,
+/**/
913,
/**/
912,
diff --git a/src/window.c b/src/window.c
index f6e611535..7dc3848e4 100644
--- a/src/window.c
+++ b/src/window.c
@@ -9,7 +9,6 @@
#include "vim.h"
-static int path_is_url(char_u *p);
static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, long Prenum);
static void win_init(win_T *newp, win_T *oldp, int flags);
static void win_init_some(win_T *newp, win_T *oldp);
@@ -61,9 +60,6 @@ static int frame_check_width(frame_T *topfrp, int width);
static win_T *win_alloc(win_T *after, int hidden);
-#define URL_SLASH 1 /* path_is_url() has found "://" */
-#define URL_BACKSLASH 2 /* path_is_url() has found ":\\" */
-
#define NOWIN (win_T *)-1 /* non-existing window */
#define ROWS_AVAIL (Rows - p_ch - tabline_height())
@@ -6098,317 +6094,6 @@ tabline_height(void)
return 1;
}
-#if defined(FEAT_SEARCHPATH) || defined(PROTO)
-/*
- * Get the file name at the cursor.
- * If Visual mode is active, use the selected text if it's in one line.
- * Returns the name in allocated memory, NULL for failure.
- */
- char_u *
-grab_file_name(long count, linenr_T *file_lnum)
-{
- int options = FNAME_MESS|FNAME_EXP|FNAME_REL|FNAME_UNESC;
-
- if (VIsual_active)
- {
- int len;
- char_u *ptr;
-
- if (get_visual_text(NULL, &ptr, &len) == FAIL)
- return NULL;
- return find_file_name_in_path(ptr, len, options,
- count, curbuf->b_ffname);
- }
- return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
-}
-
-/*
- * Return the file name under or after the cursor.
- *
- * The 'path' option is searched if the file name is not absolute.
- * The string returned has been alloc'ed and should be freed by the caller.
- * NULL is returned if the file name or file is not found.
- *
- * options:
- * FNAME_MESS give error messages
- * FNAME_EXP expand to path
- * FNAME_HYP check for hypertext link
- * FNAME_INCL apply "includeexpr"
- */
- char_u *
-file_name_at_cursor(int options, long count, linenr_T *file_lnum)
-{
- return file_name_in_line(ml_get_curline(),
- curwin->w_cursor.col, options, count, curbuf->b_ffname,
- file_lnum);
-}
-
-/*
- * Return the name of the file under or after ptr[col].
- * Otherwise like file_name_at_cursor().
- */
- char_u *
-file_name_in_line(
- char_u *line,
- int col,
- int options,
- long count,
- char_u *rel_fname, /* file we are searching relative to */
- linenr_T *file_lnum) /* line number after the file name */
-{
- char_u *ptr;
- int len;
- int in_type = TRUE;
- int is_url = FALSE;
-
- /*
- * search forward for what could be the start of a file name
- */
- ptr = line + col;
- while (*ptr != NUL && !vim_isfilec(*ptr))
- MB_PTR_ADV(ptr);
- if (*ptr == NUL) /* nothing found */
- {
- if (options & FNAME_MESS)
- emsg(_("E446: No file name under cursor"));
- return NULL;
- }
-
- /*
- * Search backward for first char of the file name.
- * Go one char back to ":" before "//" even when ':' is not in 'isfname'.
- */
- while (ptr > line)
- {
- if (has_mbyte && (len = (*mb_head_off)(line, ptr - 1)) > 0)
- ptr -= len + 1;
- else if (vim_isfilec(ptr[-1])
- || ((options & FNAME_HYP) && path_is_url(ptr - 1)))
- --ptr;
- else
- break;
- }
-
- /*
- * Search forward for the last char of the file name.
- * Also allow "://" when ':' is not in 'isfname'.
- */
- len = 0;
- while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
- || ((options & FNAME_HYP) && path_is_url(ptr + len))
- || (is_url && vim_strchr((char_u *)"?&=", ptr[len]) != NULL))
- {
- /* After type:// we also include ?, & and = as valid characters, so that
- * http://google.com?q=this&that=ok works. */
- if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z'))
- {
- if (in_type && path_is_url(ptr + len + 1))
- is_url = TRUE;
- }
- else
- in_type = FALSE;
-
- if (ptr[len] == '\\')
- /* Skip over the "\" in "\ ". */
- ++len;
- if (has_mbyte)
- len += (*mb_ptr2len)(ptr + len);
- else
- ++len;
- }
-
- /*
- * If there is trailing punctuation, remove it.
- * But don't remove "..", could be a directory name.
- */
- if (len > 2 && vim_strchr((char_u *)".,:;!", ptr[len - 1]) != NULL
- && ptr[len - 2] != '.')
- --len;
-
- if (file_lnum != NULL)
- {
- char_u *p;
-
- /* Get the number after the file name and a separator character */
- p = ptr + len;
- p = skipwhite(p);
- if (*p != NUL)
- {
- if (!isdigit(*p))
- ++p; /* skip the separator */
- p = skipwhite(p);
- if (isdigit(*p))
- *file_lnum = (int)getdigits(&p);
- }
- }
-
- return find_file_name_in_path(ptr, len, options, count, rel_fname);
-}
-
-# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
- static char_u *
-eval_includeexpr(char_u *ptr, int len)
-{
- char_u *res;
-
- set_vim_var_string(VV_FNAME, ptr, len);
- res = eval_to_string_safe(curbuf->b_p_inex, NULL,
- was_set_insecurely((char_u *)"includeexpr", OPT_LOCAL));
- set_vim_var_string(VV_FNAME, NULL, 0);
- return res;
-}
-#endif
-
-/*
- * Return the name of the file ptr[len] in 'path'.
- * Otherwise like file_name_at_cursor().
- */
- char_u *
-find_file_name_in_path(
- char_u *ptr,
- int len,
- int options,
- long count,
- char_u *rel_fname) /* file we are searching relative to */
-{
- char_u *file_name;
- int c;
-# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
- char_u *tofree = NULL;
-
- if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
- {
- tofree = eval_includeexpr(ptr, len);
- if (tofree != NULL)
- {
- ptr = tofree;
- len = (int)STRLEN(ptr);
- }
- }
-# endif
-
- if (options & FNAME_EXP)
- {
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- TRUE, rel_fname);
-
-# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
- /*
- * If the file could not be found in a normal way, try applying
- * 'includeexpr' (unless done already).
- */
- if (file_name == NULL
- && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL)
- {
- tofree = eval_includeexpr(ptr, len);
- if (tofree != NULL)
- {
- ptr = tofree;
- len = (int)STRLEN(ptr);
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- TRUE, rel_fname);
- }
- }
-# endif
- if (file_name == NULL && (options & FNAME_MESS))
- {
- c = ptr[len];
- ptr[len] = NUL;
- semsg(_("E447: Can't find file \"%s\" in path"), ptr);
- ptr[len] = c;
- }
-
- /* Repeat finding the file "count" times. This matters when it
- * appears several times in the path. */
- while (file_name != NULL && --count > 0)
- {
- vim_free(file_name);
- file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname);
- }
- }
- else
- file_name = vim_strnsave(ptr, len);
-
-# if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
- vim_free(tofree);
-# endif
-
- return file_name;
-}
-#endif /* FEAT_SEARCHPATH */
-
-/*
- * Check if the "://" of a URL is at the pointer, return URL_SLASH.
- * Also check for ":\\", which MS Internet Explorer accepts, return
- * URL_BACKSLASH.
- */
- static int
-path_is_url(char_u *p)
-{
- if (STRNCMP(p, "://", (size_t)3) == 0)
- return URL_SLASH;
- else if (STRNCMP(p, ":\\\\", (size_t)3) == 0)
- return URL_BACKSLASH;
- return 0;
-}
-
-/*
- * Check if "fname" starts with "name://". Return URL_SLASH if it does.
- * Return URL_BACKSLASH for "name:\\".
- * Return zero otherwise.
- */
- int
-path_with_url(char_u *fname)
-{
- char_u *p;
-
- for (p = fname; isalpha(*p); ++p)
- ;
- return path_is_url(p);
-}
-
-/*
- * Return TRUE if "name" is a full (absolute) path name or URL.
- */
- int
-vim_isAbsName(char_u *name)
-{
- return (path_with_url(name) != 0 || mch_isFullName(name));
-}
-
-/*
- * Get absolute file name into buffer "buf[len]".
- *
- * return FAIL for failure, OK otherwise
- */
- int
-vim_FullName(
- char_u *fname,
- char_u *buf,
- int len,
- int force) /* force expansion even when already absolute */
-{
- int retval = OK;
- int url;
-
- *buf = NUL;
- if (fname == NULL)
- return FAIL;
-
- url = path_with_url(fname);
- if (!url)
- retval = mch_FullName(fname, buf, len, force);
- if (url || retval == FAIL)
- {
- /* something failed; use the file name (truncate when too long) */
- vim_strncpy(buf, fname, len - 1);
- }
-#if defined(MSWIN)
- slash_adjust(buf);
-#endif
- return retval;
-}
-
/*
* Return the minimal number of rows that is needed on the screen to display
* the current number of windows.