Skip to content

Commit ecb2742

Browse files
authored
wip(ui): diff virtualization (anomalyco#12693)
1 parent 5f42188 commit ecb2742

10 files changed

Lines changed: 220 additions & 126 deletions

File tree

bun.lock

Lines changed: 6 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@tsconfig/bun": "1.0.9",
3636
"@cloudflare/workers-types": "4.20251008.0",
3737
"@openauthjs/openauth": "0.0.0-20250322224806",
38-
"@pierre/diffs": "1.0.2",
38+
"@pierre/diffs": "1.1.0-beta.13",
3939
"@solid-primitives/storage": "4.3.3",
4040
"@tailwindcss/vite": "4.1.11",
4141
"diff": "8.0.2",

packages/app/src/pages/session/review-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
139139
open={props.view().review.open()}
140140
onOpenChange={props.view().review.setOpen}
141141
classes={{
142-
root: props.classes?.root ?? "pb-40",
142+
root: props.classes?.root ?? "pb-6",
143143
header: props.classes?.header ?? "px-6",
144144
container: props.classes?.container ?? "px-6",
145145
}}

packages/ui/src/components/code.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export function Code<T>(props: CodeProps<T>) {
318318
const needle = query.toLowerCase()
319319
const out: Range[] = []
320320

321-
const cols = Array.from(root.querySelectorAll("[data-column-content]")).filter(
321+
const cols = Array.from(root.querySelectorAll("[data-content] [data-line], [data-column-content]")).filter(
322322
(node): node is HTMLElement => node instanceof HTMLElement,
323323
)
324324

@@ -537,17 +537,28 @@ export function Code<T>(props: CodeProps<T>) {
537537
node.removeAttribute("data-comment-selected")
538538
}
539539

540+
const annotations = Array.from(root.querySelectorAll("[data-line-annotation]")).filter(
541+
(node): node is HTMLElement => node instanceof HTMLElement,
542+
)
543+
540544
for (const range of ranges) {
541545
const start = Math.max(1, Math.min(range.start, range.end))
542546
const end = Math.max(range.start, range.end)
543547

544548
for (let line = start; line <= end; line++) {
545-
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"]`))
549+
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-column-number="${line}"]`))
546550
for (const node of nodes) {
547551
if (!(node instanceof HTMLElement)) continue
548552
node.setAttribute("data-comment-selected", "")
549553
}
550554
}
555+
556+
for (const annotation of annotations) {
557+
const line = parseInt(annotation.dataset.lineAnnotation?.split(",")[1] ?? "", 10)
558+
if (Number.isNaN(line)) continue
559+
if (line < start || line > end) continue
560+
annotation.setAttribute("data-comment-selected", "")
561+
}
551562
}
552563
}
553564

packages/ui/src/components/diff-ssr.tsx

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { DIFFS_TAG_NAME, FileDiff, type SelectedLineRange } from "@pierre/diffs"
1+
import { DIFFS_TAG_NAME, FileDiff, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs"
22
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
33
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
44
import { Dynamic, isServer } from "solid-js/web"
55
import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
6+
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
67
import { useWorkerPool } from "../context/worker-pool"
78

89
export type SSRDiffProps<T = {}> = DiffProps<T> & {
@@ -24,10 +25,21 @@ export function Diff<T>(props: SSRDiffProps<T>) {
2425
const workerPool = useWorkerPool(props.diffStyle)
2526

2627
let fileDiffInstance: FileDiff<T> | undefined
28+
let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
2729
const cleanupFunctions: Array<() => void> = []
2830

2931
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
3032

33+
const getVirtualizer = () => {
34+
if (sharedVirtualizer) return sharedVirtualizer.virtualizer
35+
36+
const result = acquireVirtualizer(container)
37+
if (!result) return
38+
39+
sharedVirtualizer = result
40+
return result.virtualizer
41+
}
42+
3143
const applyScheme = () => {
3244
const scheme = document.documentElement.dataset.colorScheme
3345
if (scheme === "dark" || scheme === "light") {
@@ -70,10 +82,10 @@ export function Diff<T>(props: SSRDiffProps<T>) {
7082
const root = getRoot()
7183
if (!root) return
7284

73-
const diffs = root.querySelector("[data-diffs]")
85+
const diffs = root.querySelector("[data-diff]")
7486
if (!(diffs instanceof HTMLElement)) return
7587

76-
const split = diffs.dataset.type === "split"
88+
const split = diffs.dataset.diffType === "split"
7789

7890
const start = rowIndex(root, split, range.start, range.side)
7991
const end = rowIndex(root, split, range.end, range.endSide ?? range.side)
@@ -132,15 +144,19 @@ export function Diff<T>(props: SSRDiffProps<T>) {
132144
node.removeAttribute("data-comment-selected")
133145
}
134146

135-
const diffs = root.querySelector("[data-diffs]")
147+
const diffs = root.querySelector("[data-diff]")
136148
if (!(diffs instanceof HTMLElement)) return
137149

138-
const split = diffs.dataset.type === "split"
150+
const split = diffs.dataset.diffType === "split"
151+
152+
const rows = Array.from(diffs.querySelectorAll("[data-line-index]")).filter(
153+
(node): node is HTMLElement => node instanceof HTMLElement,
154+
)
155+
if (rows.length === 0) return
139156

140-
const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
157+
const annotations = Array.from(diffs.querySelectorAll("[data-line-annotation]")).filter(
141158
(node): node is HTMLElement => node instanceof HTMLElement,
142159
)
143-
if (code.length === 0) return
144160

145161
const lineIndex = (element: HTMLElement) => {
146162
const raw = element.dataset.lineIndex
@@ -183,19 +199,18 @@ export function Diff<T>(props: SSRDiffProps<T>) {
183199
const first = Math.min(start, end)
184200
const last = Math.max(start, end)
185201

186-
for (const block of code) {
187-
for (const element of Array.from(block.children)) {
188-
if (!(element instanceof HTMLElement)) continue
189-
const idx = lineIndex(element)
190-
if (idx === undefined) continue
191-
if (idx > last) break
192-
if (idx < first) continue
193-
element.setAttribute("data-comment-selected", "")
194-
const next = element.nextSibling
195-
if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
196-
next.setAttribute("data-comment-selected", "")
197-
}
198-
}
202+
for (const row of rows) {
203+
const idx = lineIndex(row)
204+
if (idx === undefined) continue
205+
if (idx < first || idx > last) continue
206+
row.setAttribute("data-comment-selected", "")
207+
}
208+
209+
for (const annotation of annotations) {
210+
const idx = parseInt(annotation.dataset.lineAnnotation?.split(",")[1] ?? "", 10)
211+
if (Number.isNaN(idx)) continue
212+
if (idx < first || idx > last) continue
213+
annotation.setAttribute("data-comment-selected", "")
199214
}
200215
}
201216
}
@@ -212,14 +227,27 @@ export function Diff<T>(props: SSRDiffProps<T>) {
212227
onCleanup(() => monitor.disconnect())
213228
}
214229

215-
fileDiffInstance = new FileDiff<T>(
216-
{
217-
...createDefaultOptions(props.diffStyle),
218-
...others,
219-
...props.preloadedDiff,
220-
},
221-
workerPool,
222-
)
230+
const virtualizer = getVirtualizer()
231+
232+
fileDiffInstance = virtualizer
233+
? new VirtualizedFileDiff<T>(
234+
{
235+
...createDefaultOptions(props.diffStyle),
236+
...others,
237+
...props.preloadedDiff,
238+
},
239+
virtualizer,
240+
virtualMetrics,
241+
workerPool,
242+
)
243+
: new FileDiff<T>(
244+
{
245+
...createDefaultOptions(props.diffStyle),
246+
...others,
247+
...props.preloadedDiff,
248+
},
249+
workerPool,
250+
)
223251
// @ts-expect-error - fileContainer is private but needed for SSR hydration
224252
fileDiffInstance.fileContainer = fileDiffRef
225253
fileDiffInstance.hydrate({
@@ -273,6 +301,8 @@ export function Diff<T>(props: SSRDiffProps<T>) {
273301
// Clean up FileDiff event handlers and dispose SolidJS components
274302
fileDiffInstance?.cleanUp()
275303
cleanupFunctions.forEach((dispose) => dispose())
304+
sharedVirtualizer?.release()
305+
sharedVirtualizer = undefined
276306
})
277307

278308
return (

0 commit comments

Comments
 (0)