mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
feat(lsp): root_markers
can control priority #33485
Problem: root_markers cannot specify "equal priority filenames. Solution: Support nesting: { ... root_markers = { { ".stylua.toml", ".luarc.json" }, { ".git "} } ... } Co-authored-by: Maria José Solano <majosolano99@gmail.com> Co-authored-by: Gregory Anders <github@gpanders.com> Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
@@ -41,7 +41,8 @@ Follow these steps to get LSP features:
|
|||||||
-- current buffer that contains either a ".luarc.json" or a
|
-- current buffer that contains either a ".luarc.json" or a
|
||||||
-- ".luarc.jsonc" file. Files that share a root directory will reuse
|
-- ".luarc.jsonc" file. Files that share a root directory will reuse
|
||||||
-- the connection to the same LSP server.
|
-- the connection to the same LSP server.
|
||||||
root_markers = { '.luarc.json', '.luarc.jsonc' },
|
-- Nested lists indicate equal priority, see |vim.lsp.Config|.
|
||||||
|
root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' },
|
||||||
|
|
||||||
-- Specific settings to send to the server. The schema for this is
|
-- Specific settings to send to the server. The schema for this is
|
||||||
-- defined by the server. For example the schema for lua-language-server
|
-- defined by the server. For example the schema for lua-language-server
|
||||||
@@ -722,9 +723,37 @@ Lua module: vim.lsp *lsp-core*
|
|||||||
the buffer. Thus a `root_dir()` function can
|
the buffer. Thus a `root_dir()` function can
|
||||||
dynamically decide per-buffer whether to activate (or
|
dynamically decide per-buffer whether to activate (or
|
||||||
skip) LSP. See example at |vim.lsp.enable()|.
|
skip) LSP. See example at |vim.lsp.enable()|.
|
||||||
• {root_markers}? (`string[]`) Directory markers (e.g. ".git/",
|
• {root_markers}? (`(string|string[])[]`) Directory markers (.e.g.
|
||||||
"package.json") used to decide `root_dir`. Unused if
|
'.git/') where the LSP server will base its
|
||||||
`root_dir` is provided.
|
workspaceFolders, rootUri, and rootPath on
|
||||||
|
initialization. Unused if `root_dir` is provided.
|
||||||
|
|
||||||
|
The list order decides the priority. To indicate
|
||||||
|
"equal priority", specify names in a nested list
|
||||||
|
(`{ { 'a', 'b' }, ... }`) Each entry in this list is
|
||||||
|
a set of one or more markers. For each set, Nvim will
|
||||||
|
search upwards for each marker contained in the set.
|
||||||
|
If a marker is found, the directory which contains
|
||||||
|
that marker is used as the root directory. If no
|
||||||
|
markers from the set are found, the process is
|
||||||
|
repeated with the next set in the list.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
root_markers = { 'stylua.toml', '.git' }
|
||||||
|
<
|
||||||
|
|
||||||
|
Find the first parent directory containing the file
|
||||||
|
`stylua.toml`. If not found, find the first parent
|
||||||
|
directory containing the file or directory `.git`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
root_markers = { { 'stylua.toml', '.luarc.json' }, '.git' }
|
||||||
|
<
|
||||||
|
|
||||||
|
Find the first parent directory containing EITHER
|
||||||
|
`stylua.toml` or `.luarc.json`. If not found, find
|
||||||
|
the first parent directory containing the file or
|
||||||
|
directory `.git`.
|
||||||
|
|
||||||
|
|
||||||
buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
|
buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
|
||||||
|
@@ -70,7 +70,7 @@ HIGHLIGHTS
|
|||||||
|
|
||||||
LSP
|
LSP
|
||||||
|
|
||||||
• todo
|
• `root_markers` in |vim.lsp.Config| can now be ordered by priority.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
@@ -293,9 +293,37 @@ end
|
|||||||
--- example at |vim.lsp.enable()|.
|
--- example at |vim.lsp.enable()|.
|
||||||
--- @field root_dir? string|fun(bufnr: integer, on_dir:fun(root_dir?:string))
|
--- @field root_dir? string|fun(bufnr: integer, on_dir:fun(root_dir?:string))
|
||||||
---
|
---
|
||||||
--- Directory markers (e.g. ".git/", "package.json") used to decide `root_dir`. Unused if `root_dir`
|
--- Directory markers (.e.g. '.git/') where the LSP server will base its workspaceFolders,
|
||||||
--- is provided.
|
--- rootUri, and rootPath on initialization. Unused if `root_dir` is provided.
|
||||||
--- @field root_markers? string[]
|
---
|
||||||
|
--- The list order decides the priority. To indicate "equal priority", specify names in a nested list (`{ { 'a', 'b' }, ... }`)
|
||||||
|
--- Each entry in this list is a set of one or more markers. For each set, Nvim
|
||||||
|
--- will search upwards for each marker contained in the set. If a marker is
|
||||||
|
--- found, the directory which contains that marker is used as the root
|
||||||
|
--- directory. If no markers from the set are found, the process is repeated
|
||||||
|
--- with the next set in the list.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
---
|
||||||
|
--- ```lua
|
||||||
|
--- root_markers = { 'stylua.toml', '.git' }
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- Find the first parent directory containing the file `stylua.toml`. If not
|
||||||
|
--- found, find the first parent directory containing the file or directory
|
||||||
|
--- `.git`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
---
|
||||||
|
--- ```lua
|
||||||
|
--- root_markers = { { 'stylua.toml', '.luarc.json' }, '.git' }
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- Find the first parent directory containing EITHER `stylua.toml` or
|
||||||
|
--- `.luarc.json`. If not found, find the first parent directory containing the
|
||||||
|
--- file or directory `.git`.
|
||||||
|
---
|
||||||
|
--- @field root_markers? (string|string[])[]
|
||||||
|
|
||||||
--- Sets the default configuration for an LSP client (or _all_ clients if the special name "*" is
|
--- Sets the default configuration for an LSP client (or _all_ clients if the special name "*" is
|
||||||
--- used).
|
--- used).
|
||||||
@@ -613,7 +641,7 @@ end
|
|||||||
--- Suppress error reporting if the LSP server fails to start (default false).
|
--- Suppress error reporting if the LSP server fails to start (default false).
|
||||||
--- @field silent? boolean
|
--- @field silent? boolean
|
||||||
---
|
---
|
||||||
--- @field package _root_markers? string[]
|
--- @field package _root_markers? (string|string[])[]
|
||||||
|
|
||||||
--- Create a new LSP client and start a language server or reuses an already
|
--- Create a new LSP client and start a language server or reuses an already
|
||||||
--- running client if one is found matching `name` and `root_dir`.
|
--- running client if one is found matching `name` and `root_dir`.
|
||||||
@@ -662,8 +690,16 @@ function lsp.start(config, opts)
|
|||||||
local bufnr = vim._resolve_bufnr(opts.bufnr)
|
local bufnr = vim._resolve_bufnr(opts.bufnr)
|
||||||
|
|
||||||
if not config.root_dir and opts._root_markers then
|
if not config.root_dir and opts._root_markers then
|
||||||
|
validate('root_markers', opts._root_markers, 'table')
|
||||||
config = vim.deepcopy(config)
|
config = vim.deepcopy(config)
|
||||||
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
if
|
if
|
||||||
|
@@ -6535,5 +6535,85 @@ describe('LSP', function()
|
|||||||
vim.lsp.config('*', {})
|
vim.lsp.config('*', {})
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('correctly handles root_markers', function()
|
||||||
|
--- Setup directories for testing
|
||||||
|
-- root/
|
||||||
|
-- ├── dir_a/
|
||||||
|
-- │ ├── dir_b/
|
||||||
|
-- │ │ ├── target
|
||||||
|
-- │ │ └── marker_d
|
||||||
|
-- │ ├── marker_b
|
||||||
|
-- │ └── marker_c
|
||||||
|
-- └── marker_a
|
||||||
|
|
||||||
|
---@param filepath string
|
||||||
|
local function touch(filepath)
|
||||||
|
local file = io.open(filepath, 'w')
|
||||||
|
if file then
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tmp_root = tmpname(false)
|
||||||
|
local marker_a = tmp_root .. '/marker_a'
|
||||||
|
local dir_a = tmp_root .. '/dir_a'
|
||||||
|
local marker_b = dir_a .. '/marker_b'
|
||||||
|
local marker_c = dir_a .. '/marker_c'
|
||||||
|
local dir_b = dir_a .. '/dir_b'
|
||||||
|
local marker_d = dir_b .. '/marker_d'
|
||||||
|
local target = dir_b .. '/target'
|
||||||
|
|
||||||
|
mkdir(tmp_root)
|
||||||
|
touch(marker_a)
|
||||||
|
mkdir(dir_a)
|
||||||
|
touch(marker_b)
|
||||||
|
touch(marker_c)
|
||||||
|
mkdir(dir_b)
|
||||||
|
touch(marker_d)
|
||||||
|
touch(target)
|
||||||
|
|
||||||
|
exec_lua(create_server_definition)
|
||||||
|
exec_lua(function()
|
||||||
|
_G._custom_server = _G._create_server()
|
||||||
|
end)
|
||||||
|
|
||||||
|
---@param root_markers (string|string[])[]
|
||||||
|
---@param expected_root_dir string?
|
||||||
|
local function markers_resolve_to(root_markers, expected_root_dir)
|
||||||
|
exec_lua(function()
|
||||||
|
vim.lsp.config['foo'] = {}
|
||||||
|
vim.lsp.config('foo', {
|
||||||
|
cmd = _G._custom_server.cmd,
|
||||||
|
reuse_client = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
filetypes = { 'foo' },
|
||||||
|
root_markers = root_markers,
|
||||||
|
})
|
||||||
|
vim.lsp.enable('foo')
|
||||||
|
vim.cmd.edit(target)
|
||||||
|
vim.bo.filetype = 'foo'
|
||||||
|
end)
|
||||||
|
retry(nil, 1000, function()
|
||||||
|
eq(
|
||||||
|
expected_root_dir,
|
||||||
|
exec_lua(function()
|
||||||
|
local clients = vim.lsp.get_clients()
|
||||||
|
return clients[#clients].root_dir
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
markers_resolve_to({ 'marker_d' }, dir_b)
|
||||||
|
markers_resolve_to({ 'marker_b' }, dir_a)
|
||||||
|
markers_resolve_to({ 'marker_c' }, dir_a)
|
||||||
|
markers_resolve_to({ 'marker_a' }, tmp_root)
|
||||||
|
markers_resolve_to({ 'foo' }, nil)
|
||||||
|
markers_resolve_to({ { 'marker_b', 'marker_a' }, 'marker_d' }, dir_a)
|
||||||
|
markers_resolve_to({ 'marker_a', { 'marker_b', 'marker_d' } }, tmp_root)
|
||||||
|
markers_resolve_to({ 'foo', { 'bar', 'baz' }, 'marker_d' }, dir_b)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user