From 191d7b8783e5454d2269d95e9658e0292119362c Mon Sep 17 00:00:00 2001 From: yetone Date: Wed, 19 Mar 2025 00:09:49 +0800 Subject: [PATCH] feat: claude text editor tool (#1631) --- README.md | 17 ++ lua/avante/api.lua | 2 +- lua/avante/config.lua | 17 +- lua/avante/llm_tools.lua | 198 +++++++++++++++++- lua/avante/providers/claude.lua | 15 ++ lua/avante/providers/init.lua | 6 +- lua/avante/sidebar.lua | 19 +- .../claude-text-editor-tool.avanterules | 1 + lua/avante/types.lua | 2 +- lua/avante/ui.lua | 66 ++++-- lua/avante/utils/init.lua | 10 +- 11 files changed, 308 insertions(+), 45 deletions(-) create mode 100644 lua/avante/templates/claude-text-editor-tool.avanterules diff --git a/README.md b/README.md index ab0426b..5a966f0 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ _See [config.lua#L9](./lua/avante/config.lua) for the full config_ minimize_diff = true, -- Whether to remove unchanged lines when applying a code block enable_token_counting = true, -- Whether to enable token counting. Default to true. enable_cursor_planning_mode = false, -- Whether to enable Cursor Planning Mode. Default to false. + enable_claude_text_editor_tool_mode = false, -- Whether to enable Claude Text Editor Tool Mode. }, mappings = { --- @class AvanteConflictMappings @@ -882,6 +883,22 @@ Avante allows you to define custom tools that can be used by the AI during code Now you can integrate MCP functionality for Avante through `mcphub.nvim`. For detailed documentation, please refer to [mcphub.nvim](https://github.com/ravitemer/mcphub.nvim#avante-integration) +## Claude Text Editor Tool Mode + +Avante leverages [Claude Text Editor Tool](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool) to provide a more elegant code editing experience. You can now enable this feature by setting `enable_claude_text_editor_tool_mode` to `true` in the `behaviour` configuration: + +```lua +{ + behaviour = { + enable_claude_text_editor_tool_mode = true, + }, +} +``` + +> [!NOTE] +> To enable **Claude Text Editor Tool Mode**, you must use the `claude-3-5-sonnet-*` or `claude-3-7-sonnet-*` model with the `claude` provider! This feature is not supported by any other models! + + ## Custom prompts By default, `avante.nvim` provides three different modes to interact with: `planning`, `editing`, and `suggesting`, followed with three different prompts per mode. diff --git a/lua/avante/api.lua b/lua/avante/api.lua index e7abbe9..03bc90c 100644 --- a/lua/avante/api.lua +++ b/lua/avante/api.lua @@ -20,7 +20,7 @@ function M.switch_file_selector_provider(target_provider) }) end ----@param target ProviderName +---@param target avante.ProviderName function M.switch_provider(target) require("avante.providers").refresh(target) end ---@param path string diff --git a/lua/avante/config.lua b/lua/avante/config.lua index aa43db4..2c3f98d 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -19,7 +19,7 @@ local M = {} ---@class avante.Config M._defaults = { debug = false, - ---@alias ProviderName "claude" | "openai" | "azure" | "gemini" | "vertex" | "cohere" | "copilot" | "bedrock" | "ollama" | string + ---@alias avante.ProviderName "claude" | "openai" | "azure" | "gemini" | "vertex" | "cohere" | "copilot" | "bedrock" | "ollama" | string provider = "claude", -- WARNING: Since auto-suggestions are a high-frequency operation and therefore expensive, -- currently designating it as `copilot` provider is dangerous because: https://github.com/yetone/avante.nvim/issues/1048 @@ -340,6 +340,7 @@ M._defaults = { minimize_diff = true, enable_token_counting = true, enable_cursor_planning_mode = false, + enable_claude_text_editor_tool_mode = false, use_cwd_as_project_root = false, }, history = { @@ -476,7 +477,7 @@ M._defaults = { ---@diagnostic disable-next-line: missing-fields M._options = {} ----@type ProviderName[] +---@type avante.ProviderName[] M.provider_names = {} ---@param opts? avante.Config @@ -514,6 +515,14 @@ function M.setup(opts) vim.validate({ vendors = { M._options.vendors, "table", true } }) M.provider_names = vim.list_extend(M.provider_names, vim.tbl_keys(M._options.vendors)) end + + if M._options.behaviour.enable_claude_text_editor_tool_mode and M._options.provider ~= "claude" then + Utils.warn( + "Claude text editor tool mode is only supported for claude provider! So it will be disabled!", + { title = "Avante" } + ) + M._options.behaviour.enable_claude_text_editor_tool_mode = false + end end ---@param opts table @@ -541,12 +550,12 @@ function M.support_paste_image() return Utils.has("img-clip.nvim") or Utils.has( function M.get_window_width() return math.ceil(vim.o.columns * (M.windows.width / 100)) end ----@param provider_name ProviderName +---@param provider_name avante.ProviderName ---@return boolean function M.has_provider(provider_name) return vim.list_contains(M.provider_names, provider_name) end ---get supported providers ----@param provider_name ProviderName +---@param provider_name avante.ProviderName function M.get_provider_config(provider_name) if not M.has_provider(provider_name) then error("No provider found: " .. provider_name, 2) end if M._options[provider_name] ~= nil then diff --git a/lua/avante/llm_tools.lua b/lua/avante/llm_tools.lua index 10a56f0..4b1aa6d 100644 --- a/lua/avante/llm_tools.lua +++ b/lua/avante/llm_tools.lua @@ -3,6 +3,8 @@ local Utils = require("avante.utils") local Path = require("plenary.path") local Config = require("avante.config") local RagService = require("avante.rag_service") +local Diff = require("avante.diff") +local Highlights = require("avante.highlights") ---@class AvanteRagService local M = {} @@ -19,7 +21,13 @@ end function M.confirm(message, callback) local UI = require("avante.ui") - UI.confirm(message, callback) + local sidebar = require("avante").get() + if not sidebar or not sidebar.input_container or not sidebar.input_container.winid then + Utils.error("Avante sidebar not found", { title = "Avante" }) + callback(false) + return + end + UI.confirm(message, callback, { container_winid = sidebar.input_container.winid }) end ---@param abs_path string @@ -171,6 +179,170 @@ function M.read_file(opts, on_log) return content, nil end +---@type AvanteLLMToolFunc<{ command: "view" | "str_replace" | "create" | "insert" | "undo_edit", path: string, old_str?: string, new_str?: string, file_text?: string, insert_line?: integer, new_str?: string }> +function M.str_replace_editor(opts, on_log, on_complete) + if on_log then on_log("command: " .. opts.command) end + if on_log then on_log("path: " .. opts.path) end + if not on_complete then return false, "on_complete not provided" end + local abs_path = get_abs_path(opts.path) + if not has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end + local sidebar = require("avante").get() + if not sidebar then return false, "Avante sidebar not found" end + local get_bufnr = function() + local current_winid = vim.api.nvim_get_current_win() + vim.api.nvim_set_current_win(sidebar.code.winid) + local bufnr = Utils.get_or_create_buffer_with_filepath(abs_path) + vim.api.nvim_set_current_win(current_winid) + return bufnr + end + if opts.command == "view" then + if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end + if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end + local file = io.open(abs_path, "r") + if not file then return false, "file not found: " .. abs_path end + local content = file:read("*a") + file:close() + on_complete(content, nil) + return + end + if opts.command == "str_replace" then + if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end + if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end + local file = io.open(abs_path, "r") + if not file then return false, "file not found: " .. abs_path end + if opts.old_str == nil then return false, "old_str not provided" end + if opts.new_str == nil then return false, "new_str not provided" end + local bufnr = get_bufnr() + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local old_lines = vim.split(opts.old_str, "\n") + local new_lines = vim.split(opts.new_str, "\n") + local start_line, end_line + for i = 1, #lines - #old_lines + 1 do + local match = true + for j = 1, #old_lines do + if lines[i + j - 1] ~= old_lines[j] then + match = false + break + end + end + if match then + start_line = i + end_line = i + #old_lines - 1 + break + end + end + if start_line == nil or end_line == nil then + on_complete(false, "Failed to find the old string: " .. opts.old_str) + return + end + local patched_new_lines = { "<<<<<<< HEAD" } + vim.list_extend(patched_new_lines, old_lines) + table.insert(patched_new_lines, "=======") + vim.list_extend(patched_new_lines, new_lines) + table.insert(patched_new_lines, ">>>>>>> new") + vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, patched_new_lines) + local current_winid = vim.api.nvim_get_current_win() + vim.api.nvim_set_current_win(sidebar.code.winid) + Diff.add_visited_buffer(bufnr) + Diff.process(bufnr) + vim.api.nvim_win_set_cursor(sidebar.code.winid, { math.max(start_line - 1, 1), 0 }) + vim.cmd("normal! zz") + vim.api.nvim_set_current_win(current_winid) + M.confirm("Are you sure you want to apply this modification?", function(ok) + vim.api.nvim_set_current_win(sidebar.code.winid) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", true) + if not ok then + vim.cmd("undo") + vim.api.nvim_set_current_win(current_winid) + on_complete(false, "User canceled") + return + end + vim.cmd("undo") + vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, new_lines) + vim.api.nvim_set_current_win(current_winid) + on_complete(true, nil) + end) + return + end + if opts.command == "create" then + if opts.file_text == nil then return false, "file_text not provided" end + if Path:new(abs_path):exists() then return false, "File already exists: " .. abs_path end + local lines = vim.split(opts.file_text, "\n") + local bufnr = get_bufnr() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + M.confirm("Are you sure you want to create this file?", function(ok) + if not ok then + -- close the buffer + vim.api.nvim_buf_delete(bufnr, { force = true }) + on_complete(false, "User canceled") + return + end + -- save the file + local current_winid = vim.api.nvim_get_current_win() + local winid = Utils.get_winid(bufnr) + vim.api.nvim_set_current_win(winid) + vim.cmd("write") + vim.api.nvim_set_current_win(current_winid) + on_complete(true, nil) + end) + return + end + if opts.command == "insert" then + if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end + if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end + if opts.insert_line == nil then return false, "insert_line not provided" end + if opts.new_str == nil then return false, "new_str not provided" end + local ns_id = vim.api.nvim_create_namespace("avante_insert_diff") + local bufnr = get_bufnr() + local function clear_highlights() vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) end + local new_lines = vim.split(opts.new_str, "\n") + local max_col = vim.o.columns + local virt_lines = vim + .iter(new_lines) + :map(function(line) + --- append spaces to the end of the line + local line_ = line .. string.rep(" ", max_col - #line) + return { { line_, Highlights.INCOMING } } + end) + :totable() + vim.api.nvim_buf_set_extmark(bufnr, ns_id, opts.insert_line - 1, 0, { + virt_lines = virt_lines, + hl_eol = true, + hl_mode = "combine", + }) + M.confirm("Are you sure you want to insert these lines?", function(ok) + clear_highlights() + if not ok then + on_complete(false, "User canceled") + return + end + vim.api.nvim_buf_set_lines(bufnr, opts.insert_line - 1, opts.insert_line - 1, false, new_lines) + on_complete(true, nil) + end) + return + end + if opts.command == "undo_edit" then + if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end + if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end + local bufnr = get_bufnr() + M.confirm("Are you sure you want to undo edit this file?", function(ok) + if not ok then + on_complete(false, "User canceled") + return + end + local current_winid = vim.api.nvim_get_current_win() + local winid = Utils.get_winid(bufnr) + vim.api.nvim_set_current_win(winid) + -- run undo + vim.cmd("undo") + vim.api.nvim_set_current_win(current_winid) + on_complete(true, nil) + end) + return + end + return false, "Unknown command: " .. opts.command +end + ---@type AvanteLLMToolFunc<{ abs_path: string }> function M.read_global_file(opts, on_log) local abs_path = get_abs_path(opts.abs_path) @@ -1411,20 +1583,26 @@ M._tools = { ---@return string | nil error function M.process_tool_use(tools, tool_use, on_log, on_complete) Utils.debug("use tool", tool_use.name, tool_use.input_json) - ---@type AvanteLLMTool? - local tool = vim.iter(tools):find(function(tool) return tool.name == tool_use.name end) ---@param tool AvanteLLMTool - if tool == nil then return end + local func + if tool_use.name == "str_replace_editor" then + func = M.str_replace_editor + else + ---@type AvanteLLMTool? + local tool = vim.iter(tools):find(function(tool) return tool.name == tool_use.name end) ---@param tool AvanteLLMTool + if tool == nil then return nil, "This tool is not provided: " .. tool_use.name end + func = tool.func or M[tool.name] + end local input_json = vim.json.decode(tool_use.input_json) - local func = tool.func or M[tool.name] - if on_log then on_log(tool.name, "running tool") end + if not func then return nil, "Tool not found: " .. tool_use.name end + if on_log then on_log(tool_use.name, "running tool") end ---@param result string | nil | boolean ---@param err string | nil local function handle_result(result, err) - if on_log then on_log(tool.name, "tool finished") end + if on_log then on_log(tool_use.name, "tool finished") end -- Utils.debug("result", result) -- Utils.debug("error", error) if err ~= nil then - if on_log then on_log(tool.name, "Error: " .. err) end + if on_log then on_log(tool_use.name, "Error: " .. err) end end local result_str ---@type string? if type(result) == "string" then @@ -1435,11 +1613,11 @@ function M.process_tool_use(tools, tool_use, on_log, on_complete) return result_str, err end local result, err = func(input_json, function(log) - if on_log then on_log(tool.name, log) end + if on_log then on_log(tool_use.name, log) end end, function(result, err) result, err = handle_result(result, err) if on_complete == nil then - Utils.error("asynchronous tool " .. tool.name .. " result not handled") + Utils.error("asynchronous tool " .. tool_use.name .. " result not handled") return end on_complete(result, err) diff --git a/lua/avante/providers/claude.lua b/lua/avante/providers/claude.lua index f760943..26b88d8 100644 --- a/lua/avante/providers/claude.lua +++ b/lua/avante/providers/claude.lua @@ -1,6 +1,7 @@ local Utils = require("avante.utils") local Clipboard = require("avante.clipboard") local P = require("avante.providers") +local Config = require("avante.config") ---@class AvanteProviderFunctor local M = {} @@ -343,6 +344,20 @@ function M:parse_curl_args(prompt_opts) end end + if prompt_opts.tools and Config.behaviour.enable_claude_text_editor_tool_mode then + if provider_conf.model:match("claude%-3%-7%-sonnet") then + table.insert(tools, { + type = "text_editor_20250124", + name = "str_replace_editor", + }) + elseif provider_conf.model:match("claude%-3%-5%-instruct") then + table.insert(tools, { + type = "text_editor_20241022", + name = "str_replace_editor", + }) + end + end + if self.support_prompt_caching and #tools > 0 then local last_tool = vim.deepcopy(tools[#tools]) last_tool.cache_control = { type = "ephemeral" } diff --git a/lua/avante/providers/init.lua b/lua/avante/providers/init.lua index 2288fc8..de1b4f6 100644 --- a/lua/avante/providers/init.lua +++ b/lua/avante/providers/init.lua @@ -150,7 +150,7 @@ M.env = E M = setmetatable(M, { ---@param t avante.Providers - ---@param k ProviderName + ---@param k avante.ProviderName __index = function(t, k) local provider_config = M.get_config(k) @@ -226,7 +226,7 @@ function M.setup() end end ----@param provider_name ProviderName +---@param provider_name avante.ProviderName function M.refresh(provider_name) require("avante.config").override({ provider = provider_name }) @@ -265,7 +265,7 @@ function M.parse_config(opts) end ---@private ----@param provider_name ProviderName +---@param provider_name avante.ProviderName function M.get_config(provider_name) provider_name = provider_name or Config.provider local cur = Config.get_provider_config(provider_name) diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 6845a61..a30eda4 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -1327,6 +1327,7 @@ function Sidebar:apply(current_cursor) process(winid) else api.nvim_create_autocmd("BufWinEnter", { + group = self.augroup, buffer = bufnr, once = true, callback = function() @@ -1365,6 +1366,7 @@ function Sidebar:apply(current_cursor) process(winid) else api.nvim_create_autocmd("BufWinEnter", { + group = self.augroup, buffer = bufnr, once = true, callback = function() @@ -1718,6 +1720,7 @@ function Sidebar:on_mount(opts) local codeblocks = {} api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { + group = self.augroup, buffer = self.result_container.bufnr, callback = function(ev) local in_codeblock = is_cursor_in_codeblock(codeblocks) @@ -1748,6 +1751,7 @@ function Sidebar:on_mount(opts) local current_filetype = Utils.get_filetype(current_filepath) api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, { + group = self.augroup, buffer = self.result_container.bufnr, callback = function(ev) codeblocks = parse_codeblocks(ev.buf, current_filepath, current_filetype) @@ -1756,6 +1760,7 @@ function Sidebar:on_mount(opts) }) api.nvim_create_autocmd("User", { + group = self.augroup, pattern = VIEW_BUFFER_UPDATED_PATTERN, callback = function() if @@ -1771,6 +1776,7 @@ function Sidebar:on_mount(opts) }) api.nvim_create_autocmd("BufLeave", { + group = self.augroup, buffer = self.result_container.bufnr, callback = function() self:unbind_sidebar_keys() end, }) @@ -2450,6 +2456,10 @@ function Sidebar:create_input_container(opts) returns = {}, }) + local mode = "planning" + if Config.behaviour.enable_cursor_planning_mode then mode = "cursor-planning" end + if Config.behaviour.enable_claude_text_editor_tool_mode then mode = "claude-text-editor-tool" end + ---@type AvanteGeneratePromptsOptions local prompts_opts = { ask = opts.ask or true, @@ -2461,7 +2471,7 @@ function Sidebar:create_input_container(opts) code_lang = filetype, selected_code = selected_code, instructions = request, - mode = Config.behaviour.enable_cursor_planning_mode and "cursor-planning" or "planning", + mode = mode, tools = tools, } @@ -2950,6 +2960,7 @@ function Sidebar:create_input_container(opts) }) api.nvim_create_autocmd("WinEnter", { + group = self.augroup, callback = function() local cur_win = api.nvim_get_current_win() if self.input_container and cur_win == self.input_container.winid then @@ -3067,7 +3078,11 @@ function Sidebar:render(opts) -- reset states when buffer is closed api.nvim_buf_attach(self.code.bufnr, false, { on_detach = function(_, _) - if self and self.reset then vim.schedule(function() self:reset() end) end + vim.schedule(function() + local bufnr = api.nvim_win_get_buf(self.code.winid) + self.code.bufnr = bufnr + self:reload_chat_history() + end) end, }) diff --git a/lua/avante/templates/claude-text-editor-tool.avanterules b/lua/avante/templates/claude-text-editor-tool.avanterules new file mode 100644 index 0000000..b584ef7 --- /dev/null +++ b/lua/avante/templates/claude-text-editor-tool.avanterules @@ -0,0 +1 @@ +{% extends "base.avanterules" %} diff --git a/lua/avante/types.lua b/lua/avante/types.lua index 6d2ddd9..795118f 100644 --- a/lua/avante/types.lua +++ b/lua/avante/types.lua @@ -294,7 +294,7 @@ vim.g.avante_login = vim.g.avante_login ---@field parse_response AvanteResponseParser ---@field build_bedrock_payload AvanteBedrockPayloadBuilder --- ----@alias AvanteLlmMode "planning" | "editing" | "suggesting" | "cursor-planning" | "cursor-applying" +---@alias AvanteLlmMode "planning" | "editing" | "suggesting" | "cursor-planning" | "cursor-applying" | "claude-text-editor-tool" --- ---@class AvanteSelectedCode ---@field path string diff --git a/lua/avante/ui.lua b/lua/avante/ui.lua index e4e0b1b..b2b6455 100644 --- a/lua/avante/ui.lua +++ b/lua/avante/ui.lua @@ -2,10 +2,15 @@ local Popup = require("nui.popup") local NuiText = require("nui.text") local event = require("nui.utils.autocmd").event local Highlights = require("avante.highlights") +local Utils = require("avante.utils") local M = {} -function M.confirm(message, callback) +---@param message string +---@param callback fun(yes: boolean) +---@param opts { container_winid: number } +---@return nil +function M.confirm(message, callback, opts) local focus_index = 2 -- 1 = Yes, 2 = No local yes_button_pos = { 23, 28 } local no_button_pos = { 33, 37 } @@ -13,25 +18,51 @@ function M.confirm(message, callback) local BUTTON_NORMAL = Highlights.BUTTON_DEFAULT local BUTTON_FOCUS = Highlights.BUTTON_DEFAULT_HOVER + local button_line = string.rep(" ", 23) .. " Yes No " + local button_line_num = 2 + #vim.split(message, "\n") + local content = vim + .iter({ + "", + vim.tbl_map(function(line) return " " .. line end, vim.split(message, "\n")), + "", + button_line, + "", + }) + :flatten() + :totable() + local button_row = #content - 1 + + local container_winid = opts.container_winid or vim.api.nvim_get_current_win() + local container_width = vim.api.nvim_win_get_width(container_winid) + local popup = Popup({ - position = { - row = vim.o.lines - 9, - col = "50%", + relative = { + type = "win", + winid = container_winid, }, - size = { width = 60, height = 9 }, + position = { + row = vim.o.lines - #content - 3, + col = (container_width - 60) / 2, + }, + size = { width = 60, height = #content + 3 }, enter = true, focusable = true, border = { style = "rounded", text = { top = NuiText(" Confirmation ", Highlights.CONFIRM_TITLE) }, }, + buf_options = { + filetype = "avante-confirm", + modifiable = false, + readonly = true, + }, win_options = { winblend = 10, }, }) local function focus_button(row) - row = row or 4 + row = row or button_row if focus_index == 1 then vim.api.nvim_win_set_cursor(popup.winid, { row, yes_button_pos[1] }) else @@ -43,20 +74,9 @@ function M.confirm(message, callback) local yes_style = (focus_index == 1) and BUTTON_FOCUS or BUTTON_NORMAL local no_style = (focus_index == 2) and BUTTON_FOCUS or BUTTON_NORMAL - local button_line = string.rep(" ", 23) .. " Yes No " - local button_line_num = 2 + #vim.split(message, "\n") - local replacement = vim - .iter({ - "", - vim.tbl_map(function(line) return " " .. line end, vim.split(message, "\n")), - "", - button_line, - "", - }) - :flatten() - :totable() - - vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, replacement) + Utils.unlock_buf(popup.bufnr) + vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, content) + Utils.lock_buf(popup.bufnr) vim.api.nvim_buf_add_highlight(popup.bufnr, 0, yes_style, button_line_num, yes_button_pos[1], yes_button_pos[2]) vim.api.nvim_buf_add_highlight(popup.bufnr, 0, no_style, button_line_num, no_button_pos[1], no_button_pos[2]) @@ -106,7 +126,7 @@ function M.confirm(message, callback) callback = function() local pos = vim.fn.getmousepos() local row, col = pos["winrow"], pos["wincol"] - if row == 4 then + if row == button_row then if col >= yes_button_pos[1] and col <= yes_button_pos[2] then focus_index = 1 render_buttons() @@ -126,7 +146,7 @@ function M.confirm(message, callback) buffer = popup.bufnr, callback = function() local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - if row == 4 then + if row == button_row then if col >= yes_button_pos[1] and col <= yes_button_pos[2] then focus_index = 1 render_buttons() @@ -138,7 +158,7 @@ function M.confirm(message, callback) end, }) - popup:on(event.BufLeave, function() popup:unmount() end) + -- popup:on(event.BufLeave, function() popup:unmount() end) popup:mount() render_buttons() diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index 9afd57a..b334d2c 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -916,6 +916,8 @@ function M.get_mentions() } end +---@param filepath string +---@return integer|nil bufnr local function get_opened_buffer_by_filepath(filepath) local project_root = M.get_project_root() local absolute_path = M.join_paths(project_root, filepath) @@ -925,10 +927,16 @@ local function get_opened_buffer_by_filepath(filepath) return nil end +---@param filepath string +---@return integer bufnr function M.get_or_create_buffer_with_filepath(filepath) -- Check if a buffer with this filepath already exists local existing_buf = get_opened_buffer_by_filepath(filepath) - if existing_buf then return existing_buf end + if existing_buf then + -- goto this buffer + api.nvim_set_current_buf(existing_buf) + return existing_buf + end -- Create a new buffer without setting its name local buf = api.nvim_create_buf(true, false)