diff --git a/README.md b/README.md index 049e4d0..f67b89f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ class Diff extends PureComponent { | splitView | `boolean` | `true` | Switch between `unified` and `split` view. | | disableWordDiff | `boolean` | `false` | Show and hide word diff in a diff line. | | compareMethod | `DiffMethod \| (string, string) => diff.Change[]` | `DiffMethod.CHARS` | JsDiff text diff method. See [Text block diff comparison](#text-block-diff-comparison). **Important:** For JSON files use `DiffMethod.JSON`, for YAML files use `DiffMethod.YAML` - these use optimized structural comparison that is significantly faster than generic text diff for large files. | +| ignoreWhitespace | `boolean` | `false` | Ignore leading and trailing whitespace when computing the line diff. | | renderGutter | `(diffData) => ReactNode` | `undefined` | Function that can be used to render an extra gutter with various information next to the line number. | | hideLineNumbers | `boolean` | `false` | Show and hide line numbers. | | alwaysShowLines | `string[]` | `[]` | List of lines to always be shown, regardless of diff status. Line numbers are prefixed with `L` and `R` for the left and right section of the diff viewer, respectively. For example, `L-20` means 20th line in the left pane. `extraLinesSurroundingDiff` applies to these lines as well. | diff --git a/src/compute-lines.ts b/src/compute-lines.ts index b3451c9..ce03ab1 100644 --- a/src/compute-lines.ts +++ b/src/compute-lines.ts @@ -440,6 +440,7 @@ const computeDiff = ( * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api * @param linesOffset line number to start counting from * @param showLines lines that are always shown, regardless of diff + * @param ignoreWhitespace Ignore leading/trailing whitespace when comparing lines */ const computeLineInformation = ( oldString: string | Record, @@ -451,6 +452,7 @@ const computeLineInformation = ( linesOffset = 0, showLines: string[] = [], deferWordDiff = false, + ignoreWhitespace = false, ): ComputedLineInformation => { let diffArray: Change[] = []; @@ -468,11 +470,13 @@ const computeLineInformation = ( // If YAML parsing fails, fall back to line diff diffArray = diff.diffLines(oldString, newString, { newlineIsToken: false, + ignoreWhitespace, }); } } else { diffArray = diff.diffLines(oldString, newString, { newlineIsToken: false, + ignoreWhitespace, }); } } else { @@ -638,6 +642,7 @@ const computeLineInformation = ( * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api * @param linesOffset line number to start counting from * @param showLines lines that are always shown, regardless of diff + * @param ignoreWhitespace Ignore leading/trailing whitespace when comparing lines * @returns Promise - Resolves with line-by-line diff data from the worker. */ // Cached Blob URL for the worker - created once and reused @@ -678,8 +683,9 @@ const computeLineInformationWorker = async ( showLines: string[] = [], deferWordDiff = false, disableWorker = false, + ignoreWhitespace = false, ): Promise => { - const fallback = () => computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff); + const fallback = () => computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff, ignoreWhitespace); if (disableWorker) { return Promise.resolve(fallback()); @@ -711,7 +717,7 @@ const computeLineInformationWorker = async ( resolve(fallback()); }; - worker.postMessage({ oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff }); + worker.postMessage({ oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff, ignoreWhitespace }); }); }; diff --git a/src/computeWorker.ts b/src/computeWorker.ts index 6d424ff..626d66f 100644 --- a/src/computeWorker.ts +++ b/src/computeWorker.ts @@ -5,7 +5,7 @@ import { computeLineInformation } from "./compute-lines.js"; * When the main thread sends a message to this worker (via postMessage), this function is triggered. */ self.onmessage = (e) => { - const { oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff } = e.data; - const result = computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff); + const { oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff, ignoreWhitespace } = e.data; + const result = computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff, ignoreWhitespace); self.postMessage(result); }; diff --git a/src/index.tsx b/src/index.tsx index 4f63613..f1270c3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -289,6 +289,9 @@ export interface ReactDiffViewerProps { disableWordDiff?: boolean; // JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api compareMethod?: DiffMethod | ((oldStr: string, newStr: string) => Change[]); + // Ignore leading/trailing whitespace when computing the line diff, so lines that + // differ only in indentation or trailing spaces are not reported as changes. + ignoreWhitespace?: boolean; // Number of unmodified lines surrounding each line diff. extraLinesSurroundingDiff?: number; // Show/hide line number. @@ -425,6 +428,7 @@ class DiffViewer extends React.Component< highlightLines: [], disableWordDiff: false, compareMethod: DiffMethod.CHARS, + ignoreWhitespace: false, styles: {}, hideLineNumbers: false, extraLinesSurroundingDiff: 3, @@ -1208,6 +1212,7 @@ class DiffViewer extends React.Component< linesOffset, alwaysShowLines, extraLinesSurroundingDiff, + ignoreWhitespace, } = this.props; return JSON.stringify({ @@ -1218,6 +1223,7 @@ class DiffViewer extends React.Component< linesOffset, alwaysShowLines, extraLinesSurroundingDiff, + ignoreWhitespace, }); } @@ -1270,6 +1276,7 @@ class DiffViewer extends React.Component< this.props.alwaysShowLines, shouldDeferWordDiff, this.props.disableWorker, + this.props.ignoreWhitespace, ); const rawExtraLines = this.props.extraLinesSurroundingDiff ?? 3; @@ -1662,6 +1669,7 @@ class DiffViewer extends React.Component< prevProps.newValue !== this.props.newValue || prevProps.compareMethod !== this.props.compareMethod || prevProps.disableWordDiff !== this.props.disableWordDiff || + prevProps.ignoreWhitespace !== this.props.ignoreWhitespace || prevProps.linesOffset !== this.props.linesOffset ) { // Clear word diff cache when diff changes diff --git a/test/compute-lines.test.ts b/test/compute-lines.test.ts index 461e744..9717e3a 100644 --- a/test/compute-lines.test.ts +++ b/test/compute-lines.test.ts @@ -434,6 +434,38 @@ Also this info`; diffLines: [0, 1], }); }); + + describe("Ignoring whitespace", (): void => { + const oldCode = ["function example() {", " const x = 1;", " return x;", "}"].join("\n"); + + it("Should report indentation-only changes by default", (): void => { + const newCode = ["function example() {", " const x = 1;", " return x;", "}"].join("\n"); + + expect(computeLineInformation(oldCode, newCode).diffLines).toEqual([1]); + }); + + it("Should ignore indentation-only changes when ignoreWhitespace is set", (): void => { + const newCode = ["function example() {", " const x = 1;", " return x;", "}"].join("\n"); + + const result = computeLineInformation(newCode, oldCode, false, undefined, 0, [], false, true); + + expect(result.diffLines).toEqual([]); + expect(result.lineInformation.every(({ left, right }) => left.type === 0 && right.type === 0)).toBe(true); + }); + + it("Should ignore trailing-whitespace-only changes when ignoreWhitespace is set", (): void => { + const newCode = ["function example() {", " const x = 1; ", " return x;", "}"].join("\n"); + + expect(computeLineInformation(oldCode, newCode).diffLines).toEqual([1]); + expect(computeLineInformation(oldCode, newCode, false, undefined, 0, [], false, true).diffLines).toEqual([]); + }); + + it("Should still report real changes when ignoreWhitespace is set", (): void => { + const newCode = ["function example() {", " const x = 2;", " return x;", "}"].join("\n"); + + expect(computeLineInformation(oldCode, newCode, false, undefined, 0, [], false, true).diffLines).toEqual([1]); + }); + }); }); import * as diff from 'diff';