diff --git a/apps/demo/src/main.ts b/apps/demo/src/main.ts index 853a8417c..ff7306fcf 100644 --- a/apps/demo/src/main.ts +++ b/apps/demo/src/main.ts @@ -48,7 +48,7 @@ const DEMO_THEME: DiffsThemeNames | ThemesType = DEFAULT_THEMES; const WORKER_POOL = true; const VIRTUALIZE = true; const CRAZY_FILE = false; -const LARGE_CONFLICT_FILE = false; +const LARGE_CONFLICT_FILE = true; const FileStreamCodeConfigs: FileStreamCodeConfigsItem[] = [ { diff --git a/packages/diffs/src/components/File.ts b/packages/diffs/src/components/File.ts index db6bff62b..53d0b61bd 100644 --- a/packages/diffs/src/components/File.ts +++ b/packages/diffs/src/components/File.ts @@ -49,7 +49,7 @@ import { upsertHostThemeStyle } from '../utils/hostTheme'; import { prerenderHTMLIfNecessary } from '../utils/prerenderHTMLIfNecessary'; import { getMeasuredScrollbarGutter } from '../utils/scrollbarGutter'; import { setPreNodeProperties } from '../utils/setWrapperNodeProps'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; import { DiffsContainerLoaded } from './web-components'; const EMPTY_STRINGS: string[] = []; @@ -166,7 +166,7 @@ export class File { ) { this.fileRenderer = new FileRenderer( options, - this.handleHighlightRender, + (metadata) => this.handleHighlightRender(metadata), this.workerManager ); this.resizeManager = new ResizeManager(); @@ -177,9 +177,9 @@ export class File { this.workerManager?.subscribeToThemeChanges(this); } - private handleHighlightRender = (): void => { + private handleHighlightRender(_metadata?: HighlightRequestMetadata): void { this.rerender(); - }; + } public rerender(): void { if (this.file == null) return; diff --git a/packages/diffs/src/components/FileDiff.ts b/packages/diffs/src/components/FileDiff.ts index f54a3db29..d7058c898 100644 --- a/packages/diffs/src/components/FileDiff.ts +++ b/packages/diffs/src/components/FileDiff.ts @@ -64,7 +64,7 @@ import { parseDiffFromFile } from '../utils/parseDiffFromFile'; import { prerenderHTMLIfNecessary } from '../utils/prerenderHTMLIfNecessary'; import { getMeasuredScrollbarGutter } from '../utils/scrollbarGutter'; import { setPreNodeProperties } from '../utils/setWrapperNodeProps'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; import { DiffsContainerLoaded } from './web-components'; export interface FileDiffRenderProps { @@ -243,9 +243,9 @@ export class FileDiff { this.enabled = true; } - protected handleHighlightRender = (): void => { + protected handleHighlightRender(_metadata?: HighlightRequestMetadata): void { this.rerender(); - }; + } protected getHunksRendererOptions( options: FileDiffOptions @@ -266,7 +266,7 @@ export class FileDiff { ): DiffHunksRenderer { return new DiffHunksRenderer( this.getHunksRendererOptions(options), - this.handleHighlightRender, + (metadata) => this.handleHighlightRender(metadata), this.workerManager ); } diff --git a/packages/diffs/src/components/UnresolvedFile.ts b/packages/diffs/src/components/UnresolvedFile.ts index 42acdfa01..18596e43d 100644 --- a/packages/diffs/src/components/UnresolvedFile.ts +++ b/packages/diffs/src/components/UnresolvedFile.ts @@ -13,6 +13,7 @@ import type { MergeConflictMarkerRow, MergeConflictRegion, MergeConflictResolution, + RenderRange, } from '../types'; import { areFilesEqual } from '../utils/areFilesEqual'; import { areMergeConflictActionsEqual } from '../utils/areMergeConflictActionsEqual'; @@ -26,7 +27,7 @@ import { } from '../utils/parseMergeConflictDiffFromFile'; import { resolveConflict as resolveConflictDiff } from '../utils/resolveConflict'; import { splitFileContents } from '../utils/splitFileContents'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; import { FileDiff, type FileDiffOptions, @@ -106,6 +107,24 @@ interface ResolveConflictReturn { markerRows: MergeConflictMarkerRow[]; } +interface PendingRender { + fileDiff: FileDiffMetadata; + actions: (MergeConflictDiffAction | undefined)[]; + markerRows: MergeConflictMarkerRow[]; + lineAnnotations: UnresolvedFileRenderProps['lineAnnotations']; + renderRange: RenderRange | undefined; + preventEmit: boolean; + forceRender?: boolean; + fileContainer?: HTMLElement; + containerWrapper?: HTMLElement; +} + +interface ActiveMergeConflictState { + actions: (MergeConflictDiffAction | undefined)[]; + markerRows: MergeConflictMarkerRow[]; + fileDiff: FileDiffMetadata | undefined; +} + type UnresolvedFileDataCache = GetOrComputeDiffProps; let instanceId = -1; @@ -124,6 +143,7 @@ export class UnresolvedFile< private markerRows: MergeConflictMarkerRow[] = []; private conflictActionCache: Map = new Map(); + private pendingRender: PendingRender | undefined; constructor( public override options: UnresolvedFileOptions = { @@ -175,7 +195,7 @@ export class UnresolvedFile< ): UnresolvedFileHunksRenderer { const renderer = new UnresolvedFileHunksRenderer( this.getHunksRendererOptions(options), - this.handleHighlightRender, + (metadata) => this.handleHighlightRender(metadata), this.workerManager ); return renderer; @@ -205,6 +225,7 @@ export class UnresolvedFile< markerRows: undefined, }; this.conflictActions = []; + this.pendingRender = undefined; super.cleanUp(); } @@ -354,7 +375,7 @@ export class UnresolvedFile< return; } this.hydrateElements(fileContainer, prerenderedHTML); - this.setActiveMergeConflictState(source.actions, source.markerRows); + this.setActiveMergeConflictState(source); // If necessary hydration elements don't exist, we should assume a full // render if ( @@ -386,6 +407,27 @@ export class UnresolvedFile< this.render({ forceRender: true, renderRange: this.renderRange }); } + protected override handleHighlightRender( + metadata?: HighlightRequestMetadata + ): void { + console.log('handleHighlightRender', metadata); + const { pendingRender } = this; + if (metadata != null) { + if (metadata === pendingRender) { + this.pendingRender = undefined; + console.log('handleHighlightRender.rendering a pre render'); + this.renderResolvedState(pendingRender, true); + } + console.log('handleHighlightRender.throwing away a render'); + // If we get in here, then it means we are waiting on a render that's + // been invalidated, so drop it + return; + } + + console.log('handleHighlightRender.rendering normie render'); + super.handleHighlightRender(metadata); + } + override render(props: UnresolvedFileRenderProps = {}): boolean { let { file, @@ -405,20 +447,30 @@ export class UnresolvedFile< if (source == null) { return false; } - this.setActiveMergeConflictState(source.actions, source.markerRows); - const didRender = super.render({ - ...rest, + const nextRender: PendingRender = { fileDiff: source.fileDiff, + actions: source.actions, + markerRows: source.markerRows, lineAnnotations, - preventEmit: true, - }); - if (didRender) { - this.renderMergeConflictActionSlots(); - if (!preventEmit) { - this.emitPostRender(); - } + renderRange: rest.renderRange, + preventEmit, + forceRender: rest.forceRender, + fileContainer: rest.fileContainer, + containerWrapper: rest.containerWrapper, + }; + + if (this.shouldDeferRender(source.fileDiff)) { + console.log('queing render'); + this.queuePendingRender(nextRender); + return false; } - return didRender; + + // if (this.pendingRender?.fileDiff === source.fileDiff) { + // } + + console.log('linear render'); + this.pendingRender = undefined; + return this.renderResolvedState(nextRender); } public resolveConflict( @@ -486,37 +538,93 @@ export class UnresolvedFile< } this.computedCache = { file, fileDiff, actions, markerRows }; - this.setActiveMergeConflictState(actions, markerRows); - // NOTE(amadeus): This is a bit jank, but helps to ensure we don't see a - // bunch of jittery re-renders as things resolve out. In a more perfect - // world we would have a more elegant way to kick off a render to the - // highlighter and then resolve actions in a cleaner way, but time is short - // right now. Can't let perfect be the enemy of good - if (this.workerManager != null) { - // Because we are using a workerManager, if we fire off the renderDiff - // call, it will eventually get back to us in a callback which will - // trigger a re-render - this.hunksRenderer.renderDiff(fileDiff); + const nextRender: PendingRender = { + fileDiff, + actions, + markerRows, + lineAnnotations: this.lineAnnotations, + renderRange: this.renderRange, + preventEmit: false, + forceRender: true, + }; + if (this.shouldDeferRender(fileDiff)) { + console.log('resolveConflictAndRender: queued'); + this.queuePendingRender(nextRender); } else { - this.render({ forceRender: true }); + console.log('resolveConflictAndRender: linear'); + this.pendingRender = undefined; + this.renderResolvedState(nextRender); } this.options.onMergeConflictResolve?.(file, payload); } - private setActiveMergeConflictState( - actions: (MergeConflictDiffAction | undefined)[] = this.conflictActions, - markerRows: MergeConflictMarkerRow[] = this.markerRows - ): void { + private shouldDeferRender(fileDiff: FileDiffMetadata): boolean { + return ( + fileDiff !== this.fileDiff && + this.hunksRenderer.willTriggerAsyncHighlight(fileDiff) + ); + } + + // The pending render payload is also the metadata passed through async + // highlighting so the completion callback can promote only the latest render. + private queuePendingRender(pendingRender: PendingRender): void { + this.pendingRender = pendingRender; + this.hunksRenderer.renderDiff( + pendingRender.fileDiff, + pendingRender.renderRange, + pendingRender + ); + } + + private renderResolvedState( + pendingRender: PendingRender, + forceRenderOverride = false + ): boolean { + const { + fileDiff, + actions, + markerRows, + lineAnnotations, + renderRange, + preventEmit, + forceRender = false, + fileContainer, + containerWrapper, + } = pendingRender; + this.setActiveMergeConflictState({ actions, markerRows, fileDiff }); + const didRender = super.render({ + fileDiff, + lineAnnotations, + renderRange, + forceRender: forceRenderOverride || forceRender, + fileContainer, + containerWrapper, + preventEmit: true, + }); + if (didRender) { + this.renderMergeConflictActionSlots(); + if (!preventEmit) { + this.emitPostRender(); + } + } + return didRender; + } + + private setActiveMergeConflictState({ + actions = this.conflictActions, + markerRows = this.markerRows, + fileDiff = this.fileDiff, + }: ActiveMergeConflictState): void { this.conflictActions = actions; this.markerRows = markerRows; if ( - this.computedCache.fileDiff != null && + fileDiff != null && this.hunksRenderer instanceof UnresolvedFileHunksRenderer ) { this.hunksRenderer.setConflictState( this.options.mergeConflictActionsType === 'none' ? [] : actions, markerRows, - this.computedCache.fileDiff + fileDiff ); } } @@ -534,6 +642,12 @@ export class UnresolvedFile< "UnresolvedFile.handleMergeConflictActionClick: conflictIndex and conflictAction don't match" ); } + // NOTE(amadeus): Not sure if this will bite us or not... maybe we could + // take the active pending render data for this and still allow things to + // get triggered? I'll need to test this ont he demo + if (this.pendingRender != null) { + return; + } const payload: MergeConflictActionPayload = { resolution: target.resolution, conflict: action.conflict, @@ -546,7 +660,7 @@ export class UnresolvedFile< }; private renderMergeConflictActionSlots(): void { - const { fileDiff } = this.computedCache; + const { fileDiff } = this; if ( this.isContainerManaged || this.fileContainer == null || diff --git a/packages/diffs/src/renderers/DiffHunksRenderer.ts b/packages/diffs/src/renderers/DiffHunksRenderer.ts index 6cc71784a..5da9e9e00 100644 --- a/packages/diffs/src/renderers/DiffHunksRenderer.ts +++ b/packages/diffs/src/renderers/DiffHunksRenderer.ts @@ -62,7 +62,7 @@ import type { DiffLineMetadata } from '../utils/iterateOverDiff'; import { iterateOverDiff } from '../utils/iterateOverDiff'; import { renderDiffWithHighlighter } from '../utils/renderDiffWithHighlighter'; import { shouldUseTokenTransformer } from '../utils/shouldUseTokenTransformer'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; interface PushLineWithAnnotation { diffStyle: 'unified' | 'split'; @@ -213,7 +213,7 @@ export class DiffHunksRenderer { constructor( public options: DiffHunksRendererOptions = { theme: DEFAULT_THEMES }, - private onRenderUpdate?: () => unknown, + private onRenderUpdate?: (metadata?: HighlightRequestMetadata) => unknown, private workerManager?: WorkerPoolManager | undefined ) { if (workerManager?.isWorkingPool() !== true) { @@ -445,9 +445,45 @@ export class DiffHunksRenderer { return { options, forceRender: false }; } + public willTriggerAsyncHighlight(diff: FileDiffMetadata): boolean { + // If fileDiff is the same and we have the highlighted renderCache, then we + // know we'll always have a synchronous render + if ( + this.renderCache == null || + diff === this.renderCache?.diff || + this.renderCache?.highlighted === false + ) { + return false; + } + + // If we have an updated fileDiff and are using the Worker Pool, async + // highlighting depends on whether we are plainText or not + if (this.workerManager?.isWorkingPool() === true) { + if (isDiffPlainText(diff)) { + return false; + } else { + return true; + } + } + + // If we aren't using the Worker Pool, then we gotta make sure we have a + // highlighter instantiated (async) + if (this.highlighter == null) { + return true; + } + + // And that we have all the languages and themes loaded (also async) + const { options } = this.getRenderOptions(diff); + const computedLang = diff.lang ?? getFiletypeFromFileName(diff.name); + const hasThemes = areThemesAttached(options.theme); + const hasLangs = areLanguagesAttached(computedLang); + return !hasThemes || !hasLangs; + } + public renderDiff( diff: FileDiffMetadata | undefined = this.renderCache?.diff, - renderRange: RenderRange = DEFAULT_RENDER_RANGE + renderRange: RenderRange = DEFAULT_RENDER_RANGE, + metadata?: HighlightRequestMetadata ): HunksRenderResult | undefined { if (diff == null) { return undefined; @@ -500,7 +536,7 @@ export class DiffHunksRenderer { renderRange.totalLines > 0 && (!this.renderCache.highlighted || forceRender) ) { - this.workerManager.highlightDiffAST(this, diff); + this.workerManager.highlightDiffAST(this, diff, metadata); } } else { this.computedLang = diff.lang ?? getFiletypeFromFileName(diff.name); @@ -544,7 +580,7 @@ export class DiffHunksRenderer { if (this.renderCache != null) { this.renderCache.highlighted = false; } - this.onHighlightSuccess(diff, result, options); + this.onHighlightSuccess(diff, result, options, metadata); }); } } @@ -619,8 +655,10 @@ export class DiffHunksRenderer { public onHighlightSuccess( diff: FileDiffMetadata, result: ThemedDiffResult, - options: RenderDiffOptions + options: RenderDiffOptions, + metadata?: HighlightRequestMetadata ): void { + console.log('onHighlightSuccess', metadata); // NOTE(amadeus): This is a bad assumption, and I should figure out // something better... // If renderCache was blown away, we can assume we've run cleanUp() @@ -641,7 +679,7 @@ export class DiffHunksRenderer { renderRange: undefined, }; if (triggerRenderUpdate) { - this.onRenderUpdate?.(); + this.onRenderUpdate?.(metadata); } } diff --git a/packages/diffs/src/renderers/FileRenderer.ts b/packages/diffs/src/renderers/FileRenderer.ts index 98e6dff3b..facdb25bf 100644 --- a/packages/diffs/src/renderers/FileRenderer.ts +++ b/packages/diffs/src/renderers/FileRenderer.ts @@ -43,7 +43,7 @@ import { iterateOverFile } from '../utils/iterateOverFile'; import { renderFileWithHighlighter } from '../utils/renderFileWithHighlighter'; import { shouldUseTokenTransformer } from '../utils/shouldUseTokenTransformer'; import { splitFileContents } from '../utils/splitFileContents'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; type AnnotationLineMap = Record< number, @@ -91,7 +91,7 @@ export class FileRenderer { constructor( public options: FileRendererOptions = { theme: DEFAULT_THEMES }, - private onRenderUpdate?: () => unknown, + private onRenderUpdate?: (metadata?: HighlightRequestMetadata) => unknown, private workerManager?: WorkerPoolManager | undefined ) { if (workerManager?.isWorkingPool() !== true) { @@ -202,7 +202,8 @@ export class FileRenderer { public renderFile( file: FileContents | undefined = this.renderCache?.file, - renderRange: RenderRange = DEFAULT_RENDER_RANGE + renderRange: RenderRange = DEFAULT_RENDER_RANGE, + metadata?: HighlightRequestMetadata ): FileRenderResult | undefined { if (file == null) { return undefined; @@ -248,7 +249,7 @@ export class FileRenderer { renderRange.totalLines > 0 && (!this.renderCache.highlighted || forceRender) ) { - this.workerManager.highlightFileAST(this, file); + this.workerManager.highlightFileAST(this, file, metadata); } } else { this.computedLang = file.lang ?? getFiletypeFromFileName(file.name); @@ -292,7 +293,7 @@ export class FileRenderer { if (this.renderCache != null) { this.renderCache.highlighted = false; } - this.onHighlightSuccess(file, result, options); + this.onHighlightSuccess(file, result, options, metadata); }); } } @@ -478,7 +479,8 @@ export class FileRenderer { public onHighlightSuccess( file: FileContents, result: ThemedFileResult, - options: RenderFileOptions + options: RenderFileOptions, + metadata?: HighlightRequestMetadata ): void { if (this.renderCache == null) { return; @@ -497,7 +499,7 @@ export class FileRenderer { }; if (triggerRenderUpdate) { - this.onRenderUpdate?.(); + this.onRenderUpdate?.(metadata); } } diff --git a/packages/diffs/src/renderers/UnresolvedFileHunksRenderer.ts b/packages/diffs/src/renderers/UnresolvedFileHunksRenderer.ts index beb45fe42..42634ff4a 100644 --- a/packages/diffs/src/renderers/UnresolvedFileHunksRenderer.ts +++ b/packages/diffs/src/renderers/UnresolvedFileHunksRenderer.ts @@ -17,7 +17,7 @@ import { getMergeConflictActionAnchor, type MergeConflictDiffAction, } from '../utils/parseMergeConflictDiffFromFile'; -import type { WorkerPoolManager } from '../worker'; +import type { HighlightRequestMetadata, WorkerPoolManager } from '../worker'; import { DiffHunksRenderer, type DiffHunksRendererOptions, @@ -82,7 +82,7 @@ export class UnresolvedFileHunksRenderer< options: UnresolvedFileHunksRendererOptions = { theme: DEFAULT_THEMES, }, - onRenderUpdate?: () => unknown, + onRenderUpdate?: (metadata?: HighlightRequestMetadata) => unknown, workerManager?: WorkerPoolManager | undefined ) { super(undefined, onRenderUpdate, workerManager); @@ -142,7 +142,8 @@ export class UnresolvedFileHunksRenderer< public override renderDiff( diff?: FileDiffMetadata | undefined, - renderRange: RenderRange = DEFAULT_RENDER_RANGE + renderRange: RenderRange = DEFAULT_RENDER_RANGE, + metadata?: HighlightRequestMetadata ): HunksRenderResult | undefined { if (diff != null) { this.syncInjectedRows( @@ -151,7 +152,7 @@ export class UnresolvedFileHunksRenderer< diff ); } - return super.renderDiff(diff, renderRange); + return super.renderDiff(diff, renderRange, metadata); } public override async asyncRender( diff --git a/packages/diffs/src/worker/WorkerPoolManager.ts b/packages/diffs/src/worker/WorkerPoolManager.ts index 405659bfa..4a20f3523 100644 --- a/packages/diffs/src/worker/WorkerPoolManager.ts +++ b/packages/diffs/src/worker/WorkerPoolManager.ts @@ -41,6 +41,7 @@ import type { AllWorkerTasks, DiffRendererInstance, FileRendererInstance, + HighlightRequestMetadata, InitializeWorkerRequest, InitializeWorkerTask, RenderDiffRequest, @@ -521,25 +522,18 @@ export class WorkerPoolManager { this.queueBroadcastStateChanges(); }; - highlightFileAST(instance: FileRendererInstance, file: FileContents): void { + highlightFileAST( + instance: FileRendererInstance, + file: FileContents, + metadata?: HighlightRequestMetadata + ): void { if (isFilePlainText(file)) { return; } - // If we already have a task in progress for this same file content, we - // should drop it - for (const tasks of [this.taskQueue, this.pendingTasks.values()]) { - for (const task of tasks) { - if ( - 'instance' in task && - task.instance === instance && - task.request.type === 'file' && - areFilesEqual(file, task.request.file) - ) { - return; - } - } + if (this.refreshMatchingFileTaskMetadata(instance, file, metadata)) { + return; } - this.submitTask(instance, { type: 'file', file }); + this.submitTask(instance, { type: 'file', file }, metadata); } getPlainFileAST( @@ -562,26 +556,17 @@ export class WorkerPoolManager { highlightDiffAST( instance: DiffRendererInstance, - diff: FileDiffMetadata + diff: FileDiffMetadata, + metadata?: HighlightRequestMetadata ): void { if (isDiffPlainText(diff)) { return; } - // If we already have a task in progress for this same diff content, we - // should ignore executing it again - for (const tasks of [this.taskQueue, this.pendingTasks.values()]) { - for (const task of tasks) { - if ( - 'instance' in task && - task.instance === instance && - task.request.type === 'diff' && - task.request.diff === diff - ) { - return; - } - } + if (this.refreshMatchingDiffTaskMetadata(instance, diff, metadata)) { + return; } - this.submitTask(instance, { type: 'diff', diff }); + console.log('highlightDiffAST', metadata); + this.submitTask(instance, { type: 'diff', diff }, metadata); } getPlainDiffAST( @@ -667,15 +652,18 @@ export class WorkerPoolManager { private submitTask( instance: FileRendererInstance, - request: Omit + request: Omit, + metadata?: HighlightRequestMetadata ): void; private submitTask( instance: DiffRendererInstance, - request: Omit + request: Omit, + metadata?: HighlightRequestMetadata ): void; private submitTask( instance: FileRendererInstance | DiffRendererInstance, - request: SubmitRequest + request: SubmitRequest, + metadata?: HighlightRequestMetadata ): void { if (this.initialized === false) { this.queueInitialization(); @@ -691,6 +679,7 @@ export class WorkerPoolManager { id, request: { ...request, id }, instance: instance as FileRendererInstance, + metadata, requestStart, }; case 'diff': @@ -699,6 +688,7 @@ export class WorkerPoolManager { id, request: { ...request, id }, instance: instance as DiffRendererInstance, + metadata, requestStart, }; } @@ -708,6 +698,74 @@ export class WorkerPoolManager { this.queueDrain(); } + // REVIEW(amadeus): Can these 2 refreshMatching lads be simplified? They are + // largely the same function, perhaps a uniform function that has 2 argument + // signatures and a type string passed in? + private refreshMatchingFileTaskMetadata( + instance: FileRendererInstance, + file: FileContents, + metadata?: HighlightRequestMetadata + ): boolean { + const queuedTask = this.taskQueue.get(instance); + if ( + queuedTask?.type === 'file' && + areFilesEqual(file, queuedTask.request.file) + ) { + queuedTask.metadata = metadata; + return true; + } + + const pendingRequestId = this.instanceRequestMap.get(instance); + if (pendingRequestId == null) { + return false; + } + + const pendingTask = this.pendingTasks.get(pendingRequestId); + if ( + pendingTask == null || + !('instance' in pendingTask) || + pendingTask.instance !== instance || + pendingTask.type !== 'file' || + !areFilesEqual(file, pendingTask.request.file) + ) { + return false; + } + + pendingTask.metadata = metadata; + return true; + } + + private refreshMatchingDiffTaskMetadata( + instance: DiffRendererInstance, + diff: FileDiffMetadata, + metadata?: HighlightRequestMetadata + ): boolean { + const queuedTask = this.taskQueue.get(instance); + if (queuedTask?.type === 'diff' && queuedTask.request.diff === diff) { + queuedTask.metadata = metadata; + return true; + } + + const pendingRequestId = this.instanceRequestMap.get(instance); + if (pendingRequestId == null) { + return false; + } + + const pendingTask = this.pendingTasks.get(pendingRequestId); + if ( + pendingTask == null || + !('instance' in pendingTask) || + pendingTask.instance !== instance || + pendingTask.type !== 'diff' || + pendingTask.request.diff !== diff + ) { + return false; + } + + pendingTask.metadata = metadata; + return true; + } + private async resolveLanguagesAndExecuteTask( availableWorker: ManagedWorker, task: RenderFileTask | RenderDiffTask, @@ -752,7 +810,7 @@ export class WorkerPoolManager { if ('reject' in task) { task.reject(error); } else { - task.instance.onHighlightError(error); + task.instance.onHighlightError(error, task.metadata); } throw error; } else { @@ -789,7 +847,12 @@ export class WorkerPoolManager { if (request.file.cacheKey != null) { this.fileCache.set(request.file.cacheKey, { result, options }); } - instance.onHighlightSuccess(request.file, result, options); + instance.onHighlightSuccess( + request.file, + result, + options, + task.metadata + ); break; } case 'diff': { @@ -802,7 +865,12 @@ export class WorkerPoolManager { if (request.diff.cacheKey != null) { this.diffCache.set(request.diff.cacheKey, { result, options }); } - instance.onHighlightSuccess(request.diff, result, options); + instance.onHighlightSuccess( + request.diff, + result, + options, + task.metadata + ); break; } } @@ -871,7 +939,7 @@ export class WorkerPoolManager { this.cleanWorkerAndTask(managedWorker, task); console.error('Failed to post message to worker:', error); if ('instance' in task) { - task.instance.onHighlightError(error); + task.instance.onHighlightError(error, task.metadata); } else if ('reject' in task) { task.reject(error as Error); } diff --git a/packages/diffs/src/worker/types.ts b/packages/diffs/src/worker/types.ts index f2bdaf4dc..d59be1ac4 100644 --- a/packages/diffs/src/worker/types.ts +++ b/packages/diffs/src/worker/types.ts @@ -16,6 +16,7 @@ import type { } from '../types'; export type WorkerRequestId = string; +export type HighlightRequestMetadata = unknown; export interface WorkerRenderingOptions { theme: DiffsThemeNames | ThemesType; @@ -30,9 +31,13 @@ export interface FileRendererInstance { onHighlightSuccess( file: FileContents, result: ThemedFileResult, - options: RenderFileOptions + options: RenderFileOptions, + metadata?: HighlightRequestMetadata + ): unknown; + onHighlightError( + error: unknown, + metadata?: HighlightRequestMetadata ): unknown; - onHighlightError(error: unknown): unknown; } export interface DiffRendererInstance { @@ -40,9 +45,13 @@ export interface DiffRendererInstance { onHighlightSuccess( diff: FileDiffMetadata, result: ThemedDiffResult, - options: RenderDiffOptions + options: RenderDiffOptions, + metadata?: HighlightRequestMetadata + ): unknown; + onHighlightError( + error: unknown, + metadata?: HighlightRequestMetadata ): unknown; - onHighlightError(error: unknown): unknown; } export interface RenderFileRequest { @@ -189,6 +198,7 @@ export interface RenderFileTask { id: WorkerRequestId; request: RenderFileRequest; instance: FileRendererInstance; + metadata?: HighlightRequestMetadata; requestStart: number; } @@ -197,6 +207,7 @@ export interface RenderDiffTask { id: WorkerRequestId; request: RenderDiffRequest; instance: DiffRendererInstance; + metadata?: HighlightRequestMetadata; requestStart: number; }