Skip to content

Commit

Permalink
feat(vim.fs): pass path to find() predicate, lazy evaluate neovim#22378
Browse files Browse the repository at this point in the history
Problem:
No easy way to find files under certain directories (ex: grab all files under
`test/`) or exclude the content of certain paths (ex. `build/`, `.git/`)

Solution:
Pass the full `path` as an arg to the predicate.
  • Loading branch information
mike325 authored and folke committed May 22, 2023
1 parent e819318 commit b627919
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 11 deletions.
35 changes: 30 additions & 5 deletions runtime/doc/lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2359,12 +2359,37 @@ find({names}, {opts}) *vim.fs.find()*
The search can be narrowed to find only files or only directories by
specifying {type} to be "file" or "directory", respectively.

Examples: >lua

-- location of Cargo.toml from the current buffer's path
local cargo = vim.fs.find('Cargo.toml', {
upward = true,
stop = vim.loop.os_homedir(),
path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)),
})

-- list all test directories under the runtime directory
local test_dirs = vim.fs.find(
{'test', 'tst', 'testdir'},
{limit = math.huge, type = 'directory', path = './runtime/'}
)

-- get all files ending with .cpp or .hpp inside lib/
local cpp_hpp = vim.fs.find(function(name, path)
return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$')
end, {limit = math.huge, type = 'file'})
<

Parameters: ~
{names} (string|table|fun(name: string): boolean) Names of the files
and directories to find. Must be base names, paths and globs
are not supported. The function is called per file and
directory within the traversed directories to test if they
match {names}.
{names} (string|table|fun(name: string, path: string): boolean) Names
of the files and directories to find. Must be base names,
paths and globs are not supported when {names} is a string or
a table. If {names} is a function, it is called for each
traversed file and directory with args:
• name: base name of the current item
• path: full path of the current item The function should
return `true` if the given file or directory is considered
a match.
{opts} (table) Optional keyword arguments:
• path (string): Path to begin searching from. If omitted,
the |current-directory| is used.
Expand Down
35 changes: 29 additions & 6 deletions runtime/lua/vim/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,34 @@ end
--- The search can be narrowed to find only files or only directories by
--- specifying {type} to be "file" or "directory", respectively.
---
---@param names (string|table|fun(name: string): boolean) Names of the files
--- Examples:
--- <pre>lua
--- -- location of Cargo.toml from the current buffer's path
--- local cargo = vim.fs.find('Cargo.toml', {
--- upward = true,
--- stop = vim.loop.os_homedir(),
--- path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)),
--- })
---
--- -- list all test directories under the runtime directory
--- local test_dirs = vim.fs.find(
--- {'test', 'tst', 'testdir'},
--- {limit = math.huge, type = 'directory', path = './runtime/'}
--- )
---
--- -- get all files ending with .cpp or .hpp inside lib/
--- local cpp_hpp = vim.fs.find(function(name, path)
--- return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$')
--- end, {limit = math.huge, type = 'file'})
--- </pre>
---
---@param names (string|table|fun(name: string, path: string): boolean) Names of the files
--- and directories to find.
--- Must be base names, paths and globs are not supported.
--- The function is called per file and directory within the
--- traversed directories to test if they match {names}.
--- Must be base names, paths and globs are not supported when {names} is a string or a table.
--- If {names} is a function, it is called for each traversed file and directory with args:
--- - name: base name of the current item
--- - path: full path of the current item
--- The function should return `true` if the given file or directory is considered a match.
---
---@param opts (table) Optional keyword arguments:
--- - path (string): Path to begin searching from. If
Expand Down Expand Up @@ -201,7 +224,7 @@ function M.find(names, opts)
test = function(p)
local t = {}
for name, type in M.dir(p) do
if names(name) and (not opts.type or opts.type == type) then
if (not opts.type or opts.type == type) and names(name, p) then
table.insert(t, join_paths(p, name))
end
end
Expand Down Expand Up @@ -250,7 +273,7 @@ function M.find(names, opts)
for other, type_ in M.dir(dir) do
local f = join_paths(dir, other)
if type(names) == 'function' then
if names(other) and (not opts.type or opts.type == type_) then
if (not opts.type or opts.type == type_) and names(other, dir) then
if add(f) then
return matches
end
Expand Down
11 changes: 11 additions & 0 deletions test/functional/lua/fs_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local mkdir_p = helpers.mkdir_p
local rmdir = helpers.rmdir
local nvim_dir = helpers.nvim_dir
local test_build_dir = helpers.test_build_dir
local test_source_path = helpers.test_source_path
local nvim_prog = helpers.nvim_prog
local is_os = helpers.is_os

Expand Down Expand Up @@ -252,6 +253,16 @@ describe('vim.fs', function()
local opts = { path = dir, upward = true, type = 'directory' }
return vim.fs.find(function(x) return x == 'no-match' end, opts)
]], nvim_dir))
eq(
exec_lua([[
local dir = ...
return vim.tbl_map(vim.fs.basename, vim.fn.glob(dir..'/contrib/*', false, true))
]], test_source_path),
exec_lua([[
local dir = ...
local opts = { path = dir, limit = math.huge }
return vim.tbl_map(vim.fs.basename, vim.fs.find(function(_, d) return d:match('[\\/]contrib$') end, opts))
]], test_source_path))
end)
end)

Expand Down

0 comments on commit b627919

Please sign in to comment.