@pierre/diffs
Version:
337 lines (336 loc) • 17.2 kB
TypeScript
import { AppliedThemeStyleCache, BaseCodeOptions, BaseDiffOptions, CustomPreProperties, DiffLineAnnotation, ExpansionDirections, FileContents, FileDiffMetadata, HighlightedToken, HunkData, HunkSeparators, LineAnnotation, MaybeDiffFileInput, PostRenderPhase, PrePropertiesConfig, RenderHeaderFilenameSuffixCallback, RenderHeaderMetadataCallback, RenderHeaderPrefixCallback, RenderRange, SelectedLineRange, SelectionSide, ThemeTypes } from "../types.js";
import { CapturedDiffSessionState, EditCompletionDecision, EditorActiveLineOptions, EditorChangeEvent, FileDiffEditCompleteEvent } from "../editor/types.js";
import { TextDocument } from "../editor/textDocument.js";
import { GetHoveredLineResult, GetLineIndexUtility, InteractionManager, InteractionManagerBaseOptions, SelectionWriteOptions } from "../managers/InteractionManager.js";
import { ResizeManager } from "../managers/ResizeManager.js";
import { WorkerPoolManager } from "../worker/WorkerPoolManager.js";
import { ScrollSyncManager } from "../managers/ScrollSyncManager.js";
import { DiffHunksRenderer, DiffHunksRendererOptions, HunksRenderResult } from "../renderers/DiffHunksRenderer.js";
import { Editor } from "../editor/editor.js";
//#region src/components/FileDiff.d.ts
type LoadedPartialDiffContents = Awaited<ReturnType<NonNullable<BaseDiffOptions['loadDiffFiles']>>>;
type DeferredSelectedLinesWrite = [range: SelectedLineRange | null, options: SelectionWriteOptions | undefined];
type DeferredEditorActiveLineWrite = [lineNumber: number | null, options: EditorActiveLineOptions | undefined];
interface FileDiffRenderBaseProps<LAnnotation> {
fileDiff?: FileDiffMetadata;
deferManagers?: boolean;
forceRender?: boolean;
preventEmit?: boolean;
fileContainer?: HTMLElement;
containerWrapper?: HTMLElement;
lineAnnotations?: DiffLineAnnotation<LAnnotation>[];
renderRange?: RenderRange;
}
type FileDiffRenderProps<LAnnotation> = FileDiffRenderBaseProps<LAnnotation> & MaybeDiffFileInput;
type FileDiffHydrationProps<LAnnotation> = Omit<FileDiffRenderBaseProps<LAnnotation>, 'fileContainer'> & MaybeDiffFileInput & {
fileContainer: HTMLElement;
prerenderedHTML?: string;
};
type FileDiffType = 'file-diff' | 'unresolved-file';
type FileDiffEditChangeHandler<LAnnotation, Caret> = (event: EditorChangeEvent<'file-diff', LAnnotation, Caret>) => void;
/**
* Decides a completed edit synchronously: return `'accept'` to install the
* event's `fileDiff` and annotations, or `'reject'` to restore the original
* values. The event is frozen, so re-key the accepted diff in place
* (`event.fileDiff.cacheKey = '…'`) before accepting. The event's editor is
* detached and returns its final state from `getViewState()`. A missing handler
* rejects.
*/
type FileDiffEditCompleteHandler<LAnnotation, Caret> = (event: FileDiffEditCompleteEvent<LAnnotation, Caret>) => EditCompletionDecision;
interface FileDiffOptions<LAnnotation, Caret> extends Omit<BaseDiffOptions, 'hunkSeparators'>, InteractionManagerBaseOptions<'diff'> {
hunkSeparators?: Exclude<HunkSeparators, 'custom'>
/**
* @deprecated Custom hunk separator functions are deprecated and will be
* removed in a future version.
*/
| ((hunk: HunkData, instance: FileDiff<LAnnotation, Caret>) => HTMLElement | DocumentFragment | null | undefined);
disableFileHeader?: boolean;
renderHeaderPrefix?: RenderHeaderPrefixCallback;
renderHeaderFilenameSuffix?: RenderHeaderFilenameSuffixCallback;
renderHeaderMetadata?: RenderHeaderMetadataCallback;
renderCustomHeader?: RenderHeaderMetadataCallback;
/**
* When true, errors during rendering are rethrown instead of being caught
* and displayed in the DOM. Useful for testing or when you want to handle
* errors yourself.
*/
disableErrorHandling?: boolean;
renderAnnotation?(annotation: DiffLineAnnotation<LAnnotation>): HTMLElement | undefined;
renderGutterUtility?(getHoveredRow: () => GetHoveredLineResult<'diff'> | undefined): HTMLElement | null | undefined;
onPostRender?(node: HTMLElement, instance: FileDiff<LAnnotation, Caret>, phase: PostRenderPhase): unknown;
/**
* Fired for every document change of an active edit session on this
* component, with the same `EditorChangeEvent` the editor reports through
* its own `onChange`. Do not feed the event's file back into the component
* while the session is active.
*/
onEditChange?: FileDiffEditChangeHandler<LAnnotation, Caret>;
/**
* Fired when `edit` toggles false or a component unmounts, including when the
* final contents are unchanged. If no callback is provided, the component
* reverts to the last `fileDiff` or `oldFile`/`newFiles` and annotations
* passed into it. The callback receives the detached editor with its final
* pre-detach state.
*/
onEditComplete?: FileDiffEditCompleteHandler<LAnnotation, Caret>;
}
interface AnnotationElementCache<LAnnotation> {
element: HTMLElement;
annotation: DiffLineAnnotation<LAnnotation>;
}
interface CustomHunkElementCache {
element: HTMLElement;
hunkData: HunkData;
}
interface PendingFileLoad {
fileDiff: FileDiffMetadata;
promise: Promise<void>;
}
type HydrationSetup<LAnnotation> = {
fileDiff: FileDiffMetadata | undefined;
lineAnnotations: DiffLineAnnotation<LAnnotation>[] | undefined;
} & MaybeDiffFileInput;
interface HeaderCache {
lastRenderedHTML: string | undefined;
html: string | undefined;
fileDiff: FileDiffMetadata | undefined;
}
declare class FileDiff<LAnnotation = undefined, Caret = undefined> {
options: FileDiffOptions<LAnnotation, Caret>;
protected workerManager?: WorkerPoolManager | undefined;
protected isContainerManaged: boolean;
static LoadedCustomComponent: boolean;
readonly __id: string;
readonly type: FileDiffType;
protected fileContainer: HTMLElement | undefined;
protected spriteSVG: SVGElement | undefined;
protected pre: HTMLPreElement | undefined;
protected codeUnified: HTMLElement | undefined;
protected codeDeletions: HTMLElement | undefined;
protected codeAdditions: HTMLElement | undefined;
protected bufferBefore: HTMLElement | undefined;
protected bufferAfter: HTMLElement | undefined;
protected themeCSSStyle: HTMLStyleElement | undefined;
protected appliedThemeCSS: AppliedThemeStyleCache | undefined;
protected hasAdoptedThemeCSS: boolean;
protected unsafeCSSStyle: HTMLStyleElement | undefined;
protected appliedUnsafeCSS: string | undefined;
protected gutterUtilityContent: HTMLElement | undefined;
protected headerElement: HTMLElement | undefined;
protected headerPrefix: HTMLElement | undefined;
protected headerFilenameSuffix: HTMLElement | undefined;
protected headerMetadata: HTMLElement | undefined;
protected headerCustom: HTMLElement | undefined;
protected separatorCache: Map<string, CustomHunkElementCache>;
protected errorWrapper: HTMLElement | undefined;
protected placeHolder: HTMLElement | undefined;
protected hunksRenderer: DiffHunksRenderer<LAnnotation>;
protected resizeManager: ResizeManager;
protected scrollSyncManager: ScrollSyncManager;
protected interactionManager: InteractionManager<'diff'>;
protected annotationCache: Map<string, AnnotationElementCache<LAnnotation>>;
protected lineAnnotations: DiffLineAnnotation<LAnnotation>[];
protected managersDirty: boolean;
protected deletionFile?: FileContents | null;
protected additionFile?: FileContents | null;
fileDiff: FileDiffMetadata | undefined;
private editSession;
protected renderedDiff: FileDiffMetadata | undefined;
protected renderRange: RenderRange | undefined;
protected pendingFiles: PendingFileLoad | undefined;
protected appliedPreAttributes: PrePropertiesConfig | undefined;
protected headerCache: HeaderCache;
protected lastRowCount: number | undefined;
private mounted;
protected enabled: boolean;
protected editor: Editor<'file-diff', LAnnotation, Caret> | undefined;
protected refreshViewTimeout: ReturnType<typeof setTimeout> | undefined;
protected lineStateRefreshPending: boolean;
protected deferredSelectedLines: DeferredSelectedLinesWrite | undefined;
protected deferredEditorActiveLine: DeferredEditorActiveLineWrite | undefined;
constructor(options?: FileDiffOptions<LAnnotation, Caret>, workerManager?: WorkerPoolManager | undefined, isContainerManaged?: boolean);
protected handleHighlightRender: () => void;
private getTheme;
protected getHunksRendererOptions(options: FileDiffOptions<LAnnotation, Caret>): DiffHunksRendererOptions;
protected createHunksRenderer(options: FileDiffOptions<LAnnotation, Caret>): DiffHunksRenderer<LAnnotation>;
getLineIndex: GetLineIndexUtility;
protected getDiffForLineIndex(): FileDiffMetadata | undefined;
protected getLineIndexForDiff(fileDiff: FileDiffMetadata | undefined, lineNumber: number, side: SelectionSide): [number, number] | undefined;
setOptions(options: FileDiffOptions<LAnnotation, Caret> | undefined): void;
protected syncInteractionOptions(): void;
private mergeOptions;
setThemeType(themeType: ThemeTypes): void;
private applyCachedThemeState;
private hasThemeChanged;
getHoveredLine: () => GetHoveredLineResult<'diff'> | undefined;
getAnnotationSlotName: (annotation: LineAnnotation<LAnnotation> | DiffLineAnnotation<LAnnotation>) => string;
protected getLatestAnnotations(): DiffLineAnnotation<LAnnotation>[];
protected isNewAnnotations(lineAnnotations: DiffLineAnnotation<LAnnotation>[]): boolean;
setLineAnnotations(lineAnnotations: DiffLineAnnotation<LAnnotation>[]): void;
protected syncEditSessionAnnotationsFromEditor(lineAnnotations: DiffLineAnnotation<LAnnotation>[]): boolean;
private canPartiallyRender;
setSelectedLines(range: SelectedLineRange | null, options?: SelectionWriteOptions): void;
setEditorActiveLine(lineNumber: number | null, options?: EditorActiveLineOptions): void;
protected flushDeferredLineState(): void;
flushManagers(): void;
protected shouldApplyColumnVariables(overflow: 'scroll' | 'wrap'): boolean;
getCodeScrollLeft(): number;
setCodeScrollLeft(position: number): void;
__getEffectiveCodeOptions(): BaseCodeOptions;
cleanUp(recycle?: boolean): void;
virtualizedSetup(): void;
hydrate({
fileContainer,
prerenderedHTML,
preventEmit,
lineAnnotations,
fileDiff,
...fileInputProps
}: FileDiffHydrationProps<LAnnotation>): void;
protected hydrateElements(fileContainer: HTMLElement, prerenderedHTML: string | undefined): void;
protected hydrationSetup({
fileDiff,
oldFile,
newFile,
lineAnnotations
}: HydrationSetup<LAnnotation>): void;
rerender(): void;
onThemeChange(): void;
handleExpandHunk: (hunkIndex: number, direction: ExpansionDirections, expansionLineCountOverride?: number) => void;
expandHunk: (hunkIndex: number, direction: ExpansionDirections, expansionLineCountOverride?: number) => void;
protected loadFilesIfNecessary(): void;
private loadFilesForDiff;
protected handleFilesLoaded(expectedDiff: FileDiffMetadata, files: LoadedPartialDiffContents): Promise<void>;
protected startHydratedEditSession(expectedDiff: FileDiffMetadata): boolean;
protected updateExternalDiff(incomingExternalDiff: FileDiffMetadata, lineAnnotations?: DiffLineAnnotation<LAnnotation>[]): boolean;
private installEditSession;
protected setHydratedState(files: LoadedPartialDiffContents): void;
render({
fileDiff,
deferManagers,
forceRender,
preventEmit,
lineAnnotations,
fileContainer,
containerWrapper,
renderRange,
...fileInputProps
}: FileDiffRenderProps<LAnnotation>): boolean;
protected finalizeRender(): void;
protected emitPostRender(unmount?: boolean): void;
protected getLatestDiff(fileDiff?: FileDiffMetadata | undefined): FileDiffMetadata | undefined;
protected getRenderedDiff(): FileDiffMetadata | undefined;
private syncRenderViewToEditor;
private computeEditorRenderRange;
/** @internal The editor applied or edited past the pending external replacement. */
__acknowledgeDocumentUpdate(): void;
/** @internal Settle annotations locally. */
__acceptEditorChange(event: EditorChangeEvent<'file-diff', LAnnotation, Caret>): void;
emitEditChange(event: EditorChangeEvent<'file-diff', LAnnotation, Caret>): void;
/**
* @internal Capture the current diff session, or return `undefined` when no
* complete compatible session exists.
*
* When `clone` is true, the returned lines and hunks are copied.
*/
__captureDocumentSessionState(clone?: boolean): CapturedDiffSessionState | undefined;
/** @internal Associate this component with its editor for a render lifecycle. */
__attachEditor(editor: Editor<'file-diff', LAnnotation, Caret>): () => void;
/** @internal Resume rendering for the editor already associated with this component. */
__resumeEditor(editor: Editor<'file-diff', LAnnotation, Caret>): void;
private resumeEditorRendering;
private finishEditSession;
/**
* @internal
*
* Ends the edit session and settles which diff this component renders.
* Requires the editor to be detached first. Does nothing when no session
* exists, so callers can invoke it again safely after it has settled.
*
* `onEditComplete` receives the completed diff, current external diff,
* complete file pair, and both annotation collections even when the final
* text is unchanged. In `install` mode, accepting installs the completed diff
* and its annotations; rejecting or having no handler restores the external
* values. `discard` mode always restores the external values. An accepted
* diff cannot reuse the replaced diff's `cacheKey`.
*/
__completeEditSession(editor: Editor<'file-diff', LAnnotation, Caret>, mode: 'install' | 'discard'): void;
private settleEditSession;
/**
* Run the session-end recompute: restore recompute-shaped hunks (a
* context-only region collapses away, boundaries re-derive), preserve
* expansion state best-effort via old-side anchors, and repaint through
* the session render path — which also invalidates virtualized layout,
* since nothing else does at exit now that editing does not flip
* expandUnchanged. Marker-guarded and idempotent; CodeView also calls this
* when ending a session whose detach closure was consumed by a recycle.
* Safe on a cleaned-up instance: the recompute is pure metadata work and
* the deferred rerender is enabled-guarded. Returns true when a recompute
* ran.
*/
finalizeEditSessionHunks(): boolean;
applyDocumentChange(textDocument: TextDocument<'file-diff', LAnnotation>, newLineAnnotations?: DiffLineAnnotation<LAnnotation>[]): void;
updateRenderCache(dirtyLines: Map<number, Array<HighlightedToken>>, themeType: 'dark' | 'light', options?: {
shouldRefreshDiffsView?: boolean;
lineCountChangeInFlight?: boolean;
}): void;
private detachAdditionLines;
isLineRenderable(lineNumber: number): boolean;
getNearestRenderableLine(lineNumber: number, direction: 'up' | 'down'): number | undefined;
revealLine(lineNumber: number): boolean;
protected shouldSelfHealEditSession(): boolean;
protected escalateEditSessionRender(): void;
private handleEditSessionRender;
private removeRenderedCode;
private clearAuxiliaryNodes;
renderPlaceholder(height: number): boolean;
primeHighlightCache(fileDiff?: FileDiffMetadata | undefined): Promise<void>;
private cleanChildNodes;
private renderSeparators;
protected renderAnnotations(): void;
protected renderGutterUtility(): void;
protected getOrCreateFileContainer(fileContainer?: HTMLElement, parentNode?: HTMLElement): HTMLElement;
private adoptReusableShellElements;
private ensureSpriteSVG;
private getOrCreatePreNode;
protected syncCodeNodesFromPre(pre: HTMLPreElement): void;
private applyHeaderToDOM;
protected clearReusableHeader(): void;
private clearHeaderSlots;
private upsertHeaderSlotElement;
private replaceHeaderSlotContent;
private createHeaderSlotElement;
protected injectUnsafeCSS(): void;
private applyThemeState;
private hydrateMeasuredScrollbar;
protected shouldGuardRebuildScroll(): boolean;
private applyHunksToDOM;
private applyCodeColumnsInPlace;
private replaceCodeColumns;
private applyPartialRender;
private insertPartialHTML;
private refreshSplitDiffView;
private refreshUnifiedDiffView;
private renderPartialColumn;
private mergeBuffersIfNecessary;
private applyRowSpan;
private trimColumnRows;
private trimColumns;
private getBufferSize;
private updateBufferSize;
private getColumnPair;
private getCodeColumns;
protected updateBuffers(renderRange: RenderRange): void;
private applyBuffers;
protected shouldDisableVirtualizationBuffers(): boolean;
protected applyPreNodeAttributes(pre: HTMLPreElement, {
additionsContentAST,
deletionsContentAST,
totalLines
}: HunksRenderResult, customProperties?: CustomPreProperties): void;
private applyErrorToDOM;
private cleanupErrorWrapper;
}
//#endregion
export { FileDiff, FileDiffEditChangeHandler, type FileDiffEditCompleteEvent, FileDiffEditCompleteHandler, FileDiffHydrationProps, FileDiffOptions, FileDiffRenderBaseProps, FileDiffRenderProps, FileDiffType };
//# sourceMappingURL=FileDiff.d.ts.map