diff --git a/packages/diffs/src/components/FileDiff.ts b/packages/diffs/src/components/FileDiff.ts index 2cbc508bb..7f3a1a110 100644 --- a/packages/diffs/src/components/FileDiff.ts +++ b/packages/diffs/src/components/FileDiff.ts @@ -588,7 +588,7 @@ export class FileDiff { this.fileDiff = fileDiff ?? (oldFile != null && newFile != null - ? parseDiffFromFile(oldFile, newFile) + ? parseDiffFromFile(oldFile, newFile, this.options.parseDiffOptions) : undefined); if (this.pre == null) { @@ -706,7 +706,11 @@ export class FileDiff { this.fileDiff = fileDiff; } else if (oldFile != null && newFile != null && filesDidChange) { diffDidChange = true; - this.fileDiff = parseDiffFromFile(oldFile, newFile); + this.fileDiff = parseDiffFromFile( + oldFile, + newFile, + this.options.parseDiffOptions + ); } if (lineAnnotations != null) { diff --git a/packages/diffs/src/components/VirtualizedFileDiff.ts b/packages/diffs/src/components/VirtualizedFileDiff.ts index e4046f7cb..0c98df628 100644 --- a/packages/diffs/src/components/VirtualizedFileDiff.ts +++ b/packages/diffs/src/components/VirtualizedFileDiff.ts @@ -347,11 +347,7 @@ export class VirtualizedFileDiff< this.fileDiff ??= fileDiff ?? (oldFile != null && newFile != null - ? // NOTE(amadeus): We might be forcing ourselves to double up the - // computation of fileDiff (in the super.render() call), so we might want - // to figure out a way to avoid that. That also could be just as simple as - // passing through fileDiff though... so maybe we good? - parseDiffFromFile(oldFile, newFile) + ? parseDiffFromFile(oldFile, newFile, this.options.parseDiffOptions) : undefined); fileContainer = this.getOrCreateFileContainer(fileContainer); diff --git a/packages/diffs/src/ssr/preloadDiffs.ts b/packages/diffs/src/ssr/preloadDiffs.ts index 3acfed112..4ee40b75b 100644 --- a/packages/diffs/src/ssr/preloadDiffs.ts +++ b/packages/diffs/src/ssr/preloadDiffs.ts @@ -40,7 +40,7 @@ export async function preloadDiffHTML({ annotations, }: PreloadDiffOptions): Promise { if (fileDiff == null && oldFile != null && newFile != null) { - fileDiff = parseDiffFromFile(oldFile, newFile); + fileDiff = parseDiffFromFile(oldFile, newFile, options?.parseDiffOptions); } if (fileDiff == null) { throw new Error( diff --git a/packages/diffs/src/types.ts b/packages/diffs/src/types.ts index 9c9a4caba..6fe6a809e 100644 --- a/packages/diffs/src/types.ts +++ b/packages/diffs/src/types.ts @@ -1,3 +1,4 @@ +import type { CreatePatchOptionsNonabortable } from 'diff'; import type { ElementContent } from 'hast'; import type { BundledLanguage, @@ -393,10 +394,19 @@ export interface BaseDiffOptions extends BaseCodeOptions { // How many lines to expand per click expansionLineCount?: number; // 100 is default + + /** + * Options forwarded to the underlying diff algorithm when computing diffs + * from file contents (oldFile/newFile). Has no effect on pre-parsed patches. + */ + parseDiffOptions?: CreatePatchOptionsNonabortable; } export type BaseDiffOptionsWithDefaults = Required< - Omit + Omit< + BaseDiffOptions, + 'unsafeCSS' | 'preferredHighlighter' | 'parseDiffOptions' + > >; export type CustomPreProperties = Record; diff --git a/packages/diffs/src/utils/areOptionsEqual.ts b/packages/diffs/src/utils/areOptionsEqual.ts index 3a3df4984..edb0bf81c 100644 --- a/packages/diffs/src/utils/areOptionsEqual.ts +++ b/packages/diffs/src/utils/areOptionsEqual.ts @@ -1,17 +1,36 @@ +import type { CreatePatchOptionsNonabortable } from 'diff'; + import type { FileDiffOptions } from '../components/FileDiff'; import { DEFAULT_THEMES } from '../constants'; import type { FileOptions } from '../react'; import { areObjectsEqual } from './areObjectsEqual'; import { areThemesEqual } from './areThemesEqual'; +type AnyOptions = FileOptions | FileDiffOptions | undefined; + export function areOptionsEqual( - optionsA: FileOptions | FileDiffOptions | undefined, - optionsB: FileOptions | FileDiffOptions | undefined + optionsA: AnyOptions, + optionsB: AnyOptions ): boolean { const themeA = optionsA?.theme ?? DEFAULT_THEMES; const themeB = optionsB?.theme ?? DEFAULT_THEMES; + const diffOptsA = getParseDiffOptions(optionsA); + const diffOptsB = getParseDiffOptions(optionsB); return ( areThemesEqual(themeA, themeB) && - areObjectsEqual(optionsA, optionsB, ['theme']) + areObjectsEqual(optionsA, optionsB, [ + 'theme', + 'parseDiffOptions' as keyof typeof optionsA, + ]) && + areObjectsEqual(diffOptsA, diffOptsB) ); } + +function getParseDiffOptions( + options: AnyOptions +): CreatePatchOptionsNonabortable | undefined { + if (options != null && 'parseDiffOptions' in options) { + return options.parseDiffOptions; + } + return undefined; +} diff --git a/packages/diffs/test/parseDiffFromFile.test.ts b/packages/diffs/test/parseDiffFromFile.test.ts index c7d280bde..368c8f633 100644 --- a/packages/diffs/test/parseDiffFromFile.test.ts +++ b/packages/diffs/test/parseDiffFromFile.test.ts @@ -35,4 +35,23 @@ describe('parseDiffFromFile', () => { const expectedNewLineCount = fileNew.split(/(?<=\n)/).length; expect(result.additionLines.length).toBe(expectedNewLineCount); }); + + test('ignoreWhitespace hides leading/trailing whitespace changes', () => { + const oldFile = { + name: 'test.txt', + contents: 'hello world\nfoo bar\n', + }; + const newFile = { + name: 'test.txt', + contents: ' hello world\nfoo bar\n', + }; + + const withWhitespace = parseDiffFromFile(oldFile, newFile); + expect(withWhitespace.hunks.length).toBeGreaterThan(0); + + const withoutWhitespace = parseDiffFromFile(oldFile, newFile, { + ignoreWhitespace: true, + }); + expect(withoutWhitespace.hunks).toHaveLength(0); + }); });