Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
diff.js1783 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    /**
372     * Split a string into segments using a word segmenter, merging consecutive
373     * segments if they are both whitespace segments. Whitespace segments can
374     * appear adjacent to one another for two reasons:
375     * - newlines always get their own segment
376     * - where a diacritic is attached to a whitespace character in the text, the
377     *   segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
378     * This function therefore runs the segmenter's .segment() method and then
379     * merges consecutive segments of whitespace into a single part.
380     */
381    function segment(string, segmenter) {
382        const parts = [];
383        for (const segmentObj of Array.from(segmenter.segment(string))) {
384            const segment = segmentObj.segment;
385            if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) {
386                parts[parts.length - 1] += segment;
387            }
388            else {
389                parts.push(segment);
390            }
391        }
392        return parts;
393    }
394    // The functions below take a `segmenter` argument so that, when called from
395    // diffWords when it is using a segmenter, they can use a notion of what
396    // constitutes "whitespace" that is consistent with the segmenter.
397    //
398    // USUALLY this will be identical to the result of the non-segmenter-based
399    // logic, but it differs in at least one case: when whitespace characters are
400    // modified by diacritics. A word segmenter considers these diacritics to be
401    // part of the whitespace, whereas our non-segmenter-based logic does not.
402    //
403    // Because the segmenter-based approach necessarily requires segmenting the
404    // entire string, we offer a leadingAndTrailingWs function to allow getting the
405    // whitespace prefix AND whitespace suffix with a single call to the segmenter,
406    // for efficiency's sake.
407    function trailingWs(string, segmenter) {
408        if (segmenter) {
409            return leadingAndTrailingWs(string, segmenter)[1];
410        }
411        // Yes, this looks overcomplicated and dumb - why not replace the whole function with
412        //     return string.match(/\s*$/)[0]
413        // you ask? Because:
414        // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
415        //    this would cause this function to take O(n²) time in the worst case (specifically when
416        //    there is a massive run of NON-TRAILING whitespace in `string`), and
417        // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
418        //    with old Safari versions that we'd like to not break if possible (see
419        //    https://github.com/kpdecker/jsdiff/pull/550)
420        // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
421        // better way that doesn't result in broken behaviour.
422        let i;
423        for (i = string.length - 1; i >= 0; i--) {
424            if (!string[i].match(/\s/)) {
425                break;
426            }
427        }
428        return string.substring(i + 1);
429    }
430    function leadingWs(string, segmenter) {
431        if (segmenter) {
432            return leadingAndTrailingWs(string, segmenter)[0];
433        }
434        // Thankfully the annoying considerations described in trailingWs don't apply here:
435        const match = string.match(/^\s*/);
436        return match ? match[0] : '';
437    }
438    function leadingAndTrailingWs(string, segmenter) {
439        if (!segmenter) {
440            return [leadingWs(string), trailingWs(string)];
441        }
442        if (segmenter.resolvedOptions().granularity != 'word') {
443            throw new Error('The segmenter passed must have a granularity of "word"');
444        }
445        const segments = segment(string, segmenter);
446        const firstSeg = segments[0];
447        const lastSeg = segments[segments.length - 1];
448        const head = (/\s/).test(firstSeg) ? firstSeg : '';
449        const tail = (/\s/).test(lastSeg) ? lastSeg : '';
450        return [head, tail];
451    }
452
453    // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
454    //
455    // Chars/ranges counted as "word" characters by this regex are as follows:
456    //
457    // + U+00AD  Soft hyphen
458    // + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except:
459    //   - U+00D7  × Multiplication sign
460    //   - U+00F7  ÷ Division sign
461    // + Latin Extended-A, 0100–017F
462    // + Latin Extended-B, 0180–024F
463    // + IPA Extensions, 0250–02AF
464    // + Spacing Modifier Letters, 02B0–02FF, except:
465    //   - U+02C7  ˇ &#711;  Caron
466    //   - U+02D8  ˘ &#728;  Breve
467    //   - U+02D9  ˙ &#729;  Dot Above
468    //   - U+02DA  ˚ &#730;  Ring Above
469    //   - U+02DB  ˛ &#731;  Ogonek
470    //   - U+02DC  ˜ &#732;  Small Tilde
471    //   - U+02DD  ˝ &#733;  Double Acute Accent
472    // + Latin Extended Additional, 1E00–1EFF
473    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}';
474    // Each token is one of the following:
475    // - A punctuation mark plus the surrounding whitespace
476    // - A word plus the surrounding whitespace
477    // - Pure whitespace (but only in the special case where the entire text
478    //   is just whitespace)
479    //
480    // We have to include surrounding whitespace in the tokens because the two
481    // alternative approaches produce horribly broken results:
482    // * If we just discard the whitespace, we can't fully reproduce the original
483    //   text from the sequence of tokens and any attempt to render the diff will
484    //   get the whitespace wrong.
485    // * If we have separate tokens for whitespace, then in a typical text every
486    //   second token will be a single space character. But this often results in
487    //   the optimal diff between two texts being a perverse one that preserves
488    //   the spaces between words but deletes and reinserts actual common words.
489    //   See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
490    //   for an example.
491    //
492    // Keeping the surrounding whitespace of course has implications for .equals
493    // and .join, not just .tokenize.
494    // This regex does NOT fully implement the tokenization rules described above.
495    // Instead, it gives runs of whitespace their own "token". The tokenize method
496    // then handles stitching whitespace tokens onto adjacent word or punctuation
497    // tokens.
498    const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug');
499    class WordDiff extends Diff {
500        equals(left, right, options) {
501            if (options.ignoreCase) {
502                left = left.toLowerCase();
503                right = right.toLowerCase();
504            }
505            return left.trim() === right.trim();
506        }
507        tokenize(value, options = {}) {
508            let parts;
509            if (options.intlSegmenter) {
510                const segmenter = options.intlSegmenter;
511                if (segmenter.resolvedOptions().granularity != 'word') {
512                    throw new Error('The segmenter passed must have a granularity of "word"');
513                }
514                // We want `parts` to be an array whose elements alternate between being
515                // pure whitespace and being pure non-whitespace. This is ALMOST what the
516                // segments returned by a word-based Intl.Segmenter already look like,
517                // but not quite - see explanation in the docs of our custom segment()
518                // function.
519                parts = segment(value, segmenter);
520            }
521            else {
522                parts = value.match(tokenizeIncludingWhitespace) || [];
523            }
524            const tokens = [];
525            let prevPart = null;
526            parts.forEach(part => {
527                if ((/\s/).test(part)) {
528                    if (prevPart == null) {
529                        tokens.push(part);
530                    }
531                    else {
532                        tokens.push(tokens.pop() + part);
533                    }
534                }
535                else if (prevPart != null && (/\s/).test(prevPart)) {
536                    if (tokens[tokens.length - 1] == prevPart) {
537                        tokens.push(tokens.pop() + part);
538                    }
539                    else {
540                        tokens.push(prevPart + part);
541                    }
542                }
543                else {
544                    tokens.push(part);
545                }
546                prevPart = part;
547            });
548            return tokens;
549        }
550        join(tokens) {
551            // Tokens being joined here will always have appeared consecutively in the
552            // same text, so we can simply strip off the leading whitespace from all the
553            // tokens except the first (and except any whitespace-only tokens - but such
554            // a token will always be the first and only token anyway) and then join them
555            // and the whitespace around words and punctuation will end up correct.
556            return tokens.map((token, i) => {
557                if (i == 0) {
558                    return token;
559                }
560                else {
561                    return token.replace((/^\s+/), '');
562                }
563            }).join('');
564        }
565        postProcess(changes, options) {
566            if (!changes || options.oneChangePerToken) {
567                return changes;
568            }
569            let lastKeep = null;
570            // Change objects representing any insertion or deletion since the last
571            // "keep" change object. There can be at most one of each.
572            let insertion = null;
573            let deletion = null;
574            changes.forEach(change => {
575                if (change.added) {
576                    insertion = change;
577                }
578                else if (change.removed) {
579                    deletion = change;
580                }
581                else {
582                    if (insertion || deletion) { // May be false at start of text
583                        dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
584                    }
585                    lastKeep = change;
586                    insertion = null;
587                    deletion = null;
588                }
589            });
590            if (insertion || deletion) {
591                dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
592            }
593            return changes;
594        }
595    }
596    const wordDiff = new WordDiff();
597    function diffWords(oldStr, newStr, options) {
598        // This option has never been documented and never will be (it's clearer to
599        // just call `diffWordsWithSpace` directly if you need that behavior), but
600        // has existed in jsdiff for a long time, so we retain support for it here
601        // for the sake of backwards compatibility.
602        if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
603            return diffWordsWithSpace(oldStr, newStr, options);
604        }
605        return wordDiff.diff(oldStr, newStr, options);
606    }
607    function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
608        // Before returning, we tidy up the leading and trailing whitespace of the
609        // change objects to eliminate cases where trailing whitespace in one object
610        // is repeated as leading whitespace in the next.
611        // Below are examples of the outcomes we want here to explain the code.
612        // I=insert, K=keep, D=delete
613        // 1. diffing 'foo bar baz' vs 'foo baz'
614        //    Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
615        //    After cleanup, we want:   K:'foo ' D:'bar ' K:'baz'
616        //
617        // 2. Diffing 'foo bar baz' vs 'foo qux baz'
618        //    Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
619        //    After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
620        //
621        // 3. Diffing 'foo\nbar baz' vs 'foo baz'
622        //    Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
623        //    After cleanup, we want K'foo' D:'\nbar' K:' baz'
624        //
625        // 4. Diffing 'foo baz' vs 'foo\nbar baz'
626        //    Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
627        //    After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
628        //    but don't actually manage this currently (the pre-cleanup change
629        //    objects don't contain enough information to make it possible).
630        //
631        // 5. Diffing 'foo   bar baz' vs 'foo  baz'
632        //    Prior to cleanup, we have K:'foo  ' D:'   bar ' K:'  baz'
633        //    After cleanup, we want K:'foo  ' D:' bar ' K:'baz'
634        //
635        // Our handling is unavoidably imperfect in the case where there's a single
636        // indel between keeps and the whitespace has changed. For instance, consider
637        // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
638        // object to represent the insertion of the space character (which isn't even
639        // a token), we have no way to avoid losing information about the texts'
640        // original whitespace in the result we return. Still, we do our best to
641        // output something that will look sensible if we e.g. print it with
642        // insertions in green and deletions in red.
643        // Between two "keep" change objects (or before the first or after the last
644        // change object), we can have either:
645        // * A "delete" followed by an "insert"
646        // * Just an "insert"
647        // * Just a "delete"
648        // We handle the three cases separately.
649        if (deletion && insertion) {
650            const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter);
651            const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter);
652            if (startKeep) {
653                const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
654                startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
655                deletion.value = removePrefix(deletion.value, commonWsPrefix);
656                insertion.value = removePrefix(insertion.value, commonWsPrefix);
657            }
658            if (endKeep) {
659                const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
660                endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
661                deletion.value = removeSuffix(deletion.value, commonWsSuffix);
662                insertion.value = removeSuffix(insertion.value, commonWsSuffix);
663            }
664        }
665        else if (insertion) {
666            // The whitespaces all reflect what was in the new text rather than
667            // the old, so we essentially have no information about whitespace
668            // insertion or deletion. We just want to dedupe the whitespace.
669            // We do that by having each change object keep its trailing
670            // whitespace and deleting duplicate leading whitespace where
671            // present.
672            if (startKeep) {
673                const ws = leadingWs(insertion.value, segmenter);
674                insertion.value = insertion.value.substring(ws.length);
675            }
676            if (endKeep) {
677                const ws = leadingWs(endKeep.value, segmenter);
678                endKeep.value = endKeep.value.substring(ws.length);
679            }
680            // otherwise we've got a deletion and no insertion
681        }
682        else if (startKeep && endKeep) {
683            const newWsFull = leadingWs(endKeep.value, segmenter), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter);
684            // Any whitespace that comes straight after startKeep in both the old and
685            // new texts, assign to startKeep and remove from the deletion.
686            const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
687            deletion.value = removePrefix(deletion.value, newWsStart);
688            // Any whitespace that comes straight before endKeep in both the old and
689            // new texts, and hasn't already been assigned to startKeep, assign to
690            // endKeep and remove from the deletion.
691            const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
692            deletion.value = removeSuffix(deletion.value, newWsEnd);
693            endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
694            // If there's any whitespace from the new text that HASN'T already been
695            // assigned, assign it to the start:
696            startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
697        }
698        else if (endKeep) {
699            // We are at the start of the text. Preserve all the whitespace on
700            // endKeep, and just remove whitespace from the end of deletion to the
701            // extent that it overlaps with the start of endKeep.
702            const endKeepWsPrefix = leadingWs(endKeep.value, segmenter);
703            const deletionWsSuffix = trailingWs(deletion.value, segmenter);
704            const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
705            deletion.value = removeSuffix(deletion.value, overlap);
706        }
707        else if (startKeep) {
708            // We are at the END of the text. Preserve all the whitespace on
709            // startKeep, and just remove whitespace from the start of deletion to
710            // the extent that it overlaps with the end of startKeep.
711            const startKeepWsSuffix = trailingWs(startKeep.value, segmenter);
712            const deletionWsPrefix = leadingWs(deletion.value, segmenter);
713            const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
714            deletion.value = removePrefix(deletion.value, overlap);
715        }
716    }
717    class WordsWithSpaceDiff extends Diff {
718        tokenize(value) {
719            // Slightly different to the tokenizeIncludingWhitespace regex used above in
720            // that this one treats each individual newline as a distinct token, rather
721            // than merging them into other surrounding whitespace. This was requested
722            // in https://github.com/kpdecker/jsdiff/issues/180 &
723            //    https://github.com/kpdecker/jsdiff/issues/211
724            const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug');
725            return value.match(regex) || [];
726        }
727    }
728    const wordsWithSpaceDiff = new WordsWithSpaceDiff();
729    function diffWordsWithSpace(oldStr, newStr, options) {
730        return wordsWithSpaceDiff.diff(oldStr, newStr, options);
731    }
732
733    function generateOptions(options, defaults) {
734        if (typeof options === 'function') {
735            defaults.callback = options;
736        }
737        else if (options) {
738            for (const name in options) {
739                /* istanbul ignore else */
740                if (Object.prototype.hasOwnProperty.call(options, name)) {
741                    defaults[name] = options[name];
742                }
743            }
744        }
745        return defaults;
746    }
747
748    class LineDiff extends Diff {
749        constructor() {
750            super(...arguments);
751            this.tokenize = tokenize;
752        }
753        equals(left, right, options) {
754            // If we're ignoring whitespace, we need to normalise lines by stripping
755            // whitespace before checking equality. (This has an annoying interaction
756            // with newlineIsToken that requires special handling: if newlines get their
757            // own token, then we DON'T want to trim the *newline* tokens down to empty
758            // strings, since this would cause us to treat whitespace-only line content
759            // as equal to a separator between lines, which would be weird and
760            // inconsistent with the documented behavior of the options.)
761            if (options.ignoreWhitespace) {
762                if (!options.newlineIsToken || !left.includes('\n')) {
763                    left = left.trim();
764                }
765                if (!options.newlineIsToken || !right.includes('\n')) {
766                    right = right.trim();
767                }
768            }
769            else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
770                if (left.endsWith('\n')) {
771                    left = left.slice(0, -1);
772                }
773                if (right.endsWith('\n')) {
774                    right = right.slice(0, -1);
775                }
776            }
777            return super.equals(left, right, options);
778        }
779    }
780    const lineDiff = new LineDiff();
781    function diffLines(oldStr, newStr, options) {
782        return lineDiff.diff(oldStr, newStr, options);
783    }
784    function diffTrimmedLines(oldStr, newStr, options) {
785        options = generateOptions(options, { ignoreWhitespace: true });
786        return lineDiff.diff(oldStr, newStr, options);
787    }
788    // Exported standalone so it can be used from jsonDiff too.
789    function tokenize(value, options) {
790        if (options.stripTrailingCr) {
791            // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
792            value = value.replace(/\r\n/g, '\n');
793        }
794        const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
795        // Ignore the final empty token that occurs if the string ends with a new line
796        if (!linesAndNewlines[linesAndNewlines.length - 1]) {
797            linesAndNewlines.pop();
798        }
799        // Merge the content and line separators into single tokens
800        for (let i = 0; i < linesAndNewlines.length; i++) {
801            const line = linesAndNewlines[i];
802            if (i % 2 && !options.newlineIsToken) {
803                retLines[retLines.length - 1] += line;
804            }
805            else {
806                retLines.push(line);
807            }
808        }
809        return retLines;
810    }
811
812    function isSentenceEndPunct(char) {
813        return char == '.' || char == '!' || char == '?';
814    }
815    class SentenceDiff extends Diff {
816        tokenize(value) {
817            var _a;
818            // If in future we drop support for environments that don't support lookbehinds, we can replace
819            // this entire function with:
820            //     return value.split(/(?<=[.!?])(\s+|$)/);
821            // but until then, for similar reasons to the trailingWs function in string.ts, we are forced
822            // to do this verbosely "by hand" instead of using a regex.
823            const result = [];
824            let tokenStartI = 0;
825            for (let i = 0; i < value.length; i++) {
826                if (i == value.length - 1) {
827                    result.push(value.slice(tokenStartI));
828                    break;
829                }
830                if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
831                    // We've hit a sentence break - i.e. a punctuation mark followed by whitespace.
832                    // We now want to push TWO tokens to the result:
833                    // 1. the sentence
834                    result.push(value.slice(tokenStartI, i + 1));
835                    // 2. the whitespace
836                    i = tokenStartI = i + 1;
837                    while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) {
838                        i++;
839                    }
840                    result.push(value.slice(tokenStartI, i + 1));
841                    // Then the next token (a sentence) starts on the character after the whitespace.
842                    // (It's okay if this is off the end of the string - then the outer loop will terminate
843                    // here anyway.)
844                    tokenStartI = i + 1;
845                }
846            }
847            return result;
848        }
849    }
850    const sentenceDiff = new SentenceDiff();
851    function diffSentences(oldStr, newStr, options) {
852        return sentenceDiff.diff(oldStr, newStr, options);
853    }
854
855    class CssDiff extends Diff {
856        tokenize(value) {
857            return value.split(/([{}:;,]|\s+)/);
858        }
859    }
860    const cssDiff = new CssDiff();
861    function diffCss(oldStr, newStr, options) {
862        return cssDiff.diff(oldStr, newStr, options);
863    }
864
865    class JsonDiff extends Diff {
866        constructor() {
867            super(...arguments);
868            this.tokenize = tokenize;
869        }
870        get useLongestToken() {
871            // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
872            // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
873            return true;
874        }
875        castInput(value, options) {
876            const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v } = options;
877            return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, '  ');
878        }
879        equals(left, right, options) {
880            return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
881        }
882    }
883    const jsonDiff = new JsonDiff();
884    function diffJson(oldStr, newStr, options) {
885        return jsonDiff.diff(oldStr, newStr, options);
886    }
887    // This function handles the presence of circular references by bailing out when encountering an
888    // object that is already on the "stack" of items being processed. Accepts an optional replacer
889    function canonicalize(obj, stack, replacementStack, replacer, key) {
890        stack = stack || [];
891        replacementStack = replacementStack || [];
892        if (replacer) {
893            obj = replacer(key === undefined ? '' : key, obj);
894        }
895        let i;
896        for (i = 0; i < stack.length; i += 1) {
897            if (stack[i] === obj) {
898                return replacementStack[i];
899            }
900        }
901        let canonicalizedObj;
902        if ('[object Array]' === Object.prototype.toString.call(obj)) {
903            stack.push(obj);
904            canonicalizedObj = new Array(obj.length);
905            replacementStack.push(canonicalizedObj);
906            for (i = 0; i < obj.length; i += 1) {
907                canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
908            }
909            stack.pop();
910            replacementStack.pop();
911            return canonicalizedObj;
912        }
913        if (obj && obj.toJSON) {
914            obj = obj.toJSON();
915        }
916        if (typeof obj === 'object' && obj !== null) {
917            stack.push(obj);
918            canonicalizedObj = {};
919            replacementStack.push(canonicalizedObj);
920            const sortedKeys = [];
921            let key;
922            for (key in obj) {
923                /* istanbul ignore else */
924                if (Object.prototype.hasOwnProperty.call(obj, key)) {
925                    sortedKeys.push(key);
926                }
927            }
928            sortedKeys.sort();
929            for (i = 0; i < sortedKeys.length; i += 1) {
930                key = sortedKeys[i];
931                canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
932            }
933            stack.pop();
934            replacementStack.pop();
935        }
936        else {
937            canonicalizedObj = obj;
938        }
939        return canonicalizedObj;
940    }
941
942    class ArrayDiff extends Diff {
943        tokenize(value) {
944            return value.slice();
945        }
946        join(value) {
947            return value;
948        }
949        removeEmpty(value) {
950            return value;
951        }
952    }
953    const arrayDiff = new ArrayDiff();
954    function diffArrays(oldArr, newArr, options) {
955        return arrayDiff.diff(oldArr, newArr, options);
956    }
957
958    function unixToWin(patch) {
959        if (Array.isArray(patch)) {
960            // It would be cleaner if instead of the line below we could just write
961            //     return patch.map(unixToWin)
962            // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will
963            // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the
964            // result would be incompatible with the overload signatures.
965            // See bug report at https://github.com/microsoft/TypeScript/issues/61398.
966            return patch.map(p => unixToWin(p));
967        }
968        return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map(hunk => (Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line, i) => {
969                    var _a;
970                    return (line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')))
971                        ? line
972                        : line + '\r';
973                }) }))) });
974    }
975    function winToUnix(patch) {
976        if (Array.isArray(patch)) {
977            // (See comment above equivalent line in unixToWin)
978            return patch.map(p => winToUnix(p));
979        }
980        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) }))) });
981    }
982    /**
983     * Returns true if the patch consistently uses Unix line endings (or only involves one line and has
984     * no line endings).
985     */
986    function isUnix(patch) {
987        if (!Array.isArray(patch)) {
988            patch = [patch];
989        }
990        return !patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => !line.startsWith('\\') && line.endsWith('\r'))));
991    }
992    /**
993     * Returns true if the patch uses Windows line endings and only Windows line endings.
994     */
995    function isWin(patch) {
996        if (!Array.isArray(patch)) {
997            patch = [patch];
998        }
999        return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r'))))
1000            && 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('\\')); })));
1001    }
1002
1003    /**
1004     * Parses a patch into structured data, in the same structure returned by `structuredPatch`.
1005     *
1006     * @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
1007     */
1008    function parsePatch(uniDiff) {
1009        const diffstr = uniDiff.split(/\n/), list = [];
1010        let i = 0;
1011        function parseIndex() {
1012            const index = {};
1013            list.push(index);
1014            // Parse diff metadata
1015            while (i < diffstr.length) {
1016                const line = diffstr[i];
1017                // File header found, end parsing diff metadata
1018                if ((/^(---|\+\+\+|@@)\s/).test(line)) {
1019                    break;
1020                }
1021                // Try to parse the line as a diff header, like
1022                //     Index: README.md
1023                // or
1024                //     diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
1025                // or
1026                //     Index: something with multiple words
1027                // and extract the filename (or whatever else is used as an index name)
1028                // from the end (i.e. 'README.md', '.hgignore', or
1029                // 'something with multiple words' in the examples above).
1030                //
1031                // TODO: It seems awkward that we indiscriminately trim off trailing
1032                //       whitespace here. Theoretically, couldn't that be meaningful -
1033                //       e.g. if the patch represents a diff of a file whose name ends
1034                //       with a space? Seems wrong to nuke it.
1035                //       But this behaviour has been around since v2.2.1 in 2015, so if
1036                //       it's going to change, it should be done cautiously and in a new
1037                //       major release, for backwards-compat reasons.
1038                //       -- ExplodingCabbage
1039                const headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
1040                if (headerMatch) {
1041                    index.index = line.substring(headerMatch[0].length).trim();
1042                }
1043                i++;
1044            }
1045            // Parse file headers if they are defined. Unified diff requires them, but
1046            // there's no technical issues to have an isolated hunk without file header
1047            parseFileHeader(index);
1048            parseFileHeader(index);
1049            // Parse hunks
1050            index.hunks = [];
1051            while (i < diffstr.length) {
1052                const line = diffstr[i];
1053                if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
1054                    break;
1055                }
1056                else if ((/^@@/).test(line)) {
1057                    index.hunks.push(parseHunk());
1058                }
1059                else if (line) {
1060                    throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
1061                }
1062                else {
1063                    i++;
1064                }
1065            }
1066        }
1067        // Parses the --- and +++ headers, if none are found, no lines
1068        // are consumed.
1069        function parseFileHeader(index) {
1070            const fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
1071            if (fileHeaderMatch) {
1072                const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
1073                let fileName = data[0].replace(/\\\\/g, '\\');
1074                if (fileName.startsWith('"') && fileName.endsWith('"')) {
1075                    fileName = fileName.substr(1, fileName.length - 2);
1076                }
1077                if (prefix === '---') {
1078                    index.oldFileName = fileName;
1079                    index.oldHeader = header;
1080                }
1081                else {
1082                    index.newFileName = fileName;
1083                    index.newHeader = header;
1084                }
1085                i++;
1086            }
1087        }
1088        // Parses a hunk
1089        // This assumes that we are at the start of a hunk.
1090        function parseHunk() {
1091            var _a;
1092            const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
1093            const hunk = {
1094                oldStart: +chunkHeader[1],
1095                oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
1096                newStart: +chunkHeader[3],
1097                newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
1098                lines: []
1099            };
1100            // Unified Diff Format quirk: If the chunk size is 0,
1101            // the first number is one lower than one would expect.
1102            // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
1103            if (hunk.oldLines === 0) {
1104                hunk.oldStart += 1;
1105            }
1106            if (hunk.newLines === 0) {
1107                hunk.newStart += 1;
1108            }
1109            let addCount = 0, removeCount = 0;
1110            for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
1111                const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
1112                if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
1113                    hunk.lines.push(diffstr[i]);
1114                    if (operation === '+') {
1115                        addCount++;
1116                    }
1117                    else if (operation === '-') {
1118                        removeCount++;
1119                    }
1120                    else if (operation === ' ') {
1121                        addCount++;
1122                        removeCount++;
1123                    }
1124                }
1125                else {
1126                    throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
1127                }
1128            }
1129            // Handle the empty block count case
1130            if (!addCount && hunk.newLines === 1) {
1131                hunk.newLines = 0;
1132            }
1133            if (!removeCount && hunk.oldLines === 1) {
1134                hunk.oldLines = 0;
1135            }
1136            // Perform sanity checking
1137            if (addCount !== hunk.newLines) {
1138                throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
1139            }
1140            if (removeCount !== hunk.oldLines) {
1141                throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
1142            }
1143            return hunk;
1144        }
1145        while (i < diffstr.length) {
1146            parseIndex();
1147        }
1148        return list;
1149    }
1150
1151    // Iterator that traverses in the range of [min, max], stepping
1152    // by distance from a given start position. I.e. for [0, 4], with
1153    // start of 2, this will iterate 2, 3, 1, 4, 0.
1154    function distanceIterator (start, minLine, maxLine) {
1155        let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
1156        return function iterator() {
1157            if (wantForward && !forwardExhausted) {
1158                if (backwardExhausted) {
1159                    localOffset++;
1160                }
1161                else {
1162                    wantForward = false;
1163                }
1164                // Check if trying to fit beyond text length, and if not, check it fits
1165                // after offset location (or desired location on first iteration)
1166                if (start + localOffset <= maxLine) {
1167                    return start + localOffset;
1168                }
1169                forwardExhausted = true;
1170            }
1171            if (!backwardExhausted) {
1172                if (!forwardExhausted) {
1173                    wantForward = true;
1174                }
1175                // Check if trying to fit before text beginning, and if not, check it fits
1176                // before offset location
1177                if (minLine <= start - localOffset) {
1178                    return start - localOffset++;
1179                }
1180                backwardExhausted = true;
1181                return iterator();
1182            }
1183            // We tried to fit hunk before text beginning and beyond text length, then
1184            // hunk can't fit on the text. Return undefined
1185            return undefined;
1186        };
1187    }
1188
1189    /**
1190     * attempts to apply a unified diff patch.
1191     *
1192     * Hunks are applied first to last.
1193     * `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly.
1194     * 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.
1195     * 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.
1196     * 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.
1197     *
1198     * Once a hunk is successfully fitted, the process begins again with the next hunk.
1199     * Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks.
1200     *

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

codekingpro/portable-devtools · Team Ai