@bochenw/react-diff-viewer-continued 4.3.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +521 -0
  3. package/lib/cjs/src/comment-row.d.ts +33 -0
  4. package/lib/cjs/src/comment-row.js +58 -0
  5. package/lib/cjs/src/compute-hidden-blocks.d.ts +13 -0
  6. package/lib/cjs/src/compute-hidden-blocks.js +36 -0
  7. package/lib/cjs/src/compute-lines.d.ts +68 -0
  8. package/lib/cjs/src/compute-lines.js +559 -0
  9. package/lib/cjs/src/computeWorker.d.ts +1 -0
  10. package/lib/cjs/src/computeWorker.js +10 -0
  11. package/lib/cjs/src/diff-row.d.ts +40 -0
  12. package/lib/cjs/src/diff-row.js +136 -0
  13. package/lib/cjs/src/expand.d.ts +1 -0
  14. package/lib/cjs/src/expand.js +4 -0
  15. package/lib/cjs/src/fold.d.ts +1 -0
  16. package/lib/cjs/src/fold.js +4 -0
  17. package/lib/cjs/src/index.d.ts +236 -0
  18. package/lib/cjs/src/index.js +783 -0
  19. package/lib/cjs/src/line-number-prefix.d.ts +4 -0
  20. package/lib/cjs/src/line-number-prefix.js +5 -0
  21. package/lib/cjs/src/render-word-diff.d.ts +22 -0
  22. package/lib/cjs/src/render-word-diff.js +212 -0
  23. package/lib/cjs/src/skipped-line-indicator.d.ts +29 -0
  24. package/lib/cjs/src/skipped-line-indicator.js +29 -0
  25. package/lib/cjs/src/styles.d.ts +102 -0
  26. package/lib/cjs/src/styles.js +430 -0
  27. package/lib/cjs/src/workerBundle.d.ts +5 -0
  28. package/lib/cjs/src/workerBundle.js +7 -0
  29. package/lib/esm/src/comment-row.js +58 -0
  30. package/lib/esm/src/compute-hidden-blocks.js +36 -0
  31. package/lib/esm/src/compute-lines.js +559 -0
  32. package/lib/esm/src/computeWorker.js +10 -0
  33. package/lib/esm/src/diff-row.js +136 -0
  34. package/lib/esm/src/expand.js +4 -0
  35. package/lib/esm/src/fold.js +4 -0
  36. package/lib/esm/src/index.js +780 -0
  37. package/lib/esm/src/line-number-prefix.js +5 -0
  38. package/lib/esm/src/render-word-diff.js +211 -0
  39. package/lib/esm/src/skipped-line-indicator.js +29 -0
  40. package/lib/esm/src/styles.js +431 -0
  41. package/lib/esm/src/workerBundle.js +7 -0
  42. package/package.json +90 -0
@@ -0,0 +1,36 @@
1
+ export function computeHiddenBlocks(lineInformation, diffLines, extraLines) {
2
+ let newBlockIndex = 0;
3
+ let currentBlock;
4
+ const lineBlocks = {};
5
+ const blocks = [];
6
+ lineInformation.forEach((line, lineIndex) => {
7
+ const isDiffLine = diffLines.some((diffLine) => diffLine >= lineIndex - extraLines &&
8
+ diffLine <= lineIndex + extraLines);
9
+ if (!isDiffLine && currentBlock === undefined) {
10
+ // block begins
11
+ currentBlock = {
12
+ index: newBlockIndex,
13
+ startLine: lineIndex,
14
+ endLine: lineIndex,
15
+ lines: 1,
16
+ };
17
+ blocks.push(currentBlock);
18
+ lineBlocks[lineIndex] = currentBlock.index;
19
+ newBlockIndex++;
20
+ }
21
+ else if (!isDiffLine && currentBlock) {
22
+ // block continues
23
+ currentBlock.endLine = lineIndex;
24
+ currentBlock.lines++;
25
+ lineBlocks[lineIndex] = currentBlock.index;
26
+ }
27
+ else {
28
+ // not a block anymore
29
+ currentBlock = undefined;
30
+ }
31
+ });
32
+ return {
33
+ lineBlocks,
34
+ blocks: blocks,
35
+ };
36
+ }
@@ -0,0 +1,68 @@
1
+ import * as diff from "diff";
2
+ export declare enum DiffType {
3
+ DEFAULT = 0,
4
+ ADDED = 1,
5
+ REMOVED = 2,
6
+ CHANGED = 3
7
+ }
8
+ export declare enum DiffMethod {
9
+ CHARS = "diffChars",
10
+ WORDS = "diffWords",
11
+ WORDS_WITH_SPACE = "diffWordsWithSpace",
12
+ LINES = "diffLines",
13
+ TRIMMED_LINES = "diffTrimmedLines",
14
+ SENTENCES = "diffSentences",
15
+ CSS = "diffCss",
16
+ JSON = "diffJson",
17
+ YAML = "diffYaml"
18
+ }
19
+ export interface DiffInformation {
20
+ value?: string | DiffInformation[];
21
+ lineNumber?: number;
22
+ type?: DiffType;
23
+ rawValue?: string;
24
+ }
25
+ export interface LineInformation {
26
+ left?: DiffInformation;
27
+ right?: DiffInformation;
28
+ }
29
+ export interface ComputedLineInformation {
30
+ lineInformation: LineInformation[];
31
+ diffLines: number[];
32
+ }
33
+ export interface ComputedDiffInformation {
34
+ left?: DiffInformation[];
35
+ right?: DiffInformation[];
36
+ }
37
+ export interface JsDiffChangeObject {
38
+ added?: boolean;
39
+ removed?: boolean;
40
+ value?: string;
41
+ }
42
+ /**
43
+ * Computes word diff information in the line.
44
+ * [TODO]: Consider adding options argument for JsDiff text block comparison
45
+ *
46
+ * @param oldValue Old word in the line.
47
+ * @param newValue New word in the line.
48
+ * @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
49
+ */
50
+ declare const computeDiff: (oldValue: string | Record<string, unknown>, newValue: string | Record<string, unknown>, compareMethod?: DiffMethod | ((oldStr: string, newStr: string) => diff.Change[])) => ComputedDiffInformation;
51
+ /**
52
+ * [TODO]: Think about moving common left and right value assignment to a
53
+ * common place. Better readability?
54
+ *
55
+ * Computes line wise information based in the js diff information passed. Each
56
+ * line contains information about left and right section. Left side denotes
57
+ * deletion and right side denotes addition.
58
+ *
59
+ * @param oldString Old string to compare.
60
+ * @param newString New string to compare with old string.
61
+ * @param disableWordDiff Flag to enable/disable word diff.
62
+ * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
63
+ * @param linesOffset line number to start counting from
64
+ * @param showLines lines that are always shown, regardless of diff
65
+ */
66
+ declare const computeLineInformation: (oldString: string | Record<string, unknown>, newString: string | Record<string, unknown>, disableWordDiff?: boolean, lineCompareMethod?: DiffMethod | ((oldStr: string, newStr: string) => diff.Change[]), linesOffset?: number, showLines?: string[], deferWordDiff?: boolean) => ComputedLineInformation;
67
+ declare const computeLineInformationWorker: (oldString: string | Record<string, unknown>, newString: string | Record<string, unknown>, disableWordDiff?: boolean, lineCompareMethod?: DiffMethod | ((oldStr: string, newStr: string) => diff.Change[]), linesOffset?: number, showLines?: string[], deferWordDiff?: boolean) => Promise<ComputedLineInformation>;
68
+ export { computeLineInformation, computeLineInformationWorker, computeDiff };
@@ -0,0 +1,559 @@
1
+ import * as diff from "diff";
2
+ import * as yaml from "js-yaml";
3
+ const jsDiff = diff;
4
+ export var DiffType;
5
+ (function (DiffType) {
6
+ DiffType[DiffType["DEFAULT"] = 0] = "DEFAULT";
7
+ DiffType[DiffType["ADDED"] = 1] = "ADDED";
8
+ DiffType[DiffType["REMOVED"] = 2] = "REMOVED";
9
+ DiffType[DiffType["CHANGED"] = 3] = "CHANGED";
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 the appropriate changes
155
+ if (valueDiff.length > 0) {
156
+ if (!valueDiff[0].removed && !valueDiff[0].added) {
157
+ // First change is neutral (e.g., opening brace of nested object) - prepend key to it only
158
+ valueDiff[0].value = keyPrefix + valueDiff[0].value;
159
+ }
160
+ else {
161
+ // First change is removed or added - this is a primitive value change
162
+ // Both the removed (old) and added (new) lines need the key
163
+ const firstRemoved = valueDiff.find(c => c.removed);
164
+ const firstAdded = valueDiff.find(c => c.added);
165
+ if (firstRemoved)
166
+ firstRemoved.value = keyPrefix + firstRemoved.value;
167
+ if (firstAdded)
168
+ firstAdded.value = keyPrefix + firstAdded.value;
169
+ }
170
+ }
171
+ // Add comma to last change if needed
172
+ if (comma && valueDiff.length > 0) {
173
+ const last = valueDiff[valueDiff.length - 1];
174
+ last.value = last.value.replace(/\n$/, comma + '\n');
175
+ }
176
+ changes.push(...valueDiff);
177
+ }
178
+ }
179
+ else if (inOld) {
180
+ // Key only in old - removed
181
+ const valueStr = reindent(stringify(oldObj[key], format), indent + 1);
182
+ changes.push({ removed: true, value: innerIndent + JSON.stringify(key) + ': ' + valueStr + comma + '\n' });
183
+ }
184
+ else {
185
+ // Key only in new - added
186
+ const valueStr = reindent(stringify(newObj[key], format), indent + 1);
187
+ changes.push({ added: true, value: innerIndent + JSON.stringify(key) + ': ' + valueStr + comma + '\n' });
188
+ }
189
+ }
190
+ changes.push({ value: indentStr + '}\n' });
191
+ return changes;
192
+ }
193
+ /**
194
+ * Diff two arrays element by element.
195
+ */
196
+ function diffArrays(oldArr, newArr, indent, format = 'json') {
197
+ const changes = [];
198
+ const indentStr = ' '.repeat(indent);
199
+ const innerIndent = ' '.repeat(indent + 1);
200
+ changes.push({ value: '[\n' });
201
+ const maxLen = Math.max(oldArr.length, newArr.length);
202
+ for (let i = 0; i < maxLen; i++) {
203
+ const isLast = i === maxLen - 1;
204
+ const comma = isLast ? '' : ',';
205
+ if (i >= oldArr.length) {
206
+ // Element only in new - added
207
+ const valueStr = reindent(stringify(newArr[i], format), indent + 1);
208
+ changes.push({ added: true, value: innerIndent + valueStr + comma + '\n' });
209
+ }
210
+ else if (i >= newArr.length) {
211
+ // Element only in old - removed
212
+ const valueStr = reindent(stringify(oldArr[i], format), indent + 1);
213
+ changes.push({ removed: true, value: innerIndent + valueStr + comma + '\n' });
214
+ }
215
+ else {
216
+ // Element in both - recursively diff
217
+ const oldElemStr = stringify(oldArr[i], format);
218
+ const newElemStr = stringify(newArr[i], format);
219
+ if (oldElemStr === newElemStr) {
220
+ // Elements identical
221
+ const valueStr = reindent(oldElemStr, indent + 1);
222
+ changes.push({ value: innerIndent + valueStr + comma + '\n' });
223
+ }
224
+ else {
225
+ // Elements differ - recursively diff them
226
+ const elemDiff = diffStructurally(oldArr[i], newArr[i], indent + 1, format);
227
+ // Prepend indent to first change so they're on the same line
228
+ if (elemDiff.length > 0) {
229
+ elemDiff[0].value = innerIndent + elemDiff[0].value;
230
+ }
231
+ // Add comma to last change if needed
232
+ if (comma && elemDiff.length > 0) {
233
+ const last = elemDiff[elemDiff.length - 1];
234
+ last.value = last.value.replace(/\n$/, comma + '\n');
235
+ }
236
+ changes.push(...elemDiff);
237
+ }
238
+ }
239
+ }
240
+ changes.push({ value: indentStr + ']\n' });
241
+ return changes;
242
+ }
243
+ /**
244
+ * Use diffLines for proper line-by-line diff of two strings.
245
+ * This is the fallback for when structural comparison finds different values.
246
+ */
247
+ function diffWithLines(oldStr, newStr, indent, _format = 'json') {
248
+ const oldIndented = reindent(oldStr, indent);
249
+ const newIndented = reindent(newStr, indent);
250
+ // Use diffLines for proper line-level comparison
251
+ const lineDiff = diff.diffLines(oldIndented, newIndented);
252
+ return lineDiff.map(change => ({
253
+ value: change.value,
254
+ added: change.added,
255
+ removed: change.removed
256
+ }));
257
+ }
258
+ /**
259
+ * Re-indent a string to the specified level.
260
+ */
261
+ function reindent(str, indent, _format = 'json') {
262
+ if (indent === 0)
263
+ return str;
264
+ const indentStr = ' '.repeat(indent);
265
+ return str.split('\n').map((line, i) => i === 0 ? line : indentStr + line).join('\n');
266
+ }
267
+ // See https://github.com/kpdecker/jsdiff/tree/v4.0.1#api for more info on the below JsDiff methods
268
+ export var DiffMethod;
269
+ (function (DiffMethod) {
270
+ DiffMethod["CHARS"] = "diffChars";
271
+ DiffMethod["WORDS"] = "diffWords";
272
+ DiffMethod["WORDS_WITH_SPACE"] = "diffWordsWithSpace";
273
+ DiffMethod["LINES"] = "diffLines";
274
+ DiffMethod["TRIMMED_LINES"] = "diffTrimmedLines";
275
+ DiffMethod["SENTENCES"] = "diffSentences";
276
+ DiffMethod["CSS"] = "diffCss";
277
+ DiffMethod["JSON"] = "diffJson";
278
+ DiffMethod["YAML"] = "diffYaml";
279
+ })(DiffMethod || (DiffMethod = {}));
280
+ /**
281
+ * Splits diff text by new line and computes final list of diff lines based on
282
+ * conditions.
283
+ *
284
+ * @param value Diff text from the js diff module.
285
+ */
286
+ const constructLines = (value) => {
287
+ if (value === "")
288
+ return [];
289
+ const lines = value.replace(/\n$/, "").split("\n");
290
+ return lines;
291
+ };
292
+ /**
293
+ * Computes word diff information in the line.
294
+ * [TODO]: Consider adding options argument for JsDiff text block comparison
295
+ *
296
+ * @param oldValue Old word in the line.
297
+ * @param newValue New word in the line.
298
+ * @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
299
+ */
300
+ const computeDiff = (oldValue, newValue, compareMethod = DiffMethod.CHARS) => {
301
+ const compareFunc = typeof compareMethod === "string" ? jsDiff[compareMethod] : compareMethod;
302
+ const diffArray = compareFunc(oldValue, newValue);
303
+ const computedDiff = {
304
+ left: [],
305
+ right: [],
306
+ };
307
+ diffArray.forEach(({ added, removed, value }) => {
308
+ const diffInformation = {};
309
+ if (added) {
310
+ diffInformation.type = DiffType.ADDED;
311
+ diffInformation.value = value;
312
+ computedDiff.right.push(diffInformation);
313
+ }
314
+ if (removed) {
315
+ diffInformation.type = DiffType.REMOVED;
316
+ diffInformation.value = value;
317
+ computedDiff.left.push(diffInformation);
318
+ }
319
+ if (!removed && !added) {
320
+ diffInformation.type = DiffType.DEFAULT;
321
+ diffInformation.value = value;
322
+ computedDiff.right.push(diffInformation);
323
+ computedDiff.left.push(diffInformation);
324
+ }
325
+ return diffInformation;
326
+ });
327
+ return computedDiff;
328
+ };
329
+ /**
330
+ * [TODO]: Think about moving common left and right value assignment to a
331
+ * common place. Better readability?
332
+ *
333
+ * Computes line wise information based in the js diff information passed. Each
334
+ * line contains information about left and right section. Left side denotes
335
+ * deletion and right side denotes addition.
336
+ *
337
+ * @param oldString Old string to compare.
338
+ * @param newString New string to compare with old string.
339
+ * @param disableWordDiff Flag to enable/disable word diff.
340
+ * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
341
+ * @param linesOffset line number to start counting from
342
+ * @param showLines lines that are always shown, regardless of diff
343
+ */
344
+ const computeLineInformation = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = [], deferWordDiff = false) => {
345
+ let diffArray = [];
346
+ // Handle different input types and compare methods
347
+ if (typeof oldString === "string" && typeof newString === "string") {
348
+ // Check if we should use structural diff for JSON or YAML
349
+ if (lineCompareMethod === DiffMethod.JSON) {
350
+ // Use JSON structural diff - preserves original formatting and key order
351
+ diffArray = structuralJsonStringDiff(oldString, newString);
352
+ }
353
+ else if (lineCompareMethod === DiffMethod.YAML) {
354
+ try {
355
+ // Use YAML structural diff - parses, normalizes, and outputs as YAML
356
+ diffArray = structuralYamlDiff(oldString, newString);
357
+ }
358
+ catch (e) {
359
+ // If YAML parsing fails, fall back to line diff
360
+ diffArray = diff.diffLines(oldString, newString, {
361
+ newlineIsToken: false,
362
+ });
363
+ }
364
+ }
365
+ else {
366
+ diffArray = diff.diffLines(oldString, newString, {
367
+ newlineIsToken: false,
368
+ });
369
+ }
370
+ }
371
+ else {
372
+ // Use our fast structural JSON diff instead of diff.diffJson
373
+ // This is O(n) for structure comparison vs O(ND) for Myers on large strings
374
+ diffArray = structuralJsonDiff(oldString, newString);
375
+ }
376
+ let rightLineNumber = linesOffset;
377
+ let leftLineNumber = linesOffset;
378
+ let lineInformation = [];
379
+ let counter = 0;
380
+ const diffLines = [];
381
+ const ignoreDiffIndexes = [];
382
+ const getLineInformation = (value, diffIndex, added, removed, evaluateOnlyFirstLine) => {
383
+ const lines = constructLines(value);
384
+ return lines
385
+ .map((line, lineIndex) => {
386
+ const left = {};
387
+ const right = {};
388
+ if (ignoreDiffIndexes.includes(`${diffIndex}-${lineIndex}`) ||
389
+ (evaluateOnlyFirstLine && lineIndex !== 0)) {
390
+ return undefined;
391
+ }
392
+ if (added || removed) {
393
+ let countAsChange = true;
394
+ if (removed) {
395
+ leftLineNumber += 1;
396
+ left.lineNumber = leftLineNumber;
397
+ left.type = DiffType.REMOVED;
398
+ left.value = line || " ";
399
+ // When the current line is of type REMOVED, check the next item in
400
+ // the diff array whether it is of type ADDED. If true, the current
401
+ // diff will be marked as both REMOVED and ADDED. Meaning, the
402
+ // current line is a modification.
403
+ const nextDiff = diffArray[diffIndex + 1];
404
+ if (nextDiff === null || nextDiff === void 0 ? void 0 : nextDiff.added) {
405
+ const nextDiffLines = constructLines(nextDiff.value)[lineIndex];
406
+ if (nextDiffLines) {
407
+ const nextDiffLineInfo = getLineInformation(nextDiffLines, diffIndex, true, false, true);
408
+ const { value: rightValue, lineNumber, type, } = nextDiffLineInfo[0].right;
409
+ // When identified as modification, push the next diff to ignore
410
+ // list as the next value will be added in this line computation as
411
+ // right and left values.
412
+ ignoreDiffIndexes.push(`${diffIndex + 1}-${lineIndex}`);
413
+ right.lineNumber = lineNumber;
414
+ if (left.value === rightValue) {
415
+ // The new value is exactly the same as the old
416
+ countAsChange = false;
417
+ right.type = 0;
418
+ left.type = 0;
419
+ right.value = rightValue;
420
+ }
421
+ else {
422
+ right.type = type;
423
+ // Do char level diff and assign the corresponding values to the
424
+ // left and right diff information object.
425
+ // Skip word diff for very long lines (>500 chars) to avoid performance issues
426
+ const MAX_LINE_LENGTH_FOR_WORD_DIFF = 500;
427
+ const lineIsTooLong = line.length > MAX_LINE_LENGTH_FOR_WORD_DIFF ||
428
+ rightValue.length > MAX_LINE_LENGTH_FOR_WORD_DIFF;
429
+ if (disableWordDiff || lineIsTooLong) {
430
+ right.value = rightValue;
431
+ }
432
+ else if (deferWordDiff) {
433
+ // Store raw values for deferred word diff computation
434
+ left.rawValue = line;
435
+ left.value = line;
436
+ right.rawValue = rightValue;
437
+ right.value = rightValue;
438
+ }
439
+ else {
440
+ const computedDiff = computeDiff(line, rightValue, lineCompareMethod);
441
+ right.value = computedDiff.right;
442
+ left.value = computedDiff.left;
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+ else {
449
+ rightLineNumber += 1;
450
+ right.lineNumber = rightLineNumber;
451
+ right.type = DiffType.ADDED;
452
+ right.value = line;
453
+ }
454
+ if (countAsChange && !evaluateOnlyFirstLine) {
455
+ if (!diffLines.includes(counter)) {
456
+ diffLines.push(counter);
457
+ }
458
+ }
459
+ }
460
+ else {
461
+ leftLineNumber += 1;
462
+ rightLineNumber += 1;
463
+ left.lineNumber = leftLineNumber;
464
+ left.type = DiffType.DEFAULT;
465
+ left.value = line;
466
+ right.lineNumber = rightLineNumber;
467
+ right.type = DiffType.DEFAULT;
468
+ right.value = line;
469
+ }
470
+ if ((showLines === null || showLines === void 0 ? void 0 : showLines.includes(`L-${left.lineNumber}`)) ||
471
+ ((showLines === null || showLines === void 0 ? void 0 : showLines.includes(`R-${right.lineNumber}`)) &&
472
+ !diffLines.includes(counter))) {
473
+ diffLines.push(counter);
474
+ }
475
+ if (!evaluateOnlyFirstLine) {
476
+ counter += 1;
477
+ }
478
+ return { right, left };
479
+ })
480
+ .filter(Boolean);
481
+ };
482
+ diffArray.forEach(({ added, removed, value }, index) => {
483
+ lineInformation = [
484
+ ...lineInformation,
485
+ ...getLineInformation(value, index, added, removed),
486
+ ];
487
+ });
488
+ return {
489
+ lineInformation,
490
+ diffLines,
491
+ };
492
+ };
493
+ /**
494
+ * Computes line diff information using a Web Worker to avoid blocking the UI thread.
495
+ * This offloads the expensive `computeLineInformation` logic to a separate thread.
496
+ *
497
+ * @param oldString Old string to compare.
498
+ * @param newString New string to compare with old string.
499
+ * @param disableWordDiff Flag to enable/disable word diff.
500
+ * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
501
+ * @param linesOffset line number to start counting from
502
+ * @param showLines lines that are always shown, regardless of diff
503
+ * @returns Promise<ComputedLineInformation> - Resolves with line-by-line diff data from the worker.
504
+ */
505
+ // Import the bundled worker code (generated by scripts/build-worker.js)
506
+ import { WORKER_CODE } from './workerBundle.js';
507
+ // Cached Blob URL for the worker - created once and reused
508
+ let workerBlobUrl = null;
509
+ let workerAvailable = null;
510
+ // Create a Blob URL from the bundled worker code
511
+ const getWorkerBlobUrl = () => {
512
+ if (workerBlobUrl !== null)
513
+ return workerBlobUrl;
514
+ if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined') {
515
+ workerAvailable = false;
516
+ return null;
517
+ }
518
+ try {
519
+ const blob = new Blob([WORKER_CODE], { type: 'application/javascript' });
520
+ workerBlobUrl = URL.createObjectURL(blob);
521
+ workerAvailable = true;
522
+ }
523
+ catch (_a) {
524
+ workerAvailable = false;
525
+ workerBlobUrl = null;
526
+ }
527
+ return workerBlobUrl;
528
+ };
529
+ const computeLineInformationWorker = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = [], deferWordDiff = false) => {
530
+ const fallback = () => computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff);
531
+ const blobUrl = getWorkerBlobUrl();
532
+ if (!blobUrl) {
533
+ return Promise.resolve(fallback());
534
+ }
535
+ return new Promise((resolve) => {
536
+ let worker;
537
+ try {
538
+ worker = new Worker(blobUrl);
539
+ }
540
+ catch (_a) {
541
+ // Worker instantiation failed - fall back to synchronous computation
542
+ workerAvailable = false;
543
+ resolve(fallback());
544
+ return;
545
+ }
546
+ worker.onmessage = (e) => {
547
+ resolve(e.data);
548
+ worker.terminate();
549
+ };
550
+ worker.onerror = () => {
551
+ // Worker error - fall back and mark as unavailable for future calls
552
+ workerAvailable = false;
553
+ worker.terminate();
554
+ resolve(fallback());
555
+ };
556
+ worker.postMessage({ oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff });
557
+ });
558
+ };
559
+ export { computeLineInformation, computeLineInformationWorker, computeDiff };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { computeLineInformation } from "./compute-lines.js";
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
+ };