aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lsp.txt5
-rw-r--r--runtime/lua/vim/lsp/buf.lua82
-rw-r--r--test/functional/plugin/lsp_spec.lua48
3 files changed, 110 insertions, 25 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 3393a1a1fd..a3d21cc6f7 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1136,6 +1136,11 @@ format({options}) *vim.lsp.buf.format()*
ID (client.id) matching this field.
• name (string|nil): Restrict formatting to the client with
name (client.name) matching this field.
+ • range (table|nil) Range to format. Table must contain
+ `start` and `end` keys with {row, col} tuples using (1,0)
+ indexing. Defaults to current selection in visual mode
+ Defaults to `nil` in other modes, formatting the full
+ buffer
formatting({options}) *vim.lsp.buf.formatting()*
Formats the current buffer.
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 6a070928d9..2ce565e1d9 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -150,6 +150,33 @@ local function select_client(method, on_choice)
end
end
+---@private
+---@return table {start={row, col}, end={row, col}} using (1, 0) indexing
+local function range_from_selection()
+ -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896
+
+ -- [bufnum, lnum, col, off]; both row and column 1-indexed
+ local start = vim.fn.getpos('v')
+ local end_ = vim.fn.getpos('.')
+ local start_row = start[2]
+ local start_col = start[3]
+ local end_row = end_[2]
+ local end_col = end_[3]
+
+ -- A user can start visual selection at the end and move backwards
+ -- Normalize the range to start < end
+ if start_row == end_row and end_col < start_col then
+ end_col, start_col = start_col, end_col
+ elseif end_row < start_row then
+ start_row, end_row = end_row, start_row
+ start_col, end_col = end_col, start_col
+ end
+ return {
+ ['start'] = { start_row, start_col - 1 },
+ ['end'] = { end_row, end_col - 1 },
+ }
+end
+
--- Formats a buffer using the attached (and optionally filtered) language
--- server clients.
---
@@ -184,7 +211,12 @@ end
--- 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.
-
+---
+--- - range (table|nil) Range to format.
+--- Table must contain `start` and `end` keys with {row, col} tuples using
+--- (1,0) indexing.
+--- Defaults to current selection in visual mode
+--- Defaults to `nil` in other modes, formatting the full buffer
function M.format(options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
@@ -206,16 +238,32 @@ function M.format(options)
vim.notify('[LSP] Format request failed, no matching language servers.')
end
+ local mode = api.nvim_get_mode().mode
+ local range = options.range
+ if not range and mode == 'v' or mode == 'V' then
+ range = range_from_selection()
+ end
+
+ ---@private
+ 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
+ end
+ return params
+ end
+
+ local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting'
if options.async then
local do_format
do_format = function(idx, client)
if not client then
return
end
- local params = util.make_formatting_params(options.formatting_options)
- client.request('textDocument/formatting', params, function(...)
- local handler = client.handlers['textDocument/formatting']
- or vim.lsp.handlers['textDocument/formatting']
+ local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ client.request(method, params, function(...)
+ local handler = client.handlers[method] or vim.lsp.handlers[method]
handler(...)
do_format(next(clients, idx))
end, bufnr)
@@ -224,8 +272,8 @@ function M.format(options)
else
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)
+ local params = set_range(client, util.make_formatting_params(options.formatting_options))
+ local result, err = client.request_sync(method, params, timeout_ms, bufnr)
if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
@@ -356,6 +404,7 @@ end
---@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_formatting(options, start_pos, end_pos)
+ vim.deprecate('vim.lsp.buf.range_formatting', 'vim.lsp.formatexpr or vim.lsp.format', '0.9.0')
local params = util.make_given_range_params(start_pos, end_pos)
params.options = util.make_formatting_params(options).options
select_client('textDocument/rangeFormatting', function(client)
@@ -885,23 +934,8 @@ function M.code_action(options)
local end_ = assert(options.range['end'], 'range must have a `end` property')
params = util.make_given_range_params(start, end_)
elseif mode == 'v' or mode == 'V' then
- -- [bufnum, lnum, col, off]; both row and column 1-indexed
- local start = vim.fn.getpos('v')
- local end_ = vim.fn.getpos('.')
- local start_row = start[2]
- local start_col = start[3]
- local end_row = end_[2]
- local end_col = end_[3]
-
- -- A user can start visual selection at the end and move backwards
- -- Normalize the range to start < end
- if start_row == end_row and end_col < start_col then
- end_col, start_col = start_col, end_col
- elseif end_row < start_row then
- start_row, end_row = end_row, start_row
- start_col, end_col = end_col, start_col
- end
- params = util.make_given_range_params({ start_row, start_col - 1 }, { end_row, end_col - 1 })
+ local range = range_from_selection()
+ params = util.make_given_range_params(range.start, range['end'])
else
params = util.make_range_params()
end
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index fa731f6faf..9da2c4aa61 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -3180,8 +3180,54 @@ describe('LSP', function()
end,
}
end)
+ it('format formats range in visual mode', function()
+ local result = exec_lua([[
+ local messages = {}
+ local server = function(dispatchers)
+ local closing = false
+ return {
+ request = function(method, params, callback)
+ table.insert(messages, {
+ method = method,
+ params = params,
+ })
+ if method == 'initialize' then
+ callback(nil, {
+ capabilities = {
+ documentFormattingProvider = true,
+ documentRangeFormattingProvider = true,
+ }
+ })
+ end
+ end,
+ notify = function(...)
+ end,
+ is_closing = function()
+ return closing
+ end,
+ terminate = function()
+ closing = true
+ end
+ }
+ end
+ local bufnr = vim.api.nvim_get_current_buf()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server })
+ vim.api.nvim_win_set_buf(0, bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'})
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
+ vim.cmd.normal('v')
+ vim.api.nvim_win_set_cursor(0, { 2, 3 })
+ vim.lsp.buf.format({ bufnr = bufnr, false })
+ return messages
+ ]])
+ eq("textDocument/rangeFormatting", result[2].method)
+ local expected_range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 1, character = 4 },
+ }
+ eq(expected_range, result[2].params.range)
+ end)
end)
-
describe('cmd', function()
it('can connect to lsp server via rpc.connect', function()
local result = exec_lua [[