diff --git a/lua/fzf-lua/previewer/codeaction.lua b/lua/fzf-lua/previewer/codeaction.lua index 6be9b5c1..c160bd98 100644 --- a/lua/fzf-lua/previewer/codeaction.lua +++ b/lua/fzf-lua/previewer/codeaction.lua @@ -142,46 +142,69 @@ local function diff_tuple(err, tuple, diff_opts) end -- https://github.com/neovim/neovim/blob/v0.9.4/runtime/lua/vim/lsp/buf.lua#L666 -local function preview_action_tuple(self, tuple, idx, callback) +local function preview_action_tuple(self, idx, callback) + local tuple = self.opts._items[idx] -- neovim changed the ui.select params with 0.10.0 (#947) -- { client_id, action } ==> { ctx = , action = } if tuple.ctx then tuple = { tuple.ctx.client_id, tuple.action } end - local client = assert(vim.lsp.get_client_by_id(tuple[1])) + -- First check our resolved action cache, if "codeAction/resolve" failed, ignore + -- the error (we already alerted the user about it in `handle_resolved_response`) + -- and display the default "unsupported" message from the original action + if self._resolved_actions[idx] then + local resolved = self._resolved_actions[idx] + return diff_tuple(nil, resolved.err and tuple or resolved.tuple, self.diff_opts) + end + -- Not found in cache, check if the client supports code action resolving + local client_id = tuple[1] + local client = assert(vim.lsp.get_client_by_id(client_id)) local action = tuple[2] - if - not action.edit - and client - and vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") - then - local function on_result(diff_callback, err, resolved_action) + local supports_resolve = utils.__HAS_NVIM_010 + -- runtime/lua/lsp/buf.lua:on_user_choice + and (function() + ---@var choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext} + local ms = require("vim.lsp.protocol").Methods + local choice = self.opts._items[idx] + local bufnr = assert(choice.ctx.bufnr, "Must have buffer number") + local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = bufnr }) + return vim.tbl_get(reg or {}, "registerOptions", "resolveProvider") + or client.supports_method(ms.codeAction_resolve) + end)() + -- prior to nvim 0.10 we could check `client.server_capabilities` + or vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") + if not action.edit and client and supports_resolve then + -- Action is not a worksapce edit, attempt to resolve the code action + -- in case it resolves to a workspace edit + local function handle_resolved_response(err, resolved_action) if err then - return diff_callback(err, tuple, self.diff_opts) - else - return diff_callback(err, { tuple[1], resolved_action }, self.diff_opts) + -- alert the user "codeAction/resolve" request failed + utils.warn(diff_tuple(err, nil, self.diff_opts)[1]) end + local resolved = { err = err, tuple = { client_id, resolved_action } } + self._resolved_actions[idx] = resolved + -- HACK: due to upstream bug with jdtls calling resolve messes + -- errs the workspace edit with "-32603: Internal error." (#1007) + if not err and client.name == "jdtls" then + if utils.__HAS_NVIM_010 then + self.opts._items[idx].action = resolved_action + else + self.opts._items[idx][2] = resolved_action + end + end + return resolved.tuple end - local function update_internal_items(resolved_action) - self.opts._items[idx] = { tuple[1], resolved_action } - end - if callback then client.request("codeAction/resolve", action, function(err, resolved_action) - update_internal_items(resolved_action) - on_result(callback, err, resolved_action) + local resolved_tuple = handle_resolved_response(err, resolved_action) + callback(nil, err and tuple or resolved_tuple) end) return { string.format("Resolving action (%s)...", action.kind) } else local res = client.request_sync("codeAction/resolve", action) local err, resolved_action = res and res.err, res and res.result - update_internal_items(resolved_action) - if type(err) == "table" or type(resolved_action) == "table" then - return on_result(diff_tuple, err, resolved_action) - else - -- display the default "unsupported" message - return diff_tuple(nil, tuple, self.diff_opts) - end + local resolved_tuple = handle_resolved_response(err, resolved_action) + return diff_tuple(nil, err and tuple or resolved_tuple, self.diff_opts) end else return diff_tuple(nil, tuple, self.diff_opts) @@ -190,12 +213,17 @@ end M.builtin = builtin.base:extend() +M.builtin.preview_action_tuple = preview_action_tuple function M.builtin:new(o, opts, fzf_win) assert(opts._ui_select and opts._ui_select.kind == "codeaction") M.builtin.super.new(self, o, opts, fzf_win) setmetatable(self, M.builtin) self.diff_opts = o.diff_opts + self._resolved_actions = {} + for i, _ in ipairs(self.opts._items) do + self._resolved_actions[i] = false + end return self end @@ -212,8 +240,7 @@ function M.builtin:populate_preview_buf(entry_str) if not self.win or not self.win:validate_preview() then return end local idx = tonumber(entry_str:match("^%d+%.")) assert(type(idx) == "number") - local tuple = self.opts._items[idx] - local lines = preview_action_tuple(self, tuple, idx, + local lines = self:preview_action_tuple(idx, -- use the async version for "codeAction/resolve" function(err, resolved_tuple) if vim.api.nvim_buf_is_valid(self.tmpbuf) then @@ -230,6 +257,7 @@ function M.builtin:populate_preview_buf(entry_str) end M.native = native.base:extend() +M.native.preview_action_tuple = preview_action_tuple function M.native:new(o, opts, fzf_win) assert(opts._ui_select and opts._ui_select.kind == "codeaction") @@ -240,6 +268,10 @@ function M.native:new(o, opts, fzf_win) self.pager = self.pager() end self.diff_opts = o.diff_opts + self._resolved_actions = {} + for i, _ in ipairs(self.opts._items) do + self._resolved_actions[i] = false + end return self end @@ -248,8 +280,7 @@ function M.native:cmdline(o) local act = shell.raw_action(function(entries, _, _) local idx = tonumber(entries[1]:match("^%d+%.")) assert(type(idx) == "number") - local tuple = self.opts._items[idx] - local lines = preview_action_tuple(self, tuple, idx) + local lines = self:preview_action_tuple(idx) return table.concat(lines, "\r\n") end, "{}", self.opts.debug) if self.pager and #self.pager > 0 and vim.fn.executable(self.pager:match("[^%s]+")) == 1 then