mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 19:09:39 +00:00
feat(vim.fs): dir() ergonomics
This commit is contained in:
@@ -177,6 +177,10 @@ end
|
||||
--- (default: `1`)
|
||||
--- @field depth? integer
|
||||
---
|
||||
--- Report errors via the iterator's third value ("err"), instead of silently skipping.
|
||||
--- (default: `false`)
|
||||
--- @field err? boolean
|
||||
---
|
||||
--- Predicate to control traversal.
|
||||
--- Return false to stop searching the current directory.
|
||||
--- Only useful when depth > 1
|
||||
@@ -187,21 +191,14 @@ end
|
||||
--- (default: `false`)
|
||||
--- @field follow? boolean
|
||||
|
||||
|
||||
--- Gets an iterator over items found in `path` (normalized via |vim.fs.normalize()|).
|
||||
---
|
||||
--- Example:
|
||||
---
|
||||
--- ```lua
|
||||
--- local it, err = vim.fs.dir(path)
|
||||
---
|
||||
--- if err then
|
||||
--- -- Failed to scan the root directory
|
||||
--- end
|
||||
---
|
||||
--- for name, type, err in it do
|
||||
--- for name, type, err in vim.fs.dir(path, { err = true }) do
|
||||
--- if err then
|
||||
--- -- Failed to scan a child directory
|
||||
--- -- Failed to scan directory {name} (may be the root {path} itself).
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
@@ -209,29 +206,36 @@ end
|
||||
---@since 10
|
||||
---@param path (string) Directory to iterate over, normalized via |vim.fs.normalize()|.
|
||||
---@param opts? vim.fs.dir.Opts Optional keyword arguments:
|
||||
---@return fun(): string?, string?, string? # Iterator over items in {path}. Each iteration yields three values: name, type, err.
|
||||
---@return fun(): string?, string?, string? # Iterator over items in {path}, yielding (name, type, err):
|
||||
--- - name: Basename of the item relative to {path}.
|
||||
--- - type: One of: "file", "directory", "link", "fifo", "socket", "char", "block", "unknown".
|
||||
--- - err: Error string, or nil.
|
||||
---@return string? err Error encountered while scanning the base directory
|
||||
--- - err: Error string, or nil. Only if `opts.err=true`. If the root {path} itself could not
|
||||
--- be scanned, yields a single (name, nil, err) item.
|
||||
function M.dir(path, opts)
|
||||
opts = opts or {}
|
||||
|
||||
vim.validate('path', path, 'string')
|
||||
vim.validate('depth', opts.depth, 'number', true)
|
||||
vim.validate('skip', opts.skip, 'function', true)
|
||||
vim.validate('err', opts.err, 'boolean', true)
|
||||
vim.validate('follow', opts.follow, 'boolean', true)
|
||||
vim.validate('skip', opts.skip, 'function', true)
|
||||
|
||||
path = M.normalize(path)
|
||||
|
||||
local rootfs, rooterr = uv.fs_scandir(path)
|
||||
|
||||
if not rootfs then
|
||||
local empty = function()
|
||||
return nil
|
||||
-- Root scan failed:
|
||||
-- - If opts.err=false, behave as an empty listing (back-compat).
|
||||
-- - If opts.err=true, surface yield a single (name, nil, err) result.
|
||||
local done = not opts.err
|
||||
return function()
|
||||
if done then
|
||||
return nil
|
||||
end
|
||||
done = true
|
||||
return path, nil, rooterr
|
||||
end
|
||||
|
||||
return empty, rooterr
|
||||
end
|
||||
|
||||
if not opts.depth or opts.depth == 1 then
|
||||
@@ -241,7 +245,7 @@ function M.dir(path, opts)
|
||||
end
|
||||
|
||||
--- @async
|
||||
local it = coroutine.wrap(function()
|
||||
return coroutine.wrap(function()
|
||||
local dirs = { { path, 1, rootfs } }
|
||||
while #dirs > 0 do
|
||||
--- @type string, integer, any
|
||||
@@ -264,7 +268,7 @@ function M.dir(path, opts)
|
||||
then
|
||||
local fs_next, err = uv.fs_scandir(M.joinpath(path, f))
|
||||
if not fs_next then
|
||||
err_scan = err
|
||||
err_scan = opts.err and err or nil
|
||||
else
|
||||
dirs[#dirs + 1] = { f, level + 1, fs_next }
|
||||
end
|
||||
@@ -273,8 +277,6 @@ function M.dir(path, opts)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return it, nil
|
||||
end
|
||||
|
||||
--- @class vim.fs.find.Opts
|
||||
@@ -338,8 +340,8 @@ end
|
||||
--- The function should return `true` if the given item is considered a match.
|
||||
---
|
||||
---@param opts? vim.fs.find.Opts Optional keyword arguments:
|
||||
---@return string[] matches # Normalized paths |vim.fs.normalize()| of all matching items.
|
||||
---@return string[] errors # Errors collected while searching.
|
||||
---@return string[] # Normalized paths |vim.fs.normalize()| of all matching items.
|
||||
---@return string[] # Errors collected while searching.
|
||||
function M.find(names, opts)
|
||||
opts = opts or {}
|
||||
vim.validate('names', names, { 'string', 'table', 'function' })
|
||||
@@ -374,17 +376,11 @@ function M.find(names, opts)
|
||||
if type(names) == 'function' then
|
||||
test = function(p)
|
||||
local t = {}
|
||||
local it, base_err = M.dir(p)
|
||||
if base_err ~= nil then
|
||||
table.insert(errors, base_err)
|
||||
end
|
||||
for name, type, err in it do
|
||||
local f = M.joinpath(p, name)
|
||||
for name, type, err in M.dir(p, { err = true }) do
|
||||
if err ~= nil then
|
||||
table.insert(errors, err)
|
||||
end
|
||||
if (not opts.type or opts.type == type) and names(name, p) then
|
||||
table.insert(t, f)
|
||||
elseif (not opts.type or opts.type == type) and names(name, p) then
|
||||
table.insert(t, M.joinpath(p, name))
|
||||
end
|
||||
end
|
||||
return t
|
||||
@@ -392,7 +388,7 @@ function M.find(names, opts)
|
||||
else
|
||||
test = function(p)
|
||||
local t = {} --- @type string[]
|
||||
local ok, aerr = uv.fs_access(p, 'R') -- Check if the directory itself is readable
|
||||
local ok, aerr = uv.fs_access(p, 'R') -- Check if the root dir is readable.
|
||||
if not ok then
|
||||
table.insert(errors, aerr)
|
||||
return t
|
||||
@@ -434,36 +430,33 @@ function M.find(names, opts)
|
||||
break
|
||||
end
|
||||
|
||||
local it, base_err = M.dir(dir)
|
||||
if base_err ~= nil then
|
||||
table.insert(errors, base_err)
|
||||
end
|
||||
for other, type_, err in it do
|
||||
local f = M.joinpath(dir, other)
|
||||
for other, type_, err in M.dir(dir, { err = true }) do
|
||||
if err ~= nil then
|
||||
table.insert(errors, err)
|
||||
end
|
||||
if type(names) == 'function' then
|
||||
if (not opts.type or opts.type == type_) and names(other, dir) then
|
||||
if add(f) then
|
||||
return matches, errors
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, name in ipairs(names) do
|
||||
if name == other and (not opts.type or opts.type == type_) then
|
||||
local f = M.joinpath(dir, other)
|
||||
if type(names) == 'function' then
|
||||
if (not opts.type or opts.type == type_) and names(other, dir) then
|
||||
if add(f) then
|
||||
return matches, errors
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, name in ipairs(names) do
|
||||
if name == other and (not opts.type or opts.type == type_) then
|
||||
if add(f) then
|
||||
return matches, errors
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if
|
||||
type_ == 'directory'
|
||||
or (type_ == 'link' and opts.follow and (uv.fs_stat(f) or {}).type == 'directory')
|
||||
then
|
||||
dirs[#dirs + 1] = f
|
||||
if
|
||||
type_ == 'directory'
|
||||
or (type_ == 'link' and opts.follow and (uv.fs_stat(f) or {}).type == 'directory')
|
||||
then
|
||||
dirs[#dirs + 1] = f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user