diff --git a/.chatgpt_config.yaml b/.chatgpt_config.yaml
index 3d14460..d0ad42b 100644
--- a/.chatgpt_config.yaml
+++ b/.chatgpt_config.yaml
@@ -16,6 +16,7 @@ enable_debug_commands: true
prompt_char_limit: 300000
enable_chunking: false
enable_step_by_step: true
+auto_lint: true
# New tool auto-accept config
tool_auto_accept:
diff --git a/lua/chatgpt_nvim/config.lua b/lua/chatgpt_nvim/config.lua
index 1dea16a..2bd541f 100644
--- a/lua/chatgpt_nvim/config.lua
+++ b/lua/chatgpt_nvim/config.lua
@@ -51,7 +51,7 @@ function M.load()
enable_chunking = false,
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,
tool_auto_accept = {
@@ -109,7 +109,7 @@ function M.load()
config.enable_step_by_step = result.enable_step_by_step
end
- -- New: load auto_lint if present
+ -- auto_lint controls whether we do LSP-based checks
if type(result.auto_lint) == "boolean" then
config.auto_lint = result.auto_lint
end
diff --git a/lua/chatgpt_nvim/tools/edit_file.lua b/lua/chatgpt_nvim/tools/edit_file.lua
index 9de8c5a..35fdb3f 100644
--- a/lua/chatgpt_nvim/tools/edit_file.lua
+++ b/lua/chatgpt_nvim/tools/edit_file.lua
@@ -1,5 +1,5 @@
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 = {}
@@ -25,15 +25,10 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
msg[#msg+1] = string.format("\n%s\n", 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"
- -- 2) Lint check if enabled
+ -- 2) If auto_lint => run robust LSP approach
if conf.auto_lint then
- local output, err = lint.lint_file(path)
- if output then
- 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
+ local diag_str = robust_lsp.lsp_check_file_content(path, new_content, 3000) -- 3s wait
+ msg[#msg+1] = "\n" .. diag_str
end
return table.concat(msg, "")
diff --git a/lua/chatgpt_nvim/tools/lint.lua b/lua/chatgpt_nvim/tools/lint.lua
deleted file mode 100644
index 8b1613b..0000000
--- a/lua/chatgpt_nvim/tools/lint.lua
+++ /dev/null
@@ -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
diff --git a/lua/chatgpt_nvim/tools/lsp_robust_diagnostics.lua b/lua/chatgpt_nvim/tools/lsp_robust_diagnostics.lua
new file mode 100644
index 0000000..70c723d
--- /dev/null
+++ b/lua/chatgpt_nvim/tools/lsp_robust_diagnostics.lua
@@ -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
diff --git a/lua/chatgpt_nvim/tools/replace_in_file.lua b/lua/chatgpt_nvim/tools/replace_in_file.lua
index 85b03b1..976f360 100644
--- a/lua/chatgpt_nvim/tools/replace_in_file.lua
+++ b/lua/chatgpt_nvim/tools/replace_in_file.lua
@@ -1,10 +1,9 @@
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 function search_and_replace(original, replacements)
- -- Basic approach: do a global plain text replace for each entry
local updated = original
for _, r in ipairs(replacements) do
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("\n%s\n", 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"
- -- 2) Lint check if enabled
if conf.auto_lint then
- local output, err = lint.lint_file(path)
- if output then
- 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
+ local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
+ msg[#msg+1] = "\n" .. diag_str
end
return table.concat(msg, "")