Skip to content

Commit 2115f28

Browse files
committed
Fixed diff editor for mutiple files
1 parent 133da0e commit 2115f28

File tree

5 files changed

+440
-405
lines changed

5 files changed

+440
-405
lines changed

src/core/Cline.ts

Lines changed: 198 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -2542,6 +2542,12 @@ export class Cline extends EventEmitter<ClineEvents> {
25422542

25432543
console.log(`[checkpointDiff] Changes found, showing diff approve view`, { changesLength: changes.length })
25442544

2545+
// Reset approved files tracking at the start of a new checkpoint diff
2546+
// Only do this if we're starting a fresh diff, not if we're continuing from a previous one
2547+
if (mode === "checkpoint" && !commitHash) {
2548+
DiffApproveProvider.resetApprovedFiles()
2549+
}
2550+
25452551
// Create a new instance of DiffApproveProvider
25462552
const provider = new DiffApproveProvider(this.providerRef.deref()?.context.extensionUri!)
25472553

@@ -2554,165 +2560,226 @@ export class Cline extends EventEmitter<ClineEvents> {
25542560
changesPaths: changes.map((c) => c.paths.relative),
25552561
})
25562562

2557-
// Show each change in the diff approve view
2558-
for (const change of changes) {
2559-
// Create a temporary file for the old version from git
2560-
const tempDir = vscode.Uri.joinPath(this.providerRef.deref()?.context.extensionUri!, "temp")
2561-
const oldVersionFileName = `${path.basename(change.paths.relative)}.${previousCommitHash?.substring(0, 7) || "original"}`
2562-
const oldVersionUri = vscode.Uri.joinPath(tempDir, oldVersionFileName)
2563+
// Filter out files that have already been approved
2564+
const pendingChanges = changes.filter(
2565+
(change) => !DiffApproveProvider.isFileApproved(change.paths.relative),
2566+
)
25632567

2564-
// Use actual file path for working copy
2565-
const workingUri = vscode.Uri.file(change.paths.absolute)
2568+
// Update active views count to only include pending changes
2569+
activeViews = pendingChanges.length
25662570

2567-
// Ensure temp directory exists
2571+
if (pendingChanges.length === 0) {
2572+
vscode.window.showInformationMessage("All changes have been approved!")
2573+
return
2574+
}
2575+
2576+
console.log(
2577+
`[checkpointDiff] Showing ${pendingChanges.length} pending changes out of ${changes.length} total changes`,
2578+
)
2579+
2580+
// Show each change in the diff approve view
2581+
for (const change of pendingChanges) {
25682582
try {
2569-
await vscode.workspace.fs.createDirectory(tempDir)
2570-
} catch (error) {
2571-
// Directory might already exist
2572-
}
2583+
// Create a temporary file for the old version from git
2584+
const tempDir = vscode.Uri.joinPath(this.providerRef.deref()?.context.extensionUri!, "temp")
2585+
const oldVersionFileName = `${path.basename(change.paths.relative)}.${previousCommitHash?.substring(0, 7) || "original"}`
2586+
const oldVersionUri = vscode.Uri.joinPath(tempDir, oldVersionFileName)
2587+
2588+
// Use actual file path for working copy
2589+
const workingUri = vscode.Uri.file(change.paths.absolute)
2590+
2591+
// Ensure temp directory exists
2592+
try {
2593+
await vscode.workspace.fs.createDirectory(tempDir)
2594+
} catch (error) {
2595+
// Directory might already exist
2596+
console.log(`[checkpointDiff] Temp directory may already exist: ${error.message}`)
2597+
}
25732598

2574-
// Write old content from git to temp file
2575-
await vscode.workspace.fs.writeFile(oldVersionUri, Buffer.from(change.content.before || ""))
2576-
2577-
// Show diff with approval UI
2578-
await provider.show(
2579-
oldVersionUri,
2580-
workingUri,
2581-
async (blockId: number, approved: boolean) => {
2582-
if (approved) {
2583-
vscode.window.showInformationMessage(
2584-
`Block ${blockId} in ${change.paths.relative} approved`,
2585-
)
2586-
} else {
2587-
// For denied blocks, revert that block in the working file
2588-
const blocks = provider.findRelatedBlocks(blockId)
2589-
if (blocks.length > 0) {
2590-
const workingContent = await vscode.workspace.fs.readFile(workingUri)
2591-
let workingLines = workingContent.toString().split("\n")
2592-
2593-
// Process blocks in reverse order to handle line numbers correctly
2594-
for (const block of blocks.reverse()) {
2595-
switch (block.type) {
2596-
case "addition":
2597-
// Remove added lines
2598-
workingLines.splice(block.newStart - 1, block.newLines.length)
2599-
break
2600-
case "deletion":
2601-
// Restore deleted lines at the correct position
2602-
workingLines.splice(block.oldStart - 1, 0, ...block.oldLines)
2603-
break
2604-
case "change":
2605-
// Replace changed lines with original
2606-
workingLines.splice(
2607-
block.newStart - 1,
2608-
block.newLines.length,
2609-
...block.oldLines,
2610-
)
2611-
break
2599+
// Write old content from git to temp file
2600+
await vscode.workspace.fs.writeFile(oldVersionUri, Buffer.from(change.content.before || ""))
2601+
2602+
// Show diff with approval UI
2603+
await provider.show(
2604+
oldVersionUri,
2605+
workingUri,
2606+
async (blockId: number, approved: boolean) => {
2607+
if (approved) {
2608+
vscode.window.showInformationMessage(
2609+
`Block ${blockId} in ${change.paths.relative} approved`,
2610+
)
2611+
} else {
2612+
// For denied blocks, revert that block in the working file
2613+
const blocks = provider.findRelatedBlocks(blockId, {
2614+
panel: {} as vscode.WebviewPanel,
2615+
pendingBlocks: new Set(),
2616+
hasCalledAllBlocksProcessed: false,
2617+
filePath: change.paths.absolute,
2618+
panelId: `dummy_${Date.now()}`,
2619+
})
2620+
if (blocks.length > 0) {
2621+
const workingContent = await vscode.workspace.fs.readFile(workingUri)
2622+
let workingLines = workingContent.toString().split("\n")
2623+
2624+
// Process blocks in reverse order to handle line numbers correctly
2625+
for (const block of blocks.reverse()) {
2626+
switch (block.type) {
2627+
case "addition":
2628+
// Remove added lines
2629+
workingLines.splice(block.newStart - 1, block.newLines.length)
2630+
break
2631+
case "deletion":
2632+
// Restore deleted lines at the correct position
2633+
workingLines.splice(block.oldStart - 1, 0, ...block.oldLines)
2634+
break
2635+
case "change":
2636+
// Replace changed lines with original
2637+
workingLines.splice(
2638+
block.newStart - 1,
2639+
block.newLines.length,
2640+
...block.oldLines,
2641+
)
2642+
break
2643+
}
26122644
}
2613-
}
26142645

2615-
// Write back the updated content
2616-
await vscode.workspace.fs.writeFile(workingUri, Buffer.from(workingLines.join("\n")))
2646+
// Write back the updated content
2647+
await vscode.workspace.fs.writeFile(
2648+
workingUri,
2649+
Buffer.from(workingLines.join("\n")),
2650+
)
2651+
}
2652+
vscode.window.showInformationMessage(`Changes in ${change.paths.relative} reverted`)
26172653
}
2618-
vscode.window.showInformationMessage(`Changes in ${change.paths.relative} reverted`)
2619-
}
2620-
},
2621-
async () => {
2622-
// Check if this file has already been processed to avoid duplicate callbacks
2623-
if (processedFiles.has(change.paths.relative)) {
2624-
console.log(
2625-
`[checkpointDiff] File ${change.paths.relative} has already been processed, ignoring duplicate callback`,
2626-
)
2627-
return // Skip processing for files we've already handled
2628-
}
2629-
2630-
console.log(
2631-
`[checkpointDiff] onAllBlocksProcessed callback started for file: ${change.paths.relative}`,
2632-
)
2633-
try {
2634-
// Clean up temp file
2635-
try {
2636-
await vscode.workspace.fs.delete(oldVersionUri)
2637-
} catch (error) {
2654+
},
2655+
async () => {
2656+
// Check if this file has already been processed to avoid duplicate callbacks
2657+
if (processedFiles.has(change.paths.relative)) {
26382658
console.log(
2639-
`[checkpointDiff] Error deleting temp file (may already be deleted): ${error.message}`,
2659+
`[checkpointDiff] File ${change.paths.relative} has already been processed, ignoring duplicate callback`,
26402660
)
2661+
return // Skip processing for files we've already handled
26412662
}
26422663

2643-
// Mark this file as processed
2644-
processedFiles.add(change.paths.relative)
2664+
// Mark this file as approved since all blocks have been processed
2665+
DiffApproveProvider.markFileAsApproved(change.paths.relative)
26452666

2646-
// Decrement active views counter
26472667
console.log(
2648-
`[checkpointDiff] Decrementing activeViews from ${activeViews} to ${activeViews - 1}`,
2649-
{ file: change.paths.relative },
2668+
`[checkpointDiff] onAllBlocksProcessed callback started for file: ${change.paths.relative}`,
26502669
)
2651-
activeViews--
2652-
2653-
// When all views are closed, reset the verified checkpoint
2654-
console.log(`[checkpointDiff] After decrement, activeViews=${activeViews}`, {
2655-
file: change.paths.relative,
2656-
processedCount: processedFiles.size,
2657-
totalChanges: changes.length,
2658-
})
2659-
if (activeViews === 0) {
2670+
try {
2671+
// Clean up temp file
2672+
try {
2673+
await vscode.workspace.fs.delete(oldVersionUri)
2674+
} catch (error) {
2675+
console.log(
2676+
`[checkpointDiff] Error deleting temp file (may already be deleted): ${error.message}`,
2677+
)
2678+
}
2679+
2680+
// Mark this file as processed
2681+
processedFiles.add(change.paths.relative)
2682+
2683+
// Decrement active views counter
26602684
console.log(
2661-
"✅✅✅ [checkpointDiff] All diff views closed, resetting verified checkpoint ✅✅✅",
2685+
`[checkpointDiff] Decrementing activeViews from ${activeViews} to ${activeViews - 1}`,
2686+
{ file: change.paths.relative },
26622687
)
2663-
const service = this.getCheckpointService()
2664-
if (service) {
2665-
// First reset the verified checkpoint
2666-
await service.resetVerifiedCheckpoint()
2667-
2668-
// Then save a new checkpoint - this will automatically become verified since we just reset
2669-
try {
2670-
const result = await service.saveCheckpoint(
2671-
"Saving state after diff view closed",
2672-
)
2673-
if (result === undefined) {
2674-
// No changes detected, set HEAD as verified checkpoint
2675-
console.log(
2676-
"[checkpointDiff] No changes detected, setting HEAD as verified checkpoint",
2677-
)
2678-
await service.setLastVerifiedCheckpoint("HEAD")
2679-
}
2680-
} catch (error) {
2681-
console.error("[checkpointDiff] Failed to save checkpoint:", error)
2682-
// If saving fails, try to set HEAD as the verified checkpoint
2688+
activeViews--
2689+
2690+
// When all views are closed, reset the verified checkpoint
2691+
console.log(`[checkpointDiff] After decrement, activeViews=${activeViews}`, {
2692+
file: change.paths.relative,
2693+
processedCount: processedFiles.size,
2694+
totalChanges: changes.length,
2695+
})
2696+
if (activeViews === 0) {
2697+
console.log(
2698+
"✅✅✅ [checkpointDiff] All diff views closed, resetting verified checkpoint ✅✅✅",
2699+
)
2700+
const service = this.getCheckpointService()
2701+
if (service) {
2702+
// First reset the verified checkpoint
2703+
await service.resetVerifiedCheckpoint()
2704+
2705+
// Then save a new checkpoint - this will automatically become verified since we just reset
26832706
try {
2684-
await service.setLastVerifiedCheckpoint("HEAD")
2685-
} catch (setError) {
2686-
console.error(
2687-
"[checkpointDiff] Failed to set HEAD as verified checkpoint:",
2688-
setError,
2707+
const result = await service.saveCheckpoint(
2708+
"Saving state after diff view closed",
26892709
)
2710+
if (result === undefined) {
2711+
// No changes detected, set HEAD as verified checkpoint
2712+
console.log(
2713+
"[checkpointDiff] No changes detected, setting HEAD as verified checkpoint",
2714+
)
2715+
await service.setLastVerifiedCheckpoint("HEAD")
2716+
}
2717+
} catch (error) {
2718+
console.error("[checkpointDiff] Failed to save checkpoint:", error)
2719+
// If saving fails, try to set HEAD as the verified checkpoint
2720+
try {
2721+
await service.setLastVerifiedCheckpoint("HEAD")
2722+
} catch (setError) {
2723+
console.error(
2724+
"[checkpointDiff] Failed to set HEAD as verified checkpoint:",
2725+
setError,
2726+
)
2727+
}
26902728
}
26912729
}
2692-
}
26932730

2694-
const provider = this.providerRef.deref()
2695-
if (provider) {
2696-
provider.log("[checkpointDiff] All diff views closed, verified checkpoint reset")
2697-
provider.postMessageToWebview({ type: "currentCheckpointUpdated", text: "" })
2731+
const provider = this.providerRef.deref()
2732+
if (provider) {
2733+
provider.log(
2734+
"[checkpointDiff] All diff views closed, verified checkpoint reset",
2735+
)
2736+
provider.postMessageToWebview({ type: "currentCheckpointUpdated", text: "" })
2737+
}
2738+
} else {
2739+
console.log(
2740+
`[checkpointDiff] Still waiting for ${activeViews} more files to be processed`,
2741+
)
26982742
}
2699-
} else {
27002743
console.log(
2701-
`[checkpointDiff] Still waiting for ${activeViews} more files to be processed`,
2744+
`[checkpointDiff] onAllBlocksProcessed callback completed for file: ${change.paths.relative}`,
27022745
)
2746+
} catch (error) {
2747+
console.error("Failed to cleanup:", error)
2748+
}
2749+
},
2750+
)
2751+
} catch (error) {
2752+
// Log the error but continue with other files
2753+
console.error(`[checkpointDiff] Error showing diff for ${change.paths.relative}:`, error)
2754+
vscode.window.showErrorMessage(`Error showing diff for ${change.paths.relative}: ${error.message}`)
2755+
2756+
// Decrement active views since this one failed
2757+
activeViews--
2758+
console.log(`[checkpointDiff] Decremented activeViews to ${activeViews} due to error`)
2759+
2760+
// If all views have failed, we should still reset the checkpoint
2761+
if (activeViews === 0) {
2762+
console.log(`[checkpointDiff] All diff views failed, resetting verified checkpoint`)
2763+
const service = this.getCheckpointService()
2764+
if (service) {
2765+
try {
2766+
await service.resetVerifiedCheckpoint()
2767+
await service.setLastVerifiedCheckpoint("HEAD")
2768+
} catch (resetError) {
2769+
console.error(`[checkpointDiff] Error resetting checkpoint:`, resetError)
27032770
}
2704-
console.log(
2705-
`[checkpointDiff] onAllBlocksProcessed callback completed for file: ${change.paths.relative}`,
2706-
)
2707-
} catch (error) {
2708-
console.error("Failed to cleanup:", error)
27092771
}
2710-
},
2711-
)
2772+
}
2773+
}
27122774
}
27132775
} catch (err) {
2714-
this.providerRef.deref()?.log("[checkpointDiff] disabling checkpoints for this task")
2715-
this.enableCheckpoints = false
2776+
console.error("[checkpointDiff] Error in checkpoint diff:", err)
2777+
vscode.window.showErrorMessage(`Error showing diff: ${err.message}`)
2778+
2779+
// Don't close panels or reset approved files - let the user continue working
2780+
2781+
// Don't disable checkpoints completely, just log the error
2782+
this.providerRef.deref()?.log("[checkpointDiff] Error occurred during diff, but checkpoints remain enabled")
27162783
}
27172784
}
27182785

0 commit comments

Comments
 (0)