fix(explorer): mounted directories being detected as non-directories in Tree:expand (#2053)

## Description

In certain filesystems — such as bind mounts, NFS, FUSE, or other
mounted paths — uv.fs_scandir_next() may return a nil type for valid
directories. This causes the snacks.nvim explorer to treat mounted
folders as files, making them unexpandable in the UI.

This patch adds a fallback to resolve the correct type when
fs_scandir_next() returns nil. It uses uv.fs_stat() first, and falls
back to vim.fn.isdirectory() if needed.

This ensures that mounted directories are properly marked as navigable.

Code change summary:

- Inside Tree:expand, we now check t == nil
- If so, we call uv.fs_stat() to get the true type
- If fs_stat fails, we fallback to vim.fn.isdirectory
- Final dir = true logic remains intact

Impact:
- Fixes a long-standing bug in mounted directories under /mnt, FUSE,
etc.
- Only runs extra checks when needed (no performance hit for regular
files)
- No changes elsewhere — clean, isolated patch

## Related Issue(s)
  - Fixes #2036

## Screenshots

before:
<img width="1907" height="946" alt="image"
src="https://github.com/user-attachments/assets/15e1b8a5-c999-49e8-8ab5-1d23c60e4969"
/>

after:
<img width="343" height="902" alt="Screenshot_20250715_222920"
src="https://github.com/user-attachments/assets/c7ca2fa0-e0cc-4e3d-a4bb-5345538408f7"
/>

---------

Co-authored-by: Folke Lemaitre <folke.lemaitre@gmail.com>
This commit is contained in:
nickx00 2025-09-25 03:45:01 -07:00 committed by GitHub
parent 68da653d20
commit 7a5eb1036a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 18 additions and 2 deletions

View file

@ -141,6 +141,7 @@ function Tree:expand(node)
if not name then
break
end
t = t or Snacks.util.path_type(node.path .. "/" .. name)
found[name] = true
local child = self:child(node, name, t)
child.type = t

View file

@ -13,6 +13,7 @@ function M.directory(ctx)
ctx.preview:set_title(ctx.item.title or name)
local ls = {} ---@type {file:string, type:"file"|"directory"}[]
for file, t in vim.fs.dir(ctx.item.file) do
t = t or Snacks.util.path_type(ctx.item.file .. "/" .. file)
ls[#ls + 1] = { file = file, type = t }
end
ctx.preview:set_lines(vim.split(string.rep("\n", #ls), "\n"))

View file

@ -454,7 +454,7 @@ function M.dir(item)
local path = type(item) == "table" and M.path(item) or item
---@cast path string
path = svim.fs.normalize(path)
return vim.fn.isdirectory(path) == 1 and path or vim.fs.dirname(path)
return Snacks.util.path_type(path) == "directory" and path or vim.fs.dirname(path)
end
---@param paths string[]
@ -476,7 +476,7 @@ function M.copy_path(from, to)
Snacks.notify.error(("File `%s` does not exist"):format(from))
return
end
if vim.fn.isdirectory(from) == 1 then
if Snacks.util.path_type(from) == "directory" then
M.copy_dir(from, to)
else
M.copy_file(from, to)

View file

@ -458,4 +458,18 @@ function M.parse(parser, range, on_parse)
end
end
--- Better validation to check if path is a dir or a file
---@param path string
---@return "directory"|"file"
function M.path_type(path)
local stat = uv.fs_stat(path)
if stat and stat.type then
return stat.type
end
if vim.fn.isdirectory(path) == 1 then
return "directory"
end
return "file"
end
return M