wip(ui): diff virtualization (#12693)
parent
5f421883a8
commit
ecb274273a
30
bun.lock
30
bun.lock
|
|
@ -513,7 +513,7 @@
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@pierre/diffs": "1.0.2",
|
"@pierre/diffs": "1.1.0-beta.13",
|
||||||
"@playwright/test": "1.51.0",
|
"@playwright/test": "1.51.0",
|
||||||
"@solid-primitives/storage": "4.3.3",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@solidjs/meta": "0.29.4",
|
"@solidjs/meta": "0.29.4",
|
||||||
|
|
@ -1409,7 +1409,7 @@
|
||||||
|
|
||||||
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
|
||||||
|
|
||||||
"@pierre/diffs": ["@pierre/diffs@1.0.2", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-RkFSDD5X/U+8QjyilPViYGJfmJNWXR17zTL8zw48+DcVC1Ujbh6I1edyuRnFfgRzpft05x2DSCkz2cjoIAxPvQ=="],
|
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
|
||||||
|
|
||||||
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||||
|
|
||||||
|
|
@ -4387,13 +4387,9 @@
|
||||||
|
|
||||||
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
|
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ=="],
|
"@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/types": "3.19.0" } }, "sha512-e6vwrsyw+wx4OkcrDbL+FVCxwx8jgKiCoXzakVur++mIWVcgpzIi8vxf4/b4dVTYrV/nUx5RjinMf4tq8YV8Fw=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki": ["shiki@3.19.0", "", { "dependencies": { "@shikijs/core": "3.19.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/engine-oniguruma": "3.19.0", "@shikijs/langs": "3.19.0", "@shikijs/themes": "3.19.0", "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA=="],
|
|
||||||
|
|
||||||
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||||
|
|
||||||
|
|
@ -4973,23 +4969,9 @@
|
||||||
|
|
||||||
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
"@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki/@shikijs/core": ["@shikijs/core@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.19.0", "", { "dependencies": { "@shikijs/types": "3.19.0" } }, "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A=="],
|
|
||||||
|
|
||||||
"@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
|
|
||||||
|
|
||||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
"@tsconfig/bun": "1.0.9",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@pierre/diffs": "1.0.2",
|
"@pierre/diffs": "1.1.0-beta.13",
|
||||||
"@solid-primitives/storage": "4.3.3",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
|
||||||
open={props.view().review.open()}
|
open={props.view().review.open()}
|
||||||
onOpenChange={props.view().review.setOpen}
|
onOpenChange={props.view().review.setOpen}
|
||||||
classes={{
|
classes={{
|
||||||
root: props.classes?.root ?? "pb-40",
|
root: props.classes?.root ?? "pb-6",
|
||||||
header: props.classes?.header ?? "px-6",
|
header: props.classes?.header ?? "px-6",
|
||||||
container: props.classes?.container ?? "px-6",
|
container: props.classes?.container ?? "px-6",
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ export function Code<T>(props: CodeProps<T>) {
|
||||||
const needle = query.toLowerCase()
|
const needle = query.toLowerCase()
|
||||||
const out: Range[] = []
|
const out: Range[] = []
|
||||||
|
|
||||||
const cols = Array.from(root.querySelectorAll("[data-column-content]")).filter(
|
const cols = Array.from(root.querySelectorAll("[data-content] [data-line], [data-column-content]")).filter(
|
||||||
(node): node is HTMLElement => node instanceof HTMLElement,
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -537,17 +537,28 @@ export function Code<T>(props: CodeProps<T>) {
|
||||||
node.removeAttribute("data-comment-selected")
|
node.removeAttribute("data-comment-selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const annotations = Array.from(root.querySelectorAll("[data-line-annotation]")).filter(
|
||||||
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
|
)
|
||||||
|
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
const start = Math.max(1, Math.min(range.start, range.end))
|
const start = Math.max(1, Math.min(range.start, range.end))
|
||||||
const end = Math.max(range.start, range.end)
|
const end = Math.max(range.start, range.end)
|
||||||
|
|
||||||
for (let line = start; line <= end; line++) {
|
for (let line = start; line <= end; line++) {
|
||||||
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"]`))
|
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-column-number="${line}"]`))
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (!(node instanceof HTMLElement)) continue
|
if (!(node instanceof HTMLElement)) continue
|
||||||
node.setAttribute("data-comment-selected", "")
|
node.setAttribute("data-comment-selected", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const annotation of annotations) {
|
||||||
|
const line = parseInt(annotation.dataset.lineAnnotation?.split(",")[1] ?? "", 10)
|
||||||
|
if (Number.isNaN(line)) continue
|
||||||
|
if (line < start || line > end) continue
|
||||||
|
annotation.setAttribute("data-comment-selected", "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { DIFFS_TAG_NAME, FileDiff, type SelectedLineRange } from "@pierre/diffs"
|
import { DIFFS_TAG_NAME, FileDiff, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs"
|
||||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||||
import { Dynamic, isServer } from "solid-js/web"
|
import { Dynamic, isServer } from "solid-js/web"
|
||||||
import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
|
import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
|
||||||
|
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
||||||
import { useWorkerPool } from "../context/worker-pool"
|
import { useWorkerPool } from "../context/worker-pool"
|
||||||
|
|
||||||
export type SSRDiffProps<T = {}> = DiffProps<T> & {
|
export type SSRDiffProps<T = {}> = DiffProps<T> & {
|
||||||
|
|
@ -24,10 +25,21 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
const workerPool = useWorkerPool(props.diffStyle)
|
const workerPool = useWorkerPool(props.diffStyle)
|
||||||
|
|
||||||
let fileDiffInstance: FileDiff<T> | undefined
|
let fileDiffInstance: FileDiff<T> | undefined
|
||||||
|
let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
||||||
const cleanupFunctions: Array<() => void> = []
|
const cleanupFunctions: Array<() => void> = []
|
||||||
|
|
||||||
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
||||||
|
|
||||||
|
const getVirtualizer = () => {
|
||||||
|
if (sharedVirtualizer) return sharedVirtualizer.virtualizer
|
||||||
|
|
||||||
|
const result = acquireVirtualizer(container)
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
sharedVirtualizer = result
|
||||||
|
return result.virtualizer
|
||||||
|
}
|
||||||
|
|
||||||
const applyScheme = () => {
|
const applyScheme = () => {
|
||||||
const scheme = document.documentElement.dataset.colorScheme
|
const scheme = document.documentElement.dataset.colorScheme
|
||||||
if (scheme === "dark" || scheme === "light") {
|
if (scheme === "dark" || scheme === "light") {
|
||||||
|
|
@ -70,10 +82,10 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
const root = getRoot()
|
const root = getRoot()
|
||||||
if (!root) return
|
if (!root) return
|
||||||
|
|
||||||
const diffs = root.querySelector("[data-diffs]")
|
const diffs = root.querySelector("[data-diff]")
|
||||||
if (!(diffs instanceof HTMLElement)) return
|
if (!(diffs instanceof HTMLElement)) return
|
||||||
|
|
||||||
const split = diffs.dataset.type === "split"
|
const split = diffs.dataset.diffType === "split"
|
||||||
|
|
||||||
const start = rowIndex(root, split, range.start, range.side)
|
const start = rowIndex(root, split, range.start, range.side)
|
||||||
const end = rowIndex(root, split, range.end, range.endSide ?? range.side)
|
const end = rowIndex(root, split, range.end, range.endSide ?? range.side)
|
||||||
|
|
@ -132,15 +144,19 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
node.removeAttribute("data-comment-selected")
|
node.removeAttribute("data-comment-selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
const diffs = root.querySelector("[data-diffs]")
|
const diffs = root.querySelector("[data-diff]")
|
||||||
if (!(diffs instanceof HTMLElement)) return
|
if (!(diffs instanceof HTMLElement)) return
|
||||||
|
|
||||||
const split = diffs.dataset.type === "split"
|
const split = diffs.dataset.diffType === "split"
|
||||||
|
|
||||||
const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
|
const rows = Array.from(diffs.querySelectorAll("[data-line-index]")).filter(
|
||||||
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
|
)
|
||||||
|
if (rows.length === 0) return
|
||||||
|
|
||||||
|
const annotations = Array.from(diffs.querySelectorAll("[data-line-annotation]")).filter(
|
||||||
(node): node is HTMLElement => node instanceof HTMLElement,
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
)
|
)
|
||||||
if (code.length === 0) return
|
|
||||||
|
|
||||||
const lineIndex = (element: HTMLElement) => {
|
const lineIndex = (element: HTMLElement) => {
|
||||||
const raw = element.dataset.lineIndex
|
const raw = element.dataset.lineIndex
|
||||||
|
|
@ -183,19 +199,18 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
const first = Math.min(start, end)
|
const first = Math.min(start, end)
|
||||||
const last = Math.max(start, end)
|
const last = Math.max(start, end)
|
||||||
|
|
||||||
for (const block of code) {
|
for (const row of rows) {
|
||||||
for (const element of Array.from(block.children)) {
|
const idx = lineIndex(row)
|
||||||
if (!(element instanceof HTMLElement)) continue
|
if (idx === undefined) continue
|
||||||
const idx = lineIndex(element)
|
if (idx < first || idx > last) continue
|
||||||
if (idx === undefined) continue
|
row.setAttribute("data-comment-selected", "")
|
||||||
if (idx > last) break
|
}
|
||||||
if (idx < first) continue
|
|
||||||
element.setAttribute("data-comment-selected", "")
|
for (const annotation of annotations) {
|
||||||
const next = element.nextSibling
|
const idx = parseInt(annotation.dataset.lineAnnotation?.split(",")[1] ?? "", 10)
|
||||||
if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
|
if (Number.isNaN(idx)) continue
|
||||||
next.setAttribute("data-comment-selected", "")
|
if (idx < first || idx > last) continue
|
||||||
}
|
annotation.setAttribute("data-comment-selected", "")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,14 +227,27 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
onCleanup(() => monitor.disconnect())
|
onCleanup(() => monitor.disconnect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fileDiffInstance = new FileDiff<T>(
|
const virtualizer = getVirtualizer()
|
||||||
{
|
|
||||||
...createDefaultOptions(props.diffStyle),
|
fileDiffInstance = virtualizer
|
||||||
...others,
|
? new VirtualizedFileDiff<T>(
|
||||||
...props.preloadedDiff,
|
{
|
||||||
},
|
...createDefaultOptions(props.diffStyle),
|
||||||
workerPool,
|
...others,
|
||||||
)
|
...props.preloadedDiff,
|
||||||
|
},
|
||||||
|
virtualizer,
|
||||||
|
virtualMetrics,
|
||||||
|
workerPool,
|
||||||
|
)
|
||||||
|
: new FileDiff<T>(
|
||||||
|
{
|
||||||
|
...createDefaultOptions(props.diffStyle),
|
||||||
|
...others,
|
||||||
|
...props.preloadedDiff,
|
||||||
|
},
|
||||||
|
workerPool,
|
||||||
|
)
|
||||||
// @ts-expect-error - fileContainer is private but needed for SSR hydration
|
// @ts-expect-error - fileContainer is private but needed for SSR hydration
|
||||||
fileDiffInstance.fileContainer = fileDiffRef
|
fileDiffInstance.fileContainer = fileDiffRef
|
||||||
fileDiffInstance.hydrate({
|
fileDiffInstance.hydrate({
|
||||||
|
|
@ -273,6 +301,8 @@ export function Diff<T>(props: SSRDiffProps<T>) {
|
||||||
// Clean up FileDiff event handlers and dispose SolidJS components
|
// Clean up FileDiff event handlers and dispose SolidJS components
|
||||||
fileDiffInstance?.cleanUp()
|
fileDiffInstance?.cleanUp()
|
||||||
cleanupFunctions.forEach((dispose) => dispose())
|
cleanupFunctions.forEach((dispose) => dispose())
|
||||||
|
sharedVirtualizer?.release()
|
||||||
|
sharedVirtualizer = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { checksum } from "@opencode-ai/util/encode"
|
import { checksum } from "@opencode-ai/util/encode"
|
||||||
import { FileDiff, type SelectedLineRange } from "@pierre/diffs"
|
import { FileDiff, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { createEffect, createMemo, createSignal, onCleanup, splitProps } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup, splitProps } from "solid-js"
|
||||||
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
|
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
|
||||||
|
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
||||||
import { getWorkerPool } from "../pierre/worker"
|
import { getWorkerPool } from "../pierre/worker"
|
||||||
|
|
||||||
type SelectionSide = "additions" | "deletions"
|
type SelectionSide = "additions" | "deletions"
|
||||||
|
|
@ -52,6 +53,7 @@ function findSide(node: Node | null): SelectionSide | undefined {
|
||||||
export function Diff<T>(props: DiffProps<T>) {
|
export function Diff<T>(props: DiffProps<T>) {
|
||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
let observer: MutationObserver | undefined
|
let observer: MutationObserver | undefined
|
||||||
|
let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
||||||
let renderToken = 0
|
let renderToken = 0
|
||||||
let selectionFrame: number | undefined
|
let selectionFrame: number | undefined
|
||||||
let dragFrame: number | undefined
|
let dragFrame: number | undefined
|
||||||
|
|
@ -92,6 +94,16 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
const [current, setCurrent] = createSignal<FileDiff<T> | undefined>(undefined)
|
const [current, setCurrent] = createSignal<FileDiff<T> | undefined>(undefined)
|
||||||
const [rendered, setRendered] = createSignal(0)
|
const [rendered, setRendered] = createSignal(0)
|
||||||
|
|
||||||
|
const getVirtualizer = () => {
|
||||||
|
if (sharedVirtualizer) return sharedVirtualizer.virtualizer
|
||||||
|
|
||||||
|
const result = acquireVirtualizer(container)
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
sharedVirtualizer = result
|
||||||
|
return result.virtualizer
|
||||||
|
}
|
||||||
|
|
||||||
const getRoot = () => {
|
const getRoot = () => {
|
||||||
const host = container.querySelector("diffs-container")
|
const host = container.querySelector("diffs-container")
|
||||||
if (!(host instanceof HTMLElement)) return
|
if (!(host instanceof HTMLElement)) return
|
||||||
|
|
@ -147,10 +159,10 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
const root = getRoot()
|
const root = getRoot()
|
||||||
if (!root) return
|
if (!root) return
|
||||||
|
|
||||||
const diffs = root.querySelector("[data-diffs]")
|
const diffs = root.querySelector("[data-diff]")
|
||||||
if (!(diffs instanceof HTMLElement)) return
|
if (!(diffs instanceof HTMLElement)) return
|
||||||
|
|
||||||
const split = diffs.dataset.type === "split"
|
const split = diffs.dataset.diffType === "split"
|
||||||
|
|
||||||
const start = rowIndex(root, split, range.start, range.side)
|
const start = rowIndex(root, split, range.start, range.side)
|
||||||
const end = rowIndex(root, split, range.end, range.endSide ?? range.side)
|
const end = rowIndex(root, split, range.end, range.endSide ?? range.side)
|
||||||
|
|
@ -261,15 +273,19 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
node.removeAttribute("data-comment-selected")
|
node.removeAttribute("data-comment-selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
const diffs = root.querySelector("[data-diffs]")
|
const diffs = root.querySelector("[data-diff]")
|
||||||
if (!(diffs instanceof HTMLElement)) return
|
if (!(diffs instanceof HTMLElement)) return
|
||||||
|
|
||||||
const split = diffs.dataset.type === "split"
|
const split = diffs.dataset.diffType === "split"
|
||||||
|
|
||||||
const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
|
const rows = Array.from(diffs.querySelectorAll("[data-line-index]")).filter(
|
||||||
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
|
)
|
||||||
|
if (rows.length === 0) return
|
||||||
|
|
||||||
|
const annotations = Array.from(diffs.querySelectorAll("[data-line-annotation]")).filter(
|
||||||
(node): node is HTMLElement => node instanceof HTMLElement,
|
(node): node is HTMLElement => node instanceof HTMLElement,
|
||||||
)
|
)
|
||||||
if (code.length === 0) return
|
|
||||||
|
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
const start = rowIndex(root, split, range.start, range.side)
|
const start = rowIndex(root, split, range.start, range.side)
|
||||||
|
|
@ -285,19 +301,18 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
const first = Math.min(start, end)
|
const first = Math.min(start, end)
|
||||||
const last = Math.max(start, end)
|
const last = Math.max(start, end)
|
||||||
|
|
||||||
for (const block of code) {
|
for (const row of rows) {
|
||||||
for (const element of Array.from(block.children)) {
|
const idx = lineIndex(split, row)
|
||||||
if (!(element instanceof HTMLElement)) continue
|
if (idx === undefined) continue
|
||||||
const idx = lineIndex(split, element)
|
if (idx < first || idx > last) continue
|
||||||
if (idx === undefined) continue
|
row.setAttribute("data-comment-selected", "")
|
||||||
if (idx > last) break
|
}
|
||||||
if (idx < first) continue
|
|
||||||
element.setAttribute("data-comment-selected", "")
|
for (const annotation of annotations) {
|
||||||
const next = element.nextSibling
|
const idx = parseInt(annotation.dataset.lineAnnotation?.split(",")[1] ?? "", 10)
|
||||||
if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
|
if (Number.isNaN(idx)) continue
|
||||||
next.setAttribute("data-comment-selected", "")
|
if (idx < first || idx > last) continue
|
||||||
}
|
annotation.setAttribute("data-comment-selected", "")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -514,12 +529,15 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const opts = options()
|
const opts = options()
|
||||||
const workerPool = getWorkerPool(props.diffStyle)
|
const workerPool = getWorkerPool(props.diffStyle)
|
||||||
|
const virtualizer = getVirtualizer()
|
||||||
const annotations = local.annotations
|
const annotations = local.annotations
|
||||||
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
||||||
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
||||||
|
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
instance = new FileDiff<T>(opts, workerPool)
|
instance = virtualizer
|
||||||
|
? new VirtualizedFileDiff<T>(opts, virtualizer, virtualMetrics, workerPool)
|
||||||
|
: new FileDiff<T>(opts, workerPool)
|
||||||
setCurrent(instance)
|
setCurrent(instance)
|
||||||
|
|
||||||
container.innerHTML = ""
|
container.innerHTML = ""
|
||||||
|
|
@ -606,6 +624,8 @@ export function Diff<T>(props: DiffProps<T>) {
|
||||||
|
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
setCurrent(undefined)
|
setCurrent(undefined)
|
||||||
|
sharedVirtualizer?.release()
|
||||||
|
sharedVirtualizer = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div data-component="diff" style={styleVariables} ref={container} />
|
return <div data-component="diff" style={styleVariables} ref={container} />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ registerCustomTheme("OpenCode", () => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
name: "OpenCode",
|
name: "OpenCode",
|
||||||
colors: {
|
colors: {
|
||||||
"editor.background": "transparent",
|
"editor.background": "var(--color-background-stronger)",
|
||||||
"editor.foreground": "var(--text-base)",
|
"editor.foreground": "var(--text-base)",
|
||||||
"gitDecoration.addedResourceForeground": "var(--syntax-diff-add)",
|
"gitDecoration.addedResourceForeground": "var(--syntax-diff-add)",
|
||||||
"gitDecoration.deletedResourceForeground": "var(--syntax-diff-delete)",
|
"gitDecoration.deletedResourceForeground": "var(--syntax-diff-delete)",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsafeCSS = `
|
const unsafeCSS = `
|
||||||
[data-diffs] {
|
[data-diff] {
|
||||||
--diffs-bg: light-dark(var(--diffs-light-bg), var(--diffs-dark-bg));
|
--diffs-bg: light-dark(var(--diffs-light-bg), var(--diffs-dark-bg));
|
||||||
--diffs-bg-buffer: var(--diffs-bg-buffer-override, light-dark( color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer))));
|
--diffs-bg-buffer: var(--diffs-bg-buffer-override, light-dark( color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-mixer))));
|
||||||
--diffs-bg-hover: var(--diffs-bg-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 97%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-mixer))));
|
--diffs-bg-hover: var(--diffs-bg-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 97%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-mixer))));
|
||||||
|
|
@ -44,7 +44,7 @@ const unsafeCSS = `
|
||||||
--diffs-bg-selection-text: rgb(from var(--surface-warning-strong) r g b / 0.2);
|
--diffs-bg-selection-text: rgb(from var(--surface-warning-strong) r g b / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-color-scheme='dark']) [data-diffs] {
|
:host([data-color-scheme='dark']) [data-diff] {
|
||||||
--diffs-selection-number-fg: #fdfbfb;
|
--diffs-selection-number-fg: #fdfbfb;
|
||||||
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--solaris-dark-6) r g b / 0.65));
|
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--solaris-dark-6) r g b / 0.65));
|
||||||
--diffs-bg-selection-number: var(
|
--diffs-bg-selection-number: var(
|
||||||
|
|
@ -53,7 +53,7 @@ const unsafeCSS = `
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] ::selection {
|
[data-diff] ::selection {
|
||||||
background-color: var(--diffs-bg-selection-text);
|
background-color: var(--diffs-bg-selection-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,61 +65,48 @@ const unsafeCSS = `
|
||||||
background-color: rgb(from var(--surface-warning-strong) r g b / 0.55);
|
background-color: rgb(from var(--surface-warning-strong) r g b / 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-content] {
|
[data-diff] [data-line][data-comment-selected]:not([data-selected-line]) {
|
||||||
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
|
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-number] {
|
[data-diff] [data-column-number][data-comment-selected]:not([data-selected-line]) {
|
||||||
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection-number);
|
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection-number);
|
||||||
color: var(--diffs-selection-number-fg);
|
color: var(--diffs-selection-number-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] [data-selected-line] {
|
[data-diff] [data-line-annotation][data-comment-selected]:not([data-selected-line]) [data-annotation-content] {
|
||||||
|
box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-diff] [data-line][data-selected-line] {
|
||||||
background-color: var(--diffs-bg-selection);
|
background-color: var(--diffs-bg-selection);
|
||||||
box-shadow: inset 2px 0 0 var(--diffs-selection-border);
|
box-shadow: inset 2px 0 0 var(--diffs-selection-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] [data-selected-line] [data-column-number] {
|
[data-diff] [data-column-number][data-selected-line] {
|
||||||
background-color: var(--diffs-bg-selection-number);
|
background-color: var(--diffs-bg-selection-number);
|
||||||
color: var(--diffs-selection-number-fg);
|
color: var(--diffs-selection-number-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs] [data-line-type='context'][data-selected-line] [data-column-number],
|
[data-diff] [data-column-number][data-line-type='context'][data-selected-line],
|
||||||
[data-diffs] [data-line-type='context-expanded'][data-selected-line] [data-column-number],
|
[data-diff] [data-column-number][data-line-type='context-expanded'][data-selected-line],
|
||||||
[data-diffs] [data-line-type='change-addition'][data-selected-line] [data-column-number],
|
[data-diff] [data-column-number][data-line-type='change-addition'][data-selected-line],
|
||||||
[data-diffs] [data-line-type='change-deletion'][data-selected-line] [data-column-number] {
|
[data-diff] [data-column-number][data-line-type='change-deletion'][data-selected-line] {
|
||||||
color: var(--diffs-selection-number-fg);
|
color: var(--diffs-selection-number-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */
|
/* 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] {
|
[data-diff] [data-line][data-line-type='change-deletion'][data-selected-line] {
|
||||||
--diffs-bg-deletion-emphasis: light-dark(
|
--diffs-bg-deletion-emphasis: light-dark(
|
||||||
rgb(from var(--diffs-deletion-base) r g b / 0.07),
|
rgb(from var(--diffs-deletion-base) r g b / 0.07),
|
||||||
rgb(from var(--diffs-deletion-base) r g b / 0.1)
|
rgb(from var(--diffs-deletion-base) r g b / 0.1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-diffs-header],
|
[data-diff-header],
|
||||||
[data-diffs] {
|
[data-diff] {
|
||||||
[data-separator-wrapper] {
|
[data-separator] {
|
||||||
margin: 0 !important;
|
height: 24px;
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
[data-expand-button] {
|
|
||||||
width: 6.5ch !important;
|
|
||||||
height: 24px !important;
|
|
||||||
justify-content: end !important;
|
|
||||||
padding-left: 3ch !important;
|
|
||||||
padding-inline: 1ch !important;
|
|
||||||
}
|
|
||||||
[data-separator-multi-button] {
|
|
||||||
grid-template-rows: 10px 10px !important;
|
|
||||||
[data-expand-button] {
|
|
||||||
height: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-separator-content] {
|
|
||||||
height: 24px !important;
|
|
||||||
}
|
}
|
||||||
[data-column-number] {
|
[data-column-number] {
|
||||||
background-color: var(--background-stronger);
|
background-color: var(--background-stronger);
|
||||||
|
|
@ -146,28 +133,15 @@ export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"])
|
||||||
overflow: "wrap",
|
overflow: "wrap",
|
||||||
diffStyle: style ?? "unified",
|
diffStyle: style ?? "unified",
|
||||||
diffIndicators: "bars",
|
diffIndicators: "bars",
|
||||||
|
lineHoverHighlight: "both",
|
||||||
disableBackground: false,
|
disableBackground: false,
|
||||||
expansionLineCount: 20,
|
expansionLineCount: 20,
|
||||||
|
hunkSeparators: "line-info-basic",
|
||||||
lineDiffType: style === "split" ? "word-alt" : "none",
|
lineDiffType: style === "split" ? "word-alt" : "none",
|
||||||
maxLineDiffLength: 1000,
|
maxLineDiffLength: 1000,
|
||||||
maxLineLengthForHighlighting: 1000,
|
maxLineLengthForHighlighting: 1000,
|
||||||
disableFileHeader: true,
|
disableFileHeader: true,
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
// hunkSeparators(hunkData: HunkData) {
|
|
||||||
// const fragment = document.createDocumentFragment()
|
|
||||||
// const numCol = document.createElement("div")
|
|
||||||
// numCol.innerHTML = `<svg data-slot="diff-hunk-separator-line-number-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.97978 14.0204L8.62623 13.6668L9.33334 12.9597L9.68689 13.3133L9.33333 13.6668L8.97978 14.0204ZM12 16.3335L12.3535 16.6871L12 17.0406L11.6464 16.687L12 16.3335ZM14.3131 13.3133L14.6667 12.9597L15.3738 13.6668L15.0202 14.0204L14.6667 13.6668L14.3131 13.3133ZM12.5 16.0002V16.5002H11.5V16.0002H12H12.5ZM9.33333 13.6668L9.68689 13.3133L12.3535 15.9799L12 16.3335L11.6464 16.687L8.97978 14.0204L9.33333 13.6668ZM12 16.3335L11.6464 15.9799L14.3131 13.3133L14.6667 13.6668L15.0202 14.0204L12.3535 16.6871L12 16.3335ZM6.5 8.00016V7.50016H8.5V8.00016V8.50016H6.5V8.00016ZM9.5 8.00016V7.50016H11.5V8.00016V8.50016H9.5V8.00016ZM12.5 8.00016V7.50016H14.5V8.00016V8.50016H12.5V8.00016ZM15.5 8.00016V7.50016H17.5V8.00016V8.50016H15.5V8.00016ZM12 10.5002H12.5V16.0002H12H11.5V10.5002H12Z" fill="currentColor"/></svg> `
|
|
||||||
// numCol.dataset["slot"] = "diff-hunk-separator-line-number"
|
|
||||||
// fragment.appendChild(numCol)
|
|
||||||
// const contentCol = document.createElement("div")
|
|
||||||
// contentCol.dataset["slot"] = "diff-hunk-separator-content"
|
|
||||||
// const span = document.createElement("span")
|
|
||||||
// span.dataset["slot"] = "diff-hunk-separator-content-span"
|
|
||||||
// span.textContent = `${hunkData.lines} unmodified lines`
|
|
||||||
// contentCol.appendChild(span)
|
|
||||||
// fragment.appendChild(contentCol)
|
|
||||||
// return fragment
|
|
||||||
// },
|
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { type VirtualFileMetrics, Virtualizer } from "@pierre/diffs"
|
||||||
|
|
||||||
|
type Target = {
|
||||||
|
key: Document | HTMLElement
|
||||||
|
root: Document | HTMLElement
|
||||||
|
content: HTMLElement | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry = {
|
||||||
|
virtualizer: Virtualizer
|
||||||
|
refs: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = new WeakMap<Document | HTMLElement, Entry>()
|
||||||
|
|
||||||
|
export const virtualMetrics: Partial<VirtualFileMetrics> = {
|
||||||
|
lineHeight: 24,
|
||||||
|
hunkSeparatorHeight: 24,
|
||||||
|
fileGap: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
function target(container: HTMLElement): Target | undefined {
|
||||||
|
if (typeof document === "undefined") return
|
||||||
|
|
||||||
|
const root = container.closest("[data-component='session-review']")
|
||||||
|
if (root instanceof HTMLElement) {
|
||||||
|
const content = root.querySelector("[data-slot='session-review-container']")
|
||||||
|
return {
|
||||||
|
key: root,
|
||||||
|
root,
|
||||||
|
content: content instanceof HTMLElement ? content : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: document,
|
||||||
|
root: document,
|
||||||
|
content: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function acquireVirtualizer(container: HTMLElement) {
|
||||||
|
const resolved = target(container)
|
||||||
|
if (!resolved) return
|
||||||
|
|
||||||
|
let entry = cache.get(resolved.key)
|
||||||
|
if (!entry) {
|
||||||
|
const virtualizer = new Virtualizer()
|
||||||
|
virtualizer.setup(resolved.root, resolved.content)
|
||||||
|
entry = {
|
||||||
|
virtualizer,
|
||||||
|
refs: 0,
|
||||||
|
}
|
||||||
|
cache.set(resolved.key, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.refs += 1
|
||||||
|
let done = false
|
||||||
|
|
||||||
|
return {
|
||||||
|
virtualizer: entry.virtualizer,
|
||||||
|
release() {
|
||||||
|
if (done) return
|
||||||
|
done = true
|
||||||
|
|
||||||
|
const current = cache.get(resolved.key)
|
||||||
|
if (!current) return
|
||||||
|
|
||||||
|
current.refs -= 1
|
||||||
|
if (current.refs > 0) return
|
||||||
|
|
||||||
|
current.virtualizer.cleanUp()
|
||||||
|
cache.delete(resolved.key)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ function createPool(lineDiffType: "none" | "word-alt") {
|
||||||
{
|
{
|
||||||
theme: "OpenCode",
|
theme: "OpenCode",
|
||||||
lineDiffType,
|
lineDiffType,
|
||||||
|
preferredHighlighter: "shiki-wasm",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue