feat: claude text editor tool (#1631)
This commit is contained in:
17
README.md
17
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
|
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_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_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 = {
|
mappings = {
|
||||||
--- @class AvanteConflictMappings
|
--- @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)
|
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
|
## Custom prompts
|
||||||
|
|
||||||
By default, `avante.nvim` provides three different modes to interact with: `planning`, `editing`, and `suggesting`, followed with three different prompts per mode.
|
By default, `avante.nvim` provides three different modes to interact with: `planning`, `editing`, and `suggesting`, followed with three different prompts per mode.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function M.switch_file_selector_provider(target_provider)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param target ProviderName
|
---@param target avante.ProviderName
|
||||||
function M.switch_provider(target) require("avante.providers").refresh(target) end
|
function M.switch_provider(target) require("avante.providers").refresh(target) end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ local M = {}
|
|||||||
---@class avante.Config
|
---@class avante.Config
|
||||||
M._defaults = {
|
M._defaults = {
|
||||||
debug = false,
|
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",
|
provider = "claude",
|
||||||
-- WARNING: Since auto-suggestions are a high-frequency operation and therefore expensive,
|
-- 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
|
-- 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,
|
minimize_diff = true,
|
||||||
enable_token_counting = true,
|
enable_token_counting = true,
|
||||||
enable_cursor_planning_mode = false,
|
enable_cursor_planning_mode = false,
|
||||||
|
enable_claude_text_editor_tool_mode = false,
|
||||||
use_cwd_as_project_root = false,
|
use_cwd_as_project_root = false,
|
||||||
},
|
},
|
||||||
history = {
|
history = {
|
||||||
@@ -476,7 +477,7 @@ M._defaults = {
|
|||||||
---@diagnostic disable-next-line: missing-fields
|
---@diagnostic disable-next-line: missing-fields
|
||||||
M._options = {}
|
M._options = {}
|
||||||
|
|
||||||
---@type ProviderName[]
|
---@type avante.ProviderName[]
|
||||||
M.provider_names = {}
|
M.provider_names = {}
|
||||||
|
|
||||||
---@param opts? avante.Config
|
---@param opts? avante.Config
|
||||||
@@ -514,6 +515,14 @@ function M.setup(opts)
|
|||||||
vim.validate({ vendors = { M._options.vendors, "table", true } })
|
vim.validate({ vendors = { M._options.vendors, "table", true } })
|
||||||
M.provider_names = vim.list_extend(M.provider_names, vim.tbl_keys(M._options.vendors))
|
M.provider_names = vim.list_extend(M.provider_names, vim.tbl_keys(M._options.vendors))
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
---@param opts table<string, any>
|
---@param opts table<string, any>
|
||||||
@@ -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
|
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
|
---@return boolean
|
||||||
function M.has_provider(provider_name) return vim.list_contains(M.provider_names, provider_name) end
|
function M.has_provider(provider_name) return vim.list_contains(M.provider_names, provider_name) end
|
||||||
|
|
||||||
---get supported providers
|
---get supported providers
|
||||||
---@param provider_name ProviderName
|
---@param provider_name avante.ProviderName
|
||||||
function M.get_provider_config(provider_name)
|
function M.get_provider_config(provider_name)
|
||||||
if not M.has_provider(provider_name) then error("No provider found: " .. provider_name, 2) end
|
if not M.has_provider(provider_name) then error("No provider found: " .. provider_name, 2) end
|
||||||
if M._options[provider_name] ~= nil then
|
if M._options[provider_name] ~= nil then
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ local Utils = require("avante.utils")
|
|||||||
local Path = require("plenary.path")
|
local Path = require("plenary.path")
|
||||||
local Config = require("avante.config")
|
local Config = require("avante.config")
|
||||||
local RagService = require("avante.rag_service")
|
local RagService = require("avante.rag_service")
|
||||||
|
local Diff = require("avante.diff")
|
||||||
|
local Highlights = require("avante.highlights")
|
||||||
|
|
||||||
---@class AvanteRagService
|
---@class AvanteRagService
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -19,7 +21,13 @@ end
|
|||||||
|
|
||||||
function M.confirm(message, callback)
|
function M.confirm(message, callback)
|
||||||
local UI = require("avante.ui")
|
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
|
end
|
||||||
|
|
||||||
---@param abs_path string
|
---@param abs_path string
|
||||||
@@ -171,6 +179,170 @@ function M.read_file(opts, on_log)
|
|||||||
return content, nil
|
return content, nil
|
||||||
end
|
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("<Esc>", 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 }>
|
---@type AvanteLLMToolFunc<{ abs_path: string }>
|
||||||
function M.read_global_file(opts, on_log)
|
function M.read_global_file(opts, on_log)
|
||||||
local abs_path = get_abs_path(opts.abs_path)
|
local abs_path = get_abs_path(opts.abs_path)
|
||||||
@@ -1411,20 +1583,26 @@ M._tools = {
|
|||||||
---@return string | nil error
|
---@return string | nil error
|
||||||
function M.process_tool_use(tools, tool_use, on_log, on_complete)
|
function M.process_tool_use(tools, tool_use, on_log, on_complete)
|
||||||
Utils.debug("use tool", tool_use.name, tool_use.input_json)
|
Utils.debug("use tool", tool_use.name, tool_use.input_json)
|
||||||
|
local func
|
||||||
|
if tool_use.name == "str_replace_editor" then
|
||||||
|
func = M.str_replace_editor
|
||||||
|
else
|
||||||
---@type AvanteLLMTool?
|
---@type AvanteLLMTool?
|
||||||
local tool = vim.iter(tools):find(function(tool) return tool.name == tool_use.name end) ---@param tool 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
|
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 input_json = vim.json.decode(tool_use.input_json)
|
||||||
local func = tool.func or M[tool.name]
|
if not func then return nil, "Tool not found: " .. tool_use.name end
|
||||||
if on_log then on_log(tool.name, "running tool") end
|
if on_log then on_log(tool_use.name, "running tool") end
|
||||||
---@param result string | nil | boolean
|
---@param result string | nil | boolean
|
||||||
---@param err string | nil
|
---@param err string | nil
|
||||||
local function handle_result(result, err)
|
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("result", result)
|
||||||
-- Utils.debug("error", error)
|
-- Utils.debug("error", error)
|
||||||
if err ~= nil then
|
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
|
end
|
||||||
local result_str ---@type string?
|
local result_str ---@type string?
|
||||||
if type(result) == "string" then
|
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
|
return result_str, err
|
||||||
end
|
end
|
||||||
local result, err = func(input_json, function(log)
|
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)
|
end, function(result, err)
|
||||||
result, err = handle_result(result, err)
|
result, err = handle_result(result, err)
|
||||||
if on_complete == nil then
|
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
|
return
|
||||||
end
|
end
|
||||||
on_complete(result, err)
|
on_complete(result, err)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local Utils = require("avante.utils")
|
local Utils = require("avante.utils")
|
||||||
local Clipboard = require("avante.clipboard")
|
local Clipboard = require("avante.clipboard")
|
||||||
local P = require("avante.providers")
|
local P = require("avante.providers")
|
||||||
|
local Config = require("avante.config")
|
||||||
|
|
||||||
---@class AvanteProviderFunctor
|
---@class AvanteProviderFunctor
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -343,6 +344,20 @@ function M:parse_curl_args(prompt_opts)
|
|||||||
end
|
end
|
||||||
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
|
if self.support_prompt_caching and #tools > 0 then
|
||||||
local last_tool = vim.deepcopy(tools[#tools])
|
local last_tool = vim.deepcopy(tools[#tools])
|
||||||
last_tool.cache_control = { type = "ephemeral" }
|
last_tool.cache_control = { type = "ephemeral" }
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ M.env = E
|
|||||||
|
|
||||||
M = setmetatable(M, {
|
M = setmetatable(M, {
|
||||||
---@param t avante.Providers
|
---@param t avante.Providers
|
||||||
---@param k ProviderName
|
---@param k avante.ProviderName
|
||||||
__index = function(t, k)
|
__index = function(t, k)
|
||||||
local provider_config = M.get_config(k)
|
local provider_config = M.get_config(k)
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ function M.setup()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param provider_name ProviderName
|
---@param provider_name avante.ProviderName
|
||||||
function M.refresh(provider_name)
|
function M.refresh(provider_name)
|
||||||
require("avante.config").override({ provider = provider_name })
|
require("avante.config").override({ provider = provider_name })
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ function M.parse_config(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param provider_name ProviderName
|
---@param provider_name avante.ProviderName
|
||||||
function M.get_config(provider_name)
|
function M.get_config(provider_name)
|
||||||
provider_name = provider_name or Config.provider
|
provider_name = provider_name or Config.provider
|
||||||
local cur = Config.get_provider_config(provider_name)
|
local cur = Config.get_provider_config(provider_name)
|
||||||
|
|||||||
@@ -1327,6 +1327,7 @@ function Sidebar:apply(current_cursor)
|
|||||||
process(winid)
|
process(winid)
|
||||||
else
|
else
|
||||||
api.nvim_create_autocmd("BufWinEnter", {
|
api.nvim_create_autocmd("BufWinEnter", {
|
||||||
|
group = self.augroup,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
once = true,
|
once = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
@@ -1365,6 +1366,7 @@ function Sidebar:apply(current_cursor)
|
|||||||
process(winid)
|
process(winid)
|
||||||
else
|
else
|
||||||
api.nvim_create_autocmd("BufWinEnter", {
|
api.nvim_create_autocmd("BufWinEnter", {
|
||||||
|
group = self.augroup,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
once = true,
|
once = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
@@ -1718,6 +1720,7 @@ function Sidebar:on_mount(opts)
|
|||||||
local codeblocks = {}
|
local codeblocks = {}
|
||||||
|
|
||||||
api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
|
api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
|
||||||
|
group = self.augroup,
|
||||||
buffer = self.result_container.bufnr,
|
buffer = self.result_container.bufnr,
|
||||||
callback = function(ev)
|
callback = function(ev)
|
||||||
local in_codeblock = is_cursor_in_codeblock(codeblocks)
|
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)
|
local current_filetype = Utils.get_filetype(current_filepath)
|
||||||
|
|
||||||
api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, {
|
api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, {
|
||||||
|
group = self.augroup,
|
||||||
buffer = self.result_container.bufnr,
|
buffer = self.result_container.bufnr,
|
||||||
callback = function(ev)
|
callback = function(ev)
|
||||||
codeblocks = parse_codeblocks(ev.buf, current_filepath, current_filetype)
|
codeblocks = parse_codeblocks(ev.buf, current_filepath, current_filetype)
|
||||||
@@ -1756,6 +1760,7 @@ function Sidebar:on_mount(opts)
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.nvim_create_autocmd("User", {
|
api.nvim_create_autocmd("User", {
|
||||||
|
group = self.augroup,
|
||||||
pattern = VIEW_BUFFER_UPDATED_PATTERN,
|
pattern = VIEW_BUFFER_UPDATED_PATTERN,
|
||||||
callback = function()
|
callback = function()
|
||||||
if
|
if
|
||||||
@@ -1771,6 +1776,7 @@ function Sidebar:on_mount(opts)
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.nvim_create_autocmd("BufLeave", {
|
api.nvim_create_autocmd("BufLeave", {
|
||||||
|
group = self.augroup,
|
||||||
buffer = self.result_container.bufnr,
|
buffer = self.result_container.bufnr,
|
||||||
callback = function() self:unbind_sidebar_keys() end,
|
callback = function() self:unbind_sidebar_keys() end,
|
||||||
})
|
})
|
||||||
@@ -2450,6 +2456,10 @@ function Sidebar:create_input_container(opts)
|
|||||||
returns = {},
|
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
|
---@type AvanteGeneratePromptsOptions
|
||||||
local prompts_opts = {
|
local prompts_opts = {
|
||||||
ask = opts.ask or true,
|
ask = opts.ask or true,
|
||||||
@@ -2461,7 +2471,7 @@ function Sidebar:create_input_container(opts)
|
|||||||
code_lang = filetype,
|
code_lang = filetype,
|
||||||
selected_code = selected_code,
|
selected_code = selected_code,
|
||||||
instructions = request,
|
instructions = request,
|
||||||
mode = Config.behaviour.enable_cursor_planning_mode and "cursor-planning" or "planning",
|
mode = mode,
|
||||||
tools = tools,
|
tools = tools,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2950,6 +2960,7 @@ function Sidebar:create_input_container(opts)
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.nvim_create_autocmd("WinEnter", {
|
api.nvim_create_autocmd("WinEnter", {
|
||||||
|
group = self.augroup,
|
||||||
callback = function()
|
callback = function()
|
||||||
local cur_win = api.nvim_get_current_win()
|
local cur_win = api.nvim_get_current_win()
|
||||||
if self.input_container and cur_win == self.input_container.winid then
|
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
|
-- reset states when buffer is closed
|
||||||
api.nvim_buf_attach(self.code.bufnr, false, {
|
api.nvim_buf_attach(self.code.bufnr, false, {
|
||||||
on_detach = function(_, _)
|
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,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
1
lua/avante/templates/claude-text-editor-tool.avanterules
Normal file
1
lua/avante/templates/claude-text-editor-tool.avanterules
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{% extends "base.avanterules" %}
|
||||||
@@ -294,7 +294,7 @@ vim.g.avante_login = vim.g.avante_login
|
|||||||
---@field parse_response AvanteResponseParser
|
---@field parse_response AvanteResponseParser
|
||||||
---@field build_bedrock_payload AvanteBedrockPayloadBuilder
|
---@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
|
---@class AvanteSelectedCode
|
||||||
---@field path string
|
---@field path string
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ local Popup = require("nui.popup")
|
|||||||
local NuiText = require("nui.text")
|
local NuiText = require("nui.text")
|
||||||
local event = require("nui.utils.autocmd").event
|
local event = require("nui.utils.autocmd").event
|
||||||
local Highlights = require("avante.highlights")
|
local Highlights = require("avante.highlights")
|
||||||
|
local Utils = require("avante.utils")
|
||||||
|
|
||||||
local M = {}
|
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 focus_index = 2 -- 1 = Yes, 2 = No
|
||||||
local yes_button_pos = { 23, 28 }
|
local yes_button_pos = { 23, 28 }
|
||||||
local no_button_pos = { 33, 37 }
|
local no_button_pos = { 33, 37 }
|
||||||
@@ -13,25 +18,51 @@ function M.confirm(message, callback)
|
|||||||
local BUTTON_NORMAL = Highlights.BUTTON_DEFAULT
|
local BUTTON_NORMAL = Highlights.BUTTON_DEFAULT
|
||||||
local BUTTON_FOCUS = Highlights.BUTTON_DEFAULT_HOVER
|
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({
|
local popup = Popup({
|
||||||
position = {
|
relative = {
|
||||||
row = vim.o.lines - 9,
|
type = "win",
|
||||||
col = "50%",
|
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,
|
enter = true,
|
||||||
focusable = true,
|
focusable = true,
|
||||||
border = {
|
border = {
|
||||||
style = "rounded",
|
style = "rounded",
|
||||||
text = { top = NuiText(" Confirmation ", Highlights.CONFIRM_TITLE) },
|
text = { top = NuiText(" Confirmation ", Highlights.CONFIRM_TITLE) },
|
||||||
},
|
},
|
||||||
|
buf_options = {
|
||||||
|
filetype = "avante-confirm",
|
||||||
|
modifiable = false,
|
||||||
|
readonly = true,
|
||||||
|
},
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 10,
|
winblend = 10,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
local function focus_button(row)
|
local function focus_button(row)
|
||||||
row = row or 4
|
row = row or button_row
|
||||||
if focus_index == 1 then
|
if focus_index == 1 then
|
||||||
vim.api.nvim_win_set_cursor(popup.winid, { row, yes_button_pos[1] })
|
vim.api.nvim_win_set_cursor(popup.winid, { row, yes_button_pos[1] })
|
||||||
else
|
else
|
||||||
@@ -43,20 +74,9 @@ function M.confirm(message, callback)
|
|||||||
local yes_style = (focus_index == 1) and BUTTON_FOCUS or BUTTON_NORMAL
|
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 no_style = (focus_index == 2) and BUTTON_FOCUS or BUTTON_NORMAL
|
||||||
|
|
||||||
local button_line = string.rep(" ", 23) .. " Yes No "
|
Utils.unlock_buf(popup.bufnr)
|
||||||
local button_line_num = 2 + #vim.split(message, "\n")
|
vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, content)
|
||||||
local replacement = vim
|
Utils.lock_buf(popup.bufnr)
|
||||||
.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)
|
|
||||||
|
|
||||||
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, 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])
|
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()
|
callback = function()
|
||||||
local pos = vim.fn.getmousepos()
|
local pos = vim.fn.getmousepos()
|
||||||
local row, col = pos["winrow"], pos["wincol"]
|
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
|
if col >= yes_button_pos[1] and col <= yes_button_pos[2] then
|
||||||
focus_index = 1
|
focus_index = 1
|
||||||
render_buttons()
|
render_buttons()
|
||||||
@@ -126,7 +146,7 @@ function M.confirm(message, callback)
|
|||||||
buffer = popup.bufnr,
|
buffer = popup.bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
|
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
|
if col >= yes_button_pos[1] and col <= yes_button_pos[2] then
|
||||||
focus_index = 1
|
focus_index = 1
|
||||||
render_buttons()
|
render_buttons()
|
||||||
@@ -138,7 +158,7 @@ function M.confirm(message, callback)
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
popup:on(event.BufLeave, function() popup:unmount() end)
|
-- popup:on(event.BufLeave, function() popup:unmount() end)
|
||||||
|
|
||||||
popup:mount()
|
popup:mount()
|
||||||
render_buttons()
|
render_buttons()
|
||||||
|
|||||||
@@ -916,6 +916,8 @@ function M.get_mentions()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param filepath string
|
||||||
|
---@return integer|nil bufnr
|
||||||
local function get_opened_buffer_by_filepath(filepath)
|
local function get_opened_buffer_by_filepath(filepath)
|
||||||
local project_root = M.get_project_root()
|
local project_root = M.get_project_root()
|
||||||
local absolute_path = M.join_paths(project_root, filepath)
|
local absolute_path = M.join_paths(project_root, filepath)
|
||||||
@@ -925,10 +927,16 @@ local function get_opened_buffer_by_filepath(filepath)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param filepath string
|
||||||
|
---@return integer bufnr
|
||||||
function M.get_or_create_buffer_with_filepath(filepath)
|
function M.get_or_create_buffer_with_filepath(filepath)
|
||||||
-- Check if a buffer with this filepath already exists
|
-- Check if a buffer with this filepath already exists
|
||||||
local existing_buf = get_opened_buffer_by_filepath(filepath)
|
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
|
-- Create a new buffer without setting its name
|
||||||
local buf = api.nvim_create_buf(true, false)
|
local buf = api.nvim_create_buf(true, false)
|
||||||
|
|||||||
Reference in New Issue
Block a user