diff --git a/src/editorProposalController.ts b/src/editorProposalController.ts index 0e2441d..6b44e9b 100644 --- a/src/editorProposalController.ts +++ b/src/editorProposalController.ts @@ -17,14 +17,20 @@ import { isAuthorable } from "./workspacePath"; export class EditorProposalController implements vscode.Disposable, vscode.CodeLensProvider { private readonly disposables: vscode.Disposable[] = []; - private readonly insertionDeco = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor("diffEditor.insertedTextBackground"), - }); - private readonly deletionDeco = vscode.window.createTextEditorDecorationType({ - // a non-editable struck-red hint injected AFTER the insertion (INV-52) - after: { color: new vscode.ThemeColor("gitDecoration.deletedResourceForeground") }, - textDecoration: "none", - }); + private readonly insDeco: Record<"human" | "claude", vscode.TextEditorDecorationType> = { + human: vscode.window.createTextEditorDecorationType({ + backgroundColor: "rgba(63,185,80,0.14)", borderColor: "#3fb950", + border: "0 0 2px 0", borderStyle: "solid", + }), + claude: vscode.window.createTextEditorDecorationType({ + backgroundColor: "rgba(88,166,255,0.15)", borderColor: "#58a6ff", + border: "0 0 2px 0", borderStyle: "solid", + }), + }; + private readonly delDeco: Record<"human" | "claude", vscode.TextEditorDecorationType> = { + human: vscode.window.createTextEditorDecorationType({ after: { color: "#f85149" } }), + claude: vscode.window.createTextEditorDecorationType({ after: { color: "#bc8cff" } }), + }; private readonly lensEmitter = new vscode.EventEmitter(); readonly onDidChangeCodeLenses = this.lensEmitter.event; /** Pending debounce timers — one per URI — coalesce rapid-fire propose events @@ -37,7 +43,7 @@ export class EditorProposalController implements vscode.Disposable, vscode.CodeL constructor(private readonly proposals: ProposalController) { this.disposables.push( - this.insertionDeco, this.deletionDeco, this.lensEmitter, + this.insDeco.human, this.insDeco.claude, this.delDeco.human, this.delDeco.claude, this.lensEmitter, vscode.languages.registerCodeLensProvider({ language: "markdown" }, this), this.proposals.onDidChangeProposals(({ uri }) => this.scheduleApply(uri)), vscode.window.onDidChangeActiveTextEditor((ed) => ed && this.renderEditor(ed)), @@ -80,31 +86,43 @@ export class EditorProposalController implements vscode.Disposable, vscode.CodeL /** Decorate the editor for every applied proposal on its document (INV-52). */ private renderEditor(editor: vscode.TextEditor): void { const doc = editor.document; - if (doc.languageId !== "markdown") { - editor.setDecorations(this.insertionDeco, []); - editor.setDecorations(this.deletionDeco, []); - return; - } + const clearAll = () => { + for (const a of ["human", "claude"] as const) { + editor.setDecorations(this.insDeco[a], []); + editor.setDecorations(this.delDeco[a], []); + } + }; + if (doc.languageId !== "markdown") return clearAll(); const key = this.proposals.keyFor(doc); - const insertions: vscode.Range[] = []; - const deletions: vscode.DecorationOptions[] = []; + const ins: Record<"human" | "claude", vscode.Range[]> = { human: [], claude: [] }; + const del: Record<"human" | "claude", vscode.DecorationOptions[]> = { human: [], claude: [] }; + // Pending proposals — always Claude (INV: proposals are agent-authored). for (const v of this.proposals.listProposals(doc)) { if (v.anchorStart === null || v.original === undefined || !this.proposals.isApplied(key, v.id)) continue; const plan = decorationPlan(v.anchorStart, v.original, v.replacement); - for (const ins of plan.insertions) { - insertions.push(new vscode.Range(doc.positionAt(ins.start), doc.positionAt(ins.end))); - } - for (const del of plan.deletions) { - deletions.push({ - range: new vscode.Range(doc.positionAt(del.at), doc.positionAt(del.at)), - renderOptions: { after: { contentText: ` ${del.text} `, textDecoration: "line-through" } }, + for (const i of plan.insertions) ins.claude.push(new vscode.Range(doc.positionAt(i.start), doc.positionAt(i.end))); + for (const d of plan.deletions) { + del.claude.push({ + range: new vscode.Range(doc.positionAt(d.at), doc.positionAt(d.at)), + renderOptions: { after: { contentText: ` ${d.text} `, textDecoration: "line-through" } }, }); } } - editor.setDecorations(this.insertionDeco, insertions); - editor.setDecorations(this.deletionDeco, deletions); + // Committed changes-since-baseline are added in Task 7 (pushes into ins/del[author]). + this.decorateCommitted(editor, ins, del); // no-op stub until Task 7 + for (const a of ["human", "claude"] as const) { + editor.setDecorations(this.insDeco[a], ins[a]); + editor.setDecorations(this.delDeco[a], del[a]); + } } + /** Overridden in Task 7 to add committed (non-proposal) author-colored changes. */ + private decorateCommitted( + _editor: vscode.TextEditor, + _ins: Record<"human" | "claude", vscode.Range[]>, + _del: Record<"human" | "claude", vscode.DecorationOptions[]>, + ): void {} + /** CodeLensProvider: a `Accept ▾` / `Reject ▾` pair above each applied block. */ provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] { if (document.languageId !== "markdown") return [];