diff options
-rw-r--r-- | runtime/doc/lsp.txt | 14 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 28 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 1 | ||||
-rw-r--r-- | test/functional/fixtures/fake-lsp-server.lua | 42 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 80 |
7 files changed, 158 insertions, 11 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index ba826f5f7e..e987f266cc 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -191,6 +191,7 @@ won't run if your server doesn't support them. - textDocument/prepareTypeHierarchy - textDocument/publishDiagnostics - textDocument/rangeFormatting +- textDocument/rangesFormatting - textDocument/references - textDocument/rename - textDocument/semanticTokens/full @@ -1371,11 +1372,14 @@ format({opts}) *vim.lsp.buf.format()* (client.id) matching this field. • {name}? (`string`) Restrict formatting to the client with name (client.name) matching this field. - • {range}? (`{start:integer[],end:integer[]}`, default: - current selection in visual mode, `nil` in other modes, - formatting the full buffer) Range to format. Table must - contain `start` and `end` keys with {row,col} tuples using - (1,0) indexing. + • {range}? + (`{start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[]`, + default: current selection in visual mode, `nil` in other + modes, formatting the full buffer) Range to format. Table + must contain `start` and `end` keys with {row,col} tuples + using (1,0) indexing. Can also be a list of tables that + contain `start` and `end` keys as described above, in which + case `textDocument/rangesFormatting` support is required. hover() *vim.lsp.buf.hover()* Displays hover information about the symbol under the cursor in a floating diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 81d2f2aa24..38be234b00 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -121,6 +121,9 @@ LSP • Completion side effects (including snippet expansion, execution of commands and application of additional text edits) is now built-in. • |vim.lsp.util.locations_to_items()| sets `end_col` and `end_lnum` fields. +• |vim.lsp.buf.format()| now supports passing a list of ranges + via the `range` parameter (this requires support for the + `textDocument/rangesFormatting` request). LUA diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 623ccdd5cd..7fe4dd8cf5 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -56,6 +56,7 @@ lsp._request_name_to_capability = { [ms.workspace_symbol] = { 'workspaceSymbolProvider' }, [ms.textDocument_references] = { 'referencesProvider' }, [ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' }, + [ms.textDocument_rangesFormatting] = { 'documentRangeFormattingProvider', 'rangesSupport' }, [ms.textDocument_formatting] = { 'documentFormattingProvider' }, [ms.textDocument_completion] = { 'completionProvider' }, [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' }, diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 299b68e134..f20730b8e6 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -205,9 +205,11 @@ end --- Range to format. --- Table must contain `start` and `end` keys with {row,col} tuples using --- (1,0) indexing. +--- Can also be a list of tables that contain `start` and `end` keys as described above, +--- in which case `textDocument/rangesFormatting` support is required. --- (Default: current selection in visual mode, `nil` in other modes, --- formatting the full buffer) ---- @field range? {start:integer[],end:integer[]} +--- @field range? {start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[] --- Formats a buffer using the attached (and optionally filtered) language --- server clients. @@ -218,10 +220,20 @@ function M.format(opts) local bufnr = opts.bufnr or api.nvim_get_current_buf() local mode = api.nvim_get_mode().mode local range = opts.range + -- Try to use visual selection if no range is given if not range and mode == 'v' or mode == 'V' then range = range_from_selection(bufnr, mode) end - local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting + + local passed_multiple_ranges = (range and #range ~= 0 and type(range[1]) == 'table') + local method ---@type string + if passed_multiple_ranges then + method = ms.textDocument_rangesFormatting + elseif range then + method = ms.textDocument_rangeFormatting + else + method = ms.textDocument_formatting + end local clients = vim.lsp.get_clients({ id = opts.id, @@ -241,10 +253,14 @@ function M.format(opts) --- @param params lsp.DocumentFormattingParams --- @return lsp.DocumentFormattingParams local function set_range(client, params) - if range then - local range_params = - util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding) - params.range = range_params.range + local to_lsp_range = function(r) ---@return lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams + return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range + end + + if passed_multiple_ranges then + params.ranges = vim.tbl_map(to_lsp_range, range) + elseif range then + params.range = to_lsp_range(range) end return params end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index eb18043843..656921644d 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -425,6 +425,7 @@ function protocol.make_client_capabilities() }, rangeFormatting = { dynamicRegistration = true, + rangesSupport = true, }, completion = { dynamicRegistration = false, diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f806869b40..f813927f77 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -939,6 +939,48 @@ function tests.basic_formatting() } end +function tests.range_formatting() + skeleton { + on_init = function() + return { + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }, + } + end, + body = function() + notify('start') + expect_request('textDocument/rangeFormatting', function() + return nil, {} + end) + notify('shutdown') + end, + } +end + +function tests.ranges_formatting() + skeleton { + on_init = function() + return { + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = { + rangesSupport = true, + }, + }, + } + end, + body = function() + notify('start') + expect_request('textDocument/rangesFormatting', function() + return nil, {} + end) + notify('shutdown') + end, + } +end + function tests.set_defaults_all_capabilities() skeleton { on_init = function(_) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index b345a3288c..be303f21ce 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -4537,6 +4537,86 @@ describe('LSP', function() end, } end) + it('Sends textDocument/rangeFormatting request to format a range', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client + test_rpc_server { + test_name = 'range_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.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'}) + 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, range = { + start = {1, 1}, + ['end'] = {1, 1}, + }}) + vim.notify = notify + return notify_msg + ]]) + eq(NIL, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + it('Sends textDocument/rangesFormatting request to format multiple ranges', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client + test_rpc_server { + test_name = 'ranges_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.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar', 'baz'}) + 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, range = { + { + start = {1, 1}, + ['end'] = {1, 1}, + }, + { + start = {2, 2}, + ['end'] = {2, 2}, + } + }}) + vim.notify = notify + return notify_msg + ]]) + eq(NIL, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) it('Can format async', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, |