@@ -96,50 +96,83 @@ function M.get_clients(buf, method)
9696 end , clients )
9797end
9898
99- --- @param buf number
99+ --- @class snacks.picker.lsp.Requester
100+ --- @field async snacks.picker.Async
101+ --- @field requests { client_id : number , request_id : number } []
102+ --- @field completed number
103+ local R = {}
104+ R .__index = R
105+
106+ function R .new ()
107+ local self = setmetatable ({}, R )
108+ self .async = Async .running ()
109+ self .requests = {}
110+ self .completed = 0
111+ self .async :on (
112+ " abort" ,
113+ vim .schedule_wrap (function ()
114+ self :cancel ()
115+ end )
116+ )
117+ return self
118+ end
119+
120+ function R :cancel ()
121+ while # self .requests > 0 do
122+ local req = table.remove (self .requests )
123+ local client = vim .lsp .get_client_by_id (req .client_id )
124+ if client then
125+ client :cancel_request (req .request_id )
126+ end
127+ end
128+ end
129+
130+ --- @param buf number | vim.lsp.Client
100131--- @param method string
101132--- @param params fun ( client : vim.lsp.Client ): table
102133--- @param cb fun ( client : vim.lsp.Client , result : table , params : table )
103134--- @async
104- function M .request (buf , method , params , cb )
105- local async = Async .running ()
106- local cancel = {} --- @type fun () []
135+ function R :request (buf , method , params , cb )
136+ local clients = type (buf ) == " number" and M .get_clients (buf , method ) or {
137+ wrap (buf --[[ @as vim.lsp.Client]] ),
138+ }
139+ if vim .tbl_isempty (clients ) then
140+ return self .async :resume ()
141+ end
107142
108- async :on (
109- " abort" ,
110- vim .schedule_wrap (function ()
111- vim .tbl_map (pcall , cancel )
112- cancel = {}
113- end )
114- )
115143 vim .schedule (function ()
116- local clients = M .get_clients (buf , method )
117- if vim .tbl_isempty (clients ) then
118- return async :resume ()
119- end
120- local remaining = # clients
121144 for _ , client in ipairs (clients ) do
122145 local p = params (client )
123146 local status , request_id = client :request (method , p , function (_ , result )
124147 if result then
125148 cb (client , result , p )
126149 end
127- remaining = remaining - 1
128- if remaining == 0 then
129- async :resume ()
130- end
150+ self .completed = self .completed + 1
151+ self .async :resume ()
131152 end )
132153 if status and request_id then
133- table.insert (cancel , function ()
134- client :cancel_request (request_id )
135- end )
154+ table.insert (self .requests , { client_id = client .id , request_id = request_id })
136155 end
137156 end
157+ self .async :resume ()
138158 end )
159+ self .async :suspend ()
160+ return self
161+ end
162+
163+ function R :wait ()
164+ while self .completed < # self .requests do
165+ self .async :suspend ()
166+ end
167+ end
139168
140- async :suspend ()
141- cancel = {}
142- async = Async .nop ()
169+ --- @param buf number
170+ --- @param method string
171+ --- @param params fun ( client : vim.lsp.Client ): table
172+ --- @param cb fun ( client : vim.lsp.Client , result : table , params : table )
173+ --- @async
174+ function M .request (buf , method , params , cb )
175+ R .new ():request (buf , method , params , cb ):wait ()
143176end
144177
145178-- Support for older versions of neovim
@@ -258,7 +291,7 @@ function M.results_to_items(client, results, opts)
258291 detail = result .detail ,
259292 name = result .name ,
260293 text = " " ,
261- range = result .range ,
294+ range = result .range or result . selectionRange ,
262295 item = result ,
263296 }
264297 local uri = result .location and result .location .uri or result .uri or opts .default_uri
@@ -389,6 +422,51 @@ function M.symbols(opts, ctx)
389422 end
390423end
391424
425+ --- @param opts snacks.picker.lsp.Config
426+ --- @param filter snacks.picker.Filter
427+ --- @param incoming ? boolean
428+ function M .call_hierarchy (opts , filter , incoming )
429+ local method = (" callHierarchy/%sCalls" ):format (incoming and " incoming" or " outgoing" )
430+ local buf = filter .current_buf
431+ local win = filter .current_win
432+
433+ --- @async
434+ --- @param cb async fun ( item : snacks.picker.finder.Item )
435+ return function (cb )
436+ local requester = R .new ()
437+ requester :request (buf , " textDocument/prepareCallHierarchy" , function (client )
438+ return vim .lsp .util .make_position_params (win , client .offset_encoding )
439+ end , function (client , result )
440+ --- @cast result lsp.CallHierarchyItem[]
441+ for _ , res in ipairs (result or {}) do
442+ requester :request (client , method , function ()
443+ return { item = res }
444+ end , function (_ , calls )
445+ --- @cast calls (lsp.CallHierarchyIncomingCall | lsp.CallHierarchyOutgoingCall )[]
446+
447+ local call_items = {} --- @type lsp.CallHierarchyItem[]
448+ --- @param call lsp.CallHierarchyIncomingCall | lsp.CallHierarchyOutgoingCall
449+ for _ , call in ipairs (calls ) do
450+ if incoming then
451+ for _ , range in ipairs (call .fromRanges or {}) do
452+ local from = vim .deepcopy (call .from )
453+ from .selectionRange = range or from .selectionRange
454+ table.insert (call_items , from )
455+ end
456+ else
457+ table.insert (call_items , call .to )
458+ end
459+ end
460+
461+ local items = M .results_to_items (client , call_items , { default_uri = res .uri })
462+ vim .tbl_map (cb , items )
463+ end )
464+ end
465+ end )
466+ requester :wait ()
467+ end
468+ end
469+
392470--- @param opts snacks.picker.lsp.references.Config
393471--- @type snacks.picker.finder
394472function M .references (opts , ctx )
@@ -402,6 +480,18 @@ function M.references(opts, ctx)
402480 )
403481end
404482
483+ --- @param opts snacks.picker.lsp.Config
484+ --- @type snacks.picker.finder
485+ function M .incoming_calls (opts , ctx )
486+ return M .call_hierarchy (opts , ctx .filter , true )
487+ end
488+
489+ --- @param opts snacks.picker.lsp.Config
490+ --- @type snacks.picker.finder
491+ function M .outgoing_calls (opts , ctx )
492+ return M .call_hierarchy (opts , ctx .filter , false )
493+ end
494+
405495--- @param opts snacks.picker.lsp.Config
406496--- @type snacks.picker.finder
407497function M .definitions (opts , ctx )
0 commit comments