Description
On Windows, typing a command that triggers path completion with a leading / (for example :e /) causes Neovim's cmdline to hang for tens of seconds. In my environment, a single :e / blocks the UI for roughly 54 seconds.
The hang is caused by fs_stat calls on paths that get interpreted as UNC (network) paths, each of which blocks for several seconds while Windows performs NetBIOS / DNS / SMB name resolution.
Environment
- OS: Windows 11
- Neovim: v0.11.5
- cmp-path: latest
main c642487
Reproduction
- On Windows, open Neovim.
- In normal mode, type
:e / in the cmdline.
- Wait.
Expected: completion either returns quickly or returns nothing.
Actual: cmdline freezes for tens of seconds.
Other triggers that produce the same symptom:
:e /
:%s/\/ (any cmdline ending with /)
- Any command where
_dirname resolves to /.
Measurement
The following benchmark isolates the problem to fs_stat on paths produced by _candidates:
:lua << EOF
local function bench(dir)
local start = vim.loop.hrtime()
local h = vim.loop.fs_scandir(dir)
local count, stat_fail = 0, 0
if h then
while true do
local name = vim.loop.fs_scandir_next(h)
if not name then break end
local path = dir .. '/' .. name
local stat = vim.loop.fs_stat(path)
if not stat then stat_fail = stat_fail + 1 end
count = count + 1
end
end
local elapsed = (vim.loop.hrtime() - start) / 1e6
print(string.format('[%-30s] %3d items (stat fail %d), %.2f ms', dir, count, stat_fail, elapsed))
end
bench('/')
bench('C:/')
bench('C:/Users')
EOF
Result on my machine:
[/ ] 13 items (stat fail 13), 54215.58 ms
[C:/ ] 30 items (stat fail 0), 1.61 ms
[C:/Users ] 8 items (stat fail 0), 4.98 ms
Key observations:
- Scanning
/ returns 13 entries, but every single fs_stat fails and each one takes ~4.2 s.
- Scanning
C:/ (the same physical location in terms of user expectation) is 1.6 ms total with zero stat failures.
The actual path in this bench:
//$RECYCLE.BIN
//Users
//System Volume Information
...
Likely Cause
Looking at source._candidates:
local path = dirname .. '/' .. name
local stat = vim.loop.fs_stat(path)
When _dirname returns '/' (which happens for :e / via the prefix:match('/$') branch in _dirname, producing vim.fn.resolve('/' .. '') → '/'), the concatenation yields paths like:
'/' .. '/' .. 'Users' == '//Users'
On Windows, libuv normalizes forward slashes to backslashes before calling Win32 APIs, so //Users is passed to the OS as \\Users. Windows then treats this as a UNC path with Users as the server name and attempts to resolve it via NetBIOS, DNS, LLMNR, and SMB — each attempt with its own timeout. The cumulative ~4 s per entry closely matches the observed per-item latency.
This also explains why C:/ is fast (the concatenation produces C://name, which libuv normalizes to C:\\name; Windows still interprets it as a drive-rooted path, not UNC) while / is catastrophic.
Description
On Windows, typing a command that triggers path completion with a leading
/(for example:e /) causes Neovim's cmdline to hang for tens of seconds. In my environment, a single:e /blocks the UI for roughly 54 seconds.The hang is caused by
fs_statcalls on paths that get interpreted as UNC (network) paths, each of which blocks for several seconds while Windows performs NetBIOS / DNS / SMB name resolution.Environment
mainc642487Reproduction
:e /in the cmdline.Expected: completion either returns quickly or returns nothing.
Actual: cmdline freezes for tens of seconds.
Other triggers that produce the same symptom:
:e /:%s/\/(any cmdline ending with/)_dirnameresolves to/.Measurement
The following benchmark isolates the problem to
fs_staton paths produced by_candidates:Result on my machine:
Key observations:
/returns 13 entries, but every singlefs_statfails and each one takes ~4.2 s.C:/(the same physical location in terms of user expectation) is 1.6 ms total with zero stat failures.The actual path in this bench:
Likely Cause
Looking at
source._candidates:When
_dirnamereturns'/'(which happens for:e /via theprefix:match('/$')branch in_dirname, producingvim.fn.resolve('/' .. '')→'/'), the concatenation yields paths like:On Windows, libuv normalizes forward slashes to backslashes before calling Win32 APIs, so
//Usersis passed to the OS as\\Users. Windows then treats this as a UNC path withUsersas the server name and attempts to resolve it via NetBIOS, DNS, LLMNR, and SMB — each attempt with its own timeout. The cumulative ~4 s per entry closely matches the observed per-item latency.This also explains why
C:/is fast (the concatenation producesC://name, which libuv normalizes toC:\\name; Windows still interprets it as a drive-rooted path, not UNC) while/is catastrophic.