fix: AvanteEdit command (#1799)

This commit is contained in:
yetone
2025-04-02 16:27:10 +08:00
committed by GitHub
parent 06757e8bf5
commit 0828d32346
6 changed files with 172 additions and 125 deletions

View File

@@ -156,13 +156,15 @@ function M.ask(opts)
return ask()
end
---@param question? string
function M.edit(question)
---@param request? string
---@param line1? integer
---@param line2? integer
function M.edit(request, line1, line2)
local _, selection = require("avante").get()
if not selection then return end
selection:create_editing_input()
if question ~= nil or question ~= "" then
vim.api.nvim_exec_autocmds("User", { pattern = "AvanteEditSubmitted", data = { request = question } })
selection:create_editing_input(request, line1, line2)
if request ~= nil and request ~= "" then
vim.api.nvim_exec_autocmds("User", { pattern = "AvanteEditSubmitted", data = { request = request } })
end
end

View File

@@ -449,7 +449,7 @@ M._defaults = {
height = 8, -- Height of the input window in vertical layout
},
edit = {
border = "rounded",
border = { " ", " ", " ", " ", " ", " ", " ", " " },
start_insert = true, -- Start insert mode when opening the edit window
},
ask = {

View File

@@ -4,6 +4,8 @@ local Llm = require("avante.llm")
local Provider = require("avante.providers")
local RepoMap = require("avante.repo_map")
local PromptInput = require("avante.ui.prompt_input")
local SelectionResult = require("avante.selection_result")
local Range = require("avante.range")
local api = vim.api
local fn = vim.fn
@@ -19,6 +21,7 @@ local PRIORITY = vim.highlight.priorities.user
---@field selected_code_extmark_id integer | nil
---@field augroup integer | nil
---@field code_winid integer | nil
---@field code_bufnr integer | nil
---@field prompt_input avante.ui.PromptInput | nil
local Selection = {}
Selection.__index = Selection
@@ -34,6 +37,7 @@ function Selection:new(id)
selection = nil,
cursor_pos = nil,
code_winid = nil,
code_bufnr = nil,
prompt_input = nil,
}, Selection)
end
@@ -88,7 +92,7 @@ function Selection:close_editing_input()
self.selected_code_extmark_id = nil
end
end
if self.cursor_pos and self.code_winid then
if self.cursor_pos and self.code_winid and api.nvim_win_is_valid(self.code_winid) then
vim.schedule(function()
local bufnr = api.nvim_win_get_buf(self.code_winid)
local line_count = api.nvim_buf_line_count(bufnr)
@@ -100,7 +104,124 @@ function Selection:close_editing_input()
end
end
function Selection:create_editing_input()
function Selection:submit_input(input)
if not input then
Utils.error("No input provided", { once = true, title = "Avante" })
return
end
if self.prompt_input and self.prompt_input.spinner_active then
Utils.error(
"Please wait for the previous request to finish before submitting another",
{ once = true, title = "Avante" }
)
return
end
local code_lines = api.nvim_buf_get_lines(self.code_bufnr, 0, -1, false)
local code_content = table.concat(code_lines, "\n")
local full_response = ""
local start_line = self.selection.range.start.lnum
local finish_line = self.selection.range.finish.lnum
local original_first_line_indentation = Utils.get_indentation(code_lines[self.selection.range.start.lnum])
local need_prepend_indentation = false
if self.prompt_input then self.prompt_input:start_spinner() end
---@type AvanteLLMStartCallback
local function on_start(_) end
---@type AvanteLLMChunkCallback
local function on_chunk(chunk)
full_response = full_response .. chunk
local response_lines_ = vim.split(full_response, "\n")
local response_lines = {}
local in_code_block = false
for _, line in ipairs(response_lines_) do
if line:match("^<code>") then
in_code_block = true
line = line:gsub("^<code>", "")
if line ~= "" then table.insert(response_lines, line) end
elseif line:match("</code>") then
in_code_block = false
line = line:gsub("</code>.*$", "")
if line ~= "" then table.insert(response_lines, line) end
elseif in_code_block then
table.insert(response_lines, line)
end
end
if #response_lines == 1 then
local first_line = response_lines[1]
local first_line_indentation = Utils.get_indentation(first_line)
need_prepend_indentation = first_line_indentation ~= original_first_line_indentation
end
if need_prepend_indentation then
for i, line in ipairs(response_lines) do
response_lines[i] = original_first_line_indentation .. line
end
end
api.nvim_buf_set_lines(self.code_bufnr, start_line - 1, finish_line, true, response_lines)
finish_line = start_line + #response_lines - 1
end
---@type AvanteLLMStopCallback
local function on_stop(stop_opts)
if stop_opts.error then
-- NOTE: in Ubuntu 22.04+ you will see this ignorable error from ~/.local/share/nvim/lazy/avante.nvim/lua/avante/llm.lua `on_error = function(err)`, check to avoid showing this error.
if type(stop_opts.error) == "table" and stop_opts.error.exit == nil and stop_opts.error.stderr == "{}" then
return
end
Utils.error(
"Error occurred while processing the response: " .. vim.inspect(stop_opts.error),
{ once = true, title = "Avante" }
)
return
end
if self.prompt_input then self.prompt_input:stop_spinner() end
vim.defer_fn(function() self:close_editing_input() end, 0)
Utils.debug("full response:", full_response)
end
local filetype = api.nvim_get_option_value("filetype", { buf = self.code_bufnr })
local file_ext = api.nvim_buf_get_name(self.code_bufnr):match("^.+%.(.+)$")
local mentions = Utils.extract_mentions(input)
input = mentions.new_content
local project_context = mentions.enable_project_context and RepoMap.get_repo_map(file_ext) or nil
local diagnostics = Utils.get_current_selection_diagnostics(self.code_bufnr, self.selection)
---@type AvanteSelectedCode | nil
local selected_code = nil
if self.selection then
selected_code = {
content = self.selection.content,
file_type = self.selection.filetype,
path = self.selection.filepath,
}
end
Llm.stream({
ask = true,
project_context = vim.json.encode(project_context),
diagnostics = vim.json.encode(diagnostics),
selected_files = { { content = code_content, file_type = filetype, path = "" } },
code_lang = filetype,
selected_code = selected_code,
instructions = input,
mode = "editing",
on_start = on_start,
on_chunk = on_chunk,
on_stop = on_stop,
})
end
---@param request? string
---@param line1? integer
---@param line2? integer
function Selection:create_editing_input(request, line1, line2)
self:close_editing_input()
if not vim.g.avante_login or vim.g.avante_login == false then
@@ -108,14 +229,24 @@ function Selection:create_editing_input()
vim.g.avante_login = true
end
local code_bufnr = api.nvim_get_current_buf()
local code_winid = api.nvim_get_current_win()
self.cursor_pos = api.nvim_win_get_cursor(code_winid)
self.code_winid = code_winid
local code_lines = api.nvim_buf_get_lines(code_bufnr, 0, -1, false)
local code_content = table.concat(code_lines, "\n")
self.code_bufnr = api.nvim_get_current_buf()
self.code_winid = api.nvim_get_current_win()
self.cursor_pos = api.nvim_win_get_cursor(self.code_winid)
local code_lines = api.nvim_buf_get_lines(self.code_bufnr, 0, -1, false)
self.selection = Utils.get_visual_selection_and_range()
if line1 ~= nil and line2 ~= nil then
local filepath = vim.fn.expand("%:p")
local filetype = Utils.get_filetype(filepath)
local content_lines = vim.list_slice(code_lines, line1, line2)
local content = table.concat(content_lines, "\n")
local range = Range:new(
{ lnum = line1, col = #content_lines[1] },
{ lnum = line2, col = #content_lines[#content_lines] }
)
self.selection = SelectionResult:new(filepath, filetype, content, range)
else
self.selection = Utils.get_visual_selection_and_range()
end
if self.selection == nil then
Utils.error("No visual selection found", { once = true, title = "Avante" })
@@ -138,116 +269,18 @@ function Selection:create_editing_input()
end_col = math.min(self.selection.range.finish.col, #code_lines[self.selection.range.finish.lnum])
end
self.selected_code_extmark_id = api.nvim_buf_set_extmark(code_bufnr, SELECTED_CODE_NAMESPACE, start_row, start_col, {
hl_group = "Visual",
hl_mode = "combine",
end_row = end_row,
end_col = end_col,
priority = PRIORITY,
})
local function submit_input(input)
local full_response = ""
local start_line = self.selection.range.start.lnum
local finish_line = self.selection.range.finish.lnum
local original_first_line_indentation = Utils.get_indentation(code_lines[self.selection.range.start.lnum])
local need_prepend_indentation = false
self.prompt_input:start_spinner()
---@type AvanteLLMStartCallback
local function on_start(start_opts) end
---@type AvanteLLMChunkCallback
local function on_chunk(chunk)
full_response = full_response .. chunk
local response_lines_ = vim.split(full_response, "\n")
local response_lines = {}
local in_code_block = false
for _, line in ipairs(response_lines_) do
if line:match("^<code>") then
in_code_block = true
line = line:gsub("^<code>", "")
if line ~= "" then table.insert(response_lines, line) end
elseif line:match("</code>") then
in_code_block = false
line = line:gsub("</code>.*$", "")
if line ~= "" then table.insert(response_lines, line) end
elseif in_code_block then
table.insert(response_lines, line)
end
end
if #response_lines == 1 then
local first_line = response_lines[1]
local first_line_indentation = Utils.get_indentation(first_line)
need_prepend_indentation = first_line_indentation ~= original_first_line_indentation
end
if need_prepend_indentation then
for i, line in ipairs(response_lines) do
response_lines[i] = original_first_line_indentation .. line
end
end
api.nvim_buf_set_lines(code_bufnr, start_line - 1, finish_line, true, response_lines)
finish_line = start_line + #response_lines - 1
end
---@type AvanteLLMStopCallback
local function on_stop(stop_opts)
if stop_opts.error then
-- NOTE: in Ubuntu 22.04+ you will see this ignorable error from ~/.local/share/nvim/lazy/avante.nvim/lua/avante/llm.lua `on_error = function(err)`, check to avoid showing this error.
if type(stop_opts.error) == "table" and stop_opts.error.exit == nil and stop_opts.error.stderr == "{}" then
return
end
Utils.error(
"Error occurred while processing the response: " .. vim.inspect(stop_opts.error),
{ once = true, title = "Avante" }
)
return
end
self.prompt_input:stop_spinner()
vim.defer_fn(function() self:close_editing_input() end, 0)
Utils.debug("full response:", full_response)
end
local filetype = api.nvim_get_option_value("filetype", { buf = code_bufnr })
local file_ext = api.nvim_buf_get_name(code_bufnr):match("^.+%.(.+)$")
local mentions = Utils.extract_mentions(input)
input = mentions.new_content
local project_context = mentions.enable_project_context and RepoMap.get_repo_map(file_ext) or nil
local diagnostics = Utils.get_current_selection_diagnostics(code_bufnr, self.selection)
---@type AvanteSelectedCode | nil
local selected_code = nil
if self.selection then
selected_code = {
content = self.selection.content,
file_type = self.selection.filetype,
path = self.selection.filepath,
}
end
Llm.stream({
ask = true,
project_context = vim.json.encode(project_context),
diagnostics = vim.json.encode(diagnostics),
selected_files = { { content = code_content, file_type = filetype, path = "" } },
code_lang = filetype,
selected_code = selected_code,
instructions = input,
mode = "editing",
on_start = on_start,
on_chunk = on_chunk,
on_stop = on_stop,
self.selected_code_extmark_id =
api.nvim_buf_set_extmark(self.code_bufnr, SELECTED_CODE_NAMESPACE, start_row, start_col, {
hl_group = "Visual",
hl_mode = "combine",
end_row = end_row,
end_col = end_col,
priority = PRIORITY,
})
end
local prompt_input = PromptInput:new({
submit_callback = submit_input,
default_value = request,
submit_callback = function(input) self:submit_input(input) end,
cancel_callback = function() self:close_editing_input() end,
win_opts = {
border = Config.windows.edit.border,
@@ -285,6 +318,13 @@ end
function Selection:setup_autocmds()
Selection.did_setup = true
api.nvim_create_autocmd("User", {
group = self.augroup,
pattern = "AvanteEditSubmitted",
callback = function(ev) self:submit_input(ev.data.request) end,
})
api.nvim_create_autocmd({ "ModeChanged" }, {
group = self.augroup,
pattern = { "n:v", "n:V", "n:" }, -- Entering Visual mode from Normal mode

View File

@@ -7,6 +7,8 @@ local SelectionResult = {}
SelectionResult.__index = SelectionResult
-- Create a selection content and range
---@param filepath string Filepath of the selected content
---@param filetype string Filetype of the selected content
---@param content string Selected content
---@param range avante.Range Selection range
function SelectionResult:new(filepath, filetype, content, range)

View File

@@ -114,6 +114,8 @@ function PromptInput:open()
self.winid = winid
api.nvim_set_option_value("wrap", false, { win = winid })
api.nvim_set_option_value("winblend", 5, { win = winid })
api.nvim_set_option_value("winhighlight", "FloatBorder:NormalFloat", { win = winid })
api.nvim_set_option_value("cursorline", true, { win = winid })
api.nvim_set_option_value("modifiable", true, { buf = bufnr })
@@ -200,6 +202,7 @@ function PromptInput:show_shortcuts_hints()
}
self.shortcuts_hints_winid = api.nvim_open_win(buf, false, opts)
api.nvim_set_option_value("winblend", 10, { win = self.shortcuts_hints_winid })
end
function PromptInput:close_shortcuts_hints()

View File

@@ -46,7 +46,7 @@ end
---@param n string
---@param c vim.api.keyset.user_command.callback
---@param o vim.api.keyset.user_command.opts
local cmd = function(n, c, o)
local function cmd(n, c, o)
o = vim.tbl_extend("force", { nargs = 0 }, o or {})
api.nvim_create_user_command("Avante" .. n, c, o)
end
@@ -102,8 +102,8 @@ end, {
})
cmd(
"Edit",
function(opts) require("avante.api").edit(vim.trim(opts.args)) end,
{ desc = "avante: edit selected block", nargs = "*" }
function(opts) require("avante.api").edit(vim.trim(opts.args), opts.line1, opts.line2) end,
{ desc = "avante: edit selected block", nargs = "*", range = 2 }
)
cmd("Refresh", function() require("avante.api").refresh() end, { desc = "avante: refresh windows" })
cmd("Focus", function() require("avante.api").focus() end, { desc = "avante: switch focus windows" })