changeset 2542:7a547db7070d vim73

Improvements for :find completion.
author Bram Moolenaar <bram@vim.org>
date Thu, 12 Aug 2010 21:50:51 +0200
parents 8a156630208b
children 8c512e2c7cb5
files runtime/doc/todo.txt src/misc1.c src/testdir/test73.in src/testdir/test73.ok
diffstat 4 files changed, 186 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 7.3f.  Last change: 2010 Aug 10
+*todo.txt*      For Vim version 7.3f.  Last change: 2010 Aug 12
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -32,6 +32,8 @@ be worked on, but only if you sponsor Vi
 
 'cursorline' stops too early in a help file, caused by conceal feature.
 
+Have a close look at :find completion, anything that could be wrong?
+
 Test 73 fails on MS-Windows when compiled with DJGPP.
 :find completion with 'path' set to "./**" results in full path for
 "./subdir/file", should shorten to start with "./".
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -9263,6 +9263,7 @@ is_unique(maybe_unique, gap, i)
     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 && !got_int; j++)
     {
@@ -9275,7 +9276,8 @@ is_unique(maybe_unique, gap, i)
 	if (other_path_len < candidate_len)
 	    continue;  /* it's different */
 
-	if (fnamecmp(maybe_unique, gettail(other_paths[j])) == 0)
+	rival = other_paths[j] + other_path_len - candidate_len;
+	if (fnamecmp(maybe_unique, rival) == 0)
 	    return FALSE;  /* match */
     }
 
@@ -9301,8 +9303,6 @@ expand_path_option(curdir, gap)
     char_u	*buf;
     char_u	*p;
 
-    ga_init2(gap, (int)sizeof(char_u *), 1);
-
     if ((buf = alloc((int)MAXPATHL)) == NULL)
 	return;
 
@@ -9357,7 +9357,7 @@ expand_path_option(curdir, gap)
  *
  *    path: /foo/bar/baz
  *   fname: /foo/bar/baz/quux.txt
- * returns:              ^this
+ * returns:		 ^this
  */
     static char_u *
 get_path_cutoff(fname, gap)
@@ -9413,15 +9413,21 @@ uniquefy_paths(gap, pattern)
     int		i;
     int		len;
     char_u	**fnames = (char_u **)gap->ga_data;
-    int		sort_again = 0;
+    int		sort_again = FALSE;
     char_u	*pat;
     char_u      *file_pattern;
     char_u	*curdir = NULL;
     regmatch_T	regmatch;
     garray_T	path_ga;
+    char_u	**in_curdir = NULL;
+    char_u	*short_name;
 
     sort_strings(fnames, gap->ga_len);
     remove_duplicates(gap);
+    if (gap->ga_len == 0)
+	return;
+
+    ga_init2(&path_ga, (int)sizeof(char_u *), 1);
 
     /*
      * We need to prepend a '*' at the beginning of file_pattern so that the
@@ -9447,17 +9453,21 @@ uniquefy_paths(gap, pattern)
 	return;
 
     if ((curdir = alloc((int)(MAXPATHL))) == NULL)
-	return;
+	goto theend;
     mch_dirname(curdir, MAXPATHL);
 
     expand_path_option(curdir, &path_ga);
+    in_curdir = (char_u **)alloc(gap->ga_len * sizeof(char_u *));
+    if (in_curdir == NULL)
+	goto theend;
 
     for (i = 0; i < gap->ga_len; i++)
     {
 	char_u	    *path = fnames[i];
 	int	    is_in_curdir;
 	char_u	    *dir_end = gettail(path);
-	char_u	    *short_name;
+	char_u	    *pathsep_p;
+	char_u	    *path_cutoff;
 
 	len = (int)STRLEN(path);
 	while (dir_end > path &&
@@ -9468,77 +9478,29 @@ uniquefy_paths(gap, pattern)
 #endif
 		)
 	    mb_ptr_back(path, dir_end);
-	is_in_curdir = STRNCMP(curdir, path, dir_end - path) == 0
+	is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0
 					     && curdir[dir_end - path] == NUL;
 
-	/*
-	 * If the file is in the current directory,
-	 * and it is not unique,
-	 * reduce it to ./{filename}
-	 *        FIXME ^ Is this portable?
-	 *
-	 * Note: If the full filename is /curdir/foo/bar/{filename}, we don't
-	 * want to shorten it to ./foo/bar/{filename} yet because 'path' might
-	 * contain ". / * *", in which case the shortened filename could be
-	 * shorter than ./foo/bar/{filename}.
-	 */
 	if (is_in_curdir)
-	{
-	    char_u *rel_path;
-
-	    short_name = shorten_fname(path, curdir);
-	    if (short_name == NULL)
-		short_name = path;
-	    if (is_unique(short_name, gap, i))
-	    {
-		STRMOVE(path, short_name);
-		continue;
-	    }
-
-	    rel_path = alloc((int)(STRLEN(short_name)
-						   + STRLEN(PATHSEPSTR) + 2));
-	    if (rel_path == NULL)
-		goto theend;
-
-	    /* FIXME Is "." a portable way of denoting the current directory? */
-	    STRCPY(rel_path, ".");
-	    add_pathsep(rel_path);
-	    STRCAT(rel_path, short_name);
-
-	    if (len < (int)STRLEN(rel_path))
-	    {
-		vim_free(fnames[i]);
-		fnames[i] = alloc((int)(STRLEN(rel_path) + 1));
-		if (fnames[i] == NULL)
-		{
-		    vim_free(rel_path);
-		    goto theend;
-		}
-	    }
-
-	    STRCPY(fnames[i], rel_path);
-	    vim_free(rel_path);
-	    sort_again = 1;
-	}
+	    in_curdir[i] = vim_strsave(path);
 	else
-	{
-	    /* Shorten the filename while maintaining its uniqueness */
-	    char_u *pathsep_p;
-	    char_u *path_cutoff = get_path_cutoff(path, &path_ga);
-
-	    /* 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 = 1;
-		    mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
-		    break;
-		}
-	}
+	    in_curdir[i] = NULL;
+
+	/* Shorten the filename while maintaining its uniqueness */
+	path_cutoff = get_path_cutoff(path, &path_ga);
+
+	/* 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))
 	{
@@ -9548,11 +9510,11 @@ uniquefy_paths(gap, pattern)
 	     * 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
+	     *	    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)
@@ -9564,8 +9526,68 @@ uniquefy_paths(gap, pattern)
 	}
     }
 
+    /* Shorten filenames in /in/current/directory/{filename} */
+    for (i = 0; i < gap->ga_len; i++)
+    {
+	char_u *rel_path;
+	char_u *path = in_curdir[i];
+
+	if (path == NULL)
+	    continue;
+	/*
+	 * If the file is in the current directory,
+	 * and it is not unique,
+	 * reduce it to ./{filename}
+	 *	  FIXME ^ Is this portable?
+	 * else reduce it to {filename}
+	 *
+	 * Note: If the full filename is /curdir/foo/bar/{filename}, we don't
+	 * want to shorten it to ./foo/bar/{filename} yet because 'path' might
+	 * contain ". / * *", in which case the shortened filename could be
+	 * shorter than ./foo/bar/{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;
+
+	/* FIXME Is "." a portable way of denoting the current directory? */
+	STRCPY(rel_path, ".");
+	add_pathsep(rel_path);
+	STRCAT(rel_path, short_name);
+
+	if (len < (int)STRLEN(rel_path))
+	{
+	    vim_free(fnames[i]);
+	    fnames[i] = alloc((int)(STRLEN(rel_path) + 1));
+	    if (fnames[i] == NULL)
+	    {
+		vim_free(rel_path);
+		goto theend;
+	    }
+	}
+
+	STRCPY(fnames[i], rel_path);
+	vim_free(rel_path);
+	sort_again = TRUE;
+    }
+
 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_free(regmatch.regprog);
 
@@ -9598,6 +9620,7 @@ expand_in_path(gap, pattern, flags)
 	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)
--- a/src/testdir/test73.in
+++ b/src/testdir/test73.in
@@ -6,7 +6,8 @@ STARTTEST
 :" delete the Xfind directory during cleanup
 :"
 :" This will cause a few errors, do it silently.
-:set nocp viminfo+=nviminfo visualbell
+:set visualbell
+:set nocp viminfo+=nviminfo
 :"
 :function! DeleteDirectory(dir)
 : if has("win16") || has("win32") || has("win64") || has("dos16") || has("dos32")
@@ -20,32 +21,33 @@ STARTTEST
 :call DeleteDirectory("Xfind")
 :new
 :let cwd=getcwd()
-:!mkdir Xfind
+:let test_out = cwd . '/test.out'
+:silent !mkdir Xfind
 :cd Xfind
 :set path=
 :find 	
-:w! ../test.out
+:exec "w! " . test_out
 :close
 :new
 :set path=.
 :find 	
-:w >>../test.out
+:exec "w >>" . test_out
 :close
 :new
 :set path=.,,
 :find 	
-:w >>../test.out
+:exec "w >>" . test_out
 :close
 :new
 :set path=./**
 :find 	
-:w >>../test.out
+:exec "w >>" . test_out
 :close
 :new
-:" We shouldn't find any file at this point, ../test.out must be empty.
-:!mkdir in
+:" We shouldn't find any file at this point, test.out must be empty.
+:silent !mkdir in
 :cd in
-:!mkdir path
+:silent !mkdir path
 :exec "cd " . cwd
 :e Xfind/file.txt
 SHoly Grail:w
@@ -57,40 +59,93 @@ SAnother Holy Grail:w
 SE.T.:w
 :set path=Xfind/**
 :find file	
-:w >> test.out
+:exec "w >>" . test_out
 :find file		
-:w >>test.out
+:exec "w >>" . test_out
 :find file			
-:w >>test.out
+:exec "w >>" . test_out
 :" Rerun the previous three find completions, using fullpath in 'path'
 :exec "set path=" . cwd . "/Xfind/**"
 :find file	
-:w >> test.out
+:exec "w >>" .  test_out
 :find file		
-:w >>test.out
+:exec "w >>" . test_out
 :find file			
-:w >>test.out
+:exec "w >>" . test_out
 :" Same steps again, using relative and fullpath items that point to the same
 :" recursive location.
 :" This is to test that there are no duplicates in the completion list.
 :exec "set path+=Xfind/**"
 :find file	
-:w >> test.out
+:exec "w >>" .  test_out
 :find file		
-:w >>test.out
+:exec "w >>" . test_out
 :find file			
-:w >>test.out
+:exec "w >>" . test_out
 :find file		
 :" Test find completion for directory of current buffer, which at this point
 :" is Xfind/in/file.txt.
 :set path=.
 :find st	
-:w >> test.out
+:exec "w >>" .  test_out
 :" Test find completion for empty path item ",," which is the current directory
 :cd Xfind
 :set path=,,
 :find f		
-:w >> ../test.out
+:exec "w >>" . test_out
+:" Test shortening of
+:"
+:"    foo/x/bar/voyager.txt
+:"    foo/y/bar/voyager.txt
+:"
+:" When current directory is above foo/ they should be shortened to (in order
+:" of appearance):
+:"
+:"    x/bar/voyager.txt
+:"    y/bar/voyager.txt
+:silent !mkdir foo
+:cd foo
+:silent !mkdir x
+:silent !mkdir y
+:cd x
+:silent !mkdir bar
+:cd ..
+:cd y
+:silent !mkdir bar
+:cd ..
+:cd ..
+:" We should now be in the Xfind directory
+:e foo/x/bar/voyager.txt
+SVoyager 1:w
+:e foo/y/bar/voyager.txt
+SVoyager 2:w
+:exec "set path=" . cwd . "/Xfind/**"
+:find voyager	
+:exec "w >>" . test_out
+:find voyager		
+:exec "w >>" . test_out
+:"
+:" When current directory is .../foo/y/bar they should be shortened to (in
+:" order of appearance):
+:"
+:"    ./voyager.txt
+:"    x/bar/voyager.txt
+:cd foo
+:cd y
+:cd bar
+:find voyager	
+:exec "w >> " . test_out
+:find voyager		
+:exec "w >> " . test_out
+:" Check the opposite too:
+:cd ..
+:cd ..
+:cd x
+:cd bar
+:find voyager	
+:exec "w >> " . test_out
+:find voyager		
+:exec "w >> " . test_out
 :cd ..
 :q
 :call DeleteDirectory("Xfind")
--- a/src/testdir/test73.ok
+++ b/src/testdir/test73.ok
@@ -9,3 +9,9 @@ Jimmy Hoffa
 E.T.
 Another Holy Grail
 Holy Grail
+Voyager 1
+Voyager 2
+Voyager 2
+Voyager 1
+Voyager 1
+Voyager 2