fix: stream display replacement content (#699)
This commit is contained in:
parent
3dac407a11
commit
4132485487
@ -145,7 +145,8 @@ end
|
|||||||
---@param code_lang string
|
---@param code_lang string
|
||||||
---@return AvanteReplacementResult
|
---@return AvanteReplacementResult
|
||||||
local function transform_result_content(original_content, result_content, code_lang)
|
local function transform_result_content(original_content, result_content, code_lang)
|
||||||
local transformed = ""
|
local transformed_lines = {}
|
||||||
|
|
||||||
local original_lines = vim.split(original_content, "\n")
|
local original_lines = vim.split(original_content, "\n")
|
||||||
local result_lines = vim.split(result_content, "\n")
|
local result_lines = vim.split(result_content, "\n")
|
||||||
|
|
||||||
@ -154,50 +155,21 @@ local function transform_result_content(original_content, result_content, code_l
|
|||||||
local last_search_tag_start_line = 0
|
local last_search_tag_start_line = 0
|
||||||
local last_replace_tag_start_line = 0
|
local last_replace_tag_start_line = 0
|
||||||
|
|
||||||
local trim_breakline_suffix = false
|
local search_start = 0
|
||||||
|
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= #result_lines do
|
while i <= #result_lines do
|
||||||
if result_lines[i] == "<SEARCH>" then
|
local line_content = result_lines[i]
|
||||||
|
if line_content == "<SEARCH>" then
|
||||||
is_searching = true
|
is_searching = true
|
||||||
|
search_start = i + 1
|
||||||
last_search_tag_start_line = i
|
last_search_tag_start_line = i
|
||||||
local search_start = i + 1
|
elseif line_content == "</SEARCH>" then
|
||||||
local search_end = search_start
|
is_searching = false
|
||||||
while search_end <= #result_lines and result_lines[search_end] ~= "</SEARCH>" do
|
local search_end = i
|
||||||
search_end = search_end + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if search_end > #result_lines then
|
local start_line = 0
|
||||||
trim_breakline_suffix = false
|
local end_line = 0
|
||||||
-- <SEARCH> tag is not closed, add remaining content and return
|
|
||||||
transformed = transformed .. table.concat(result_lines, "\n", i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local replace_start = search_end + 2 -- Skip </SEARCH> and <REPLACE>
|
|
||||||
if replace_start > #result_lines or result_lines[replace_start - 1] ~= "<REPLACE>" then
|
|
||||||
trim_breakline_suffix = false
|
|
||||||
-- <REPLACE> tag is missing, add remaining content and return
|
|
||||||
transformed = transformed .. table.concat(result_lines, "\n", i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
is_replacing = true
|
|
||||||
last_replace_tag_start_line = replace_start - 1
|
|
||||||
local replace_end = replace_start
|
|
||||||
while replace_end <= #result_lines and result_lines[replace_end] ~= "</REPLACE>" do
|
|
||||||
replace_end = replace_end + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if replace_end > #result_lines then
|
|
||||||
trim_breakline_suffix = false
|
|
||||||
-- </REPLACE> tag is missing, add remaining content and return
|
|
||||||
transformed = transformed .. table.concat(result_lines, "\n", i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Find the corresponding lines in the original content
|
|
||||||
local start_line, end_line
|
|
||||||
for j = 1, #original_lines - (search_end - search_start) + 1 do
|
for j = 1, #original_lines - (search_end - search_start) + 1 do
|
||||||
local match = true
|
local match = true
|
||||||
for k = 0, search_end - search_start - 1 do
|
for k = 0, search_end - search_start - 1 do
|
||||||
@ -215,26 +187,37 @@ local function transform_result_content(original_content, result_content, code_l
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if start_line and end_line then
|
local search_start_tag_idx_in_transformed_lines = 0
|
||||||
transformed = transformed .. string.format("Replace lines: %d-%d\n```%s\n", start_line, end_line, code_lang)
|
for j = 1, #transformed_lines do
|
||||||
for j = replace_start, replace_end - 1 do
|
if transformed_lines[j] == "<SEARCH>" then
|
||||||
transformed = transformed .. result_lines[j] .. "\n"
|
search_start_tag_idx_in_transformed_lines = j
|
||||||
|
break
|
||||||
end
|
end
|
||||||
transformed = transformed .. "```\n"
|
|
||||||
end
|
end
|
||||||
|
if search_start_tag_idx_in_transformed_lines > 0 then
|
||||||
i = replace_end + 1 -- Move to the line after </REPLACE>
|
transformed_lines = vim.list_slice(transformed_lines, 1, search_start_tag_idx_in_transformed_lines - 1)
|
||||||
is_searching = false
|
end
|
||||||
|
vim.list_extend(transformed_lines, {
|
||||||
|
string.format("Replace lines: %d-%d", start_line, end_line),
|
||||||
|
string.format("```%s", code_lang),
|
||||||
|
})
|
||||||
|
goto continue
|
||||||
|
elseif line_content == "<REPLACE>" then
|
||||||
|
is_replacing = true
|
||||||
|
last_replace_tag_start_line = i
|
||||||
|
goto continue
|
||||||
|
elseif line_content == "</REPLACE>" then
|
||||||
is_replacing = false
|
is_replacing = false
|
||||||
else
|
table.insert(transformed_lines, "```")
|
||||||
trim_breakline_suffix = true
|
goto continue
|
||||||
transformed = transformed .. result_lines[i] .. "\n"
|
|
||||||
i = i + 1
|
|
||||||
end
|
end
|
||||||
|
table.insert(transformed_lines, line_content)
|
||||||
|
::continue::
|
||||||
|
i = i + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content = trim_breakline_suffix and transformed:sub(1, -2) or transformed, -- Remove trailing newline
|
content = table.concat(transformed_lines, "\n"),
|
||||||
is_searching = is_searching,
|
is_searching = is_searching,
|
||||||
is_replacing = is_replacing,
|
is_replacing = is_replacing,
|
||||||
last_search_tag_start_line = last_search_tag_start_line,
|
last_search_tag_start_line = last_search_tag_start_line,
|
||||||
@ -242,25 +225,69 @@ local function transform_result_content(original_content, result_content, code_l
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local searching_hints = { "\n 🔍 Searching...", "\n 🔍 Searching...", "\n 🔍 Searching..." }
|
local spinner_chars = {
|
||||||
local searching_hint_idx = 1
|
"⡀",
|
||||||
|
"⠄",
|
||||||
|
"⠂",
|
||||||
|
"⠁",
|
||||||
|
"⠈",
|
||||||
|
"⠐",
|
||||||
|
"⠠",
|
||||||
|
"⢀",
|
||||||
|
"⣀",
|
||||||
|
"⢄",
|
||||||
|
"⢂",
|
||||||
|
"⢁",
|
||||||
|
"⢈",
|
||||||
|
"⢐",
|
||||||
|
"⢠",
|
||||||
|
"⣠",
|
||||||
|
"⢤",
|
||||||
|
"⢢",
|
||||||
|
"⢡",
|
||||||
|
"⢨",
|
||||||
|
"⢰",
|
||||||
|
"⣰",
|
||||||
|
"⢴",
|
||||||
|
"⢲",
|
||||||
|
"⢱",
|
||||||
|
"⢸",
|
||||||
|
"⣸",
|
||||||
|
"⢼",
|
||||||
|
"⢺",
|
||||||
|
"⢹",
|
||||||
|
"⣹",
|
||||||
|
"⢽",
|
||||||
|
"⢻",
|
||||||
|
"⣻",
|
||||||
|
"⢿",
|
||||||
|
"⣿",
|
||||||
|
"⣶",
|
||||||
|
"⣤",
|
||||||
|
"⣀",
|
||||||
|
}
|
||||||
|
local spinner_index = 1
|
||||||
|
|
||||||
|
local function get_searching_hint()
|
||||||
|
spinner_index = (spinner_index % #spinner_chars) + 1
|
||||||
|
local spinner = spinner_chars[spinner_index]
|
||||||
|
return "\n" .. spinner .. " Searching..."
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_display_content_suffix(replacement)
|
||||||
|
if replacement.is_searching then return get_searching_hint() end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
---@param replacement AvanteReplacementResult
|
---@param replacement AvanteReplacementResult
|
||||||
---@return string
|
---@return string
|
||||||
local function generate_display_content(replacement)
|
local function generate_display_content(replacement)
|
||||||
local searching_hint = searching_hints[searching_hint_idx]
|
|
||||||
if searching_hint_idx >= #searching_hints then
|
|
||||||
searching_hint_idx = 1
|
|
||||||
else
|
|
||||||
searching_hint_idx = searching_hint_idx + 1
|
|
||||||
end
|
|
||||||
if replacement.is_searching then
|
if replacement.is_searching then
|
||||||
return table.concat(
|
return table.concat(
|
||||||
vim.list_slice(vim.split(replacement.content, "\n"), 1, replacement.last_search_tag_start_line - 1),
|
vim.list_slice(vim.split(replacement.content, "\n"), 1, replacement.last_search_tag_start_line - 1),
|
||||||
"\n"
|
"\n"
|
||||||
) .. searching_hint
|
)
|
||||||
end
|
end
|
||||||
if replacement.is_replacing then return replacement.content .. "\n```" end
|
|
||||||
return replacement.content
|
return replacement.content
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -901,6 +928,34 @@ function Sidebar:is_focused_on(winid)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function delete_last_n_chars(bufnr, n)
|
||||||
|
bufnr = bufnr or api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local line_count = api.nvim_buf_line_count(bufnr)
|
||||||
|
|
||||||
|
while n > 0 and line_count > 0 do
|
||||||
|
local last_line = api.nvim_buf_get_lines(bufnr, line_count - 1, line_count, false)[1]
|
||||||
|
|
||||||
|
local total_chars_in_line = #last_line + 1
|
||||||
|
|
||||||
|
if total_chars_in_line > n then
|
||||||
|
local chars_to_keep = total_chars_in_line - n - 1 - 1
|
||||||
|
local new_last_line = last_line:sub(1, chars_to_keep)
|
||||||
|
if new_last_line == "" then
|
||||||
|
api.nvim_buf_set_lines(bufnr, line_count - 1, line_count, false, {})
|
||||||
|
line_count = line_count - 1
|
||||||
|
else
|
||||||
|
api.nvim_buf_set_lines(bufnr, line_count - 1, line_count, false, { new_last_line })
|
||||||
|
end
|
||||||
|
n = 0
|
||||||
|
else
|
||||||
|
n = n - total_chars_in_line
|
||||||
|
api.nvim_buf_set_lines(bufnr, line_count - 1, line_count, false, {})
|
||||||
|
line_count = line_count - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param content string concatenated content of the buffer
|
---@param content string concatenated content of the buffer
|
||||||
---@param opts? {focus?: boolean, stream?: boolean, scroll?: boolean, backspace?: integer, callback?: fun(): nil} whether to focus the result view
|
---@param opts? {focus?: boolean, stream?: boolean, scroll?: boolean, backspace?: integer, callback?: fun(): nil} whether to focus the result view
|
||||||
function Sidebar:update_content(content, opts)
|
function Sidebar:update_content(content, opts)
|
||||||
@ -924,18 +979,9 @@ function Sidebar:update_content(content, opts)
|
|||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
if not self.result or not self.result.bufnr or not api.nvim_buf_is_valid(self.result.bufnr) then return end
|
if not self.result or not self.result.bufnr or not api.nvim_buf_is_valid(self.result.bufnr) then return end
|
||||||
scroll_to_bottom()
|
|
||||||
Utils.unlock_buf(self.result.bufnr)
|
Utils.unlock_buf(self.result.bufnr)
|
||||||
if opts.backspace ~= nil then
|
if opts.backspace ~= nil and opts.backspace > 0 then delete_last_n_chars(self.result.bufnr, opts.backspace) end
|
||||||
-- Delete the specified number of char from the end of the buffer
|
scroll_to_bottom()
|
||||||
-- sends the buffer to the backend
|
|
||||||
for _ = 1, opts.backspace do
|
|
||||||
api.nvim_buf_call(
|
|
||||||
self.result.bufnr,
|
|
||||||
function() api.nvim_feedkeys(api.nvim_replace_termcodes("<BS>", true, false, true), "n", true) end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local lines = vim.split(content, "\n")
|
local lines = vim.split(content, "\n")
|
||||||
api.nvim_buf_call(self.result.bufnr, function() api.nvim_put(lines, "c", true, true) end)
|
api.nvim_buf_call(self.result.bufnr, function() api.nvim_put(lines, "c", true, true) end)
|
||||||
Utils.lock_buf(self.result.bufnr)
|
Utils.lock_buf(self.result.bufnr)
|
||||||
@ -1133,6 +1179,8 @@ function Sidebar:create_selected_code()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local generating_text = "**Generating response ...**\n"
|
||||||
|
|
||||||
local hint_window = nil
|
local hint_window = nil
|
||||||
|
|
||||||
---@param opts AskOptions
|
---@param opts AskOptions
|
||||||
@ -1155,7 +1203,7 @@ function Sidebar:create_input(opts)
|
|||||||
--- prevent the cursor from jumping to the bottom of the
|
--- prevent the cursor from jumping to the bottom of the
|
||||||
--- buffer at the beginning
|
--- buffer at the beginning
|
||||||
self:update_content("", { focus = true, scroll = false })
|
self:update_content("", { focus = true, scroll = false })
|
||||||
self:update_content(content_prefix .. "**Generating response ...**\n")
|
self:update_content(content_prefix .. generating_text)
|
||||||
|
|
||||||
local content = table.concat(Utils.get_buf_lines(0, -1, self.code.bufnr), "\n")
|
local content = table.concat(Utils.get_buf_lines(0, -1, self.code.bufnr), "\n")
|
||||||
|
|
||||||
@ -1205,30 +1253,21 @@ function Sidebar:create_input(opts)
|
|||||||
|
|
||||||
local is_first_chunk = true
|
local is_first_chunk = true
|
||||||
|
|
||||||
local prev_is_searching = false
|
|
||||||
|
|
||||||
---@type AvanteChunkParser
|
---@type AvanteChunkParser
|
||||||
local on_chunk = function(chunk)
|
local on_chunk = function(chunk)
|
||||||
original_response = original_response .. chunk
|
original_response = original_response .. chunk
|
||||||
local transformed = transform_result_content(content, transformed_response .. chunk, filetype)
|
local transformed = transform_result_content(content, transformed_response .. chunk, filetype)
|
||||||
transformed_response = transformed.content
|
transformed_response = transformed.content
|
||||||
prev_is_searching = transformed.is_searching
|
|
||||||
local cur_displayed_response = generate_display_content(transformed)
|
local cur_displayed_response = generate_display_content(transformed)
|
||||||
if is_first_chunk then
|
if is_first_chunk then
|
||||||
is_first_chunk = false
|
is_first_chunk = false
|
||||||
self:update_content(content_prefix .. chunk, { stream = false, scroll = true })
|
self:update_content(content_prefix .. chunk, { stream = false, scroll = true })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if cur_displayed_response ~= displayed_response then
|
local suffix = get_display_content_suffix(transformed)
|
||||||
local backspace = nil
|
self:update_content(content_prefix .. cur_displayed_response .. suffix, { stream = false, scroll = true })
|
||||||
if prev_is_searching and not transformed.is_searching then backspace = #searching_hints[1] end
|
vim.schedule(function() vim.cmd("redraw") end)
|
||||||
displayed_response = cur_displayed_response
|
displayed_response = cur_displayed_response
|
||||||
self:update_content(
|
|
||||||
content_prefix .. displayed_response,
|
|
||||||
{ stream = false, scroll = true, backspace = backspace }
|
|
||||||
)
|
|
||||||
vim.schedule(function() vim.cmd("redraw") end)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type AvanteCompleteParser
|
---@type AvanteCompleteParser
|
||||||
|
@ -128,6 +128,7 @@ Keep *SEARCH/REPLACE* blocks concise.
|
|||||||
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||||
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
||||||
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
||||||
|
*DO NOT* include three backticks: {%raw%}```{%endraw%} in your response!
|
||||||
|
|
||||||
Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!
|
Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user