160 lines
4.4 KiB
TypeScript
160 lines
4.4 KiB
TypeScript
import * as vscode from "vscode";
|
|
import type { SfClient } from "./sf-client.js";
|
|
|
|
/**
|
|
* Integrates with VS Code's diagnostic system:
|
|
* - Reads diagnostics (errors/warnings) from the Problems panel and sends them to the agent
|
|
* - Provides a DiagnosticCollection for the agent to surface its own findings
|
|
*/
|
|
export class SfDiagnosticBridge implements vscode.Disposable {
|
|
private readonly collection: vscode.DiagnosticCollection;
|
|
private disposables: vscode.Disposable[] = [];
|
|
|
|
constructor(private readonly client: SfClient) {
|
|
this.collection = vscode.languages.createDiagnosticCollection("sf");
|
|
this.disposables.push(this.collection);
|
|
}
|
|
|
|
/**
|
|
* Read all diagnostics for the active file and send them to the agent
|
|
* as a "fix these problems" prompt.
|
|
*/
|
|
async fixProblemsInFile(): Promise<void> {
|
|
const editor = vscode.window.activeTextEditor;
|
|
if (!editor) {
|
|
vscode.window.showWarningMessage("No active file to fix.");
|
|
return;
|
|
}
|
|
|
|
const uri = editor.document.uri;
|
|
const diagnostics = vscode.languages.getDiagnostics(uri);
|
|
|
|
if (diagnostics.length === 0) {
|
|
vscode.window.showInformationMessage("No problems found in this file.");
|
|
return;
|
|
}
|
|
|
|
const fileName = vscode.workspace.asRelativePath(uri);
|
|
const problemText = formatDiagnostics(fileName, diagnostics);
|
|
|
|
const prompt = [
|
|
`Fix the following problems in \`${fileName}\`:`,
|
|
"",
|
|
problemText,
|
|
"",
|
|
"Fix all of these issues. Show me the changes.",
|
|
].join("\n");
|
|
|
|
await this.client.sendPrompt(prompt);
|
|
}
|
|
|
|
/**
|
|
* Read all diagnostics across the workspace (errors only) and send
|
|
* them to the agent as a "fix all errors" prompt.
|
|
*/
|
|
async fixAllProblems(): Promise<void> {
|
|
const allDiagnostics = vscode.languages.getDiagnostics();
|
|
const errorFiles: { fileName: string; diagnostics: vscode.Diagnostic[] }[] =
|
|
[];
|
|
|
|
for (const [uri, diagnostics] of allDiagnostics) {
|
|
// Only include errors and warnings, skip hints/info
|
|
const significant = diagnostics.filter(
|
|
(d) =>
|
|
d.severity === vscode.DiagnosticSeverity.Error ||
|
|
d.severity === vscode.DiagnosticSeverity.Warning,
|
|
);
|
|
if (significant.length > 0) {
|
|
errorFiles.push({
|
|
fileName: vscode.workspace.asRelativePath(uri),
|
|
diagnostics: significant,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (errorFiles.length === 0) {
|
|
vscode.window.showInformationMessage(
|
|
"No errors or warnings found in the workspace.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Cap at 20 files to avoid overwhelming the agent
|
|
const capped = errorFiles.slice(0, 20);
|
|
const totalProblems = capped.reduce(
|
|
(sum, f) => sum + f.diagnostics.length,
|
|
0,
|
|
);
|
|
|
|
const sections = capped.map((f) =>
|
|
formatDiagnostics(f.fileName, f.diagnostics),
|
|
);
|
|
|
|
const prompt = [
|
|
`Fix the following ${totalProblems} problems across ${capped.length} file${capped.length > 1 ? "s" : ""}:`,
|
|
"",
|
|
...sections,
|
|
"",
|
|
"Fix all of these issues.",
|
|
].join("\n");
|
|
|
|
await this.client.sendPrompt(prompt);
|
|
}
|
|
|
|
/**
|
|
* Add a SF diagnostic (agent finding) to a file.
|
|
* Can be used to surface agent review findings in the Problems panel.
|
|
*/
|
|
addFinding(
|
|
uri: vscode.Uri,
|
|
range: vscode.Range,
|
|
message: string,
|
|
severity: vscode.DiagnosticSeverity = vscode.DiagnosticSeverity.Warning,
|
|
): void {
|
|
const existing = this.collection.get(uri) ?? [];
|
|
const diagnostic = new vscode.Diagnostic(range, message, severity);
|
|
diagnostic.source = "SF Agent";
|
|
this.collection.set(uri, [...existing, diagnostic]);
|
|
}
|
|
|
|
/** Clear all SF diagnostics */
|
|
clearFindings(): void {
|
|
this.collection.clear();
|
|
}
|
|
|
|
dispose(): void {
|
|
for (const d of this.disposables) {
|
|
d.dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
function formatDiagnostics(
|
|
fileName: string,
|
|
diagnostics: vscode.Diagnostic[],
|
|
): string {
|
|
const lines = [`**${fileName}**`];
|
|
for (const d of diagnostics) {
|
|
const severity = severityLabel(d.severity);
|
|
const line = d.range.start.line + 1;
|
|
const col = d.range.start.character + 1;
|
|
const source = d.source ? ` [${d.source}]` : "";
|
|
lines.push(` - ${severity} (line ${line}:${col}): ${d.message}${source}`);
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function severityLabel(severity: vscode.DiagnosticSeverity): string {
|
|
switch (severity) {
|
|
case vscode.DiagnosticSeverity.Error:
|
|
return "Error";
|
|
case vscode.DiagnosticSeverity.Warning:
|
|
return "Warning";
|
|
case vscode.DiagnosticSeverity.Information:
|
|
return "Info";
|
|
case vscode.DiagnosticSeverity.Hint:
|
|
return "Hint";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|