Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 99 additions & 12 deletions lua/codecompanion/providers/actions/fzf_lua.lua
Original file line number Diff line number Diff line change
@@ -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<string, CodeCompanion.ActionItem>
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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add annotations and types to the function names like I do throughout CodeCompanion? Otherwise this looks good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait a sec, I'm adding tests. I'll handle it once that's done. :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I've obviously confused things. I meant provide type annotations like string or number. Not expecting you to come up with an fzf specific class and types

Copy link
Contributor Author

@nothankyouzzz nothankyouzzz Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not entirely sure which parts you’d like me to annotate.

If you’re referring to o and opts in the ActionPreviewer constructor, their content and usage are internal to fzf-lua. Specifically, o corresponds to the previewer table in fzf_exec, and opts is fzf’s internal config table.

Since these annotations were generated by GPT, it might be better to either remove them or replace them with a link to the relevant file in the fzf-lua repo.

The ActionPreviewer class in the diff is a chat previewer that customizes the behavior of fzf_exec, similar to the action_previewer in actions/telescope.lua. Since I haven’t modified any internal API interfaces (new, picker, or select), and as you mentioned there’s no need to cover fzf-specific classes, I believe the existing annotations should be sufficient.

If any part of the PR remains unclear, I’d be happy to provide a more detailed explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve force-pushed the modified annotations — hopefully this clears up any doubts.

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
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion lua/codecompanion/providers/slash_commands/fzf_lua.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions lua/codecompanion/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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