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) -- Create a unique buffer name so we never clash with an existing one local scratch_name = string.format("chatgpt-scratch://%s#%d", path, math.random(100000, 999999)) local bufnr = api.nvim_create_buf(false, true) if bufnr == 0 then return nil end -- Assign the unique name to the buffer api.nvim_buf_set_name(bufnr, scratch_name) -- Convert content string to lines 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) -- Mark it as a scratch buffer 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") -- Even though the buffer name is unique, the LSP server sees this file as if at 'path' 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, path) 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_fname(path) local version = 1 client.rpc.notify("textDocument/didChange", { textDocument = { uri = uri, version = version, }, contentChanges = { { text = text } } }) end -- Use vim.wait to allow the event loop to process LSP diagnostics 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 }) vim.wait(timeout_ms, function() return done end, 50) 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 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 api.nvim_buf_delete(bufnr, { force = true }) return "(LSP) " .. err2 end -- Optionally do a didChange with consistent URI send_did_change(bufnr, client_id, path) local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000) local diag_str = diagnostics_to_string(diags) api.nvim_buf_delete(bufnr, { force = true }) return diag_str end return M