Skip to content

Commit 118a471

Browse files
NaccOlldaniel-lxs
authored andcommitted
feat: enhance checkpoint diff functionality with new modes and UI updates
1 parent ac88d66 commit 118a471

File tree

24 files changed

+129
-21
lines changed

24 files changed

+129
-21
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
@@ -306,10 +306,10 @@ export interface WebviewMessage {
306306
}
307307

308308
export const checkoutDiffPayloadSchema = z.object({
309-
ts: z.number(),
309+
ts: z.number().optional(),
310310
previousCommitHash: z.string().optional(),
311311
commitHash: z.string(),
312-
mode: z.enum(["full", "checkpoint"]),
312+
mode: z.enum(["full", "checkpoint", "from-init", "to-current"]),
313313
})
314314

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

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1616
import { vscode } from "@src/utils/vscode"
1717
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
1818
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
19+
import { Button, StandardTooltip } from "@src/components/ui"
1920

2021
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
2122
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
@@ -75,6 +76,7 @@ interface ChatRowProps {
7576
onFollowUpUnmount?: () => void
7677
isFollowUpAnswered?: boolean
7778
editable?: boolean
79+
hasCheckpoint?: boolean
7880
}
7981

8082
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -127,6 +129,7 @@ export const ChatRowContent = ({
127129
onBatchFileResponse,
128130
isFollowUpAnswered,
129131
editable,
132+
hasCheckpoint,
130133
}: ChatRowContentProps) => {
131134
const { t } = useTranslation()
132135

@@ -968,6 +971,25 @@ export const ChatRowContent = ({
968971
}
969972
}
970973

974+
const viewFullDiffBtn =
975+
hasCheckpoint && isLast && !isStreaming ? (
976+
<div style={{ marginTop: 16, display: "flex", justifyContent: "flex-start" }}>
977+
<StandardTooltip content={t("chat:showChangesFromInit")}>
978+
<VSCodeButton
979+
appearance="primary"
980+
className="flex-1 mr-[6px]"
981+
onClick={() =>
982+
vscode.postMessage({
983+
type: "checkpointDiff",
984+
payload: { mode: "full", commitHash: "" },
985+
})
986+
}>
987+
{t("chat:checkpoint.menu.viewDiffFromInit")}
988+
</VSCodeButton>
989+
</StandardTooltip>
990+
</div>
991+
) : null
992+
971993
switch (message.type) {
972994
case "say":
973995
switch (message.say) {
@@ -1201,6 +1223,7 @@ export const ChatRowContent = ({
12011223
</div>
12021224
<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
12031225
<Markdown markdown={message.text} />
1226+
{viewFullDiffBtn}
12041227
</div>
12051228
</>
12061229
)
@@ -1441,6 +1464,7 @@ export const ChatRowContent = ({
14411464
</div>
14421465
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
14431466
<Markdown markdown={message.text} partial={message.partial} />
1467+
{viewFullDiffBtn}
14441468
</div>
14451469
</div>
14461470
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15251525
/>
15261526
)
15271527
}
1528+
const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved")
15281529

15291530
// regular message
15301531
return (
@@ -1559,6 +1560,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15591560
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
15601561
})()
15611562
}
1563+
hasCheckpoint={hasCheckpoint}
15621564
/>
15631565
)
15641566
},

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, open, onOpenChange
4141
})
4242
}, [ts, previousCommitHash, commitHash])
4343

44+
const onDiffFromInit = useCallback(() => {
45+
vscode.postMessage({
46+
type: "checkpointDiff",
47+
payload: { ts, commitHash, mode: "from-init" },
48+
})
49+
}, [ts, commitHash])
50+
51+
const onDiffWithCurrent = useCallback(() => {
52+
vscode.postMessage({
53+
type: "checkpointDiff",
54+
payload: { ts, commitHash, mode: "to-current" },
55+
})
56+
}, [ts, commitHash])
57+
4458
const onPreview = useCallback(() => {
4559
vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "preview" } })
4660
setOpen(false)
@@ -68,6 +82,16 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, open, onOpenChange
6882
<span className="codicon codicon-diff-single" />
6983
</Button>
7084
</StandardTooltip>
85+
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffFromInit")}>
86+
<Button variant="ghost" size="icon" onClick={onDiffFromInit}>
87+
<span className="codicon codicon-versions" />
88+
</Button>
89+
</StandardTooltip>
90+
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffWithCurrent")}>
91+
<Button variant="ghost" size="icon" onClick={onDiffWithCurrent}>
92+
<span className="codicon codicon-diff" />
93+
</Button>
94+
</StandardTooltip>
7195
<Popover open={isOpen} onOpenChange={handleOpenChange}>
7296
<StandardTooltip content={t("chat:checkpoint.menu.restore")}>
7397
<PopoverTrigger asChild>

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
@@ -154,6 +154,8 @@
154154
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> and restart your task.",
155155
"menu": {
156156
"viewDiff": "View Diff",
157+
"viewDiffFromInit": "View Diff With First Checkpoint",
158+
"viewDiffWithCurrent": "View Diff With Current Workspace",
157159
"restore": "Restore Checkpoint",
158160
"restoreFiles": "Restore Files",
159161
"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)