fix(lsp): support -1 column in format range #40570

Problem:
`vim.lsp.buf.format()` accepts ranges using nvim indexing, where an
end column of -1 means end of line. LSP ranges cannot use that,
which is confusing for things like range formatting.

Solution:
Resolve -1 end columns to the line length before converting the range to
LSP positions.
This commit is contained in:
Barrett Ruth
2026-07-05 05:14:55 -05:00
committed by GitHub
parent 0f86ea5ed8
commit 81e01a80b9
3 changed files with 59 additions and 4 deletions

View File

@@ -1595,9 +1595,11 @@ format({opts}) *vim.lsp.buf.format()*
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.
using (1,0) indexing. The end column can be -1 to format
through the end of the line. Can also be a list of tables
that contain `start` and `end` keys as described above, in
which case `textDocument/rangesFormatting` support is
required.
• {timeout_ms}? (`integer`, default: `1000`) Time in
milliseconds to block for formatting requests. No effect if
async=true.

View File

@@ -611,7 +611,7 @@ end
---
--- Range to format.
--- Table must contain `start` and `end` keys with {row,col} tuples using
--- (1,0) indexing.
--- (1,0) indexing. The end column can be -1 to format through the end of the line.
--- 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,
@@ -664,6 +664,13 @@ function M.format(opts)
local function set_range(client, params)
--- @param r {start:[integer,integer],end:[integer, integer]}
local function to_lsp_range(r)
if r['end'][2] == -1 then
local end_line = api.nvim_buf_get_lines(bufnr, r['end'][1] - 1, r['end'][1], true)[1]
return {
start = vim.pos(bufnr, r.start[1] - 1, r.start[2]):to_lsp(client.offset_encoding),
['end'] = vim.pos(bufnr, r['end'][1] - 1, #end_line):to_lsp(client.offset_encoding),
}
end
return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range
end

View File

@@ -1343,6 +1343,52 @@ describe('vim.lsp.buf', function()
}
end)
it('formats range ending with -1 column', function()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
capabilities = {
documentRangeFormattingProvider = true,
},
handlers = {
['textDocument/rangeFormatting'] = function(_, params, callback)
callback(nil, {
{
range = params.range,
newText = 'function fn(abc)\n print("hello")',
},
})
end,
},
})
local bufnr = vim.api.nvim_get_current_buf()
local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd }))
vim.api.nvim_win_set_buf(0, bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {
'function fn(abc )',
' print("hello" )',
'end',
})
vim.lsp.buf.format({
bufnr = bufnr,
range = {
start = { 1, 0 },
['end'] = { 2, -1 },
},
})
vim.lsp.get_client_by_id(client_id):stop()
return {
lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true),
range = server.messages[3].params.range,
}
end)
eq({
start = { line = 0, character = 0 },
['end'] = { line = 1, character = 17 },
}, result.range)
eq({ 'function fn(abc)', ' print("hello")', 'end' }, result.lines)
end)
it('sends textDocument/rangesFormatting request to format multiple ranges', function()
local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } },