refactor: refactor planning prompts to resolve line number issues and indentation issues (#382)
This commit is contained in:
@@ -47,7 +47,7 @@ M.defaults = {
|
||||
model = "claude-3-5-sonnet-20240620",
|
||||
timeout = 30000, -- Timeout in milliseconds
|
||||
temperature = 0,
|
||||
max_tokens = 4096,
|
||||
max_tokens = 8000,
|
||||
["local"] = false,
|
||||
},
|
||||
---@type AvanteSupportedProvider
|
||||
|
||||
@@ -13,71 +13,97 @@ M.CANCEL_PATTERN = "AvanteLLMEscape"
|
||||
|
||||
------------------------------Prompt and type------------------------------
|
||||
|
||||
---@alias AvanteSystemPrompt string
|
||||
local system_prompt = [[
|
||||
You are an excellent programming expert.
|
||||
]]
|
||||
|
||||
-- Copy from: https://github.com/Doriandarko/claude-engineer/blob/15c94963cbf9d01b8ae7bbb5d42d7025aa0555d5/main.py#L276
|
||||
---@alias AvanteBasePrompt string
|
||||
local planning_mode_prompt = [[
|
||||
Your primary task is to suggest code modifications with precise line number ranges. Follow these instructions meticulously:
|
||||
local planning_mode_system_prompt_tpl = [[
|
||||
You are an AI coding agent that generates code according to the instructions. Follow these steps:
|
||||
|
||||
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.
|
||||
1. Review the entire file content to understand the context:
|
||||
${file_content}
|
||||
|
||||
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:
|
||||
2. Carefully analyze the selected code:
|
||||
${selected_code}
|
||||
|
||||
Replace lines: {{start_line}}-{{end_line}}
|
||||
```{{language}}
|
||||
{{suggested_code}}
|
||||
```
|
||||
3. Carefully analyze the specific instructions:
|
||||
${instructions}
|
||||
|
||||
3. Crucial guidelines for suggested code snippets:
|
||||
- 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. Take into account the overall project context:
|
||||
${project_context}
|
||||
|
||||
4. Crucial guidelines for line numbers:
|
||||
- The content regarding line numbers MUST strictly follow the format "Replace lines: {{start_line}}-{{end_line}}". Do not be lazy!
|
||||
- The range {{start_line}}-{{end_line}} 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!
|
||||
- For single-line changes, use the same number for start and end lines.
|
||||
- For multi-line changes, ensure the range covers ALL affected lines, from the very first to the very last.
|
||||
- Double-check that your line numbers align perfectly with the original code structure.
|
||||
5. Consider the memory of previous edits:
|
||||
${memory_context}
|
||||
|
||||
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 show the full content after these modifications.
|
||||
6. Consider the full context of all files in the project:
|
||||
${full_file_contents_context}
|
||||
|
||||
Remember: Accurate line numbers are CRITICAL. The range start_line to end_line must include ALL lines to be replaced, from the very first to the very last. Double-check every range before finalizing your response, paying special attention to the start_line to ensure it hasn't shifted down. Ensure that your line numbers perfectly match the original code structure without any overall shift.
|
||||
7. Generate SEARCH/REPLACE blocks for each necessary change. Each block should:
|
||||
- Include enough context to uniquely identify the code to be changed
|
||||
- Provide the exact replacement code, maintaining correct INDENTATION and FORMATTING
|
||||
- Focus on specific, targeted changes rather than large, sweeping modifications
|
||||
- The content in the SEARCH tag MUST NOT contain any of your generated content
|
||||
- The content in the SEARCH tag MUST be based on the original content of the source file
|
||||
- The content in the SEARCH tag needs to ensure a certain context to guarantee its UNIQUENESS
|
||||
- The content in the REPLACE tag should also correspond to the context of the SEARCH tag
|
||||
- There should be NO OVERLAP between the code of each SEARCH tag.
|
||||
- DO NOT use ``` to wrap code blocks
|
||||
|
||||
8. Ensure that your SEARCH/REPLACE blocks:
|
||||
- Address all relevant aspects of the instructions
|
||||
- Maintain or enhance code readability and efficiency
|
||||
- Consider the overall structure and purpose of the code
|
||||
- Follow best practices and coding standards for the language
|
||||
- Maintain consistency with the project context and previous edits
|
||||
- Take into account the full context of all files in the project
|
||||
|
||||
IMPORTANT: MUST TO ADD EXPLANATIONS BEFORE AND AFTER EACH SEARCH/REPLACE BLOCK.
|
||||
USE THE FOLLOWING FORMAT FOR EACH BLOCK:
|
||||
|
||||
<SEARCH>
|
||||
Code to be replaced
|
||||
</SEARCH>
|
||||
<REPLACE>
|
||||
New code to insert
|
||||
</REPLACE>
|
||||
|
||||
If no changes are needed, return an empty list.
|
||||
]]
|
||||
|
||||
local editing_mode_prompt = [[
|
||||
Your task is to modify the provided code according to the user's request. Follow these instructions precisely:
|
||||
local editing_mode_system_prompt_tpl = [[
|
||||
You are an AI coding agent that generates code according to the instructions. Follow these steps:
|
||||
|
||||
1. Carefully analyze the original code and the user's request.
|
||||
2. Make the necessary modifications to the code as requested.
|
||||
3. Return ONLY the complete modified code.
|
||||
4. Do not include any explanations, comments, or line numbers in your response.
|
||||
5. Ensure the returned code is complete and can be directly used as a replacement for the original code.
|
||||
6. Preserve the original structure, indentation, and formatting of the code as much as possible.
|
||||
7. Do not omit any parts of the code, even if they are unchanged.
|
||||
8. Maintain the SAME indentation in the returned code as in the source code
|
||||
9. Do NOT include three backticks: ```
|
||||
10. Only return code part, do NOT return the context part!
|
||||
1. Review the entire file content to understand the context:
|
||||
${file_content}
|
||||
|
||||
2. Carefully analyze the selected code:
|
||||
${selected_code}
|
||||
|
||||
3. Carefully analyze the specific instructions:
|
||||
${instructions}
|
||||
|
||||
4. Take into account the overall project context:
|
||||
${project_context}
|
||||
|
||||
5. Consider the memory of previous edits:
|
||||
${memory_context}
|
||||
|
||||
6. Consider the full context of all files in the project:
|
||||
${full_file_contents_context}
|
||||
|
||||
7. Return ONLY the complete modified code.
|
||||
|
||||
8. Do not include any explanations, comments, or line numbers in your response.
|
||||
|
||||
9. Ensure the returned code is complete and can be directly used as a replacement for the original code.
|
||||
|
||||
11. Preserve the original structure, indentation, and formatting of the code as much as possible.
|
||||
|
||||
12. Do not omit any parts of the code, even if they are unchanged.
|
||||
|
||||
13. Maintain the SAME indentation in the returned code as in the source code
|
||||
|
||||
14. Do NOT include three backticks: ```
|
||||
|
||||
15. Only return code part, do NOT return the context part!
|
||||
|
||||
Remember: Your response should contain nothing but ONLY the modified code, ready to be used as a direct replacement for the original file.
|
||||
]]
|
||||
@@ -85,41 +111,48 @@ Remember: Your response should contain nothing but ONLY the modified code, ready
|
||||
local group = api.nvim_create_augroup("avante_llm", { clear = true })
|
||||
local active_job = nil
|
||||
|
||||
---@param question string
|
||||
---@param code_lang string
|
||||
---@param code_content string
|
||||
---@param selected_content_content string | nil
|
||||
---@param mode "planning" | "editing"
|
||||
---@param on_chunk AvanteChunkParser
|
||||
---@param on_complete AvanteCompleteParser
|
||||
M.stream = function(question, code_lang, code_content, selected_content_content, mode, on_chunk, on_complete)
|
||||
mode = mode or "planning"
|
||||
---@class StreamOptions
|
||||
---@field file_content string
|
||||
---@field selected_code string | nil
|
||||
---@field instructions string
|
||||
---@field project_context string | nil
|
||||
---@field memory_context string | nil
|
||||
---@field full_file_contents_context string | nil
|
||||
---@field mode "planning" | "editing"
|
||||
---@field on_chunk AvanteChunkParser
|
||||
---@field on_complete AvanteCompleteParser
|
||||
|
||||
---@param opts StreamOptions
|
||||
M.stream = function(opts)
|
||||
local mode = opts.mode or "planning"
|
||||
local provider = Config.provider
|
||||
|
||||
local system_prompt_tpl = mode == "planning" and planning_mode_system_prompt_tpl or editing_mode_system_prompt_tpl
|
||||
|
||||
-- Check if the question contains an image path
|
||||
local image_path = nil
|
||||
local original_question = question
|
||||
if question:match("image: ") then
|
||||
local lines = vim.split(question, "\n")
|
||||
local image_paths = {}
|
||||
local original_instructions = opts.instructions
|
||||
if opts.instructions:match("image: ") then
|
||||
local lines = vim.split(opts.instructions, "\n")
|
||||
for i, line in ipairs(lines) do
|
||||
if line:match("^image: ") then
|
||||
image_path = line:gsub("^image: ", "")
|
||||
local image_path = line:gsub("^image: ", "")
|
||||
table.insert(image_paths, image_path)
|
||||
table.remove(lines, i)
|
||||
original_question = table.concat(lines, "\n")
|
||||
break
|
||||
end
|
||||
end
|
||||
original_instructions = table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
local system_prompt =
|
||||
system_prompt_tpl:gsub("%${(.-)}", vim.tbl_deep_extend("force", opts, { instructions = original_instructions }))
|
||||
|
||||
---@type AvantePromptOptions
|
||||
local code_opts = {
|
||||
base_prompt = mode == "planning" and planning_mode_prompt or editing_mode_prompt,
|
||||
system_prompt = system_prompt,
|
||||
question = original_question,
|
||||
image_path = image_path,
|
||||
code_lang = code_lang,
|
||||
code_content = code_content,
|
||||
selected_code_content = selected_content_content,
|
||||
user_prompt = opts.selected_code and "Please suggest modifications to the selected code."
|
||||
or "Please suggest modifications to the file coontent.",
|
||||
image_paths = image_paths,
|
||||
}
|
||||
|
||||
---@type string
|
||||
@@ -129,7 +162,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
|
||||
local Provider = P[provider]
|
||||
|
||||
---@type AvanteHandlerOptions
|
||||
local handler_opts = { on_chunk = on_chunk, on_complete = on_complete }
|
||||
local handler_opts = { on_chunk = opts.on_chunk, on_complete = opts.on_complete }
|
||||
---@type AvanteCurlOutput
|
||||
local spec = Provider.parse_curl_args(Provider, code_opts)
|
||||
|
||||
@@ -163,7 +196,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
|
||||
stream = function(err, data, _)
|
||||
if err then
|
||||
completed = true
|
||||
on_complete(err)
|
||||
opts.on_complete(err)
|
||||
return
|
||||
end
|
||||
if not data then
|
||||
@@ -189,7 +222,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
|
||||
end,
|
||||
on_error = function(err)
|
||||
completed = true
|
||||
on_complete(err)
|
||||
opts.on_complete(err)
|
||||
end,
|
||||
callback = function(result)
|
||||
if result.status >= 400 then
|
||||
@@ -201,7 +234,9 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
|
||||
vim.schedule(function()
|
||||
if not completed then
|
||||
completed = true
|
||||
on_complete("API request failed with status " .. result.status .. ". Body: " .. vim.inspect(result.body))
|
||||
opts.on_complete(
|
||||
"API request failed with status " .. result.status .. ". Body: " .. vim.inspect(result.body)
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -7,54 +7,24 @@ local M = {}
|
||||
|
||||
M.api_key_name = "ANTHROPIC_API_KEY"
|
||||
|
||||
M.parse_message = function(opts)
|
||||
local code_prompt_obj = {
|
||||
type = "text",
|
||||
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.code_content),
|
||||
}
|
||||
---@param prompt_opts AvantePromptOptions
|
||||
M.parse_message = function(prompt_opts)
|
||||
local message_content = {}
|
||||
|
||||
if Utils.tokens.calculate_tokens(code_prompt_obj.text) > 1024 then
|
||||
code_prompt_obj.cache_control = { type = "ephemeral" }
|
||||
end
|
||||
|
||||
if opts.selected_code_content then
|
||||
code_prompt_obj.text = string.format("<code_context>```%s\n%s```</code_context>", opts.code_lang, opts.code_content)
|
||||
end
|
||||
|
||||
local message_content = {
|
||||
code_prompt_obj,
|
||||
}
|
||||
|
||||
if opts.selected_code_content then
|
||||
local selected_code_obj = {
|
||||
type = "text",
|
||||
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.selected_code_content),
|
||||
}
|
||||
|
||||
if Utils.tokens.calculate_tokens(selected_code_obj.text) > 1024 then
|
||||
selected_code_obj.cache_control = { type = "ephemeral" }
|
||||
if Clipboard.support_paste_image() and prompt_opts.image_paths then
|
||||
for _, image_path in ipairs(prompt_opts.image_paths) do
|
||||
table.insert(message_content, {
|
||||
type = "image",
|
||||
source = {
|
||||
type = "base64",
|
||||
media_type = "image/png",
|
||||
data = Clipboard.get_base64_content(image_path),
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
table.insert(message_content, selected_code_obj)
|
||||
end
|
||||
|
||||
if Clipboard.support_paste_image() and opts.image_path then
|
||||
table.insert(message_content, {
|
||||
type = "image",
|
||||
source = {
|
||||
type = "base64",
|
||||
media_type = "image/png",
|
||||
data = Clipboard.get_base64_content(opts.image_path),
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
table.insert(message_content, {
|
||||
type = "text",
|
||||
text = string.format("<question>%s</question>", opts.question),
|
||||
})
|
||||
|
||||
local user_prompt = opts.base_prompt
|
||||
local user_prompt = prompt_opts.user_prompt
|
||||
|
||||
local user_prompt_obj = {
|
||||
type = "text",
|
||||
@@ -91,9 +61,9 @@ M.parse_response = function(data_stream, event_state, opts)
|
||||
end
|
||||
|
||||
---@param provider AvanteProviderFunctor
|
||||
---@param code_opts AvantePromptOptions
|
||||
---@param prompt_opts AvantePromptOptions
|
||||
---@return table
|
||||
M.parse_curl_args = function(provider, code_opts)
|
||||
M.parse_curl_args = function(provider, prompt_opts)
|
||||
local base, body_opts = P.parse_config(provider)
|
||||
|
||||
local headers = {
|
||||
@@ -112,7 +82,14 @@ M.parse_curl_args = function(provider, code_opts)
|
||||
headers = headers,
|
||||
body = vim.tbl_deep_extend("force", {
|
||||
model = base.model,
|
||||
messages = M.parse_message(code_opts),
|
||||
system = {
|
||||
{
|
||||
type = "text",
|
||||
text = prompt_opts.system_prompt,
|
||||
cache_control = { type = "ephemeral" },
|
||||
},
|
||||
},
|
||||
messages = M.parse_message(prompt_opts),
|
||||
stream = true,
|
||||
}, body_opts),
|
||||
}
|
||||
|
||||
@@ -31,37 +31,9 @@ local M = {}
|
||||
M.api_key_name = "CO_API_KEY"
|
||||
|
||||
M.parse_message = function(opts)
|
||||
local user_prompt = opts.base_prompt
|
||||
.. "\n\nCODE:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.code_content
|
||||
.. "\n```"
|
||||
.. "\n\nQUESTION:\n"
|
||||
.. opts.question
|
||||
|
||||
if opts.selected_code_content ~= nil then
|
||||
user_prompt = opts.base_prompt
|
||||
.. "\n\nCODE CONTEXT:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.code_content
|
||||
.. "\n```"
|
||||
.. "\n\nCODE:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.selected_code_content
|
||||
.. "\n```"
|
||||
.. "\n\nQUESTION:\n"
|
||||
.. opts.question
|
||||
end
|
||||
|
||||
return {
|
||||
preamble = opts.system_prompt,
|
||||
message = user_prompt,
|
||||
message = opts.user_prompt,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -8,41 +8,24 @@ local M = {}
|
||||
M.api_key_name = "GEMINI_API_KEY"
|
||||
|
||||
M.parse_message = function(opts)
|
||||
local code_prompt_obj = {
|
||||
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.code_content),
|
||||
}
|
||||
local message_content = {}
|
||||
|
||||
if opts.selected_code_content then
|
||||
code_prompt_obj.text = string.format("<code_context>```%s\n%s```</code_context>", opts.code_lang, opts.code_content)
|
||||
end
|
||||
if Clipboard.support_paste_image() and opts.image_paths then
|
||||
for _, image_path in ipairs(opts.image_paths) do
|
||||
local image_data = {
|
||||
inline_data = {
|
||||
mime_type = "image/png",
|
||||
data = Clipboard.get_base64_content(image_path),
|
||||
},
|
||||
}
|
||||
|
||||
-- parts ready
|
||||
local message_content = {
|
||||
code_prompt_obj,
|
||||
}
|
||||
|
||||
if opts.selected_code_content then
|
||||
local selected_code_obj = {
|
||||
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.selected_code_content),
|
||||
}
|
||||
|
||||
table.insert(message_content, selected_code_obj)
|
||||
end
|
||||
|
||||
if Clipboard.support_paste_image() and opts.image_path then
|
||||
local image_data = {
|
||||
inline_data = {
|
||||
mime_type = "image/png",
|
||||
data = Clipboard.get_base64_content(opts.image_path),
|
||||
},
|
||||
}
|
||||
|
||||
table.insert(message_content, image_data)
|
||||
table.insert(message_content, image_data)
|
||||
end
|
||||
end
|
||||
|
||||
-- insert a part into parts
|
||||
table.insert(message_content, {
|
||||
text = string.format("<question>%s</question>", opts.question),
|
||||
text = opts.user_prompt,
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -50,7 +33,7 @@ M.parse_message = function(opts)
|
||||
role = "user",
|
||||
parts = {
|
||||
{
|
||||
text = opts.system_prompt .. "\n" .. opts.base_prompt,
|
||||
text = opts.system_prompt,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,13 +9,9 @@ local Dressing = require("avante.ui.dressing")
|
||||
---@field on_complete AvanteCompleteParser
|
||||
---
|
||||
---@class AvantePromptOptions: table<[string], string>
|
||||
---@field base_prompt AvanteBasePrompt
|
||||
---@field system_prompt AvanteSystemPrompt
|
||||
---@field question string
|
||||
---@field image_path? string
|
||||
---@field code_lang string
|
||||
---@field code_content string
|
||||
---@field selected_code_content? string
|
||||
---@field system_prompt string
|
||||
---@field user_prompt string
|
||||
---@field image_paths? string[]
|
||||
---
|
||||
---@class AvanteBaseMessage
|
||||
---@field role "user" | "system"
|
||||
@@ -34,7 +30,7 @@ local Dressing = require("avante.ui.dressing")
|
||||
---@alias AvanteMessageParser fun(opts: AvantePromptOptions): AvanteChatMessage[]
|
||||
---
|
||||
---@class AvanteCurlOutput: {url: string, proxy: string, insecure: boolean, body: table<string, any> | string, headers: table<string, string>}
|
||||
---@alias AvanteCurlArgsParser fun(opts: AvanteProvider, code_opts: AvantePromptOptions): AvanteCurlOutput
|
||||
---@alias AvanteCurlArgsParser fun(opts: AvanteProvider | AvanteProviderFunctor, code_opts: AvantePromptOptions): AvanteCurlOutput
|
||||
---
|
||||
---@class ResponseParser
|
||||
---@field on_chunk fun(chunk: string): any
|
||||
@@ -337,7 +333,7 @@ M.commands = function()
|
||||
})
|
||||
end
|
||||
|
||||
---@param opts AvanteProvider | AvanteSupportedProvider
|
||||
---@param opts AvanteProvider | AvanteSupportedProvider | AvanteProviderFunctor
|
||||
---@return AvanteDefaultBaseProvider, table<string, any>
|
||||
M.parse_config = function(opts)
|
||||
---@type AvanteDefaultBaseProvider
|
||||
|
||||
@@ -27,53 +27,22 @@ local M = {}
|
||||
|
||||
M.api_key_name = "OPENAI_API_KEY"
|
||||
|
||||
---@param opts AvantePromptOptions
|
||||
M.get_user_message = function(opts)
|
||||
local user_prompt = opts.base_prompt
|
||||
.. "\n\nCODE:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.code_content
|
||||
.. "\n```"
|
||||
.. "\n\nQUESTION:\n"
|
||||
.. opts.question
|
||||
|
||||
if opts.selected_code_content ~= nil then
|
||||
user_prompt = opts.base_prompt
|
||||
.. "\n\nCODE CONTEXT:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.code_content
|
||||
.. "\n```"
|
||||
.. "\n\nCODE:\n"
|
||||
.. "```"
|
||||
.. opts.code_lang
|
||||
.. "\n"
|
||||
.. opts.selected_code_content
|
||||
.. "\n```"
|
||||
.. "\n\nQUESTION:\n"
|
||||
.. opts.question
|
||||
end
|
||||
|
||||
return user_prompt
|
||||
end
|
||||
|
||||
M.parse_message = function(opts)
|
||||
---@type string | OpenAIMessage[]
|
||||
local user_content
|
||||
if Config.behaviour.support_paste_from_clipboard and opts.image_path then
|
||||
if Config.behaviour.support_paste_from_clipboard and opts.image_paths then
|
||||
user_content = {}
|
||||
table.insert(user_content, {
|
||||
type = "image_url",
|
||||
image_url = {
|
||||
url = "data:image/png;base64," .. Clipboard.get_base64_content(opts.image_path),
|
||||
},
|
||||
})
|
||||
table.insert(user_content, { type = "text", text = M.get_user_message(opts) })
|
||||
for _, image_path in ipairs(opts.image_paths) do
|
||||
table.insert(user_content, {
|
||||
type = "image_url",
|
||||
image_url = {
|
||||
url = "data:image/png;base64," .. Clipboard.get_base64_content(image_path),
|
||||
},
|
||||
})
|
||||
end
|
||||
table.insert(user_content, { type = "text", text = opts.user_prompt })
|
||||
else
|
||||
user_content = M.get_user_message(opts)
|
||||
user_content = opts.user_prompt
|
||||
end
|
||||
|
||||
return {
|
||||
|
||||
@@ -277,7 +277,6 @@ function Selection:create_editing_input()
|
||||
local code_wind = api.nvim_get_current_win()
|
||||
self.cursor_pos = api.nvim_win_get_cursor(code_wind)
|
||||
self.code_winid = code_wind
|
||||
local filetype = api.nvim_get_option_value("filetype", { buf = code_bufnr })
|
||||
local code_lines = api.nvim_buf_get_lines(code_bufnr, 0, -1, false)
|
||||
local code_content = table.concat(code_lines, "\n")
|
||||
|
||||
@@ -408,7 +407,14 @@ function Selection:create_editing_input()
|
||||
end, 0)
|
||||
end
|
||||
|
||||
Llm.stream(input, filetype, code_content, self.selection.content, "editing", on_chunk, on_complete)
|
||||
Llm.stream({
|
||||
file_content = code_content,
|
||||
selected_code = self.selection.content,
|
||||
instructions = input,
|
||||
mode = "editing",
|
||||
on_chunk = on_chunk,
|
||||
on_complete = on_complete,
|
||||
})
|
||||
end
|
||||
|
||||
vim.keymap.set("i", Config.mappings.submit.insert, submit_input, { buffer = bufnr, noremap = true, silent = true })
|
||||
|
||||
@@ -134,53 +134,130 @@ function Sidebar:toggle()
|
||||
end
|
||||
end
|
||||
|
||||
local function realign_line_numbers(code_lines, snippet)
|
||||
local snippet_lines = vim.split(snippet.content, "\n")
|
||||
local snippet_lines_count = #snippet_lines
|
||||
---@class AvanteReplacementResult
|
||||
---@field content string
|
||||
---@field is_searching boolean
|
||||
---@field is_replacing boolean
|
||||
---@field last_search_tag_start_line integer
|
||||
---@field last_replace_tag_start_line integer
|
||||
|
||||
local start_line = snippet.range[1]
|
||||
---@param original_content string
|
||||
---@param result_content string
|
||||
---@param code_lang string
|
||||
---@return AvanteReplacementResult
|
||||
local function transform_result_content(original_content, result_content, code_lang)
|
||||
local transformed = ""
|
||||
local original_lines = vim.split(original_content, "\n")
|
||||
local result_lines = vim.split(result_content, "\n")
|
||||
|
||||
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
|
||||
local is_searching = false
|
||||
local is_replacing = false
|
||||
local last_search_tag_start_line = 0
|
||||
local last_replace_tag_start_line = 0
|
||||
|
||||
local trim_breakline_suffix = false
|
||||
|
||||
local i = 1
|
||||
while i <= #result_lines do
|
||||
if result_lines[i] == "<SEARCH>" then
|
||||
is_searching = true
|
||||
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] ~= "</SEARCH>" do
|
||||
search_end = search_end + 1
|
||||
end
|
||||
|
||||
if search_end > #result_lines then
|
||||
trim_breakline_suffix = false
|
||||
-- <SEARCH> tag is not closed, add remaining content and return
|
||||
transformed = transformed .. table.concat(result_lines, "\n", i)
|
||||
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
|
||||
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
|
||||
end
|
||||
if matched then
|
||||
correct_end = end_line + i
|
||||
break
|
||||
|
||||
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
|
||||
local match = true
|
||||
for k = 0, search_end - search_start - 1 do
|
||||
if
|
||||
Utils.remove_indentation(original_lines[j + k]) ~= Utils.remove_indentation(result_lines[search_start + k])
|
||||
then
|
||||
match = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if match then
|
||||
start_line = j
|
||||
end_line = j + (search_end - search_start) - 1
|
||||
break
|
||||
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"
|
||||
end
|
||||
transformed = transformed .. "```\n"
|
||||
end
|
||||
|
||||
i = replace_end + 1 -- Move to the line after </REPLACE>
|
||||
is_searching = false
|
||||
is_replacing = false
|
||||
else
|
||||
trim_breakline_suffix = true
|
||||
transformed = transformed .. result_lines[i] .. "\n"
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
if correct_start then
|
||||
snippet.range[1] = correct_start
|
||||
end
|
||||
return {
|
||||
content = trim_breakline_suffix and transformed:sub(1, -2) or transformed, -- Remove trailing newline
|
||||
is_searching = is_searching,
|
||||
is_replacing = is_replacing,
|
||||
last_search_tag_start_line = last_search_tag_start_line,
|
||||
last_replace_tag_start_line = last_replace_tag_start_line,
|
||||
}
|
||||
end
|
||||
|
||||
if correct_end then
|
||||
snippet.range[2] = correct_end
|
||||
end
|
||||
local searching_hint = "\n 🔍 Searching..."
|
||||
|
||||
return snippet
|
||||
---@param replacement AvanteReplacementResult
|
||||
---@return string
|
||||
local function generate_display_content(replacement)
|
||||
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
|
||||
|
||||
---@class AvanteCodeSnippet
|
||||
@@ -191,11 +268,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
|
||||
@@ -219,7 +294,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 = {}
|
||||
@@ -346,7 +420,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 snippets = extract_code_snippets(content, response)
|
||||
local snippets = extract_code_snippets(response)
|
||||
if current_cursor then
|
||||
if self.result and self.result.winid then
|
||||
local cursor_line = Utils.get_cursor_pos(self.result.winid)
|
||||
@@ -876,17 +950,6 @@ function Sidebar:update_content(content, opts)
|
||||
return self
|
||||
end
|
||||
|
||||
local function prepend_line_number(content, start_line)
|
||||
start_line = start_line or 1
|
||||
local lines = vim.split(content, "\n")
|
||||
local result = {}
|
||||
for i, line in ipairs(lines) do
|
||||
i = i + start_line - 1
|
||||
table.insert(result, "L" .. i .. ": " .. line)
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
-- Function to get current timestamp
|
||||
local function get_timestamp()
|
||||
return os.date("%Y-%m-%d %H:%M:%S")
|
||||
@@ -1079,12 +1142,11 @@ function Sidebar:create_input()
|
||||
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 = prepend_line_number(content)
|
||||
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
||||
|
||||
local selected_code_content_with_line_numbers = nil
|
||||
local selected_code_content = nil
|
||||
if self.code.selection ~= nil then
|
||||
selected_code_content_with_line_numbers =
|
||||
prepend_line_number(self.code.selection.content, self.code.selection.range.start.line)
|
||||
selected_code_content = self.code.selection.content
|
||||
end
|
||||
|
||||
if request:sub(1, 1) == "/" then
|
||||
@@ -1113,10 +1175,8 @@ function Sidebar:create_input()
|
||||
Utils.error("Invalid end line number", { once = true, title = "Avante" })
|
||||
return
|
||||
end
|
||||
selected_code_content_with_line_numbers = 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
|
||||
@@ -1129,24 +1189,30 @@ function Sidebar:create_input()
|
||||
end
|
||||
end
|
||||
|
||||
local full_response = ""
|
||||
|
||||
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
||||
local original_response = ""
|
||||
local transformed_response = ""
|
||||
local displayed_response = ""
|
||||
|
||||
local is_first_chunk = true
|
||||
|
||||
---@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
|
||||
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
|
||||
displayed_response = cur_displayed_response
|
||||
self:update_content(content_prefix .. displayed_response, { stream = false, scroll = true })
|
||||
vim.schedule(function()
|
||||
vim.cmd("redraw")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@type AvanteCompleteParser
|
||||
@@ -1157,13 +1223,18 @@ function Sidebar:create_input()
|
||||
end
|
||||
|
||||
-- Execute when the stream request is actually completed
|
||||
self:update_content("\n\n🎉🎉🎉 **Generation complete!** Please review the code suggestions above.", {
|
||||
stream = true,
|
||||
scroll = true,
|
||||
callback = function()
|
||||
api.nvim_exec_autocmds("User", { pattern = VIEW_BUFFER_UPDATED_PATTERN })
|
||||
end,
|
||||
})
|
||||
self:update_content(
|
||||
content_prefix
|
||||
.. displayed_response
|
||||
.. "\n\n🎉🎉🎉 **Generation complete!** Please review the code suggestions above.",
|
||||
{
|
||||
stream = false,
|
||||
scroll = true,
|
||||
callback = function()
|
||||
api.nvim_exec_autocmds("User", { pattern = VIEW_BUFFER_UPDATED_PATTERN })
|
||||
end,
|
||||
}
|
||||
)
|
||||
|
||||
vim.defer_fn(function()
|
||||
if self.result and self.result.winid and api.nvim_win_is_valid(self.result.winid) then
|
||||
@@ -1177,20 +1248,20 @@ function Sidebar:create_input()
|
||||
provider = Config.provider,
|
||||
model = model,
|
||||
request = request,
|
||||
response = full_response,
|
||||
response = displayed_response,
|
||||
original_response = original_response,
|
||||
})
|
||||
History.save(self.code.bufnr, chat_history)
|
||||
end
|
||||
|
||||
Llm.stream(
|
||||
request,
|
||||
filetype,
|
||||
content_with_line_numbers,
|
||||
selected_code_content_with_line_numbers,
|
||||
"planning",
|
||||
on_chunk,
|
||||
on_complete
|
||||
)
|
||||
Llm.stream({
|
||||
file_content = content,
|
||||
selected_code = selected_code_content,
|
||||
instructions = request,
|
||||
mode = "planning",
|
||||
on_chunk = on_chunk,
|
||||
on_complete = on_complete,
|
||||
})
|
||||
|
||||
if Config.behaviour.auto_apply_diff_after_generation then
|
||||
self:apply(false)
|
||||
|
||||
@@ -479,4 +479,9 @@ function M.get_indentation(code)
|
||||
return code:match("^%s*") or ""
|
||||
end
|
||||
|
||||
--- remove indentation from code: spaces or tabs
|
||||
function M.remove_indentation(code)
|
||||
return code:gsub("^%s*", "")
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user