diff --git a/apps/docs/app/diff-examples/Annotations/constants.ts b/apps/docs/app/diff-examples/Annotations/constants.ts index 2602191c5..a619aec0e 100644 --- a/apps/docs/app/diff-examples/Annotations/constants.ts +++ b/apps/docs/app/diff-examples/Annotations/constants.ts @@ -1,4 +1,5 @@ import { + DEFAULT_THEMES, type DiffLineAnnotation, type FileContents, parseDiffFromFile, @@ -69,7 +70,8 @@ def verify_token(token: str) -> Optional[dict]: `, }, options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'unified', unsafeCSS: CustomScrollbarCSS, }, @@ -141,7 +143,8 @@ export const ACCEPT_REJECT_EXAMPLE: PreloadFileDiffOptions { fileDiff: parseDiffFromFile(ACCEPT_REJECT_OLD_FILE, ACCEPT_REJECT_NEW_FILE), options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'unified', }, annotations: ACCEPT_REJECT_ANNOTATIONS, diff --git a/apps/docs/app/diff-examples/ArbitraryFiles/constants.ts b/apps/docs/app/diff-examples/ArbitraryFiles/constants.ts index e886f9da4..ec92e3cc0 100644 --- a/apps/docs/app/diff-examples/ArbitraryFiles/constants.ts +++ b/apps/docs/app/diff-examples/ArbitraryFiles/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -19,7 +20,8 @@ export const ARBITRARY_DIFF_EXAMPLE: PreloadMultiFileDiffOptions = { `, }, options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'unified', unsafeCSS: CustomScrollbarCSS, }, diff --git a/apps/docs/app/diff-examples/CustomHeader/CustomHeader.tsx b/apps/docs/app/diff-examples/CustomHeader/CustomHeader.tsx index 22953b82e..87cf966a0 100644 --- a/apps/docs/app/diff-examples/CustomHeader/CustomHeader.tsx +++ b/apps/docs/app/diff-examples/CustomHeader/CustomHeader.tsx @@ -72,31 +72,23 @@ export function CustomHeader({ prerenderedDiff }: CustomHeaderProps) { } : undefined } - renderHeaderPrefix={ - headerMode === 'metadata' - ? () => { - return ( - - ); - } - : undefined - } - renderHeaderMetadata={ - headerMode === 'metadata' - ? () => { - return ( - - ); - } - : undefined - } + renderHeaderPrefix={() => { + return ( + + ); + }} + renderHeaderMetadata={() => { + return ( + + ); + }} /> ); diff --git a/apps/docs/app/diff-examples/CustomHeader/constants.ts b/apps/docs/app/diff-examples/CustomHeader/constants.ts index aa7535b1b..84a9fdb92 100644 --- a/apps/docs/app/diff-examples/CustomHeader/constants.ts +++ b/apps/docs/app/diff-examples/CustomHeader/constants.ts @@ -1,4 +1,4 @@ -import { type FileContents } from '@pierre/diffs'; +import { DEFAULT_THEMES, type FileContents } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -68,7 +68,7 @@ export const CUSTOM_HEADER_EXAMPLE: PreloadMultiFileDiffOptions = { oldFile: CUSTOM_HEADER_OLD_FILE, newFile: CUSTOM_HEADER_NEW_FILE, options: { - theme: { dark: 'pierre-dark', light: 'pierre-light' }, + theme: DEFAULT_THEMES, themeType: 'dark', diffStyle: 'split', disableBackground: false, diff --git a/apps/docs/app/diff-examples/CustomHunkSeparators/CustomHunkSeparators.tsx b/apps/docs/app/diff-examples/CustomHunkSeparators/CustomHunkSeparators.tsx index 8a168468e..56dc50fc8 100644 --- a/apps/docs/app/diff-examples/CustomHunkSeparators/CustomHunkSeparators.tsx +++ b/apps/docs/app/diff-examples/CustomHunkSeparators/CustomHunkSeparators.tsx @@ -254,11 +254,6 @@ export function CustomHunkSeparators({ ) : ( ; } + export function DiffStyles({ prerenderedDiff: { options, ...props }, }: DiffStylesProps) { const [diffIndicators, setDiffStyle] = useState<'classic' | 'bars' | 'none'>( 'bars' ); - const [lineDiffStyle, setLineDiffStyle] = useState< + const [lineDiffType, setLineDiffType] = useState< 'word-alt' | 'word' | 'char' | 'none' >('word-alt'); const [disableBackground, setDisableBackground] = useState(false); @@ -110,8 +111,8 @@ export function DiffStyles({ > {} - {diffStyleOptions.find((opt) => opt.value === lineDiffStyle) - ?.label ?? lineDiffStyle} + {diffStyleOptions.find((opt) => opt.value === lineDiffType) + ?.label ?? lineDiffType} @@ -123,11 +124,11 @@ export function DiffStyles({ {diffStyleOptions.map((option) => ( setLineDiffStyle(option.value)} - selected={lineDiffStyle === option.value} + onClick={() => setLineDiffType(option.value)} + selected={lineDiffType === option.value} className="flex items-start gap-2 py-2" > - {lineDiffStyle === option.value ? ( + {lineDiffType === option.value ? ( ) : (
@@ -213,14 +214,14 @@ export function DiffStyles({ className="diff-container" options={{ ...options, - theme: 'pierre-dark', - diffStyle: 'split', diffIndicators, disableBackground, - overflow: overflow, - lineDiffType: lineDiffStyle, + overflow, + lineDiffType, disableLineNumbers, }} + // Because we need to change lineDiffType, we can't use the WorkerPool + disableWorkerPool />
); diff --git a/apps/docs/app/diff-examples/DiffStyles/constants.ts b/apps/docs/app/diff-examples/DiffStyles/constants.ts index 346121fac..defae1f7b 100644 --- a/apps/docs/app/diff-examples/DiffStyles/constants.ts +++ b/apps/docs/app/diff-examples/DiffStyles/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -45,7 +46,8 @@ pub fn main() !void { `, }, options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'split', overflow: 'wrap', disableLineNumbers: false, diff --git a/apps/docs/app/diff-examples/FontStyles/constants.ts b/apps/docs/app/diff-examples/FontStyles/constants.ts index df654fe55..35033a6de 100644 --- a/apps/docs/app/diff-examples/FontStyles/constants.ts +++ b/apps/docs/app/diff-examples/FontStyles/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -131,7 +132,8 @@ func (c *Cache) onEviction(key string, value interface{}) { `, }, options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'unified', unsafeCSS: CustomScrollbarCSS, }, diff --git a/apps/docs/app/diff-examples/LineSelection/constants.ts b/apps/docs/app/diff-examples/LineSelection/constants.ts index 6b00d1b6c..80e34785f 100644 --- a/apps/docs/app/diff-examples/LineSelection/constants.ts +++ b/apps/docs/app/diff-examples/LineSelection/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -118,7 +119,7 @@ public: `, }, options: { - theme: { dark: 'pierre-dark', light: 'pierre-light' }, + theme: DEFAULT_THEMES, themeType: 'dark', diffStyle: 'split', disableBackground: false, diff --git a/apps/docs/app/diff-examples/MergeConflict/MergeConflict.tsx b/apps/docs/app/diff-examples/MergeConflict/MergeConflict.tsx index 4b42ae3d6..286ded8e8 100644 --- a/apps/docs/app/diff-examples/MergeConflict/MergeConflict.tsx +++ b/apps/docs/app/diff-examples/MergeConflict/MergeConflict.tsx @@ -5,7 +5,12 @@ import { type UnresolvedFileReactOptions, } from '@pierre/diffs/react'; import type { PreloadUnresolvedFileResult } from '@pierre/diffs/ssr'; -import { IconColorDark, IconColorLight, IconRefresh } from '@pierre/icons'; +import { + IconColorAuto, + IconColorDark, + IconColorLight, + IconRefresh, +} from '@pierre/icons'; import { useMemo, useState } from 'react'; import { FeatureHeader } from '../FeatureHeader'; @@ -21,8 +26,14 @@ export function MergeConflict({ prerenderedFile }: MergeConflictProps) { const [hasResolved, setHasResolved] = useState(false); const [themeType, setThemeType] = useState<'light' | 'dark' | 'system'>( - () => prerenderedFile.options?.themeType ?? 'dark' + () => prerenderedFile.options?.themeType ?? 'system' ); + const borderClass = + themeType === 'light' + ? 'border-neutral-200' + : themeType === 'dark' + ? 'border-neutral-800' + : 'border-neutral-200 dark:border-neutral-800'; // NOTE(amadeus): These server render APIs definitely suck, and it's // something we need to take a pass at. Curious if it's something Nicolas @@ -70,8 +81,14 @@ export function MergeConflict({ prerenderedFile }: MergeConflictProps) { setThemeType(value as 'light' | 'dark')} + onValueChange={(value) => + setThemeType(value as 'light' | 'dark' | 'system') + } > + + + Auto + Light @@ -103,7 +120,7 @@ export function MergeConflict({ prerenderedFile }: MergeConflictProps) { file={prerenderedFile.file} options={options} prerenderedHTML={prerenderedFile.prerenderedHTML} - className={`overflow-hidden rounded-lg border ${themeType === 'light' ? 'border-neutral-200' : 'border-neutral-800'}`} + className={`overflow-hidden rounded-lg border ${borderClass}`} // NOTE(amadeus): Test code, I need to better solve the whole server/vanilla/custom js thing with react // renderMergeConflictUtility={(action, getInstance) => { // return ( diff --git a/apps/docs/app/diff-examples/MergeConflict/constants.ts b/apps/docs/app/diff-examples/MergeConflict/constants.ts index cb5c63f16..103f8fd68 100644 --- a/apps/docs/app/diff-examples/MergeConflict/constants.ts +++ b/apps/docs/app/diff-examples/MergeConflict/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadUnresolvedFileOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -95,8 +96,8 @@ export async function validateSession(token: string): Promise { `, }, options: { + theme: DEFAULT_THEMES, themeType: 'dark', - theme: { light: 'pierre-light', dark: 'pierre-dark' }, overflow: 'wrap', diffIndicators: 'none', unsafeCSS: CustomScrollbarCSS, diff --git a/apps/docs/app/diff-examples/ShikiThemes/ShikiThemes.tsx b/apps/docs/app/diff-examples/ShikiThemes/ShikiThemes.tsx index 8446448e0..9c02146b0 100644 --- a/apps/docs/app/diff-examples/ShikiThemes/ShikiThemes.tsx +++ b/apps/docs/app/diff-examples/ShikiThemes/ShikiThemes.tsx @@ -222,10 +222,13 @@ export function ShikiThemes({ {...props} className="overflow-hidden rounded-lg border dark:border-neutral-800" options={{ - diffStyle: 'split', - themeType: selectedColorMode, + ...options, theme: { dark: selectedDarkTheme, light: selectedLightTheme }, + themeType: selectedColorMode, }} + // WorkerPool is disabled because we need to be able to change themes + // without changing our whole page + disableWorkerPool /> diff --git a/apps/docs/app/diff-examples/ShikiThemes/constants.ts b/apps/docs/app/diff-examples/ShikiThemes/constants.ts index 2717e9ed0..b5a3022a7 100644 --- a/apps/docs/app/diff-examples/ShikiThemes/constants.ts +++ b/apps/docs/app/diff-examples/ShikiThemes/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -37,7 +38,7 @@ fn add(a: i32, b: i32) -> i32 { }, options: { diffStyle: 'split', - theme: { dark: 'pierre-dark', light: 'pierre-light' }, + theme: DEFAULT_THEMES, unsafeCSS: CustomScrollbarCSS, enableLineSelection: true, }, diff --git a/apps/docs/app/diff-examples/SplitUnified/SplitUnified.tsx b/apps/docs/app/diff-examples/SplitUnified/SplitUnified.tsx index 3cbd6cc15..1b1adbb64 100644 --- a/apps/docs/app/diff-examples/SplitUnified/SplitUnified.tsx +++ b/apps/docs/app/diff-examples/SplitUnified/SplitUnified.tsx @@ -15,7 +15,9 @@ interface SplitUnifiedProps { export function SplitUnified({ prerenderedDiff: { options, ...props }, }: SplitUnifiedProps) { - const [diffStyle, setDiffStyle] = useState<'split' | 'unified'>('split'); + const [diffStyle, setDiffStyle] = useState<'split' | 'unified'>( + options?.diffStyle ?? 'split' + ); return (
); diff --git a/apps/docs/app/diff-examples/SplitUnified/constants.ts b/apps/docs/app/diff-examples/SplitUnified/constants.ts index 70d462948..7db51159d 100644 --- a/apps/docs/app/diff-examples/SplitUnified/constants.ts +++ b/apps/docs/app/diff-examples/SplitUnified/constants.ts @@ -1,3 +1,4 @@ +import { DEFAULT_THEMES } from '@pierre/diffs'; import type { PreloadMultiFileDiffOptions } from '@pierre/diffs/ssr'; import { CustomScrollbarCSS } from '@/components/CustomScrollbarCSS'; @@ -143,7 +144,8 @@ export function createHunkSeparator() { `, }, options: { - theme: 'pierre-dark', + theme: DEFAULT_THEMES, + themeType: 'dark', diffStyle: 'split', unsafeCSS: CustomScrollbarCSS, }, diff --git a/apps/docs/app/docs/page.tsx b/apps/docs/app/docs/page.tsx index 3a1352f1b..ae019e06c 100644 --- a/apps/docs/app/docs/page.tsx +++ b/apps/docs/app/docs/page.tsx @@ -139,7 +139,13 @@ export default function DocsPage() { async function MergeConflictDemoSection() { return ( ); } diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 91428050c..d1298b041 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -12,6 +12,7 @@ import { import localFont from 'next/font/local'; import './globals.css'; +import { PreloadHighlighter } from '@/components/PreloadHighlighter'; import { Toaster } from '@/components/ui/sonner'; const inter = Inter({ @@ -108,6 +109,7 @@ export default function RootLayout({ data-theme="light" > + ); diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx index 1ace7fc12..508426bd3 100644 --- a/apps/docs/app/page.tsx +++ b/apps/docs/app/page.tsx @@ -35,42 +35,33 @@ import type { ProductId } from './product-config'; import Footer from '@/components/Footer'; import { Header } from '@/components/Header'; import { PierreCompanySection } from '@/components/PierreCompanySection'; +import { WorkerPoolContext } from '@/components/WorkerPoolContext'; const PRODUCT_ID: ProductId = 'diffs'; export default function Home() { return ( -
-
- -
- - - - - - - - {/* */} - - - - -
- - {/* TODO: add this back once we add the migration APIs - -
-

Migrate to @pierre/diffs

-

- Already using git-diff-viewer? Learn how to migrate your diff - rendering to @pierre/diffs. -

-
*/} - - -
-
+ +
+
+ +
+ + + + + + + + + + + +
+ +
+
+
); } @@ -109,9 +100,13 @@ async function CustomHeaderSection() { async function CustomHunkSeparatorsSection() { return ( ); } diff --git a/apps/docs/components/PreloadHighlighter.tsx b/apps/docs/components/PreloadHighlighter.tsx new file mode 100644 index 000000000..cf36c92e4 --- /dev/null +++ b/apps/docs/components/PreloadHighlighter.tsx @@ -0,0 +1,14 @@ +'use client'; +import { preloadHighlighter } from '@pierre/diffs'; +import { useEffect } from 'react'; + +export function PreloadHighlighter() { + useEffect(() => { + void preloadHighlighter({ + themes: ['pierre-dark', 'pierre-light'], + langs: ['zig', 'rust', 'typescript', 'tsx'], + preferredHighlighter: 'shiki-wasm', + }); + }, []); + return null; +} diff --git a/apps/docs/components/WorkerPoolContext.tsx b/apps/docs/components/WorkerPoolContext.tsx index 6aa8d5adf..978f5850f 100644 --- a/apps/docs/components/WorkerPoolContext.tsx +++ b/apps/docs/components/WorkerPoolContext.tsx @@ -22,18 +22,35 @@ const PoolOptions: WorkerPoolOptions = { const HighlighterOptions: WorkerInitializationRenderOptions = { theme: { dark: 'pierre-dark', light: 'pierre-light' }, - langs: ['zig', 'typescript', 'tsx', 'css', 'sh'], + langs: [ + 'cpp', + 'css', + 'go', + 'python', + 'rust', + 'sh', + 'swift', + 'tsx', + 'typescript', + 'zig', + ], }; interface WorkerPoolProps { children: ReactNode; + highlighterOptions?: WorkerInitializationRenderOptions; + poolOptions?: WorkerPoolOptions; } -export function WorkerPoolContext({ children }: WorkerPoolProps) { +export function WorkerPoolContext({ + children, + highlighterOptions = HighlighterOptions, + poolOptions = PoolOptions, +}: WorkerPoolProps) { return ( {children} diff --git a/packages/diffs/src/components/File.ts b/packages/diffs/src/components/File.ts index 72b1b9cd6..efd8b0f75 100644 --- a/packages/diffs/src/components/File.ts +++ b/packages/diffs/src/components/File.ts @@ -109,6 +109,11 @@ interface ColumnElements { content: HTMLElement; } +interface HydrationSetup { + file: FileContents; + lineAnnotations: LineAnnotation[] | undefined; +} + let instanceId = -1; export class File { @@ -262,8 +267,32 @@ export class File { } public hydrate(props: FileHydrateProps): void { - const { overflow = 'scroll' } = this.options; - const { fileContainer, prerenderedHTML, preventEmit = false } = props; + const { + fileContainer, + prerenderedHTML, + preventEmit = false, + file, + lineAnnotations, + } = props; + this.hydrateElements(fileContainer, prerenderedHTML); + // If we have no pre tag and header tag, then something probably didn't + // pre-render and we should kick off a render. + if (this.pre == null && this.headerElement == null) { + this.render({ ...props, preventEmit: true }); + } + // Otherwise orchestrate our setup. + else { + this.hydrationSetup({ file, lineAnnotations }); + } + if (!preventEmit) { + this.emitPostRender(); + } + } + + protected hydrateElements( + fileContainer: HTMLElement, + prerenderedHTML: string | undefined + ): void { prerenderHTMLIfNecessary(fileContainer, prerenderedHTML); for (const element of Array.from( fileContainer.shadowRoot?.children ?? [] @@ -301,28 +330,34 @@ export class File { continue; } } - // If we have no pre tag, then we should render - if (this.pre == null) { - this.render({ ...props, preventEmit: true }); + if (this.pre != null) { + this.syncCodeNodeFromPre(this.pre); + this.pre.removeAttribute('data-dehydrated'); } - // Otherwise orchestrate our setup - else { - const { file, lineAnnotations } = props; - this.fileContainer = fileContainer; - delete this.pre.dataset.dehydrated; + this.fileContainer = fileContainer; + } - this.lineAnnotations = lineAnnotations ?? this.lineAnnotations; - this.file = file; - this.fileRenderer.hydrate(file); - this.renderAnnotations(); - this.renderGutterUtility(); - this.injectUnsafeCSS(); - this.interactionManager.setup(this.pre); - this.resizeManager.setup(this.pre, overflow === 'wrap'); - } - if (!preventEmit) { - this.emitPostRender(); + protected hydrationSetup({ + file, + lineAnnotations, + }: HydrationSetup): void { + const { overflow = 'scroll' } = this.options; + this.lineAnnotations = lineAnnotations ?? this.lineAnnotations; + this.file = file; + this.fileRenderer.setOptions({ + ...this.options, + headerRenderMode: + this.options.renderCustomHeader != null ? 'custom' : 'default', + }); + if (this.pre == null) { + return; } + this.fileRenderer.hydrate(file); + this.renderAnnotations(); + this.renderGutterUtility(); + this.injectUnsafeCSS(); + this.interactionManager.setup(this.pre); + this.resizeManager.setup(this.pre, overflow === 'wrap'); } public getOrCreateLineCache( @@ -1130,6 +1165,19 @@ export class File { return this.pre; } + private syncCodeNodeFromPre(pre: HTMLPreElement): void { + this.code = undefined; + for (const child of Array.from(pre.children)) { + if (!(child instanceof HTMLElement)) { + continue; + } + if (child.hasAttribute('data-code')) { + this.code = child; + return; + } + } + } + private applyPreNodeAttributes( pre: HTMLPreElement, { totalLines }: FileRenderResult diff --git a/packages/diffs/src/components/FileDiff.ts b/packages/diffs/src/components/FileDiff.ts index 7c1171388..2cbc508bb 100644 --- a/packages/diffs/src/components/FileDiff.ts +++ b/packages/diffs/src/components/FileDiff.ts @@ -157,6 +157,13 @@ interface ApplyPartialRenderProps { renderRange: RenderRange | undefined; } +interface HydrationSetup { + fileDiff: FileDiffMetadata | undefined; + lineAnnotations: DiffLineAnnotation[] | undefined; + oldFile?: FileContents; + newFile?: FileContents; +} + let instanceId = -1; export class FileDiff { @@ -476,8 +483,39 @@ export class FileDiff { } public hydrate(props: FileDiffHydrationProps): void { - const { diffStyle = 'split', overflow = 'scroll' } = this.options; - const { fileContainer, prerenderedHTML, preventEmit = false } = props; + const { + fileContainer, + prerenderedHTML, + preventEmit = false, + lineAnnotations, + oldFile, + newFile, + fileDiff, + } = props; + this.hydrateElements(fileContainer, prerenderedHTML); + // If we have no pre tag and header tag, then something probably didn't + // pre-render and we should kick off a render. + if (this.pre == null && this.headerElement == null) { + this.render({ ...props, preventEmit: true }); + } + // Otherwise orchestrate our setup + else { + this.hydrationSetup({ + fileDiff, + oldFile, + newFile, + lineAnnotations, + }); + } + if (!preventEmit) { + this.emitPostRender(); + } + } + + protected hydrateElements( + fileContainer: HTMLElement, + prerenderedHTML: string | undefined + ): void { prerenderHTMLIfNecessary(fileContainer, prerenderedHTML); for (const element of fileContainer.shadowRoot?.children ?? []) { if (element instanceof SVGElement) { @@ -530,44 +568,47 @@ export class FileDiff { } if (this.pre != null) { this.syncCodeNodesFromPre(this.pre); + this.pre.removeAttribute('data-dehydrated'); } - // If we have no pre tag, then we should render + this.fileContainer = fileContainer; + } + + protected hydrationSetup({ + fileDiff, + oldFile, + newFile, + lineAnnotations, + }: HydrationSetup): void { + // It's possible we are hydrating a pure-rename and therefore there will be + // no pre element + const { diffStyle = 'split', overflow = 'scroll' } = this.options; + this.lineAnnotations = lineAnnotations ?? this.lineAnnotations; + this.additionFile = newFile; + this.deletionFile = oldFile; + this.fileDiff = + fileDiff ?? + (oldFile != null && newFile != null + ? parseDiffFromFile(oldFile, newFile) + : undefined); + if (this.pre == null) { - this.render({ ...props, preventEmit: true }); - } - // Otherwise orchestrate our setup - else { - const { lineAnnotations, oldFile, newFile, fileDiff } = props; - this.fileContainer = fileContainer; - delete this.pre.dataset.dehydrated; - - this.lineAnnotations = lineAnnotations ?? this.lineAnnotations; - this.additionFile = newFile; - this.deletionFile = oldFile; - this.fileDiff = - fileDiff ?? - (oldFile != null && newFile != null - ? parseDiffFromFile(oldFile, newFile) - : undefined); - - this.hunksRenderer.hydrate(this.fileDiff); - // FIXME(amadeus): not sure how to handle this yet... - // this.renderSeparators(); - this.renderAnnotations(); - this.renderGutterUtility(); - this.injectUnsafeCSS(); - this.interactionManager.setup(this.pre); - this.resizeManager.setup(this.pre, overflow === 'wrap'); - if (overflow === 'scroll' && diffStyle === 'split') { - this.scrollSyncManager.setup( - this.pre, - this.codeDeletions, - this.codeAdditions - ); - } + return; } - if (!preventEmit) { - this.emitPostRender(); + + this.hunksRenderer.hydrate(this.fileDiff); + // FIXME(amadeus): not sure how to handle this yet... + // this.renderSeparators(); + this.renderAnnotations(); + this.renderGutterUtility(); + this.injectUnsafeCSS(); + this.interactionManager.setup(this.pre); + this.resizeManager.setup(this.pre, overflow === 'wrap'); + if (overflow === 'scroll' && diffStyle === 'split') { + this.scrollSyncManager.setup( + this.pre, + this.codeDeletions, + this.codeAdditions + ); } } @@ -1098,11 +1139,11 @@ export class FileDiff { if (!(child instanceof HTMLElement)) { continue; } - if ('unified' in child.dataset) { + if (child.hasAttribute('data-unified')) { this.codeUnified = child; - } else if ('deletions' in child.dataset) { + } else if (child.hasAttribute('data-deletions')) { this.codeDeletions = child; - } else if ('additions' in child.dataset) { + } else if (child.hasAttribute('data-additions')) { this.codeAdditions = child; } } diff --git a/packages/diffs/src/components/UnresolvedFile.ts b/packages/diffs/src/components/UnresolvedFile.ts index 7941f0b28..852c960c8 100644 --- a/packages/diffs/src/components/UnresolvedFile.ts +++ b/packages/diffs/src/components/UnresolvedFile.ts @@ -1,4 +1,4 @@ -import { DEFAULT_THEMES, UNSAFE_CSS_ATTRIBUTE } from '../constants'; +import { DEFAULT_THEMES } from '../constants'; import type { MergeConflictActionTarget } from '../managers/InteractionManager'; import { pluckInteractionOptions } from '../managers/InteractionManager'; import type { HunksRenderResult } from '../renderers/DiffHunksRenderer'; @@ -24,7 +24,6 @@ import { type MergeConflictDiffAction, parseMergeConflictDiffFromFile, } from '../utils/parseMergeConflictDiffFromFile'; -import { prerenderHTMLIfNecessary } from '../utils/prerenderHTMLIfNecessary'; import { resolveConflict as resolveConflictDiff } from '../utils/resolveConflict'; import { splitFileContents } from '../utils/splitFileContents'; import type { WorkerPoolManager } from '../worker'; @@ -351,55 +350,10 @@ export class UnresolvedFile< actions, markerRows, }); - const { overflow = 'scroll' } = this.options; if (source == null) { return; } - prerenderHTMLIfNecessary(fileContainer, prerenderedHTML); - for (const element of fileContainer.shadowRoot?.children ?? []) { - if (element instanceof SVGElement) { - this.spriteSVG = element; - continue; - } - if (!(element instanceof HTMLElement)) { - continue; - } - if (element instanceof HTMLPreElement) { - this.pre = element; - for (const code of element.children) { - if ( - !(code instanceof HTMLElement) || - code.tagName.toLowerCase() !== 'code' - ) { - continue; - } - if ('deletions' in code.dataset) { - this.codeDeletions = code; - } - if ('additions' in code.dataset) { - this.codeAdditions = code; - } - if ('unified' in code.dataset) { - this.codeUnified = code; - } - } - continue; - } - if ('diffsHeader' in element.dataset) { - this.headerElement = element; - continue; - } - if ( - element instanceof HTMLStyleElement && - element.hasAttribute(UNSAFE_CSS_ATTRIBUTE) - ) { - this.unsafeCSSStyle = element; - continue; - } - } - if (this.pre != null) { - this.syncCodeNodesFromPre(this.pre); - } + this.hydrateElements(fileContainer, prerenderedHTML); this.setActiveMergeConflictState(source.actions, source.markerRows); // If we have no pre tag, then we should render if (this.pre == null) { @@ -407,18 +361,7 @@ export class UnresolvedFile< } // Otherwise orchestrate our setup else { - this.fileContainer = fileContainer; - delete this.pre.dataset.dehydrated; - - this.lineAnnotations = lineAnnotations ?? this.lineAnnotations; - this.fileDiff = source.fileDiff; - - this.hunksRenderer.hydrate(this.fileDiff); - this.renderAnnotations(); - this.renderGutterUtility(); - this.injectUnsafeCSS(); - this.interactionManager.setup(this.pre); - this.resizeManager.setup(this.pre, overflow === 'wrap'); + this.hydrationSetup({ fileDiff: source.fileDiff, lineAnnotations }); } this.renderMergeConflictActionSlots(); diff --git a/packages/diffs/src/react/File.tsx b/packages/diffs/src/react/File.tsx index 5fd631497..11727a8d3 100644 --- a/packages/diffs/src/react/File.tsx +++ b/packages/diffs/src/react/File.tsx @@ -24,6 +24,7 @@ export function File({ prerenderedHTML, renderGutterUtility, renderHoverUtility, + disableWorkerPool = false, }: FileProps): React.JSX.Element { const { ref, getHoveredLine } = useFileInstance({ file, @@ -35,6 +36,7 @@ export function File({ hasGutterRenderUtility: renderGutterUtility != null || renderHoverUtility != null, hasCustomHeader: renderCustomHeader != null, + disableWorkerPool, }); const children = renderFileChildren({ file, diff --git a/packages/diffs/src/react/FileDiff.tsx b/packages/diffs/src/react/FileDiff.tsx index d9e11923e..85096fe09 100644 --- a/packages/diffs/src/react/FileDiff.tsx +++ b/packages/diffs/src/react/FileDiff.tsx @@ -13,6 +13,7 @@ export interface FileDiffProps< LAnnotation, > extends DiffBasePropsReact { fileDiff: FileDiffMetadata; + disableWorkerPool?: boolean; } export function FileDiff({ @@ -30,6 +31,7 @@ export function FileDiff({ renderHeaderMetadata, renderGutterUtility, renderHoverUtility, + disableWorkerPool = false, }: FileDiffProps): React.JSX.Element { const { ref, getHoveredLine } = useFileDiffInstance({ fileDiff, @@ -41,6 +43,7 @@ export function FileDiff({ hasGutterRenderUtility: renderGutterUtility != null || renderHoverUtility != null, hasCustomHeader: renderCustomHeader != null, + disableWorkerPool, }); const children = renderDiffChildren({ fileDiff, diff --git a/packages/diffs/src/react/MultiFileDiff.tsx b/packages/diffs/src/react/MultiFileDiff.tsx index ac6569c7d..a412fa978 100644 --- a/packages/diffs/src/react/MultiFileDiff.tsx +++ b/packages/diffs/src/react/MultiFileDiff.tsx @@ -17,6 +17,7 @@ export interface MultiFileDiffProps< > extends DiffBasePropsReact { oldFile: FileContents; newFile: FileContents; + disableWorkerPool?: boolean; } export function MultiFileDiff({ @@ -35,6 +36,7 @@ export function MultiFileDiff({ renderHeaderMetadata, renderGutterUtility, renderHoverUtility, + disableWorkerPool = false, }: MultiFileDiffProps): React.JSX.Element { const fileDiff = useMemo(() => { return parseDiffFromFile(oldFile, newFile); @@ -49,6 +51,7 @@ export function MultiFileDiff({ hasGutterRenderUtility: renderGutterUtility != null || renderHoverUtility != null, hasCustomHeader: renderCustomHeader != null, + disableWorkerPool, }); const children = renderDiffChildren({ fileDiff, diff --git a/packages/diffs/src/react/PatchDiff.tsx b/packages/diffs/src/react/PatchDiff.tsx index 3e9ea196e..515141045 100644 --- a/packages/diffs/src/react/PatchDiff.tsx +++ b/packages/diffs/src/react/PatchDiff.tsx @@ -14,6 +14,7 @@ export interface PatchDiffProps< LAnnotation, > extends DiffBasePropsReact { patch: string; + disableWorkerPool?: boolean; } export function PatchDiff({ @@ -31,6 +32,7 @@ export function PatchDiff({ renderHeaderMetadata, renderGutterUtility, renderHoverUtility, + disableWorkerPool = false, }: PatchDiffProps): React.JSX.Element { const fileDiff = usePatch(patch); const { ref, getHoveredLine } = useFileDiffInstance({ @@ -43,6 +45,7 @@ export function PatchDiff({ hasGutterRenderUtility: renderGutterUtility != null || renderHoverUtility != null, hasCustomHeader: renderCustomHeader != null, + disableWorkerPool, }); const children = renderDiffChildren({ fileDiff, diff --git a/packages/diffs/src/react/UnresolvedFile.tsx b/packages/diffs/src/react/UnresolvedFile.tsx index b830e3982..b6f601511 100644 --- a/packages/diffs/src/react/UnresolvedFile.tsx +++ b/packages/diffs/src/react/UnresolvedFile.tsx @@ -56,6 +56,7 @@ export interface UnresolvedFileProps extends Omit< action: MergeConflictDiffAction, getInstance: () => UnresolvedFileClass | undefined ): ReactNode; + disableWorkerPool?: boolean; } export function UnresolvedFile({ @@ -73,6 +74,7 @@ export function UnresolvedFile({ renderGutterUtility, renderHoverUtility, renderMergeConflictUtility, + disableWorkerPool = false, }: UnresolvedFileProps): React.JSX.Element { const { ref, getHoveredLine, fileDiff, actions, getInstance } = useUnresolvedFileInstance({ @@ -85,6 +87,7 @@ export function UnresolvedFile({ hasGutterRenderUtility: renderGutterUtility != null || renderHoverUtility != null, hasCustomHeader: renderCustomHeader != null, + disableWorkerPool, }); const children = renderDiffChildren({ fileDiff, diff --git a/packages/diffs/src/react/WorkerPoolContext.tsx b/packages/diffs/src/react/WorkerPoolContext.tsx index 9123eb496..2f06979fe 100644 --- a/packages/diffs/src/react/WorkerPoolContext.tsx +++ b/packages/diffs/src/react/WorkerPoolContext.tsx @@ -47,11 +47,14 @@ export function WorkerPoolContextProvider({ // We use insertion effect for the instance counting to essentially debounce // potentially conflicting mount/unmounts useInsertionEffect(() => { - instanceCount++; - return () => { - instanceCount--; - }; - }, []); + if (poolManager != null) { + instanceCount++; + return () => { + instanceCount--; + }; + } + return undefined; + }, [poolManager]); useEffect(() => { return () => { if (instanceCount === 0) { diff --git a/packages/diffs/src/react/types.ts b/packages/diffs/src/react/types.ts index 072ed7135..47ee19078 100644 --- a/packages/diffs/src/react/types.ts +++ b/packages/diffs/src/react/types.ts @@ -59,4 +59,5 @@ export interface FileProps { className?: string; style?: CSSProperties; prerenderedHTML?: string; + disableWorkerPool?: boolean; } diff --git a/packages/diffs/src/react/utils/useFileDiffInstance.ts b/packages/diffs/src/react/utils/useFileDiffInstance.ts index fe4d2cf67..4b417717f 100644 --- a/packages/diffs/src/react/utils/useFileDiffInstance.ts +++ b/packages/diffs/src/react/utils/useFileDiffInstance.ts @@ -35,6 +35,7 @@ interface UseFileDiffInstanceProps { metrics?: VirtualFileMetrics; hasGutterRenderUtility: boolean; hasCustomHeader: boolean; + disableWorkerPool: boolean; } interface UseFileDiffInstanceReturn { @@ -51,6 +52,7 @@ export function useFileDiffInstance({ metrics, hasGutterRenderUtility, hasCustomHeader, + disableWorkerPool, }: UseFileDiffInstanceProps): UseFileDiffInstanceReturn { const simpleVirtualizer = useVirtualizer(); const poolManager = useContext(WorkerPoolContext); @@ -73,7 +75,7 @@ export function useFileDiffInstance({ }), simpleVirtualizer, metrics, - poolManager, + !disableWorkerPool ? poolManager : undefined, true ); } else { @@ -83,7 +85,7 @@ export function useFileDiffInstance({ hasGutterRenderUtility, options, }), - poolManager, + !disableWorkerPool ? poolManager : undefined, true ); } diff --git a/packages/diffs/src/react/utils/useFileInstance.ts b/packages/diffs/src/react/utils/useFileInstance.ts index a0c280e3d..9b0971594 100644 --- a/packages/diffs/src/react/utils/useFileInstance.ts +++ b/packages/diffs/src/react/utils/useFileInstance.ts @@ -35,6 +35,7 @@ interface UseFileInstanceProps { metrics?: VirtualFileMetrics; hasGutterRenderUtility: boolean; hasCustomHeader: boolean; + disableWorkerPool: boolean; } interface UseFileInstanceReturn { @@ -51,6 +52,7 @@ export function useFileInstance({ metrics, hasGutterRenderUtility, hasCustomHeader, + disableWorkerPool, }: UseFileInstanceProps): UseFileInstanceReturn { const simpleVirtualizer = useVirtualizer(); const poolManager = useContext(WorkerPoolContext); @@ -73,7 +75,7 @@ export function useFileInstance({ }), simpleVirtualizer, metrics, - poolManager, + !disableWorkerPool ? poolManager : undefined, true ); } else { @@ -83,7 +85,7 @@ export function useFileInstance({ hasGutterRenderUtility, options, }), - poolManager, + !disableWorkerPool ? poolManager : undefined, true ); } diff --git a/packages/diffs/src/react/utils/useUnresolvedFileInstance.ts b/packages/diffs/src/react/utils/useUnresolvedFileInstance.ts index f06898133..3de0e786c 100644 --- a/packages/diffs/src/react/utils/useUnresolvedFileInstance.ts +++ b/packages/diffs/src/react/utils/useUnresolvedFileInstance.ts @@ -45,6 +45,7 @@ interface UseUnresolvedFileInstanceProps { hasConflictUtility: boolean; hasGutterRenderUtility: boolean; hasCustomHeader: boolean; + disableWorkerPool: boolean; } interface UseUnresolvedFileInstanceReturn { @@ -65,6 +66,7 @@ export function useUnresolvedFileInstance({ hasConflictUtility, hasGutterRenderUtility, hasCustomHeader, + disableWorkerPool, }: UseUnresolvedFileInstanceProps): UseUnresolvedFileInstanceReturn { const [{ fileDiff, actions, markerRows }, setState] = useState(() => { const { fileDiff, actions, markerRows } = parseMergeConflictDiffFromFile( @@ -113,7 +115,7 @@ export function useUnresolvedFileInstance({ onMergeConflictAction, options, }), - poolManager, + !disableWorkerPool ? poolManager : undefined, true ); void instanceRef.current.hydrate({ diff --git a/packages/diffs/src/renderers/DiffHunksRenderer.ts b/packages/diffs/src/renderers/DiffHunksRenderer.ts index 9c2392627..c60f810b9 100644 --- a/packages/diffs/src/renderers/DiffHunksRenderer.ts +++ b/packages/diffs/src/renderers/DiffHunksRenderer.ts @@ -394,8 +394,6 @@ export class DiffHunksRenderer { } this.renderCache ??= { diff, - // NOTE(amadeus): If we're hydrating, we can assume there was - // pre-rendered HTML, otherwise one should not be hydrating highlighted: true, options, result: cache?.result, @@ -405,11 +403,8 @@ export class DiffHunksRenderer { this.workerManager?.isWorkingPool() === true && this.renderCache.result == null ) { + // We should only kick off a preload of the AST if we have a WorkerPool this.workerManager.highlightDiffAST(this, this.diff); - } else { - void this.asyncHighlight(diff).then(({ result, options }) => { - this.onHighlightSuccess(diff, result, options); - }); } } diff --git a/packages/diffs/src/renderers/FileRenderer.ts b/packages/diffs/src/renderers/FileRenderer.ts index 03b64ae51..0f4ab0daf 100644 --- a/packages/diffs/src/renderers/FileRenderer.ts +++ b/packages/diffs/src/renderers/FileRenderer.ts @@ -135,8 +135,6 @@ export class FileRenderer { this.renderCache ??= { file, options, - // NOTE(amadeus): If we're hydrating, we can assume there was - // pre-rendered HTML, otherwise one should not be hydrating highlighted: true, result: cache?.result, // FIXME(amadeus): Add support for renderRanges @@ -146,11 +144,8 @@ export class FileRenderer { this.workerManager?.isWorkingPool() === true && this.renderCache.result == null ) { + // We should only kick off a preload of the AST if we have a WorkerPool this.workerManager.highlightFileAST(this, file); - } else { - void this.asyncHighlight(file).then(({ result, options }) => { - this.onHighlightSuccess(file, result, options); - }); } }