mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	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:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user