Skip to content

Commit e4361d4

Browse files
committed
feat: enhance checkpoint diff functionality with new modes and UI updates
1 parent 48d592f commit e4361d4

File tree

24 files changed

+129
-22
lines changed

24 files changed

+129
-22
lines changed

src/core/checkpoints/__tests__/checkpoint.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ describe("Checkpoint functionality", () => {
283283
]
284284
})
285285

286-
it("should show diff for full mode", async () => {
286+
it("should show diff for to-current mode", async () => {
287287
const mockChanges = [
288288
{
289289
paths: { absolute: "/test/file.ts", relative: "file.ts" },
@@ -295,7 +295,7 @@ describe("Checkpoint functionality", () => {
295295
await checkpointDiff(mockTask, {
296296
ts: 4,
297297
commitHash: "commit2",
298-
mode: "full",
298+
mode: "to-current",
299299
})
300300

301301
expect(mockCheckpointService.getDiff).toHaveBeenCalledWith({
@@ -304,7 +304,7 @@ describe("Checkpoint functionality", () => {
304304
})
305305
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
306306
"vscode.changes",
307-
"Changes since task started",
307+
"Changes to current workspace",
308308
expect.any(Array),
309309
)
310310
})
@@ -361,7 +361,7 @@ describe("Checkpoint functionality", () => {
361361
await checkpointDiff(mockTask, {
362362
ts: 4,
363363
commitHash: "commit2",
364-
mode: "full",
364+
mode: "to-current",
365365
})
366366

367367
expect(vscode.window.showInformationMessage).toHaveBeenCalledWith("No changes found.")
@@ -374,7 +374,7 @@ describe("Checkpoint functionality", () => {
374374
await checkpointDiff(mockTask, {
375375
ts: 4,
376376
commitHash: "commit2",
377-
mode: "full",
377+
mode: "to-current",
378378
})
379379

380380
expect(mockTask.enableCheckpoints).toBe(false)

src/core/checkpoints/index.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,10 @@ export async function checkpointRestore(
270270
}
271271

272272
export type CheckpointDiffOptions = {
273-
ts: number
273+
ts?: number
274274
previousCommitHash?: string
275275
commitHash: string
276-
mode: "full" | "checkpoint"
276+
mode: "from-init" | "checkpoint" | "to-current" | "full"
277277
}
278278

279279
export async function checkpointDiff(task: Task, { ts, previousCommitHash, commitHash, mode }: CheckpointDiffOptions) {
@@ -285,21 +285,43 @@ export async function checkpointDiff(task: Task, { ts, previousCommitHash, commi
285285

286286
TelemetryService.instance.captureCheckpointDiffed(task.taskId)
287287

288-
let prevHash = commitHash
289-
let nextHash: string | undefined = undefined
288+
let fromHash: string | undefined
289+
let toHash: string | undefined
290+
let title: string
291+
292+
const checkpoints = task.clineMessages.filter(({ say }) => say === "checkpoint_saved").map(({ text }) => text!)
293+
294+
const idx = checkpoints.indexOf(commitHash)
295+
switch (mode) {
296+
case "checkpoint":
297+
fromHash = commitHash
298+
toHash = idx !== -1 && idx < checkpoints.length - 1 ? checkpoints[idx + 1] : undefined
299+
title = "Changes compare with next checkpoint"
300+
break
301+
case "from-init":
302+
fromHash = checkpoints[0]
303+
toHash = commitHash
304+
title = "Changes since first checkpoint"
305+
break
306+
case "to-current":
307+
fromHash = commitHash
308+
toHash = undefined
309+
title = "Changes to current workspace"
310+
break
311+
case "full":
312+
fromHash = checkpoints[0]
313+
toHash = undefined
314+
title = "Changes since first checkpoint"
315+
break
316+
}
290317

291-
if (mode !== "full") {
292-
const checkpoints = task.clineMessages.filter(({ say }) => say === "checkpoint_saved").map(({ text }) => text!)
293-
const idx = checkpoints.indexOf(commitHash)
294-
if (idx !== -1 && idx < checkpoints.length - 1) {
295-
nextHash = checkpoints[idx + 1]
296-
} else {
297-
nextHash = undefined
298-
}
318+
if (!fromHash) {
319+
vscode.window.showInformationMessage("No previous checkpoint to compare.")
320+
return
299321
}
300322

301323
try {
302-
const changes = await service.getDiff({ from: prevHash, to: nextHash })
324+
const changes = await service.getDiff({ from: fromHash, to: toHash })
303325

304326
if (!changes?.length) {
305327
vscode.window.showInformationMessage("No changes found.")
@@ -308,7 +330,7 @@ export async function checkpointDiff(task: Task, { ts, previousCommitHash, commi
308330

309331
await vscode.commands.executeCommand(
310332
"vscode.changes",
311-
mode === "full" ? "Changes since task started" : "Changes compare with next checkpoint",
333+
title,
312334
changes.map((change) => [
313335
vscode.Uri.file(change.paths.absolute),
314336
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,10 @@ export interface WebviewMessage {
296296
}
297297

298298
export const checkoutDiffPayloadSchema = z.object({
299-
ts: z.number(),
299+
ts: z.number().optional(),
300300
previousCommitHash: z.string().optional(),
301301
commitHash: z.string(),
302-
mode: z.enum(["full", "checkpoint"]),
302+
mode: z.enum(["full", "checkpoint", "from-init", "to-current"]),
303303
})
304304

305305
export type CheckpointDiffPayload = z.infer<typeof checkoutDiffPayloadSchema>

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1717
import { vscode } from "@src/utils/vscode"
1818
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
1919
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
20-
import { Button } from "@src/components/ui"
20+
import { Button, StandardTooltip } from "@src/components/ui"
2121

2222
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
2323
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
@@ -61,6 +61,7 @@ interface ChatRowProps {
6161
onFollowUpUnmount?: () => void
6262
isFollowUpAnswered?: boolean
6363
editable?: boolean
64+
hasCheckpoint?: boolean
6465
}
6566

6667
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -113,6 +114,7 @@ export const ChatRowContent = ({
113114
onBatchFileResponse,
114115
isFollowUpAnswered,
115116
editable,
117+
hasCheckpoint,
116118
}: ChatRowContentProps) => {
117119
const { t } = useTranslation()
118120

@@ -953,6 +955,25 @@ export const ChatRowContent = ({
953955
}
954956
}
955957

958+
const viewFullDiffBtn =
959+
hasCheckpoint && isLast && !isStreaming ? (
960+
<div style={{ marginTop: 16, display: "flex", justifyContent: "flex-start" }}>
961+
<StandardTooltip content={t("chat:showChangesFromInit")}>
962+
<VSCodeButton
963+
appearance="primary"
964+
className="flex-1 mr-[6px]"
965+
onClick={() =>
966+
vscode.postMessage({
967+
type: "checkpointDiff",
968+
payload: { mode: "full", commitHash: "" },
969+
})
970+
}>
971+
{t("chat:checkpoint.menu.viewDiffFromInit")}
972+
</VSCodeButton>
973+
</StandardTooltip>
974+
</div>
975+
) : null
976+
956977
switch (message.type) {
957978
case "say":
958979
switch (message.say) {
@@ -1272,6 +1293,7 @@ export const ChatRowContent = ({
12721293
</div>
12731294
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
12741295
<Markdown markdown={message.text} />
1296+
{viewFullDiffBtn}
12751297
</div>
12761298
</>
12771299
)
@@ -1515,6 +1537,7 @@ export const ChatRowContent = ({
15151537
</div>
15161538
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
15171539
<Markdown markdown={message.text} partial={message.partial} />
1540+
{viewFullDiffBtn}
15181541
</div>
15191542
</div>
15201543
)

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15141514
/>
15151515
)
15161516
}
1517+
const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved")
15171518

15181519
// regular message
15191520
return (
@@ -1548,6 +1549,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15481549
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
15491550
})()
15501551
}
1552+
hasCheckpoint={hasCheckpoint}
15511553
/>
15521554
)
15531555
},

webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
3232
})
3333
}, [ts, previousCommitHash, commitHash])
3434

35+
const onDiffFromInit = useCallback(() => {
36+
vscode.postMessage({
37+
type: "checkpointDiff",
38+
payload: { ts, commitHash, mode: "from-init" },
39+
})
40+
}, [ts, commitHash])
41+
42+
const onDiffWithCurrent = useCallback(() => {
43+
vscode.postMessage({
44+
type: "checkpointDiff",
45+
payload: { ts, commitHash, mode: "to-current" },
46+
})
47+
}, [ts, commitHash])
48+
3549
const onPreview = useCallback(() => {
3650
vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "preview" } })
3751
setIsOpen(false)
@@ -49,6 +63,16 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
4963
<span className="codicon codicon-diff-single" />
5064
</Button>
5165
</StandardTooltip>
66+
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffFromInit")}>
67+
<Button variant="ghost" size="icon" onClick={onDiffFromInit}>
68+
<span className="codicon codicon-versions" />
69+
</Button>
70+
</StandardTooltip>
71+
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffWithCurrent")}>
72+
<Button variant="ghost" size="icon" onClick={onDiffWithCurrent}>
73+
<span className="codicon codicon-diff" />
74+
</Button>
75+
</StandardTooltip>
5276
<Popover
5377
open={isOpen}
5478
onOpenChange={(open) => {

webview-ui/src/i18n/locales/ca/chat.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/chat.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@
152152
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> and restart your task.",
153153
"menu": {
154154
"viewDiff": "View Diff",
155+
"viewDiffFromInit": "View Diff With First Checkpoint",
156+
"viewDiffWithCurrent": "View Diff With Current Workspace",
155157
"restore": "Restore Checkpoint",
156158
"restoreFiles": "Restore Files",
157159
"restoreFilesDescription": "Restores your project's files back to a snapshot taken at this point.",

webview-ui/src/i18n/locales/es/chat.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)