change-to-tools #2
@@ -16,6 +16,7 @@ enable_debug_commands: true
|
|||||||
prompt_char_limit: 300000
|
prompt_char_limit: 300000
|
||||||
enable_chunking: false
|
enable_chunking: false
|
||||||
enable_step_by_step: true
|
enable_step_by_step: true
|
||||||
|
auto_lint: true
|
||||||
|
|
||||||
# New tool auto-accept config
|
# New tool auto-accept config
|
||||||
tool_auto_accept:
|
tool_auto_accept:
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function M.load()
|
|||||||
enable_chunking = false,
|
enable_chunking = false,
|
||||||
enable_step_by_step = true,
|
enable_step_by_step = true,
|
||||||
|
|
||||||
-- If auto_lint is true, we'll run a quick linter after editing or replacing files.
|
-- If auto_lint is true, we only do LSP-based checks.
|
||||||
auto_lint = false,
|
auto_lint = false,
|
||||||
|
|
||||||
tool_auto_accept = {
|
tool_auto_accept = {
|
||||||
@@ -109,7 +109,7 @@ function M.load()
|
|||||||
config.enable_step_by_step = result.enable_step_by_step
|
config.enable_step_by_step = result.enable_step_by_step
|
||||||
end
|
end
|
||||||
|
|
||||||
-- New: load auto_lint if present
|
-- auto_lint controls whether we do LSP-based checks
|
||||||
if type(result.auto_lint) == "boolean" then
|
if type(result.auto_lint) == "boolean" then
|
||||||
config.auto_lint = result.auto_lint
|
config.auto_lint = result.auto_lint
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local handler = require("chatgpt_nvim.handler")
|
local handler = require("chatgpt_nvim.handler")
|
||||||
local lint = require("chatgpt_nvim.tools.lint")
|
local robust_lsp = require("chatgpt_nvim.tools.lsp_robust_diagnostics")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -25,15 +25,10 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, new_content)
|
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, new_content)
|
||||||
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
||||||
|
|
||||||
-- 2) Lint check if enabled
|
-- 2) If auto_lint => run robust LSP approach
|
||||||
if conf.auto_lint then
|
if conf.auto_lint then
|
||||||
local output, err = lint.lint_file(path)
|
local diag_str = robust_lsp.lsp_check_file_content(path, new_content, 3000) -- 3s wait
|
||||||
if output then
|
msg[#msg+1] = "\n" .. diag_str
|
||||||
msg[#msg+1] = "\n--- Lint Results ---\n"
|
|
||||||
msg[#msg+1] = output
|
|
||||||
else
|
|
||||||
msg[#msg+1] = "\n(Lint) " .. (err or "Could not lint this file.")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(msg, "")
|
return table.concat(msg, "")
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
-- A naive lint approach: if a file ends with .lua, run "luacheck" if available.
|
|
||||||
-- If .go, run "go build" or "go vet", etc. This is just an example; adapt to your needs.
|
|
||||||
-- Return nil if lint tool can't be determined or is not installed.
|
|
||||||
local function guess_linter_command(path)
|
|
||||||
if path:match("%.lua$") then
|
|
||||||
return "luacheck " .. vim.fn.shellescape(path)
|
|
||||||
elseif path:match("%.go$") then
|
|
||||||
-- We'll just do a quick "go build" or "go vet"
|
|
||||||
return "go vet " .. vim.fn.fnamemodify(path, ":h")
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Executes the linter command and returns the output or an error if it fails to run.
|
|
||||||
local function run_command(cmd)
|
|
||||||
local handle = io.popen(cmd)
|
|
||||||
if not handle then
|
|
||||||
return nil, ("Failed to run: %s"):format(cmd)
|
|
||||||
end
|
|
||||||
local output = handle:read("*a") or ""
|
|
||||||
handle:close()
|
|
||||||
return output, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.lint_file(path)
|
|
||||||
local lint_cmd = guess_linter_command(path)
|
|
||||||
if not lint_cmd then
|
|
||||||
return nil, "No known lint command for file: " .. path
|
|
||||||
end
|
|
||||||
|
|
||||||
local output, err = run_command(lint_cmd)
|
|
||||||
if not output then
|
|
||||||
return nil, err or ("Failed to lint: " .. path)
|
|
||||||
end
|
|
||||||
return output, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
167
lua/chatgpt_nvim/tools/lsp_robust_diagnostics.lua
Normal file
167
lua/chatgpt_nvim/tools/lsp_robust_diagnostics.lua
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
local api = vim.api
|
||||||
|
local lsp = vim.lsp
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function guess_filetype(path)
|
||||||
|
local ext = path:match("^.+%.([^./\\]+)$")
|
||||||
|
if not ext then return nil end
|
||||||
|
ext = ext:lower()
|
||||||
|
local map = {
|
||||||
|
lua = "lua",
|
||||||
|
go = "go",
|
||||||
|
rs = "rust",
|
||||||
|
js = "javascript",
|
||||||
|
jsx = "javascriptreact",
|
||||||
|
ts = "typescript",
|
||||||
|
tsx = "typescriptreact",
|
||||||
|
php = "php",
|
||||||
|
}
|
||||||
|
return map[ext]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_scratch_buffer(path, content)
|
||||||
|
local bufnr = api.nvim_create_buf(false, true)
|
||||||
|
if bufnr == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
api.nvim_buf_set_name(bufnr, path)
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for line in (content.."\n"):gmatch("(.-)\n") do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
|
api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
|
||||||
|
api.nvim_buf_set_option(bufnr, "swapfile", false)
|
||||||
|
return bufnr
|
||||||
|
end
|
||||||
|
|
||||||
|
local function attach_existing_lsp_client(bufnr, filetype)
|
||||||
|
local clients = lsp.get_active_clients()
|
||||||
|
if not clients or #clients == 0 then
|
||||||
|
return nil, "No active LSP clients"
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, client in ipairs(clients) do
|
||||||
|
local ft_conf = client.config.filetypes or {}
|
||||||
|
if vim.tbl_contains(ft_conf, filetype) then
|
||||||
|
if not client.attached_buffers[bufnr] then
|
||||||
|
vim.lsp.buf_attach_client(bufnr, client.id)
|
||||||
|
end
|
||||||
|
return client.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, ("No active LSP client supports filetype '%s'"):format(filetype)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function send_did_open(bufnr, client_id, path, filetype)
|
||||||
|
local client = lsp.get_client_by_id(client_id)
|
||||||
|
if not client then
|
||||||
|
return "Invalid client ID."
|
||||||
|
end
|
||||||
|
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
||||||
|
local uri = vim.uri_from_fname(path)
|
||||||
|
|
||||||
|
local didOpenParams = {
|
||||||
|
textDocument = {
|
||||||
|
uri = uri,
|
||||||
|
languageId = filetype,
|
||||||
|
version = 0,
|
||||||
|
text = text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.rpc.notify("textDocument/didOpen", didOpenParams)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function send_did_change(bufnr, client_id)
|
||||||
|
local client = lsp.get_client_by_id(client_id)
|
||||||
|
if not client then return end
|
||||||
|
|
||||||
|
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
||||||
|
local uri = vim.uri_from_bufnr(bufnr)
|
||||||
|
local version = 1
|
||||||
|
|
||||||
|
client.rpc.notify("textDocument/didChange", {
|
||||||
|
textDocument = {
|
||||||
|
uri = uri,
|
||||||
|
version = version,
|
||||||
|
},
|
||||||
|
contentChanges = {
|
||||||
|
{ text = text }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function wait_for_diagnostics(bufnr, timeout_ms)
|
||||||
|
local done = false
|
||||||
|
local result_diags = {}
|
||||||
|
|
||||||
|
local augrp = api.nvim_create_augroup("chatgpt_lsp_diag_"..bufnr, { clear = true })
|
||||||
|
api.nvim_create_autocmd("DiagnosticChanged", {
|
||||||
|
group = augrp,
|
||||||
|
callback = function(args)
|
||||||
|
if args.buf == bufnr then
|
||||||
|
local diags = vim.diagnostic.get(bufnr)
|
||||||
|
result_diags = diags
|
||||||
|
done = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
local waited = 0
|
||||||
|
local interval = 50
|
||||||
|
while not done and waited < timeout_ms do
|
||||||
|
vim.cmd(("sleep %d m"):format(interval))
|
||||||
|
waited = waited + interval
|
||||||
|
end
|
||||||
|
|
||||||
|
pcall(api.nvim_del_augroup_by_id, augrp)
|
||||||
|
return result_diags
|
||||||
|
end
|
||||||
|
|
||||||
|
local function diagnostics_to_string(diags)
|
||||||
|
if #diags == 0 then
|
||||||
|
return "No LSP diagnostics reported."
|
||||||
|
end
|
||||||
|
local lines = { "--- LSP Diagnostics ---" }
|
||||||
|
for _, d in ipairs(diags) do
|
||||||
|
local sev = vim.diagnostic.severity[d.severity] or d.severity
|
||||||
|
lines[#lines+1] = string.format("Line %d [%s]: %s", d.lnum + 1, sev, d.message)
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.lsp_check_file_content(path, new_content, timeout_ms)
|
||||||
|
local filetype = guess_filetype(path) or "plaintext"
|
||||||
|
|
||||||
|
local bufnr = create_scratch_buffer(path, new_content)
|
||||||
|
if not bufnr then
|
||||||
|
return "(LSP) Could not create scratch buffer."
|
||||||
|
end
|
||||||
|
|
||||||
|
local client_id, err = attach_existing_lsp_client(bufnr, filetype)
|
||||||
|
if not client_id then
|
||||||
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
return "(LSP) " .. (err or "No suitable LSP client.")
|
||||||
|
end
|
||||||
|
|
||||||
|
local err2 = send_did_open(bufnr, client_id, path, filetype)
|
||||||
|
if err2 then
|
||||||
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
return "(LSP) " .. err2
|
||||||
|
end
|
||||||
|
|
||||||
|
send_did_change(bufnr, client_id)
|
||||||
|
|
||||||
|
local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000)
|
||||||
|
local diag_str = diagnostics_to_string(diags)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
return diag_str
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
local handler = require("chatgpt_nvim.handler")
|
local handler = require("chatgpt_nvim.handler")
|
||||||
local lint = require("chatgpt_nvim.tools.lint")
|
local robust_lsp = require("chatgpt_nvim.tools.lsp_robust_diagnostics")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function search_and_replace(original, replacements)
|
local function search_and_replace(original, replacements)
|
||||||
-- Basic approach: do a global plain text replace for each entry
|
|
||||||
local updated = original
|
local updated = original
|
||||||
for _, r in ipairs(replacements) do
|
for _, r in ipairs(replacements) do
|
||||||
local search_str = r.search or ""
|
local search_str = r.search or ""
|
||||||
@@ -41,15 +40,9 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, updated_data)
|
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, updated_data)
|
||||||
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
||||||
|
|
||||||
-- 2) Lint check if enabled
|
|
||||||
if conf.auto_lint then
|
if conf.auto_lint then
|
||||||
local output, err = lint.lint_file(path)
|
local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
|
||||||
if output then
|
msg[#msg+1] = "\n" .. diag_str
|
||||||
msg[#msg+1] = "\n--- Lint Results ---\n"
|
|
||||||
msg[#msg+1] = output
|
|
||||||
else
|
|
||||||
msg[#msg+1] = "\n(Lint) " .. (err or "Could not lint this file.")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(msg, "")
|
return table.concat(msg, "")
|
||||||
|
|||||||
Reference in New Issue
Block a user