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:
Branden Call
2025-11-24 18:10:50 -07:00
committed by GitHub
parent 60c35cc4c7
commit e82aef2e22
3 changed files with 69 additions and 58 deletions

View File

@@ -83,7 +83,7 @@ These GLOBAL keymaps are created unconditionally when Nvim starts:
- "grt" is mapped to |vim.lsp.buf.type_definition()| - "grt" is mapped to |vim.lsp.buf.type_definition()|
- "gO" is mapped to |vim.lsp.buf.document_symbol()| - "gO" is mapped to |vim.lsp.buf.document_symbol()|
- CTRL-S (Insert mode) is mapped to |vim.lsp.buf.signature_help()| - 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()| selections, respectively, using |vim.lsp.buf.selection_range()|
BUFFER-LOCAL DEFAULTS BUFFER-LOCAL DEFAULTS
@@ -1512,13 +1512,16 @@ rename({new_name}, {opts}) *vim.lsp.buf.rename()*
ones where client.name matches this field. ones where client.name matches this field.
• {bufnr}? (`integer`) (default: current buffer) • {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 Perform an incremental selection at the cursor position based on ranges
given by the LSP. The `direction` parameter specifies the number of times given by the LSP. The `direction` parameter specifies the number of times
to expand the selection. Negative values will shrink the selection. to expand the selection. Negative values will shrink the selection.
Parameters: ~ 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()* signature_help({config}) *vim.lsp.buf.signature_help()*
Displays signature information about the symbol under the cursor in a Displays signature information about the symbol under the cursor in a

View File

@@ -227,6 +227,14 @@ do
vim.lsp.buf.selection_range(-vim.v.count1) vim.lsp.buf.selection_range(-vim.v.count1)
end, { desc = '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.keymap.set('n', 'gO', function()
vim.lsp.buf.document_symbol() vim.lsp.buf.document_symbol()
end, { desc = 'vim.lsp.buf.document_symbol()' }) end, { desc = 'vim.lsp.buf.document_symbol()' })

View File

@@ -1408,8 +1408,10 @@ end
--- will shrink the selection. --- will shrink the selection.
--- ---
--- @param direction integer --- @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('direction', direction, 'number')
validate('timeout_ms', timeout_ms, 'number', true)
if selection_ranges then if selection_ranges then
local new_index = selection_ranges.index + direction local new_index = selection_ranges.index + direction
@@ -1434,21 +1436,21 @@ function M.selection_range(direction)
positions = { position_params.position }, positions = { position_params.position },
} }
lsp.buf_request( timeout_ms = timeout_ms or 1000
0, local result, err = lsp.buf_request_sync(0, method, params, timeout_ms)
method,
params,
---@param response lsp.SelectionRange[]?
function(err, response)
if err then if err then
lsp.log.error(err.code, err.message) lsp.log.error('selectionRange request failed: ' .. err)
return return
end end
if not response then if not result or not result[client.id] or not result[client.id].result then
return return
end end
-- We only requested one range, thus we get the first and only response here. if result[client.id].error then
response = response[1] lsp.log.error(result[client.id].error.code, result[client.id].error.message)
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 ranges = {} ---@type lsp.Range[]
local lines = api.nvim_buf_get_lines(0, 0, -1, false) local lines = api.nvim_buf_get_lines(0, 0, -1, false)
@@ -1489,8 +1491,6 @@ function M.selection_range(direction)
selection_ranges = { index = index, ranges = ranges } selection_ranges = { index = index, ranges = ranges }
select_range(ranges[index]) select_range(ranges[index])
end end
end
)
end end
return M return M