Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/core/checkpoints/__tests__/checkpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ describe("Checkpoint functionality", () => {
]
})

it("should show diff for full mode", async () => {
it("should show diff for to-current mode", async () => {
const mockChanges = [
{
paths: { absolute: "/test/file.ts", relative: "file.ts" },
Expand All @@ -295,7 +295,7 @@ describe("Checkpoint functionality", () => {
await checkpointDiff(mockTask, {
ts: 4,
commitHash: "commit2",
mode: "full",
mode: "to-current",
})

expect(mockCheckpointService.getDiff).toHaveBeenCalledWith({
Expand All @@ -304,7 +304,7 @@ describe("Checkpoint functionality", () => {
})
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
"vscode.changes",
"Changes since task started",
"Changes to current workspace",
expect.any(Array),
)
})
Expand Down Expand Up @@ -361,7 +361,7 @@ describe("Checkpoint functionality", () => {
await checkpointDiff(mockTask, {
ts: 4,
commitHash: "commit2",
mode: "full",
mode: "to-current",
})

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

expect(mockTask.enableCheckpoints).toBe(false)
Expand Down
50 changes: 36 additions & 14 deletions src/core/checkpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,10 @@ export async function checkpointRestore(
}

export type CheckpointDiffOptions = {
ts: number
ts?: number
previousCommitHash?: string
commitHash: string
mode: "full" | "checkpoint"
mode: "from-init" | "checkpoint" | "to-current" | "full"
}

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

TelemetryService.instance.captureCheckpointDiffed(task.taskId)

let prevHash = commitHash
let nextHash: string | undefined = undefined
let fromHash: string | undefined
let toHash: string | undefined
let title: string

const checkpoints = task.clineMessages.filter(({ say }) => say === "checkpoint_saved").map(({ text }) => text!)

const idx = checkpoints.indexOf(commitHash)
switch (mode) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting this switch logic into a separate function for better testability and readability. Something like:

This would make the logic easier to unit test independently.

case "checkpoint":
fromHash = commitHash
toHash = idx !== -1 && idx < checkpoints.length - 1 ? checkpoints[idx + 1] : undefined
title = "Changes compare with next checkpoint"
break
case "from-init":
fromHash = checkpoints[0]
toHash = commitHash
title = "Changes since first checkpoint"
break
case "to-current":
fromHash = commitHash
toHash = undefined
title = "Changes to current workspace"
break
case "full":
fromHash = checkpoints[0]
toHash = undefined
title = "Changes since first checkpoint"
break
}

if (mode !== "full") {
const checkpoints = task.clineMessages.filter(({ say }) => say === "checkpoint_saved").map(({ text }) => text!)
const idx = checkpoints.indexOf(commitHash)
if (idx !== -1 && idx < checkpoints.length - 1) {
nextHash = checkpoints[idx + 1]
} else {
nextHash = undefined
}
if (!fromHash) {
vscode.window.showInformationMessage("No previous checkpoint to compare.")
return
}

try {
const changes = await service.getDiff({ from: prevHash, to: nextHash })
const changes = await service.getDiff({ from: fromHash, to: toHash })

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

await vscode.commands.executeCommand(
"vscode.changes",
mode === "full" ? "Changes since task started" : "Changes compare with next checkpoint",
title,
changes.map((change) => [
vscode.Uri.file(change.paths.absolute),
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({
Expand Down
4 changes: 2 additions & 2 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,10 @@ export interface WebviewMessage {
}

export const checkoutDiffPayloadSchema = z.object({
ts: z.number(),
ts: z.number().optional(),
previousCommitHash: z.string().optional(),
commitHash: z.string(),
mode: z.enum(["full", "checkpoint"]),
mode: z.enum(["full", "checkpoint", "from-init", "to-current"]),
})

export type CheckpointDiffPayload = z.infer<typeof checkoutDiffPayloadSchema>
Expand Down
25 changes: 24 additions & 1 deletion webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
import { vscode } from "@src/utils/vscode"
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import { Button } from "@src/components/ui"
import { Button, StandardTooltip } from "@src/components/ui"

import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
Expand Down Expand Up @@ -61,6 +61,7 @@ interface ChatRowProps {
onFollowUpUnmount?: () => void
isFollowUpAnswered?: boolean
editable?: boolean
hasCheckpoint?: boolean
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
Expand Down Expand Up @@ -113,6 +114,7 @@ export const ChatRowContent = ({
onBatchFileResponse,
isFollowUpAnswered,
editable,
hasCheckpoint,
}: ChatRowContentProps) => {
const { t } = useTranslation()

Expand Down Expand Up @@ -953,6 +955,25 @@ export const ChatRowContent = ({
}
}

const viewFullDiffBtn =
hasCheckpoint && isLast && !isStreaming ? (
<div style={{ marginTop: 16, display: "flex", justifyContent: "flex-start" }}>
<StandardTooltip content={t("chat:showChangesFromInit")}>
<VSCodeButton
appearance="primary"
className="flex-1 mr-[6px]"
onClick={() =>
vscode.postMessage({
type: "checkpointDiff",
payload: { mode: "full", commitHash: "" },
})
}>
{t("chat:checkpoint.menu.viewDiffFromInit")}
</VSCodeButton>
</StandardTooltip>
</div>
) : null

switch (message.type) {
case "say":
switch (message.say) {
Expand Down Expand Up @@ -1272,6 +1293,7 @@ export const ChatRowContent = ({
</div>
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
<Markdown markdown={message.text} />
{viewFullDiffBtn}
</div>
</>
)
Expand Down Expand Up @@ -1515,6 +1537,7 @@ export const ChatRowContent = ({
</div>
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
<Markdown markdown={message.text} partial={message.partial} />
{viewFullDiffBtn}
</div>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
/>
)
}
const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved")

// regular message
return (
Expand Down Expand Up @@ -1548,6 +1549,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
})()
}
hasCheckpoint={hasCheckpoint}
/>
)
},
Expand Down
24 changes: 24 additions & 0 deletions webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
})
}, [ts, previousCommitHash, commitHash])

const onDiffFromInit = useCallback(() => {
vscode.postMessage({
type: "checkpointDiff",
payload: { ts, commitHash, mode: "from-init" },
})
}, [ts, commitHash])

const onDiffWithCurrent = useCallback(() => {
vscode.postMessage({
type: "checkpointDiff",
payload: { ts, commitHash, mode: "to-current" },
})
}, [ts, commitHash])

const onPreview = useCallback(() => {
vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "preview" } })
setIsOpen(false)
Expand All @@ -49,6 +63,16 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
<span className="codicon codicon-diff-single" />
</Button>
</StandardTooltip>
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffFromInit")}>
<Button variant="ghost" size="icon" onClick={onDiffFromInit}>
<span className="codicon codicon-versions" />
</Button>
</StandardTooltip>
<StandardTooltip content={t("chat:checkpoint.menu.viewDiffWithCurrent")}>
<Button variant="ghost" size="icon" onClick={onDiffWithCurrent}>
<span className="codicon codicon-diff" />
</Button>
</StandardTooltip>
<Popover
open={isOpen}
onOpenChange={(open) => {
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ca/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/de/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
"initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in <settingsLink>settings</settingsLink> and restart your task.",
"menu": {
"viewDiff": "View Diff",
"viewDiffFromInit": "View Diff With First Checkpoint",
"viewDiffWithCurrent": "View Diff With Current Workspace",
"restore": "Restore Checkpoint",
"restoreFiles": "Restore Files",
"restoreFilesDescription": "Restores your project's files back to a snapshot taken at this point.",
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/es/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/fr/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/hi/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/id/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/it/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ja/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ko/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/nl/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/pl/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/pt-BR/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/ru/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/tr/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/vi/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading