Skip to content

cmdline hang on Windows when completing paths starting with '/' due to UNC path resolution #94

@elecalion

Description

@elecalion

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

  1. On Windows, open Neovim.
  2. In normal mode, type :e / in the cmdline.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions