aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt40
-rw-r--r--runtime/lua/vim/fs.lua117
-rw-r--r--test/functional/lua/fs_spec.lua13
3 files changed, 170 insertions, 0 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 5274b829b5..ba59c67446 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2183,6 +2183,46 @@ dirname({file}) *vim.fs.dirname()*
Return: ~
(string) Parent directory of {file}
+find({names}, {opts}) *vim.fs.find()*
+ 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.
+
+ Parameters: ~
+ {names} (string|table) Names of the files and directories
+ to find. Must be base names, paths and globs are
+ not supported.
+ {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
+
parents({start}) *vim.fs.parents()*
Iterate over all the parents of the given file or directory.
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index c28b06536b..4519f2a1e4 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -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
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 6f1f1df012..204bdc1567 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -66,4 +66,17 @@ describe('vim.fs', function()
]], nvim_dir, nvim_prog_basename))
end)
end)
+
+ describe('find()', function()
+ it('works', function()
+ eq({test_build_dir}, exec_lua([[
+ local dir = ...
+ return vim.fs.find('build', { path = dir, upward = true, type = 'directory' })
+ ]], nvim_dir))
+ eq({nvim_prog}, exec_lua([[
+ local dir, nvim = ...
+ return vim.fs.find(nvim, { path = dir, type = 'file' })
+ ]], test_build_dir, nvim_prog_basename))
+ end)
+ end)
end)