diff options
author | cbarrete <62146989+cbarrete@users.noreply.github.com> | 2020-07-18 21:10:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-18 15:10:09 -0400 |
commit | 08efa7037e05ce229150d17db11b1b1c2419631f (patch) | |
tree | 0064d34f56ed96f82094970d60870d02773349cc | |
parent | a02a267f8ad4b6d8b9038d2c7d9b85f03e734814 (diff) | |
download | rneovim-08efa7037e05ce229150d17db11b1b1c2419631f.tar.gz rneovim-08efa7037e05ce229150d17db11b1b1c2419631f.tar.bz2 rneovim-08efa7037e05ce229150d17db11b1b1c2419631f.zip |
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 <>
-rw-r--r-- | runtime/doc/lsp.txt | 10 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 32 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 27 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 4 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 143 |
6 files changed, 217 insertions, 0 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 15587955de..9f878ad8c7 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -837,6 +837,16 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* enter a string on the command line. An empty string means no 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* diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 7442f0c0b5..6fe1d15b7e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -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.document_symbol and method == 'textDocument/documentSymbol') or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') + or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy') then callback(unsupported_method(method), method, nil, client_id, bufnr) return diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 839e00c67d..476bb3ba6f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -143,6 +143,38 @@ function M.document_symbol() request('textDocument/documentSymbol', params) 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. --- diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 4b14f0132d..1ed58995d0 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -214,6 +214,33 @@ M['textDocument/documentHighlight'] = function(_, _, result, _) util.buf_highlight_references(bufnr, result) 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) local message_type = result.type local message = result.message diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 4fded1961d..ef5e08680e 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -713,6 +713,9 @@ function protocol.make_client_capabilities() }; applyEdit = true; }; + callHierarchy = { + dynamicRegistration = false; + }; experimental = nil; } end @@ -912,6 +915,7 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false general_properties.document_formatting = server_capabilities.documentFormattingProvider 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 general_properties.code_action = false diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1b022f50df..aaa28390ea 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1497,4 +1497,147 @@ describe('LSP', function() it('with softtabstop = 0', function() test_tabstop(2, 0) end) it('with softtabstop = -1', function() test_tabstop(3, -1) 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) |