backport: feat(vim.fs): vim.fs.root() can control priority #34413

feat(vim.fs): vim.fs.root() can control priority

Adds the capability of controlling the priority of searched markers in
vim.fs.root() by nesting lists.

(cherry picked from commit 0f0b96dd0f)
This commit is contained in:
Siddhant Agarwal
2025-06-10 19:49:51 +05:30
committed by GitHub
parent 718b3ffe74
commit 5d0766ddce
5 changed files with 80 additions and 21 deletions

View File

@ -3165,6 +3165,10 @@ vim.fs.root({source}, {marker}) *vim.fs.root()*
vim.fs.root(0, function(name, path)
return name:match('%.csproj$') ~= nil
end)
-- Find the first ancestor directory containing EITHER "stylua.toml" or ".luarc.json"; if
-- not found, find the first ancestor containing ".git":
vim.fs.root(0, { { 'stylua.toml', '.luarc.json' }, '.git' })
<
Attributes: ~
@ -3174,10 +3178,22 @@ vim.fs.root({source}, {marker}) *vim.fs.root()*
• {source} (`integer|string`) Buffer number (0 for current buffer) or
file path (absolute or relative to the |current-directory|)
to begin the search from.
• {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.
• {marker} (`(string|string[]|fun(name: string, path: string): boolean)[]|string|fun(name: string, path: string): boolean`)
A marker or a list of markers. A marker has one of three
types: string, a list of strings or a function. The
parameter also accepts a list of markers, each of which is
any of those three types. If a marker is a function, it is
called for each evaluated item and should return true if
{name} and {path} are a match. If a list of markers is
passed, each marker in the list is evaluated in order and
the first marker which is matched returns the parent
directory that it found. This allows listing markers with
priority. E.g. - in the following list, a parent directory
containing either 'a' or 'b' is searched for. If neither is
found, then 'c' is searched for. So, 'c' has lower priority
than 'a' and 'b' which have equal priority. >lua
marker = { { 'a', 'b' }, 'c' }
<
Return: ~
(`string?`) Directory path containing one of the given markers, or nil

View File

@ -305,6 +305,7 @@ LUA
• |vim.hl.range()| now has a optional `timeout` field which allows for multiple
timed highlights.
• |vim.text.indent()| indents/dedents text.
• |vim.fs.root()| can define "equal priority" via nested lists.
OPTIONS

View File

@ -388,14 +388,29 @@ end
--- vim.fs.root(0, function(name, path)
--- return name:match('%.csproj$') ~= nil
--- end)
---
--- -- Find the first ancestor directory containing EITHER "stylua.toml" or ".luarc.json"; if
--- -- not found, find the first ancestor containing ".git":
--- vim.fs.root(0, { { 'stylua.toml', '.luarc.json' }, '.git' })
--- ```
---
--- @since 12
--- @param source integer|string Buffer number (0 for current buffer) or file path (absolute or
--- relative to the |current-directory|) 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.
--- @param marker (string|string[]|fun(name: string, path: string): boolean)[]|string|fun(name: string, path: string): boolean A marker or a list of markers.
--- A marker has one of three types: string, a list of strings or a function. The
--- parameter also accepts a list of markers, each of which is any of those three
--- types. If a marker is a function, it is called for each evaluated item and
--- should return true if {name} and {path} are a match. If a list of markers is
--- passed, each marker in the list is evaluated in order and the first marker
--- which is matched returns the parent directory that it found. This allows
--- listing markers with priority. E.g. - in the following list, a parent directory
--- containing either 'a' or 'b' is searched for. If neither is found, then 'c' is
--- searched for. So, 'c' has lower priority than 'a' and 'b' which have equal
--- priority.
--- ```lua
--- marker = { { 'a', 'b' }, 'c' }
--- ```
--- @return string? # Directory path containing one of the given markers, or nil if no directory was
--- found.
function M.root(source, marker)
@ -415,16 +430,19 @@ function M.root(source, marker)
error('invalid type for argument "source": expected string or buffer number')
end
local paths = M.find(marker, {
upward = true,
path = vim.fn.fnamemodify(path, ':p:h'),
})
local markers = type(marker) == 'table' and marker or { marker }
for _, mark in ipairs(markers) do
local paths = M.find(mark, {
upward = true,
path = vim.fn.fnamemodify(path, ':p:h'),
})
if #paths == 0 then
return nil
if #paths ~= 0 then
return vim.fs.dirname(paths[1])
end
end
return vim.fs.dirname(paths[1])
return nil
end
--- Split a Windows path into a prefix and a body, such that the body can be processed like a POSIX

View File

@ -708,13 +708,7 @@ function lsp.start(config, opts)
validate('root_markers', opts._root_markers, 'table')
config = vim.deepcopy(config)
for _, marker in ipairs(opts._root_markers) do
local root = vim.fs.root(bufnr, marker)
if root ~= nil then
config.root_dir = root
break
end
end
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
end
if

View File

@ -357,6 +357,36 @@ describe('vim.fs', function()
)
end)
it('nested markers have equal priority', function()
local bufnr = api.nvim_get_current_buf()
eq(
vim.fs.joinpath(test_source_path, 'test/functional'),
exec_lua(
[[return vim.fs.root(..., { 'example_spec.lua', {'CMakeLists.txt', 'CMakePresets.json'}, '.luarc.json'})]],
bufnr
)
)
eq(
vim.fs.joinpath(test_source_path, 'test/functional/fixtures'),
exec_lua(
[[return vim.fs.root(..., { {'CMakeLists.txt', 'CMakePresets.json'}, 'example_spec.lua', '.luarc.json'})]],
bufnr
)
)
eq(
vim.fs.joinpath(test_source_path, 'test/functional/fixtures'),
exec_lua(
[[return vim.fs.root(..., {
function(name, _)
return name:match('%.txt$')
end,
'example_spec.lua',
'.luarc.json' })]],
bufnr
)
)
end)
it('works with a function', function()
---@type string
local result = exec_lua(function()