Skip to content

Commit 6075ff5

Browse files
authored
Add support for various JsDiff text comparison methods (#51)
Add support for various JsDiff text comparison methods
2 parents 7a30013 + 736bb62 commit 6075ff5

File tree

5 files changed

+339
-28
lines changed

5 files changed

+339
-28
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class Diff extends PureComponent {
7676
|newValue |`string` |`''` |New value as string. |
7777
|splitView |`boolean` |`true` |Switch between `unified` and `split` view. |
7878
|disableWordDiff |`boolean` |`false` |Show and hide word diff in a diff line. |
79+
|compareMethod |`string` |`'diffChars'` |JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api |
7980
|hideLineNumbers |`boolean` |`false` |Show and hide line numbers. |
8081
|renderContent |`function` |`undefined` |Render Prop API to render code in the diff viewer. Helpful for [syntax highlighting](#syntax-highlighting) |
8182
|onLineNumberClick |`function` |`undefined` |Event handler for line number click. `(lineId: string) => void` |
@@ -146,6 +147,43 @@ class Diff extends PureComponent {
146147
}
147148
```
148149

150+
## Text block diff comparison
151+
152+
Different styles of text block diffing are possible by using the enums corresponding to various v4.0.1 JsDiff text block method names ([learn more](https://github.com/kpdecker/jsdiff/tree/v4.0.1#api)).
153+
154+
```javascript
155+
import React, { PureComponent } from 'react'
156+
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer'
157+
158+
const oldCode = `
159+
{
160+
"name": "Original name",
161+
"description": null
162+
}
163+
`
164+
const newCode = `
165+
{
166+
"name": "My updated name",
167+
"description": "Brand new description",
168+
"status": "running"
169+
}
170+
`
171+
172+
class Diff extends PureComponent {
173+
render = () => {
174+
return (
175+
<ReactDiffViewer
176+
oldValue={oldCode}
177+
newValue={newCode}
178+
compareMethod={DiffMethod.WORDS}
179+
splitView={true}
180+
renderContent={this.highlightSyntax}
181+
/>
182+
)
183+
}
184+
}
185+
```
186+
149187

150188
## Overriding Styles
151189

examples/src/index.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface ExampleState {
1919
highlightLine?: string[];
2020
language?: string;
2121
enableSyntaxHighlighting?: boolean;
22+
compareMethod?: string;
2223
}
2324

2425
const P = (window as any).Prism;
@@ -31,6 +32,7 @@ class Example extends React.Component<{}, ExampleState> {
3132
highlightLine: [],
3233
language: 'javascript',
3334
enableSyntaxHighlighting: true,
35+
compareMethod: 'diffChars'
3436
};
3537
}
3638

@@ -45,6 +47,9 @@ class Example extends React.Component<{}, ExampleState> {
4547
private onLanguageChange = (e: any): void =>
4648
this.setState({ language: e.target.value, highlightLine: [] });
4749

50+
private onCompareMethodChange = (e: any): void =>
51+
this.setState({ compareMethod: e.target.value });
52+
4853
private onLineNumberClick = (
4954
id: string,
5055
e: React.MouseEvent<HTMLTableCellElement>,
@@ -155,16 +160,41 @@ class Example extends React.Component<{}, ExampleState> {
155160
</div>
156161
</div>
157162
<div className="controls">
158-
<select
159-
name="language"
160-
id="language"
161-
onChange={this.onLanguageChange}
162-
value={this.state.language}
163-
>
164-
<option value="json">JSON</option>
165-
<option value="xml">XML</option>
166-
<option value="javascript">Javascript</option>
167-
</select>
163+
<span>
164+
<label htmlFor="js_diff_compare_method">JsDiff Compare Method</label>
165+
{' '}
166+
(<a href="https://github.com/kpdecker/jsdiff/tree/v4.0.1#api">Learn More</a>)
167+
{' '}
168+
<select
169+
name="js_diff_compare_method"
170+
id="js_diff_compare_method"
171+
onChange={this.onCompareMethodChange}
172+
value={this.state.compareMethod}
173+
>
174+
<option value="disabled">DISABLE</option>
175+
<option value="diffChars">diffChars</option>
176+
<option value="diffWords">diffWords</option>
177+
<option value="diffWordsWithSpace">diffWordsWithSpace</option>
178+
<option value="diffLines">diffLines</option>
179+
<option value="diffTrimmedLines">diffTrimmedLines</option>
180+
<option value="diffCss">diffCss</option>
181+
<option value="diffSentences">diffSentences</option>
182+
</select>
183+
</span>
184+
<span>
185+
<label htmlFor="language">Language</label>
186+
{' '}
187+
<select
188+
name="language"
189+
id="language"
190+
onChange={this.onLanguageChange}
191+
value={this.state.language}
192+
>
193+
<option value="json">JSON</option>
194+
<option value="xml">XML</option>
195+
<option value="javascript">Javascript</option>
196+
</select>
197+
</span>
168198
<span>
169199
<label>
170200
<input
@@ -190,6 +220,8 @@ class Example extends React.Component<{}, ExampleState> {
190220
</div>
191221
<div className="diff-viewer">
192222
<ReactDiff
223+
disableWordDiff={ this.state.compareMethod === 'disabled' }
224+
compareMethod={ this.state.compareMethod }
193225
highlightLines={this.state.highlightLine}
194226
onLineNumberClick={this.onLineNumberClick}
195227
oldValue={oldValue}

src/compute-lines.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import * as diff from 'diff';
22

3+
const jsDiff: { [key: string]: any } = diff;
4+
35
export enum DiffType {
46
DEFAULT = 0,
57
ADDED = 1,
68
REMOVED = 2,
79
}
810

11+
// See https://github.com/kpdecker/jsdiff/tree/v4.0.1#api for more info on the below JsDiff methods
12+
export enum DiffMethod {
13+
CHARS = 'diffChars',
14+
WORDS = 'diffWords',
15+
WORDS_WITH_SPACE = 'diffWordsWithSpace',
16+
LINES = 'diffLines',
17+
TRIMMED_LINES = 'diffTrimmedLines',
18+
SENTENCES = 'diffSentences',
19+
CSS = 'diffCss',
20+
}
21+
922
export interface DiffInformation {
1023
value?: string | DiffInformation[];
1124
lineNumber?: number;
@@ -22,11 +35,18 @@ export interface ComputedLineInformation {
2235
diffLines: number[];
2336
}
2437

25-
export interface WordDiffInformation {
38+
export interface ComputedDiffInformation {
2639
left?: DiffInformation[];
2740
right?: DiffInformation[];
2841
}
2942

43+
// See https://github.com/kpdecker/jsdiff/tree/v4.0.1#change-objects for more info on JsDiff Change Objects
44+
export interface JsDiffChangeObject {
45+
added?: boolean;
46+
removed?: boolean;
47+
value?: string;
48+
}
49+
3050
/**
3151
* Splits diff text by new line and computes final list of diff lines based on
3252
* conditions.
@@ -60,14 +80,15 @@ const constructLines = (value: string): string[] => {
6080

6181
/**
6282
* Computes word diff information in the line.
83+
* [TODO]: Consider adding options argument for JsDiff text block comparison
6384
*
6485
* @param oldValue Old word in the line.
6586
* @param newValue New word in the line.
87+
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
6688
*/
67-
const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformation => {
68-
const diffArray = diff
69-
.diffChars(oldValue, newValue);
70-
const wordDiff: WordDiffInformation = {
89+
const computeDiff = (oldValue: string, newValue: string, compareMethod: string): ComputedDiffInformation => {
90+
const diffArray: Array<JsDiffChangeObject> = jsDiff[compareMethod](oldValue, newValue);
91+
const computedDiff: ComputedDiffInformation = {
7192
left: [],
7293
right: [],
7394
};
@@ -77,22 +98,22 @@ const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformatio
7798
if (added) {
7899
diffInformation.type = DiffType.ADDED;
79100
diffInformation.value = value;
80-
wordDiff.right.push(diffInformation);
101+
computedDiff.right.push(diffInformation);
81102
}
82103
if (removed) {
83104
diffInformation.type = DiffType.REMOVED;
84105
diffInformation.value = value;
85-
wordDiff.left.push(diffInformation);
106+
computedDiff.left.push(diffInformation);
86107
}
87108
if (!removed && !added) {
88109
diffInformation.type = DiffType.DEFAULT;
89110
diffInformation.value = value;
90-
wordDiff.right.push(diffInformation);
91-
wordDiff.left.push(diffInformation);
111+
computedDiff.right.push(diffInformation);
112+
computedDiff.left.push(diffInformation);
92113
}
93114
return diffInformation;
94115
});
95-
return wordDiff;
116+
return computedDiff;
96117
};
97118

98119
/**
@@ -106,11 +127,13 @@ const computeWordDiff = (oldValue: string, newValue: string): WordDiffInformatio
106127
* @param oldString Old string to compare.
107128
* @param newString New string to compare with old string.
108129
* @param disableWordDiff Flag to enable/disable word diff.
130+
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
109131
*/
110132
const computeLineInformation = (
111133
oldString: string,
112134
newString: string,
113135
disableWordDiff: boolean = false,
136+
compareMethod: string = DiffMethod.CHARS,
114137
): ComputedLineInformation => {
115138
const diffArray = diff.diffLines(
116139
oldString.trimRight(),
@@ -173,12 +196,12 @@ const computeLineInformation = (
173196
right.type = type;
174197
// Do word level diff and assign the corresponding values to the
175198
// left and right diff information object.
176-
if (disableWordDiff) {
199+
if (disableWordDiff || !(<any>Object).values(DiffMethod).includes(compareMethod)) {
177200
right.value = rightValue;
178201
} else {
179-
const wordDiff = computeWordDiff(line, rightValue as string);
180-
right.value = wordDiff.right;
181-
left.value = wordDiff.left;
202+
const computedDiff = computeDiff(line, rightValue as string, compareMethod);
203+
right.value = computedDiff.right;
204+
left.value = computedDiff.left;
182205
}
183206
}
184207
}

src/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
LineInformation,
88
DiffInformation,
99
DiffType,
10+
DiffMethod,
1011
} from './compute-lines';
1112
import computeStyles, { ReactDiffViewerStylesOverride, ReactDiffViewerStyles } from './styles';
1213

@@ -29,6 +30,8 @@ export interface ReactDiffViewerProps {
2930
splitView?: boolean;
3031
// Enable/Disable word diff.
3132
disableWordDiff?: boolean;
33+
// JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
34+
compareMethod?: string;
3235
// Number of unmodified lines surrounding each line diff.
3336
extraLinesSurroundingDiff?: number;
3437
// Show/hide line number.
@@ -68,6 +71,7 @@ class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerSt
6871
splitView: true,
6972
highlightLines: [],
7073
disableWordDiff: false,
74+
compareMethod: DiffMethod.CHARS,
7175
styles: {},
7276
hideLineNumbers: false,
7377
extraLinesSurroundingDiff: 3,
@@ -79,6 +83,7 @@ class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerSt
7983
newValue: PropTypes.string.isRequired,
8084
splitView: PropTypes.bool,
8185
disableWordDiff: PropTypes.bool,
86+
compareMethod: PropTypes.oneOf(Object.values(DiffMethod)),
8287
renderContent: PropTypes.func,
8388
onLineNumberClick: PropTypes.func,
8489
extraLinesSurroundingDiff: PropTypes.number,
@@ -420,11 +425,12 @@ class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerSt
420425
* Generates the entire diff view.
421426
*/
422427
private renderDiff = (): JSX.Element[] => {
423-
const { oldValue, newValue, splitView } = this.props;
428+
const { oldValue, newValue, splitView, disableWordDiff, compareMethod } = this.props;
424429
const { lineInformation, diffLines } = computeLineInformation(
425430
oldValue,
426431
newValue,
427-
this.props.disableWordDiff,
432+
disableWordDiff,
433+
compareMethod,
428434
);
429435
const extraLines = this.props.extraLinesSurroundingDiff < 0
430436
? 0
@@ -501,4 +507,4 @@ class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerSt
501507
}
502508

503509
export default DiffViewer;
504-
export { ReactDiffViewerStylesOverride };
510+
export { ReactDiffViewerStylesOverride, DiffMethod };

0 commit comments

Comments
 (0)