mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 20:08:17 +00:00
lsp: Add support for call hierarchies (#12556)
* LSP: Add support for call hierarchies * LSP: Add support for call hierarchies * LSP: Add support for call hierarchies * LSP: Jump to call location Jump to the call site instead of jumping to the definition of the caller/callee. * LSP: add tests for the call hierarchy callbacks * Fix linting error Co-authored-by: Cédric Barreteau <>
This commit is contained in:
@@ -837,6 +837,16 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()*
|
|||||||
enter a string on the command line. An empty string means no
|
enter a string on the command line. An empty string means no
|
||||||
filtering is done.
|
filtering is done.
|
||||||
|
|
||||||
|
incoming_calls() *vim.lsp.buf.incoming_calls()*
|
||||||
|
Lists all the call sites of the symbol under the cursor in the
|
||||||
|
|quickfix| window. If the symbol can resolve to multiple
|
||||||
|
items, the user can pick one in the |inputlist|.
|
||||||
|
|
||||||
|
outgoing_calls() *vim.lsp.buf.outgoing_calls()*
|
||||||
|
Lists all the items that are called by the symbol under the
|
||||||
|
cursor in the |quickfix| window. If the symbol can resolve to
|
||||||
|
multiple items, the user can pick one in the |inputlist|.
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.lsp.callbacks *lsp-callbacks*
|
Lua module: vim.lsp.callbacks *lsp-callbacks*
|
||||||
|
@@ -511,6 +511,7 @@ function lsp.start_client(config)
|
|||||||
or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition')
|
or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition')
|
||||||
or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
|
or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
|
||||||
or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
|
or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
|
||||||
|
or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy')
|
||||||
then
|
then
|
||||||
callback(unsupported_method(method), method, nil, client_id, bufnr)
|
callback(unsupported_method(method), method, nil, client_id, bufnr)
|
||||||
return
|
return
|
||||||
|
@@ -143,6 +143,38 @@ function M.document_symbol()
|
|||||||
request('textDocument/documentSymbol', params)
|
request('textDocument/documentSymbol', params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function pick_call_hierarchy_item(call_hierarchy_items)
|
||||||
|
if not call_hierarchy_items then return end
|
||||||
|
if #call_hierarchy_items == 1 then
|
||||||
|
return call_hierarchy_items[1]
|
||||||
|
end
|
||||||
|
local items = {}
|
||||||
|
for i, item in ipairs(call_hierarchy_items) do
|
||||||
|
local entry = item.detail or item.name
|
||||||
|
table.insert(items, string.format("%d. %s", i, entry))
|
||||||
|
end
|
||||||
|
local choice = vim.fn.inputlist(items)
|
||||||
|
if choice < 1 or choice > #items then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return choice
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.incoming_calls()
|
||||||
|
local params = util.make_position_params()
|
||||||
|
request('textDocument/prepareCallHierarchy', params, function(_, _, result)
|
||||||
|
local call_hierarchy_item = pick_call_hierarchy_item(result)
|
||||||
|
vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.outgoing_calls()
|
||||||
|
local params = util.make_position_params()
|
||||||
|
request('textDocument/prepareCallHierarchy', params, function(_, _, result)
|
||||||
|
local call_hierarchy_item = pick_call_hierarchy_item(result)
|
||||||
|
vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
--- Lists all symbols in the current workspace in the quickfix window.
|
--- Lists all symbols in the current workspace in the quickfix window.
|
||||||
---
|
---
|
||||||
|
@@ -214,6 +214,33 @@ M['textDocument/documentHighlight'] = function(_, _, result, _)
|
|||||||
util.buf_highlight_references(bufnr, result)
|
util.buf_highlight_references(bufnr, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- direction is "from" for incoming calls and "to" for outgoing calls
|
||||||
|
local make_call_hierarchy_callback = function(direction)
|
||||||
|
-- result is a CallHierarchy{Incoming,Outgoing}Call[]
|
||||||
|
return function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
local items = {}
|
||||||
|
for _, call_hierarchy_call in pairs(result) do
|
||||||
|
local call_hierarchy_item = call_hierarchy_call[direction]
|
||||||
|
for _, range in pairs(call_hierarchy_call.fromRanges) do
|
||||||
|
table.insert(items, {
|
||||||
|
filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
|
||||||
|
text = call_hierarchy_item.name,
|
||||||
|
lnum = range.start.line + 1,
|
||||||
|
col = range.start.character + 1,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
util.set_qflist(items)
|
||||||
|
api.nvim_command("copen")
|
||||||
|
api.nvim_command("wincmd p")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from')
|
||||||
|
|
||||||
|
M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to')
|
||||||
|
|
||||||
M['window/logMessage'] = function(_, _, result, client_id)
|
M['window/logMessage'] = function(_, _, result, client_id)
|
||||||
local message_type = result.type
|
local message_type = result.type
|
||||||
local message = result.message
|
local message = result.message
|
||||||
|
@@ -713,6 +713,9 @@ function protocol.make_client_capabilities()
|
|||||||
};
|
};
|
||||||
applyEdit = true;
|
applyEdit = true;
|
||||||
};
|
};
|
||||||
|
callHierarchy = {
|
||||||
|
dynamicRegistration = false;
|
||||||
|
};
|
||||||
experimental = nil;
|
experimental = nil;
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -912,6 +915,7 @@ function protocol.resolve_capabilities(server_capabilities)
|
|||||||
general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
|
general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
|
||||||
general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
|
general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
|
||||||
general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false
|
general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false
|
||||||
|
general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
|
||||||
|
|
||||||
if server_capabilities.codeActionProvider == nil then
|
if server_capabilities.codeActionProvider == nil then
|
||||||
general_properties.code_action = false
|
general_properties.code_action = false
|
||||||
|
@@ -1497,4 +1497,147 @@ describe('LSP', function()
|
|||||||
it('with softtabstop = 0', function() test_tabstop(2, 0) end)
|
it('with softtabstop = 0', function() test_tabstop(2, 0) end)
|
||||||
it('with softtabstop = -1', function() test_tabstop(3, -1) end)
|
it('with softtabstop = -1', function() test_tabstop(3, -1) end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('vim.lsp.buf.outgoing_calls', function()
|
||||||
|
it('does nothing for an empty response', function()
|
||||||
|
local qflist_count = exec_lua([=[
|
||||||
|
require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']()
|
||||||
|
return #vim.fn.getqflist()
|
||||||
|
]=])
|
||||||
|
eq(0, qflist_count)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('opens the quickfix list with the right caller', function()
|
||||||
|
local qflist = exec_lua([=[
|
||||||
|
local rust_analyzer_response = { {
|
||||||
|
fromRanges = { {
|
||||||
|
['end'] = {
|
||||||
|
character = 7,
|
||||||
|
line = 3
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 4,
|
||||||
|
line = 3
|
||||||
|
}
|
||||||
|
} },
|
||||||
|
to = {
|
||||||
|
detail = "fn foo()",
|
||||||
|
kind = 12,
|
||||||
|
name = "foo",
|
||||||
|
range = {
|
||||||
|
['end'] = {
|
||||||
|
character = 11,
|
||||||
|
line = 0
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 0,
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectionRange = {
|
||||||
|
['end'] = {
|
||||||
|
character = 6,
|
||||||
|
line = 0
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 3,
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uri = "file:///src/main.rs"
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']
|
||||||
|
callback(nil, nil, rust_analyzer_response)
|
||||||
|
return vim.fn.getqflist()
|
||||||
|
]=])
|
||||||
|
|
||||||
|
local expected = { {
|
||||||
|
bufnr = 2,
|
||||||
|
col = 5,
|
||||||
|
lnum = 4,
|
||||||
|
module = "",
|
||||||
|
nr = 0,
|
||||||
|
pattern = "",
|
||||||
|
text = "foo",
|
||||||
|
type = "",
|
||||||
|
valid = 1,
|
||||||
|
vcol = 0
|
||||||
|
} }
|
||||||
|
|
||||||
|
eq(expected, qflist)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('vim.lsp.buf.incoming_calls', function()
|
||||||
|
it('does nothing for an empty response', function()
|
||||||
|
local qflist_count = exec_lua([=[
|
||||||
|
require'vim.lsp.callbacks'['callHierarchy/incomingCalls']()
|
||||||
|
return #vim.fn.getqflist()
|
||||||
|
]=])
|
||||||
|
eq(0, qflist_count)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('opens the quickfix list with the right callee', function()
|
||||||
|
local qflist = exec_lua([=[
|
||||||
|
local rust_analyzer_response = { {
|
||||||
|
from = {
|
||||||
|
detail = "fn main()",
|
||||||
|
kind = 12,
|
||||||
|
name = "main",
|
||||||
|
range = {
|
||||||
|
['end'] = {
|
||||||
|
character = 1,
|
||||||
|
line = 4
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 0,
|
||||||
|
line = 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectionRange = {
|
||||||
|
['end'] = {
|
||||||
|
character = 7,
|
||||||
|
line = 2
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 3,
|
||||||
|
line = 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uri = "file:///src/main.rs"
|
||||||
|
},
|
||||||
|
fromRanges = { {
|
||||||
|
['end'] = {
|
||||||
|
character = 7,
|
||||||
|
line = 3
|
||||||
|
},
|
||||||
|
start = {
|
||||||
|
character = 4,
|
||||||
|
line = 3
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
} }
|
||||||
|
|
||||||
|
local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls']
|
||||||
|
callback(nil, nil, rust_analyzer_response)
|
||||||
|
return vim.fn.getqflist()
|
||||||
|
]=])
|
||||||
|
|
||||||
|
local expected = { {
|
||||||
|
bufnr = 2,
|
||||||
|
col = 5,
|
||||||
|
lnum = 4,
|
||||||
|
module = "",
|
||||||
|
nr = 0,
|
||||||
|
pattern = "",
|
||||||
|
text = "main",
|
||||||
|
type = "",
|
||||||
|
valid = 1,
|
||||||
|
vcol = 0
|
||||||
|
} }
|
||||||
|
|
||||||
|
eq(expected, qflist)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user