mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
feat(lsp): vim.lsp.buf.format() supports textDocument/rangesFormatting #27323
While this relies on a proposed LSP 3.18 feature, it's fully backwards compatible, so IMO there's no harm in adding this already. Looks like some servers already support for this e.g. - gopls: https://go-review.googlesource.com/c/tools/+/510235 - clangd: https://github.com/llvm/llvm-project/pull/80180 Fixes #27293
This commit is contained in:
@@ -191,6 +191,7 @@ won't run if your server doesn't support them.
|
|||||||
- textDocument/prepareTypeHierarchy
|
- textDocument/prepareTypeHierarchy
|
||||||
- textDocument/publishDiagnostics
|
- textDocument/publishDiagnostics
|
||||||
- textDocument/rangeFormatting
|
- textDocument/rangeFormatting
|
||||||
|
- textDocument/rangesFormatting
|
||||||
- textDocument/references
|
- textDocument/references
|
||||||
- textDocument/rename
|
- textDocument/rename
|
||||||
- textDocument/semanticTokens/full
|
- textDocument/semanticTokens/full
|
||||||
@@ -1371,11 +1372,14 @@ format({opts}) *vim.lsp.buf.format()*
|
|||||||
(client.id) matching this field.
|
(client.id) matching this field.
|
||||||
• {name}? (`string`) Restrict formatting to the client with
|
• {name}? (`string`) Restrict formatting to the client with
|
||||||
name (client.name) matching this field.
|
name (client.name) matching this field.
|
||||||
• {range}? (`{start:integer[],end:integer[]}`, default:
|
• {range}?
|
||||||
current selection in visual mode, `nil` in other modes,
|
(`{start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[]`,
|
||||||
formatting the full buffer) Range to format. Table must
|
default: current selection in visual mode, `nil` in other
|
||||||
contain `start` and `end` keys with {row,col} tuples using
|
modes, formatting the full buffer) Range to format. Table
|
||||||
(1,0) indexing.
|
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()*
|
hover() *vim.lsp.buf.hover()*
|
||||||
Displays hover information about the symbol under the cursor in a floating
|
Displays hover information about the symbol under the cursor in a floating
|
||||||
|
@@ -121,6 +121,9 @@ LSP
|
|||||||
• Completion side effects (including snippet expansion, execution of commands
|
• Completion side effects (including snippet expansion, execution of commands
|
||||||
and application of additional text edits) is now built-in.
|
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.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
|
LUA
|
||||||
|
|
||||||
|
@@ -56,6 +56,7 @@ lsp._request_name_to_capability = {
|
|||||||
[ms.workspace_symbol] = { 'workspaceSymbolProvider' },
|
[ms.workspace_symbol] = { 'workspaceSymbolProvider' },
|
||||||
[ms.textDocument_references] = { 'referencesProvider' },
|
[ms.textDocument_references] = { 'referencesProvider' },
|
||||||
[ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' },
|
[ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' },
|
||||||
|
[ms.textDocument_rangesFormatting] = { 'documentRangeFormattingProvider', 'rangesSupport' },
|
||||||
[ms.textDocument_formatting] = { 'documentFormattingProvider' },
|
[ms.textDocument_formatting] = { 'documentFormattingProvider' },
|
||||||
[ms.textDocument_completion] = { 'completionProvider' },
|
[ms.textDocument_completion] = { 'completionProvider' },
|
||||||
[ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
|
[ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
|
||||||
|
@@ -205,9 +205,11 @@ end
|
|||||||
--- Range to format.
|
--- Range to format.
|
||||||
--- Table must contain `start` and `end` keys with {row,col} tuples using
|
--- Table must contain `start` and `end` keys with {row,col} tuples using
|
||||||
--- (1,0) indexing.
|
--- (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,
|
--- (Default: current selection in visual mode, `nil` in other modes,
|
||||||
--- formatting the full buffer)
|
--- 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
|
--- Formats a buffer using the attached (and optionally filtered) language
|
||||||
--- server clients.
|
--- server clients.
|
||||||
@@ -218,10 +220,20 @@ function M.format(opts)
|
|||||||
local bufnr = opts.bufnr or api.nvim_get_current_buf()
|
local bufnr = opts.bufnr or api.nvim_get_current_buf()
|
||||||
local mode = api.nvim_get_mode().mode
|
local mode = api.nvim_get_mode().mode
|
||||||
local range = opts.range
|
local range = opts.range
|
||||||
|
-- Try to use visual selection if no range is given
|
||||||
if not range and mode == 'v' or mode == 'V' then
|
if not range and mode == 'v' or mode == 'V' then
|
||||||
range = range_from_selection(bufnr, mode)
|
range = range_from_selection(bufnr, mode)
|
||||||
end
|
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({
|
local clients = vim.lsp.get_clients({
|
||||||
id = opts.id,
|
id = opts.id,
|
||||||
@@ -241,10 +253,14 @@ function M.format(opts)
|
|||||||
--- @param params lsp.DocumentFormattingParams
|
--- @param params lsp.DocumentFormattingParams
|
||||||
--- @return lsp.DocumentFormattingParams
|
--- @return lsp.DocumentFormattingParams
|
||||||
local function set_range(client, params)
|
local function set_range(client, params)
|
||||||
if range then
|
local to_lsp_range = function(r) ---@return lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams
|
||||||
local range_params =
|
return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range
|
||||||
util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
|
end
|
||||||
params.range = range_params.range
|
|
||||||
|
if passed_multiple_ranges then
|
||||||
|
params.ranges = vim.tbl_map(to_lsp_range, range)
|
||||||
|
elseif range then
|
||||||
|
params.range = to_lsp_range(range)
|
||||||
end
|
end
|
||||||
return params
|
return params
|
||||||
end
|
end
|
||||||
|
@@ -425,6 +425,7 @@ function protocol.make_client_capabilities()
|
|||||||
},
|
},
|
||||||
rangeFormatting = {
|
rangeFormatting = {
|
||||||
dynamicRegistration = true,
|
dynamicRegistration = true,
|
||||||
|
rangesSupport = true,
|
||||||
},
|
},
|
||||||
completion = {
|
completion = {
|
||||||
dynamicRegistration = false,
|
dynamicRegistration = false,
|
||||||
|
@@ -939,6 +939,48 @@ function tests.basic_formatting()
|
|||||||
}
|
}
|
||||||
end
|
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()
|
function tests.set_defaults_all_capabilities()
|
||||||
skeleton {
|
skeleton {
|
||||||
on_init = function(_)
|
on_init = function(_)
|
||||||
|
@@ -4537,6 +4537,86 @@ describe('LSP', function()
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
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()
|
it('Can format async', function()
|
||||||
local expected_handlers = {
|
local expected_handlers = {
|
||||||
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
|
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
|
||||||
|
Reference in New Issue
Block a user