feat: automatically obtain diagnostics after finishing editing the file (#2018)
This commit is contained in:
@@ -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
|
--- 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
|
if is_replace_func_call and path then
|
||||||
local lines = Utils.read_file_from_buf_or_disk(path)
|
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_name = "view"
|
||||||
local view_tool_input = { path = path }
|
local view_tool_input = { path = path }
|
||||||
if is_str_replace_editor_func_call then
|
if is_str_replace_editor_func_call then
|
||||||
view_tool_name = "str_replace_editor"
|
view_tool_name = "str_replace_editor"
|
||||||
view_tool_input = { command = "view", path = path }
|
view_tool_input = { command = "view", path = path }
|
||||||
end
|
end
|
||||||
|
local diagnostics = Utils.lsp.get_diagnostics_from_filepath(path)
|
||||||
history_messages = vim.list_extend(history_messages, {
|
history_messages = vim.list_extend(history_messages, {
|
||||||
HistoryMessage:new({
|
HistoryMessage:new({
|
||||||
role = "assistant",
|
role = "assistant",
|
||||||
@@ -194,7 +196,7 @@ function M.generate_prompts(opts)
|
|||||||
content = {
|
content = {
|
||||||
{
|
{
|
||||||
type = "tool_use",
|
type = "tool_use",
|
||||||
id = tool_use_id,
|
id = view_tool_use_id,
|
||||||
name = view_tool_name,
|
name = view_tool_name,
|
||||||
input = view_tool_input,
|
input = view_tool_input,
|
||||||
},
|
},
|
||||||
@@ -207,7 +209,7 @@ function M.generate_prompts(opts)
|
|||||||
content = {
|
content = {
|
||||||
{
|
{
|
||||||
type = "tool_result",
|
type = "tool_result",
|
||||||
tool_use_id = tool_use_id,
|
tool_use_id = view_tool_use_id,
|
||||||
content = table.concat(lines or {}, "\n"),
|
content = table.concat(lines or {}, "\n"),
|
||||||
is_error = false,
|
is_error = false,
|
||||||
},
|
},
|
||||||
@@ -215,6 +217,41 @@ function M.generate_prompts(opts)
|
|||||||
}, {
|
}, {
|
||||||
is_dummy = true,
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
51
lua/avante/llm_tools/get_diagnostics.lua
Normal file
51
lua/avante/llm_tools/get_diagnostics.lua
Normal file
@@ -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
|
||||||
@@ -125,7 +125,7 @@ function M.get_bufnr(abs_path)
|
|||||||
if not sidebar then return 0, "Avante sidebar not found" end
|
if not sidebar then return 0, "Avante sidebar not found" end
|
||||||
local current_winid = vim.api.nvim_get_current_win()
|
local current_winid = vim.api.nvim_get_current_win()
|
||||||
vim.api.nvim_set_current_win(sidebar.code.winid)
|
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)
|
vim.api.nvim_set_current_win(current_winid)
|
||||||
return bufnr, nil
|
return bufnr, nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -989,6 +989,7 @@ M._tools = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
require("avante.llm_tools.get_diagnostics"),
|
||||||
require("avante.llm_tools.bash"),
|
require("avante.llm_tools.bash"),
|
||||||
{
|
{
|
||||||
name = "web_search",
|
name = "web_search",
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ function Selection:submit_input(input)
|
|||||||
input = mentions.new_content
|
input = mentions.new_content
|
||||||
local project_context = mentions.enable_project_context and RepoMap.get_repo_map(file_ext) or nil
|
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
|
---@type AvanteSelectedCode | nil
|
||||||
local selected_code = nil
|
local selected_code = nil
|
||||||
|
|||||||
@@ -920,7 +920,7 @@ function Sidebar:apply(current_cursor)
|
|||||||
api.nvim_set_current_win(self.code.winid)
|
api.nvim_set_current_win(self.code.winid)
|
||||||
for filepath, snippets in pairs(selected_snippets_map) do
|
for filepath, snippets in pairs(selected_snippets_map) do
|
||||||
if Config.behaviour.minimize_diff then snippets = self:minimize_snippets(filepath, snippets) end
|
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)
|
local path_ = PPath:new(Utils.is_win() and filepath:gsub("/", "\\") or filepath)
|
||||||
path_:parent():mkdir({ parents = true, exists_ok = true })
|
path_:parent():mkdir({ parents = true, exists_ok = true })
|
||||||
insert_conflict_contents(bufnr, snippets)
|
insert_conflict_contents(bufnr, snippets)
|
||||||
@@ -2181,9 +2181,9 @@ function Sidebar:get_generate_prompts_options(request, cb)
|
|||||||
local diagnostics = nil
|
local diagnostics = nil
|
||||||
if mentions.enable_diagnostics then
|
if mentions.enable_diagnostics then
|
||||||
if self.code ~= nil and self.code.bufnr ~= nil and self.code.selection ~= nil 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
|
else
|
||||||
diagnostics = Utils.get_diagnostics(self.code.bufnr)
|
diagnostics = Utils.lsp.get_diagnostics(self.code.bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ L5: pass
|
|||||||
|
|
||||||
local history_messages = vim.iter(llm_messages):map(function(msg) return HistoryMessage:new(msg) end):totable()
|
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({
|
Llm.stream({
|
||||||
provider = provider,
|
provider = provider,
|
||||||
|
|||||||
@@ -933,41 +933,22 @@ function M.get_mentions()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param filepath string
|
---@param path string
|
||||||
---@return integer|nil bufnr
|
---@param set_current_buf? boolean
|
||||||
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
|
|
||||||
---@return integer bufnr
|
---@return integer bufnr
|
||||||
function M.get_or_create_buffer_with_filepath(filepath)
|
function M.open_buffer(path, set_current_buf)
|
||||||
-- Check if a buffer with this filepath already exists
|
if set_current_buf == nil then set_current_buf = true end
|
||||||
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
|
|
||||||
|
|
||||||
-- Create a new buffer without setting its name
|
path = vim.fn.fnamemodify(path, ":p")
|
||||||
local buf = api.nvim_create_buf(true, false)
|
|
||||||
|
|
||||||
-- Set the buffer options
|
local bufnr = vim.fn.bufnr(path, true)
|
||||||
api.nvim_set_option_value("buftype", "", { buf = buf })
|
vim.fn.bufload(bufnr)
|
||||||
|
|
||||||
-- Set the current buffer to the new buffer
|
if set_current_buf then vim.api.nvim_set_current_buf(bufnr) end
|
||||||
api.nvim_set_current_buf(buf)
|
|
||||||
|
|
||||||
-- Use the edit command to load the file content and set the buffer name
|
vim.cmd("filetype detect")
|
||||||
vim.cmd("edit " .. fn.fnameescape(filepath))
|
|
||||||
|
|
||||||
return buf
|
return bufnr
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param old_lines avante.ui.Line[]
|
---@param old_lines avante.ui.Line[]
|
||||||
@@ -1060,61 +1041,6 @@ function M.update_buffer_lines(ns_id, bufnr, old_lines, new_lines)
|
|||||||
end
|
end
|
||||||
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)
|
function M.uniform_path(path)
|
||||||
if type(path) ~= "string" then path = tostring(path) end
|
if type(path) ~= "string" then path = tostring(path) end
|
||||||
if not M.file.is_in_cwd(path) then return path end
|
if not M.file.is_in_cwd(path) then return path end
|
||||||
|
|||||||
@@ -117,4 +117,67 @@ function M.read_definitions(bufnr, symbol_name, show_line_numbers, on_complete)
|
|||||||
end)
|
end)
|
||||||
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
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user