diff --git a/src/render/diffViewer.ts b/src/render/diffViewer.ts index 8e07ed8..29d673f 100644 --- a/src/render/diffViewer.ts +++ b/src/render/diffViewer.ts @@ -5,7 +5,7 @@ * 1. 临时文件(非 untitled)避免保存提示 * 2. ★ 临时文件只在 Accept/Reject 后删除,手动关闭 Diff 不影响 * 3. Accept/Reject 后自动推进到下一个 pending 文件 - * 4. 状态栏按钮在 Diff 模式下可见 + * 4. 状态栏按钮在 Diff 模式下可见(isOpening 锁防竞态) */ import * as vscode from 'vscode'; @@ -27,8 +27,9 @@ export interface DiffSession { export class DiffViewer { private activeSession: DiffSession | null = null; - private closeListener: vscode.Disposable | null = null; private tmpFiles: string[] = []; + /** ★ 原子锁:Diff 正在打开中,禁止清除 session */ + private isOpening = false; /** 外部回调:Diff 操作完成后通知(用于刷新 UI) */ onDiffAction?: () => void; @@ -49,23 +50,24 @@ export class DiffViewer { const beforePath = this.makeTmpFile(`ai-diff-before-${editRecord.id.slice(0, 8)}${ext}`, editRecord.beforeContent); const afterPath = this.makeTmpFile(`ai-diff-after-${editRecord.id.slice(0, 8)}${ext}`, editRecord.afterContent); - this.setSession({ + this.isOpening = true; + this.activeSession = { fileId: fileChangeId, editId: editRecord.id, beforeTmpPath: beforePath, afterTmpPath: afterPath, beforeUri: vscode.Uri.file(beforePath), afterUri: vscode.Uri.file(afterPath), - }); + }; await vscode.commands.executeCommand( 'vscode.diff', - this.activeSession!.beforeUri, - this.activeSession!.afterUri, + this.activeSession.beforeUri, + this.activeSession.afterUri, `${fileName} — AI ${toolLabel} #${editRecord.id.slice(0, 6)} · 状态栏操作` ); - // ★ 通知外部刷新状态栏(切换到 Diff 模式显示 Accept/Reject 按钮) + this.isOpening = false; this.onDiffAction?.(); } @@ -76,22 +78,23 @@ export class DiffViewer { const beforePath = this.makeTmpFile(`ai-diff-before-${fileChange.id.slice(0, 8)}${ext}`, fileChange.originalContent); const afterPath = this.makeTmpFile(`ai-diff-after-${fileChange.id.slice(0, 8)}${ext}`, fileChange.latestContent); - this.setSession({ + this.isOpening = true; + this.activeSession = { fileId: fileChange.id, beforeTmpPath: beforePath, afterTmpPath: afterPath, beforeUri: vscode.Uri.file(beforePath), afterUri: vscode.Uri.file(afterPath), - }); + }; await vscode.commands.executeCommand( 'vscode.diff', - this.activeSession!.beforeUri, - this.activeSession!.afterUri, + this.activeSession.beforeUri, + this.activeSession.afterUri, `${fileName} — ${fileChange.edits.length} 次 AI 编辑 · 状态栏操作` ); - // ★ 通知外部刷新状态栏(切换到 Diff 模式显示 Accept/Reject 按钮) + this.isOpening = false; this.onDiffAction?.(); } @@ -108,9 +111,7 @@ export class DiffViewer { else vscode.window.showInformationMessage('AI Diff: 当前文件无待处理变更'); } - // ---- Diff 界面操作 (Bug 1 修复: 自动推进) ---- - - // ★ Accept/Reject 后删除临时文件 + 关闭 Diff + // ---- Accept/Reject(Accept/Reject 后删除临时文件 + 关闭 Diff) ---- async acceptCurrentDiff(): Promise { if (!this.activeSession) return; @@ -123,7 +124,7 @@ export class DiffViewer { } vscode.window.showInformationMessage('AI Diff: 已接受 ✓'); - this.deleteSessionFiles(); // ★ Accept 后才删 + this.deleteSessionFiles(); await this.closeDiff(); this.onDiffAction?.(); this.advanceToNext(fileId); @@ -140,7 +141,7 @@ export class DiffViewer { } vscode.window.showInformationMessage('AI Diff: 已拒绝 ✗'); - this.deleteSessionFiles(); // ★ Reject 后才删 + this.deleteSessionFiles(); await this.closeDiff(); this.onDiffAction?.(); this.advanceToNext(fileId); @@ -153,16 +154,15 @@ export class DiffViewer { if (ok) { vscode.window.showInformationMessage('AI Diff: 已接受此文件全部 ✓'); } - this.deleteSessionFiles(); // ★ AcceptAll 后才删 + this.deleteSessionFiles(); await this.closeDiff(); this.onDiffAction?.(); this.advanceToNext(fileId); } - // ---- ★ Bug 1: 自动推进 ---- + // ---- 自动推进 ---- private advanceToNext(afterFileId: string): void { - // 如果此文件仍有待处理编辑,重新打开此文件 if (!this.changeSetManager.isFileFullyProcessed(afterFileId)) { const set = this.changeSetManager.getCurrentSet(); const same = set?.changes.find(c => c.id === afterFileId); @@ -172,7 +172,6 @@ export class DiffViewer { } } - // 否则推进到下一个 pending 文件 const next = this.changeSetManager.findNextPending(afterFileId); if (next) { setTimeout(() => this.showFileDiff(next), 150); @@ -189,7 +188,7 @@ export class DiffViewer { if (this.activeSession) { try { await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); } catch { /* ok */ } } - this.clearSession(); + this.activeSession = null; } // ---- 内部 ---- @@ -203,20 +202,6 @@ export class DiffViewer { return filePath; } - private setSession(session: DiffSession): void { - this.clearSession(); - this.activeSession = session; - this.closeListener = vscode.window.onDidChangeVisibleTextEditors(() => { - if (!this.activeSession) return; - const stillOpen = vscode.window.visibleTextEditors.some( - e => e.document.uri.toString() === this.activeSession!.beforeUri.toString() || - e.document.uri.toString() === this.activeSession!.afterUri.toString() - ); - if (!stillOpen) this.clearSession(); - }); - } - - /** ★ 删除当前会话的临时文件(只在 Accept/Reject 时调用) */ private deleteSessionFiles(): void { if (!this.activeSession) return; [this.activeSession.beforeTmpPath, this.activeSession.afterTmpPath].forEach(p => { @@ -227,16 +212,10 @@ export class DiffViewer { ); } - /** 清除会话状态(不删文件!手动关闭 Diff 后不影响重新打开) */ - private clearSession(): void { - this.activeSession = null; - if (this.closeListener) { this.closeListener.dispose(); this.closeListener = null; } - } - cleanupTmpFiles(): void { for (const p of this.tmpFiles) { try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch { /* ok */ } } this.tmpFiles = []; } -} +} \ No newline at end of file