feat: Allow inline buttons and popup confirmation for both ACP and normal Providers (#2760)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
65
lua/avante/ui/acp_confirm_adapter.lua
Normal file
65
lua/avante/ui/acp_confirm_adapter.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local Highlights = require("avante.highlights")
|
||||
|
||||
---@class avante.ui.ConfirmAdapter
|
||||
local M = {}
|
||||
|
||||
---@class avante.ui.ACPConfirmAdapter.ACPMappedOptions
|
||||
---@field yes? string
|
||||
---@field all? string
|
||||
---@field no? string
|
||||
|
||||
---Converts the ACP permission options to confirmation popup-compatible format (yes/all/no)
|
||||
---@param options avante.acp.PermissionOption[]
|
||||
---@return avante.ui.ACPConfirmAdapter.ACPMappedOptions
|
||||
function M.map_acp_options(options)
|
||||
local option_map = { yes = nil, all = nil, no = nil }
|
||||
|
||||
for _, opt in ipairs(options) do
|
||||
if opt.kind == "allow_once" then
|
||||
option_map.yes = opt.optionId
|
||||
elseif opt.kind == "allow_always" then
|
||||
option_map.all = opt.optionId
|
||||
elseif opt.kind == "reject_once" then
|
||||
option_map.no = opt.optionId
|
||||
|
||||
-- elseif opt.kind == "reject_always" then
|
||||
-- ignore, no 4th option in the confirm popup yet
|
||||
end
|
||||
end
|
||||
|
||||
return option_map
|
||||
end
|
||||
|
||||
---@class avante.ui.ACPConfirmAdapter.ButtonOption
|
||||
---@field id string
|
||||
---@field icon string
|
||||
---@field name string
|
||||
---@field hl? string
|
||||
|
||||
---@param options avante.acp.PermissionOption[]
|
||||
---@return avante.ui.ACPConfirmAdapter.ButtonOption[]
|
||||
function M.generate_buttons_for_acp_options(options)
|
||||
local items = vim
|
||||
.iter(options)
|
||||
:map(function(item)
|
||||
---@cast item avante.acp.PermissionOption
|
||||
local icon = item.kind == "allow_once" and "" or ""
|
||||
if item.kind == "allow_always" then icon = "" end
|
||||
local hl = nil
|
||||
if item.kind == "reject_once" or item.kind == "reject_always" then hl = Highlights.BUTTON_DANGER_HOVER end
|
||||
---@type avante.ui.ACPConfirmAdapter.ButtonOption
|
||||
local button = {
|
||||
id = item.optionId,
|
||||
name = item.name,
|
||||
icon = icon,
|
||||
hl = hl,
|
||||
}
|
||||
return button
|
||||
end)
|
||||
:totable()
|
||||
-- Sort to have "allow" first, then "allow always", then "reject"
|
||||
table.sort(items, function(a, b) return a.name < b.name end)
|
||||
return items
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -15,19 +15,27 @@ local Config = require("avante.config")
|
||||
---@field _popup NuiPopup | nil
|
||||
---@field _prev_winid number | nil
|
||||
---@field _ns_id number | nil
|
||||
---@field _skip_reject_prompt boolean | nil
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@class avante.ui.ConfirmOptions
|
||||
---@field container_winid? number
|
||||
---@field focus? boolean | nil
|
||||
---@field skip_reject_prompt? boolean ACP doesn't support reject reason
|
||||
---@field permission_options? avante.acp.PermissionOption[] ACP permission options to show in the confirm popup
|
||||
|
||||
---@param message string
|
||||
---@param callback fun(type: "yes" | "all" | "no", reason?: string)
|
||||
---@param opts { container_winid: number, focus?: boolean }
|
||||
---@param opts avante.ui.ConfirmOptions
|
||||
---@return avante.ui.Confirm
|
||||
function M:new(message, callback, opts)
|
||||
local this = setmetatable({}, M)
|
||||
this.message = message
|
||||
this.message = message or ""
|
||||
this.callback = callback
|
||||
this._container_winid = opts.container_winid or vim.api.nvim_get_current_win()
|
||||
this._focus = opts.focus
|
||||
this._skip_reject_prompt = opts.skip_reject_prompt
|
||||
this._ns_id = vim.api.nvim_create_namespace("avante_confirm")
|
||||
return this
|
||||
end
|
||||
@@ -35,12 +43,12 @@ end
|
||||
function M:open()
|
||||
if self._popup then return end
|
||||
self._prev_winid = vim.api.nvim_get_current_win()
|
||||
local message = self.message
|
||||
local message = self.message or ""
|
||||
local callback = self.callback
|
||||
|
||||
local win_width = 60
|
||||
|
||||
local focus_index = 3 -- 1 = Yes, 2 = All Yes, 3 = No
|
||||
local focus_index = 1 -- 1 = Yes, 2 = All Yes, 3 = No
|
||||
|
||||
local BUTTON_NORMAL = Highlights.BUTTON_DEFAULT
|
||||
local BUTTON_FOCUS = Highlights.BUTTON_DEFAULT_HOVER
|
||||
@@ -61,21 +69,26 @@ function M:open()
|
||||
{ " - input ", commentfg },
|
||||
{ " " },
|
||||
})
|
||||
|
||||
local buttons_line = Line:new({
|
||||
{ " [Y]es ", function() return focus_index == 1 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
{ " [Y]es ", function() return focus_index == 1 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
{ " " },
|
||||
{ " [A]ll yes ", function() return focus_index == 2 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
{ " [A]ll yes ", function() return focus_index == 2 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
{ " " },
|
||||
{ " [N]o ", function() return focus_index == 3 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
{ " [N]o ", function() return focus_index == 3 and BUTTON_FOCUS or BUTTON_NORMAL end },
|
||||
})
|
||||
|
||||
local buttons_content = tostring(buttons_line)
|
||||
local buttons_start_col = math.floor((win_width - #buttons_content) / 2)
|
||||
|
||||
local yes_button_pos = buttons_line:get_section_pos(1, buttons_start_col)
|
||||
local all_button_pos = buttons_line:get_section_pos(3, buttons_start_col)
|
||||
local no_button_pos = buttons_line:get_section_pos(5, buttons_start_col)
|
||||
|
||||
local buttons_line_content = string.rep(" ", buttons_start_col) .. buttons_content
|
||||
local keybindings_line_num = 5 + #vim.split(message, "\n")
|
||||
local buttons_line_num = 2 + #vim.split(message, "\n")
|
||||
|
||||
local content = vim
|
||||
.iter({
|
||||
"",
|
||||
@@ -157,12 +170,19 @@ function M:open()
|
||||
callback("yes")
|
||||
return
|
||||
end
|
||||
|
||||
if focus_index == 2 then
|
||||
self:close()
|
||||
Utils.notify("Accept all")
|
||||
callback("all")
|
||||
return
|
||||
end
|
||||
|
||||
if self._skip_reject_prompt then
|
||||
self:close()
|
||||
callback("no")
|
||||
return
|
||||
end
|
||||
|
||||
local prompt_input = PromptInput:new({
|
||||
submit_callback = function(input)
|
||||
self:close()
|
||||
|
||||
Reference in New Issue
Block a user