feat: request permission button group (#2685)

This commit is contained in:
yetone
2025-09-04 15:35:40 +08:00
committed by GitHub
parent 4ac4c8ed3f
commit 10e0312ec4
6 changed files with 394 additions and 125 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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")