mirror of
https://github.com/neovim/neovim.git
synced 2025-12-17 11:55:34 +00:00
Merge pull request #14462 from kabouzeid/feature/formatting_seq_sync
[LSP] Support for sequential formatting with multiple clients
This commit is contained in:
@@ -483,6 +483,13 @@ end
|
|||||||
--- result. You can use this with `client.cancel_request(request_id)`
|
--- result. You can use this with `client.cancel_request(request_id)`
|
||||||
--- to cancel the request.
|
--- to cancel the request.
|
||||||
---
|
---
|
||||||
|
--- - request_sync(method, params, timeout_ms, bufnr)
|
||||||
|
--- Sends a request to the server and synchronously waits for the response.
|
||||||
|
--- This is a wrapper around {client.request}
|
||||||
|
--- Returns: { err=err, result=result }, a dictionary, where `err` and `result` come from
|
||||||
|
--- the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a
|
||||||
|
--- string describing the failure reason. If the request was unsuccessful returns `nil`.
|
||||||
|
---
|
||||||
--- - notify(method, params)
|
--- - notify(method, params)
|
||||||
--- Sends a notification to an LSP server.
|
--- Sends a notification to an LSP server.
|
||||||
--- Returns: a boolean to indicate if the notification was successful. If
|
--- Returns: a boolean to indicate if the notification was successful. If
|
||||||
@@ -890,6 +897,42 @@ function lsp.start_client(config)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
--- Sends a request to the server and synchronously waits for the response.
|
||||||
|
---
|
||||||
|
--- This is a wrapper around {client.request}
|
||||||
|
---
|
||||||
|
--@param method (string) LSP method name.
|
||||||
|
--@param params (table) LSP request params.
|
||||||
|
--@param timeout_ms (number, optional, default=1000) Maximum time in
|
||||||
|
---milliseconds to wait for a result.
|
||||||
|
--@param bufnr (number) Buffer handle (0 for current).
|
||||||
|
--@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
|
||||||
|
---On timeout, cancel or error, returns `(nil, err)` where `err` is a
|
||||||
|
---string describing the failure reason. If the request was unsuccessful
|
||||||
|
---returns `nil`.
|
||||||
|
--@see |vim.lsp.buf_request_sync()|
|
||||||
|
function client.request_sync(method, params, timeout_ms, bufnr)
|
||||||
|
local request_result = nil
|
||||||
|
local function _sync_handler(err, _, result)
|
||||||
|
request_result = { err = err, result = result }
|
||||||
|
end
|
||||||
|
|
||||||
|
local success, request_id = client.request(method, params, _sync_handler,
|
||||||
|
bufnr)
|
||||||
|
if not success then return nil end
|
||||||
|
|
||||||
|
local wait_result, reason = vim.wait(timeout_ms or 1000, function()
|
||||||
|
return request_result ~= nil
|
||||||
|
end, 10)
|
||||||
|
|
||||||
|
if not wait_result then
|
||||||
|
client.cancel_request(request_id)
|
||||||
|
return nil, wait_result_reason[reason]
|
||||||
|
end
|
||||||
|
return request_result
|
||||||
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Sends a notification to an LSP server.
|
--- Sends a notification to an LSP server.
|
||||||
---
|
---
|
||||||
@@ -1289,12 +1332,12 @@ end
|
|||||||
---
|
---
|
||||||
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
|
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
|
||||||
--- Parameters are the same as |vim.lsp.buf_request()| but the return result is
|
--- Parameters are the same as |vim.lsp.buf_request()| but the return result is
|
||||||
--- different. Wait maximum of {timeout_ms} (default 100) ms.
|
--- different. Wait maximum of {timeout_ms} (default 1000) ms.
|
||||||
---
|
---
|
||||||
--@param bufnr (number) Buffer handle, or 0 for current.
|
--@param bufnr (number) Buffer handle, or 0 for current.
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@param params (optional, table) Parameters to send to the server
|
--@param params (optional, table) Parameters to send to the server
|
||||||
--@param timeout_ms (optional, number, default=100) Maximum time in
|
--@param timeout_ms (optional, number, default=1000) Maximum time in
|
||||||
--- milliseconds to wait for a result.
|
--- milliseconds to wait for a result.
|
||||||
---
|
---
|
||||||
--@returns Map of client_id:request_result. On timeout, cancel or error,
|
--@returns Map of client_id:request_result. On timeout, cancel or error,
|
||||||
@@ -1307,7 +1350,7 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
|
|||||||
request_results = it
|
request_results = it
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local wait_result, reason = vim.wait(timeout_ms or 100, function()
|
local wait_result, reason = vim.wait(timeout_ms or 1000, function()
|
||||||
return request_results ~= nil
|
return request_results ~= nil
|
||||||
end, 10)
|
end, 10)
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,39 @@ function M.completion(context)
|
|||||||
return request('textDocument/completion', params)
|
return request('textDocument/completion', params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
--- If there is more than one client that supports the given method,
|
||||||
|
--- asks the user to select one.
|
||||||
|
--
|
||||||
|
--@returns The client that the user selected or nil
|
||||||
|
local function select_client(method)
|
||||||
|
local clients = vim.tbl_values(vim.lsp.buf_get_clients());
|
||||||
|
clients = vim.tbl_filter(function (client)
|
||||||
|
return client.supports_method(method)
|
||||||
|
end, clients)
|
||||||
|
-- better UX when choices are always in the same order (between restarts)
|
||||||
|
table.sort(clients, function (a, b) return a.name < b.name end)
|
||||||
|
|
||||||
|
if #clients > 1 then
|
||||||
|
local choices = {}
|
||||||
|
for k,v in ipairs(clients) do
|
||||||
|
table.insert(choices, string.format("%d %s", k, v.name))
|
||||||
|
end
|
||||||
|
local user_choice = vim.fn.confirm(
|
||||||
|
"Select a language server:",
|
||||||
|
table.concat(choices, "\n"),
|
||||||
|
0,
|
||||||
|
"Question"
|
||||||
|
)
|
||||||
|
if user_choice == 0 then return nil end
|
||||||
|
return clients[user_choice]
|
||||||
|
elseif #clients < 1 then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return clients[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Formats the current buffer.
|
--- Formats the current buffer.
|
||||||
---
|
---
|
||||||
--@param options (optional, table) Can be used to specify FormattingOptions.
|
--@param options (optional, table) Can be used to specify FormattingOptions.
|
||||||
@@ -119,8 +152,11 @@ end
|
|||||||
--
|
--
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
|
--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
|
||||||
function M.formatting(options)
|
function M.formatting(options)
|
||||||
|
local client = select_client("textDocument/formatting")
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
local params = util.make_formatting_params(options)
|
local params = util.make_formatting_params(options)
|
||||||
return request('textDocument/formatting', params)
|
return client.request("textDocument/formatting", params)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Performs |vim.lsp.buf.formatting()| synchronously.
|
--- Performs |vim.lsp.buf.formatting()| synchronously.
|
||||||
@@ -134,14 +170,62 @@ end
|
|||||||
---
|
---
|
||||||
--@param options Table with valid `FormattingOptions` entries
|
--@param options Table with valid `FormattingOptions` entries
|
||||||
--@param timeout_ms (number) Request timeout
|
--@param timeout_ms (number) Request timeout
|
||||||
|
--@see |vim.lsp.buf.formatting_seq_sync|
|
||||||
function M.formatting_sync(options, timeout_ms)
|
function M.formatting_sync(options, timeout_ms)
|
||||||
|
local client = select_client("textDocument/formatting")
|
||||||
|
if client == nil then return end
|
||||||
|
|
||||||
local params = util.make_formatting_params(options)
|
local params = util.make_formatting_params(options)
|
||||||
local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
|
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
|
||||||
if not result or vim.tbl_isempty(result) then return end
|
if result and result.result then
|
||||||
local _, formatting_result = next(result)
|
util.apply_text_edits(result.result)
|
||||||
result = formatting_result.result
|
elseif err then
|
||||||
if not result then return end
|
vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN)
|
||||||
vim.lsp.util.apply_text_edits(result)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats the current buffer by sequentially requesting formatting from attached clients.
|
||||||
|
---
|
||||||
|
--- Useful when multiple clients with formatting capability are attached.
|
||||||
|
---
|
||||||
|
--- Since it's synchronous, can be used for running on save, to make sure buffer is formatted
|
||||||
|
--- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method.
|
||||||
|
--- Example:
|
||||||
|
--- <pre>
|
||||||
|
--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
|
||||||
|
--- </pre>
|
||||||
|
---
|
||||||
|
--@param options (optional, table) `FormattingOptions` entries
|
||||||
|
--@param timeout_ms (optional, number) Request timeout
|
||||||
|
--@param order (optional, table) List of client names. Formatting is requested from clients
|
||||||
|
---in the following order: first all clients that are not in the `order` list, then
|
||||||
|
---the remaining clients in the order as they occur in the `order` list.
|
||||||
|
function M.formatting_seq_sync(options, timeout_ms, order)
|
||||||
|
local clients = vim.tbl_values(vim.lsp.buf_get_clients());
|
||||||
|
|
||||||
|
-- sort the clients according to `order`
|
||||||
|
for _, client_name in ipairs(order or {}) do
|
||||||
|
-- if the client exists, move to the end of the list
|
||||||
|
for i, client in ipairs(clients) do
|
||||||
|
if client.name == client_name then
|
||||||
|
table.insert(clients, table.remove(clients, i))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- loop through the clients and make synchronous formatting requests
|
||||||
|
for _, client in ipairs(clients) do
|
||||||
|
if client.resolved_capabilities.document_formatting then
|
||||||
|
local params = util.make_formatting_params(options)
|
||||||
|
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
|
||||||
|
if result and result.result then
|
||||||
|
util.apply_text_edits(result.result)
|
||||||
|
elseif err then
|
||||||
|
vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Formats a given range.
|
--- Formats a given range.
|
||||||
@@ -152,15 +236,12 @@ end
|
|||||||
--@param end_pos ({number, number}, optional) mark-indexed position.
|
--@param end_pos ({number, number}, optional) mark-indexed position.
|
||||||
---Defaults to the end of the last visual selection.
|
---Defaults to the end of the last visual selection.
|
||||||
function M.range_formatting(options, start_pos, end_pos)
|
function M.range_formatting(options, start_pos, end_pos)
|
||||||
validate { options = {options, 't', true} }
|
local client = select_client("textDocument/rangeFormatting")
|
||||||
local sts = vim.bo.softtabstop;
|
if client == nil then return end
|
||||||
options = vim.tbl_extend('keep', options or {}, {
|
|
||||||
tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
|
|
||||||
insertSpaces = vim.bo.expandtab;
|
|
||||||
})
|
|
||||||
local params = util.make_given_range_params(start_pos, end_pos)
|
local params = util.make_given_range_params(start_pos, end_pos)
|
||||||
params.options = options
|
params.options = util.make_formatting_params(options).options
|
||||||
return request('textDocument/rangeFormatting', params)
|
return client.request("textDocument/rangeFormatting", params)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Renames all references to the symbol under the cursor.
|
--- Renames all references to the symbol under the cursor.
|
||||||
|
|||||||
Reference in New Issue
Block a user