0
# Diff Editor
1
2
Side-by-side code comparison editor for visualizing differences between two versions of code. Perfect for code reviews, version comparisons, and merge conflict resolution.
3
4
## Capabilities
5
6
### DiffEditor Component
7
8
Monaco Diff Editor component that displays two code versions side-by-side with highlighted differences.
9
10
```typescript { .api }
11
/**
12
* Monaco Diff Editor React component for comparing two code versions
13
* @param props - Diff editor configuration and content
14
* @returns React component rendering Monaco Diff Editor
15
*/
16
declare const DiffEditor: React.ComponentType<DiffEditorProps>;
17
18
interface DiffEditorProps {
19
/** Original source (left side) value */
20
original?: string;
21
/** Modified source (right side) value */
22
modified?: string;
23
/** Language for both models */
24
language?: string;
25
/** Language for original model only */
26
originalLanguage?: string;
27
/** Language for modified model only */
28
modifiedLanguage?: string;
29
/** Path for the original model */
30
originalModelPath?: string;
31
/** Path for the modified model */
32
modifiedModelPath?: string;
33
/** Whether to keep original model when unmounting */
34
keepCurrentOriginalModel?: boolean;
35
/** Whether to keep modified model when unmounting */
36
keepCurrentModifiedModel?: boolean;
37
/** Theme for the monaco editor */
38
theme?: Theme | string;
39
/** Loading component shown before editor is mounted */
40
loading?: ReactNode;
41
/** Diff editor construction options */
42
options?: editor.IDiffEditorConstructionOptions;
43
/** Width of editor wrapper */
44
width?: number | string;
45
/** Height of editor wrapper */
46
height?: number | string;
47
/** CSS class name for editor container */
48
className?: string;
49
/** Props applied to the wrapper element */
50
wrapperProps?: object;
51
/** Called before diff editor is mounted */
52
beforeMount?: DiffBeforeMount;
53
/** Called when diff editor is mounted */
54
onMount?: DiffOnMount;
55
}
56
```
57
58
**Usage Examples:**
59
60
```typescript
61
import React from "react";
62
import { DiffEditor } from "@monaco-editor/react";
63
64
// Basic diff comparison
65
function BasicDiffEditor() {
66
const originalCode = `function hello() {
67
console.log("Hello");
68
}`;
69
70
const modifiedCode = `function hello(name) {
71
console.log("Hello " + name);
72
}`;
73
74
return (
75
<DiffEditor
76
height="400px"
77
language="javascript"
78
original={originalCode}
79
modified={modifiedCode}
80
/>
81
);
82
}
83
84
// Advanced diff with custom options
85
function AdvancedDiffEditor() {
86
const original = `const users = [
87
{ name: "Alice", age: 25 },
88
{ name: "Bob", age: 30 }
89
];`;
90
91
const modified = `const users = [
92
{ name: "Alice", age: 25, active: true },
93
{ name: "Bob", age: 30, active: false },
94
{ name: "Charlie", age: 35, active: true }
95
];`;
96
97
const options = {
98
readOnly: false,
99
renderSideBySide: true,
100
enableSplitViewResizing: true,
101
renderOverviewRuler: true,
102
ignoreTrimWhitespace: false,
103
};
104
105
return (
106
<DiffEditor
107
height="500px"
108
language="javascript"
109
original={original}
110
modified={modified}
111
options={options}
112
theme="vs-dark"
113
/>
114
);
115
}
116
117
// Multi-language diff
118
function MultiLanguageDiff() {
119
const tsCode = `interface User {
120
name: string;
121
age: number;
122
}`;
123
124
const jsCode = `/**
125
* @typedef {Object} User
126
* @property {string} name
127
* @property {number} age
128
*/`;
129
130
return (
131
<DiffEditor
132
height="300px"
133
originalLanguage="typescript"
134
modifiedLanguage="javascript"
135
original={tsCode}
136
modified={jsCode}
137
/>
138
);
139
}
140
```
141
142
### Diff Event Handlers
143
144
Event handlers for diff editor lifecycle management.
145
146
```typescript { .api }
147
/**
148
* Called when diff editor is mounted
149
* @param editor - The standalone diff editor instance
150
* @param monaco - The monaco editor namespace
151
*/
152
type DiffOnMount = (editor: editor.IStandaloneDiffEditor, monaco: Monaco) => void;
153
154
/**
155
* Called before diff editor is mounted
156
* @param monaco - The monaco editor namespace
157
*/
158
type DiffBeforeMount = (monaco: Monaco) => void;
159
160
type MonacoDiffEditor = editor.IStandaloneDiffEditor;
161
```
162
163
**Event Handler Examples:**
164
165
```typescript
166
import { DiffEditor } from "@monaco-editor/react";
167
168
function DiffWithHandlers() {
169
const handleBeforeMount = (monaco) => {
170
// Configure monaco before diff editor creation
171
monaco.editor.defineTheme('diffTheme', {
172
base: 'vs',
173
inherit: true,
174
rules: [],
175
colors: {
176
'diffEditor.insertedTextBackground': '#90EE9050',
177
'diffEditor.removedTextBackground': '#FF634750',
178
}
179
});
180
};
181
182
const handleMount = (diffEditor, monaco) => {
183
// Access both original and modified editors
184
const originalEditor = diffEditor.getOriginalEditor();
185
const modifiedEditor = diffEditor.getModifiedEditor();
186
187
// Configure diff editor behavior
188
diffEditor.updateOptions({
189
renderSideBySide: true,
190
enableSplitViewResizing: true,
191
});
192
193
// Focus on modified editor
194
modifiedEditor.focus();
195
196
// Add navigation commands
197
diffEditor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.DownArrow, () => {
198
diffEditor.goToNextDiff();
199
});
200
201
diffEditor.addCommand(monaco.KeyMod.Alt | monaco.KeyCode.UpArrow, () => {
202
diffEditor.goToPreviousDiff();
203
});
204
};
205
206
return (
207
<DiffEditor
208
height="400px"
209
language="javascript"
210
original="const a = 1;"
211
modified="const a = 2;"
212
beforeMount={handleBeforeMount}
213
onMount={handleMount}
214
theme="diffTheme"
215
/>
216
);
217
}
218
```
219
220
### Diff Editor Configuration
221
222
Configuration options specific to the diff editor functionality.
223
224
```typescript { .api }
225
// Diff editor specific options
226
interface DiffEditorOptions {
227
/** Monaco diff editor construction options */
228
options?: editor.IDiffEditorConstructionOptions;
229
}
230
231
// Content configuration for diff comparison
232
interface DiffContentProps {
233
/** Original (left) content */
234
original?: string;
235
/** Modified (right) content */
236
modified?: string;
237
/** Language for both sides */
238
language?: string;
239
/** Separate language for original side */
240
originalLanguage?: string;
241
/** Separate language for modified side */
242
modifiedLanguage?: string;
243
}
244
245
// Model paths for diff editor
246
interface DiffModelProps {
247
/** Path/URI for original model */
248
originalModelPath?: string;
249
/** Path/URI for modified model */
250
modifiedModelPath?: string;
251
/** Keep original model when unmounting */
252
keepCurrentOriginalModel?: boolean;
253
/** Keep modified model when unmounting */
254
keepCurrentModifiedModel?: boolean;
255
}
256
```
257
258
**Configuration Examples:**
259
260
```typescript
261
import { DiffEditor } from "@monaco-editor/react";
262
263
// Customized diff editor
264
function CustomDiffEditor() {
265
const diffOptions = {
266
// Display options
267
renderSideBySide: true, // Side-by-side vs inline
268
renderMarginRevertIcon: true, // Show revert icons
269
renderOverviewRuler: true, // Show overview ruler
270
renderIndicators: true, // Show change indicators
271
272
// Interaction options
273
enableSplitViewResizing: true, // Allow resizing split
274
readOnly: false, // Allow editing
275
originalEditable: false, // Original side read-only
276
277
// Diff algorithm options
278
ignoreTrimWhitespace: true, // Ignore whitespace at line ends
279
renderWhitespace: 'selection', // Show whitespace
280
diffWordWrap: 'inherit', // Word wrapping
281
282
// Navigation
283
automaticLayout: true, // Auto-resize
284
scrollBeyondLastLine: false,
285
};
286
287
return (
288
<DiffEditor
289
height="600px"
290
width="100%"
291
language="typescript"
292
theme="vs-dark"
293
options={diffOptions}
294
original="// Original code"
295
modified="// Modified code"
296
/>
297
);
298
}
299
300
// File-based diff with model paths
301
function FileDiffEditor() {
302
return (
303
<DiffEditor
304
height="500px"
305
language="javascript"
306
originalModelPath="file:///original/main.js"
307
modifiedModelPath="file:///modified/main.js"
308
original={`function calculate(a, b) {
309
return a + b;
310
}`}
311
modified={`function calculate(a, b) {
312
// Added validation
313
if (typeof a !== 'number' || typeof b !== 'number') {
314
throw new Error('Arguments must be numbers');
315
}
316
return a + b;
317
}`}
318
keepCurrentOriginalModel={true}
319
keepCurrentModifiedModel={true}
320
/>
321
);
322
}
323
```
324
325
## Common Use Cases
326
327
### Code Review Workflow
328
329
```typescript
330
import React, { useState } from "react";
331
import { DiffEditor } from "@monaco-editor/react";
332
333
function CodeReviewDiff() {
334
const [currentFile, setCurrentFile] = useState(0);
335
336
const changedFiles = [
337
{
338
filename: "src/utils.js",
339
original: `export function formatDate(date) {
340
return date.toLocaleDateString();
341
}`,
342
modified: `export function formatDate(date) {
343
if (!date || !(date instanceof Date)) {
344
throw new Error('Invalid date provided');
345
}
346
return date.toLocaleDateString();
347
}`
348
},
349
{
350
filename: "src/api.js",
351
original: `async function fetchUser(id) {
352
const response = await fetch(\`/api/users/\${id}\`);
353
return response.json();
354
}`,
355
modified: `async function fetchUser(id) {
356
const response = await fetch(\`/api/users/\${id}\`);
357
if (!response.ok) {
358
throw new Error(\`Failed to fetch user: \${response.status}\`);
359
}
360
return response.json();
361
}`
362
}
363
];
364
365
return (
366
<div>
367
<div style={{ marginBottom: 10 }}>
368
{changedFiles.map((file, index) => (
369
<button
370
key={index}
371
onClick={() => setCurrentFile(index)}
372
style={{
373
marginRight: 10,
374
fontWeight: currentFile === index ? 'bold' : 'normal',
375
background: currentFile === index ? '#007acc' : '#f0f0f0',
376
color: currentFile === index ? 'white' : 'black',
377
border: 'none',
378
padding: '8px 12px',
379
cursor: 'pointer'
380
}}
381
>
382
{file.filename}
383
</button>
384
))}
385
</div>
386
387
<DiffEditor
388
height="500px"
389
language="javascript"
390
original={changedFiles[currentFile].original}
391
modified={changedFiles[currentFile].modified}
392
options={{
393
renderSideBySide: true,
394
renderMarginRevertIcon: false,
395
readOnly: true,
396
}}
397
/>
398
</div>
399
);
400
}
401
```
402
403
### Version Comparison
404
405
```typescript
406
function VersionComparisonDiff() {
407
const [versions] = useState([
408
{ version: "1.0.0", code: "const API_URL = 'http://localhost';" },
409
{ version: "1.1.0", code: "const API_URL = process.env.API_URL || 'http://localhost';" },
410
{ version: "2.0.0", code: "const API_URL = process.env.REACT_APP_API_URL || 'https://api.example.com';" }
411
]);
412
413
const [leftVersion, setLeftVersion] = useState(0);
414
const [rightVersion, setRightVersion] = useState(1);
415
416
return (
417
<div>
418
<div style={{ marginBottom: 10 }}>
419
<label>Original:
420
<select value={leftVersion} onChange={(e) => setLeftVersion(+e.target.value)}>
421
{versions.map((v, i) => (
422
<option key={i} value={i}>{v.version}</option>
423
))}
424
</select>
425
</label>
426
<label style={{ marginLeft: 20 }}>Modified:
427
<select value={rightVersion} onChange={(e) => setRightVersion(+e.target.value)}>
428
{versions.map((v, i) => (
429
<option key={i} value={i}>{v.version}</option>
430
))}
431
</select>
432
</label>
433
</div>
434
435
<DiffEditor
436
height="300px"
437
language="javascript"
438
original={versions[leftVersion].code}
439
modified={versions[rightVersion].code}
440
/>
441
</div>
442
);
443
}
444
```