From b25838217697c9e018b73b9859b8c66c7e0b3f94 Mon Sep 17 00:00:00 2001 From: 11soda11 <115734183+11soda11@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:33:01 +0100 Subject: [PATCH] fix(completion): complete drive-letter filepath on Windows #36353 Problem: On MSWIN, file completion (CTRL-X CTRL-F) only works for the current drive (so not for actual absolute paths), since drive letters are never included in the completion pattern. e.g. when completing "F:\Hello" Nvim currently completes "\Hello" which is relative to the current drive/volume. vim solves this by adding ':' to the default 'isfname' value on mswin, but that causes issues as ':' is not a valid windows path char anywhere _except_ after the drive letter. Solution: detect drive letters in front of the path when creating the completion pattern. --- src/nvim/file_search.c | 2 +- src/nvim/insexpand.c | 12 +++++++++++- src/nvim/path.c | 8 ++++---- test/functional/editor/completion_spec.lua | 10 ++++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 3facd32c32..15658e74e2 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1679,7 +1679,7 @@ char *file_name_in_line(char *line, int col, int options, int count, char *rel_f // Search forward for the last char of the file name. // Also allow ":/" when ':' is not in 'isfname'. - len = path_has_drive_letter(ptr) ? 2 : 0; + len = path_has_drive_letter(ptr, strlen(ptr)) ? 2 : 0; while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') || ((options & FNAME_HYP) && path_is_url(ptr + len)) || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) { diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index bc40e93b74..9f26e44eea 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -5786,7 +5786,17 @@ static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) while (p > line && vim_isfilec(utf_ptr2char(p))) { MB_PTR_BACK(line, p); } - if (p == line && vim_isfilec(utf_ptr2char(p))) { + bool p_is_filec = false; +#ifdef MSWIN + // check for drive letters on mswin + if (p > line && path_has_drive_letter(p - 1, line + startcol - (p - 1))) { + p -= p == line + 1 ? 1 : 2; + p_is_filec = true; + } +#endif + p_is_filec = p_is_filec || vim_isfilec(utf_ptr2char(p)); + + if (p == line && p_is_filec) { startcol = 0; } else { startcol = (int)(p - line) + 1; diff --git a/src/nvim/path.c b/src/nvim/path.c index f4393f07cd..267c0d1fab 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1715,13 +1715,13 @@ size_t simplify_filename(char *filename) /// Checks for a Windows drive letter ("C:/") at the start of the path. /// /// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter -bool path_has_drive_letter(const char *p) +bool path_has_drive_letter(const char *p, size_t path_len) FUNC_ATTR_NONNULL_ALL { - return strlen(p) >= 2 + return path_len >= 2 && ASCII_ISALPHA(p[0]) && (p[1] == ':' || p[1] == '|') - && (strlen(p) == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); + && (path_len == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); } // Check if the ":/" of a URL is at the pointer, return URL_SLASH. @@ -1758,7 +1758,7 @@ int path_with_url(const char *fname) return 0; } - if (path_has_drive_letter(fname)) { + if (path_has_drive_letter(fname, strlen(fname))) { return 0; } diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 51d2ba6755..45bf6c80bc 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -30,6 +30,16 @@ describe('completion', function() } end) + it('ctrl-x_ctrl-f completes Windows drive letter', function() + if not t.is_os('win') then + return + end + feed('iblablaC:/W') + screen:expect { + any = [[C:\Windows\]], + } + end) + describe('v:completed_item', function() it('is empty dict until completion', function() eq({}, eval('v:completed_item'))