diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f0d01f92e7..2bf912dbfd 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -15,6 +15,9 @@ BREAKING CHANGES *news-breaking* The following changes may require adaptations in user config or plugins. +• Windows file path drive letters are now detected even if ":" is not in + 'isfname'. The default 'isfname' no longer includes ":". + • |vim.tbl_islist()| now checks whether a table is actually list-like (i.e., has integer keys without gaps and starting from 1). For the previous behavior (only check for integer keys, allow gaps or not starting with 1), diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index bc207d7755..283c1e3612 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3453,7 +3453,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'isfname'* *'isf'* 'isfname' 'isf' string (default for Windows: - "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=" + "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=" otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,=") global The characters specified by this option are included in file names and diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index e9c04443a6..974aa051cf 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -53,6 +53,8 @@ Defaults *nvim-defaults* - 'hlsearch' is enabled - 'include' defaults to "". The C ftplugin sets it to "^\\s*#\\s*include" - 'incsearch' is enabled +- 'isfname' does not include ":" on Windows. Include ":" in 'isfname' to treat + it as part of a filename anywhere in the name (not only the drive letter). - 'joinspaces' is disabled - 'langnoremap' is enabled - 'langremap' is disabled diff --git a/src/nvim/options.lua b/src/nvim/options.lua index a0b9306908..e70fe8614f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -4298,9 +4298,9 @@ return { defaults = { condition = 'BACKSLASH_IN_FILENAME', if_false = '@,48-57,/,.,-,_,+,,,#,$,%,~,=', - if_true = '@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=', + if_true = '@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=', doc = [[for Windows: - "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=" + "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=" otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,="]], }, deny_duplicates = true, diff --git a/src/nvim/window.c b/src/nvim/window.c index 41199306fa..ef6d3fd4a9 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6943,11 +6943,12 @@ char *file_name_in_line(char *line, int col, int options, int count, char *rel_f bool is_url = false; // Search backward for first char of the file name. - // Go one char back to ":" before "//" even when ':' is not in 'isfname'. + // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":" + // is not in 'isfname'). while (ptr > line) { if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) { ptr -= len + 1; - } else if (vim_isfilec((uint8_t)ptr[-1]) + } else if (vim_isfilec((uint8_t)ptr[-1]) || path_has_drive_letter(ptr - 2) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { ptr--; } else { @@ -6957,14 +6958,13 @@ 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 = 0; + len = path_has_drive_letter(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)) { // After type:// we also include :, ?, & and = as valid characters, so that // http://google.com:8080?q=this&that=ok works. - if ((ptr[len] >= 'A' && ptr[len] <= 'Z') - || (ptr[len] >= 'a' && ptr[len] <= 'z')) { + if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) { if (in_type && path_is_url(ptr + len + 1)) { is_url = true; } diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua index 6c677d76d1..1c6faf8d60 100644 --- a/test/functional/core/path_spec.lua +++ b/test/functional/core/path_spec.lua @@ -91,4 +91,80 @@ describe('file search', function() feed('gf') eq('filename_with_unicode_ααα', eval('expand("%:t")')) end) + + it('matches Windows drive-letter filepaths (without ":" in &isfname)', function() + local os_win = is_os('win') + + insert([[c:/d:/foo/bar.txt]]) + eq([[c:/d:/foo/bar.txt]], eval('expand("")')) + command('%delete') + + insert([[//share/c:/foo/bar/]]) + eq([[//share/c:/foo/bar/]], eval('expand("")')) + command('%delete') + + insert([[file://c:/foo/bar]]) + eq([[file://c:/foo/bar]], eval('expand("")')) + command('%delete') + + insert([[https://c:/foo/bar]]) + eq([[https://c:/foo/bar]], eval('expand("")')) + command('%delete') + + insert([[\foo\bar]]) + eq(os_win and [[\foo\bar]] or [[bar]], eval('expand("")')) + command('%delete') + + insert([[/foo/bar]]) + eq([[/foo/bar]], eval('expand("")')) + command('%delete') + + insert([[c:\foo\bar]]) + eq(os_win and [[c:\foo\bar]] or [[bar]], eval('expand("")')) + command('%delete') + + insert([[c:/foo/bar]]) + eq([[c:/foo/bar]], eval('expand("")')) + command('%delete') + + insert([[c:foo\bar]]) + eq(os_win and [[foo\bar]] or [[bar]], eval('expand("")')) + command('%delete') + + insert([[c:foo/bar]]) + eq([[foo/bar]], eval('expand("")')) + command('%delete') + + insert([[c:foo]]) + eq([[foo]], eval('expand("")')) + command('%delete') + + -- Examples from: https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#example-ways-to-refer-to-the-same-file + insert([[c:\temp\test-file.txt]]) + eq(os_win and [[c:\temp\test-file.txt]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\127.0.0.1\c$\temp\test-file.txt]]) + eq(os_win and [[\\127.0.0.1\c$\temp\test-file.txt]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\LOCALHOST\c$\temp\test-file.txt]]) + eq(os_win and [[\\LOCALHOST\c$\temp\test-file.txt]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\.\c:\temp\test-file.txt]]) -- not supported yet + eq(os_win and [[\\.\c]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\?\c:\temp\test-file.txt]]) -- not supported yet + eq(os_win and [[\c]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\.\UNC\LOCALHOST\c$\temp\test-file.txt]]) + eq(os_win and [[\\.\UNC\LOCALHOST\c$\temp\test-file.txt]] or [[test-file.txt]], eval('expand("")')) + command('%delete') + + insert([[\\127.0.0.1\c$\temp\test-file.txt]]) + eq(os_win and [[\\127.0.0.1\c$\temp\test-file.txt]] or [[test-file.txt]], eval('expand("")')) + end) end) diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 096ddba40b..b0c2a15a3f 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -26,6 +26,9 @@ if exists('s:did_load') set sessionoptions+=options set viewoptions+=options set switchbuf= + if has('win32') + set isfname+=: + endif if g:testname !~ 'test_mapping.vim$' " Make "Q" switch to Ex mode. " This does not work for all tests.