commit 2f0fbdaa480c5b014b68bfdb85aa105d0bef3637
parent 2d980e37c836a52074bea59bbeeed9b9062c26db
Author: Siddhant Agarwal <68201519+siddhantdev@users.noreply.github.com>
Date: Mon, 9 Jun 2025 22:01:37 +0530
feat(vim.fs): root() can specify "equal priority" #34276
Diffstat:
5 files changed, 80 insertions(+), 21 deletions(-)
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
@@ -3216,6 +3216,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: ~
@@ -3225,10 +3229,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
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -171,6 +171,7 @@ LUA
• Lua type annotations for `vim.uv`.
• |vim.hl.range()| now allows multiple timed highlights.
• |vim.tbl_extend()| and |vim.tbl_deep_extend()| now accept a function behavior argument.
+• |vim.fs.root()| can define "equal priority" via nested lists.
OPTIONS
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
@@ -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
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
@@ -726,13 +726,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
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
@@ -366,6 +366,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()