diff --git a/lua/codecompanion/providers/actions/fzf_lua.lua b/lua/codecompanion/providers/actions/fzf_lua.lua index d13cbe128..583cdc0f2 100644 --- a/lua/codecompanion/providers/actions/fzf_lua.lua +++ b/lua/codecompanion/providers/actions/fzf_lua.lua @@ -1,6 +1,87 @@ local config = require("codecompanion.config") local fzf = require("fzf-lua") +---A custom fzf-lua previewer for the Action Palette +---@class CodeCompanion.Actions.FzfLua.ActionPreviewer: fzf-lua.previewer.Builtin +---@field name_to_item table +local ActionPreviewer = require("fzf-lua.previewer.builtin").base:extend() + +---Initialize the previewer instance +---@param o table? `previewer` table passed to `fzf_exec` +---@param opts fzf-lua.config.Base +function ActionPreviewer:new(o, opts) + ActionPreviewer.super.new(self, o, opts) + self.name_to_item = o.name_to_item +end + +---Return enforced window options for the preview window +---@return table +function ActionPreviewer:gen_winopts() + local enforced_win_opts = { + wrap = true, + number = false, + relativenumber = false, + cursorcolumn = false, + spell = false, + list = false, + signcolumn = "no", + foldcolumn = "0", + colorcolumn = "", + } + return vim.tbl_extend("force", self.winopts, enforced_win_opts) +end + +---Disable clearing preview between updates +---@param _ any +---@return boolean +function ActionPreviewer:should_clear_preview(_) + return false +end + +---Parse the selected entry string into the original action item +---@param entry_str string +---@return CodeCompanion.ActionItem|table +function ActionPreviewer:parse_entry(entry_str) + if not entry_str or entry_str == "" then + return {} + end + return self.name_to_item[entry_str] or {} +end + +---Render the preview buffer for the provided entry +---@param entry_str string +---@return nil +function ActionPreviewer:populate_preview_buf(entry_str) + if not self.win or not self.win:validate_preview() then + return + end + + local item = self:parse_entry(entry_str) + if not item or vim.tbl_isempty(item) then + return + end + + -- Update preview title to the action's name if available + if item.name and type(item.name) == "string" and #item.name > 0 then + self.win:update_preview_title(" " .. item.name .. " ") + end + + if item.description == "[No messages]" and item.bufnr and vim.api.nvim_buf_is_valid(item.bufnr) then + -- Attach the provided buffer directly + -- Protect user buffer from being deleted when the previewer closes + self.listed_buffers[tostring(item.bufnr)] = true + self:set_preview_buf(item.bufnr) + self:update_render_markdown() + else + local tmpbuf = self:get_tmp_buffer() + local description = item.description and vim.split(item.description, "\n", { plain = true }) or { "No description" } + vim.api.nvim_buf_set_lines(tmpbuf, 0, -1, false, description) + self:set_preview_buf(tmpbuf) + end + + self.win:update_preview_scrollbar() +end + ---@class CodeCompanion.Actions.Provider.FZF: CodeCompanion.SlashCommand.Provider ---@field context table ---@field resolve function @@ -11,33 +92,39 @@ function FZF.new(args) return setmetatable(args, { __index = FZF }) end ----@param items table The items to display in the picker +---@param items CodeCompanion.ActionItem[] The items to display in the picker ---@param opts? table The options for the picker ---@return nil function FZF:picker(items, opts) opts = opts or {} opts.prompt = opts.prompt or config.display.action_palette.opts.title or "CodeCompanion actions" - local item_names = {} - local name_to_item = {} + local names = vim.tbl_map(function(item) + return item.name + end, items) + local name_to_item = {} for _, item in ipairs(items) do - table.insert(item_names, item.name) name_to_item[item.name] = item end - fzf.fzf_exec(item_names, { - prompt = opts.prompt, - preview = { - fn = function(it) - return name_to_item[it[1]].description + fzf.fzf_exec(names, { + winopts = { title = " " .. opts.prompt .. " " }, + previewer = { + _ctor = function() + return ActionPreviewer end, + name_to_item = name_to_item, }, actions = { ["default"] = function(selected) - if selected or vim.tbl_count(selected) ~= 0 then - for _, selection in ipairs(selected) do - return require("codecompanion.providers.actions.shared").select(self, name_to_item[selection]) + if not selected or #selected == 0 then + return + end + for _, selection in ipairs(selected) do + local item = name_to_item[selection] + if item then + return require("codecompanion.providers.actions.shared").select(self, item) end end end, diff --git a/lua/codecompanion/providers/slash_commands/fzf_lua.lua b/lua/codecompanion/providers/slash_commands/fzf_lua.lua index 6fe918901..d28c5e816 100644 --- a/lua/codecompanion/providers/slash_commands/fzf_lua.lua +++ b/lua/codecompanion/providers/slash_commands/fzf_lua.lua @@ -22,7 +22,7 @@ end ---@return table function FZF:display(transformer) return { - prompt = self.title, + winopts = { title = " " .. self.title .. " " }, actions = { ["default"] = function(selected, opts) if selected or vim.tbl_count(selected) ~= 0 then diff --git a/lua/codecompanion/types.lua b/lua/codecompanion/types.lua index c95d64368..1fe7ad8b2 100644 --- a/lua/codecompanion/types.lua +++ b/lua/codecompanion/types.lua @@ -164,3 +164,29 @@ ---@field validate table Validate an item ---@field resolve table Resolve an item into an action ---@field context table The buffer context + +---@class CodeCompanion.ActionOptions +---@field index? number Ordering position for display +---@field stop_context_insertion? boolean Prevents auto-added context on open +---@field modes? string[] Optional list of modes where the action is visible (e.g. {"n","v"}) +---@field is_default? boolean Marks prompt as part of default library +---@field user_prompt? boolean Ask user for additional input before executing +---@field auto_submit? boolean Auto-submit the resulting prompt to the LLM +---@field short_name? string Short alias used for command-line invocation + +---@class CodeCompanion.ActionPicker +---@field prompt? string Optional prompt/title shown by providers +---@field items table|fun(context: table): table Items to show or a function returning items +---@field columns? any Optional provider-specific column config + +---@class CodeCompanion.ActionItem +---@field name string Display name in the Action Palette +---@field description? string Description or preview text +---@field strategy? string Strategy to execute (e.g. "chat") +---@field type? string Optional type hint used by some providers +---@field opts? CodeCompanion.ActionOptions Additional behavior/config flags +---@field prompts? table Optional initial prompts specification +---@field picker? CodeCompanion.ActionPicker Optional nested picker spec +---@field callback? fun(context: table): any Optional direct action callback +---@field context? table Optional context forwarded to execution +---@field bufnr? integer Optional buffer number for open chats preview