feat(fs): add vim.fs.root (#28477)

vim.fs.root() is a function for finding a project root relative to a
buffer using one or more "root markers". This is useful for LSP and
could be useful for other "projects" designs, as well as for any plugins
which work with a "projects" concept.
This commit is contained in:
Gregory Anders
2024-04-24 21:43:46 -05:00
committed by GitHub
parent 16513b3033
commit 38b9c322c9
6 changed files with 123 additions and 21 deletions

View File

@@ -197,13 +197,6 @@ end
--- Examples:
---
--- ```lua
--- -- location of Cargo.toml from the current buffer's path
--- local cargo = vim.fs.find('Cargo.toml', {
--- upward = true,
--- stop = vim.uv.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'},
@@ -334,6 +327,56 @@ function M.find(names, opts)
return matches
end
--- Find the first parent directory containing a specific "marker", relative to a buffer's
--- directory.
---
--- Example:
---
--- ```lua
--- -- Find the root of a Python project, starting from file 'main.py'
--- vim.fs.root(vim.fs.joinpath(vim.env.PWD, 'main.py'), {'pyproject.toml', 'setup.py' })
---
--- -- Find the root of a git repository
--- vim.fs.root(0, '.git')
---
--- -- Find the parent directory containing any file with a .csproj extension
--- vim.fs.root(0, function(name, path)
--- return name:match('%.csproj$') ~= nil
--- end)
--- ```
---
--- @param source integer|string Buffer number (0 for current buffer) or file path to begin the
--- search from.
--- @param marker (string|string[]|fun(name: string, path: string): boolean) A marker, or list
--- of markers, to search for. If a function, the function is called for each
--- evaluated item and should return true if {name} and {path} are a match.
--- @return string? # Directory path containing one of the given markers, or nil if no directory was
--- found.
function M.root(source, marker)
assert(source, 'missing required argument: source')
assert(marker, 'missing required argument: marker')
local path ---@type string
if type(source) == 'string' then
path = source
elseif type(source) == 'number' then
path = vim.api.nvim_buf_get_name(source)
else
error('invalid type for argument "source": expected string or buffer number')
end
local paths = M.find(marker, {
upward = true,
path = path,
})
if #paths == 0 then
return nil
end
return vim.fs.dirname(paths[1])
end
--- Split a Windows path into a prefix and a body, such that the body can be processed like a POSIX
--- path. The path must use forward slashes as path separator.
---

View File

@@ -210,7 +210,7 @@ end
--- vim.lsp.start({
--- name = 'my-server-name',
--- cmd = {'name-of-language-server-executable'},
--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
--- root_dir = vim.fs.root(0, {'pyproject.toml', 'setup.py'}),
--- })
--- ```
---
@@ -219,9 +219,9 @@ end
--- - `name` arbitrary name for the LSP client. Should be unique per language server.
--- - `cmd` command string[] or function, described at |vim.lsp.start_client()|.
--- - `root_dir` path to the project root. By default this is used to decide if an existing client
--- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the
--- root by traversing the file system upwards starting from the current directory until either
--- a `pyproject.toml` or `setup.py` file is found.
--- should be re-used. The example above uses |vim.fs.root()| and |vim.fs.dirname()| to detect
--- the root by traversing the file system upwards starting from the current directory until
--- either a `pyproject.toml` or `setup.py` file is found.
--- - `workspace_folders` list of `{ uri:string, name: string }` tables specifying the project root
--- folders used by the language server. If `nil` the property is derived from `root_dir` for
--- convenience.