mirror of
https://github.com/neovim/neovim.git
synced 2025-12-10 16:42:42 +00:00
fix(lsp): fix rename capability checks and multi client support (#18441)
Adds filter and id options to filter the client to use for rename. Similar to the recently added `format` function. rename will use all matching clients one after another and can handle a mix of prepareRename/rename support. Also ensures the right `offset_encoding` is used for the `make_position_params` calls
This commit is contained in:
committed by
GitHub
parent
d14d308ce8
commit
55187de115
@@ -1216,13 +1216,21 @@ remove_workspace_folder({workspace_folder})
|
|||||||
{path} is not provided, the user will be prompted for a path
|
{path} is not provided, the user will be prompted for a path
|
||||||
using |input()|.
|
using |input()|.
|
||||||
|
|
||||||
rename({new_name}) *vim.lsp.buf.rename()*
|
rename({new_name}, {options}) *vim.lsp.buf.rename()*
|
||||||
Renames all references to the symbol under the cursor.
|
Renames all references to the symbol under the cursor.
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
{new_name} (string) If not provided, the user will be
|
{new_name} string|nil If not provided, the user will be
|
||||||
prompted for a new name using
|
prompted for a new name using
|
||||||
|vim.ui.input()|.
|
|vim.ui.input()|.
|
||||||
|
{options} table|nil additional options
|
||||||
|
• filter (function|nil): Predicate to filter
|
||||||
|
clients used for rename. Receives the
|
||||||
|
attached clients as argument and must return
|
||||||
|
a list of clients.
|
||||||
|
• name (string|nil): Restrict clients used for
|
||||||
|
rename to ones where client.name matches
|
||||||
|
this field.
|
||||||
|
|
||||||
server_ready() *vim.lsp.buf.server_ready()*
|
server_ready() *vim.lsp.buf.server_ready()*
|
||||||
Checks whether the language servers attached to the current
|
Checks whether the language servers attached to the current
|
||||||
|
|||||||
@@ -359,50 +359,128 @@ end
|
|||||||
|
|
||||||
--- Renames all references to the symbol under the cursor.
|
--- Renames all references to the symbol under the cursor.
|
||||||
---
|
---
|
||||||
---@param new_name (string) If not provided, the user will be prompted for a new
|
---@param new_name string|nil If not provided, the user will be prompted for a new
|
||||||
---name using |vim.ui.input()|.
|
--- name using |vim.ui.input()|.
|
||||||
function M.rename(new_name)
|
---@param options table|nil additional options
|
||||||
local opts = {
|
--- - filter (function|nil):
|
||||||
prompt = "New Name: "
|
--- Predicate to filter clients used for rename.
|
||||||
}
|
--- Receives the attached clients as argument and must return a list of
|
||||||
|
--- clients.
|
||||||
|
--- - name (string|nil):
|
||||||
|
--- Restrict clients used for rename to ones where client.name matches
|
||||||
|
--- this field.
|
||||||
|
function M.rename(new_name, options)
|
||||||
|
options = options or {}
|
||||||
|
local bufnr = options.bufnr or vim.api.nvim_get_current_buf()
|
||||||
|
local clients = vim.lsp.buf_get_clients(bufnr)
|
||||||
|
|
||||||
---@private
|
if options.filter then
|
||||||
local function on_confirm(input)
|
clients = options.filter(clients)
|
||||||
if not (input and #input > 0) then return end
|
elseif options.name then
|
||||||
local params = util.make_position_params()
|
clients = vim.tbl_filter(
|
||||||
params.newName = input
|
function(client) return client.name == options.name end,
|
||||||
request('textDocument/rename', params)
|
clients
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #clients == 0 then
|
||||||
|
vim.notify("[LSP] Rename request failed, no matching language servers.")
|
||||||
|
end
|
||||||
|
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
-- Compute early to account for cursor movements after going async
|
||||||
|
local cword = vfn.expand('<cword>')
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
local function prepare_rename(err, result)
|
local function get_text_at_range(range)
|
||||||
if err == nil and result == nil then
|
return vim.api.nvim_buf_get_text(
|
||||||
vim.notify('nothing to rename', vim.log.levels.INFO)
|
bufnr,
|
||||||
|
range.start.line,
|
||||||
|
range.start.character,
|
||||||
|
range['end'].line,
|
||||||
|
range['end'].character,
|
||||||
|
{}
|
||||||
|
)[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local try_use_client
|
||||||
|
try_use_client = function(idx, client)
|
||||||
|
if not client then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if result and result.placeholder then
|
|
||||||
opts.default = result.placeholder
|
---@private
|
||||||
if not new_name then npcall(vim.ui.input, opts, on_confirm) end
|
local function rename(name)
|
||||||
elseif result and result.start and result['end'] and
|
local params = util.make_position_params(win, client.offset_encoding)
|
||||||
result.start.line == result['end'].line then
|
params.newName = name
|
||||||
local line = vfn.getline(result.start.line+1)
|
local handler = client.handlers['textDocument/rename'] or vim.lsp.handlers['textDocument/rename']
|
||||||
local start_char = result.start.character+1
|
client.request('textDocument/rename', params, function(...)
|
||||||
local end_char = result['end'].character
|
handler(...)
|
||||||
opts.default = string.sub(line, start_char, end_char)
|
try_use_client(next(clients, idx))
|
||||||
if not new_name then npcall(vim.ui.input, opts, on_confirm) end
|
end, bufnr)
|
||||||
else
|
end
|
||||||
-- fallback to guessing symbol using <cword>
|
|
||||||
--
|
if client.supports_method("textDocument/prepareRename") then
|
||||||
-- this can happen if the language server does not support prepareRename,
|
local params = util.make_position_params(win, client.offset_encoding)
|
||||||
-- returns an unexpected response, or requests for "default behavior"
|
client.request('textDocument/prepareRename', params, function(err, result)
|
||||||
--
|
if err or result == nil then
|
||||||
-- see https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename
|
if next(clients, idx) then
|
||||||
opts.default = vfn.expand('<cword>')
|
try_use_client(next(clients, idx))
|
||||||
if not new_name then npcall(vim.ui.input, opts, on_confirm) end
|
else
|
||||||
|
local msg = err and ('Error on prepareRename: ' .. (err.message or '')) or 'Nothing to rename'
|
||||||
|
vim.notify(msg, vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_name then
|
||||||
|
rename(new_name)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local prompt_opts = {
|
||||||
|
prompt = "New Name: "
|
||||||
|
}
|
||||||
|
-- result: Range | { range: Range, placeholder: string }
|
||||||
|
if result.placeholder then
|
||||||
|
prompt_opts.default = result.placeholder
|
||||||
|
elseif result.start then
|
||||||
|
prompt_opts.default = get_text_at_range(result)
|
||||||
|
elseif result.range then
|
||||||
|
prompt_opts.default = get_text_at_range(result.range)
|
||||||
|
else
|
||||||
|
prompt_opts.default = cword
|
||||||
|
end
|
||||||
|
vim.ui.input(prompt_opts, function(input)
|
||||||
|
if not input or #input == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rename(input)
|
||||||
|
end)
|
||||||
|
end, bufnr)
|
||||||
|
elseif client.supports_method("textDocument/rename") then
|
||||||
|
if new_name then
|
||||||
|
rename(new_name)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local prompt_opts = {
|
||||||
|
prompt = "New Name: ",
|
||||||
|
default = cword
|
||||||
|
}
|
||||||
|
vim.ui.input(prompt_opts, function(input)
|
||||||
|
if not input or #input == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rename(input)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
vim.notify('Client ' .. client.id .. '/' .. client.name .. ' has no rename capability')
|
||||||
end
|
end
|
||||||
if new_name then on_confirm(new_name) end
|
|
||||||
end
|
end
|
||||||
request('textDocument/prepareRename', util.make_position_params(), prepare_rename)
|
|
||||||
|
try_use_client(next(clients))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Lists all the references to the symbol under the cursor in the quickfix window.
|
--- Lists all the references to the symbol under the cursor in the quickfix window.
|
||||||
|
|||||||
@@ -222,10 +222,6 @@ function tests.prepare_rename_error()
|
|||||||
expect_request('textDocument/prepareRename', function()
|
expect_request('textDocument/prepareRename', function()
|
||||||
return {}, nil
|
return {}, nil
|
||||||
end)
|
end)
|
||||||
expect_request('textDocument/rename', function(params)
|
|
||||||
assert_eq(params.newName, 'renameto')
|
|
||||||
return nil, nil
|
|
||||||
end)
|
|
||||||
notify('shutdown')
|
notify('shutdown')
|
||||||
end;
|
end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2636,10 +2636,8 @@ describe('LSP', function()
|
|||||||
name = "prepare_rename_error",
|
name = "prepare_rename_error",
|
||||||
expected_handlers = {
|
expected_handlers = {
|
||||||
{NIL, {}, {method="shutdown", client_id=1}};
|
{NIL, {}, {method="shutdown", client_id=1}};
|
||||||
{NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}};
|
|
||||||
{NIL, {}, {method="start", client_id=1}};
|
{NIL, {}, {method="start", client_id=1}};
|
||||||
},
|
},
|
||||||
expected_text = "two", -- see test case
|
|
||||||
},
|
},
|
||||||
}) do
|
}) do
|
||||||
it(test.it, function()
|
it(test.it, function()
|
||||||
|
|||||||
Reference in New Issue
Block a user