feat(fs): add vim.fs.find()

This is a pure Lua implementation of the Vim findfile() and finddir()
functions without the special syntax.
This commit is contained in:
Gregory Anders
2022-05-15 20:37:35 -06:00
parent 2a62bec37c
commit f271d70661
3 changed files with 170 additions and 0 deletions

View File

@@ -61,4 +61,121 @@ function M.dir(path)
end, vim.loop.fs_scandir(path)
end
--- Find files or directories in the given path.
---
--- Finds any files or directories given in {names} starting from {path}. If
--- {upward} is "true" then the search traverses upward through parent
--- directories; otherwise, the search traverses downward. Note that downward
--- searches are recursive and may search through many directories! If {stop}
--- is non-nil, then the search stops when the directory given in {stop} is
--- reached. The search terminates when {limit} (default 1) matches are found.
--- The search can be narrowed to find only files or or only directories by
--- specifying {type} to be "file" or "directory", respectively.
---
---@param names (string|table) Names of the files and directories to find. Must
--- be base names, paths and globs are not supported.
---@param opts (table) Optional keyword arguments:
--- - path (string): Path to begin searching from. If
--- omitted, the current working directory is used.
--- - upward (boolean, default false): If true, search
--- upward through parent directories. Otherwise,
--- search through child directories
--- (recursively).
--- - stop (string): Stop searching when this directory is
--- reached. The directory itself is not searched.
--- - type (string): Find only files ("file") or
--- directories ("directory"). If omitted, both
--- files and directories that match {name} are
--- included.
--- - limit (number, default 1): Stop the search after
--- finding this many matches. Use `math.huge` to
--- place no limit on the number of matches.
---@return (table) The paths of all matching files or directories
function M.find(names, opts)
opts = opts or {}
vim.validate({
names = { names, { 's', 't' } },
path = { opts.path, 's', true },
upward = { opts.upward, 'b', true },
stop = { opts.stop, 's', true },
type = { opts.type, 's', true },
limit = { opts.limit, 'n', true },
})
names = type(names) == 'string' and { names } or names
local path = opts.path or vim.loop.cwd()
local stop = opts.stop
local limit = opts.limit or 1
local matches = {}
---@private
local function add(match)
matches[#matches + 1] = match
if #matches == limit then
return true
end
end
if opts.upward then
---@private
local function test(p)
local t = {}
for _, name in ipairs(names) do
local f = p .. '/' .. name
local stat = vim.loop.fs_stat(f)
if stat and (not opts.type or opts.type == stat.type) then
t[#t + 1] = f
end
end
return t
end
for _, match in ipairs(test(path)) do
if add(match) then
return matches
end
end
for parent in M.parents(path) do
if stop and parent == stop then
break
end
for _, match in ipairs(test(parent)) do
if add(match) then
return matches
end
end
end
else
local dirs = { path }
while #dirs > 0 do
local dir = table.remove(dirs, 1)
if stop and dir == stop then
break
end
for other, type in M.dir(dir) do
local f = dir .. '/' .. other
for _, name in ipairs(names) do
if name == other and (not opts.type or opts.type == type) then
if add(f) then
return matches
end
end
end
if type == 'directory' then
dirs[#dirs + 1] = f
end
end
end
end
return matches
end
return M