diff --git a/lua/avante/llm.lua b/lua/avante/llm.lua index cd494ea..0f08b29 100644 --- a/lua/avante/llm.lua +++ b/lua/avante/llm.lua @@ -175,13 +175,15 @@ function M.generate_prompts(opts) --- For models like gpt-4o, the input parameter of replace_in_file is treated as the latest file content, so here we need to insert a fake view tool call to ensure it uses the latest file content if is_replace_func_call and path then local lines = Utils.read_file_from_buf_or_disk(path) - local tool_use_id = Utils.uuid() + local get_diagnostics_tool_use_id = Utils.uuid() + local view_tool_use_id = Utils.uuid() local view_tool_name = "view" local view_tool_input = { path = path } if is_str_replace_editor_func_call then view_tool_name = "str_replace_editor" view_tool_input = { command = "view", path = path } end + local diagnostics = Utils.lsp.get_diagnostics_from_filepath(path) history_messages = vim.list_extend(history_messages, { HistoryMessage:new({ role = "assistant", @@ -194,7 +196,7 @@ function M.generate_prompts(opts) content = { { type = "tool_use", - id = tool_use_id, + id = view_tool_use_id, name = view_tool_name, input = view_tool_input, }, @@ -207,7 +209,7 @@ function M.generate_prompts(opts) content = { { type = "tool_result", - tool_use_id = tool_use_id, + tool_use_id = view_tool_use_id, content = table.concat(lines or {}, "\n"), is_error = false, }, @@ -215,6 +217,41 @@ function M.generate_prompts(opts) }, { is_dummy = true, }), + HistoryMessage:new({ + role = "assistant", + content = string.format( + "The file %s has been modified, let me check if there are any errors in the changes.", + path + ), + }, { + is_dummy = true, + }), + HistoryMessage:new({ + role = "assistant", + content = { + { + type = "tool_use", + id = get_diagnostics_tool_use_id, + name = "get_diagnostics", + input = { path = path }, + }, + }, + }, { + is_dummy = true, + }), + HistoryMessage:new({ + role = "user", + content = { + { + type = "tool_result", + tool_use_id = get_diagnostics_tool_use_id, + content = vim.json.encode(diagnostics), + is_error = false, + }, + }, + }, { + is_dummy = true, + }), }) end end diff --git a/lua/avante/llm_tools/get_diagnostics.lua b/lua/avante/llm_tools/get_diagnostics.lua new file mode 100644 index 0000000..4d5d6de --- /dev/null +++ b/lua/avante/llm_tools/get_diagnostics.lua @@ -0,0 +1,51 @@ +local Base = require("avante.llm_tools.base") +local Helpers = require("avante.llm_tools.helpers") +local Utils = require("avante.utils") + +---@class AvanteLLMTool +local M = setmetatable({}, Base) + +M.name = "get_diagnostics" + +M.description = "Get diagnostics from a specific file" + +---@type AvanteLLMToolParam +M.param = { + type = "table", + fields = { + { + name = "path", + description = "The path to the file in the current project scope", + type = "string", + }, + }, +} + +---@type AvanteLLMToolReturn[] +M.returns = { + { + name = "diagnostics", + description = "The diagnostics for the file", + type = "string", + }, + { + name = "error", + description = "Error message if the replacement failed", + type = "string", + optional = true, + }, +} + +---@type AvanteLLMToolFunc<{ path: string, diff: string }> +function M.func(opts, on_log, on_complete, session_ctx) + if not opts.path then return false, "pathf are required" end + if on_log then on_log("path: " .. opts.path) end + local abs_path = Helpers.get_abs_path(opts.path) + if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end + if not on_complete then return false, "on_complete is required" end + local diagnostics = Utils.lsp.get_diagnostics_from_filepath(abs_path) + local jsn_str = vim.json.encode(diagnostics) + on_complete(true, jsn_str) +end + +return M diff --git a/lua/avante/llm_tools/helpers.lua b/lua/avante/llm_tools/helpers.lua index 8e4e214..47087cd 100644 --- a/lua/avante/llm_tools/helpers.lua +++ b/lua/avante/llm_tools/helpers.lua @@ -125,7 +125,7 @@ function M.get_bufnr(abs_path) if not sidebar then return 0, "Avante sidebar not found" end local current_winid = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(sidebar.code.winid) - local bufnr = Utils.get_or_create_buffer_with_filepath(abs_path) + local bufnr = Utils.open_buffer(abs_path) vim.api.nvim_set_current_win(current_winid) return bufnr, nil end diff --git a/lua/avante/llm_tools/init.lua b/lua/avante/llm_tools/init.lua index 80ec481..6adca75 100644 --- a/lua/avante/llm_tools/init.lua +++ b/lua/avante/llm_tools/init.lua @@ -989,6 +989,7 @@ M._tools = { }, }, }, + require("avante.llm_tools.get_diagnostics"), require("avante.llm_tools.bash"), { name = "web_search", diff --git a/lua/avante/selection.lua b/lua/avante/selection.lua index 4405fa6..4dc8f10 100644 --- a/lua/avante/selection.lua +++ b/lua/avante/selection.lua @@ -190,7 +190,7 @@ function Selection:submit_input(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) + local diagnostics = Utils.lsp.get_current_selection_diagnostics(self.code_bufnr, self.selection) ---@type AvanteSelectedCode | nil local selected_code = nil diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 78033ec..72b4029 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -920,7 +920,7 @@ function Sidebar:apply(current_cursor) api.nvim_set_current_win(self.code.winid) for filepath, snippets in pairs(selected_snippets_map) do if Config.behaviour.minimize_diff then snippets = self:minimize_snippets(filepath, snippets) end - local bufnr = Utils.get_or_create_buffer_with_filepath(filepath) + local bufnr = Utils.open_buffer(filepath) local path_ = PPath:new(Utils.is_win() and filepath:gsub("/", "\\") or filepath) path_:parent():mkdir({ parents = true, exists_ok = true }) insert_conflict_contents(bufnr, snippets) @@ -2181,9 +2181,9 @@ function Sidebar:get_generate_prompts_options(request, cb) local diagnostics = nil if mentions.enable_diagnostics then if self.code ~= nil and self.code.bufnr ~= nil and self.code.selection ~= nil then - diagnostics = Utils.get_current_selection_diagnostics(self.code.bufnr, self.code.selection) + diagnostics = Utils.lsp.get_current_selection_diagnostics(self.code.bufnr, self.code.selection) else - diagnostics = Utils.get_diagnostics(self.code.bufnr) + diagnostics = Utils.lsp.get_diagnostics(self.code.bufnr) end end diff --git a/lua/avante/suggestion.lua b/lua/avante/suggestion.lua index e7c864c..e0b6d31 100644 --- a/lua/avante/suggestion.lua +++ b/lua/avante/suggestion.lua @@ -135,7 +135,7 @@ L5: pass local history_messages = vim.iter(llm_messages):map(function(msg) return HistoryMessage:new(msg) end):totable() - local diagnostics = Utils.get_diagnostics(bufnr) + local diagnostics = Utils.lsp.get_diagnostics(bufnr) Llm.stream({ provider = provider, diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index 90f11f4..aec68c4 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -933,41 +933,22 @@ function M.get_mentions() } end ----@param filepath string ----@return integer|nil bufnr -local function get_opened_buffer_by_filepath(filepath) - local project_root = M.get_project_root() - local absolute_path = M.join_paths(project_root, filepath) - for _, buf in ipairs(api.nvim_list_bufs()) do - if M.join_paths(project_root, fn.bufname(buf)) == absolute_path then return buf end - end - return nil -end - ----@param filepath string +---@param path string +---@param set_current_buf? boolean ---@return integer bufnr -function M.get_or_create_buffer_with_filepath(filepath) - -- Check if a buffer with this filepath already exists - local existing_buf = get_opened_buffer_by_filepath(filepath) - if existing_buf then - -- goto this buffer - api.nvim_set_current_buf(existing_buf) - return existing_buf - end +function M.open_buffer(path, set_current_buf) + if set_current_buf == nil then set_current_buf = true end - -- Create a new buffer without setting its name - local buf = api.nvim_create_buf(true, false) + path = vim.fn.fnamemodify(path, ":p") - -- Set the buffer options - api.nvim_set_option_value("buftype", "", { buf = buf }) + local bufnr = vim.fn.bufnr(path, true) + vim.fn.bufload(bufnr) - -- Set the current buffer to the new buffer - api.nvim_set_current_buf(buf) + if set_current_buf then vim.api.nvim_set_current_buf(bufnr) end - -- Use the edit command to load the file content and set the buffer name - vim.cmd("edit " .. fn.fnameescape(filepath)) + vim.cmd("filetype detect") - return buf + return bufnr end ---@param old_lines avante.ui.Line[] @@ -1060,61 +1041,6 @@ function M.update_buffer_lines(ns_id, bufnr, old_lines, new_lines) end end -local severity = { - [1] = "ERROR", - [2] = "WARNING", - [3] = "INFORMATION", - [4] = "HINT", -} - ----@class AvanteDiagnostic ----@field content string ----@field start_line number ----@field end_line number ----@field severity string ----@field source string - ----@param bufnr integer ----@return AvanteDiagnostic[] -function M.get_diagnostics(bufnr) - if bufnr == nil then bufnr = api.nvim_get_current_buf() end - local diagnositcs = ---@type vim.Diagnostic[] - vim.diagnostic.get(bufnr, { - severity = { - vim.diagnostic.severity.ERROR, - vim.diagnostic.severity.WARN, - vim.diagnostic.severity.INFO, - vim.diagnostic.severity.HINT, - }, - }) - return vim - .iter(diagnositcs) - :map(function(diagnostic) - local d = { - content = diagnostic.message, - start_line = diagnostic.lnum + 1, - end_line = diagnostic.end_lnum and diagnostic.end_lnum + 1 or diagnostic.lnum + 1, - severity = severity[diagnostic.severity], - source = diagnostic.source, - } - return d - end) - :totable() -end - ----@param bufnr integer ----@param selection avante.SelectionResult -function M.get_current_selection_diagnostics(bufnr, selection) - local diagnostics = M.get_diagnostics(bufnr) - local selection_diagnostics = {} - for _, diagnostic in ipairs(diagnostics) do - if selection.range.start.lnum <= diagnostic.start_line and selection.range.finish.lnum >= diagnostic.end_line then - table.insert(selection_diagnostics, diagnostic) - end - end - return selection_diagnostics -end - function M.uniform_path(path) if type(path) ~= "string" then path = tostring(path) end if not M.file.is_in_cwd(path) then return path end diff --git a/lua/avante/utils/lsp.lua b/lua/avante/utils/lsp.lua index c3600ee..085ada7 100644 --- a/lua/avante/utils/lsp.lua +++ b/lua/avante/utils/lsp.lua @@ -117,4 +117,67 @@ function M.read_definitions(bufnr, symbol_name, show_line_numbers, on_complete) end) end +local severity = { + [1] = "ERROR", + [2] = "WARNING", + [3] = "INFORMATION", + [4] = "HINT", +} + +---@class AvanteDiagnostic +---@field content string +---@field start_line number +---@field end_line number +---@field severity string +---@field source string + +---@param bufnr integer +---@return AvanteDiagnostic[] +function M.get_diagnostics(bufnr) + if bufnr == nil then bufnr = vim.api.nvim_get_current_buf() end + local diagnositcs = ---@type vim.Diagnostic[] + vim.diagnostic.get(bufnr, { + severity = { + vim.diagnostic.severity.ERROR, + vim.diagnostic.severity.WARN, + vim.diagnostic.severity.INFO, + vim.diagnostic.severity.HINT, + }, + }) + return vim + .iter(diagnositcs) + :map(function(diagnostic) + local d = { + content = diagnostic.message, + start_line = diagnostic.lnum + 1, + end_line = diagnostic.end_lnum and diagnostic.end_lnum + 1 or diagnostic.lnum + 1, + severity = severity[diagnostic.severity], + source = diagnostic.source, + } + return d + end) + :totable() +end + +---@param filepath string +---@return AvanteDiagnostic[] +function M.get_diagnostics_from_filepath(filepath) + local Utils = require("avante.utils") + local bufnr = Utils.open_buffer(filepath, false) + return M.get_diagnostics(bufnr) +end + +---@param bufnr integer +---@param selection avante.SelectionResult +function M.get_current_selection_diagnostics(bufnr, selection) + local diagnostics = M.get_diagnostics(bufnr) + local selection_diagnostics = {} + for _, diagnostic in ipairs(diagnostics) do + if selection.range.start.lnum <= diagnostic.start_line and selection.range.finish.lnum >= diagnostic.end_line then + table.insert(selection_diagnostics, diagnostic) + end + end + return selection_diagnostics +end + return M