mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
feat(lsp): workspace_required (#33608)
Problem: Some language servers do not work properly without a workspace folder. Solution: Add `workspace_required`, which skips starting the lsp client if no workspace folder is found. Co-authored-by: Michael Strobel <71396679+Kibadda@users.noreply.github.com>
This commit is contained in:
@ -1240,15 +1240,16 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
server (treated as in |jobstart()|, must be
|
server (treated as in |jobstart()|, must be
|
||||||
absolute or on `$PATH`, shell constructs like
|
absolute or on `$PATH`, shell constructs like
|
||||||
"~" are not expanded), or function that creates
|
"~" are not expanded), or function that creates
|
||||||
an RPC client. Function receives a `dispatchers`
|
an RPC client. Function receives a
|
||||||
table and returns a table with member functions
|
`dispatchers` table and returns a table with
|
||||||
`request`, `notify`, `is_closing` and
|
member functions `request`, `notify`,
|
||||||
`terminate`. See |vim.lsp.rpc.request()|,
|
`is_closing` and `terminate`. See
|
||||||
|
|vim.lsp.rpc.request()|,
|
||||||
|vim.lsp.rpc.notify()|. For TCP there is a
|
|vim.lsp.rpc.notify()|. For TCP there is a
|
||||||
builtin RPC client factory:
|
builtin RPC client factory:
|
||||||
|vim.lsp.rpc.connect()|
|
|vim.lsp.rpc.connect()|
|
||||||
• {cmd_cwd}? (`string`, default: cwd) Directory to launch the
|
• {cmd_cwd}? (`string`, default: cwd) Directory to launch
|
||||||
`cmd` process. Not related to `root_dir`.
|
the `cmd` process. Not related to `root_dir`.
|
||||||
• {cmd_env}? (`table`) Environment flags to pass to the LSP
|
• {cmd_env}? (`table`) Environment flags to pass to the LSP
|
||||||
on spawn. Must be specified using a table.
|
on spawn. Must be specified using a table.
|
||||||
Non-string values are coerced to string.
|
Non-string values are coerced to string.
|
||||||
@ -1266,23 +1267,26 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
will be derived from the first workspace folder
|
will be derived from the first workspace folder
|
||||||
in this list. See `workspaceFolders` in the LSP
|
in this list. See `workspaceFolders` in the LSP
|
||||||
spec.
|
spec.
|
||||||
|
• {workspace_required}? (`boolean`) (default false) Server requires a
|
||||||
|
workspace (no "single file" support).
|
||||||
• {capabilities}? (`lsp.ClientCapabilities`) Map overriding the
|
• {capabilities}? (`lsp.ClientCapabilities`) Map overriding the
|
||||||
default capabilities defined by
|
default capabilities defined by
|
||||||
|vim.lsp.protocol.make_client_capabilities()|,
|
|vim.lsp.protocol.make_client_capabilities()|,
|
||||||
passed to the language server on initialization.
|
passed to the language server on
|
||||||
Hint: use make_client_capabilities() and modify
|
initialization. Hint: use
|
||||||
its result.
|
make_client_capabilities() and modify its
|
||||||
|
result.
|
||||||
• Note: To send an empty dictionary use
|
• Note: To send an empty dictionary use
|
||||||
|vim.empty_dict()|, else it will be encoded as
|
|vim.empty_dict()|, else it will be encoded
|
||||||
an array.
|
as an array.
|
||||||
• {handlers}? (`table<string,function>`) Map of language
|
• {handlers}? (`table<string,function>`) Map of language
|
||||||
server method names to |lsp-handler|
|
server method names to |lsp-handler|
|
||||||
• {settings}? (`lsp.LSPObject`) Map with language server
|
• {settings}? (`lsp.LSPObject`) Map with language server
|
||||||
specific settings. See the {settings} in
|
specific settings. See the {settings} in
|
||||||
|vim.lsp.Client|.
|
|vim.lsp.Client|.
|
||||||
• {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`)
|
• {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`)
|
||||||
Table that maps string of clientside commands to
|
Table that maps string of clientside commands
|
||||||
user-defined functions. Commands passed to
|
to user-defined functions. Commands passed to
|
||||||
`start()` take precedence over the global
|
`start()` take precedence over the global
|
||||||
command registry. Each key must be a unique
|
command registry. Each key must be a unique
|
||||||
command name, and the value is a function which
|
command name, and the value is a function which
|
||||||
@ -1290,17 +1294,17 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
lenses, ...) triggers the command.
|
lenses, ...) triggers the command.
|
||||||
• {init_options}? (`lsp.LSPObject`) Values to pass in the
|
• {init_options}? (`lsp.LSPObject`) Values to pass in the
|
||||||
initialization request as
|
initialization request as
|
||||||
`initializationOptions`. See `initialize` in the
|
`initializationOptions`. See `initialize` in
|
||||||
LSP spec.
|
the LSP spec.
|
||||||
• {name}? (`string`, default: client-id) Name in log
|
• {name}? (`string`, default: client-id) Name in log
|
||||||
messages.
|
messages.
|
||||||
• {get_language_id}? (`fun(bufnr: integer, filetype: string): string`)
|
• {get_language_id}? (`fun(bufnr: integer, filetype: string): string`)
|
||||||
Language ID as string. Defaults to the buffer
|
Language ID as string. Defaults to the buffer
|
||||||
filetype.
|
filetype.
|
||||||
• {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position
|
• {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position
|
||||||
encoding" in LSP spec, the encoding that the LSP
|
encoding" in LSP spec, the encoding that the
|
||||||
server expects. Client does not verify this is
|
LSP server expects. Client does not verify this
|
||||||
correct.
|
is correct.
|
||||||
• {on_error}? (`fun(code: integer, err: string)`) Callback
|
• {on_error}? (`fun(code: integer, err: string)`) Callback
|
||||||
invoked when the client operation throws an
|
invoked when the client operation throws an
|
||||||
error. `code` is a number describing the error.
|
error. `code` is a number describing the error.
|
||||||
@ -1313,17 +1317,18 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
Callback invoked before the LSP "initialize"
|
Callback invoked before the LSP "initialize"
|
||||||
phase, where `params` contains the parameters
|
phase, where `params` contains the parameters
|
||||||
being sent to the server and `config` is the
|
being sent to the server and `config` is the
|
||||||
config that was passed to |vim.lsp.start()|. You
|
config that was passed to |vim.lsp.start()|.
|
||||||
can use this to modify parameters before they
|
You can use this to modify parameters before
|
||||||
are sent.
|
they are sent.
|
||||||
• {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`)
|
• {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`)
|
||||||
Callback invoked after LSP "initialize", where
|
Callback invoked after LSP "initialize", where
|
||||||
`result` is a table of `capabilities` and
|
`result` is a table of `capabilities` and
|
||||||
anything else the server may send. For example,
|
anything else the server may send. For example,
|
||||||
clangd sends `init_result.offsetEncoding` if
|
clangd sends `init_result.offsetEncoding` if
|
||||||
`capabilities.offsetEncoding` was sent to it.
|
`capabilities.offsetEncoding` was sent to it.
|
||||||
You can only modify the `client.offset_encoding`
|
You can only modify the
|
||||||
here before any notifications are sent.
|
`client.offset_encoding` here before any
|
||||||
|
notifications are sent.
|
||||||
• {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`)
|
• {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`)
|
||||||
Callback invoked on client exit.
|
Callback invoked on client exit.
|
||||||
• code: exit code of the process
|
• code: exit code of the process
|
||||||
@ -1336,11 +1341,11 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
• {trace}? (`'off'|'messages'|'verbose'`, default: "off")
|
• {trace}? (`'off'|'messages'|'verbose'`, default: "off")
|
||||||
Passed directly to the language server in the
|
Passed directly to the language server in the
|
||||||
initialize request. Invalid/empty values will
|
initialize request. Invalid/empty values will
|
||||||
• {flags}? (`table`) A table with flags for the client. The
|
• {flags}? (`table`) A table with flags for the client.
|
||||||
current (experimental) flags are:
|
The current (experimental) flags are:
|
||||||
• {allow_incremental_sync}? (`boolean`, default:
|
• {allow_incremental_sync}? (`boolean`,
|
||||||
`true`) Allow using incremental sync for
|
default: `true`) Allow using incremental sync
|
||||||
buffer edits
|
for buffer edits
|
||||||
• {debounce_text_changes} (`integer`, default:
|
• {debounce_text_changes} (`integer`, default:
|
||||||
`150`) Debounce `didChange` notifications to
|
`150`) Debounce `didChange` notifications to
|
||||||
the server by the given number in
|
the server by the given number in
|
||||||
@ -1352,8 +1357,8 @@ Lua module: vim.lsp.client *lsp-client*
|
|||||||
false, nvim exits immediately after sending
|
false, nvim exits immediately after sending
|
||||||
the "shutdown" request to the server.
|
the "shutdown" request to the server.
|
||||||
• {root_dir}? (`string`) Directory where the LSP server will
|
• {root_dir}? (`string`) Directory where the LSP server will
|
||||||
base its workspaceFolders, rootUri, and rootPath
|
base its workspaceFolders, rootUri, and
|
||||||
on initialization.
|
rootPath on initialization.
|
||||||
|
|
||||||
|
|
||||||
Client:cancel_request({id}) *Client:cancel_request()*
|
Client:cancel_request({id}) *Client:cancel_request()*
|
||||||
|
@ -276,6 +276,7 @@ LSP
|
|||||||
`codeAction/resolve` request.
|
`codeAction/resolve` request.
|
||||||
• The `textDocument/completion` request now includes the completion context in
|
• The `textDocument/completion` request now includes the completion context in
|
||||||
its parameters.
|
its parameters.
|
||||||
|
• |vim.lsp.Config| gained `workspace_required`.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
@ -633,6 +633,17 @@ function lsp.start(config, opts)
|
|||||||
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
|
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
not config.root_dir
|
||||||
|
and (not config.workspace_folders or #config.workspace_folders == 0)
|
||||||
|
and config.workspace_required
|
||||||
|
then
|
||||||
|
log.info(
|
||||||
|
('skipping config "%s": workspace_required=true, no workspace found'):format(config.name)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
for _, client in pairs(all_clients) do
|
for _, client in pairs(all_clients) do
|
||||||
if reuse_client(client, config) then
|
if reuse_client(client, config) then
|
||||||
if opts.attach == false then
|
if opts.attach == false then
|
||||||
|
@ -63,6 +63,9 @@ local validate = vim.validate
|
|||||||
--- folder in this list. See `workspaceFolders` in the LSP spec.
|
--- folder in this list. See `workspaceFolders` in the LSP spec.
|
||||||
--- @field workspace_folders? lsp.WorkspaceFolder[]
|
--- @field workspace_folders? lsp.WorkspaceFolder[]
|
||||||
---
|
---
|
||||||
|
--- (default false) Server requires a workspace (no "single file" support).
|
||||||
|
--- @field workspace_required? boolean
|
||||||
|
---
|
||||||
--- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
|
--- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
|
||||||
--- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
|
--- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
|
||||||
--- its result.
|
--- its result.
|
||||||
|
@ -6456,5 +6456,40 @@ describe('LSP', function()
|
|||||||
vim.lsp.config('*', {})
|
vim.lsp.config('*', {})
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('does not start without workspace if workspace_required=true', function()
|
||||||
|
exec_lua(create_server_definition)
|
||||||
|
|
||||||
|
local tmp1 = t.tmpname(true)
|
||||||
|
|
||||||
|
eq(
|
||||||
|
{ workspace_required = false },
|
||||||
|
exec_lua(function()
|
||||||
|
local server = _G._create_server({
|
||||||
|
handlers = {
|
||||||
|
initialize = function(_, _, callback)
|
||||||
|
callback(nil, { capabilities = {} })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local ws_required = { cmd = server.cmd, workspace_required = true, filetypes = { 'foo' } }
|
||||||
|
local ws_not_required = vim.deepcopy(ws_required)
|
||||||
|
ws_not_required.workspace_required = false
|
||||||
|
|
||||||
|
vim.lsp.config('ws_required', ws_required)
|
||||||
|
vim.lsp.config('ws_not_required', ws_not_required)
|
||||||
|
vim.lsp.enable('ws_required')
|
||||||
|
vim.lsp.enable('ws_not_required')
|
||||||
|
|
||||||
|
vim.cmd.edit(assert(tmp1))
|
||||||
|
vim.bo.filetype = 'foo'
|
||||||
|
|
||||||
|
local clients = vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })
|
||||||
|
assert(1 == #clients)
|
||||||
|
return { workspace_required = clients[1].config.workspace_required }
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user