feat(lsp): support function for client root_dir

If root_dir is a function it is evaluated when the client is created to
determine the root directory.

This enables dynamically determining the root directory based on e.g.
project or directory structure (example: finding a parent Cargo.toml
file that contains "[workspace]" in a Rust project).
This commit is contained in:
Gregory Anders 2024-12-18 20:34:37 -06:00
parent 02bc40c194
commit 1d6170da8e
4 changed files with 48 additions and 9 deletions

View File

@ -1317,9 +1317,10 @@ Lua module: vim.lsp.client *lsp-client*
request before sending kill -15. If set to
false, nvim exits immediately after sending
the "shutdown" request to the server.
• {root_dir}? (`string`) Directory where the LSP server will
base its workspaceFolders, rootUri, and rootPath
on initialization.
• {root_dir}? (`string|fun():string`) Directory (or a function
which returns a directory) where the LSP server
will base its workspaceFolders, rootUri, and
rootPath on initialization.
Client:cancel_request({id}) *Client:cancel_request()*

View File

@ -200,12 +200,17 @@ local function reuse_client_default(client, config)
return false
end
local config_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir)
local root_dir = config.root_dir
if type(root_dir) == 'function' then
root_dir = root_dir()
end
local config_folders = lsp._get_workspace_folders(config.workspace_folders or root_dir)
if not config_folders or not next(config_folders) then
-- Reuse if the client was configured with no workspace folders
local client_config_folders =
lsp._get_workspace_folders(client.config.workspace_folders or client.config.root_dir)
lsp._get_workspace_folders(client.config.workspace_folders or client.root_dir)
return not client_config_folders or not next(client_config_folders)
end

View File

@ -130,8 +130,9 @@ local validate = vim.validate
--- A table with flags for the client. The current (experimental) flags are:
--- @field flags? vim.lsp.Client.Flags
---
--- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on initialization.
--- @field root_dir? string
--- Directory (or a function which returns a directory) where the LSP server will base its
--- workspaceFolders, rootUri, and rootPath on initialization.
--- @field root_dir? string|fun():string
--- @class vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
--- @field pending table<lsp.ProgressToken,lsp.LSPAny>
@ -373,6 +374,11 @@ function Client.create(config)
local id = client_index
local name = get_name(id, config)
local root_dir = config.root_dir
if type(root_dir) == 'function' then
root_dir = root_dir()
end
--- @class vim.lsp.Client
local self = {
id = id,
@ -390,8 +396,8 @@ function Client.create(config)
flags = config.flags or {},
get_language_id = config.get_language_id or default_get_language_id,
capabilities = config.capabilities,
workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir),
root_dir = config.root_dir,
workspace_folders = lsp._get_workspace_folders(config.workspace_folders or root_dir),
root_dir = root_dir,
_before_init_cb = config.before_init,
_on_init_cbs = vim._ensure_list(config.on_init),
_on_exit_cbs = vim._ensure_list(config.on_exit),

View File

@ -271,6 +271,33 @@ describe('LSP', function()
}
end)
it('supports a function for root_dir', function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({})
local client_id = vim.lsp.start({
name = 'dummy',
cmd = server.cmd,
root_dir = function()
return 'some_directory'
end
})
if not client_id then
return 'vim.lsp.start did not return client_id'
end
local client = vim.lsp.get_client_by_id(client_id)
if not client then
return 'No client found with id ' .. client_id
end
return client.root_dir
end)
eq('some_directory', result)
end)
it('should send didChangeConfiguration after initialize if there are settings', function()
test_rpc_server({
test_name = 'basic_init_did_change_configuration',