diff --git a/README.md b/README.md index f95097e00..95fffea24 100644 --- a/README.md +++ b/README.md @@ -450,11 +450,12 @@ We would like to express our heartfelt gratitude to the contributors of the foll | Nvim Plugin | License | Functionality | Location | | --- | --- | --- | --- | | [git-conflict.nvim](https://github.com/akinsho/git-conflict.nvim) | No License | Diff comparison functionality | [lua/avante/diff.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/diff.lua) | -| [ChatGPT.nvim](https://github.com/jackMort/ChatGPT.nvim) | Apache 2.0 License | Calculation of tokens count | [avante/utils/tokens.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/utils/tokens.lua) | -| [img-clip.nvim](https://github.com/HakonHarnes/img-clip.nvim) | MIT License | Clipboard image support | [avante/clipboard.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/clipboard.lua) | -| [copilot.lua](https://github.com/zbirenbaum/copilot.lua) | MIT License | Copilot support | [avante/providers/copilot.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/copilot.lua) | +| [ChatGPT.nvim](https://github.com/jackMort/ChatGPT.nvim) | Apache 2.0 License | Calculation of tokens count | [lua/avante/utils/tokens.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/utils/tokens.lua) | +| [img-clip.nvim](https://github.com/HakonHarnes/img-clip.nvim) | MIT License | Clipboard image support | [lua/avante/clipboard.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/clipboard.lua) | +| [copilot.lua](https://github.com/zbirenbaum/copilot.lua) | MIT License | Copilot support | [lua/avante/providers/copilot.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/copilot.lua) | | [jinja.vim](https://github.com/HiPhish/jinja.vim) | MIT License | Template filetype support | [syntax/jinja.vim](https://github.com/yetone/avante.nvim/blob/main/syntax/jinja.vim) | -| [codecompanion.nvim](https://github.com/olimorris/codecompanion.nvim) | MIT License | Secrets logic support | [avante/providers/init.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/init.lua) | +| [codecompanion.nvim](https://github.com/olimorris/codecompanion.nvim) | MIT License | Secrets logic support | [lua/avante/providers/init.lua](https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/init.lua) | +| [aider](https://github.com/paul-gauthier/aider) | Apache 2.0 License | Planning mode user prompt | [lua/avante/templates/planning.avanterules](https://github.com/yetone/avante.nvim/blob/main/lua/avante/templates/planning.avanterules) | The high quality and ingenuity of these projects' source code have been immensely beneficial throughout our development process. We extend our sincere thanks and respect to the authors and contributors of these projects. It is the selfless dedication of the open-source community that drives projects like avante.nvim forward. diff --git a/lua/avante/config.lua b/lua/avante/config.lua index ab132cd21..b18679dc2 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -23,7 +23,9 @@ M.defaults = { -- You can use `require('avante.config').override({system_prompt = "MY_SYSTEM_PROMPT"}) conditionally -- in your own autocmds to do it per directory, or that fit your needs. system_prompt = [[ -You are an excellent programming expert. +Act as an expert software developer. +Always use best practices when coding. +Respect and use existing conventions, libraries, etc that are already present in the code base. ]], ---@type AvanteSupportedProvider openai = { diff --git a/lua/avante/selection.lua b/lua/avante/selection.lua index 5fc274123..79d6b96a4 100644 --- a/lua/avante/selection.lua +++ b/lua/avante/selection.lua @@ -362,7 +362,13 @@ function Selection:create_editing_input() ---@type AvanteChunkParser local on_chunk = function(chunk) full_response = full_response .. chunk - local response_lines = vim.split(full_response, "\n") + local response_lines_ = vim.split(full_response, "\n") + local response_lines = {} + for i, line in ipairs(response_lines_) do + if not (string.match(line, "^```") and (i == 1 or i == #response_lines_)) 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) diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index bf22371c7..88961a302 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -133,51 +133,6 @@ function Sidebar:toggle(opts) end end -local function realign_line_numbers(code_lines, snippet) - local snippet_lines = vim.split(snippet.content, "\n") - local snippet_lines_count = #snippet_lines - - local start_line = snippet.range[1] - - local correct_start - for i = start_line, math.max(1, start_line - snippet_lines_count + 1), -1 do - local matched = true - for j = 1, math.min(snippet_lines_count, start_line - i + 1) do - if code_lines[i + j - 1] ~= snippet_lines[j] then - matched = false - break - end - end - if matched then - correct_start = i - break - end - end - - local end_line = snippet.range[2] - - local correct_end - for i = snippet_lines_count - 1, 1, -1 do - local matched = true - for j = 1, i do - if code_lines[end_line + j - 1] ~= snippet_lines[snippet_lines_count - j] then - matched = false - break - end - end - if matched then - correct_end = end_line + i - break - end - end - - if correct_start then snippet.range[1] = correct_start end - - if correct_end then snippet.range[2] = correct_end end - - return snippet -end - ---@class AvanteReplacementResult ---@field content string ---@field is_searching boolean @@ -317,11 +272,9 @@ end ---@field start_line_in_response_buf integer ---@field end_line_in_response_buf integer ----@param code_content string ---@param response_content string ---@return AvanteCodeSnippet[] -local function extract_code_snippets(code_content, response_content) - local code_lines = vim.split(code_content, "\n") +local function extract_code_snippets(response_content) local snippets = {} local current_snippet = {} local in_code_block = false @@ -358,7 +311,6 @@ local function extract_code_snippets(code_content, response_content) start_line_in_response_buf = start_line_in_response_buf, end_line_in_response_buf = idx, } - snippet = realign_line_numbers(code_lines, snippet) table.insert(snippets, snippet) end current_snippet = {} @@ -448,7 +400,6 @@ local function insert_conflict_contents(bufnr, snippets) local snippet_lines = vim.split(snippet.content, "\n") for idx, line in ipairs(snippet_lines) do - line = Utils.trim_line_number(line) if idx == 1 then local indentation = Utils.get_indentation(line) need_prepend_indentation = indentation ~= original_start_line_indentation @@ -512,7 +463,7 @@ end function Sidebar:apply(current_cursor) local content = table.concat(Utils.get_buf_lines(0, -1, self.code.bufnr), "\n") local response, response_start_line = self:get_content_between_separators() - local all_snippets = extract_code_snippets(content, response) + local all_snippets = extract_code_snippets(response) all_snippets = ensure_snippets_no_overlap(content, all_snippets) local selected_snippets = {} if current_cursor then @@ -951,7 +902,7 @@ function Sidebar:is_focused_on(winid) end ---@param content string concatenated content of the buffer ----@param opts? {focus?: boolean, stream?: boolean, scroll?: boolean, 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) if not self.result or not self.result.bufnr then return end opts = vim.tbl_deep_extend("force", { focus = true, scroll = true, stream = false, callback = nil }, opts or {}) @@ -974,8 +925,18 @@ 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() - local lines = vim.split(content, "\n") 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 + 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) api.nvim_set_option_value("filetype", "Avante", { buf = self.result.bufnr }) @@ -1197,15 +1158,11 @@ function Sidebar:create_input(opts) self:update_content(content_prefix .. "**Generating response ...**\n") local content = table.concat(Utils.get_buf_lines(0, -1, self.code.bufnr), "\n") - local content_with_line_numbers = Utils.prepend_line_number(content) local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr }) - local selected_code_content_with_line_numbers = nil - if self.code.selection ~= nil then - selected_code_content_with_line_numbers = - Utils.prepend_line_number(self.code.selection.content, self.code.selection.range.start.line) - end + local selected_code_content = nil + if self.code.selection ~= nil then selected_code_content = self.code.selection.content end if request:sub(1, 1) == "/" then local command, args = request:match("^/(%S+)%s*(.*)") @@ -1228,10 +1185,8 @@ function Sidebar:create_input(opts) Utils.error("Invalid end line number", { once = true, title = "Avante" }) return end - selected_code_content_with_line_numbers = Utils.prepend_line_number( - table.concat(api.nvim_buf_get_lines(self.code.bufnr, start_line - 1, end_line, false), "\n"), - start_line - ) + selected_code_content = + table.concat(api.nvim_buf_get_lines(self.code.bufnr, start_line - 1, end_line, false), "\n") request = question end) else @@ -1244,20 +1199,36 @@ function Sidebar:create_input(opts) end end - local full_response = "" + local original_response = "" + local transformed_response = "" + local displayed_response = "" local is_first_chunk = true + local prev_is_searching = false + ---@type AvanteChunkParser local on_chunk = function(chunk) - full_response = full_response .. 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 - self:update_content(chunk, { stream = true, scroll = true }) - vim.schedule(function() vim.cmd("redraw") 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 end ---@type AvanteCompleteParser @@ -1286,7 +1257,8 @@ function Sidebar:create_input(opts) provider = Config.provider, model = model, request = request, - response = full_response, + response = displayed_response, + original_response = original_response, }) Path.history.save(self.code.bufnr, chat_history) end @@ -1302,9 +1274,9 @@ function Sidebar:create_input(opts) bufnr = self.code.bufnr, ask = opts.ask, project_context = vim.json.encode(project_context), - file_content = content_with_line_numbers, + file_content = content, code_lang = filetype, - selected_code = selected_code_content_with_line_numbers, + selected_code = selected_code_content, instructions = request, mode = "planning", on_chunk = on_chunk, diff --git a/lua/avante/templates/editing.avanterules b/lua/avante/templates/editing.avanterules index 29b0054a2..a02b2e716 100644 --- a/lua/avante/templates/editing.avanterules +++ b/lua/avante/templates/editing.avanterules @@ -2,21 +2,21 @@ {% block user_prompt %} Your task is to modify the provided code according to the user's request. Follow these instructions precisely: -1. Return ONLY the complete modified code. +1. Return *ONLY* the complete modified code. -2. Do not include any explanations, comments, or line numbers in your response. +2. *DO NOT* include three backticks: {%raw%}```{%endraw%} in your suggestion! Treat the suggested code AS IS. -3. Ensure the returned code is complete and can be directly used as a replacement for the original code. +3. *DO NOT* include any explanations, comments, or line numbers in your response. -4. Preserve the original structure, indentation, and formatting of the code as much as possible. +4. Ensure the returned code is complete and can be directly used as a replacement for the original code. -5. Do not omit any parts of the code, even if they are unchanged. +5. Preserve the original structure, indentation, and formatting of the code as much as possible. -6. Maintain the SAME indentation in the returned code as in the source code +6. *DO NOT* omit any parts of the code, even if they are unchanged. -7. DO NOT include three backticks: {%raw%}```{%endraw%} in your suggestion. Treat the suggested code AS IS. +7. Maintain the *SAME INDENTATION* in the returned code as in the source code -8. Only return the new code snippets to be updated, DO NOT return the entire file content. +8. *ONLY* return the new code snippets to be updated, *DO NOT* return the entire file content. Remember that Your response SHOULD CONTAIN ONLY THE MODIFIED CODE to be used as DIRECT REPLACEMENT to the original file. {% endblock %} diff --git a/lua/avante/templates/planning.avanterules b/lua/avante/templates/planning.avanterules index 0374cddcb..0ea165c1b 100644 --- a/lua/avante/templates/planning.avanterules +++ b/lua/avante/templates/planning.avanterules @@ -12,49 +12,138 @@ INSTRUCTION:{% else -%} {% endif -%} {% block user_prompt %} -Your primary task is to suggest code modifications with precise line number ranges. Follow these instructions meticulously: - -1. Carefully analyze the original code, paying close attention to its structure and line numbers. Line numbers start from 1 and include ALL lines, even empty ones. - -2. When suggesting modifications: - a. Use the language in the question to reply. If there are non-English parts in the question, use the language of those parts. - b. Explain why the change is necessary or beneficial. - c. If an image is provided, make sure to use the image in conjunction with the code snippet. - d. Provide the exact code snippet to be replaced using this format: -{% raw %} -Replace lines: {{start_line}}-{{end_line}} -```{{language}} -{{suggested_code}} -``` -{% endraw %} -3. Crucial guidelines for suggested code snippets: - - The content regarding line numbers MUST strictly follow the format "Replace lines: {%raw%}{{start_line}}{%endraw%}-{%raw%}{{end_line}}{%endraw%}". Do not be lazy! - - Only apply the change(s) suggested by the most recent assistant message (before your generation). - - Do not make any unrelated changes to the code. - - Produce a valid full rewrite of the entire original file without skipping any lines. Do not be lazy! - - Do not arbitrarily delete pre-existing comments/empty Lines. - - Do not omit large parts of the original file for no reason. - - Do not omit any needed changes from the requisite messages/code blocks. - - If there is a clicked code block, bias towards just applying that (and applying other changes implied). - - Please keep your suggested code changes minimal, and do not include irrelevant lines in the code snippet. - - Maintain the SAME indentation in the returned code as in the source code - -4. Crucial guidelines for line numbers: - - The range {%raw%}{{start_line}}{%endraw%}-{%raw%}{{end_line}}{%endraw%} is INCLUSIVE. Both start_line and end_line are included in the replacement. - - Count EVERY line, including empty lines and comments lines, comments. Do not be lazy! - - Use the same number for start and end lines for single-line changes. - - For multi-line changes, ensure the range covers ALL affected lines, from first to last. - - Double-check that your line numbers align perfectly with the original code structure. - -5. Final check: - - Review all suggestions, ensuring each line number is correct, especially the start_line and end_line. - - Confirm that no unrelated code is accidentally modified or deleted. - - Verify that the start_line and end_line correctly include all intended lines for replacement. - - Perform a final alignment check to ensure your line numbers haven't shifted, especially the start_line. - - Double-check that your line numbers align perfectly with the original code structure. - - DO NOT return the complete modified code with applied changes! - -Remember that ACCURATE line numbers are CRITICAL. The range {%raw%}{{start_line}}{%endraw%} to {%raw%}{{end_line}}{%endraw%} must include ALL LINES to be replaced. Double-check ALL RANGES before finalizing your response, and MAKE SURE THAT {%raw%}{{start_line}}{%endraw%} hasn't been shifted down. ENSURE line numbers MATCH the original code structure and indentation ARE PRESERVED. +Take requests for changes to the supplied code. +If the request is ambiguous, ask questions. + +Always reply to the user in the same language they are using. + +Once you understand the request you MUST: + +1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat. You can create new files without asking! + +But if you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*. +End your reply and wait for their approval. +You can keep asking if you then decide you need to edit more files. + +2. Think step-by-step and explain the needed changes in a few short sentences. + +3. Describe each change with a *SEARCH/REPLACE block* per the examples below. + +All changes to files must use this *SEARCH/REPLACE block* format. +ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*! + +# Example conversations: + +## USER: Change get_factorial() to use math.factorial + +## ASSISTANT: To make this change we need to modify `mathweb/flask/app.py` to: + +1. Import the math package. +2. Remove the existing factorial() function. +3. Update get_factorial() to call math.factorial instead. + +Here are the *SEARCH/REPLACE* blocks: + +mathweb/flask/app.py + +from flask import Flask + + +import math +from flask import Flask + + +mathweb/flask/app.py + +def factorial(n): + "compute factorial" + + if n == 0: + return 1 + else: + return n * factorial(n-1) + + + + + +mathweb/flask/app.py + + return str(factorial(n)) + + + return str(math.factorial(n)) + + + +## USER: Refactor hello() into its own file. + +## ASSISTANT: To make this change we need to modify `main.py` and make a new file `hello.py`: + +1. Make a new hello.py file with hello() in it. +2. Remove hello() from main.py and replace it with an import. + +Here are the *SEARCH/REPLACE* blocks: + +hello.py + + + +def hello(): + "print a greeting" + + print("hello") + + +main.py + +def hello(): + "print a greeting" + + print("hello") + + +from hello import hello + +# *SEARCH/REPLACE block* Rules: + +Every *SEARCH/REPLACE block* must use this format: +1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc. +2. The start of search block: +3. A contiguous chunk of lines to search for in the existing source code +4. The end of the search block: +5. The start of replace block: +6. The lines to replace into the source code +7. The end of the replace block: + +Use the *FULL* file path, as shown to you by the user. + +Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc. +If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup. + +*SEARCH/REPLACE* blocks will replace *all* matching occurrences. +Include enough lines to make the SEARCH blocks uniquely match the lines to change. + +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. + +Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat! + +To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location. + +Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file. + +If you want to put code in a new file, use a *SEARCH/REPLACE block* with: +- A new file path, including dir name if needed +- An empty `SEARCH` section +- The new file's contents in the `REPLACE` section + +To rename files which have been added to the chat, use shell commands at the end of your response. + + +ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*! {% endblock %} {%- if use_xml_format -%}