231 lines Β· 6.7 KB Β· TypeScript
RawDownload
import { parsePatch } from "diff"import { createMemo } from "solid-js"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 === "" ? " " : content,                right: content === "" ? " " : 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) => (              <div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>                <ContentCode code={line} lang={props.lang} flush />              </div>            ))}          </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`

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t