diff --git a/lua/typescript-tools/capabilities.lua b/lua/typescript-tools/capabilities.lua index dce888e..e611247 100644 --- a/lua/typescript-tools/capabilities.lua +++ b/lua/typescript-tools/capabilities.lua @@ -16,6 +16,7 @@ local function make_capabilities() c.InternalCommands.CallApiFunction, c.InternalCommands.RequestReferences, c.InternalCommands.RequestImplementations, + c.InternalCommands.InteractiveCodeAction, }, }, renameProvider = { diff --git a/lua/typescript-tools/integrations.lua b/lua/typescript-tools/integrations.lua new file mode 100644 index 0000000..f46d304 --- /dev/null +++ b/lua/typescript-tools/integrations.lua @@ -0,0 +1,41 @@ +local M = {} + +---@param picker function +---@param callback fun(err: boolean|nil, file: string?) +function M.telescope_picker(picker, callback) + local ok, actions = pcall(require, "telescope.actions") + + if not ok then + vim.notify("Telescope need to be installed to call this integration", vim.log.levels.WARN) + callback(true, nil) + return + end + + local action_state = require "telescope.actions.state" + picker = picker or require("telescope.builtin").find_files + + picker { + attach_mappings = function(prompt_bufnr) + local selected = nil + + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + selected = true + + if selection then + selected = vim.fs.joinpath(vim.loop.cwd(), selection.value) + end + + actions.close(prompt_bufnr) + end) + actions.close:enhance { + post = function() + callback(nil, selected) + end, + } + return true + end, + } +end + +return M diff --git a/lua/typescript-tools/internal_commands.lua b/lua/typescript-tools/internal_commands.lua index 93d635b..c8793b7 100644 --- a/lua/typescript-tools/internal_commands.lua +++ b/lua/typescript-tools/internal_commands.lua @@ -1,7 +1,10 @@ local api = vim.api +local a = require "plenary.async" local c = require "typescript-tools.protocol.constants" local plugin_api = require "typescript-tools.api" +local async = require "typescript-tools.async" +local integrations = require "typescript-tools.integrations" local M = {} @@ -57,4 +60,43 @@ M[c.InternalCommands.RequestImplementations] = function(params) vim.lsp.buf_request(0, c.LspMethods.Implementation, params.arguments) end +M[c.InternalCommands.InteractiveCodeAction] = function(params) + local request = unpack(params.arguments) + a.void(function() + ---@type string|boolean|nil + local target_file + + local telescope_err, file = a.wrap(integrations.telescope_picker, 2)() + + if telescope_err then + target_file = async.ui_input { prompt = "Move to file: " } + else + target_file = file + end + + if target_file == nil or not vim.fn.filereadable(target_file) then + vim.notify("This refactor require existing file", vim.log.levels.WARN) + return + end + + local err, result = async.buf_request_isomorphic( + false, + 0, + c.LspMethods.CodeActionResolve, + vim.tbl_deep_extend( + "force", + request, + { data = { interactiveRefactorArguments = { targetFile = target_file } } } + ) + ) + + if err or not result or not result.edit or (result.edit and vim.tbl_isempty(result.edit)) then + vim.notify("No refactors available", vim.log.levels.WARN) + return + end + + vim.lsp.util.apply_workspace_edit(result.edit, "utf-16") + end)() +end + return M diff --git a/lua/typescript-tools/protocol/constants.lua b/lua/typescript-tools/protocol/constants.lua index 714e70f..0bcfb42 100644 --- a/lua/typescript-tools/protocol/constants.lua +++ b/lua/typescript-tools/protocol/constants.lua @@ -9,6 +9,7 @@ return { CallApiFunction = "call_api_function", RequestReferences = "request_references", RequestImplementations = "request_implementations", + InteractiveCodeAction = "interactive_codeaction", }, ---@enum CommandTypes CommandTypes = { diff --git a/lua/typescript-tools/protocol/text_document/code_action/init.lua b/lua/typescript-tools/protocol/text_document/code_action/init.lua index 2e3d5ee..1fd1c77 100644 --- a/lua/typescript-tools/protocol/text_document/code_action/init.lua +++ b/lua/typescript-tools/protocol/text_document/code_action/init.lua @@ -1,11 +1,18 @@ +local TsserverProvider = require "typescript-tools.tsserver_provider" +local proto_utils = require "typescript-tools.protocol.utils" local c = require "typescript-tools.protocol.constants" -local utils = require "typescript-tools.protocol.utils" local plugin_config = require "typescript-tools.config" +local utils = require "typescript-tools.utils" local M = {} local ALL_CODE_ACTIONS_KEY = "all" +local interactive_codeactions = { + "Move to file", +} +utils.add_reverse_lookup(interactive_codeactions) + local internal_commands_map = { fix_all = { name = "Fix all problems" }, remove_unused = { name = "Remove unused" }, @@ -33,9 +40,11 @@ end ---@type TsserverProtocolHandler function M.handler(request, response, params, ctx) + local tsserver_provider = TsserverProvider.get_instance() + local version = tsserver_provider:get_version() local text_document = params.textDocument - local range = utils.convert_lsp_range_to_tsserver(params.range) + local range = proto_utils.convert_lsp_range_to_tsserver(params.range) local request_range = { file = vim.uri_to_fname(text_document.uri), @@ -43,6 +52,7 @@ function M.handler(request, response, params, ctx) startOffset = range.start.offset, endLine = range["end"].line, endOffset = range["end"].offset, + includeInteractiveActions = utils.version_compare("gt", version, { 5, 1 }), } ctx.dependent_seq = { @@ -74,7 +84,7 @@ function M.handler(request, response, params, ctx) local kind = make_lsp_code_action_kind(action.kind or "") if kind and not action.notApplicableReason then - table.insert(code_actions, { + local code_action = { title = action.description, kind = kind, data = vim.tbl_extend("force", request_range, { @@ -82,7 +92,17 @@ function M.handler(request, response, params, ctx) kind = kind, refactor = refactor.name, }), - }) + } + + code_action.command = interactive_codeactions[action.description] + and { + title = action.description, + command = c.InternalCommands.InteractiveCodeAction, + arguments = { vim.tbl_deep_extend("force", {}, code_action) }, + } + or nil + + table.insert(code_actions, code_action) end end end @@ -96,7 +116,7 @@ function M.handler(request, response, params, ctx) title = fix.description, kind = c.CodeActionKind.QuickFix, edit = { - changes = utils.convert_tsserver_edits_to_lsp(fix.changes), + changes = proto_utils.convert_tsserver_edits_to_lsp(fix.changes), }, }) end diff --git a/tests/requests_spec.lua b/tests/requests_spec.lua index a912644..600c856 100644 --- a/tests/requests_spec.lua +++ b/tests/requests_spec.lua @@ -541,9 +541,11 @@ describe("Lsp request", function() assert.is.same(result[1].title, "Infer function return type") assert.is.same(result[2].title, "Remove variable statement") elseif version and v.gt(version, { 5, 1 }) then - assert.is.same(2, #result) + print(vim.inspect(result)) + assert.is.same(3, #result) assert.is.same(result[1].title, "Move to a new file") - assert.is.same(result[2].title, "Remove variable statement") + assert.is.same(result[2].title, "Move to file") + assert.is.same(result[3].title, "Remove variable statement") else assert.is.same(1, #result) assert.is.same(result[1].title, "Remove variable statement")