feat: request permission button group (#2685)
This commit is contained in:
@@ -362,6 +362,9 @@ function M.get_tool_display_name(message)
|
||||
local pieces = vim.split(param, "\n")
|
||||
if #pieces > 1 then param = pieces[1] .. "..." end
|
||||
end
|
||||
if native_tool_name == "execute" and not param then
|
||||
if message.acp_tool_call and message.acp_tool_call.title then param = message.acp_tool_call.title end
|
||||
end
|
||||
if not param and path then
|
||||
local relative_path = Utils.relative_path(path)
|
||||
param = relative_path
|
||||
@@ -466,7 +469,13 @@ local function tool_to_lines(item, message, messages, expanded)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #lines <= 1 then table.insert(lines, Line:new({ { decoration }, { "completed" } })) end
|
||||
if #lines <= 1 then
|
||||
if state == "generating" then
|
||||
table.insert(lines, Line:new({ { decoration }, { "...", Highlights.AVANTE_COMMENT_FG } }))
|
||||
else
|
||||
table.insert(lines, Line:new({ { decoration }, { "completed" } }))
|
||||
end
|
||||
end
|
||||
local last_line = lines[#lines]
|
||||
last_line.sections[1][1] = "╰─ "
|
||||
return lines
|
||||
|
||||
@@ -14,7 +14,7 @@ local Providers = require("avante.providers")
|
||||
local LLMToolHelpers = require("avante.llm_tools.helpers")
|
||||
local LLMTools = require("avante.llm_tools")
|
||||
local History = require("avante.history")
|
||||
local Selector = require("avante.ui.selector")
|
||||
local Highlights = require("avante.highlights")
|
||||
|
||||
---@class avante.LLM
|
||||
local M = {}
|
||||
@@ -801,9 +801,10 @@ end
|
||||
---@param opts AvanteLLMStreamOptions
|
||||
function M._stream_acp(opts)
|
||||
Utils.debug("use ACP", Config.provider)
|
||||
local Render = require("avante.history.render")
|
||||
---@type table<string, avante.HistoryMessage>
|
||||
local tool_call_messages = {}
|
||||
---@type avante.HistoryMessage
|
||||
local last_tool_call_message = nil
|
||||
local acp_provider = Config.acp_providers[Config.provider]
|
||||
local on_messages_add = function(messages)
|
||||
if opts.on_messages_add then opts.on_messages_add(messages) end
|
||||
@@ -816,6 +817,7 @@ function M._stream_acp(opts)
|
||||
name = update.kind,
|
||||
input = update.rawInput or {},
|
||||
})
|
||||
last_tool_call_message = message
|
||||
message.acp_tool_call = update
|
||||
if update.status == "pending" or update.status == "in_progress" then message.is_calling = true end
|
||||
tool_call_messages[update.toolCallId] = message
|
||||
@@ -908,6 +910,7 @@ function M._stream_acp(opts)
|
||||
tool_call_message.acp_tool_call = update
|
||||
end
|
||||
if tool_call_message.acp_tool_call then
|
||||
if update.content and next(update.content) == nil then update.content = nil end
|
||||
tool_call_message.acp_tool_call = vim.tbl_deep_extend("force", tool_call_message.acp_tool_call, update)
|
||||
end
|
||||
tool_call_message.tool_use_logs = tool_call_message.tool_use_logs or {}
|
||||
@@ -933,83 +936,76 @@ function M._stream_acp(opts)
|
||||
end
|
||||
end,
|
||||
on_request_permission = function(tool_call, options, callback)
|
||||
local message = tool_call_messages[tool_call.toolCallId]
|
||||
if not message then
|
||||
add_tool_call_message(tool_call)
|
||||
else
|
||||
if message.acp_tool_call then
|
||||
message.acp_tool_call = vim.tbl_deep_extend("force", message.acp_tool_call, tool_call)
|
||||
tool_call = message.acp_tool_call
|
||||
end
|
||||
local sidebar = require("avante").get()
|
||||
if not sidebar then
|
||||
Utils.error("Avante sidebar not found")
|
||||
return
|
||||
end
|
||||
---@cast tool_call avante.acp.ToolCall
|
||||
local items = vim
|
||||
.iter(options)
|
||||
:map(
|
||||
function(item)
|
||||
return {
|
||||
id = item.optionId,
|
||||
title = item.name,
|
||||
}
|
||||
:map(function(item)
|
||||
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
|
||||
)
|
||||
return {
|
||||
id = item.optionId,
|
||||
name = item.name,
|
||||
icon = icon,
|
||||
hl = hl,
|
||||
}
|
||||
end)
|
||||
:totable()
|
||||
local default_item = vim.iter(items):find(function(item) return item.id == options[1].optionId end)
|
||||
|
||||
local function on_select(item_ids)
|
||||
if not item_ids then return end
|
||||
local choice = vim.iter(items):find(function(item) return item.id == item_ids[1] end)
|
||||
if not choice then return end
|
||||
Utils.debug("on_select", choice.id)
|
||||
callback(choice.id)
|
||||
sidebar.permission_button_options = items
|
||||
sidebar.permission_handler = function(id)
|
||||
callback(id)
|
||||
sidebar.scroll = true
|
||||
sidebar.permission_button_options = nil
|
||||
sidebar.permission_handler = nil
|
||||
sidebar._history_cache_invalidated = true
|
||||
sidebar:update_content("")
|
||||
end
|
||||
|
||||
local tool_name, error = Render.get_tool_display_name(message)
|
||||
if error then
|
||||
Utils.error(error)
|
||||
tool_name = message.message.content[1].name
|
||||
local message = tool_call_messages[tool_call.toolCallId]
|
||||
if not message then
|
||||
message = add_tool_call_message(tool_call)
|
||||
else
|
||||
if message.acp_tool_call then
|
||||
if tool_call.content and next(tool_call.content) == nil then tool_call.content = nil end
|
||||
message.acp_tool_call = vim.tbl_deep_extend("force", message.acp_tool_call, tool_call)
|
||||
end
|
||||
end
|
||||
|
||||
local selector = Selector:new({
|
||||
title = tool_name,
|
||||
items = items,
|
||||
default_item_id = default_item and default_item.name or nil,
|
||||
provider = Config.selector.provider,
|
||||
provider_opts = Config.selector.provider_opts,
|
||||
on_select = on_select,
|
||||
get_preview_content = function(_)
|
||||
local file_content = ""
|
||||
local filetype = "text"
|
||||
local content = tool_call.content
|
||||
if type(content) == "table" then
|
||||
for _, item in ipairs(content) do
|
||||
if item.type == "content" then
|
||||
if type(item.content) == "table" then
|
||||
if item.content.type == "text" then
|
||||
file_content = file_content .. item.content.text .. "\n\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
if item.type == "diff" then
|
||||
local unified_diff = Utils.get_unified_diff(item.oldText, item.newText, { algorithm = "myers" })
|
||||
local result = "--- a/" .. item.path .. "\n+++ b/" .. item.path .. "\n" .. unified_diff .. "\n\n"
|
||||
filetype = "diff"
|
||||
file_content = file_content .. result
|
||||
end
|
||||
end
|
||||
end
|
||||
return file_content, filetype
|
||||
end,
|
||||
})
|
||||
|
||||
selector:open()
|
||||
on_messages_add({ message })
|
||||
end,
|
||||
on_read_file = function(path, line, limit, callback)
|
||||
local abs_path = Utils.to_absolute_path(path)
|
||||
local lines = Utils.read_file_from_buf_or_disk(abs_path)
|
||||
lines = lines or {}
|
||||
if line ~= nil and limit ~= nil then lines = vim.list_slice(lines, line, line + limit) end
|
||||
callback(table.concat(lines, "\n"))
|
||||
local content = table.concat(lines, "\n")
|
||||
if
|
||||
last_tool_call_message
|
||||
and last_tool_call_message.acp_tool_call
|
||||
and last_tool_call_message.acp_tool_call.kind == "read"
|
||||
then
|
||||
if
|
||||
last_tool_call_message.acp_tool_call.content
|
||||
and next(last_tool_call_message.acp_tool_call.content) == nil
|
||||
then
|
||||
last_tool_call_message.acp_tool_call.content = {
|
||||
{
|
||||
type = "content",
|
||||
content = {
|
||||
type = "text",
|
||||
text = content,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
callback(content)
|
||||
end,
|
||||
on_write_file = function(path, content, callback)
|
||||
local abs_path = Utils.to_absolute_path(path)
|
||||
|
||||
@@ -21,6 +21,7 @@ local Render = require("avante.history.render")
|
||||
local Line = require("avante.ui.line")
|
||||
local LRUCache = require("avante.utils.lru_cache")
|
||||
local logo = require("avante.utils.logo")
|
||||
local ButtonGroupLine = require("avante.ui.button_group_line")
|
||||
|
||||
local RESULT_BUF_NAME = "AVANTE_RESULT"
|
||||
local VIEW_BUFFER_UPDATED_PATTERN = "AvanteViewBufferUpdated"
|
||||
@@ -79,7 +80,8 @@ Sidebar.__index = Sidebar
|
||||
---@field acp_client avante.acp.ACPClient | nil
|
||||
---@field acp_session_id string | nil
|
||||
---@field post_render? fun(sidebar: avante.Sidebar)
|
||||
---@field message_button_handlers table<string, table<string, fun(arg: any)>>
|
||||
---@field permission_handler fun(id: string) | nil
|
||||
---@field permission_button_options ({ id: string, icon: string|nil, name: string }[]) | nil
|
||||
---@field expanded_message_uuids table<string, boolean>
|
||||
---@field tool_message_positions table<string, [integer, integer]>
|
||||
---@field skip_line_count integer | nil
|
||||
@@ -116,7 +118,6 @@ function Sidebar:new(id)
|
||||
_cached_history_lines = nil,
|
||||
_history_cache_invalidated = true,
|
||||
post_render = nil,
|
||||
message_handlers = {},
|
||||
tool_message_positions = {},
|
||||
expanded_message_ids = {},
|
||||
current_tool_use_extmark_id = nil,
|
||||
@@ -156,7 +157,6 @@ function Sidebar:reset()
|
||||
self.scroll = true
|
||||
self.old_result_lines = {}
|
||||
self.token_count = nil
|
||||
self.message_button_handlers = {}
|
||||
self.tool_message_positions = {}
|
||||
self.expanded_message_uuids = {}
|
||||
self.current_tool_use_extmark_id = nil
|
||||
@@ -868,7 +868,20 @@ function Sidebar:handle_expand_message(message_uuid, expanded)
|
||||
Utils.debug("handle_expand_message", message_uuid, expanded)
|
||||
self.expanded_message_uuids[message_uuid] = expanded
|
||||
self._history_cache_invalidated = true
|
||||
local old_scroll = self.scroll
|
||||
self.scroll = false
|
||||
self:update_content("")
|
||||
self.scroll = old_scroll
|
||||
vim.defer_fn(function()
|
||||
local cursor_line = api.nvim_win_get_cursor(self.containers.result.winid)[1]
|
||||
local positions = self.tool_message_positions[message_uuid]
|
||||
if positions then
|
||||
local skip_line_count = self.skip_line_count or 0
|
||||
if cursor_line > positions[2] + skip_line_count then
|
||||
api.nvim_win_set_cursor(self.containers.result.winid, { positions[2] + skip_line_count, 0 })
|
||||
end
|
||||
end
|
||||
end, 100)
|
||||
end
|
||||
|
||||
function Sidebar:edit_user_request()
|
||||
@@ -1760,6 +1773,16 @@ function Sidebar:update_content(content, opts)
|
||||
api.nvim_set_option_value("filetype", "Avante", { buf = bufnr })
|
||||
Utils.lock_buf(bufnr)
|
||||
|
||||
vim.defer_fn(function()
|
||||
if self.permission_button_options and self.permission_handler then
|
||||
local cur_winid = api.nvim_get_current_win()
|
||||
if cur_winid == self.containers.result.winid then
|
||||
local line_count = api.nvim_buf_line_count(bufnr)
|
||||
api.nvim_win_set_cursor(cur_winid, { line_count - 3, 0 })
|
||||
end
|
||||
end
|
||||
end, 100)
|
||||
|
||||
if opts.focus and not self:is_focused_on_result() then
|
||||
xpcall(function() api.nvim_set_current_win(self.containers.result.winid) end, function(err)
|
||||
Utils.debug("Failed to set current win:", err)
|
||||
@@ -1840,13 +1863,13 @@ function Sidebar:get_layout()
|
||||
return vim.tbl_contains({ "left", "right" }, calculate_config_window_position()) and "vertical" or "horizontal"
|
||||
end
|
||||
|
||||
---@param ctx table
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@param ctx table
|
||||
---@param ignore_record_prefix boolean | nil
|
||||
---@param expanded boolean | nil
|
||||
---@return avante.ui.Line[]
|
||||
local function _get_message_lines(message, messages, ctx, ignore_record_prefix, expanded)
|
||||
function Sidebar:_get_message_lines(ctx, message, messages, ignore_record_prefix)
|
||||
local expanded = self.expanded_message_uuids[message.uuid]
|
||||
if message.visible == false then return {} end
|
||||
local lines = Render.message_to_lines(message, messages, expanded)
|
||||
if message.is_user_submission and not ignore_record_prefix then
|
||||
@@ -1892,15 +1915,24 @@ end
|
||||
|
||||
local _message_to_lines_lru_cache = LRUCache:new(100)
|
||||
|
||||
---@param ctx table
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@param ctx table
|
||||
---@param ignore_record_prefix boolean | nil
|
||||
---@param expanded boolean | nil
|
||||
---@return avante.ui.Line[]
|
||||
local function get_message_lines(message, messages, ctx, ignore_record_prefix, expanded)
|
||||
function Sidebar:get_message_lines(ctx, message, messages, ignore_record_prefix)
|
||||
local expanded = self.expanded_message_uuids[message.uuid]
|
||||
if message.state == "generating" or message.is_calling then
|
||||
return _get_message_lines(message, messages, ctx, ignore_record_prefix, expanded)
|
||||
local lines = self:_get_message_lines(ctx, message, messages, ignore_record_prefix)
|
||||
if self.permission_handler and self.permission_button_options then
|
||||
local button_group_line = ButtonGroupLine:new(self.permission_button_options, {
|
||||
on_click = self.permission_handler,
|
||||
group_label = "Waiting for Confirmation... ",
|
||||
})
|
||||
table.insert(lines, Line:new({ { "" } }))
|
||||
table.insert(lines, button_group_line)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
local text_len = 0
|
||||
local content = message.message.content
|
||||
@@ -1920,7 +1952,7 @@ local function get_message_lines(message, messages, ctx, ignore_record_prefix, e
|
||||
local cache_key = message.uuid .. ":" .. tostring(text_len) .. ":" .. tostring(expanded == true)
|
||||
local cached_lines = _message_to_lines_lru_cache:get(cache_key)
|
||||
if cached_lines then return cached_lines end
|
||||
local lines = _get_message_lines(message, messages, ctx, ignore_record_prefix, expanded)
|
||||
local lines = self:_get_message_lines(ctx, message, messages, ignore_record_prefix)
|
||||
_message_to_lines_lru_cache:set(cache_key, lines)
|
||||
return lines
|
||||
end
|
||||
@@ -1937,8 +1969,7 @@ function Sidebar:get_history_lines(history, ignore_record_prefix)
|
||||
local tool_message_positions = {}
|
||||
local is_first_user_submission = true
|
||||
for _, message in ipairs(history_messages) do
|
||||
local expanded = self.expanded_message_uuids[message.uuid]
|
||||
local lines = get_message_lines(message, history_messages, ctx, ignore_record_prefix, expanded)
|
||||
local lines = self:get_message_lines(ctx, message, history_messages, ignore_record_prefix)
|
||||
if #lines == 0 then goto continue end
|
||||
if message.is_user_submission then
|
||||
if not is_first_user_submission then
|
||||
@@ -1961,6 +1992,8 @@ function Sidebar:get_history_lines(history, ignore_record_prefix)
|
||||
::continue::
|
||||
end
|
||||
table.insert(res, Line:new({ { "" } }))
|
||||
table.insert(res, Line:new({ { "" } }))
|
||||
table.insert(res, Line:new({ { "" } }))
|
||||
return res, tool_message_positions
|
||||
end
|
||||
|
||||
@@ -2188,7 +2221,6 @@ function Sidebar:new_chat(args, cb)
|
||||
self:reload_chat_history()
|
||||
self.current_state = nil
|
||||
self.acp_session_id = nil
|
||||
self.message_button_handlers = {}
|
||||
self.expanded_message_uuids = {}
|
||||
self.tool_message_positions = {}
|
||||
self.current_tool_use_extmark_id = nil
|
||||
|
||||
241
lua/avante/ui/button_group_line.lua
Normal file
241
lua/avante/ui/button_group_line.lua
Normal file
@@ -0,0 +1,241 @@
|
||||
local Highlights = require("avante.highlights")
|
||||
local Line = require("avante.ui.line")
|
||||
local Utils = require("avante.utils")
|
||||
|
||||
---@class avante.ui.ButtonGroupLine
|
||||
---@field _line avante.ui.Line
|
||||
---@field _button_options { id: string, icon?: string, name: string, hl?: string }[]
|
||||
---@field _focus_index integer
|
||||
---@field _group_label string|nil
|
||||
---@field _start_col integer
|
||||
---@field _button_pos integer[][]
|
||||
---@field _ns_id integer|nil
|
||||
---@field _bufnr integer|nil
|
||||
---@field _line_1b integer|nil
|
||||
---@field on_click? fun(id: string)
|
||||
local ButtonGroupLine = {}
|
||||
ButtonGroupLine.__index = ButtonGroupLine
|
||||
|
||||
-- per-buffer registry for dispatching shared keymaps/autocmds
|
||||
local registry ---@type table<integer, { lines: table<integer, avante.ui.ButtonGroupLine>, mapped: boolean, autocmd: integer|nil }>
|
||||
registry = {}
|
||||
|
||||
local function ensure_dispatch(bufnr)
|
||||
local entry = registry[bufnr]
|
||||
if not entry then
|
||||
entry = { lines = {}, mapped = false, autocmd = nil }
|
||||
registry[bufnr] = entry
|
||||
end
|
||||
if not entry.mapped then
|
||||
-- Tab: next button if on a group line; otherwise fall back to sidebar switch_windows
|
||||
vim.keymap.set("n", "<Tab>", function()
|
||||
local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
local group = entry.lines[row]
|
||||
if not group then
|
||||
local ok, sidebar = pcall(require, "avante")
|
||||
if ok and sidebar and sidebar.get then
|
||||
local sb = sidebar.get()
|
||||
if sb and sb.switch_window_focus then
|
||||
sb:switch_window_focus("next")
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Fallback to raw <Tab> if sidebar is unavailable
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Tab>", true, false, true), "n", true)
|
||||
return
|
||||
end
|
||||
group._focus_index = group._focus_index + 1
|
||||
if group._focus_index > #group._button_options then group._focus_index = 1 end
|
||||
group:_refresh_highlights()
|
||||
group:_move_cursor_to_focus()
|
||||
end, { buffer = bufnr, nowait = true })
|
||||
|
||||
vim.keymap.set("n", "<S-Tab>", function()
|
||||
local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
local group = entry.lines[row]
|
||||
if not group then
|
||||
local ok, sidebar = pcall(require, "avante")
|
||||
if ok and sidebar and sidebar.get then
|
||||
local sb = sidebar.get()
|
||||
if sb and sb.switch_window_focus then
|
||||
sb:switch_window_focus("previous")
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Fallback to raw <S-Tab> if sidebar is unavailable
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<S-Tab>", true, false, true), "n", true)
|
||||
return
|
||||
end
|
||||
group._focus_index = group._focus_index - 1
|
||||
if group._focus_index < 1 then group._focus_index = #group._button_options end
|
||||
group:_refresh_highlights()
|
||||
group:_move_cursor_to_focus()
|
||||
end, { buffer = bufnr, nowait = true })
|
||||
|
||||
vim.keymap.set("n", "<CR>", function()
|
||||
local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
local group = entry.lines[row]
|
||||
if not group then
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<CR>", true, false, true), "n", true)
|
||||
return
|
||||
end
|
||||
group:_click_focused()
|
||||
end, { buffer = bufnr, nowait = true })
|
||||
|
||||
-- Mouse click to activate
|
||||
vim.api.nvim_buf_set_keymap(bufnr, "n", "<LeftMouse>", "", {
|
||||
callback = function()
|
||||
local pos = vim.fn.getmousepos()
|
||||
local row, col = pos.winrow, pos.wincol
|
||||
local group = entry.lines[row]
|
||||
if not group then return end
|
||||
group:_update_focus_by_col(col)
|
||||
group:_click_focused()
|
||||
end,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
})
|
||||
|
||||
-- CursorMoved hover highlight
|
||||
entry.autocmd = vim.api.nvim_create_autocmd("CursorMoved", {
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
local group = entry.lines[row]
|
||||
if not group then return end
|
||||
group:_update_focus_by_col(col)
|
||||
end,
|
||||
})
|
||||
|
||||
entry.mapped = true
|
||||
end
|
||||
end
|
||||
|
||||
local function cleanup_dispatch_if_empty(bufnr)
|
||||
local entry = registry[bufnr]
|
||||
if not entry then return end
|
||||
-- Do not delete keymaps when no button lines remain.
|
||||
-- Deleting buffer-local mappings would not restore any previous mapping,
|
||||
-- which breaks the original Tab behavior in the sidebar.
|
||||
-- We intentionally keep the keymaps and autocmds; they safely no-op or
|
||||
-- fall back when not on a button group line.
|
||||
end
|
||||
|
||||
---@param button_options { id: string, icon: string|nil, name: string, hl?: string }[]
|
||||
---@param opts? { on_click: fun(id: string), start_col?: integer, group_label?: string }
|
||||
function ButtonGroupLine:new(button_options, opts)
|
||||
opts = opts or {}
|
||||
local o = setmetatable({}, ButtonGroupLine)
|
||||
o._button_options = vim.deepcopy(button_options)
|
||||
o._focus_index = 1
|
||||
o._start_col = opts.start_col or 0
|
||||
o._group_label = opts.group_label
|
||||
|
||||
local BUTTON_NORMAL = Highlights.BUTTON_DEFAULT
|
||||
local BUTTON_FOCUS = Highlights.BUTTON_DEFAULT_HOVER
|
||||
|
||||
local sections = {}
|
||||
if o._group_label and #o._group_label > 0 then table.insert(sections, { o._group_label .. " " }) end
|
||||
local btn_sep = " "
|
||||
for i, opt in ipairs(o._button_options) do
|
||||
local label
|
||||
if opt.icon and #opt.icon > 0 then
|
||||
label = string.format(" %s %s ", opt.icon, opt.name)
|
||||
else
|
||||
label = string.format(" %s ", opt.name)
|
||||
end
|
||||
local focus_hl = opt.hl or BUTTON_FOCUS
|
||||
table.insert(sections, { label, function() return (o._focus_index == i) and focus_hl or BUTTON_NORMAL end })
|
||||
if i < #o._button_options then table.insert(sections, { btn_sep }) end
|
||||
end
|
||||
o._line = Line:new(sections)
|
||||
|
||||
-- precalc positions for quick hover/click checks
|
||||
o._button_pos = {}
|
||||
local sec_idx = (o._group_label and #o._group_label > 0) and 2 or 1
|
||||
for i = 1, #o._button_options do
|
||||
local start_end = o._line:get_section_pos(sec_idx, o._start_col)
|
||||
o._button_pos[i] = { start_end[1], start_end[2] }
|
||||
if i < #o._button_options then
|
||||
sec_idx = sec_idx + 2
|
||||
else
|
||||
sec_idx = sec_idx + 1
|
||||
end
|
||||
end
|
||||
|
||||
if opts.on_click then o.on_click = opts.on_click end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
function ButtonGroupLine:__tostring() return string.rep(" ", self._start_col) .. tostring(self._line) end
|
||||
|
||||
---@param ns_id integer
|
||||
---@param bufnr integer
|
||||
---@param line_0b integer
|
||||
---@param _offset integer|nil -- ignored; offset handled in __tostring and pos precalc
|
||||
function ButtonGroupLine:set_highlights(ns_id, bufnr, line_0b, _offset)
|
||||
_offset = _offset or 0
|
||||
self._ns_id = ns_id
|
||||
self._bufnr = bufnr
|
||||
self._line_1b = line_0b + 1
|
||||
self._line:set_highlights(ns_id, bufnr, line_0b, self._start_col + _offset)
|
||||
end
|
||||
|
||||
-- called by utils.update_buffer_lines after content is written
|
||||
---@param _ns_id integer
|
||||
---@param bufnr integer
|
||||
---@param line_1b integer
|
||||
function ButtonGroupLine:bind_events(_ns_id, bufnr, line_1b)
|
||||
self._bufnr = bufnr
|
||||
self._line_1b = line_1b
|
||||
ensure_dispatch(bufnr)
|
||||
local entry = registry[bufnr]
|
||||
entry.lines[line_1b] = self
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@param line_1b integer
|
||||
function ButtonGroupLine:unbind_events(bufnr, line_1b)
|
||||
local entry = registry[bufnr]
|
||||
if not entry then return end
|
||||
entry.lines[line_1b] = nil
|
||||
cleanup_dispatch_if_empty(bufnr)
|
||||
end
|
||||
|
||||
function ButtonGroupLine:_refresh_highlights()
|
||||
if not (self._ns_id and self._bufnr and self._line_1b) then return end
|
||||
--- refresh content
|
||||
Utils.unlock_buf(self._bufnr)
|
||||
vim.api.nvim_buf_set_lines(self._bufnr, self._line_1b - 1, self._line_1b, false, { tostring(self) })
|
||||
Utils.lock_buf(self._bufnr)
|
||||
self._line:set_highlights(self._ns_id, self._bufnr, self._line_1b - 1, self._start_col)
|
||||
end
|
||||
|
||||
function ButtonGroupLine:_move_cursor_to_focus()
|
||||
local pos = self._button_pos[self._focus_index]
|
||||
if not pos then return end
|
||||
local winid = require("avante.utils").get_winid(self._bufnr)
|
||||
if winid and vim.api.nvim_win_is_valid(winid) then vim.api.nvim_win_set_cursor(winid, { self._line_1b, pos[1] }) end
|
||||
end
|
||||
|
||||
---@param col integer 0-based column
|
||||
function ButtonGroupLine:_update_focus_by_col(col)
|
||||
for i, rng in ipairs(self._button_pos) do
|
||||
if col >= rng[1] and col <= rng[2] then
|
||||
if self._focus_index ~= i then
|
||||
self._focus_index = i
|
||||
self:_refresh_highlights()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ButtonGroupLine:_click_focused()
|
||||
local opt = self._button_options[self._focus_index]
|
||||
if not opt then return end
|
||||
if self.on_click then pcall(self.on_click, opt.id) end
|
||||
end
|
||||
|
||||
return ButtonGroupLine
|
||||
@@ -39,10 +39,13 @@ function M:get_section_pos(section_index, offset)
|
||||
for i = 1, section_index - 1 do
|
||||
if i == section_index then break end
|
||||
local section = self.sections[i]
|
||||
col_start = col_start + #section
|
||||
local text = type(section) == "table" and section[1] or section
|
||||
col_start = col_start + #text
|
||||
end
|
||||
|
||||
return { offset + col_start, offset + col_start + #self.sections[section_index] }
|
||||
local current = self.sections[section_index]
|
||||
local text = type(current) == "table" and current[1] or current
|
||||
return { offset + col_start, offset + col_start + #text }
|
||||
end
|
||||
|
||||
function M:__tostring()
|
||||
@@ -59,4 +62,8 @@ function M:__eq(other)
|
||||
return vim.deep_equal(self.sections, other.sections)
|
||||
end
|
||||
|
||||
function M:bind_events(ns_id, bufnr, line) end
|
||||
|
||||
function M:unbind_events(bufnr, line) end
|
||||
|
||||
return M
|
||||
|
||||
@@ -397,7 +397,7 @@ function M.notify(msg, opts)
|
||||
local n = opts.once and vim.notify_once or vim.notify
|
||||
n(msg, opts.level or vim.log.levels.INFO, {
|
||||
on_open = function(win)
|
||||
local ok = pcall(function() vim.treesitter.language.add("markdown") end)
|
||||
pcall(function() vim.treesitter.language.add("markdown") end)
|
||||
vim.wo[win].conceallevel = 3
|
||||
vim.wo[win].concealcursor = ""
|
||||
vim.wo[win].spell = false
|
||||
@@ -1210,47 +1210,6 @@ function M.open_buffer(path, set_current_buf)
|
||||
return bufnr
|
||||
end
|
||||
|
||||
---@param old_lines avante.ui.Line[]
|
||||
---@param new_lines avante.ui.Line[]
|
||||
---@return { start_line: integer, end_line: integer, content: avante.ui.Line[] }[]
|
||||
local function get_lines_diff(old_lines, new_lines)
|
||||
local remaining_lines = 100
|
||||
local start_line = 0
|
||||
if #new_lines >= #old_lines then
|
||||
start_line = math.max(#old_lines - remaining_lines, 0)
|
||||
old_lines = vim.list_slice(old_lines, start_line + 1)
|
||||
new_lines = vim.list_slice(new_lines, start_line + 1)
|
||||
end
|
||||
local diffs = {}
|
||||
local prev_diff_idx = nil
|
||||
for i, line in ipairs(new_lines) do
|
||||
if line ~= old_lines[i] then
|
||||
if prev_diff_idx == nil then prev_diff_idx = i end
|
||||
else
|
||||
if prev_diff_idx ~= nil then
|
||||
local content = vim.list_slice(new_lines, prev_diff_idx, i - 1)
|
||||
table.insert(diffs, { start_line = start_line + prev_diff_idx, end_line = start_line + i, content = content })
|
||||
prev_diff_idx = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if prev_diff_idx ~= nil then
|
||||
table.insert(diffs, {
|
||||
start_line = start_line + prev_diff_idx,
|
||||
end_line = start_line + #new_lines + 1,
|
||||
content = vim.list_slice(new_lines, prev_diff_idx),
|
||||
})
|
||||
end
|
||||
if #new_lines < #old_lines then
|
||||
table.insert(
|
||||
diffs,
|
||||
{ start_line = start_line + #new_lines + 1, end_line = start_line + #old_lines + 1, content = {} }
|
||||
)
|
||||
end
|
||||
table.sort(diffs, function(a, b) return a.start_line > b.start_line end)
|
||||
return diffs
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@param new_lines string[]
|
||||
---@return { start_line: integer, end_line: integer, content: string[] }[]
|
||||
@@ -1309,6 +1268,15 @@ function M.update_buffer_lines(ns_id, bufnr, old_lines, new_lines, skip_line_cou
|
||||
end
|
||||
end
|
||||
if diff_start_idx > 0 then
|
||||
-- Unbind events on old lines that will be replaced/moved
|
||||
for i = diff_start_idx, #old_lines do
|
||||
local old_line = old_lines[i]
|
||||
if old_line and type(old_line.unbind_events) == "function" then
|
||||
local line_1b = skip_line_count + i
|
||||
pcall(old_line.unbind_events, old_line, bufnr, line_1b)
|
||||
end
|
||||
end
|
||||
|
||||
local changed_lines = vim.list_slice(new_lines, diff_start_idx)
|
||||
local text_lines = vim.tbl_map(function(line) return tostring(line) end, changed_lines)
|
||||
vim.api.nvim_buf_set_lines(
|
||||
@@ -1319,10 +1287,26 @@ function M.update_buffer_lines(ns_id, bufnr, old_lines, new_lines, skip_line_cou
|
||||
text_lines
|
||||
)
|
||||
for i, line in ipairs(changed_lines) do
|
||||
line:set_highlights(ns_id, bufnr, skip_line_count + diff_start_idx + i - 2)
|
||||
-- Apply highlights
|
||||
if type(line.set_highlights) == "function" then
|
||||
line:set_highlights(ns_id, bufnr, skip_line_count + diff_start_idx + i - 2)
|
||||
end
|
||||
-- Bind events if provided by the line
|
||||
if type(line.bind_events) == "function" then
|
||||
local line_1b = skip_line_count + diff_start_idx + i - 1
|
||||
pcall(line.bind_events, line, ns_id, bufnr, line_1b)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #old_lines > #new_lines then
|
||||
-- Unbind events on removed trailing lines
|
||||
for i = #new_lines + 1, #old_lines do
|
||||
local old_line = old_lines[i]
|
||||
if old_line and type(old_line.unbind_events) == "function" then
|
||||
local line_1b = skip_line_count + i
|
||||
pcall(old_line.unbind_events, old_line, bufnr, line_1b)
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(bufnr, skip_line_count + #new_lines, skip_line_count + #old_lines, false, {})
|
||||
end
|
||||
vim.cmd("redraw")
|
||||
|
||||
Reference in New Issue
Block a user