diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx index ac98a6d24..9456e4a3c 100644 --- a/packages/ui/src/components/diff-ssr.tsx +++ b/packages/ui/src/components/diff-ssr.tsx @@ -29,10 +29,16 @@ export function Diff(props: SSRDiffProps) { const getRoot = () => fileDiffRef?.shadowRoot ?? undefined const findSide = (element: HTMLElement): "additions" | "deletions" => { + const line = element.closest("[data-line], [data-alt-line]") + if (line instanceof HTMLElement) { + const type = line.dataset.lineType + if (type === "change-deletion") return "deletions" + if (type === "change-addition" || type === "change-additions") return "additions" + } + const code = element.closest("[data-code]") if (!(code instanceof HTMLElement)) return "additions" - if (code.hasAttribute("data-deletions")) return "deletions" - return "additions" + return code.hasAttribute("data-deletions") ? "deletions" : "additions" } const applyCommentedLines = (ranges: SelectedLineRange[]) => { @@ -45,19 +51,69 @@ export function Diff(props: SSRDiffProps) { node.removeAttribute("data-comment-selected") } + const diffs = root.querySelector("[data-diffs]") + if (!(diffs instanceof HTMLElement)) return + + const split = diffs.dataset.type === "split" + + const code = Array.from(diffs.querySelectorAll("[data-code]")).filter( + (node): node is HTMLElement => node instanceof HTMLElement, + ) + if (code.length === 0) return + + const lineIndex = (element: HTMLElement) => { + const raw = element.dataset.lineIndex + if (!raw) return + const values = raw + .split(",") + .map((value) => parseInt(value, 10)) + .filter((value) => !Number.isNaN(value)) + if (values.length === 0) return + if (!split) return values[0] + if (values.length === 2) return values[1] + return values[0] + } + + const rowIndex = (line: number, side: "additions" | "deletions" | undefined) => { + const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter( + (node): node is HTMLElement => node instanceof HTMLElement, + ) + if (nodes.length === 0) return + + const targetSide = side ?? "additions" + + for (const node of nodes) { + if (findSide(node) === targetSide) return lineIndex(node) + if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node) + } + } + for (const range of ranges) { - const start = Math.max(1, Math.min(range.start, range.end)) - const end = Math.max(range.start, range.end) + const start = rowIndex(range.start, range.side) + if (start === undefined) continue - for (let line = start; line <= end; line++) { - const expectedSide = - line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide) + const end = (() => { + const same = range.end === range.start && (range.endSide == null || range.endSide === range.side) + if (same) return start + return rowIndex(range.end, range.endSide ?? range.side) + })() + if (end === undefined) continue - const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)) - for (const node of nodes) { - if (!(node instanceof HTMLElement)) continue - if (expectedSide && findSide(node) !== expectedSide) continue - node.setAttribute("data-comment-selected", "") + const first = Math.min(start, end) + const last = Math.max(start, end) + + for (const block of code) { + for (const element of Array.from(block.children)) { + if (!(element instanceof HTMLElement)) continue + const idx = lineIndex(element) + if (idx === undefined) continue + if (idx > last) break + if (idx < first) continue + element.setAttribute("data-comment-selected", "") + const next = element.nextSibling + if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) { + next.setAttribute("data-comment-selected", "") + } } } } diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx index 5aff93d1d..2fb2ea3bc 100644 --- a/packages/ui/src/components/diff.tsx +++ b/packages/ui/src/components/diff.tsx @@ -191,24 +191,69 @@ export function Diff(props: DiffProps) { node.removeAttribute("data-comment-selected") } + const diffs = root.querySelector("[data-diffs]") + if (!(diffs instanceof HTMLElement)) return + + const split = diffs.dataset.type === "split" + + const code = Array.from(diffs.querySelectorAll("[data-code]")).filter( + (node): node is HTMLElement => node instanceof HTMLElement, + ) + if (code.length === 0) return + + const lineIndex = (element: HTMLElement) => { + const raw = element.dataset.lineIndex + if (!raw) return + const values = raw + .split(",") + .map((value) => parseInt(value, 10)) + .filter((value) => !Number.isNaN(value)) + if (values.length === 0) return + if (!split) return values[0] + if (values.length === 2) return values[1] + return values[0] + } + + const rowIndex = (line: number, side: SelectionSide | undefined) => { + const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter( + (node): node is HTMLElement => node instanceof HTMLElement, + ) + if (nodes.length === 0) return + + const targetSide = side ?? "additions" + + for (const node of nodes) { + if (findSide(node) === targetSide) return lineIndex(node) + if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node) + } + } + for (const range of ranges) { - const start = Math.max(1, Math.min(range.start, range.end)) - const end = Math.max(range.start, range.end) + const start = rowIndex(range.start, range.side) + if (start === undefined) continue - for (let line = start; line <= end; line++) { - const expectedSide = - line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide) + const end = (() => { + const same = range.end === range.start && (range.endSide == null || range.endSide === range.side) + if (same) return start + return rowIndex(range.end, range.endSide ?? range.side) + })() + if (end === undefined) continue - const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)) - for (const node of nodes) { - if (!(node instanceof HTMLElement)) continue + const first = Math.min(start, end) + const last = Math.max(start, end) - if (expectedSide) { - const side = findSide(node) - if (side && side !== expectedSide) continue + for (const block of code) { + for (const element of Array.from(block.children)) { + if (!(element instanceof HTMLElement)) continue + const idx = lineIndex(element) + if (idx === undefined) continue + if (idx > last) break + if (idx < first) continue + element.setAttribute("data-comment-selected", "") + const next = element.nextSibling + if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) { + next.setAttribute("data-comment-selected", "") } - - node.setAttribute("data-comment-selected", "") } } } diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts index 54262e4aa..a83af9890 100644 --- a/packages/ui/src/pierre/index.ts +++ b/packages/ui/src/pierre/index.ts @@ -44,12 +44,12 @@ const unsafeCSS = ` background-color: var(--diffs-bg-selection-text); } -[data-diffs] [data-comment-selected] { - background-color: var(--diffs-bg-selection); +[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-content] { + box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection); } -[data-diffs] [data-comment-selected] [data-column-number] { - background-color: var(--diffs-bg-selection-number); +[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-number] { + box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection-number); color: var(--diffs-selection-number-fg); } @@ -73,7 +73,7 @@ const unsafeCSS = ` /* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */ [data-diffs] [data-line-type='change-deletion'][data-selected-line] { --diffs-bg-deletion-emphasis: light-dark( - rgb(from var(--diffs-deletion-base) r g b / 0.15), + rgb(from var(--diffs-deletion-base) r g b / 0.07), rgb(from var(--diffs-deletion-base) r g b / 0.1) ); }