react-diff-viewer-continued 4.0.6 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,10 +22,13 @@ export interface ReactDiffViewerStyles {
22
22
  emptyGutter?: string;
23
23
  emptyLine?: string;
24
24
  codeFold?: string;
25
+ stickyHeader?: string;
26
+ columnHeaders?: string;
25
27
  titleBlock?: string;
26
28
  content?: string;
27
29
  column?: string;
28
30
  noSelect?: string;
31
+ noWrap?: string;
29
32
  splitView?: string;
30
33
  allExpandButton?: string;
31
34
  [key: string]: string | undefined;
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  var __rest = (this && this.__rest) || function (s, e) {
3
2
  var t = {};
4
3
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -10,12 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
10
9
  }
11
10
  return t;
12
11
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- const create_instance_1 = __importDefault(require("@emotion/css/create-instance"));
18
- exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
12
+ import createEmotion from "@emotion/css/create-instance";
13
+ export default (styleOverride, useDarkTheme = false, nonce = "") => {
19
14
  const { variables: overrideVariables = {} } = styleOverride, styles = __rest(styleOverride, ["variables"]);
20
15
  const themeVariables = {
21
16
  light: Object.assign({
@@ -48,15 +43,15 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
48
43
  dark: Object.assign({
49
44
  diffViewerBackground: "#2e303c",
50
45
  diffViewerColor: "#FFF",
51
- addedBackground: "#044B53",
46
+ addedBackground: "#2ea04326",
52
47
  addedColor: "white",
53
- removedBackground: "#632F34",
48
+ removedBackground: "#f851491a",
54
49
  removedColor: "white",
55
50
  changedBackground: "#3e302c",
56
- wordAddedBackground: "#055d67",
57
- wordRemovedBackground: "#7d383f",
58
- addedGutterBackground: "#034148",
59
- removedGutterBackground: "#632b30",
51
+ wordAddedBackground: "#2ea04366",
52
+ wordRemovedBackground: "#f8514966",
53
+ addedGutterBackground: "#3fb9504d",
54
+ removedGutterBackground: "#f851494d",
60
55
  gutterBackground: "#2c2f3a",
61
56
  gutterBackgroundDark: "#262933",
62
57
  highlightBackground: "#2a3967",
@@ -64,24 +59,31 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
64
59
  codeFoldGutterBackground: "#262831",
65
60
  codeFoldBackground: "#262831",
66
61
  emptyLineBackground: "#363946",
67
- gutterColor: "#666c87",
68
- addedGutterColor: "#8c8c8c",
69
- removedGutterColor: "#8c8c8c",
70
- codeFoldContentColor: "#656a8b",
62
+ gutterColor: "#f0f6fc",
63
+ addedGutterColor: "#f0f6fc",
64
+ removedGutterColor: "#f0f6fc",
65
+ codeFoldContentColor: "#9198a1",
71
66
  diffViewerTitleBackground: "#2f323e",
72
- diffViewerTitleColor: "#757a9b",
67
+ diffViewerTitleColor: "#f0f6fc",
73
68
  diffViewerTitleBorderColor: "#353846",
74
69
  }, (overrideVariables.dark || {})),
75
70
  };
76
71
  const variables = useDarkTheme ? themeVariables.dark : themeVariables.light;
77
- const { css, cx } = (0, create_instance_1.default)({ key: "react-diff", nonce });
72
+ const { css, cx } = createEmotion({ key: "react-diff", nonce });
78
73
  const content = css({
79
74
  width: "auto",
75
+ overflow: "hidden",
80
76
  label: "content",
81
77
  });
82
78
  const splitView = css({
83
79
  label: "split-view",
84
80
  });
81
+ const stickyHeader = css({
82
+ position: "sticky",
83
+ top: 0,
84
+ zIndex: 2,
85
+ label: "sticky-header",
86
+ });
85
87
  const summary = css({
86
88
  background: variables.diffViewerTitleBackground,
87
89
  color: variables.diffViewerTitleColor,
@@ -90,13 +92,19 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
90
92
  alignItems: "center",
91
93
  gap: "0.5em",
92
94
  fontFamily: "monospace",
95
+ fontSize: 12,
93
96
  fill: variables.diffViewerTitleColor,
94
97
  });
98
+ const columnHeaders = css({
99
+ display: "flex",
100
+ label: "column-headers",
101
+ });
95
102
  const diffContainer = css({
96
103
  width: "100%",
97
104
  minWidth: "1000px",
98
105
  overflowX: "auto",
99
106
  tableLayout: "fixed",
107
+ fontSize: 12,
100
108
  background: variables.diffViewerBackground,
101
109
  pre: {
102
110
  margin: 0,
@@ -106,6 +114,9 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
106
114
  },
107
115
  label: "diff-container",
108
116
  borderCollapse: "collapse",
117
+ "@media (max-width: 768px)": {
118
+ minWidth: "unset",
119
+ },
109
120
  });
110
121
  const lineContent = css({
111
122
  overflow: "hidden",
@@ -123,6 +134,16 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
123
134
  userSelect: "none",
124
135
  label: "unselectable",
125
136
  });
137
+ const noWrap = css({
138
+ label: "no-wrap",
139
+ pre: {
140
+ whiteSpace: "pre",
141
+ },
142
+ [`.${contentText}`]: {
143
+ whiteSpace: "pre",
144
+ lineBreak: "auto",
145
+ },
146
+ });
126
147
  const allExpandButton = css({
127
148
  background: "transparent",
128
149
  border: "none",
@@ -147,8 +168,13 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
147
168
  overflow: "hidden",
148
169
  width: "50%",
149
170
  borderBottom: `1px solid ${variables.diffViewerTitleBorderColor}`,
171
+ boxSizing: "border-box",
172
+ fontSize: 12,
150
173
  label: "title-block",
151
- ":last-child": {
174
+ ":only-child": {
175
+ width: "100%",
176
+ },
177
+ ":last-child:not(:only-child)": {
152
178
  borderLeft: `1px solid ${variables.diffViewerTitleBorderColor}`,
153
179
  },
154
180
  [`.${contentText}`]: {
@@ -189,20 +215,16 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
189
215
  label: "diff-changed",
190
216
  });
191
217
  const wordDiff = css({
192
- padding: 2,
193
- display: "inline-flex",
194
- borderRadius: 4,
195
- wordBreak: "break-all",
218
+ display: "inline",
219
+ textDecoration: "none",
196
220
  label: "word-diff",
197
221
  });
198
222
  const wordAdded = css({
199
223
  background: variables.wordAddedBackground,
200
- textDecoration: "none",
201
224
  label: "word-added",
202
225
  });
203
226
  const wordRemoved = css({
204
227
  background: variables.wordRemovedBackground,
205
- textDecoration: "none",
206
228
  label: "word-removed",
207
229
  });
208
230
  const codeFoldGutter = css({
@@ -210,6 +232,8 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
210
232
  label: "code-fold-gutter",
211
233
  minWidth: "50px",
212
234
  width: "50px",
235
+ textAlign: "center",
236
+ fill: variables.codeFoldContentColor,
213
237
  });
214
238
  const codeFoldContentContainer = css({
215
239
  padding: "",
@@ -220,6 +244,7 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
220
244
  display: "inline",
221
245
  margin: 0,
222
246
  border: "none",
247
+ fill: variables.codeFoldContentColor,
223
248
  label: "code-fold-expand-button",
224
249
  });
225
250
  const codeFoldContent = css({
@@ -244,12 +269,20 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
244
269
  });
245
270
  const codeFold = css({
246
271
  backgroundColor: variables.codeFoldBackground,
247
- height: 40,
248
- fontSize: 14,
272
+ fontSize: 12,
249
273
  alignItems: "center",
250
274
  userSelect: "none",
251
275
  fontWeight: 700,
276
+ cursor: "pointer",
252
277
  label: "code-fold",
278
+ "&:hover": {
279
+ color: variables.diffViewerColor,
280
+ fill: variables.diffViewerColor,
281
+ "& *": {
282
+ color: variables.diffViewerColor,
283
+ fill: variables.diffViewerColor,
284
+ },
285
+ },
253
286
  a: {
254
287
  textDecoration: "underline !important",
255
288
  cursor: "pointer",
@@ -296,7 +329,8 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
296
329
  padding: "0 10px",
297
330
  whiteSpace: "nowrap",
298
331
  label: "gutter",
299
- textAlign: "right",
332
+ textAlign: "center",
333
+ color: variables.gutterColor,
300
334
  background: variables.gutterBackground,
301
335
  "&:hover": {
302
336
  cursor: "pointer",
@@ -307,6 +341,8 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
307
341
  },
308
342
  pre: {
309
343
  opacity: 0.5,
344
+ textAlign: "center",
345
+ width: "100%",
310
346
  },
311
347
  [`&.${diffAdded}`]: {
312
348
  background: variables.addedGutterBackground,
@@ -354,6 +390,7 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
354
390
  blockDeletion,
355
391
  wordRemoved,
356
392
  noSelect: unselectable,
393
+ noWrap,
357
394
  codeFoldGutter,
358
395
  codeFoldExpandButton,
359
396
  codeFoldContentContainer,
@@ -365,6 +402,8 @@ exports.default = (styleOverride, useDarkTheme = false, nonce = "") => {
365
402
  content,
366
403
  column,
367
404
  codeFoldContent,
405
+ stickyHeader,
406
+ columnHeaders,
368
407
  titleBlock,
369
408
  allExpandButton,
370
409
  };
@@ -1,4 +1,5 @@
1
1
  import * as diff from "diff";
2
+ import * as yaml from "js-yaml";
2
3
  const jsDiff = diff;
3
4
  export var DiffType;
4
5
  (function (DiffType) {
@@ -7,6 +8,249 @@ export var DiffType;
7
8
  DiffType[DiffType["REMOVED"] = 2] = "REMOVED";
8
9
  DiffType[DiffType["CHANGED"] = 3] = "CHANGED";
9
10
  })(DiffType || (DiffType = {}));
11
+ /**
12
+ * Stringify a value using the specified format.
13
+ */
14
+ function stringify(val, format) {
15
+ if (format === 'yaml') {
16
+ return yaml.dump(val, { indent: 2, lineWidth: -1, noRefs: true }).trimEnd();
17
+ }
18
+ return JSON.stringify(val, null, 2);
19
+ }
20
+ /**
21
+ * Performs a fast structural diff on objects.
22
+ *
23
+ * Strategy: Use structural comparison to identify which subtrees changed,
24
+ * then use diffLines only on those changed subtrees. This avoids running
25
+ * the expensive O(ND) Myers diff on the entire content, while still producing
26
+ * proper line-by-line diffs for the parts that changed.
27
+ */
28
+ function structuralDiff(oldObj, newObj, format = 'json') {
29
+ const oldStr = stringify(oldObj, format);
30
+ const newStr = stringify(newObj, format);
31
+ // Fast path: identical objects
32
+ if (oldStr === newStr) {
33
+ return [{ value: oldStr }];
34
+ }
35
+ // Use recursive structural diff that applies diffLines to changed subtrees
36
+ return diffStructurally(oldObj, newObj, 0, format);
37
+ }
38
+ /**
39
+ * JSON diff that preserves key order from each object.
40
+ * Uses structural comparison for performance.
41
+ */
42
+ function structuralJsonDiff(oldObj, newObj) {
43
+ return structuralDiff(oldObj, newObj, 'json');
44
+ }
45
+ /**
46
+ * Optimized diff for JSON strings that preserves original formatting and key order.
47
+ * Uses parsing only to check for structural equality (fast path),
48
+ * then falls back to diffLines on original strings to preserve formatting.
49
+ */
50
+ function structuralJsonStringDiff(oldJson, newJson) {
51
+ try {
52
+ // Parse JSON to check for structural equality
53
+ const oldObj = JSON.parse(oldJson);
54
+ const newObj = JSON.parse(newJson);
55
+ // Fast path: check if structurally identical by comparing serialized forms
56
+ const oldNormalized = JSON.stringify(oldObj);
57
+ const newNormalized = JSON.stringify(newObj);
58
+ if (oldNormalized === newNormalized) {
59
+ // Structurally identical - return original string to preserve formatting
60
+ return [{ value: oldJson }];
61
+ }
62
+ // Files differ - use diffLines on ORIGINAL strings to preserve key order and formatting
63
+ return diff.diffLines(oldJson, newJson, {
64
+ newlineIsToken: false,
65
+ });
66
+ }
67
+ catch (e) {
68
+ // If JSON parsing fails, fall back to line diff
69
+ return diff.diffLines(oldJson, newJson, {
70
+ newlineIsToken: false,
71
+ });
72
+ }
73
+ }
74
+ /**
75
+ * Optimized diff for YAML that preserves original line numbers.
76
+ * Uses parsing only to check for structural equality (fast path),
77
+ * then falls back to diffLines on original strings to preserve line numbers.
78
+ */
79
+ function structuralYamlDiff(oldYaml, newYaml) {
80
+ // Parse YAML to check for structural equality
81
+ const oldObj = yaml.load(oldYaml);
82
+ const newObj = yaml.load(newYaml);
83
+ // Fast path: check if structurally identical by comparing serialized forms
84
+ const oldNormalized = yaml.dump(oldObj, { indent: 2, lineWidth: -1, noRefs: true });
85
+ const newNormalized = yaml.dump(newObj, { indent: 2, lineWidth: -1, noRefs: true });
86
+ if (oldNormalized === newNormalized) {
87
+ // Structurally identical - return original string to preserve formatting
88
+ return [{ value: oldYaml }];
89
+ }
90
+ // Files differ - use diffLines on ORIGINAL strings to preserve line numbers
91
+ return diff.diffLines(oldYaml, newYaml, {
92
+ newlineIsToken: false,
93
+ });
94
+ }
95
+ /**
96
+ * Recursively diff two values structurally.
97
+ * For unchanged parts, output as-is.
98
+ * For changed parts, use diffLines to get proper line-by-line diff.
99
+ */
100
+ function diffStructurally(oldVal, newVal, indent, format = 'json') {
101
+ const oldStr = stringify(oldVal, format);
102
+ const newStr = stringify(newVal, format);
103
+ // Fast path: identical
104
+ if (oldStr === newStr) {
105
+ return [{ value: reindent(oldStr, indent, format) }];
106
+ }
107
+ // Both are objects - compare key by key
108
+ if (typeof oldVal === 'object' && oldVal !== null &&
109
+ typeof newVal === 'object' && newVal !== null &&
110
+ !Array.isArray(oldVal) && !Array.isArray(newVal)) {
111
+ return diffObjects(oldVal, newVal, indent, format);
112
+ }
113
+ // Both are arrays - compare element by element
114
+ if (Array.isArray(oldVal) && Array.isArray(newVal)) {
115
+ return diffArrays(oldVal, newVal, indent, format);
116
+ }
117
+ // Different types or primitives - use diffLines for proper diff
118
+ return diffWithLines(oldStr, newStr, indent, format);
119
+ }
120
+ /**
121
+ * Diff two objects key by key.
122
+ * Iterates in NEW key order to preserve the new file's structure,
123
+ * then appends any old-only keys as removed.
124
+ */
125
+ function diffObjects(oldObj, newObj, indent, format = 'json') {
126
+ const changes = [];
127
+ const indentStr = ' '.repeat(indent);
128
+ const innerIndent = ' '.repeat(indent + 1);
129
+ const oldKeySet = new Set(Object.keys(oldObj));
130
+ const newKeys = Object.keys(newObj);
131
+ // Build ordered key list: new keys in their order, then old-only keys
132
+ const oldOnlyKeys = [...oldKeySet].filter(k => !(k in newObj));
133
+ const allKeys = [...newKeys, ...oldOnlyKeys];
134
+ changes.push({ value: '{\n' });
135
+ for (let i = 0; i < allKeys.length; i++) {
136
+ const key = allKeys[i];
137
+ const isLast = i === allKeys.length - 1;
138
+ const comma = isLast ? '' : ',';
139
+ const inOld = key in oldObj;
140
+ const inNew = key in newObj;
141
+ if (inOld && inNew) {
142
+ // Key in both - recursively diff values
143
+ const oldValStr = stringify(oldObj[key], format);
144
+ const newValStr = stringify(newObj[key], format);
145
+ if (oldValStr === newValStr) {
146
+ // Values identical
147
+ const valueStr = reindent(oldValStr, indent + 1);
148
+ changes.push({ value: innerIndent + JSON.stringify(key) + ': ' + valueStr + comma + '\n' });
149
+ }
150
+ else {
151
+ // Values differ - recursively diff them
152
+ const keyPrefix = innerIndent + JSON.stringify(key) + ': ';
153
+ const valueDiff = diffStructurally(oldObj[key], newObj[key], indent + 1, format);
154
+ // Prepend key to first change so they're on the same line
155
+ if (valueDiff.length > 0) {
156
+ valueDiff[0].value = keyPrefix + valueDiff[0].value;
157
+ }
158
+ // Add comma to last change if needed
159
+ if (comma && valueDiff.length > 0) {
160
+ const last = valueDiff[valueDiff.length - 1];
161
+ last.value = last.value.replace(/\n$/, comma + '\n');
162
+ }
163
+ changes.push(...valueDiff);
164
+ }
165
+ }
166
+ else if (inOld) {
167
+ // Key only in old - removed
168
+ const valueStr = reindent(stringify(oldObj[key], format), indent + 1);
169
+ changes.push({ removed: true, value: innerIndent + JSON.stringify(key) + ': ' + valueStr + comma + '\n' });
170
+ }
171
+ else {
172
+ // Key only in new - added
173
+ const valueStr = reindent(stringify(newObj[key], format), indent + 1);
174
+ changes.push({ added: true, value: innerIndent + JSON.stringify(key) + ': ' + valueStr + comma + '\n' });
175
+ }
176
+ }
177
+ changes.push({ value: indentStr + '}\n' });
178
+ return changes;
179
+ }
180
+ /**
181
+ * Diff two arrays element by element.
182
+ */
183
+ function diffArrays(oldArr, newArr, indent, format = 'json') {
184
+ const changes = [];
185
+ const indentStr = ' '.repeat(indent);
186
+ const innerIndent = ' '.repeat(indent + 1);
187
+ changes.push({ value: '[\n' });
188
+ const maxLen = Math.max(oldArr.length, newArr.length);
189
+ for (let i = 0; i < maxLen; i++) {
190
+ const isLast = i === maxLen - 1;
191
+ const comma = isLast ? '' : ',';
192
+ if (i >= oldArr.length) {
193
+ // Element only in new - added
194
+ const valueStr = reindent(stringify(newArr[i], format), indent + 1);
195
+ changes.push({ added: true, value: innerIndent + valueStr + comma + '\n' });
196
+ }
197
+ else if (i >= newArr.length) {
198
+ // Element only in old - removed
199
+ const valueStr = reindent(stringify(oldArr[i], format), indent + 1);
200
+ changes.push({ removed: true, value: innerIndent + valueStr + comma + '\n' });
201
+ }
202
+ else {
203
+ // Element in both - recursively diff
204
+ const oldElemStr = stringify(oldArr[i], format);
205
+ const newElemStr = stringify(newArr[i], format);
206
+ if (oldElemStr === newElemStr) {
207
+ // Elements identical
208
+ const valueStr = reindent(oldElemStr, indent + 1);
209
+ changes.push({ value: innerIndent + valueStr + comma + '\n' });
210
+ }
211
+ else {
212
+ // Elements differ - recursively diff them
213
+ const elemDiff = diffStructurally(oldArr[i], newArr[i], indent + 1, format);
214
+ // Prepend indent to first change so they're on the same line
215
+ if (elemDiff.length > 0) {
216
+ elemDiff[0].value = innerIndent + elemDiff[0].value;
217
+ }
218
+ // Add comma to last change if needed
219
+ if (comma && elemDiff.length > 0) {
220
+ const last = elemDiff[elemDiff.length - 1];
221
+ last.value = last.value.replace(/\n$/, comma + '\n');
222
+ }
223
+ changes.push(...elemDiff);
224
+ }
225
+ }
226
+ }
227
+ changes.push({ value: indentStr + ']\n' });
228
+ return changes;
229
+ }
230
+ /**
231
+ * Use diffLines for proper line-by-line diff of two strings.
232
+ * This is the fallback for when structural comparison finds different values.
233
+ */
234
+ function diffWithLines(oldStr, newStr, indent, _format = 'json') {
235
+ const oldIndented = reindent(oldStr, indent);
236
+ const newIndented = reindent(newStr, indent);
237
+ // Use diffLines for proper line-level comparison
238
+ const lineDiff = diff.diffLines(oldIndented, newIndented);
239
+ return lineDiff.map(change => ({
240
+ value: change.value,
241
+ added: change.added,
242
+ removed: change.removed
243
+ }));
244
+ }
245
+ /**
246
+ * Re-indent a string to the specified level.
247
+ */
248
+ function reindent(str, indent, _format = 'json') {
249
+ if (indent === 0)
250
+ return str;
251
+ const indentStr = ' '.repeat(indent);
252
+ return str.split('\n').map((line, i) => i === 0 ? line : indentStr + line).join('\n');
253
+ }
10
254
  // See https://github.com/kpdecker/jsdiff/tree/v4.0.1#api for more info on the below JsDiff methods
11
255
  export var DiffMethod;
12
256
  (function (DiffMethod) {
@@ -18,6 +262,7 @@ export var DiffMethod;
18
262
  DiffMethod["SENTENCES"] = "diffSentences";
19
263
  DiffMethod["CSS"] = "diffCss";
20
264
  DiffMethod["JSON"] = "diffJson";
265
+ DiffMethod["YAML"] = "diffYaml";
21
266
  })(DiffMethod || (DiffMethod = {}));
22
267
  /**
23
268
  * Splits diff text by new line and computes final list of diff lines based on
@@ -83,18 +328,37 @@ const computeDiff = (oldValue, newValue, compareMethod = DiffMethod.CHARS) => {
83
328
  * @param linesOffset line number to start counting from
84
329
  * @param showLines lines that are always shown, regardless of diff
85
330
  */
86
- const computeLineInformation = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = []) => {
331
+ const computeLineInformation = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = [], deferWordDiff = false) => {
87
332
  let diffArray = [];
88
- // Use diffLines for strings, and diffJson for objects...
333
+ // Handle different input types and compare methods
89
334
  if (typeof oldString === "string" && typeof newString === "string") {
90
- diffArray = diff.diffLines(oldString, newString, {
91
- newlineIsToken: false,
92
- ignoreWhitespace: false,
93
- ignoreCase: false,
94
- });
335
+ // Check if we should use structural diff for JSON or YAML
336
+ if (lineCompareMethod === DiffMethod.JSON) {
337
+ // Use JSON structural diff - preserves original formatting and key order
338
+ diffArray = structuralJsonStringDiff(oldString, newString);
339
+ }
340
+ else if (lineCompareMethod === DiffMethod.YAML) {
341
+ try {
342
+ // Use YAML structural diff - parses, normalizes, and outputs as YAML
343
+ diffArray = structuralYamlDiff(oldString, newString);
344
+ }
345
+ catch (e) {
346
+ // If YAML parsing fails, fall back to line diff
347
+ diffArray = diff.diffLines(oldString, newString, {
348
+ newlineIsToken: false,
349
+ });
350
+ }
351
+ }
352
+ else {
353
+ diffArray = diff.diffLines(oldString, newString, {
354
+ newlineIsToken: false,
355
+ });
356
+ }
95
357
  }
96
358
  else {
97
- diffArray = diff.diffJson(oldString, newString);
359
+ // Use our fast structural JSON diff instead of diff.diffJson
360
+ // This is O(n) for structure comparison vs O(ND) for Myers on large strings
361
+ diffArray = structuralJsonDiff(oldString, newString);
98
362
  }
99
363
  let rightLineNumber = linesOffset;
100
364
  let leftLineNumber = linesOffset;
@@ -145,7 +409,18 @@ const computeLineInformation = (oldString, newString, disableWordDiff = false, l
145
409
  right.type = type;
146
410
  // Do char level diff and assign the corresponding values to the
147
411
  // left and right diff information object.
148
- if (disableWordDiff) {
412
+ // Skip word diff for very long lines (>500 chars) to avoid performance issues
413
+ const MAX_LINE_LENGTH_FOR_WORD_DIFF = 500;
414
+ const lineIsTooLong = line.length > MAX_LINE_LENGTH_FOR_WORD_DIFF ||
415
+ rightValue.length > MAX_LINE_LENGTH_FOR_WORD_DIFF;
416
+ if (disableWordDiff || lineIsTooLong) {
417
+ right.value = rightValue;
418
+ }
419
+ else if (deferWordDiff) {
420
+ // Store raw values for deferred word diff computation
421
+ left.rawValue = line;
422
+ left.value = line;
423
+ right.rawValue = rightValue;
149
424
  right.value = rightValue;
150
425
  }
151
426
  else {
@@ -202,4 +477,44 @@ const computeLineInformation = (oldString, newString, disableWordDiff = false, l
202
477
  diffLines,
203
478
  };
204
479
  };
205
- export { computeLineInformation };
480
+ /**
481
+ * Computes line diff information using a Web Worker to avoid blocking the UI thread.
482
+ * This offloads the expensive `computeLineInformation` logic to a separate thread.
483
+ *
484
+ * @param oldString Old string to compare.
485
+ * @param newString New string to compare with old string.
486
+ * @param disableWordDiff Flag to enable/disable word diff.
487
+ * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
488
+ * @param linesOffset line number to start counting from
489
+ * @param showLines lines that are always shown, regardless of diff
490
+ * @returns Promise<ComputedLineInformation> - Resolves with line-by-line diff data from the worker.
491
+ */
492
+ const computeLineInformationWorker = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = [], deferWordDiff = false) => {
493
+ const fallback = () => computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff);
494
+ // Fall back to synchronous computation if Worker is not available (e.g., in Node.js/test environments)
495
+ if (typeof Worker === 'undefined') {
496
+ return Promise.resolve(fallback());
497
+ }
498
+ return new Promise((resolve) => {
499
+ let worker;
500
+ try {
501
+ worker = new Worker(new URL('./computeWorker.js', import.meta.url), { type: 'module' });
502
+ }
503
+ catch {
504
+ // Worker instantiation failed - fall back to synchronous computation
505
+ resolve(fallback());
506
+ return;
507
+ }
508
+ worker.onmessage = (e) => {
509
+ resolve(e.data);
510
+ worker.terminate();
511
+ };
512
+ worker.onerror = () => {
513
+ // Worker error - fall back to synchronous computation
514
+ worker.terminate();
515
+ resolve(fallback());
516
+ };
517
+ worker.postMessage({ oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff });
518
+ });
519
+ };
520
+ export { computeLineInformation, computeLineInformationWorker, computeDiff };
@@ -0,0 +1,10 @@
1
+ import { computeLineInformation } from "./compute-lines";
2
+ /**
3
+ * This sets up a message handler inside the Web Worker.
4
+ * When the main thread sends a message to this worker (via postMessage), this function is triggered.
5
+ */
6
+ self.onmessage = (e) => {
7
+ const { oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff } = e.data;
8
+ const result = computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff);
9
+ self.postMessage(result);
10
+ };