fix(ui): better handle patch file support when rendering patch/edit tools (#26828)

This commit is contained in:
Brendan Allan
2026-05-11 17:21:59 +08:00
committed by GitHub
parent 2d0d3d596e
commit c933504d9c
2 changed files with 46 additions and 18 deletions
+33 -13
View File
@@ -264,6 +264,7 @@ function getDirectory(path: string | undefined) {
} }
import type { IconProps } from "./icon" import type { IconProps } from "./icon"
import { normalize } from "./session-diff"
export type ToolInfo = { export type ToolInfo = {
icon: IconProps["name"] icon: IconProps["name"]
@@ -1878,6 +1879,31 @@ ToolRegistry.register({
const path = createMemo(() => props.metadata?.filediff?.file || props.input.filePath || "") const path = createMemo(() => props.metadata?.filediff?.file || props.input.filePath || "")
const filename = () => getFilename(props.input.filePath ?? "") const filename = () => getFilename(props.input.filePath ?? "")
const pending = () => props.status === "pending" || props.status === "running" const pending = () => props.status === "pending" || props.status === "running"
const fileCompProps = createMemo(() => {
try {
if (props.metadata?.filediff) {
const diff = normalize({
...props.metadata?.filediff,
status: "modified",
})
const fileDiff = diff.fileDiff
if (fileDiff) return { fileDiff, hunkSeparators: fileDiff.isPartial ? "simple" : "line-info-basic" }
}
} catch {}
return {
before: {
name: props.metadata?.filediff?.file || props.input.filePath,
contents: props.metadata?.filediff?.before || props.input.oldString || "",
},
after: {
name: props.metadata?.filediff?.file || props.input.filePath,
contents: props.metadata?.filediff?.after || props.input.newString || "",
},
}
})
return ( return (
<div data-component="edit-tool"> <div data-component="edit-tool">
<BasicTool <BasicTool
@@ -1919,18 +1945,7 @@ ToolRegistry.register({
} }
> >
<div data-component="edit-content"> <div data-component="edit-content">
<Dynamic <Dynamic component={fileComponent} mode="diff" {...fileCompProps()} />
component={fileComponent}
mode="diff"
before={{
name: props.metadata?.filediff?.file || props.input.filePath,
contents: props.metadata?.filediff?.before || props.input.oldString || "",
}}
after={{
name: props.metadata?.filediff?.file || props.input.filePath,
contents: props.metadata?.filediff?.after || props.input.newString || "",
}}
/>
</div> </div>
</ToolFileAccordion> </ToolFileAccordion>
</Show> </Show>
@@ -2111,7 +2126,12 @@ ToolRegistry.register({
<Accordion.Content> <Accordion.Content>
<Show when={visible()}> <Show when={visible()}>
<div data-component="apply-patch-file-diff"> <div data-component="apply-patch-file-diff">
<Dynamic component={fileComponent} mode="diff" fileDiff={file.view.fileDiff} /> <Dynamic
component={fileComponent}
mode="diff"
fileDiff={file.view.fileDiff}
hunkSeparators={file.view.fileDiff.isPartial ? "simple" : "line-info-basic"}
/>
</div> </div>
</Show> </Show>
</Accordion.Content> </Accordion.Content>
+13 -5
View File
@@ -1,4 +1,4 @@
import { parseDiffFromFile, type FileDiffMetadata } from "@pierre/diffs" import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
import { formatPatch, parsePatch, structuredPatch } from "diff" import { formatPatch, parsePatch, structuredPatch } from "diff"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
@@ -34,6 +34,8 @@ function patch(diff: ReviewDiff) {
const afterLines: Array<{ text: string; newline: boolean }> = [] const afterLines: Array<{ text: string; newline: boolean }> = []
let previous: "-" | "+" | " " | undefined let previous: "-" | "+" | " " | undefined
const patchIsPartial = patch.hunks.every((h) => h.oldStart > 1)
for (const hunk of patch.hunks) { for (const hunk of patch.hunks) {
for (const line of hunk.lines) { for (const line of hunk.lines) {
if (line.startsWith("\\")) { if (line.startsWith("\\")) {
@@ -67,9 +69,10 @@ function patch(diff: ReviewDiff) {
before: beforeLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), before: beforeLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""),
after: afterLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), after: afterLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""),
patch: diff.patch, patch: diff.patch,
patchIsPartial,
} }
} catch { } catch {
return { before: "", after: "", patch: diff.patch } return { before: "", after: "", patch: diff.patch, patchIsPartial: false }
} }
} }
return { return {
@@ -86,27 +89,32 @@ function patch(diff: ReviewDiff) {
{ context: Number.MAX_SAFE_INTEGER }, { context: Number.MAX_SAFE_INTEGER },
), ),
), ),
patchIsPartial: false,
} }
} }
function file(file: string, patch: string, before: string, after: string) { function file(file: string, patch: string, before: string, after: string, partial = false) {
const hit = cache.get(patch) const hit = cache.get(patch)
if (hit) return hit if (hit) return hit
const value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after }) let value: FileDiffMetadata | undefined
if (partial) value = parsePatchFiles(patch)[0]?.files[0]
if (value === undefined) value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
cache.set(patch, value) cache.set(patch, value)
return value return value
} }
export function normalize(diff: ReviewDiff): ViewDiff { export function normalize(diff: ReviewDiff): ViewDiff {
const next = patch(diff) const next = patch(diff)
const fileDiff = file(diff.file, next.patch, next.before, next.after, next.patchIsPartial)
return { return {
file: diff.file, file: diff.file,
patch: next.patch, patch: next.patch,
additions: diff.additions, additions: diff.additions,
deletions: diff.deletions, deletions: diff.deletions,
status: diff.status, status: diff.status,
fileDiff: file(diff.file, next.patch, next.before, next.after), fileDiff,
} }
} }