vim-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 \folder and /folder as absolute
          (Miguel Barro).

closes: vim/vim#17829

a2f13bf782

Co-authored-by: Miguel Barro <miguel.barro@live.com>
This commit is contained in:
zeertzjq
2025-08-18 10:33:27 +08:00
parent d548b52b0f
commit 43bf045005
6 changed files with 90 additions and 17 deletions

View File

@@ -111,7 +111,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);
xfree(*bufp); // free any allocated file name
*bufp = *fnamep;

View File

@@ -331,17 +331,6 @@ void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *st
ff_expand_buffer.size = strlen(ff_expand_buffer.data);
search_ctx->ffsc_start_dir = copy_string(ff_expand_buffer, NULL);
#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.data[1] == ':') {
search_ctx->ffsc_start_dir.data[2] = NUL;
search_ctx->ffsc_start_dir.size = 2;
}
#endif
}
// If stopdirs are given, split them into an array of pointers.

View File

@@ -5,6 +5,9 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef MSWIN
# include <direct.h>
#endif
#include "auto/config.h"
#include "nvim/ascii_defs.h"
@@ -376,6 +379,38 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, size_t le
const char *p1 = fname1;
const char *p2 = fname2;
# 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
c1 = utf_ptr2char(p1);
c2 = utf_ptr2char(p2);
if ((c1 == '/' || c1 == '\\') && ASCII_ISALPHA(c2)) {
drive = mb_toupper(c2) - 'A' + 1;
// Check for the colon
p2 += utfc_ptr2len(p2);
c2 = utf_ptr2char(p2);
if (c2 == ':' && drive == _getdrive()) { // skip the drive for comparisson
p2 += utfc_ptr2len(p2);
break;
} else { // ignore
p2 -= utfc_ptr2len(p2);
}
}
// swap pointers
const char *tmp = p1;
p1 = p2;
p2 = tmp;
}
# endif
while (len > 0) {
c1 = utf_ptr2char(p1);
c2 = utf_ptr2char(p2);
@@ -1834,7 +1869,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force)
/// the root may have relative paths (like dir/../subdir) or symlinks
/// embedded, or even extra separators (//). This function addresses
/// those possibilities, returning a resolved absolute path.
/// For MS-Windows, this also expands names like "longna~1".
/// For MS-Windows, this also provides drive letter for all absolute paths.
///
/// @param fname is the filename to expand
/// @return [allocated] Full path (NULL for failure).
@@ -1848,6 +1883,10 @@ char *fix_fname(const char *fname)
|| strstr(fname, "//") != NULL
# ifdef BACKSLASH_IN_FILENAME
|| strstr(fname, "\\\\") != NULL
# endif
# ifdef MSWIN
|| fname[0] == '/'
|| fname[0] == '\\'
# endif
) {
return FullName_save(fname, false);
@@ -2328,7 +2367,11 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
const char *end_of_path = fname;
// expand it if forced or not an absolute path
if (force || !path_is_absolute(fname)) {
if (force || !path_is_absolute(fname)
#ifdef MSWIN // enforce drive letter on Windows paths
|| fname[0] == '/' || fname[0] == '\\'
#endif
) {
p = strrchr(fname, '/');
#ifdef MSWIN
if (p == NULL) {
@@ -2372,8 +2415,9 @@ bool path_is_absolute(const char *fname)
return false;
}
// A name like "d:/foo" and "//server/share" is absolute
return ((isalpha((uint8_t)fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2]))
|| (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1]));
// /foo and \foo are absolute too because windows keeps a current drive.
return ((ASCII_ISALPHA(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2]))
|| vim_ispathsep_nocolon(fname[0]));
#else
// UNIX: This just checks if the file name starts with '/' or '~'.
return *fname == '/' || *fname == '~';