snacks.nvim icon indicating copy to clipboard operation
snacks.nvim copied to clipboard

feature: lsp_incoming_calls and lsp_outgoing_calls support for Snacks.picker

Open DanWlker opened this issue 1 year ago • 1 comments

Did you check the docs?

  • [X] I have read all the snacks.nvim docs

Is your feature request related to a problem? Please describe.

fzf-lua and telescope both have lsp_incoming_calls and lsp_outgoing_calls, would be great if Snacks.picker supports it as well

Describe the solution you'd like

Support lsp_incoming_calls and lsp_outgoing_calls

Describe alternatives you've considered

Just using lsp_references

Additional context

No response

DanWlker avatar Jan 15 '25 01:01 DanWlker

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Feb 14 '25 02:02 github-actions[bot]

This issue was closed because it has been stalled for 7 days with no activity.

github-actions[bot] avatar Feb 21 '25 02:02 github-actions[bot]

DanWlker, you should probably reopen this? I dunno if @folke is interested in doing something like this though.

vinoff avatar Feb 21 '25 10:02 vinoff

I am not

folke avatar Feb 21 '25 12:02 folke

I'll give this a shot! 🤞

acarl005 avatar Apr 07 '25 15:04 acarl005

In the meantime, here is something people can add to their user config...

vim.keymap.set("n", "<leader>lc", function()
  require("snacks").picker.pick {
    title = "LSP Incoming Calls",
    finder = function(opts, ctx)
      local lsp = require "snacks.picker.source.lsp"
      local Async = require "snacks.picker.util.async"
      local win = ctx.filter.current_win
      local buf = ctx.filter.current_buf
      local bufmap = lsp.bufmap()

      ---@async
      ---@param cb async fun(item: snacks.picker.finder.Item)
      return function(cb)
        local async = Async.running()
        local cancel = {} ---@type fun()[]

        async:on(
          "abort",
          vim.schedule_wrap(function()
            vim.tbl_map(pcall, cancel)
            cancel = {}
          end)
        )

        vim.schedule(function()
          -- First prepare the call hierarchy
          local clients = lsp.get_clients(buf, "textDocument/prepareCallHierarchy")
          if vim.tbl_isempty(clients) then return async:resume() end

          local remaining = #clients
          for _, client in ipairs(clients) do
            local params = vim.lsp.util.make_position_params(win, client.offset_encoding)
            local status, request_id = client:request("textDocument/prepareCallHierarchy", params, function(_, result)
              if result and not vim.tbl_isempty(result) then
                -- Then get incoming calls for each item
                local call_remaining = #result
                if call_remaining == 0 then
                  remaining = remaining - 1
                  if remaining == 0 then async:resume() end
                  return
                end

                for _, item in ipairs(result) do
                  local call_params = { item = item }
                  local call_status, call_request_id = client:request(
                    "callHierarchy/incomingCalls",
                    call_params,
                    function(_, calls)
                      if calls then
                        for _, call in ipairs(calls) do
                          ---@type snacks.picker.finder.Item
                          local item = {
                            text = call.from.name .. "    " .. call.from.detail,
                            kind = lsp.symbol_kind(call.from.kind),
                            line = "    " .. call.from.detail,
                          }
                          local loc = {
                            uri = call.from.uri,
                            range = call.from.range,
                          }
                          lsp.add_loc(item, loc, client)
                          item.buf = bufmap[item.file]
                          item.text = item.file .. "    " .. call.from.detail
                          ---@diagnostic disable-next-line: await-in-sync
                          cb(item)
                        end
                      end
                      call_remaining = call_remaining - 1
                      if call_remaining == 0 then
                        remaining = remaining - 1
                        if remaining == 0 then async:resume() end
                      end
                    end
                  )
                  if call_status and call_request_id then
                    table.insert(cancel, function() client:cancel_request(call_request_id) end)
                  end
                end
              else
                remaining = remaining - 1
                if remaining == 0 then async:resume() end
              end
            end)
            if status and request_id then table.insert(cancel, function() client:cancel_request(request_id) end) end
          end
        end)

        async:suspend()
        cancel = {}
        async = Async.nop()
      end
    end,
  }
end, { desc = "LSP incoming function calls" })

acarl005 avatar Apr 08 '25 16:04 acarl005

This is literally the last picker I am missing. I used to use the incoming calls picker from Telescope so often, and I would love this one to be part of the Snacks collection.

pawelgrzybek avatar Jun 24 '25 21:06 pawelgrzybek