diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 88961a3..b65ad09 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -145,7 +145,8 @@ end ---@param code_lang string ---@return AvanteReplacementResult 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 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_replace_tag_start_line = 0 - local trim_breakline_suffix = false + local search_start = 0 local i = 1 while i <= #result_lines do - if result_lines[i] == "" then + local line_content = result_lines[i] + if line_content == "" then is_searching = true + search_start = i + 1 last_search_tag_start_line = i - local search_start = i + 1 - local search_end = search_start - while search_end <= #result_lines and result_lines[search_end] ~= "" do - search_end = search_end + 1 - end + elseif line_content == "" then + is_searching = false + local search_end = i - if search_end > #result_lines then - trim_breakline_suffix = false - -- 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 and - if replace_start > #result_lines or result_lines[replace_start - 1] ~= "" then - trim_breakline_suffix = false - -- 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] ~= "" do - replace_end = replace_end + 1 - end - - if replace_end > #result_lines then - trim_breakline_suffix = false - -- 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 + local start_line = 0 + local end_line = 0 for j = 1, #original_lines - (search_end - search_start) + 1 do local match = true 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 - if start_line and end_line then - transformed = transformed .. string.format("Replace lines: %d-%d\n```%s\n", start_line, end_line, code_lang) - for j = replace_start, replace_end - 1 do - transformed = transformed .. result_lines[j] .. "\n" + local search_start_tag_idx_in_transformed_lines = 0 + for j = 1, #transformed_lines do + if transformed_lines[j] == "" then + search_start_tag_idx_in_transformed_lines = j + break end - transformed = transformed .. "```\n" end - - i = replace_end + 1 -- Move to the line after - is_searching = false + if search_start_tag_idx_in_transformed_lines > 0 then + transformed_lines = vim.list_slice(transformed_lines, 1, search_start_tag_idx_in_transformed_lines - 1) + 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 == "" then + is_replacing = true + last_replace_tag_start_line = i + goto continue + elseif line_content == "" then is_replacing = false - else - trim_breakline_suffix = true - transformed = transformed .. result_lines[i] .. "\n" - i = i + 1 + table.insert(transformed_lines, "```") + goto continue end + table.insert(transformed_lines, line_content) + ::continue:: + i = i + 1 end 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_replacing = is_replacing, 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 -local searching_hints = { "\n 🔍 Searching...", "\n 🔍 Searching...", "\n 🔍 Searching..." } -local searching_hint_idx = 1 +local spinner_chars = { + "⡀", + "⠄", + "⠂", + "⠁", + "⠈", + "⠐", + "⠠", + "⢀", + "⣀", + "⢄", + "⢂", + "⢁", + "⢈", + "⢐", + "⢠", + "⣠", + "⢤", + "⢢", + "⢡", + "⢨", + "⢰", + "⣰", + "⢴", + "⢲", + "⢱", + "⢸", + "⣸", + "⢼", + "⢺", + "⢹", + "⣹", + "⢽", + "⢻", + "⣻", + "⢿", + "⣿", + "⣶", + "⣤", + "⣀", +} +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 ---@return string 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 return table.concat( vim.list_slice(vim.split(replacement.content, "\n"), 1, replacement.last_search_tag_start_line - 1), "\n" - ) .. searching_hint + ) end - if replacement.is_replacing then return replacement.content .. "\n```" end return replacement.content end @@ -901,6 +928,34 @@ function Sidebar:is_focused_on(winid) return false 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 opts? {focus?: boolean, stream?: boolean, scroll?: boolean, backspace?: integer, callback?: fun(): nil} whether to focus the result view function Sidebar:update_content(content, opts) @@ -924,18 +979,9 @@ function Sidebar:update_content(content, opts) 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 - scroll_to_bottom() Utils.unlock_buf(self.result.bufnr) - if opts.backspace ~= nil then - -- Delete the specified number of char from the end of the buffer - -- 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("", true, false, true), "n", true) end - ) - end - end + if opts.backspace ~= nil and opts.backspace > 0 then delete_last_n_chars(self.result.bufnr, opts.backspace) end + scroll_to_bottom() local lines = vim.split(content, "\n") api.nvim_buf_call(self.result.bufnr, function() api.nvim_put(lines, "c", true, true) end) Utils.lock_buf(self.result.bufnr) @@ -1133,6 +1179,8 @@ function Sidebar:create_selected_code() end end +local generating_text = "**Generating response ...**\n" + local hint_window = nil ---@param opts AskOptions @@ -1155,7 +1203,7 @@ function Sidebar:create_input(opts) --- prevent the cursor from jumping to the bottom of the --- buffer at the beginning 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") @@ -1205,30 +1253,21 @@ function Sidebar:create_input(opts) local is_first_chunk = true - local prev_is_searching = false - ---@type AvanteChunkParser local on_chunk = function(chunk) original_response = original_response .. chunk local transformed = transform_result_content(content, transformed_response .. chunk, filetype) transformed_response = transformed.content - prev_is_searching = transformed.is_searching local cur_displayed_response = generate_display_content(transformed) if is_first_chunk then is_first_chunk = false self:update_content(content_prefix .. chunk, { stream = false, scroll = true }) return end - if cur_displayed_response ~= displayed_response then - local backspace = nil - if prev_is_searching and not transformed.is_searching then backspace = #searching_hints[1] end - 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 + local suffix = get_display_content_suffix(transformed) + self:update_content(content_prefix .. cur_displayed_response .. suffix, { stream = false, scroll = true }) + vim.schedule(function() vim.cmd("redraw") end) + displayed_response = cur_displayed_response end ---@type AvanteCompleteParser diff --git a/lua/avante/templates/planning.avanterules b/lua/avante/templates/planning.avanterules index 0ea165c..9bd146f 100644 --- a/lua/avante/templates/planning.avanterules +++ b/lua/avante/templates/planning.avanterules @@ -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. 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 three backticks: {%raw%}```{%endraw%} in your response! Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat!