Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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