aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lsp.txt14
-rw-r--r--runtime/doc/news.txt3
-rw-r--r--runtime/lua/vim/lsp.lua1
-rw-r--r--runtime/lua/vim/lsp/buf.lua28
-rw-r--r--runtime/lua/vim/lsp/protocol.lua1
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua42
-rw-r--r--test/functional/plugin/lsp_spec.lua80
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 } },