diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 1119728e71..a0260f2710 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -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 diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c0692b355e..e151a859a9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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 diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 1a99627347..496d37af31 100644 --- 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 index e5036cef90..46fa481109 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -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 diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 33af25f629..7d176bcbf5 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -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()