Improve diff rendering pipeline

This commit is contained in:
Bohdan Triapitsyn
2026-06-13 23:55:50 +03:00
parent cf8eac966e
commit 876738229f
12 changed files with 684 additions and 323 deletions
@@ -1,9 +1,12 @@
import React, { useMemo, useRef, useCallback, useEffect } from 'react';
import {
areFilesEqual,
areOptionsEqual,
FileDiff as PierreFileDiff,
VirtualizedFileDiff,
Virtualizer,
type FileContents,
type FileDiffMetadata,
type FileDiffOptions,
type DiffLineAnnotation,
type SelectedLineRange,
@@ -34,6 +37,7 @@ const LARGE_CONTENT_BYTES = 500_000;
interface PierreDiffViewerProps {
original: string;
modified: string;
fileDiff?: FileDiffMetadata;
language: string;
fileName?: string;
renderSideBySide: boolean;
@@ -109,10 +113,17 @@ function makeContentCacheKey(contents: string): string {
return `${contents.length}:${fnv1a32(sample)}`;
}
const extractSelectedCode = (original: string, modified: string, range: SelectedLineRange): string => {
const extractSelectedCode = (
original: string,
modified: string,
fileDiff: FileDiffMetadata | undefined,
range: SelectedLineRange,
): string => {
// Default to modified if side is ambiguous, as users mostly comment on new code
const isOriginal = range.side === 'deletions';
const content = isOriginal ? original : modified;
const content = fileDiff
? (isOriginal ? fileDiff.deletionLines : fileDiff.additionLines).join('')
: (isOriginal ? original : modified);
const lines = content.split('\n');
// Ensure bounds
@@ -132,8 +143,111 @@ const isSameSelection = (left: SelectedLineRange | null, right: SelectedLineRang
return left.start === right.start && left.end === right.end && left.side === right.side;
};
const isScrollable = (value: string): boolean =>
value === 'auto' || value === 'scroll' || value === 'overlay';
const findScrollParent = (node: HTMLElement | null): HTMLElement | null => {
let current = node?.parentElement ?? null;
while (current) {
const style = window.getComputedStyle(current);
if (isScrollable(style.overflowY)) return current;
current = current.parentElement;
}
return null;
};
const preserveScrollPosition = (wrapper: HTMLElement | null, container: HTMLElement | null): (() => void) => {
if (!wrapper || !container || typeof window === 'undefined') return () => {};
const scrollParent = findScrollParent(wrapper);
if (!scrollParent) return () => {};
const height = container.getBoundingClientRect().height;
if (!height) return () => {};
const top = wrapper.getBoundingClientRect().top - scrollParent.getBoundingClientRect().top;
const previousMinHeight = container.style.minHeight;
container.style.minHeight = `${Math.ceil(height)}px`;
let done = false;
return () => {
if (done) return;
done = true;
container.style.minHeight = previousMinHeight;
const nextTop = wrapper.getBoundingClientRect().top - scrollParent.getBoundingClientRect().top;
const delta = nextTop - top;
if (delta) {
scrollParent.scrollTop += delta;
}
};
};
const waitForDiffReady = (
container: HTMLElement,
onReady: () => void,
): (() => void) => {
if (typeof window === 'undefined') return () => {};
let frameId: number | null = null;
let observer: MutationObserver | null = null;
let cancelled = false;
const finish = () => {
if (cancelled) return;
observer?.disconnect();
observer = null;
frameId = window.requestAnimationFrame(() => {
frameId = window.requestAnimationFrame(() => {
if (!cancelled) onReady();
});
});
};
const getRoot = (): ShadowRoot | undefined => {
const host = container.querySelector('diffs-container');
return host?.shadowRoot ?? undefined;
};
const isReady = (root = getRoot()) => {
return Boolean(root?.querySelector('[data-line]'));
};
if (isReady()) {
finish();
} else if (typeof MutationObserver !== 'undefined') {
observer = new MutationObserver(() => {
const root = getRoot();
if (!root) return;
if (isReady(root)) {
finish();
return;
}
observer?.disconnect();
observer = new MutationObserver(() => {
if (isReady(root)) {
finish();
}
});
observer.observe(root, { childList: true, subtree: true });
});
observer.observe(container, { childList: true, subtree: true });
} else {
frameId = window.requestAnimationFrame(finish);
}
return () => {
cancelled = true;
observer?.disconnect();
if (frameId !== null) {
window.cancelAnimationFrame(frameId);
}
};
};
type SharedVirtualizer = {
virtualizer: Virtualizer;
root: Document | HTMLElement;
release: () => void;
};
@@ -192,6 +306,7 @@ function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | n
return {
virtualizer: entry.virtualizer,
root: target.root,
release: () => {
if (released) return;
released = true;
@@ -208,9 +323,48 @@ function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | n
};
}
const wakeVirtualizer = (
instance: PierreFileDiff<unknown>,
sharedVirtualizer: SharedVirtualizer | null,
forceUpdate: () => void,
): (() => void) => {
if (typeof window === 'undefined') return () => {};
const frameIds: number[] = [];
const run = () => {
try {
instance.rerender();
} catch {
// ignored
}
const root = sharedVirtualizer?.root;
if (root instanceof HTMLElement) {
root.dispatchEvent(new Event('scroll', { bubbles: false }));
} else {
document.dispatchEvent(new Event('scroll', { bubbles: false }));
}
window.dispatchEvent(new Event('resize'));
forceUpdate();
};
frameIds.push(window.requestAnimationFrame(run));
frameIds.push(window.requestAnimationFrame(() => {
frameIds.push(window.requestAnimationFrame(run));
}));
return () => {
for (const frameId of frameIds) {
window.cancelAnimationFrame(frameId);
}
};
};
export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
original,
modified,
fileDiff,
language,
fileName,
renderSideBySide,
@@ -229,7 +383,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
source: 'diff',
fileLabel: fileName || 'unknown',
language,
getCodeForRange: (range) => extractSelectedCode(original, modified, range),
getCodeForRange: (range) => extractSelectedCode(original, modified, fileDiff, range),
toStoreRange: (range) => ({
startLine: range.start,
endLine: range.end,
@@ -379,12 +533,28 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const diffThemeKey = `${lightTheme.metadata.id}:${darkTheme.metadata.id}:${isDark ? 'dark' : 'light'}`;
const isLargeContent = useMemo(() => {
if (fileDiff) {
const deletionLength = fileDiff.deletionLines.reduce((total, line) => total + line.length, 0);
const additionLength = fileDiff.additionLines.reduce((total, line) => total + line.length, 0);
return Math.max(deletionLength, additionLength) > LARGE_CONTENT_BYTES;
}
return Math.max(original.length, modified.length) > LARGE_CONTENT_BYTES;
}, [fileDiff, modified.length, original.length]);
const diffRootRef = useRef<HTMLDivElement | null>(null);
const diffContainerRef = useRef<HTMLDivElement | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<unknown> | null>(null);
const sharedVirtualizerRef = useRef<SharedVirtualizer | null>(null);
const instanceVirtualizerRef = useRef<Virtualizer | null>(null);
const instanceWorkerPoolRef = useRef<unknown>(null);
const instanceVirtualHunkSeparatorsRef = useRef<FileDiffOptions<unknown>['hunkSeparators'] | undefined>(undefined);
const instanceFileDiffRef = useRef<FileDiffMetadata | undefined>(undefined);
const instanceOldFileRef = useRef<FileContents | undefined>(undefined);
const instanceNewFileRef = useRef<FileContents | undefined>(undefined);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const workerPool = useWorkerPool(renderSideBySide ? 'split' : 'unified');
const workerPool = useWorkerPool(isLargeContent ? 'unified' : (renderSideBySide ? 'split' : 'unified'));
const lightResolvedTheme = useMemo(() => getResolvedShikiTheme(lightTheme), [lightTheme]);
const darkResolvedTheme = useMemo(() => getResolvedShikiTheme(darkTheme), [darkTheme]);
@@ -464,11 +634,6 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
}, [darkResolvedTheme, diffThemeKey, isDark, lightResolvedTheme]);
const isLargeContent = useMemo(() =>
Math.max(original.length, modified.length) > LARGE_CONTENT_BYTES,
[original.length, modified.length],
);
const options = useMemo(() => ({
theme: {
dark: darkTheme.metadata.id,
@@ -483,6 +648,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
// Perf: degrade tokenization/highlighting for large files (>500KB)
maxLineDiffLength: isLargeContent ? 0 : 1000,
maxLineLengthForHighlighting: isLargeContent ? 1 : 1000,
tokenizeMaxLineLength: isLargeContent ? 1 : 1000,
expansionLineCount: 20,
overflow: wrapLines ? ('wrap' as const) : ('scroll' as const),
disableFileHeader: true,
@@ -508,70 +674,136 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
lineAnnotationsRef.current = lineAnnotations;
}, [lineAnnotations]);
useEffect(() => {
const container = diffContainerRef.current;
return () => {
diffInstanceRef.current?.cleanUp();
diffInstanceRef.current = null;
sharedVirtualizerRef.current?.release();
sharedVirtualizerRef.current = null;
instanceVirtualizerRef.current = null;
instanceWorkerPoolRef.current = null;
instanceVirtualHunkSeparatorsRef.current = undefined;
instanceFileDiffRef.current = undefined;
instanceOldFileRef.current = undefined;
instanceNewFileRef.current = undefined;
if (container) {
container.innerHTML = '';
}
};
}, []);
useEffect(() => {
if (typeof window === 'undefined') return;
const container = diffContainerRef.current;
const wrapper = diffRootRef.current;
if (!container) return;
if (!workerPool) return;
// Dispose previous instance
diffInstanceRef.current?.cleanUp();
diffInstanceRef.current = null;
sharedVirtualizerRef.current?.release();
sharedVirtualizerRef.current = null;
container.innerHTML = '';
const sharedVirtualizer = acquireSharedVirtualizer(container);
const preserveDone = preserveScrollPosition(wrapper, container);
let sharedVirtualizer = sharedVirtualizerRef.current;
if (!sharedVirtualizer) {
sharedVirtualizer = acquireSharedVirtualizer(container);
sharedVirtualizerRef.current = sharedVirtualizer;
}
sharedVirtualizerRef.current = sharedVirtualizer;
const virtualizer = sharedVirtualizer?.virtualizer ?? null;
const instance = sharedVirtualizer
? new VirtualizedFileDiff(
options as unknown as FileDiffOptions<unknown>,
sharedVirtualizer.virtualizer,
VIRTUAL_METRICS,
workerPool,
)
: new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
const oldFile: FileContents = {
const oldFile: FileContents | undefined = fileDiff ? undefined : {
name: fileName || '',
contents: original,
lang: language as FileContents['lang'],
cacheKey: `old:${diffThemeKey}:${fileName}:${makeContentCacheKey(original)}`,
};
const newFile: FileContents = {
const newFile: FileContents | undefined = fileDiff ? undefined : {
name: fileName || '',
contents: modified,
lang: language as FileContents['lang'],
cacheKey: `new:${diffThemeKey}:${fileName}:${makeContentCacheKey(modified)}`,
};
instance.render({
oldFile,
newFile,
lineAnnotations: lineAnnotationsRef.current,
containerWrapper: container,
});
const targetChanged = fileDiff
? instanceFileDiffRef.current !== fileDiff
: instanceFileDiffRef.current !== undefined
|| !oldFile
|| !newFile
|| !instanceOldFileRef.current
|| !instanceNewFileRef.current
|| !areFilesEqual(instanceOldFileRef.current, oldFile)
|| !areFilesEqual(instanceNewFileRef.current, newFile);
requestAnimationFrame(() => {
forceUpdate();
const currentInstance = diffInstanceRef.current;
const shouldReset = Boolean(
currentInstance
&& (
instanceVirtualizerRef.current !== virtualizer
|| instanceWorkerPoolRef.current !== workerPool
|| (virtualizer && (instanceVirtualHunkSeparatorsRef.current !== options.hunkSeparators || targetChanged))
)
);
if (shouldReset) {
currentInstance?.cleanUp();
diffInstanceRef.current = null;
container.innerHTML = '';
}
let instance = diffInstanceRef.current;
const forceRender = !shouldReset && currentInstance
? !areOptionsEqual(currentInstance.options, options)
: false;
if (!instance) {
instance = sharedVirtualizer
? new VirtualizedFileDiff(
options as unknown as FileDiffOptions<unknown>,
sharedVirtualizer.virtualizer,
VIRTUAL_METRICS,
workerPool,
)
: new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
} else {
instance.setOptions(options as unknown as FileDiffOptions<unknown>);
}
instanceVirtualizerRef.current = virtualizer;
instanceWorkerPoolRef.current = workerPool;
instanceVirtualHunkSeparatorsRef.current = virtualizer ? options.hunkSeparators : undefined;
instanceFileDiffRef.current = fileDiff;
instanceOldFileRef.current = oldFile;
instanceNewFileRef.current = newFile;
if (fileDiff) {
instance.render({
fileDiff,
forceRender,
lineAnnotations: lineAnnotationsRef.current,
containerWrapper: container,
});
} else {
if (!oldFile || !newFile) return;
instance.render({
oldFile,
newFile,
forceRender,
lineAnnotations: lineAnnotationsRef.current,
containerWrapper: container,
});
}
const cancelReady = waitForDiffReady(container, () => {
preserveDone();
wakeVirtualizer(instance, sharedVirtualizer, forceUpdate);
});
return () => {
instance.cleanUp();
if (diffInstanceRef.current === instance) {
diffInstanceRef.current = null;
}
sharedVirtualizer?.release();
if (sharedVirtualizer && sharedVirtualizerRef.current === sharedVirtualizer) {
sharedVirtualizerRef.current = null;
}
container.innerHTML = '';
cancelReady();
preserveDone();
};
}, [diffThemeKey, fileName, language, modified, options, original, workerPool]);
}, [diffThemeKey, fileDiff, fileName, language, modified, options, original, workerPool]);
useEffect(() => {
const instance = diffInstanceRef.current;