feat(lsp): add vim.lsp.buf.format (#18193)

This commit is contained in:
Michael Lingelbach 2022-04-30 06:36:40 -07:00 committed by GitHub
parent eecc6535eb
commit 5b04e46d23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 21 deletions

View File

@ -1045,11 +1045,52 @@ execute_command({command_params}) *vim.lsp.buf.execute_command()*
See also: ~
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
format({options}) *vim.lsp.buf.format()*
Formats a buffer using the attached (and optionally filtered)
language server clients.
Parameters: ~
{options} table|nil Optional table which holds the
following optional fields:
• formatting_options (table|nil): Can be used
to specify FormattingOptions. Some
unspecified options will be automatically
derived from the current Neovim options.
See also: ~
https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
• timeout_ms (integer|nil, default 1000): Time in
milliseconds to block for formatting requests.
Formatting requests are current synchronous to prevent
editing of the buffer.
• bufnr (number|nil): Restrict formatting to the clients
attached to the given buffer, defaults to the current
buffer (0).
• filter (function|nil): Predicate to filter clients used
for formatting. Receives the list of clients attached to
bufnr as the argument and must return the list of
clients on which to request formatting. Example: • >
-- Never request typescript-language-server for formatting
vim.lsp.buf.format {
filter = function(clients)
return vim.tbl_filter(
function(client) return client.name ~= "tsserver" end,
clients
)
end
}
<
• id (number|nil): Restrict formatting to the client with
ID (client.id) matching this field.
• name (string|nil): Restrict formatting to the client
with name (client.name) matching this field.
formatting({options}) *vim.lsp.buf.formatting()*
Formats the current buffer.
Parameters: ~
{options} (optional, table) Can be used to specify
{options} (table|nil) Can be used to specify
FormattingOptions. Some unspecified options
will be automatically derived from the current
Neovim options.
@ -1073,15 +1114,13 @@ formatting_seq_sync({options}, {timeout_ms}, {order})
<
Parameters: ~
{options} (optional, table) `FormattingOptions`
entries
{timeout_ms} (optional, number) Request timeout
{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.
{options} (table|nil) `FormattingOptions` entries
{timeout_ms} (number|nil) Request timeout
{order} (table|nil) 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.
*vim.lsp.buf.formatting_sync()*
formatting_sync({options}, {timeout_ms})
@ -1096,7 +1135,8 @@ formatting_sync({options}, {timeout_ms})
<
Parameters: ~
{options} Table with valid `FormattingOptions` entries
{options} table|nil with valid `FormattingOptions`
entries
{timeout_ms} (number) Request timeout
See also: ~
@ -1471,8 +1511,7 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
Returns indentation size.
Parameters: ~
{bufnr} (optional, number): Buffer handle, defaults to
current
{bufnr} (number|nil): Buffer handle, defaults to current
Return: ~
(number) indentation size
@ -1548,7 +1587,8 @@ make_formatting_params({options})
buffer and cursor position.
Parameters: ~
{options} Table with valid `FormattingOptions` entries
{options} table|nil with valid `FormattingOptions`
entries
Return: ~
`DocumentFormattingParams` object

View File

@ -143,9 +143,85 @@ local function select_client(method, on_choice)
end
end
--- Formats a buffer using the attached (and optionally filtered) language
--- server clients.
---
--- @param options table|nil Optional table which holds the following optional fields:
--- - formatting_options (table|nil):
--- Can be used to specify FormattingOptions. Some unspecified options will be
--- automatically derived from the current Neovim options.
--- @see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
--- - timeout_ms (integer|nil, default 1000):
--- Time in milliseconds to block for formatting requests. Formatting requests are current
--- synchronous to prevent editing of the buffer.
--- - bufnr (number|nil):
--- Restrict formatting to the clients attached to the given buffer, defaults to the current
--- buffer (0).
--- - filter (function|nil):
--- Predicate to filter clients used for formatting. Receives the list of clients attached
--- to bufnr as the argument and must return the list of clients on which to request
--- formatting. Example:
---
--- <pre>
--- -- Never request typescript-language-server for formatting
--- vim.lsp.buf.format {
--- filter = function(clients)
--- return vim.tbl_filter(
--- function(client) return client.name ~= "tsserver" end,
--- clients
--- )
--- end
--- }
--- </pre>
---
--- - id (number|nil):
--- Restrict formatting to the client with ID (client.id) matching this field.
--- - name (string|nil):
--- Restrict formatting to the client with name (client.name) matching this field.
function M.format(options)
options = options or {}
local bufnr = options.bufnr or vim.api.nvim_get_current_buf()
local clients = vim.lsp.buf_get_clients(bufnr)
if options.filter then
clients = options.filter(clients)
elseif options.id then
clients = vim.tbl_filter(
function(client) return client.id == options.id end,
clients
)
elseif options.name then
clients = vim.tbl_filter(
function(client) return client.name == options.name end,
clients
)
end
clients = vim.tbl_filter(
function(client) return client.supports_method("textDocument/formatting") end,
clients
)
if #clients == 0 then
vim.notify("[LSP] Format request failed, no matching language servers.")
end
local timeout_ms = options.timeout_ms or 1000
for _, client in pairs(clients) do
local params = util.make_formatting_params(options.formatting_options)
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, bufnr)
if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
vim.notify(string.format("[LSP][%s] %s", client.name, err), vim.log.levels.WARN)
end
end
end
--- Formats the current buffer.
---
---@param options (optional, table) Can be used to specify FormattingOptions.
---@param options (table|nil) Can be used to specify FormattingOptions.
--- Some unspecified options will be automatically derived from the current
--- Neovim options.
--
@ -171,10 +247,11 @@ end
--- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()
--- </pre>
---
---@param options Table with valid `FormattingOptions` entries
---@param options table|nil with valid `FormattingOptions` entries
---@param timeout_ms (number) Request timeout
---@see |vim.lsp.buf.formatting_seq_sync|
function M.formatting_sync(options, timeout_ms)
vim.notify_once('vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead', vim.log.levels.WARN)
local params = util.make_formatting_params(options)
local bufnr = vim.api.nvim_get_current_buf()
select_client('textDocument/formatting', function(client)
@ -202,12 +279,13 @@ end
--- 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
---@param options (table|nil) `FormattingOptions` entries
---@param timeout_ms (number|nil) Request timeout
---@param order (table|nil) 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)
vim.notify_once('vim.lsp.buf.formatting_seq_sync is deprecated. Use vim.lsp.buf.format instead', vim.log.levels.WARN)
local clients = vim.tbl_values(vim.lsp.buf_get_clients());
local bufnr = vim.api.nvim_get_current_buf()

View File

@ -1873,7 +1873,7 @@ end
--- Returns indentation size.
---
---@see |shiftwidth|
---@param bufnr (optional, number): Buffer handle, defaults to current
---@param bufnr (number|nil): Buffer handle, defaults to current
---@returns (number) indentation size
function M.get_effective_tabstop(bufnr)
validate { bufnr = {bufnr, 'n', true} }
@ -1884,7 +1884,7 @@ end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
---
---@param options Table with valid `FormattingOptions` entries
---@param options table|nil with valid `FormattingOptions` entries
---@returns `DocumentFormattingParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options)

View File

@ -744,6 +744,26 @@ function tests.clientside_commands()
}
end
function tests.basic_formatting()
skeleton {
on_init = function()
return {
capabilities = {
documentFormattingProvider = true,
}
}
end;
body = function()
notify('start')
expect_request('textDocument/formatting', function()
return nil, {}
end)
notify('shutdown')
end;
}
end
-- Tests will be indexed by TEST_NAME
local kill_timer = vim.loop.new_timer()

View File

@ -2773,4 +2773,65 @@ describe('LSP', function()
}
end)
end)
describe("vim.lsp.buf.format", function()
it("Aborts with notify if no client matches filter", function()
local client
test_rpc_server {
test_name = "basic_init",
on_init = function(c)
client = c
end,
on_handler = function()
local notify_msg = exec_lua([[
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
local notify_msg
local notify = vim.notify
vim.notify = function(msg, log_level)
notify_msg = msg
end
vim.lsp.buf.format({ name = 'does-not-exist' })
vim.notify = notify
return notify_msg
]])
eq("[LSP] Format request failed, no matching language servers.", notify_msg)
client.stop()
end,
}
end)
it("Sends textDocument/formatting request to format buffer", function()
local expected_handlers = {
{NIL, {}, {method="shutdown", client_id=1}};
{NIL, {}, {method="start", client_id=1}};
}
local client
test_rpc_server {
test_name = "basic_formatting",
on_init = function(c)
client = c
end,
on_handler = function(_, _, ctx)
table.remove(expected_handlers)
if ctx.method == "start" then
local notify_msg = exec_lua([[
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
local notify_msg
local notify = vim.notify
vim.notify = function(msg, log_level)
notify_msg = msg
end
vim.lsp.buf.format({ bufnr = bufnr })
vim.notify = notify
return notify_msg
]])
eq(NIL, notify_msg)
elseif ctx.method == "shutdown" then
client.stop()
end
end,
}
end)
end)
end)