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" },
|
{ "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" },
|
||||||
{ "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" },
|
{ "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" },
|
||||||
{ "gy", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto T[y]pe Definition" },
|
{ "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_symbols() end, desc = "LSP Symbols" },
|
||||||
{ "<leader>sS", function() Snacks.picker.lsp_workspace_symbols() end, desc = "LSP Workspace 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 },
|
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
|
-- LSP references
|
||||||
---@class snacks.picker.lsp.references.Config: snacks.picker.lsp.Config
|
---@class snacks.picker.lsp.references.Config: snacks.picker.lsp.Config
|
||||||
---@field include_declaration? boolean default true
|
---@field include_declaration? boolean default true
|
||||||
|
|
|
||||||
|
|
@ -96,50 +96,83 @@ function M.get_clients(buf, method)
|
||||||
end, clients)
|
end, clients)
|
||||||
end
|
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 method string
|
||||||
---@param params fun(client:vim.lsp.Client):table
|
---@param params fun(client:vim.lsp.Client):table
|
||||||
---@param cb fun(client:vim.lsp.Client, result:table, params:table)
|
---@param cb fun(client:vim.lsp.Client, result:table, params:table)
|
||||||
---@async
|
---@async
|
||||||
function M.request(buf, method, params, cb)
|
function R:request(buf, method, params, cb)
|
||||||
local async = Async.running()
|
local clients = type(buf) == "number" and M.get_clients(buf, method) or {
|
||||||
local cancel = {} ---@type fun()[]
|
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()
|
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
|
for _, client in ipairs(clients) do
|
||||||
local p = params(client)
|
local p = params(client)
|
||||||
local status, request_id = client:request(method, p, function(_, result)
|
local status, request_id = client:request(method, p, function(_, result)
|
||||||
if result then
|
if result then
|
||||||
cb(client, result, p)
|
cb(client, result, p)
|
||||||
end
|
end
|
||||||
remaining = remaining - 1
|
self.completed = self.completed + 1
|
||||||
if remaining == 0 then
|
self.async:resume()
|
||||||
async:resume()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
if status and request_id then
|
if status and request_id then
|
||||||
table.insert(cancel, function()
|
table.insert(self.requests, { client_id = client.id, request_id = request_id })
|
||||||
client:cancel_request(request_id)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
self.async:resume()
|
||||||
end)
|
end)
|
||||||
|
self.async:suspend()
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
async:suspend()
|
function R:wait()
|
||||||
cancel = {}
|
while self.completed < #self.requests do
|
||||||
async = Async.nop()
|
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
|
end
|
||||||
|
|
||||||
-- Support for older versions of neovim
|
-- Support for older versions of neovim
|
||||||
|
|
@ -258,7 +291,7 @@ function M.results_to_items(client, results, opts)
|
||||||
detail = result.detail,
|
detail = result.detail,
|
||||||
name = result.name,
|
name = result.name,
|
||||||
text = "",
|
text = "",
|
||||||
range = result.range,
|
range = result.range or result.selectionRange,
|
||||||
item = result,
|
item = result,
|
||||||
}
|
}
|
||||||
local uri = result.location and result.location.uri or result.uri or opts.default_uri
|
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
|
||||||
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
|
---@param opts snacks.picker.lsp.references.Config
|
||||||
---@type snacks.picker.finder
|
---@type snacks.picker.finder
|
||||||
function M.references(opts, ctx)
|
function M.references(opts, ctx)
|
||||||
|
|
@ -402,6 +480,18 @@ function M.references(opts, ctx)
|
||||||
)
|
)
|
||||||
end
|
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
|
---@param opts snacks.picker.lsp.Config
|
||||||
---@type snacks.picker.finder
|
---@type snacks.picker.finder
|
||||||
function M.definitions(opts, ctx)
|
function M.definitions(opts, ctx)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
---@field lsp_declarations fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
---@field lsp_declarations fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||||
---@field lsp_definitions 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_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_references fun(opts?: snacks.picker.lsp.references.Config|{}): snacks.Picker
|
||||||
---@field lsp_symbols fun(opts?: snacks.picker.lsp.symbols.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
|
---@field lsp_type_definitions fun(opts?: snacks.picker.lsp.Config|{}): snacks.Picker
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue