patch 9.1.1646: MS-Windows: completion cannot handle implicit drive letters
Commit:
https://github.com/vim/vim/commit/a2f13bf782f723e116c5d4cc7d79a23e918a24db
Author: Miguel Barro <
miguel...@live.com>
Date: Sun Aug 17 22:04:24 2025 +0200
patch 9.1.1646: MS-Windows: completion cannot handle implicit drive letters
Problem: MS-Windows: completion cannot handle implicit drive letters
Solution: Consider paths like older and /folder as absolute
(Miguel Barro).
closes: #17829
Co-authored-by: zeertzjq <
zeer...@outlook.com>
Signed-off-by: Miguel Barro <
miguel...@live.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 17be46d03..6dcce6bec 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.1. Last change: 2025 Aug 16
+*version9.txt* For Vim version 9.1. Last change: 2025 Aug 17
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41745,6 +41745,8 @@ Others: ~
Unicode 16.
- Two additional digraphs have been added: LEFT ANGLE BRACKET "<[" and RIGHT
ANGLE BRACKET "]>".
+- MS-Winodws: Paths like "\Windows" and "/Windows" are now considered to be
+ absolute paths (to the current drive) and no longer relative.
*added-9.2*
Added ~
diff --git a/src/buffer.c b/src/buffer.c
index 5937a9e12..0feafc590 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -5472,7 +5472,8 @@ fix_fname(char_u *fname)
* Also expand when there is ".." in the file name, try to remove it,
* because "c:/src/../README" is equal to "c:/README".
* Similarly "c:/src//file" is equal to "c:/src/file".
- * For MS-Windows also expand names like "longna~1" to "longname".
+ * For MS-Windows also expand names like "longna~1" to "longname"
+ * and provide drive letter for all absolute paths.
*/
#ifdef UNIX
return FullName_save(fname, TRUE);
@@ -5485,6 +5486,8 @@ fix_fname(char_u *fname)
# endif
# if defined(MSWIN)
|| vim_strchr(fname, '~') != NULL
+ || fname[0] == '/'
+ || fname[0] == '\'
# endif
)
return FullName_save(fname, FALSE);
diff --git a/src/filepath.c b/src/filepath.c
index 0ef77f9d8..fc3caaf08 100644
--- a/src/filepath.c
+++ b/src/filepath.c
@@ -359,7 +359,11 @@ repeat:
}
// FullName_save() is slow, don't use it when not needed.
- if (*p != NUL || !vim_isAbsName(*fnamep))
+ if (*p != NUL || !vim_isAbsName(*fnamep)
+#ifdef MSWIN // enforce drive letter on windows paths
+ || **fnamep == '/' || **fnamep == '\'
+#endif
+ )
{
*fnamep = FullName_save(*fnamep, *p != NUL);
vim_free(*bufp); // free any allocated file name
@@ -3110,6 +3114,42 @@ vim_fnamencmp(char_u *x, char_u *y, size_t len)
int cx = NUL;
int cy = NUL;
+#ifdef MSWIN
+ /*
+ * To allow proper comparisson of absolute paths:
+ * - one with explicit drive letter C:\xxx
+ * - another with implicit drive letter \xxx
+ * advance the pointer, of the explicit one, to skip the drive
+ */
+ for (int swap = 0, drive = NUL; swap < 2; ++swap)
+ {
+ // Handle absolute paths with implicit drive letter
+ cx = PTR2CHAR(px);
+ cy = PTR2CHAR(py);
+
+ if ((cx == '/' || cx == '\') && ASCII_ISALPHA(cy))
+ {
+ drive = MB_TOUPPER(cy) - 'A' + 1;
+
+ // Check for the colon
+ py += mb_ptr2len(py);
+ cy = PTR2CHAR(py);
+ if (cy == ':' && drive == _getdrive())
+ { // skip the drive for comparisson
+ py += mb_ptr2len(py);
+ break;
+ }
+ else // ignore
+ py -= mb_ptr2len(py);
+ }
+
+ // swap pointers
+ char_u *tmp = px;
+ px = py;
+ py = tmp;
+ }
+#endif
+
while (len > 0)
{
cx = PTR2CHAR(px);
diff --git a/src/findfile.c b/src/findfile.c
index 0f5f2dc62..008338cda 100644
--- a/src/findfile.c
+++ b/src/findfile.c
@@ -398,18 +398,6 @@ vim_findfile_init(
search_ctx->ffsc_start_dir.length);
if (search_ctx->ffsc_start_dir.string == 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.string[1] == ':')
- {
- search_ctx->ffsc_start_dir.string[2] = NUL;
- search_ctx->ffsc_start_dir.length = 2;
- }
-#endif
}
/*
diff --git a/src/os_mswin.c b/src/os_mswin.c
index c2c1bd0c2..405a6c5c1 100644
--- a/src/os_mswin.c
+++ b/src/os_mswin.c
@@ -319,6 +319,7 @@ mch_FullName(
mch_isFullName(char_u *fname)
{
// A name like "d:/foo" and "//server/share" is absolute. "d:foo" is not.
+ // /foo and oo are absolute too because windows keeps a current drive.
// Another way to check is to use mch_FullName() and see if the result is
// the same as the name or mch_FullName() fails. However, this has quite a
// bit of overhead, so let's not do that.
@@ -326,7 +327,7 @@ mch_isFullName(char_u *fname)
return FALSE;
return ((ASCII_ISALPHA(fname[0]) && fname[1] == ':'
&& (fname[2] == '/' || fname[2] == '\'))
- || (fname[0] == fname[1] && (fname[0] == '/' || fname[0] == '\')));
+ || (fname[0] == '/' || fname[0] == '\'));
}
/*
diff --git a/src/testdir/test_cd.vim b/src/testdir/test_cd.vim
index 1b7777e1b..78c6c577e 100644
--- a/src/testdir/test_cd.vim
+++ b/src/testdir/test_cd.vim
@@ -221,6 +221,39 @@ func Test_cd_completion()
call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/ XComplDir3/', @:)
endfor
set cdpath&
+
+ if has('win32')
+ " Test windows absolute path completion
+ " Retrieve a suitable dir in the current drive
+ let dir = readdir('/', 'isdirectory("/" .. v:val) && len(v:val) > 2')[-1]
+ " Get partial path
+ let partial = dir[0:-2]
+ " Get the current drive letter
+ let old = chdir('/' . dir)
+ let full = getcwd()
+ let drive = full[0]
+ call chdir(old)
+
+ for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir']
+ for sep in [ '/', '\']
+
+ " Explicit drive letter
+ call feedkeys(':' .. cmd .. ' ' .. drive .. ':' .. sep ..
+ \ partial .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match(full, @:)
+
+ " Implicit drive letter
+ call feedkeys(':' .. cmd .. ' ' .. sep .. partial .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('/' .. dir .. '/', @:)
+
+ " UNC path
+ call feedkeys(':' .. cmd .. ' ' .. sep .. sep .. $COMPUTERNAME .. sep ..
+ \ drive .. '$' .. sep .. partial .."\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('//' .. $COMPUTERNAME .. '/' .. drive .. '$/' .. dir .. '/' , @:)
+
+ endfor
+ endfor
+ endif
endfunc
func Test_cd_unknown_dir()
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index fccd5af49..ca54d3729 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -4106,7 +4106,8 @@ func Test_isabsolutepath()
call assert_true(isabsolutepath('A:\Foo'))
call assert_true(isabsolutepath('A:/Foo'))
call assert_false(isabsolutepath('A:Foo'))
- call assert_false(isabsolutepath('\Windows'))
+ call assert_true(isabsolutepath('\Windows'))
+ call assert_true(isabsolutepath('/Windows'))
call assert_true(isabsolutepath('\Server2\Share\Test\Foo.txt'))
else
call assert_true(isabsolutepath('/'))
diff --git a/src/version.c b/src/version.c
index fe5d37cda..fd7849cba 100644
--- a/src/version.c
+++ b/src/version.c
@@ -719,6 +719,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1646,
/**/
1645,
/**/
diff --git a/src/vim9script.c b/src/vim9script.c
index ece4a3acf..e526637e2 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -479,13 +479,7 @@ handle_import(
res = handle_import_fname(from_name, is_autoload, &sid);
vim_free(from_name);
}
- else if (mch_isFullName(tv.vval.v_string)
-#ifdef BACKSLASH_IN_FILENAME
- // On MS-Windows omitting the drive is still handled like an
- // absolute path, not using 'runtimepath'.
- || *tv.vval.v_string == '/' || *tv.vval.v_string == '\'
-#endif
- )
+ else if (mch_isFullName(tv.vval.v_string))
{
// Absolute path: "/tmp/name.vim"
res = handle_import_fname(tv.vval.v_string, is_autoload, &sid);