diff options
-rw-r--r-- | runtime/doc/lsp.txt | 68 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 88 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 4 | ||||
-rw-r--r-- | test/functional/fixtures/fake-lsp-server.lua | 20 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 61 |
5 files changed, 220 insertions, 21 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 29f8fab68e..d5ee959c36 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -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 diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 62de8d7321..59682e8a0a 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -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() diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 1f1a34b04a..77ab1d4224 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -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) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index bd59e5dc1f..5403405905 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -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() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 6cde96ceaf..6e28946cc4 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -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) |