Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
diff.js1742 linesDownload Raw Back to dist
1(function (global, factory) {
2    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3    typeof define === 'function' && define.amd ? define(['exports'], factory) :
4    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Diff = {}));
5})(this, (function (exports) { 'use strict';
6
7    class Diff {
8        diff(oldStr, newStr,
9        // Type below is not accurate/complete - see above for full possibilities - but it compiles
10        options = {}) {
11            let callback;
12            if (typeof options === 'function') {
13                callback = options;
14                options = {};
15            }
16            else if ('callback' in options) {
17                callback = options.callback;
18            }
19            // Allow subclasses to massage the input prior to running
20            const oldString = this.castInput(oldStr, options);
21            const newString = this.castInput(newStr, options);
22            const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
23            const newTokens = this.removeEmpty(this.tokenize(newString, options));
24            return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
25        }
26        diffWithOptionsObj(oldTokens, newTokens, options, callback) {
27            var _a;
28            const done = (value) => {
29                value = this.postProcess(value, options);
30                if (callback) {
31                    setTimeout(function () { callback(value); }, 0);
32                    return undefined;
33                }
34                else {
35                    return value;
36                }
37            };
38            const newLen = newTokens.length, oldLen = oldTokens.length;
39            let editLength = 1;
40            let maxEditLength = newLen + oldLen;
41            if (options.maxEditLength != null) {
42                maxEditLength = Math.min(maxEditLength, options.maxEditLength);
43            }
44            const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
45            const abortAfterTimestamp = Date.now() + maxExecutionTime;
46            const bestPath = [{ oldPos: -1, lastComponent: undefined }];
47            // Seed editLength = 0, i.e. the content starts with the same values
48            let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
49            if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
50                // Identity per the equality and tokenizer
51                return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
52            }
53            // Once we hit the right edge of the edit graph on some diagonal k, we can
54            // definitely reach the end of the edit graph in no more than k edits, so
55            // there's no point in considering any moves to diagonal k+1 any more (from
56            // which we're guaranteed to need at least k+1 more edits).
57            // Similarly, once we've reached the bottom of the edit graph, there's no
58            // point considering moves to lower diagonals.
59            // We record this fact by setting minDiagonalToConsider and
60            // maxDiagonalToConsider to some finite value once we've hit the edge of
61            // the edit graph.
62            // This optimization is not faithful to the original algorithm presented in
63            // Myers's paper, which instead pointlessly extends D-paths off the end of
64            // the edit graph - see page 7 of Myers's paper which notes this point
65            // explicitly and illustrates it with a diagram. This has major performance
66            // implications for some common scenarios. For instance, to compute a diff
67            // where the new text simply appends d characters on the end of the
68            // original text of length n, the true Myers algorithm will take O(n+d^2)
69            // time while this optimization needs only O(n+d) time.
70            let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
71            // Main worker method. checks all permutations of a given edit length for acceptance.
72            const execEditLength = () => {
73                for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
74                    let basePath;
75                    const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
76                    if (removePath) {
77                        // No one else is going to attempt to use this value, clear it
78                        // @ts-expect-error - perf optimisation. This type-violating value will never be read.
79                        bestPath[diagonalPath - 1] = undefined;
80                    }
81                    let canAdd = false;
82                    if (addPath) {
83                        // what newPos will be after we do an insertion:
84                        const addPathNewPos = addPath.oldPos - diagonalPath;
85                        canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
86                    }
87                    const canRemove = removePath && removePath.oldPos + 1 < oldLen;
88                    if (!canAdd && !canRemove) {
89                        // If this path is a terminal then prune
90                        // @ts-expect-error - perf optimisation. This type-violating value will never be read.
91                        bestPath[diagonalPath] = undefined;
92                        continue;
93                    }
94                    // Select the diagonal that we want to branch from. We select the prior
95                    // path whose position in the old string is the farthest from the origin
96                    // and does not pass the bounds of the diff graph
97                    if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {
98                        basePath = this.addToPath(addPath, true, false, 0, options);
99                    }
100                    else {
101                        basePath = this.addToPath(removePath, false, true, 1, options);
102                    }
103                    newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
104                    if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
105                        // If we have hit the end of both strings, then we are done
106                        return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
107                    }
108                    else {
109                        bestPath[diagonalPath] = basePath;
110                        if (basePath.oldPos + 1 >= oldLen) {
111                            maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
112                        }
113                        if (newPos + 1 >= newLen) {
114                            minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
115                        }
116                    }
117                }
118                editLength++;
119            };
120            // Performs the length of edit iteration. Is a bit fugly as this has to support the
121            // sync and async mode which is never fun. Loops over execEditLength until a value
122            // is produced, or until the edit length exceeds options.maxEditLength (if given),
123            // in which case it will return undefined.
124            if (callback) {
125                (function exec() {
126                    setTimeout(function () {
127                        if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
128                            return callback(undefined);
129                        }
130                        if (!execEditLength()) {
131                            exec();
132                        }
133                    }, 0);
134                }());
135            }
136            else {
137                while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
138                    const ret = execEditLength();
139                    if (ret) {
140                        return ret;
141                    }
142                }
143            }
144        }
145        addToPath(path, added, removed, oldPosInc, options) {
146            const last = path.lastComponent;
147            if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
148                return {
149                    oldPos: path.oldPos + oldPosInc,
150                    lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }
151                };
152            }
153            else {
154                return {
155                    oldPos: path.oldPos + oldPosInc,
156                    lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }
157                };
158            }
159        }
160        extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
161            const newLen = newTokens.length, oldLen = oldTokens.length;
162            let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
163            while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
164                newPos++;
165                oldPos++;
166                commonCount++;
167                if (options.oneChangePerToken) {
168                    basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
169                }
170            }
171            if (commonCount && !options.oneChangePerToken) {
172                basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
173            }
174            basePath.oldPos = oldPos;
175            return newPos;
176        }
177        equals(left, right, options) {
178            if (options.comparator) {
179                return options.comparator(left, right);
180            }
181            else {
182                return left === right
183                    || (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());
184            }
185        }
186        removeEmpty(array) {
187            const ret = [];
188            for (let i = 0; i < array.length; i++) {
189                if (array[i]) {
190                    ret.push(array[i]);
191                }
192            }
193            return ret;
194        }
195        // eslint-disable-next-line @typescript-eslint/no-unused-vars
196        castInput(value, options) {
197            return value;
198        }
199        // eslint-disable-next-line @typescript-eslint/no-unused-vars
200        tokenize(value, options) {
201            return Array.from(value);
202        }
203        join(chars) {
204            // Assumes ValueT is string, which is the case for most subclasses.
205            // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)
206            // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF
207            // assume tokens and values are strings, but not completely - is weird and janky.
208            return chars.join('');
209        }
210        postProcess(changeObjects,
211        // eslint-disable-next-line @typescript-eslint/no-unused-vars
212        options) {
213            return changeObjects;
214        }
215        get useLongestToken() {
216            return false;
217        }
218        buildValues(lastComponent, newTokens, oldTokens) {
219            // First we convert our linked list of components in reverse order to an
220            // array in the right order:
221            const components = [];
222            let nextComponent;
223            while (lastComponent) {
224                components.push(lastComponent);
225                nextComponent = lastComponent.previousComponent;
226                delete lastComponent.previousComponent;
227                lastComponent = nextComponent;
228            }
229            components.reverse();
230            const componentLen = components.length;
231            let componentPos = 0, newPos = 0, oldPos = 0;
232            for (; componentPos < componentLen; componentPos++) {
233                const component = components[componentPos];
234                if (!component.removed) {
235                    if (!component.added && this.useLongestToken) {
236                        let value = newTokens.slice(newPos, newPos + component.count);
237                        value = value.map(function (value, i) {
238                            const oldValue = oldTokens[oldPos + i];
239                            return oldValue.length > value.length ? oldValue : value;
240                        });
241                        component.value = this.join(value);
242                    }
243                    else {
244                        component.value = this.join(newTokens.slice(newPos, newPos + component.count));
245                    }
246                    newPos += component.count;
247                    // Common case
248                    if (!component.added) {
249                        oldPos += component.count;
250                    }
251                }
252                else {
253                    component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
254                    oldPos += component.count;
255                }
256            }
257            return components;
258        }
259    }
260
261    class CharacterDiff extends Diff {
262    }
263    const characterDiff = new CharacterDiff();
264    function diffChars(oldStr, newStr, options) {
265        return characterDiff.diff(oldStr, newStr, options);
266    }
267
268    function longestCommonPrefix(str1, str2) {
269        let i;
270        for (i = 0; i < str1.length && i < str2.length; i++) {
271            if (str1[i] != str2[i]) {
272                return str1.slice(0, i);
273            }
274        }
275        return str1.slice(0, i);
276    }
277    function longestCommonSuffix(str1, str2) {
278        let i;
279        // Unlike longestCommonPrefix, we need a special case to handle all scenarios
280        // where we return the empty string since str1.slice(-0) will return the
281        // entire string.
282        if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
283            return '';
284        }
285        for (i = 0; i < str1.length && i < str2.length; i++) {
286            if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
287                return str1.slice(-i);
288            }
289        }
290        return str1.slice(-i);
291    }
292    function replacePrefix(string, oldPrefix, newPrefix) {
293        if (string.slice(0, oldPrefix.length) != oldPrefix) {
294            throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
295        }
296        return newPrefix + string.slice(oldPrefix.length);
297    }
298    function replaceSuffix(string, oldSuffix, newSuffix) {
299        if (!oldSuffix) {
300            return string + newSuffix;
301        }
302        if (string.slice(-oldSuffix.length) != oldSuffix) {
303            throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
304        }
305        return string.slice(0, -oldSuffix.length) + newSuffix;
306    }
307    function removePrefix(string, oldPrefix) {
308        return replacePrefix(string, oldPrefix, '');
309    }
310    function removeSuffix(string, oldSuffix) {
311        return replaceSuffix(string, oldSuffix, '');
312    }
313    function maximumOverlap(string1, string2) {
314        return string2.slice(0, overlapCount(string1, string2));
315    }
316    // Nicked from https://stackoverflow.com/a/60422853/1709587
317    function overlapCount(a, b) {
318        // Deal with cases where the strings differ in length
319        let startA = 0;
320        if (a.length > b.length) {
321            startA = a.length - b.length;
322        }
323        let endB = b.length;
324        if (a.length < b.length) {
325            endB = a.length;
326        }
327        // Create a back-reference for each index
328        //   that should be followed in case of a mismatch.
329        //   We only need B to make these references:
330        const map = Array(endB);
331        let k = 0; // Index that lags behind j
332        map[0] = 0;
333        for (let j = 1; j < endB; j++) {
334            if (b[j] == b[k]) {
335                map[j] = map[k]; // skip over the same character (optional optimisation)
336            }
337            else {
338                map[j] = k;
339            }
340            while (k > 0 && b[j] != b[k]) {
341                k = map[k];
342            }
343            if (b[j] == b[k]) {
344                k++;
345            }
346        }
347        // Phase 2: use these references while iterating over A
348        k = 0;
349        for (let i = startA; i < a.length; i++) {
350            while (k > 0 && a[i] != b[k]) {
351                k = map[k];
352            }
353            if (a[i] == b[k]) {
354                k++;
355            }
356        }
357        return k;
358    }
359    /**
360     * Returns true if the string consistently uses Windows line endings.
361     */
362    function hasOnlyWinLineEndings(string) {
363        return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
364    }
365    /**
366     * Returns true if the string consistently uses Unix line endings.
367     */
368    function hasOnlyUnixLineEndings(string) {
369        return !string.includes('\r\n') && string.includes('\n');
370    }
371    function trailingWs(string) {
372        // Yes, this looks overcomplicated and dumb - why not replace the whole function with
373        //     return string.match(/\s*$/)[0]
374        // you ask? Because:
375        // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
376        //    this would cause this function to take O(n²) time in the worst case (specifically when
377        //    there is a massive run of NON-TRAILING whitespace in `string`), and
378        // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
379        //    with old Safari versions that we'd like to not break if possible (see
380        //    https://github.com/kpdecker/jsdiff/pull/550)
381        // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
382        // better way that doesn't result in broken behaviour.
383        let i;
384        for (i = string.length - 1; i >= 0; i--) {
385            if (!string[i].match(/\s/)) {
386                break;
387            }
388        }
389        return string.substring(i + 1);
390    }
391    function leadingWs(string) {
392        // Thankfully the annoying considerations described in trailingWs don't apply here:
393        const match = string.match(/^\s*/);
394        return match ? match[0] : '';
395    }
396
397    // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
398    //
399    // Chars/ranges counted as "word" characters by this regex are as follows:
400    //
401    // + U+00AD  Soft hyphen
402    // + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except:
403    //   - U+00D7  × Multiplication sign
404    //   - U+00F7  ÷ Division sign
405    // + Latin Extended-A, 0100–017F
406    // + Latin Extended-B, 0180–024F
407    // + IPA Extensions, 0250–02AF
408    // + Spacing Modifier Letters, 02B0–02FF, except:
409    //   - U+02C7  ˇ &#711;  Caron
410    //   - U+02D8  ˘ &#728;  Breve
411    //   - U+02D9  ˙ &#729;  Dot Above
412    //   - U+02DA  ˚ &#730;  Ring Above
413    //   - U+02DB  ˛ &#731;  Ogonek
414    //   - U+02DC  ˜ &#732;  Small Tilde
415    //   - U+02DD  ˝ &#733;  Double Acute Accent
416    // + Latin Extended Additional, 1E00–1EFF
417    const extendedWordChars = 'a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}';
418    // Each token is one of the following:
419    // - A punctuation mark plus the surrounding whitespace
420    // - A word plus the surrounding whitespace
421    // - Pure whitespace (but only in the special case where the entire text
422    //   is just whitespace)
423    //
424    // We have to include surrounding whitespace in the tokens because the two
425    // alternative approaches produce horribly broken results:
426    // * If we just discard the whitespace, we can't fully reproduce the original
427    //   text from the sequence of tokens and any attempt to render the diff will
428    //   get the whitespace wrong.
429    // * If we have separate tokens for whitespace, then in a typical text every
430    //   second token will be a single space character. But this often results in
431    //   the optimal diff between two texts being a perverse one that preserves
432    //   the spaces between words but deletes and reinserts actual common words.
433    //   See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
434    //   for an example.
435    //
436    // Keeping the surrounding whitespace of course has implications for .equals
437    // and .join, not just .tokenize.
438    // This regex does NOT fully implement the tokenization rules described above.
439    // Instead, it gives runs of whitespace their own "token". The tokenize method
440    // then handles stitching whitespace tokens onto adjacent word or punctuation
441    // tokens.
442    const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug');
443    class WordDiff extends Diff {
444        equals(left, right, options) {
445            if (options.ignoreCase) {
446                left = left.toLowerCase();
447                right = right.toLowerCase();
448            }
449            return left.trim() === right.trim();
450        }
451        tokenize(value, options = {}) {
452            let parts;
453            if (options.intlSegmenter) {
454                const segmenter = options.intlSegmenter;
455                if (segmenter.resolvedOptions().granularity != 'word') {
456                    throw new Error('The segmenter passed must have a granularity of "word"');
457                }
458                // We want `parts` to be an array whose elements alternate between being
459                // pure whitespace and being pure non-whitespace. This is ALMOST what the
460                // segments returned by a word-based Intl.Segmenter already look like,
461                // and therefore we can ALMOST get what we want by simply doing...
462                //     parts = Array.from(segmenter.segment(value), segment => segment.segment);
463                // ... but not QUITE, because there's of one annoying special case: every
464                // newline character gets its own segment, instead of sharing a segment
465                // with other surrounding whitespace. We therefore need to manually merge
466                // consecutive segments of whitespace into a single part:
467                parts = [];
468                for (const segmentObj of Array.from(segmenter.segment(value))) {
469                    const segment = segmentObj.segment;
470                    if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) {
471                        parts[parts.length - 1] += segment;
472                    }
473                    else {
474                        parts.push(segment);
475                    }
476                }
477            }
478            else {
479                parts = value.match(tokenizeIncludingWhitespace) || [];
480            }
481            const tokens = [];
482            let prevPart = null;
483            parts.forEach(part => {
484                if ((/\s/).test(part)) {
485                    if (prevPart == null) {
486                        tokens.push(part);
487                    }
488                    else {
489                        tokens.push(tokens.pop() + part);
490                    }
491                }
492                else if (prevPart != null && (/\s/).test(prevPart)) {
493                    if (tokens[tokens.length - 1] == prevPart) {
494                        tokens.push(tokens.pop() + part);
495                    }
496                    else {
497                        tokens.push(prevPart + part);
498                    }
499                }
500                else {
501                    tokens.push(part);
502                }
503                prevPart = part;
504            });
505            return tokens;
506        }
507        join(tokens) {
508            // Tokens being joined here will always have appeared consecutively in the
509            // same text, so we can simply strip off the leading whitespace from all the
510            // tokens except the first (and except any whitespace-only tokens - but such
511            // a token will always be the first and only token anyway) and then join them
512            // and the whitespace around words and punctuation will end up correct.
513            return tokens.map((token, i) => {
514                if (i == 0) {
515                    return token;
516                }
517                else {
518                    return token.replace((/^\s+/), '');
519                }
520            }).join('');
521        }
522        postProcess(changes, options) {
523            if (!changes || options.oneChangePerToken) {
524                return changes;
525            }
526            let lastKeep = null;
527            // Change objects representing any insertion or deletion since the last
528            // "keep" change object. There can be at most one of each.
529            let insertion = null;
530            let deletion = null;
531            changes.forEach(change => {
532                if (change.added) {
533                    insertion = change;
534                }
535                else if (change.removed) {
536                    deletion = change;
537                }
538                else {
539                    if (insertion || deletion) { // May be false at start of text
540                        dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
541                    }
542                    lastKeep = change;
543                    insertion = null;
544                    deletion = null;
545                }
546            });
547            if (insertion || deletion) {
548                dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
549            }
550            return changes;
551        }
552    }
553    const wordDiff = new WordDiff();
554    function diffWords(oldStr, newStr, options) {
555        // This option has never been documented and never will be (it's clearer to
556        // just call `diffWordsWithSpace` directly if you need that behavior), but
557        // has existed in jsdiff for a long time, so we retain support for it here
558        // for the sake of backwards compatibility.
559        if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
560            return diffWordsWithSpace(oldStr, newStr, options);
561        }
562        return wordDiff.diff(oldStr, newStr, options);
563    }
564    function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) {
565        // Before returning, we tidy up the leading and trailing whitespace of the
566        // change objects to eliminate cases where trailing whitespace in one object
567        // is repeated as leading whitespace in the next.
568        // Below are examples of the outcomes we want here to explain the code.
569        // I=insert, K=keep, D=delete
570        // 1. diffing 'foo bar baz' vs 'foo baz'
571        //    Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
572        //    After cleanup, we want:   K:'foo ' D:'bar ' K:'baz'
573        //
574        // 2. Diffing 'foo bar baz' vs 'foo qux baz'
575        //    Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
576        //    After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
577        //
578        // 3. Diffing 'foo\nbar baz' vs 'foo baz'
579        //    Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
580        //    After cleanup, we want K'foo' D:'\nbar' K:' baz'
581        //
582        // 4. Diffing 'foo baz' vs 'foo\nbar baz'
583        //    Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
584        //    After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
585        //    but don't actually manage this currently (the pre-cleanup change
586        //    objects don't contain enough information to make it possible).
587        //
588        // 5. Diffing 'foo   bar baz' vs 'foo  baz'
589        //    Prior to cleanup, we have K:'foo  ' D:'   bar ' K:'  baz'
590        //    After cleanup, we want K:'foo  ' D:' bar ' K:'baz'
591        //
592        // Our handling is unavoidably imperfect in the case where there's a single
593        // indel between keeps and the whitespace has changed. For instance, consider
594        // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
595        // object to represent the insertion of the space character (which isn't even
596        // a token), we have no way to avoid losing information about the texts'
597        // original whitespace in the result we return. Still, we do our best to
598        // output something that will look sensible if we e.g. print it with
599        // insertions in green and deletions in red.
600        // Between two "keep" change objects (or before the first or after the last
601        // change object), we can have either:
602        // * A "delete" followed by an "insert"
603        // * Just an "insert"
604        // * Just a "delete"
605        // We handle the three cases separately.
606        if (deletion && insertion) {
607            const oldWsPrefix = leadingWs(deletion.value);
608            const oldWsSuffix = trailingWs(deletion.value);
609            const newWsPrefix = leadingWs(insertion.value);
610            const newWsSuffix = trailingWs(insertion.value);
611            if (startKeep) {
612                const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
613                startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
614                deletion.value = removePrefix(deletion.value, commonWsPrefix);
615                insertion.value = removePrefix(insertion.value, commonWsPrefix);
616            }
617            if (endKeep) {
618                const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
619                endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
620                deletion.value = removeSuffix(deletion.value, commonWsSuffix);
621                insertion.value = removeSuffix(insertion.value, commonWsSuffix);
622            }
623        }
624        else if (insertion) {
625            // The whitespaces all reflect what was in the new text rather than
626            // the old, so we essentially have no information about whitespace
627            // insertion or deletion. We just want to dedupe the whitespace.
628            // We do that by having each change object keep its trailing
629            // whitespace and deleting duplicate leading whitespace where
630            // present.
631            if (startKeep) {
632                const ws = leadingWs(insertion.value);
633                insertion.value = insertion.value.substring(ws.length);
634            }
635            if (endKeep) {
636                const ws = leadingWs(endKeep.value);
637                endKeep.value = endKeep.value.substring(ws.length);
638            }
639            // otherwise we've got a deletion and no insertion
640        }
641        else if (startKeep && endKeep) {
642            const newWsFull = leadingWs(endKeep.value), delWsStart = leadingWs(deletion.value), delWsEnd = trailingWs(deletion.value);
643            // Any whitespace that comes straight after startKeep in both the old and
644            // new texts, assign to startKeep and remove from the deletion.
645            const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
646            deletion.value = removePrefix(deletion.value, newWsStart);
647            // Any whitespace that comes straight before endKeep in both the old and
648            // new texts, and hasn't already been assigned to startKeep, assign to
649            // endKeep and remove from the deletion.
650            const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
651            deletion.value = removeSuffix(deletion.value, newWsEnd);
652            endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
653            // If there's any whitespace from the new text that HASN'T already been
654            // assigned, assign it to the start:
655            startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
656        }
657        else if (endKeep) {
658            // We are at the start of the text. Preserve all the whitespace on
659            // endKeep, and just remove whitespace from the end of deletion to the
660            // extent that it overlaps with the start of endKeep.
661            const endKeepWsPrefix = leadingWs(endKeep.value);
662            const deletionWsSuffix = trailingWs(deletion.value);
663            const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
664            deletion.value = removeSuffix(deletion.value, overlap);
665        }
666        else if (startKeep) {
667            // We are at the END of the text. Preserve all the whitespace on
668            // startKeep, and just remove whitespace from the start of deletion to
669            // the extent that it overlaps with the end of startKeep.
670            const startKeepWsSuffix = trailingWs(startKeep.value);
671            const deletionWsPrefix = leadingWs(deletion.value);
672            const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
673            deletion.value = removePrefix(deletion.value, overlap);
674        }
675    }
676    class WordsWithSpaceDiff extends Diff {
677        tokenize(value) {
678            // Slightly different to the tokenizeIncludingWhitespace regex used above in
679            // that this one treats each individual newline as a distinct token, rather
680            // than merging them into other surrounding whitespace. This was requested
681            // in https://github.com/kpdecker/jsdiff/issues/180 &
682            //    https://github.com/kpdecker/jsdiff/issues/211
683            const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug');
684            return value.match(regex) || [];
685        }
686    }
687    const wordsWithSpaceDiff = new WordsWithSpaceDiff();
688    function diffWordsWithSpace(oldStr, newStr, options) {
689        return wordsWithSpaceDiff.diff(oldStr, newStr, options);
690    }
691
692    function generateOptions(options, defaults) {
693        if (typeof options === 'function') {
694            defaults.callback = options;
695        }
696        else if (options) {
697            for (const name in options) {
698                /* istanbul ignore else */
699                if (Object.prototype.hasOwnProperty.call(options, name)) {
700                    defaults[name] = options[name];
701                }
702            }
703        }
704        return defaults;
705    }
706
707    class LineDiff extends Diff {
708        constructor() {
709            super(...arguments);
710            this.tokenize = tokenize;
711        }
712        equals(left, right, options) {
713            // If we're ignoring whitespace, we need to normalise lines by stripping
714            // whitespace before checking equality. (This has an annoying interaction
715            // with newlineIsToken that requires special handling: if newlines get their
716            // own token, then we DON'T want to trim the *newline* tokens down to empty
717            // strings, since this would cause us to treat whitespace-only line content
718            // as equal to a separator between lines, which would be weird and
719            // inconsistent with the documented behavior of the options.)
720            if (options.ignoreWhitespace) {
721                if (!options.newlineIsToken || !left.includes('\n')) {
722                    left = left.trim();
723                }
724                if (!options.newlineIsToken || !right.includes('\n')) {
725                    right = right.trim();
726                }
727            }
728            else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
729                if (left.endsWith('\n')) {
730                    left = left.slice(0, -1);
731                }
732                if (right.endsWith('\n')) {
733                    right = right.slice(0, -1);
734                }
735            }
736            return super.equals(left, right, options);
737        }
738    }
739    const lineDiff = new LineDiff();
740    function diffLines(oldStr, newStr, options) {
741        return lineDiff.diff(oldStr, newStr, options);
742    }
743    function diffTrimmedLines(oldStr, newStr, options) {
744        options = generateOptions(options, { ignoreWhitespace: true });
745        return lineDiff.diff(oldStr, newStr, options);
746    }
747    // Exported standalone so it can be used from jsonDiff too.
748    function tokenize(value, options) {
749        if (options.stripTrailingCr) {
750            // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
751            value = value.replace(/\r\n/g, '\n');
752        }
753        const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
754        // Ignore the final empty token that occurs if the string ends with a new line
755        if (!linesAndNewlines[linesAndNewlines.length - 1]) {
756            linesAndNewlines.pop();
757        }
758        // Merge the content and line separators into single tokens
759        for (let i = 0; i < linesAndNewlines.length; i++) {
760            const line = linesAndNewlines[i];
761            if (i % 2 && !options.newlineIsToken) {
762                retLines[retLines.length - 1] += line;
763            }
764            else {
765                retLines.push(line);
766            }
767        }
768        return retLines;
769    }
770
771    function isSentenceEndPunct(char) {
772        return char == '.' || char == '!' || char == '?';
773    }
774    class SentenceDiff extends Diff {
775        tokenize(value) {
776            var _a;
777            // If in future we drop support for environments that don't support lookbehinds, we can replace
778            // this entire function with:
779            //     return value.split(/(?<=[.!?])(\s+|$)/);
780            // but until then, for similar reasons to the trailingWs function in string.ts, we are forced
781            // to do this verbosely "by hand" instead of using a regex.
782            const result = [];
783            let tokenStartI = 0;
784            for (let i = 0; i < value.length; i++) {
785                if (i == value.length - 1) {
786                    result.push(value.slice(tokenStartI));
787                    break;
788                }
789                if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
790                    // We've hit a sentence break - i.e. a punctuation mark followed by whitespace.
791                    // We now want to push TWO tokens to the result:
792                    // 1. the sentence
793                    result.push(value.slice(tokenStartI, i + 1));
794                    // 2. the whitespace
795                    i = tokenStartI = i + 1;
796                    while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) {
797                        i++;
798                    }
799                    result.push(value.slice(tokenStartI, i + 1));
800                    // Then the next token (a sentence) starts on the character after the whitespace.
801                    // (It's okay if this is off the end of the string - then the outer loop will terminate
802                    // here anyway.)
803                    tokenStartI = i + 1;
804                }
805            }
806            return result;
807        }
808    }
809    const sentenceDiff = new SentenceDiff();
810    function diffSentences(oldStr, newStr, options) {
811        return sentenceDiff.diff(oldStr, newStr, options);
812    }
813
814    class CssDiff extends Diff {
815        tokenize(value) {
816            return value.split(/([{}:;,]|\s+)/);
817        }
818    }
819    const cssDiff = new CssDiff();
820    function diffCss(oldStr, newStr, options) {
821        return cssDiff.diff(oldStr, newStr, options);
822    }
823
824    class JsonDiff extends Diff {
825        constructor() {
826            super(...arguments);
827            this.tokenize = tokenize;
828        }
829        get useLongestToken() {
830            // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
831            // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
832            return true;
833        }
834        castInput(value, options) {
835            const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v } = options;
836            return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, '  ');
837        }
838        equals(left, right, options) {
839            return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
840        }
841    }
842    const jsonDiff = new JsonDiff();
843    function diffJson(oldStr, newStr, options) {
844        return jsonDiff.diff(oldStr, newStr, options);
845    }
846    // This function handles the presence of circular references by bailing out when encountering an
847    // object that is already on the "stack" of items being processed. Accepts an optional replacer
848    function canonicalize(obj, stack, replacementStack, replacer, key) {
849        stack = stack || [];
850        replacementStack = replacementStack || [];
851        if (replacer) {
852            obj = replacer(key === undefined ? '' : key, obj);
853        }
854        let i;
855        for (i = 0; i < stack.length; i += 1) {
856            if (stack[i] === obj) {
857                return replacementStack[i];
858            }
859        }
860        let canonicalizedObj;
861        if ('[object Array]' === Object.prototype.toString.call(obj)) {
862            stack.push(obj);
863            canonicalizedObj = new Array(obj.length);
864            replacementStack.push(canonicalizedObj);
865            for (i = 0; i < obj.length; i += 1) {
866                canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
867            }
868            stack.pop();
869            replacementStack.pop();
870            return canonicalizedObj;
871        }
872        if (obj && obj.toJSON) {
873            obj = obj.toJSON();
874        }
875        if (typeof obj === 'object' && obj !== null) {
876            stack.push(obj);
877            canonicalizedObj = {};
878            replacementStack.push(canonicalizedObj);
879            const sortedKeys = [];
880            let key;
881            for (key in obj) {
882                /* istanbul ignore else */
883                if (Object.prototype.hasOwnProperty.call(obj, key)) {
884                    sortedKeys.push(key);
885                }
886            }
887            sortedKeys.sort();
888            for (i = 0; i < sortedKeys.length; i += 1) {
889                key = sortedKeys[i];
890                canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
891            }
892            stack.pop();
893            replacementStack.pop();
894        }
895        else {
896            canonicalizedObj = obj;
897        }
898        return canonicalizedObj;
899    }
900
901    class ArrayDiff extends Diff {
902        tokenize(value) {
903            return value.slice();
904        }
905        join(value) {
906            return value;
907        }
908        removeEmpty(value) {
909            return value;
910        }
911    }
912    const arrayDiff = new ArrayDiff();
913    function diffArrays(oldArr, newArr, options) {
914        return arrayDiff.diff(oldArr, newArr, options);
915    }
916
917    function unixToWin(patch) {
918        if (Array.isArray(patch)) {
919            // It would be cleaner if instead of the line below we could just write
920            //     return patch.map(unixToWin)
921            // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will
922            // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the
923            // result would be incompatible with the overload signatures.
924            // See bug report at https://github.com/microsoft/TypeScript/issues/61398.
925            return patch.map(p => unixToWin(p));
926        }
927        return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map(hunk => (Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line, i) => {
928                    var _a;
929                    return (line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')))
930                        ? line
931                        : line + '\r';
932                }) }))) });
933    }
934    function winToUnix(patch) {
935        if (Array.isArray(patch)) {
936            // (See comment above equivalent line in unixToWin)
937            return patch.map(p => winToUnix(p));
938        }
939        return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map(hunk => (Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map(line => line.endsWith('\r') ? line.substring(0, line.length - 1) : line) }))) });
940    }
941    /**
942     * Returns true if the patch consistently uses Unix line endings (or only involves one line and has
943     * no line endings).
944     */
945    function isUnix(patch) {
946        if (!Array.isArray(patch)) {
947            patch = [patch];
948        }
949        return !patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => !line.startsWith('\\') && line.endsWith('\r'))));
950    }
951    /**
952     * Returns true if the patch uses Windows line endings and only Windows line endings.
953     */
954    function isWin(patch) {
955        if (!Array.isArray(patch)) {
956            patch = [patch];
957        }
958        return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r'))))
959            && patch.every(index => index.hunks.every(hunk => hunk.lines.every((line, i) => { var _a; return line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')); })));
960    }
961
962    /**
963     * Parses a patch into structured data, in the same structure returned by `structuredPatch`.
964     *
965     * @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
966     */
967    function parsePatch(uniDiff) {
968        const diffstr = uniDiff.split(/\n/), list = [];
969        let i = 0;
970        function parseIndex() {
971            const index = {};
972            list.push(index);
973            // Parse diff metadata
974            while (i < diffstr.length) {
975                const line = diffstr[i];
976                // File header found, end parsing diff metadata
977                if ((/^(---|\+\+\+|@@)\s/).test(line)) {
978                    break;
979                }
980                // Try to parse the line as a diff header, like
981                //     Index: README.md
982                // or
983                //     diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
984                // or
985                //     Index: something with multiple words
986                // and extract the filename (or whatever else is used as an index name)
987                // from the end (i.e. 'README.md', '.hgignore', or
988                // 'something with multiple words' in the examples above).
989                //
990                // TODO: It seems awkward that we indiscriminately trim off trailing
991                //       whitespace here. Theoretically, couldn't that be meaningful -
992                //       e.g. if the patch represents a diff of a file whose name ends
993                //       with a space? Seems wrong to nuke it.
994                //       But this behaviour has been around since v2.2.1 in 2015, so if
995                //       it's going to change, it should be done cautiously and in a new
996                //       major release, for backwards-compat reasons.
997                //       -- ExplodingCabbage
998                const headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
999                if (headerMatch) {
1000                    index.index = line.substring(headerMatch[0].length).trim();
1001                }
1002                i++;
1003            }
1004            // Parse file headers if they are defined. Unified diff requires them, but
1005            // there's no technical issues to have an isolated hunk without file header
1006            parseFileHeader(index);
1007            parseFileHeader(index);
1008            // Parse hunks
1009            index.hunks = [];
1010            while (i < diffstr.length) {
1011                const line = diffstr[i];
1012                if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
1013                    break;
1014                }
1015                else if ((/^@@/).test(line)) {
1016                    index.hunks.push(parseHunk());
1017                }
1018                else if (line) {
1019                    throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
1020                }
1021                else {
1022                    i++;
1023                }
1024            }
1025        }
1026        // Parses the --- and +++ headers, if none are found, no lines
1027        // are consumed.
1028        function parseFileHeader(index) {
1029            const fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
1030            if (fileHeaderMatch) {
1031                const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
1032                let fileName = data[0].replace(/\\\\/g, '\\');
1033                if (fileName.startsWith('"') && fileName.endsWith('"')) {
1034                    fileName = fileName.substr(1, fileName.length - 2);
1035                }
1036                if (prefix === '---') {
1037                    index.oldFileName = fileName;
1038                    index.oldHeader = header;
1039                }
1040                else {
1041                    index.newFileName = fileName;
1042                    index.newHeader = header;
1043                }
1044                i++;
1045            }
1046        }
1047        // Parses a hunk
1048        // This assumes that we are at the start of a hunk.
1049        function parseHunk() {
1050            var _a;
1051            const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
1052            const hunk = {
1053                oldStart: +chunkHeader[1],
1054                oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
1055                newStart: +chunkHeader[3],
1056                newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
1057                lines: []
1058            };
1059            // Unified Diff Format quirk: If the chunk size is 0,
1060            // the first number is one lower than one would expect.
1061            // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
1062            if (hunk.oldLines === 0) {
1063                hunk.oldStart += 1;
1064            }
1065            if (hunk.newLines === 0) {
1066                hunk.newStart += 1;
1067            }
1068            let addCount = 0, removeCount = 0;
1069            for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
1070                const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
1071                if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
1072                    hunk.lines.push(diffstr[i]);
1073                    if (operation === '+') {
1074                        addCount++;
1075                    }
1076                    else if (operation === '-') {
1077                        removeCount++;
1078                    }
1079                    else if (operation === ' ') {
1080                        addCount++;
1081                        removeCount++;
1082                    }
1083                }
1084                else {
1085                    throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
1086                }
1087            }
1088            // Handle the empty block count case
1089            if (!addCount && hunk.newLines === 1) {
1090                hunk.newLines = 0;
1091            }
1092            if (!removeCount && hunk.oldLines === 1) {
1093                hunk.oldLines = 0;
1094            }
1095            // Perform sanity checking
1096            if (addCount !== hunk.newLines) {
1097                throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
1098            }
1099            if (removeCount !== hunk.oldLines) {
1100                throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
1101            }
1102            return hunk;
1103        }
1104        while (i < diffstr.length) {
1105            parseIndex();
1106        }
1107        return list;
1108    }
1109
1110    // Iterator that traverses in the range of [min, max], stepping
1111    // by distance from a given start position. I.e. for [0, 4], with
1112    // start of 2, this will iterate 2, 3, 1, 4, 0.
1113    function distanceIterator (start, minLine, maxLine) {
1114        let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
1115        return function iterator() {
1116            if (wantForward && !forwardExhausted) {
1117                if (backwardExhausted) {
1118                    localOffset++;
1119                }
1120                else {
1121                    wantForward = false;
1122                }
1123                // Check if trying to fit beyond text length, and if not, check it fits
1124                // after offset location (or desired location on first iteration)
1125                if (start + localOffset <= maxLine) {
1126                    return start + localOffset;
1127                }
1128                forwardExhausted = true;
1129            }
1130            if (!backwardExhausted) {
1131                if (!forwardExhausted) {
1132                    wantForward = true;
1133                }
1134                // Check if trying to fit before text beginning, and if not, check it fits
1135                // before offset location
1136                if (minLine <= start - localOffset) {
1137                    return start - localOffset++;
1138                }
1139                backwardExhausted = true;
1140                return iterator();
1141            }
1142            // We tried to fit hunk before text beginning and beyond text length, then
1143            // hunk can't fit on the text. Return undefined
1144            return undefined;
1145        };
1146    }
1147
1148    /**
1149     * attempts to apply a unified diff patch.
1150     *
1151     * Hunks are applied first to last.
1152     * `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly.
1153     * If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly.
1154     * If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match.
1155     * Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly.
1156     *
1157     * Once a hunk is successfully fitted, the process begins again with the next hunk.
1158     * Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks.
1159     *
1160     * If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`.
1161     *
1162     * If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly.
1163     * (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.)
1164     *
1165     * If the patch was applied successfully, returns a string containing the patched text.
1166     * If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false.
1167     *
1168     * @param patch a string diff or the output from the `parsePatch` or `structuredPatch` methods.
1169     */
1170    function applyPatch(source, patch, options = {}) {
1171        let patches;
1172        if (typeof patch === 'string') {
1173            patches = parsePatch(patch);
1174        }
1175        else if (Array.isArray(patch)) {
1176            patches = patch;
1177        }
1178        else {
1179            patches = [patch];
1180        }
1181        if (patches.length > 1) {
1182            throw new Error('applyPatch only works with a single input.');
1183        }
1184        return applyStructuredPatch(source, patches[0], options);
1185    }
1186    function applyStructuredPatch(source, patch, options = {}) {
1187        if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
1188            if (hasOnlyWinLineEndings(source) && isUnix(patch)) {
1189                patch = unixToWin(patch);
1190            }
1191            else if (hasOnlyUnixLineEndings(source) && isWin(patch)) {
1192                patch = winToUnix(patch);
1193            }
1194        }
1195        // Apply the diff to the input
1196        const lines = source.split('\n'), hunks = patch.hunks, compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), fuzzFactor = options.fuzzFactor || 0;
1197        let minLine = 0;
1198        if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
1199            throw new Error('fuzzFactor must be a non-negative integer');
1200        }

Showing the first 1,200 of 1742 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai