mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(lsp)!: support multiple clients in goto methods (#30877)
Relates to: - https://github.com/neovim/neovim/issues/30034 - https://github.com/neovim/neovim/issues/17712 - https://github.com/neovim/neovim/issues/16363 Closes: - https://github.com/neovim/neovim/issues/26936 (but only provides bufnr and method) - https://github.com/neovim/neovim/issues/22318 Might fix: https://github.com/neovim/neovim/issues/30737
This commit is contained in:
parent
9b8907d905
commit
0083e03d6f
@ -41,6 +41,9 @@ TREESITTER
|
|||||||
|TSNode:child_with_descendant()| instead; it is identical except that it can
|
|TSNode:child_with_descendant()| instead; it is identical except that it can
|
||||||
return the descendant itself.
|
return the descendant itself.
|
||||||
|
|
||||||
|
LSP
|
||||||
|
• *vim.lsp.util.jump_to_location*
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
DEPRECATED IN 0.10 *deprecated-0.10*
|
DEPRECATED IN 0.10 *deprecated-0.10*
|
||||||
|
|
||||||
|
@ -2027,19 +2027,6 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
|
|||||||
See also: ~
|
See also: ~
|
||||||
• 'shiftwidth'
|
• 'shiftwidth'
|
||||||
|
|
||||||
*vim.lsp.util.jump_to_location()*
|
|
||||||
jump_to_location({location}, {offset_encoding}, {reuse_win})
|
|
||||||
Jumps to a location.
|
|
||||||
|
|
||||||
Parameters: ~
|
|
||||||
• {location} (`lsp.Location|lsp.LocationLink`)
|
|
||||||
• {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'?`)
|
|
||||||
• {reuse_win} (`boolean?`) Jump to existing window if buffer is
|
|
||||||
already open.
|
|
||||||
|
|
||||||
Return: ~
|
|
||||||
(`boolean`) `true` if the jump succeeded
|
|
||||||
|
|
||||||
*vim.lsp.util.locations_to_items()*
|
*vim.lsp.util.locations_to_items()*
|
||||||
locations_to_items({locations}, {offset_encoding})
|
locations_to_items({locations}, {offset_encoding})
|
||||||
Returns the items with the byte position calculated correctly and in
|
Returns the items with the byte position calculated correctly and in
|
||||||
|
@ -82,6 +82,11 @@ LSP
|
|||||||
• |vim.lsp.buf.references()| now handles multiple clients but no longer
|
• |vim.lsp.buf.references()| now handles multiple clients but no longer
|
||||||
triggers the global `textDocument/references` handler from
|
triggers the global `textDocument/references` handler from
|
||||||
`vim.lsp.handlers`
|
`vim.lsp.handlers`
|
||||||
|
• |vim.lsp.buf.declaration()|, |vim.lsp.buf.definition()|,
|
||||||
|
|vim.lsp.buf.type_definition()| and |vim.lsp.buf.implementation()| now
|
||||||
|
support merging the results of multiple clients but no longer trigger the
|
||||||
|
global handlers from `vim.lsp.handlers`
|
||||||
|
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
@ -50,6 +50,79 @@ local function request_with_opts(name, params, opts)
|
|||||||
request(name, params, req_handler)
|
request(name, params, req_handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param method string
|
||||||
|
---@param opts? vim.lsp.LocationOpts
|
||||||
|
local function get_locations(method, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
local clients = vim.lsp.get_clients({ method = method, bufnr = bufnr })
|
||||||
|
if not next(clients) then
|
||||||
|
vim.notify(vim.lsp._unsupported_method(method), vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local win = api.nvim_get_current_win()
|
||||||
|
local remaining = #clients
|
||||||
|
|
||||||
|
---@type vim.quickfix.entry[]
|
||||||
|
local all_items = {}
|
||||||
|
|
||||||
|
---@param result nil|lsp.Location|lsp.Location[]
|
||||||
|
---@param client vim.lsp.Client
|
||||||
|
local function on_response(_, result, client)
|
||||||
|
local locations = {}
|
||||||
|
if result then
|
||||||
|
locations = vim.islist(result) and result or { result }
|
||||||
|
end
|
||||||
|
local items = util.locations_to_items(locations, client.offset_encoding)
|
||||||
|
vim.list_extend(all_items, items)
|
||||||
|
remaining = remaining - 1
|
||||||
|
if remaining == 0 then
|
||||||
|
if vim.tbl_isempty(all_items) then
|
||||||
|
vim.notify('No locations found', vim.log.levels.INFO)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local title = 'LSP locations'
|
||||||
|
if opts.on_list then
|
||||||
|
assert(vim.is_callable(opts.on_list), 'on_list is not a function')
|
||||||
|
opts.on_list({
|
||||||
|
title = title,
|
||||||
|
items = all_items,
|
||||||
|
context = { bufnr = bufnr, method = method },
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if #all_items == 1 then
|
||||||
|
local item = all_items[1]
|
||||||
|
local b = item.bufnr or vim.fn.bufadd(item.filename)
|
||||||
|
vim.bo[b].buflisted = true
|
||||||
|
local w = opts.reuse_win and vim.fn.win_findbuf(b)[1] or win
|
||||||
|
api.nvim_win_set_buf(w, b)
|
||||||
|
api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 })
|
||||||
|
vim._with({ win = w }, function()
|
||||||
|
-- Open folds under the cursor
|
||||||
|
vim.cmd('normal! zv')
|
||||||
|
end)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if opts.loclist then
|
||||||
|
vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items })
|
||||||
|
vim.cmd.lopen()
|
||||||
|
else
|
||||||
|
vim.fn.setqflist({}, ' ', { title = title, items = all_items })
|
||||||
|
vim.cmd('botright copen')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, client in ipairs(clients) do
|
||||||
|
local params = util.make_position_params(win, client.offset_encoding)
|
||||||
|
client.request(method, params, function(_, result)
|
||||||
|
on_response(_, result, client)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- @class vim.lsp.ListOpts
|
--- @class vim.lsp.ListOpts
|
||||||
---
|
---
|
||||||
--- list-handler replacing the default handler.
|
--- list-handler replacing the default handler.
|
||||||
@ -87,30 +160,26 @@ end
|
|||||||
--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
|
--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
|
||||||
--- @param opts? vim.lsp.LocationOpts
|
--- @param opts? vim.lsp.LocationOpts
|
||||||
function M.declaration(opts)
|
function M.declaration(opts)
|
||||||
local params = util.make_position_params()
|
get_locations(ms.textDocument_declaration, opts)
|
||||||
request_with_opts(ms.textDocument_declaration, params, opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Jumps to the definition of the symbol under the cursor.
|
--- Jumps to the definition of the symbol under the cursor.
|
||||||
--- @param opts? vim.lsp.LocationOpts
|
--- @param opts? vim.lsp.LocationOpts
|
||||||
function M.definition(opts)
|
function M.definition(opts)
|
||||||
local params = util.make_position_params()
|
get_locations(ms.textDocument_definition, opts)
|
||||||
request_with_opts(ms.textDocument_definition, params, opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Jumps to the definition of the type of the symbol under the cursor.
|
--- Jumps to the definition of the type of the symbol under the cursor.
|
||||||
--- @param opts? vim.lsp.LocationOpts
|
--- @param opts? vim.lsp.LocationOpts
|
||||||
function M.type_definition(opts)
|
function M.type_definition(opts)
|
||||||
local params = util.make_position_params()
|
get_locations(ms.textDocument_typeDefinition, opts)
|
||||||
request_with_opts(ms.textDocument_typeDefinition, params, opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Lists all the implementations for the symbol under the cursor in the
|
--- Lists all the implementations for the symbol under the cursor in the
|
||||||
--- quickfix window.
|
--- quickfix window.
|
||||||
--- @param opts? vim.lsp.LocationOpts
|
--- @param opts? vim.lsp.LocationOpts
|
||||||
function M.implementation(opts)
|
function M.implementation(opts)
|
||||||
local params = util.make_position_params()
|
get_locations(ms.textDocument_implementation, opts)
|
||||||
request_with_opts(ms.textDocument_implementation, params, opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Displays signature information about the symbol under the cursor in a
|
--- Displays signature information about the symbol under the cursor in a
|
||||||
@ -438,12 +507,12 @@ end
|
|||||||
---@param opts? vim.lsp.ListOpts
|
---@param opts? vim.lsp.ListOpts
|
||||||
function M.references(context, opts)
|
function M.references(context, opts)
|
||||||
validate('context', context, 'table', true)
|
validate('context', context, 'table', true)
|
||||||
local clients = vim.lsp.get_clients({ method = ms.textDocument_references })
|
local bufnr = api.nvim_get_current_buf()
|
||||||
|
local clients = vim.lsp.get_clients({ method = ms.textDocument_references, bufnr = bufnr })
|
||||||
if not next(clients) then
|
if not next(clients) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local win = api.nvim_get_current_win()
|
local win = api.nvim_get_current_win()
|
||||||
local bufnr = api.nvim_get_current_buf()
|
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
||||||
local all_items = {}
|
local all_items = {}
|
||||||
|
@ -386,57 +386,6 @@ end
|
|||||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
|
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
|
||||||
M[ms.textDocument_hover] = M.hover
|
M[ms.textDocument_hover] = M.hover
|
||||||
|
|
||||||
--- Jumps to a location. Used as a handler for multiple LSP methods.
|
|
||||||
---@param _ nil not used
|
|
||||||
---@param result (table) result of LSP method; a location or a list of locations.
|
|
||||||
---@param ctx (lsp.HandlerContext) table containing the context of the request, including the method
|
|
||||||
---@param config? vim.lsp.LocationOpts
|
|
||||||
---(`textDocument/definition` can return `Location` or `Location[]`
|
|
||||||
local function location_handler(_, result, ctx, config)
|
|
||||||
if result == nil or vim.tbl_isempty(result) then
|
|
||||||
log.info(ctx.method, 'No location found')
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
|
|
||||||
|
|
||||||
config = config or {}
|
|
||||||
|
|
||||||
-- textDocument/definition can return Location or Location[]
|
|
||||||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
|
||||||
if not vim.islist(result) then
|
|
||||||
result = { result }
|
|
||||||
end
|
|
||||||
|
|
||||||
local title = 'LSP locations'
|
|
||||||
local items = util.locations_to_items(result, client.offset_encoding)
|
|
||||||
|
|
||||||
if config.on_list then
|
|
||||||
assert(vim.is_callable(config.on_list), 'on_list is not a function')
|
|
||||||
config.on_list({ title = title, items = items })
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if #result == 1 then
|
|
||||||
util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if config.loclist then
|
|
||||||
vim.fn.setloclist(0, {}, ' ', { title = title, items = items })
|
|
||||||
vim.cmd.lopen()
|
|
||||||
else
|
|
||||||
vim.fn.setqflist({}, ' ', { title = title, items = items })
|
|
||||||
vim.cmd('botright copen')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
|
|
||||||
M[ms.textDocument_declaration] = location_handler
|
|
||||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
|
||||||
M[ms.textDocument_definition] = location_handler
|
|
||||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
|
|
||||||
M[ms.textDocument_typeDefinition] = location_handler
|
|
||||||
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
|
|
||||||
M[ms.textDocument_implementation] = location_handler
|
|
||||||
|
|
||||||
local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
|
local sig_help_ns = api.nvim_create_namespace('vim_lsp_signature_help')
|
||||||
|
|
||||||
--- |lsp-handler| for the method "textDocument/signatureHelp".
|
--- |lsp-handler| for the method "textDocument/signatureHelp".
|
||||||
|
@ -1023,18 +1023,13 @@ end
|
|||||||
|
|
||||||
--- Jumps to a location.
|
--- Jumps to a location.
|
||||||
---
|
---
|
||||||
|
---@deprecated
|
||||||
---@param location lsp.Location|lsp.LocationLink
|
---@param location lsp.Location|lsp.LocationLink
|
||||||
---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'?
|
---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'?
|
||||||
---@param reuse_win boolean? Jump to existing window if buffer is already open.
|
---@param reuse_win boolean? Jump to existing window if buffer is already open.
|
||||||
---@return boolean `true` if the jump succeeded
|
---@return boolean `true` if the jump succeeded
|
||||||
function M.jump_to_location(location, offset_encoding, reuse_win)
|
function M.jump_to_location(location, offset_encoding, reuse_win)
|
||||||
if offset_encoding == nil then
|
vim.deprecate('vim.lsp.util.jump_to_location', nil, '0.12')
|
||||||
vim.notify_once(
|
|
||||||
'jump_to_location must be called with valid offset encoding',
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M.show_document(location, offset_encoding, { reuse_win = reuse_win, focus = true })
|
return M.show_document(location, offset_encoding, { reuse_win = reuse_win, focus = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -5076,6 +5076,86 @@ describe('LSP', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('lsp.buf.definition', function()
|
||||||
|
it('jumps to single location', function()
|
||||||
|
exec_lua(create_server_definition)
|
||||||
|
local result = exec_lua(function()
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local server = _G._create_server({
|
||||||
|
capabilities = {
|
||||||
|
definitionProvider = true,
|
||||||
|
},
|
||||||
|
handlers = {
|
||||||
|
['textDocument/definition'] = function(_, _, callback)
|
||||||
|
local location = {
|
||||||
|
range = {
|
||||||
|
start = { line = 0, character = 0 },
|
||||||
|
['end'] = { line = 0, character = 0 },
|
||||||
|
},
|
||||||
|
uri = vim.uri_from_bufnr(bufnr),
|
||||||
|
}
|
||||||
|
callback(nil, location)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' })
|
||||||
|
vim.api.nvim_win_set_cursor(win, { 3, 6 })
|
||||||
|
local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd }))
|
||||||
|
vim.lsp.buf.definition()
|
||||||
|
vim.lsp.stop_client(client_id)
|
||||||
|
return {
|
||||||
|
cursor = vim.api.nvim_win_get_cursor(win),
|
||||||
|
messages = server.messages,
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
eq('textDocument/definition', result.messages[3].method)
|
||||||
|
eq({ 1, 0 }, result.cursor)
|
||||||
|
end)
|
||||||
|
it('merges results from multiple servers', function()
|
||||||
|
exec_lua(create_server_definition)
|
||||||
|
local result = exec_lua(function()
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local function serveropts(character)
|
||||||
|
return {
|
||||||
|
capabilities = {
|
||||||
|
definitionProvider = true,
|
||||||
|
},
|
||||||
|
handlers = {
|
||||||
|
['textDocument/definition'] = function(_, _, callback)
|
||||||
|
local location = {
|
||||||
|
range = {
|
||||||
|
start = { line = 0, character = character },
|
||||||
|
['end'] = { line = 0, character = character },
|
||||||
|
},
|
||||||
|
uri = vim.uri_from_bufnr(bufnr),
|
||||||
|
}
|
||||||
|
callback(nil, location)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local server1 = _G._create_server(serveropts(0))
|
||||||
|
local server2 = _G._create_server(serveropts(7))
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' })
|
||||||
|
vim.api.nvim_win_set_cursor(win, { 3, 6 })
|
||||||
|
local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd }))
|
||||||
|
local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd }))
|
||||||
|
local response
|
||||||
|
vim.lsp.buf.definition({
|
||||||
|
on_list = function(r)
|
||||||
|
response = r
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
vim.lsp.stop_client(client_id1)
|
||||||
|
vim.lsp.stop_client(client_id2)
|
||||||
|
return response
|
||||||
|
end)
|
||||||
|
eq(2, #result.items)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe('vim.lsp.tagfunc', function()
|
describe('vim.lsp.tagfunc', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
clear()
|
clear()
|
||||||
|
Loading…
Reference in New Issue
Block a user