From fc46ff3ea3ef1d587ead8f584ee76a5a76470287 Mon Sep 17 00:00:00 2001 From: Manuel Coenen Date: Tue, 23 Nov 2021 14:41:37 +0100 Subject: [PATCH] feat(builtin.lsp): implement builtin handlers for lsp.(incoming|outgoing)_calls Fixes #863 --- README.md | 2 + lua/telescope/builtin/init.lua | 10 ++++ lua/telescope/builtin/lsp.lua | 102 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/README.md b/README.md index acdc9b47c9..6e4a8bd2d4 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,8 @@ Built-in functions. Ready to be bound to any key you like. | Functions | Description | |---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| | `builtin.lsp_references` | Lists LSP references for word under the cursor | +| `builtin.lsp_incoming_calls` | Lists LSP incoming calls for word under the cursor | +| `builtin.lsp_outgoing_calls` | Lists LSP outgoing calls for word under the cursor | | `builtin.lsp_document_symbols` | Lists LSP document symbols in the current buffer | | `builtin.lsp_workspace_symbols` | Lists LSP document symbols in the current workspace | | `builtin.lsp_dynamic_workspace_symbols` | Dynamically Lists LSP for all workspace symbols | diff --git a/lua/telescope/builtin/init.lua b/lua/telescope/builtin/init.lua index 0a69aae3e5..157e296af8 100644 --- a/lua/telescope/builtin/init.lua +++ b/lua/telescope/builtin/init.lua @@ -368,6 +368,16 @@ builtin.jumplist = require_on_exported_call("telescope.builtin.internal").jumpli ---@field timeout number: timeout for the sync call (default: 10000) builtin.lsp_references = require_on_exported_call("telescope.builtin.lsp").references +--- Lists LSP incoming calls for word under the cursor, jumps to reference on `` +---@param opts table: options to pass to the picker +---@field timeout number: timeout for the sync call (default: 10000) +builtin.lsp_incoming_calls = require_on_exported_call("telescope.builtin.lsp").incoming_calls + +--- Lists LSP outgoing calls for word under the cursor, jumps to reference on `` +---@param opts table: options to pass to the picker +---@field timeout number: timeout for the sync call (default: 10000) +builtin.lsp_outgoing_calls = require_on_exported_call("telescope.builtin.lsp").outgoing_calls + --- Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope ---@param opts table: options to pass to the picker ---@field timeout number: timeout for the sync call (default: 10000) diff --git a/lua/telescope/builtin/lsp.lua b/lua/telescope/builtin/lsp.lua index 9b40913211..88491a8f49 100644 --- a/lua/telescope/builtin/lsp.lua +++ b/lua/telescope/builtin/lsp.lua @@ -44,6 +44,106 @@ lsp.references = function(opts) }):find() end +local function call_hierarchy(method, title, direction, opts, item, timeout) + local result, err = vim.lsp.buf_request_sync(0, method, { item = item }, timeout) + if err then + vim.api.nvim_err_writeln("Error handling " .. title .. ": " .. err) + return + end + if not result or vim.tbl_isempty(result) then + return + end + local locations = {} + for _, server_result in pairs(result) do + for _, ch_call in pairs(server_result.result) do + local ch_item = ch_call[direction] + for _, range in pairs(ch_call.fromRanges) do + table.insert(locations, { + filename = vim.uri_to_fname(ch_item.uri), + text = ch_item.name, + lnum = range.start.line + 1, + col = range.start.character + 1, + }) + end + end + end + pickers.new(opts, { + prompt_title = title, + finder = finders.new_table { + results = locations, + entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.generic_sorter(opts), + }):find() +end + +local function calls(opts, direction) + opts = opts or {} + + local params = vim.lsp.util.make_position_params() + local timeout = opts.timeout or 10000 + local start = os.time() + local result, err = vim.lsp.buf_request_sync(0, "textDocument/prepareCallHierarchy", params, timeout) + if err then + vim.api.nvim_err_writeln("Error when preparing call hierarchy: " .. err) + return + end + -- NOTE: os.time() and os.difftime() only have second precision so we might stop too early later on + local remaining = timeout - os.difftime(os.time(), start) + + local flattened_results = {} + for _, server_results in pairs(result) do + if server_results.result then + for _, result_result in pairs(server_results.result) do + if not vim.tbl_islist(result_result) then + flattened_results = { result_result } + break + end + + vim.list_extend(flattened_results, server_results.result) + end + end + end + if #flattened_results == 0 then + return + else + if #flattened_results == 1 then + if direction == "from" then + call_hierarchy( + "callHierarchy/incomingCalls", + "LSP Incoming Calls", + direction, + opts, + flattened_results[1], + remaining + ) + else + call_hierarchy( + "callHierarchy/outgoingCalls", + "LSP Outgoing Calls", + direction, + opts, + flattened_results[1], + remaining + ) + end + else + -- TODO: handle more than one candidate + -- Based on LSP spec there could be several candidates and the client hasn + -- to choose among them. + end + end +end + +lsp.incoming_calls = function(opts) + calls(opts, "from") +end + +lsp.outgoing_calls = function(opts) + calls(opts, "to") +end + local function list_or_jump(action, title, opts) opts = opts or {} @@ -486,6 +586,8 @@ local feature_map = { ["type_definitions"] = "type_definition", ["implementations"] = "implementation", ["workspace_symbols"] = "workspace_symbol", + ["incoming_calls"] = "call_hierarchy", + ["outgoing_calls"] = "call_hierarchy", } local function apply_checks(mod)