From 17c18efbe561d51ccf7568763e1056fb906f25f3 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 17 Jun 2025 14:10:57 -0700 Subject: [PATCH] fix(lsp): support v:count in selection_range() #34551 Co-authored-by: Yi Ming --- runtime/doc/lsp.txt | 6 +- runtime/doc/news.txt | 3 +- runtime/lua/vim/_defaults.lua | 8 +- runtime/lua/vim/lsp/buf.lua | 21 ++- .../plugin/lsp/selection_range_spec.lua | 122 ++++++++++++++++++ 5 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 test/functional/plugin/lsp/selection_range_spec.lua diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index c437c51649..4ae472b11b 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1839,11 +1839,11 @@ rename({new_name}, {opts}) *vim.lsp.buf.rename()* selection_range({direction}) *vim.lsp.buf.selection_range()* Perform an incremental selection at the cursor position based on ranges - given by the LSP. The `direction` parameter specifies whether the - selection should head inward or outward. + given by the LSP. The `direction` parameter specifies the number of times + to expand the selection. Negative values will shrink the selection. Parameters: ~ - • {direction} (`'inner'|'outer'`) + • {direction} (`integer`) signature_help({config}) *vim.lsp.buf.signature_help()* Displays signature information about the symbol under the cursor in a diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 9de08bef81..8738e9f78a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -24,7 +24,8 @@ EXPERIMENTS LSP -• todo +• |vim.lsp.buf.selection_range()| now accepts an integer which specifies its + direction, rather than a string `'outer'` or `'inner'`. OPTIONS diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index e945596ae8..aa3eb57254 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -210,12 +210,12 @@ do end, { desc = 'vim.lsp.buf.implementation()' }) vim.keymap.set('x', 'an', function() - vim.lsp.buf.selection_range('outer') - end, { desc = "vim.lsp.buf.selection_range('outer')" }) + vim.lsp.buf.selection_range(vim.v.count1) + end, { desc = 'vim.lsp.buf.selection_range(vim.v.count1)' }) vim.keymap.set('x', 'in', function() - vim.lsp.buf.selection_range('inner') - end, { desc = "vim.lsp.buf.selection_range('inner')" }) + vim.lsp.buf.selection_range(-vim.v.count1) + end, { desc = 'vim.lsp.buf.selection_range(-vim.v.count1)' }) vim.keymap.set('n', 'gO', function() vim.lsp.buf.document_symbol() diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index e3388fb1ff..9511db9b99 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1413,20 +1413,16 @@ local function is_empty(range) end --- Perform an incremental selection at the cursor position based on ranges given by the LSP. The ---- `direction` parameter specifies whether the selection should head inward or outward. +--- `direction` parameter specifies the number of times to expand the selection. Negative values +--- will shrink the selection. --- ---- @param direction 'inner' | 'outer' +--- @param direction integer function M.selection_range(direction) - validate('direction', direction, function(v) - return v == 'inner' or v == 'outer' - end) + validate('direction', direction, 'number') if selection_ranges then - local offset = direction == 'outer' and 1 or -1 - local new_index = selection_ranges.index + offset - if new_index <= #selection_ranges.ranges and new_index >= 1 then - selection_ranges.index = new_index - end + local new_index = selection_ranges.index + direction + selection_ranges.index = math.min(#selection_ranges.ranges, math.max(1, new_index)) select_range(selection_ranges.ranges[selection_ranges.index]) return @@ -1498,8 +1494,9 @@ function M.selection_range(direction) }) if #ranges > 0 then - selection_ranges = { index = 1, ranges = ranges } - select_range(ranges[1]) + local index = math.min(#ranges, math.max(1, direction)) + selection_ranges = { index = index, ranges = ranges } + select_range(ranges[index]) end end ) diff --git a/test/functional/plugin/lsp/selection_range_spec.lua b/test/functional/plugin/lsp/selection_range_spec.lua new file mode 100644 index 0000000000..074b1a2992 --- /dev/null +++ b/test/functional/plugin/lsp/selection_range_spec.lua @@ -0,0 +1,122 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local t_lsp = require('test.functional.plugin.lsp.testutil') +local Screen = require('test.functional.ui.screen') + +local dedent = t.dedent +local exec_lua = n.exec_lua +local insert = n.insert + +local clear_notrace = t_lsp.clear_notrace +local create_server_definition = t_lsp.create_server_definition + +describe('vim.lsp.selection_range', function() + local text = dedent([[ + hello + hello + hello + hello + hello]]) + + --- @type test.functional.ui.screen + local screen + + before_each(function() + clear_notrace() + screen = Screen.new(50, 9) + + exec_lua(create_server_definition) + exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + selectionRangeProvider = true, + }, + handlers = { + ['textDocument/selectionRange'] = function(_, _, callback) + callback(nil, { + { + range = { + start = { line = 2, character = 0 }, + ['end'] = { line = 2, character = 5 }, + }, + parent = { + range = { + start = { line = 1, character = 0 }, + ['end'] = { line = 3, character = 5 }, + }, + parent = { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 5, character = 5 }, + }, + parent = nil, + }, + }, + }, + }) + end, + }, + }) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + + insert(text) + end) + + it('selects ranges', function() + -- Initial range + exec_lua(function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_cursor(win, { 3, 0 }) + vim.lsp.buf.selection_range(1) + end) + + screen:expect([[ + hello |*2 + {17:hell}^o | + hello |*2 + {1:~ }|*3 + {5:-- VISUAL --} | + ]]) + + -- Outermost range + exec_lua(function() + vim.lsp.buf.selection_range(99) + end) + + screen:expect([[ + {17:hello} |*4 + {17:hell}^o | + {1:~ }|*3 + {5:-- VISUAL --} | + ]]) + + -- Back to innermost + exec_lua(function() + vim.lsp.buf.selection_range(-99) + end) + + screen:expect([[ + hello |*2 + {17:hell}^o | + hello |*2 + {1:~ }|*3 + {5:-- VISUAL --} | + ]]) + + -- Middle range + exec_lua(function() + vim.lsp.buf.selection_range(1) + end) + + screen:expect([[ + hello | + {17:hello} |*2 + {17:hell}^o | + hello | + {1:~ }|*3 + {5:-- VISUAL --} | + ]]) + end) +end)