diff options
| author | Dax <[email protected]> | 2025-07-07 15:53:43 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-07 15:53:43 -0400 |
| commit | f884766445bbf1fbce11f1db4bc6174e72d9baa5 (patch) | |
| tree | e7d9e7b3d8efc8bed249f9d29e2d8fb838a275f2 /packages/web/src/components/share/content-diff.tsx | |
| parent | 76b2e4539cb97bae5812ed2d832ce49d02e70c64 (diff) | |
| download | opencode-f884766445bbf1fbce11f1db4bc6174e72d9baa5.tar.gz opencode-f884766445bbf1fbce11f1db4bc6174e72d9baa5.zip | |
v2 message format and upgrade to ai sdk v5 (#743)
Co-authored-by: GitHub Action <[email protected]>
Co-authored-by: Liang-Shih Lin <[email protected]>
Co-authored-by: Dominik Engelhardt <[email protected]>
Co-authored-by: Jay V <[email protected]>
Co-authored-by: adamdottv <[email protected]>
Diffstat (limited to 'packages/web/src/components/share/content-diff.tsx')
| -rw-r--r-- | packages/web/src/components/share/content-diff.tsx | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/packages/web/src/components/share/content-diff.tsx b/packages/web/src/components/share/content-diff.tsx new file mode 100644 index 000000000..894145c34 --- /dev/null +++ b/packages/web/src/components/share/content-diff.tsx @@ -0,0 +1,231 @@ +import { type Component, createMemo } from "solid-js" +import { parsePatch } from "diff" +import { ContentCode } from "./content-code" +import styles from "./content-diff.module.css" + +type DiffRow = { + left: string + right: string + type: "added" | "removed" | "unchanged" | "modified" +} + +interface Props { + diff: string + lang?: string +} + +export function ContentDiff(props: Props) { + const rows = createMemo(() => { + const diffRows: DiffRow[] = [] + + try { + const patches = parsePatch(props.diff) + + for (const patch of patches) { + for (const hunk of patch.hunks) { + const lines = hunk.lines + let i = 0 + + while (i < lines.length) { + const line = lines[i] + const content = line.slice(1) + const prefix = line[0] + + if (prefix === "-") { + // Look ahead for consecutive additions to pair with removals + const removals: string[] = [content] + let j = i + 1 + + // Collect all consecutive removals + while (j < lines.length && lines[j][0] === "-") { + removals.push(lines[j].slice(1)) + j++ + } + + // Collect all consecutive additions that follow + const additions: string[] = [] + while (j < lines.length && lines[j][0] === "+") { + additions.push(lines[j].slice(1)) + j++ + } + + // Pair removals with additions + const maxLength = Math.max(removals.length, additions.length) + for (let k = 0; k < maxLength; k++) { + const hasLeft = k < removals.length + const hasRight = k < additions.length + + if (hasLeft && hasRight) { + // Replacement - left is removed, right is added + diffRows.push({ + left: removals[k], + right: additions[k], + type: "modified", + }) + } else if (hasLeft) { + // Pure removal + diffRows.push({ + left: removals[k], + right: "", + type: "removed", + }) + } else if (hasRight) { + // Pure addition - only create if we actually have content + diffRows.push({ + left: "", + right: additions[k], + type: "added", + }) + } + } + + i = j + } else if (prefix === "+") { + // Standalone addition (not paired with removal) + diffRows.push({ + left: "", + right: content, + type: "added", + }) + i++ + } else if (prefix === " ") { + diffRows.push({ + left: content, + right: content, + type: "unchanged", + }) + i++ + } else { + i++ + } + } + } + } + } catch (error) { + console.error("Failed to parse patch:", error) + return [] + } + + return diffRows + }) + + const mobileRows = createMemo(() => { + const mobileBlocks: { type: "removed" | "added" | "unchanged"; lines: string[] }[] = [] + const currentRows = rows() + + let i = 0 + while (i < currentRows.length) { + const removedLines: string[] = [] + const addedLines: string[] = [] + + // Collect consecutive modified/removed/added rows + while ( + i < currentRows.length && + (currentRows[i].type === "modified" || currentRows[i].type === "removed" || currentRows[i].type === "added") + ) { + const row = currentRows[i] + if (row.left && (row.type === "removed" || row.type === "modified")) { + removedLines.push(row.left) + } + if (row.right && (row.type === "added" || row.type === "modified")) { + addedLines.push(row.right) + } + i++ + } + + // Add grouped blocks + if (removedLines.length > 0) { + mobileBlocks.push({ type: "removed", lines: removedLines }) + } + if (addedLines.length > 0) { + mobileBlocks.push({ type: "added", lines: addedLines }) + } + + // Add unchanged rows as-is + if (i < currentRows.length && currentRows[i].type === "unchanged") { + mobileBlocks.push({ + type: "unchanged", + lines: [currentRows[i].left], + }) + i++ + } + } + + return mobileBlocks + }) + + return ( + <div class={styles.root}> + <div data-component="desktop"> + {rows().map((r) => ( + <div data-component="diff-row" data-type={r.type}> + <div data-slot="before" data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}> + <ContentCode code={r.left} flush lang={props.lang} /> + </div> + <div data-slot="after" data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}> + <ContentCode code={r.right} lang={props.lang} flush /> + </div> + </div> + ))} + </div> + + <div data-component="mobile"> + {mobileRows().map((block) => ( + <div data-component="diff-block" data-type={block.type}> + {block.lines.map((line) => ( + <ContentCode + code={line} + lang={props.lang} + data-section="cell" + data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""} + /> + ))} + </div> + ))} + </div> + </div> + ) +} + +// const testDiff = `--- combined_before.txt 2025-06-24 16:38:08 +// +++ combined_after.txt 2025-06-24 16:38:12 +// @@ -1,21 +1,25 @@ +// unchanged line +// -deleted line +// -old content +// +added line +// +new content +// +// -removed empty line below +// +added empty line above +// +// - tab indented +// -trailing spaces +// -very long line that will definitely wrap in most editors and cause potential alignment issues when displayed in a two column diff view +// -unicode content: π β¨ δΈζ +// -mixed content with tabs and spaces +// + space indented +// +no trailing spaces +// +short line +// +very long replacement line that will also wrap and test how the diff viewer handles long line additions after short line removals +// +different unicode: π π» ζ₯ζ¬θͺ +// +normalized content with consistent spacing +// +newline to content +// +// -content to remove +// -whitespace only: +// -multiple +// -consecutive +// -deletions +// -single deletion +// + +// +single addition +// +first addition +// +second addition +// +third addition +// line before addition +// +first added line +// + +// +third added line +// line after addition +// final unchanged line` |
