On Fri, Feb 27, 2009 at 3:00 AM, Craig Barkhouse <[email protected]> wrote:
> Windows definitely does have symbolic links, starting with Vista.
> (See: mklink /?) There are also junctions, which are like symbolic
> links but only for directories, starting with Win2K.
>
> Both of these are examples of reparse points. To detect if a file or
> directory is a reparse point, call GetFileAttributes or
> GetFileAttributesEx, and see if the FILE_ATTRIBUTE_REPARSE_POINT flag
> is set.
Thanks, Craig.
IIRC on windows platform symbolic link support comes with the NTFS
filesystem so it's been there since NT.
Attached patch (untested!) skips symbolic links to directory when
completing filenames in the 'path' setting to avoid cycle on windows
platform.
If anyone could verify that it works fine on windows too that would be
super.
Test setup:
1. Apply the patch to vim (svn r1390) and compile
2. Create this directory hierarchy: c:\a\b\c\d
3. Create a file in the d directory, say c:\a\b\c\d\file.txt
4. Create a link c:\a\b\d that links to its parent directory (..)
5. vim -u NONE
:set path=c:/a/**
:find f<C-e><C-e><C-e>
That is hit ctrl+e to trigger the expansion multiple times. It
should complete to file.txt ONLY (the correct behavior).
Now test that without this patch it breaks:
6. vim src/misc.c, search for nextfile, and comment out the three lines
starting from "if (flags & EW_NOSYMLINK..." test for triggering the
'goto nextfile;' (in dos_expandpath function).
7. Compile and repeat step 5 and things should go haywire
nazri.
--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---
Index: ex_docmd.c
===================================================================
--- ex_docmd.c (revision 1390)
+++ ex_docmd.c (working copy)
@@ -3422,6 +3422,11 @@
*/
switch (ea.cmdidx)
{
+ case CMD_find:
+ case CMD_sfind:
+ case CMD_tabfind:
+ xp->xp_context = EXPAND_FILES_IN_PATH;
+ break;
case CMD_cd:
case CMD_chdir:
case CMD_lcd:
Index: ex_getln.c
===================================================================
--- ex_getln.c (revision 1390)
+++ ex_getln.c (working copy)
@@ -4063,6 +4063,7 @@
char_u *tail;
if (context != EXPAND_FILES
+ && context != EXPAND_FILES_IN_PATH
&& context != EXPAND_SHELLCMD
&& context != EXPAND_DIRECTORIES)
{
@@ -4377,7 +4378,9 @@
if (options & WILD_SILENT)
flags |= EW_SILENT;
- if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES)
+ if (xp->xp_context == EXPAND_FILES
+ || xp->xp_context == EXPAND_DIRECTORIES
+ || xp->xp_context == EXPAND_FILES_IN_PATH)
{
/*
* Expand file or directory names.
@@ -4407,6 +4410,8 @@
if (xp->xp_context == EXPAND_FILES)
flags |= EW_FILE;
+ else if (xp->xp_context == EXPAND_FILES_IN_PATH)
+ flags |= (EW_FILE | EW_PATH);
else
flags = (flags | EW_DIR) & ~EW_FILE;
ret = expand_wildcards(1, &pat, num_file, file, flags);
Index: misc1.c
===================================================================
--- misc1.c (revision 1390)
+++ misc1.c (working copy)
@@ -8770,6 +8770,11 @@
|| vim_regexec(®match, p, (colnr_T)0)))
{
#ifdef WIN3264
+ /* Skip symbolic link if requested */
+ if (flags & EW_NOSYMLINK
+ && fb.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ goto nextfile;
+
STRCPY(s, p);
#else
namelowcpy(s, p);
@@ -8806,6 +8811,7 @@
}
#ifdef WIN3264
+nextfile:
# ifdef FEAT_MBYTE
if (wn != NULL)
{
@@ -9037,6 +9043,10 @@
if ((dp->d_name[0] != '.' || starts_with_dot)
&& vim_regexec(®match, (char_u *)dp->d_name, (colnr_T)0))
{
+ /* Skip symbolic link if requested */
+ if (flags & EW_NOSYMLINK && dp->d_type == DT_LNK)
+ continue;
+
STRCPY(s, dp->d_name);
len = STRLEN(buf);
@@ -9097,7 +9107,255 @@
}
#endif
+#if defined(FEAT_SEARCHPATH)
+/*
+ * Expand the files matching pattern, starting from the given path, save
+ * the matches in their equivalent fullpath.
+ */
+ static int
+expand_to_fullpath(path, gap, pattern, flags)
+ char_u *path;
+ garray_T *gap;
+ char_u *pattern;
+ int flags; /* EW_* flags */
+{
+ int i;
+ int c;
+ int old_len;
+ int new_len;
+ char_u **files;
+ char_u *fullpath;
+
+ old_len = gap->ga_len;
+ c = mch_expandpath(gap, pattern, flags);
+
+ files = (gap->ga_data != NULL) ? (char_u **)gap->ga_data : (char_u **)"";
+ new_len = gap->ga_len;
+
+ for(i = old_len; i < new_len; i++)
+ {
+ fullpath = concat_fnames(path, files[i], TRUE);
+ vim_free(files[i]);
+ files[i] = fullpath;
+ }
+ return c;
+}
+
/*
+ * Moves psep to the previous path separator in path, starting from the
+ * end of path. Returns FAIL is psep ends up at the beginning of path.
+ */
+ static int
+find_previous_pathsep(path, psep)
+ char_u *path;
+ char_u **psep;
+{
+ /*
+ * As we're looking for the previous path separator, skip the current
+ * separator.
+ */
+ if (vim_ispathsep(**psep))
+ (*psep)--;
+
+ while (*psep >= path && !vim_ispathsep(**psep))
+ (*psep)--;
+
+ if (*psep != path && vim_ispathsep(**psep))
+ return OK;
+
+ 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(maybe_unique, gap, i)
+ char_u *maybe_unique;
+ garray_T *gap;
+ int i;
+{
+ int j;
+ int candidate_len;
+ int other_path_len;
+ char_u *rival;
+ char_u **other_paths;
+
+ other_paths = (gap->ga_data != NULL) ? (char_u **)gap->ga_data : (char_u **)"";
+
+ for (j = 0; j < gap->ga_len && !got_int; j++)
+ {
+ /* Don't compare it with itself */
+ if(j == i)
+ continue;
+
+ candidate_len = STRLEN(maybe_unique);
+ other_path_len = STRLEN(other_paths[j]);
+
+ if(other_path_len < candidate_len)
+ /* It's different, */
+ continue;
+
+ rival = other_paths[j] + other_path_len - candidate_len;
+
+ if (fnamecmp(maybe_unique, rival) == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Sorts, removes duplicates and modifies all the fullpath names in gap so that
+ * they are unique with respect to each other. Beware, this is at least O(n^2)
+ * wrt gap->ga_len.
+ */
+ static void
+uniquefy_paths(gap)
+ garray_T *gap;
+{
+ int i;
+ int path_len;
+ char_u *pathsep_p;
+ char_u *path;
+ char_u **fnames = (char_u **) gap->ga_data;
+
+ int j;
+ int sort_again = 0;
+
+ /* Remove duplicate entries */
+ sort_strings(fnames, gap->ga_len);
+ for (i = 0; i < gap->ga_len - 1; i++)
+ if (fnamecmp(fnames[i], fnames[i+1]) == 0)
+ {
+ vim_free(fnames[i]);
+ for (j = i+1; j < gap->ga_len; j++)
+ fnames[j-1] = fnames[j];
+ gap->ga_len--;
+ i--;
+ }
+
+ for (i = 0; i < gap->ga_len; i++)
+ {
+ path = fnames[i];
+ path_len = STRLEN(path);
+
+ /* We start at the end of the path */
+ pathsep_p = path + path_len - 1;
+
+ while (find_previous_pathsep(path, &pathsep_p))
+ if (is_unique(pathsep_p, gap, i))
+ {
+ sort_again = 1;
+ mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
+ break;
+ }
+ }
+
+ if (sort_again)
+ sort_strings(fnames, gap->ga_len);
+}
+
+/*
+ * Calls mch_expandpath for each 'path' values for the given pattern and stores
+ * the result in gap. Returns the total number of match.
+ */
+ static int
+expand_in_path(gap, pattern, flags)
+ garray_T *gap;
+ char_u *pattern;
+ int flags; /* EW_* flags */
+{
+ /*
+ * For each expandable 'path' option values we get the list of all
+ * directories and expand the file names in each the directories.
+ */
+ int c = 0;
+ char_u *cwd = NULL;
+ char_u *cwd_orig = NULL;
+ char_u *path_opt;
+ char_u *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
+
+ if ((cwd_orig = alloc((int)(MAXPATHL))) == NULL ||
+ (cwd = alloc((int)(MAXPATHL))) == NULL ||
+ (path_opt = alloc((int)(MAXPATHL))) == NULL)
+ return 0; /* FIXME Give emsg instead? */
+
+ if (getcwd((char *)cwd_orig, MAXPATHL) == NULL)
+ /* FIXME The directory may have been deleted, is it ok to ignore it? */
+ ;
+
+ /*
+ * Search for matching files in each 'path' options.
+ */
+ for(;;)
+ {
+ int i;
+ int dir_count;
+ garray_T ga_dirs;
+
+ path_opt[0] = 0;
+ copy_option_part(&path_option, path_opt, MAXPATHL, " ;,");
+
+ STRCPY(cwd, cwd_orig);
+
+ if (mch_isFullName(path_opt))
+ STRCPY(cwd, path_opt);
+ else
+ {
+ char_u *fullpath;
+
+ fullpath = concat_fnames(cwd_orig, path_opt, TRUE);
+ if (fullpath == NULL)
+ continue; /* FIXME Give emsg too? */
+
+ STRNCPY(cwd, fullpath, MAXPATHL);
+ vim_free(fullpath);
+ }
+
+ ga_init2(&ga_dirs, (int)sizeof(char_u *), 30);
+ /* Skip symbolic links to avoid circular paths. */
+ dir_count = mch_expandpath(&ga_dirs, cwd,
+ EW_DIR|EW_NOSYMLINK|EW_SILENT);
+
+ if (dir_count == 0)
+ {
+ if (mch_chdir(cwd) == 0)
+ c += expand_to_fullpath(cwd, gap, pattern, flags);
+ }
+ else
+ {
+ /* Add files from each expanded path. */
+ int i;
+ char_u **tmpdirs;
+ tmpdirs = (ga_dirs.ga_data != NULL) ? (char_u **)ga_dirs.ga_data
+ : (char_u **)"";
+ for (i = 0; i < dir_count && !got_int ; i++)
+ {
+ /* FIXME Skip symbolic links to avoid cycle */
+ if (mch_chdir(tmpdirs[i]) == 0)
+ c += expand_to_fullpath(tmpdirs[i], gap, pattern, flags);
+ }
+ }
+ ga_clear_strings(&ga_dirs);
+
+ if (path_option == NULL || *path_option == NUL)
+ break;
+ }
+
+ if (mch_chdir(cwd_orig) != 0)
+ /* FIXME Is it safe to ignore this? */ ;
+
+ vim_free(path_opt);
+ vim_free(cwd);
+ vim_free(cwd_orig);
+
+ return c;
+}
+#endif
+
+/*
* Generic wildcard expansion code.
*
* Characters in "pat" that should not be expanded must be preceded with a
@@ -9205,7 +9463,14 @@
* when EW_NOTFOUND is given.
*/
if (mch_has_exp_wildcard(p))
- add_pat = mch_expandpath(&ga, p, flags);
+ {
+#if defined(FEAT_SEARCHPATH)
+ if (*p != '.' && !vim_ispathsep(*p) && flags & EW_PATH)
+ add_pat = expand_in_path(&ga, p, flags);
+ else
+#endif
+ add_pat = mch_expandpath(&ga, p, flags);
+ }
}
if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND)))
@@ -9228,6 +9493,11 @@
vim_free(p);
}
+#if defined(FEAT_SEARCHPATH)
+ if (flags & EW_PATH)
+ uniquefy_paths(&ga);
+#endif
+
*num_file = ga.ga_len;
*file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data : (char_u **)"";
Index: vim.h
===================================================================
--- vim.h (revision 1390)
+++ vim.h (working copy)
@@ -708,6 +708,7 @@
#define EXPAND_USER_DEFINED 30
#define EXPAND_USER_LIST 31
#define EXPAND_SHELLCMD 32
+#define EXPAND_FILES_IN_PATH 33
/* Values for exmode_active (0 is no exmode) */
#define EXMODE_NORMAL 1
@@ -739,6 +740,8 @@
#define EW_KEEPALL 0x10 /* keep all matches */
#define EW_SILENT 0x20 /* don't print "1 returned" from shell */
#define EW_EXEC 0x40 /* executable files */
+#define EW_PATH 0x80 /* search in 'path' too */
+#define EW_NOSYMLINK 0x100 /* skip symlink */
/* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND
* is used when executing commands and EW_SILENT for interactive expanding. */