mirror of
https://github.com/neovim/neovim.git
synced 2025-12-13 01:52:55 +00:00
feat(lsp): incremental-selection operator-pending mode #36575
Problem: LSP incremental selection provides default visual-mode keymaps for `an` and `in`. Operator-pending mode is not supported, so `dan` and `can` do not apply the operation. Solution: Modify selection_range() to be synchronous. Add operator-pending mappings.
This commit is contained in:
@@ -83,7 +83,7 @@ These GLOBAL keymaps are created unconditionally when Nvim starts:
|
||||
- "grt" is mapped to |vim.lsp.buf.type_definition()|
|
||||
- "gO" is mapped to |vim.lsp.buf.document_symbol()|
|
||||
- CTRL-S (Insert mode) is mapped to |vim.lsp.buf.signature_help()|
|
||||
- "an" and "in" (Visual mode) are mapped to outer and inner incremental
|
||||
- "an" and "in" (Visual and Operator-pending mode) are mapped to outer and inner incremental
|
||||
selections, respectively, using |vim.lsp.buf.selection_range()|
|
||||
|
||||
BUFFER-LOCAL DEFAULTS
|
||||
@@ -1512,13 +1512,16 @@ rename({new_name}, {opts}) *vim.lsp.buf.rename()*
|
||||
ones where client.name matches this field.
|
||||
• {bufnr}? (`integer`) (default: current buffer)
|
||||
|
||||
selection_range({direction}) *vim.lsp.buf.selection_range()*
|
||||
*vim.lsp.buf.selection_range()*
|
||||
selection_range({direction}, {timeout_ms})
|
||||
Perform an incremental selection at the cursor position based on ranges
|
||||
given by the LSP. The `direction` parameter specifies the number of times
|
||||
to expand the selection. Negative values will shrink the selection.
|
||||
|
||||
Parameters: ~
|
||||
• {direction} (`integer`)
|
||||
• {direction} (`integer`)
|
||||
• {timeout_ms} (`integer?`) (default: `1000`) Maximum time
|
||||
(milliseconds) to wait for a result.
|
||||
|
||||
signature_help({config}) *vim.lsp.buf.signature_help()*
|
||||
Displays signature information about the symbol under the cursor in a
|
||||
|
||||
@@ -227,6 +227,14 @@ do
|
||||
vim.lsp.buf.selection_range(-vim.v.count1)
|
||||
end, { desc = 'vim.lsp.buf.selection_range(-vim.v.count1)' })
|
||||
|
||||
vim.keymap.set('o', 'an', function()
|
||||
vim.lsp.buf.selection_range(vim.v.count1, 1000)
|
||||
end, { desc = 'vim.lsp.buf.selection_range(vim.v.count1, timeout_ms)' })
|
||||
|
||||
vim.keymap.set('o', 'in', function()
|
||||
vim.lsp.buf.selection_range(-vim.v.count1, 1000)
|
||||
end, { desc = 'vim.lsp.buf.selection_range(-vim.v.count1, timeout_ms)' })
|
||||
|
||||
vim.keymap.set('n', 'gO', function()
|
||||
vim.lsp.buf.document_symbol()
|
||||
end, { desc = 'vim.lsp.buf.document_symbol()' })
|
||||
|
||||
@@ -1408,8 +1408,10 @@ end
|
||||
--- will shrink the selection.
|
||||
---
|
||||
--- @param direction integer
|
||||
function M.selection_range(direction)
|
||||
--- @param timeout_ms integer? (default: `1000`) Maximum time (milliseconds) to wait for a result.
|
||||
function M.selection_range(direction, timeout_ms)
|
||||
validate('direction', direction, 'number')
|
||||
validate('timeout_ms', timeout_ms, 'number', true)
|
||||
|
||||
if selection_ranges then
|
||||
local new_index = selection_ranges.index + direction
|
||||
@@ -1434,63 +1436,61 @@ function M.selection_range(direction)
|
||||
positions = { position_params.position },
|
||||
}
|
||||
|
||||
lsp.buf_request(
|
||||
0,
|
||||
method,
|
||||
params,
|
||||
---@param response lsp.SelectionRange[]?
|
||||
function(err, response)
|
||||
if err then
|
||||
lsp.log.error(err.code, err.message)
|
||||
return
|
||||
end
|
||||
if not response then
|
||||
return
|
||||
end
|
||||
-- We only requested one range, thus we get the first and only response here.
|
||||
response = response[1]
|
||||
local ranges = {} ---@type lsp.Range[]
|
||||
local lines = api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
timeout_ms = timeout_ms or 1000
|
||||
local result, err = lsp.buf_request_sync(0, method, params, timeout_ms)
|
||||
if err then
|
||||
lsp.log.error('selectionRange request failed: ' .. err)
|
||||
return
|
||||
end
|
||||
if not result or not result[client.id] or not result[client.id].result then
|
||||
return
|
||||
end
|
||||
if result[client.id].error then
|
||||
lsp.log.error(result[client.id].error.code, result[client.id].error.message)
|
||||
end
|
||||
|
||||
-- Populate the list of ranges from the given request.
|
||||
while response do
|
||||
local range = response.range
|
||||
if not is_empty(range) then
|
||||
local start_line = range.start.line
|
||||
local end_line = range['end'].line
|
||||
range.start.character = vim.str_byteindex(
|
||||
lines[start_line + 1] or '',
|
||||
client.offset_encoding,
|
||||
range.start.character,
|
||||
false
|
||||
)
|
||||
range['end'].character = vim.str_byteindex(
|
||||
lines[end_line + 1] or '',
|
||||
client.offset_encoding,
|
||||
range['end'].character,
|
||||
false
|
||||
)
|
||||
ranges[#ranges + 1] = range
|
||||
end
|
||||
response = response.parent
|
||||
end
|
||||
-- We only requested one range, thus we get the first and only reponse here.
|
||||
local response = assert(result[client.id].result[1]) ---@type lsp.SelectionRange
|
||||
local ranges = {} ---@type lsp.Range[]
|
||||
local lines = api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
|
||||
-- Clear selection ranges when leaving visual mode.
|
||||
api.nvim_create_autocmd('ModeChanged', {
|
||||
once = true,
|
||||
pattern = 'v*:*',
|
||||
callback = function()
|
||||
selection_ranges = nil
|
||||
end,
|
||||
})
|
||||
|
||||
if #ranges > 0 then
|
||||
local index = math.min(#ranges, math.max(1, direction))
|
||||
selection_ranges = { index = index, ranges = ranges }
|
||||
select_range(ranges[index])
|
||||
end
|
||||
-- Populate the list of ranges from the given request.
|
||||
while response do
|
||||
local range = response.range
|
||||
if not is_empty(range) then
|
||||
local start_line = range.start.line
|
||||
local end_line = range['end'].line
|
||||
range.start.character = vim.str_byteindex(
|
||||
lines[start_line + 1] or '',
|
||||
client.offset_encoding,
|
||||
range.start.character,
|
||||
false
|
||||
)
|
||||
range['end'].character = vim.str_byteindex(
|
||||
lines[end_line + 1] or '',
|
||||
client.offset_encoding,
|
||||
range['end'].character,
|
||||
false
|
||||
)
|
||||
ranges[#ranges + 1] = range
|
||||
end
|
||||
)
|
||||
response = response.parent
|
||||
end
|
||||
|
||||
-- Clear selection ranges when leaving visual mode.
|
||||
api.nvim_create_autocmd('ModeChanged', {
|
||||
once = true,
|
||||
pattern = 'v*:*',
|
||||
callback = function()
|
||||
selection_ranges = nil
|
||||
end,
|
||||
})
|
||||
|
||||
if #ranges > 0 then
|
||||
local index = math.min(#ranges, math.max(1, direction))
|
||||
selection_ranges = { index = index, ranges = ranges }
|
||||
select_range(ranges[index])
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user