mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat(picker.lsp): added lsp_incoming_calls and lsp_outgoing_calls. Closes #1843
This commit is contained in:
parent
a45503b957
commit
55d6670a7e
4 changed files with 143 additions and 27 deletions
|
|
@ -65,6 +65,8 @@ M.examples.general = {
|
|||
{ "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" },
|
||||
{ "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" },
|
||||
{ "gy", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto T[y]pe Definition" },
|
||||
{ "gai", function() Snacks.picker.lsp_incoming_calls() end, desc = "C[a]lls Incoming" },
|
||||
{ "gao", function() Snacks.picker.lsp_outgoing_calls() end, desc = "C[a]lls Outgoing" },
|
||||
{ "<leader>ss", function() Snacks.picker.lsp_symbols() end, desc = "LSP Symbols" },
|
||||
{ "<leader>sS", function() Snacks.picker.lsp_workspace_symbols() end, desc = "LSP Workspace Symbols" },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -550,6 +550,28 @@ M.lsp_implementations = {
|
|||
jump = { tagstack = true, reuse_win = true },
|
||||
}
|
||||
|
||||
-- LSP incoming calls
|
||||
---@type snacks.picker.lsp.Config
|
||||
M.lsp_incoming_calls = {
|
||||
finder = "lsp_incoming_calls",
|
||||
format = "lsp_symbol",
|
||||
include_current = false,
|
||||
workspace = true, -- this ensures the file is included in the formatter
|
||||
auto_confirm = true,
|
||||
jump = { tagstack = true, reuse_win = true },
|
||||
}
|
||||
|
||||
-- LSP outgoing calls
|
||||
---@type snacks.picker.lsp.Config
|
||||
M.lsp_outgoing_calls = {
|
||||
finder = "lsp_outgoing_calls",
|
||||
format = "lsp_symbol",
|
||||
include_current = false,
|
||||
workspace = true, -- this ensures the file is included in the formatter
|
||||
auto_confirm = true,
|
||||
jump = { tagstack = true, reuse_win = true },
|
||||
}
|
||||
|
||||
-- LSP references
|
||||
---@class snacks.picker.lsp.references.Config: snacks.picker.lsp.Config
|
||||
---@field include_declaration? boolean default true
|
||||
|
|
|
|||
|
|
@ -96,50 +96,83 @@ function M.get_clients(buf, method)
|
|||
end, clients)
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@class snacks.picker.lsp.Requester
|
||||
---@field async snacks.picker.Async
|
||||
---@field requests {client_id:number, request_id:number}[]
|
||||
---@field completed number
|
||||
local R = {}
|
||||
R.__index = R
|
||||
|
||||
function R.new()
|
||||
local self = setmetatable({}, R)
|
||||
self.async = Async.running()
|
||||
self.requests = {}
|
||||
self.completed = 0
|
||||
self.async:on(
|
||||
"abort",
|
||||
vim.schedule_wrap(function()
|
||||
self:cancel()
|
||||
end)
|
||||
)
|
||||
return self
|
||||
end
|
||||
|
||||
function R:cancel()
|
||||
while #self.requests > 0 do
|
||||
local req = table.remove(self.requests)
|
||||
local client = vim.lsp.get_client_by_id(req.client_id)
|
||||
if client then
|
||||
client:cancel_request(req.request_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param buf number|vim.lsp.Client
|
||||
---@param method string
|
||||
---@param params fun(client:vim.lsp.Client):table
|
||||
---@param cb fun(client:vim.lsp.Client, result:table, params:table)
|
||||
---@async
|
||||
function M.request(buf, method, params, cb)
|
||||
local async = Async.running()
|
||||
local cancel = {} ---@type fun()[]
|
||||
function R:request(buf, method, params, cb)
|
||||
local clients = type(buf) == "number" and M.get_clients(buf, method) or {
|
||||
wrap(buf --[[@as vim.lsp.Client]]),
|
||||
}
|
||||
if vim.tbl_isempty(clients) then
|
||||
return self.async:resume()
|
||||
end
|
||||
|
||||
async:on(
|
||||
"abort",
|
||||
vim.schedule_wrap(function()
|
||||
vim.tbl_map(pcall, cancel)
|
||||
cancel = {}
|
||||
end)
|
||||
)
|
||||
vim.schedule(function()
|
||||
local clients = M.get_clients(buf, method)
|
||||
if vim.tbl_isempty(clients) then
|
||||
return async:resume()
|
||||
end
|
||||
local remaining = #clients
|
||||
for _, client in ipairs(clients) do
|
||||
local p = params(client)
|
||||
local status, request_id = client:request(method, p, function(_, result)
|
||||
if result then
|
||||
cb(client, result, p)
|
||||
end
|
||||
remaining = remaining - 1
|
||||
if remaining == 0 then
|
||||
async:resume()
|
||||
end
|
||||
self.completed = self.completed + 1
|
||||
self.async:resume()
|
||||
end)
|
||||
if status and request_id then
|
||||
table.insert(cancel, function()
|
||||
client:cancel_request(request_id)
|
||||
end)
|
||||
table.insert(self.requests, { client_id = client.id, request_id = request_id })
|
||||
end
|
||||
end
|
||||
self.async:resume()
|
||||
end)
|
||||
self.async:suspend()
|
||||
return self
|
||||
end
|
||||
|
||||
async:suspend()
|
||||
cancel = {}
|
||||
async = Async.nop()
|
||||
function R:wait()
|
||||
while self.completed < #self.requests do
|
||||
self.async:suspend()
|
||||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param method string
|
||||
---@param params fun(client:vim.lsp.Client):table
|
||||
---@param cb fun(client:vim.lsp.Client, result:table, params:table)
|
||||
---@async
|
||||
function M.request(buf, method, params, cb)
|
||||
R.new():request(buf, method, params, cb):wait()
|
||||
end
|
||||
|
||||
-- Support for older versions of neovim
|
||||
|
|
@ -258,7 +291,7 @@ function M.results_to_items(client, results, opts)
|
|||
detail = result.detail,
|
||||
name = result.name,
|
||||
text = "",
|
||||
range = result.range,
|
||||
range = result.range or result.selectionRange,
|
||||
item = result,
|
||||
}
|
||||
local uri = result.location and result.location.uri or result.uri or opts.default_uri
|
||||
|
|
@ -389,6 +422,51 @@ function M.symbols(opts, ctx)
|
|||
end
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.lsp.Config
|
||||
---@param filter snacks.picker.Filter
|
||||
---@param incoming? boolean
|
||||
function M.call_hierarchy(opts, filter, incoming)
|
||||
local method = ("callHierarchy/%sCalls"):format(incoming and "incoming" or "outgoing")
|
||||
local buf = filter.current_buf
|
||||
local win = filter.current_win
|
||||
|
||||
---@async
|
||||
---@param cb async fun(item: snacks.picker.finder.Item)
|
||||
return function(cb)
|
||||
local requester = R.new()
|
||||
requester:request(buf, "textDocument/prepareCallHierarchy", function(client)
|
||||
return vim.lsp.util.make_position_params(win, client.offset_encoding)
|
||||
end, function(client, result)
|
||||
---@cast result lsp.CallHierarchyItem[]
|
||||
for _, res in ipairs(result or {}) do
|
||||
requester:request(client, method, function()
|
||||
return { item = res }
|
||||
end, function(_, calls)
|
||||
---@cast calls (lsp.CallHierarchyIncomingCall|lsp.CallHierarchyOutgoingCall)[]
|
||||
|
||||
local call_items = {} ---@type lsp.CallHierarchyItem[]
|
||||
---@param call lsp.CallHierarchyIncomingCall|lsp.CallHierarchyOutgoingCall
|
||||
for _, call in ipairs(calls) do
|
||||
if incoming then
|
||||
for _, range in ipairs(call.fromRanges or {}) do
|
||||
local from = vim.deepcopy(call.from)
|
||||
from.selectionRange = range or from.selectionRange
|
||||
table.insert(call_items, from)
|
||||
end
|
||||
else
|
||||
table.insert(call_items, call.to)
|
||||
end
|
||||
end
|
||||
|
||||
local items = M.results_to_items(client, call_items, { default_uri = res.uri })
|
||||
vim.tbl_map(cb, items)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
requester:wait()
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.lsp.references.Config
|
||||
---@type snacks.picker.finder
|
||||
function M.references(opts, ctx)
|
||||
|
|
@ -402,6 +480,18 @@ function M.references(opts, ctx)
|
|||
)
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.lsp.Config
|
||||
---@type snacks.picker.finder
|
||||
function M.incoming_calls(opts, ctx)
|
||||
return M.call_hierarchy(opts, ctx.filter, true)
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.lsp.Config
|
||||
---@type snacks.picker.finder
|
||||
function M.outgoing_calls(opts, ctx)
|
||||
return M.call_hierarchy(opts, ctx.filter, false)
|
||||
end
|
||||
|
||||
---@param opts snacks.picker.lsp.Config
|
||||
---@type snacks.picker.finder
|
||||
function M.definitions(opts, ctx)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
---@field lsp_declarations fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
---@field lsp_definitions fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
---@field lsp_implementations fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
---@field lsp_incoming_calls fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
---@field lsp_outgoing_calls fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
---@field lsp_references fun(opts?: snacks.picker.lsp.references.Config|{}): snacks.Picker
|
||||
---@field lsp_symbols fun(opts?: snacks.picker.lsp.symbols.Config|{}): snacks.Picker
|
||||
---@field lsp_type_definitions fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue