blob: 03571caa7e9e67c9e8a49dce35c1c62802718f0c [file] [log] [blame]
Samuel Shuert274a4d62023-12-01 15:04:55 -05001/*!
2
3 diff v4.0.1
4
5Software License Agreement (BSD License)
6
7Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms, with or without modification,
12are permitted provided that the following conditions are met:
13
14* Redistributions of source code must retain the above
15 copyright notice, this list of conditions and the
16 following disclaimer.
17
18* Redistributions in binary form must reproduce the above
19 copyright notice, this list of conditions and the
20 following disclaimer in the documentation and/or other
21 materials provided with the distribution.
22
23* Neither the name of Kevin Decker nor the names of its
24 contributors may be used to endorse or promote products
25 derived from this software without specific prior
26 written permission.
27
28THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
29IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
30FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
31CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
34IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
35OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36@license
37*/
38(function (global, factory) {
39 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
40 typeof define === 'function' && define.amd ? define(['exports'], factory) :
41 (global = global || self, factory(global.Diff = {}));
42}(this, function (exports) { 'use strict';
43
44 function Diff() {}
45 Diff.prototype = {
46 diff: function diff(oldString, newString) {
47 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
48 var callback = options.callback;
49
50 if (typeof options === 'function') {
51 callback = options;
52 options = {};
53 }
54
55 this.options = options;
56 var self = this;
57
58 function done(value) {
59 if (callback) {
60 setTimeout(function () {
61 callback(undefined, value);
62 }, 0);
63 return true;
64 } else {
65 return value;
66 }
67 } // Allow subclasses to massage the input prior to running
68
69
70 oldString = this.castInput(oldString);
71 newString = this.castInput(newString);
72 oldString = this.removeEmpty(this.tokenize(oldString));
73 newString = this.removeEmpty(this.tokenize(newString));
74 var newLen = newString.length,
75 oldLen = oldString.length;
76 var editLength = 1;
77 var maxEditLength = newLen + oldLen;
78 var bestPath = [{
79 newPos: -1,
80 components: []
81 }]; // Seed editLength = 0, i.e. the content starts with the same values
82
83 var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
84
85 if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
86 // Identity per the equality and tokenizer
87 return done([{
88 value: this.join(newString),
89 count: newString.length
90 }]);
91 } // Main worker method. checks all permutations of a given edit length for acceptance.
92
93
94 function execEditLength() {
95 for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
96 var basePath = void 0;
97
98 var addPath = bestPath[diagonalPath - 1],
99 removePath = bestPath[diagonalPath + 1],
100 _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
101
102 if (addPath) {
103 // No one else is going to attempt to use this value, clear it
104 bestPath[diagonalPath - 1] = undefined;
105 }
106
107 var canAdd = addPath && addPath.newPos + 1 < newLen,
108 canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen;
109
110 if (!canAdd && !canRemove) {
111 // If this path is a terminal then prune
112 bestPath[diagonalPath] = undefined;
113 continue;
114 } // Select the diagonal that we want to branch from. We select the prior
115 // path whose position in the new string is the farthest from the origin
116 // and does not pass the bounds of the diff graph
117
118
119 if (!canAdd || canRemove && addPath.newPos < removePath.newPos) {
120 basePath = clonePath(removePath);
121 self.pushComponent(basePath.components, undefined, true);
122 } else {
123 basePath = addPath; // No need to clone, we've pulled it from the list
124
125 basePath.newPos++;
126 self.pushComponent(basePath.components, true, undefined);
127 }
128
129 _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done
130
131 if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) {
132 return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken));
133 } else {
134 // Otherwise track this path as a potential candidate and continue.
135 bestPath[diagonalPath] = basePath;
136 }
137 }
138
139 editLength++;
140 } // Performs the length of edit iteration. Is a bit fugly as this has to support the
141 // sync and async mode which is never fun. Loops over execEditLength until a value
142 // is produced.
143
144
145 if (callback) {
146 (function exec() {
147 setTimeout(function () {
148 // This should not happen, but we want to be safe.
149
150 /* istanbul ignore next */
151 if (editLength > maxEditLength) {
152 return callback();
153 }
154
155 if (!execEditLength()) {
156 exec();
157 }
158 }, 0);
159 })();
160 } else {
161 while (editLength <= maxEditLength) {
162 var ret = execEditLength();
163
164 if (ret) {
165 return ret;
166 }
167 }
168 }
169 },
170 pushComponent: function pushComponent(components, added, removed) {
171 var last = components[components.length - 1];
172
173 if (last && last.added === added && last.removed === removed) {
174 // We need to clone here as the component clone operation is just
175 // as shallow array clone
176 components[components.length - 1] = {
177 count: last.count + 1,
178 added: added,
179 removed: removed
180 };
181 } else {
182 components.push({
183 count: 1,
184 added: added,
185 removed: removed
186 });
187 }
188 },
189 extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) {
190 var newLen = newString.length,
191 oldLen = oldString.length,
192 newPos = basePath.newPos,
193 oldPos = newPos - diagonalPath,
194 commonCount = 0;
195
196 while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
197 newPos++;
198 oldPos++;
199 commonCount++;
200 }
201
202 if (commonCount) {
203 basePath.components.push({
204 count: commonCount
205 });
206 }
207
208 basePath.newPos = newPos;
209 return oldPos;
210 },
211 equals: function equals(left, right) {
212 if (this.options.comparator) {
213 return this.options.comparator(left, right);
214 } else {
215 return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
216 }
217 },
218 removeEmpty: function removeEmpty(array) {
219 var ret = [];
220
221 for (var i = 0; i < array.length; i++) {
222 if (array[i]) {
223 ret.push(array[i]);
224 }
225 }
226
227 return ret;
228 },
229 castInput: function castInput(value) {
230 return value;
231 },
232 tokenize: function tokenize(value) {
233 return value.split('');
234 },
235 join: function join(chars) {
236 return chars.join('');
237 }
238 };
239
240 function buildValues(diff, components, newString, oldString, useLongestToken) {
241 var componentPos = 0,
242 componentLen = components.length,
243 newPos = 0,
244 oldPos = 0;
245
246 for (; componentPos < componentLen; componentPos++) {
247 var component = components[componentPos];
248
249 if (!component.removed) {
250 if (!component.added && useLongestToken) {
251 var value = newString.slice(newPos, newPos + component.count);
252 value = value.map(function (value, i) {
253 var oldValue = oldString[oldPos + i];
254 return oldValue.length > value.length ? oldValue : value;
255 });
256 component.value = diff.join(value);
257 } else {
258 component.value = diff.join(newString.slice(newPos, newPos + component.count));
259 }
260
261 newPos += component.count; // Common case
262
263 if (!component.added) {
264 oldPos += component.count;
265 }
266 } else {
267 component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
268 oldPos += component.count; // Reverse add and remove so removes are output first to match common convention
269 // The diffing algorithm is tied to add then remove output and this is the simplest
270 // route to get the desired output with minimal overhead.
271
272 if (componentPos && components[componentPos - 1].added) {
273 var tmp = components[componentPos - 1];
274 components[componentPos - 1] = components[componentPos];
275 components[componentPos] = tmp;
276 }
277 }
278 } // Special case handle for when one terminal is ignored (i.e. whitespace).
279 // For this case we merge the terminal into the prior string and drop the change.
280 // This is only available for string mode.
281
282
283 var lastComponent = components[componentLen - 1];
284
285 if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) {
286 components[componentLen - 2].value += lastComponent.value;
287 components.pop();
288 }
289
290 return components;
291 }
292
293 function clonePath(path) {
294 return {
295 newPos: path.newPos,
296 components: path.components.slice(0)
297 };
298 }
299
300 var characterDiff = new Diff();
301 function diffChars(oldStr, newStr, options) {
302 return characterDiff.diff(oldStr, newStr, options);
303 }
304
305 function generateOptions(options, defaults) {
306 if (typeof options === 'function') {
307 defaults.callback = options;
308 } else if (options) {
309 for (var name in options) {
310 /* istanbul ignore else */
311 if (options.hasOwnProperty(name)) {
312 defaults[name] = options[name];
313 }
314 }
315 }
316
317 return defaults;
318 }
319
320 //
321 // Ranges and exceptions:
322 // Latin-1 Supplement, 0080–00FF
323 // - U+00D7 × Multiplication sign
324 // - U+00F7 ÷ Division sign
325 // Latin Extended-A, 0100–017F
326 // Latin Extended-B, 0180–024F
327 // IPA Extensions, 0250–02AF
328 // Spacing Modifier Letters, 02B0–02FF
329 // - U+02C7 ˇ &#711; Caron
330 // - U+02D8 ˘ &#728; Breve
331 // - U+02D9 ˙ &#729; Dot Above
332 // - U+02DA ˚ &#730; Ring Above
333 // - U+02DB ˛ &#731; Ogonek
334 // - U+02DC ˜ &#732; Small Tilde
335 // - U+02DD ˝ &#733; Double Acute Accent
336 // Latin Extended Additional, 1E00–1EFF
337
338 var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/;
339 var reWhitespace = /\S/;
340 var wordDiff = new Diff();
341
342 wordDiff.equals = function (left, right) {
343 if (this.options.ignoreCase) {
344 left = left.toLowerCase();
345 right = right.toLowerCase();
346 }
347
348 return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right);
349 };
350
351 wordDiff.tokenize = function (value) {
352 var tokens = value.split(/(\s+|[()[\]{}'"]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set.
353
354 for (var i = 0; i < tokens.length - 1; i++) {
355 // If we have an empty string in the next field and we have only word chars before and after, merge
356 if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) {
357 tokens[i] += tokens[i + 2];
358 tokens.splice(i + 1, 2);
359 i--;
360 }
361 }
362
363 return tokens;
364 };
365
366 function diffWords(oldStr, newStr, options) {
367 options = generateOptions(options, {
368 ignoreWhitespace: true
369 });
370 return wordDiff.diff(oldStr, newStr, options);
371 }
372 function diffWordsWithSpace(oldStr, newStr, options) {
373 return wordDiff.diff(oldStr, newStr, options);
374 }
375
376 var lineDiff = new Diff();
377
378 lineDiff.tokenize = function (value) {
379 var retLines = [],
380 linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line
381
382 if (!linesAndNewlines[linesAndNewlines.length - 1]) {
383 linesAndNewlines.pop();
384 } // Merge the content and line separators into single tokens
385
386
387 for (var i = 0; i < linesAndNewlines.length; i++) {
388 var line = linesAndNewlines[i];
389
390 if (i % 2 && !this.options.newlineIsToken) {
391 retLines[retLines.length - 1] += line;
392 } else {
393 if (this.options.ignoreWhitespace) {
394 line = line.trim();
395 }
396
397 retLines.push(line);
398 }
399 }
400
401 return retLines;
402 };
403
404 function diffLines(oldStr, newStr, callback) {
405 return lineDiff.diff(oldStr, newStr, callback);
406 }
407 function diffTrimmedLines(oldStr, newStr, callback) {
408 var options = generateOptions(callback, {
409 ignoreWhitespace: true
410 });
411 return lineDiff.diff(oldStr, newStr, options);
412 }
413
414 var sentenceDiff = new Diff();
415
416 sentenceDiff.tokenize = function (value) {
417 return value.split(/(\S.+?[.!?])(?=\s+|$)/);
418 };
419
420 function diffSentences(oldStr, newStr, callback) {
421 return sentenceDiff.diff(oldStr, newStr, callback);
422 }
423
424 var cssDiff = new Diff();
425
426 cssDiff.tokenize = function (value) {
427 return value.split(/([{}:;,]|\s+)/);
428 };
429
430 function diffCss(oldStr, newStr, callback) {
431 return cssDiff.diff(oldStr, newStr, callback);
432 }
433
434 function _typeof(obj) {
435 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
436 _typeof = function (obj) {
437 return typeof obj;
438 };
439 } else {
440 _typeof = function (obj) {
441 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
442 };
443 }
444
445 return _typeof(obj);
446 }
447
448 function _toConsumableArray(arr) {
449 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
450 }
451
452 function _arrayWithoutHoles(arr) {
453 if (Array.isArray(arr)) {
454 for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
455
456 return arr2;
457 }
458 }
459
460 function _iterableToArray(iter) {
461 if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
462 }
463
464 function _nonIterableSpread() {
465 throw new TypeError("Invalid attempt to spread non-iterable instance");
466 }
467
468 var objectPrototypeToString = Object.prototype.toString;
469 var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
470 // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
471
472 jsonDiff.useLongestToken = true;
473 jsonDiff.tokenize = lineDiff.tokenize;
474
475 jsonDiff.castInput = function (value) {
476 var _this$options = this.options,
477 undefinedReplacement = _this$options.undefinedReplacement,
478 _this$options$stringi = _this$options.stringifyReplacer,
479 stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) {
480 return typeof v === 'undefined' ? undefinedReplacement : v;
481 } : _this$options$stringi;
482 return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
483 };
484
485 jsonDiff.equals = function (left, right) {
486 return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'));
487 };
488
489 function diffJson(oldObj, newObj, options) {
490 return jsonDiff.diff(oldObj, newObj, options);
491 } // This function handles the presence of circular references by bailing out when encountering an
492 // object that is already on the "stack" of items being processed. Accepts an optional replacer
493
494 function canonicalize(obj, stack, replacementStack, replacer, key) {
495 stack = stack || [];
496 replacementStack = replacementStack || [];
497
498 if (replacer) {
499 obj = replacer(key, obj);
500 }
501
502 var i;
503
504 for (i = 0; i < stack.length; i += 1) {
505 if (stack[i] === obj) {
506 return replacementStack[i];
507 }
508 }
509
510 var canonicalizedObj;
511
512 if ('[object Array]' === objectPrototypeToString.call(obj)) {
513 stack.push(obj);
514 canonicalizedObj = new Array(obj.length);
515 replacementStack.push(canonicalizedObj);
516
517 for (i = 0; i < obj.length; i += 1) {
518 canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
519 }
520
521 stack.pop();
522 replacementStack.pop();
523 return canonicalizedObj;
524 }
525
526 if (obj && obj.toJSON) {
527 obj = obj.toJSON();
528 }
529
530 if (_typeof(obj) === 'object' && obj !== null) {
531 stack.push(obj);
532 canonicalizedObj = {};
533 replacementStack.push(canonicalizedObj);
534
535 var sortedKeys = [],
536 _key;
537
538 for (_key in obj) {
539 /* istanbul ignore else */
540 if (obj.hasOwnProperty(_key)) {
541 sortedKeys.push(_key);
542 }
543 }
544
545 sortedKeys.sort();
546
547 for (i = 0; i < sortedKeys.length; i += 1) {
548 _key = sortedKeys[i];
549 canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
550 }
551
552 stack.pop();
553 replacementStack.pop();
554 } else {
555 canonicalizedObj = obj;
556 }
557
558 return canonicalizedObj;
559 }
560
561 var arrayDiff = new Diff();
562
563 arrayDiff.tokenize = function (value) {
564 return value.slice();
565 };
566
567 arrayDiff.join = arrayDiff.removeEmpty = function (value) {
568 return value;
569 };
570
571 function diffArrays(oldArr, newArr, callback) {
572 return arrayDiff.diff(oldArr, newArr, callback);
573 }
574
575 function parsePatch(uniDiff) {
576 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
577 var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/),
578 delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [],
579 list = [],
580 i = 0;
581
582 function parseIndex() {
583 var index = {};
584 list.push(index); // Parse diff metadata
585
586 while (i < diffstr.length) {
587 var line = diffstr[i]; // File header found, end parsing diff metadata
588
589 if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) {
590 break;
591 } // Diff index
592
593
594 var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line);
595
596 if (header) {
597 index.index = header[1];
598 }
599
600 i++;
601 } // Parse file headers if they are defined. Unified diff requires them, but
602 // there's no technical issues to have an isolated hunk without file header
603
604
605 parseFileHeader(index);
606 parseFileHeader(index); // Parse hunks
607
608 index.hunks = [];
609
610 while (i < diffstr.length) {
611 var _line = diffstr[i];
612
613 if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) {
614 break;
615 } else if (/^@@/.test(_line)) {
616 index.hunks.push(parseHunk());
617 } else if (_line && options.strict) {
618 // Ignore unexpected content unless in strict mode
619 throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line));
620 } else {
621 i++;
622 }
623 }
624 } // Parses the --- and +++ headers, if none are found, no lines
625 // are consumed.
626
627
628 function parseFileHeader(index) {
629 var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]);
630
631 if (fileHeader) {
632 var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new';
633 var data = fileHeader[2].split('\t', 2);
634 var fileName = data[0].replace(/\\\\/g, '\\');
635
636 if (/^".*"$/.test(fileName)) {
637 fileName = fileName.substr(1, fileName.length - 2);
638 }
639
640 index[keyPrefix + 'FileName'] = fileName;
641 index[keyPrefix + 'Header'] = (data[1] || '').trim();
642 i++;
643 }
644 } // Parses a hunk
645 // This assumes that we are at the start of a hunk.
646
647
648 function parseHunk() {
649 var chunkHeaderIndex = i,
650 chunkHeaderLine = diffstr[i++],
651 chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
652 var hunk = {
653 oldStart: +chunkHeader[1],
654 oldLines: +chunkHeader[2] || 1,
655 newStart: +chunkHeader[3],
656 newLines: +chunkHeader[4] || 1,
657 lines: [],
658 linedelimiters: []
659 };
660 var addCount = 0,
661 removeCount = 0;
662
663 for (; i < diffstr.length; i++) {
664 // Lines starting with '---' could be mistaken for the "remove line" operation
665 // But they could be the header for the next file. Therefore prune such cases out.
666 if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) {
667 break;
668 }
669
670 var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0];
671
672 if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
673 hunk.lines.push(diffstr[i]);
674 hunk.linedelimiters.push(delimiters[i] || '\n');
675
676 if (operation === '+') {
677 addCount++;
678 } else if (operation === '-') {
679 removeCount++;
680 } else if (operation === ' ') {
681 addCount++;
682 removeCount++;
683 }
684 } else {
685 break;
686 }
687 } // Handle the empty block count case
688
689
690 if (!addCount && hunk.newLines === 1) {
691 hunk.newLines = 0;
692 }
693
694 if (!removeCount && hunk.oldLines === 1) {
695 hunk.oldLines = 0;
696 } // Perform optional sanity checking
697
698
699 if (options.strict) {
700 if (addCount !== hunk.newLines) {
701 throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
702 }
703
704 if (removeCount !== hunk.oldLines) {
705 throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
706 }
707 }
708
709 return hunk;
710 }
711
712 while (i < diffstr.length) {
713 parseIndex();
714 }
715
716 return list;
717 }
718
719 // Iterator that traverses in the range of [min, max], stepping
720 // by distance from a given start position. I.e. for [0, 4], with
721 // start of 2, this will iterate 2, 3, 1, 4, 0.
722 function distanceIterator (start, minLine, maxLine) {
723 var wantForward = true,
724 backwardExhausted = false,
725 forwardExhausted = false,
726 localOffset = 1;
727 return function iterator() {
728 if (wantForward && !forwardExhausted) {
729 if (backwardExhausted) {
730 localOffset++;
731 } else {
732 wantForward = false;
733 } // Check if trying to fit beyond text length, and if not, check it fits
734 // after offset location (or desired location on first iteration)
735
736
737 if (start + localOffset <= maxLine) {
738 return localOffset;
739 }
740
741 forwardExhausted = true;
742 }
743
744 if (!backwardExhausted) {
745 if (!forwardExhausted) {
746 wantForward = true;
747 } // Check if trying to fit before text beginning, and if not, check it fits
748 // before offset location
749
750
751 if (minLine <= start - localOffset) {
752 return -localOffset++;
753 }
754
755 backwardExhausted = true;
756 return iterator();
757 } // We tried to fit hunk before text beginning and beyond text length, then
758 // hunk can't fit on the text. Return undefined
759
760 };
761 }
762
763 function applyPatch(source, uniDiff) {
764 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
765
766 if (typeof uniDiff === 'string') {
767 uniDiff = parsePatch(uniDiff);
768 }
769
770 if (Array.isArray(uniDiff)) {
771 if (uniDiff.length > 1) {
772 throw new Error('applyPatch only works with a single input.');
773 }
774
775 uniDiff = uniDiff[0];
776 } // Apply the diff to the input
777
778
779 var lines = source.split(/\r\n|[\n\v\f\r\x85]/),
780 delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [],
781 hunks = uniDiff.hunks,
782 compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) {
783 return line === patchContent;
784 },
785 errorCount = 0,
786 fuzzFactor = options.fuzzFactor || 0,
787 minLine = 0,
788 offset = 0,
789 removeEOFNL,
790 addEOFNL;
791 /**
792 * Checks if the hunk exactly fits on the provided location
793 */
794
795
796 function hunkFits(hunk, toPos) {
797 for (var j = 0; j < hunk.lines.length; j++) {
798 var line = hunk.lines[j],
799 operation = line.length > 0 ? line[0] : ' ',
800 content = line.length > 0 ? line.substr(1) : line;
801
802 if (operation === ' ' || operation === '-') {
803 // Context sanity check
804 if (!compareLine(toPos + 1, lines[toPos], operation, content)) {
805 errorCount++;
806
807 if (errorCount > fuzzFactor) {
808 return false;
809 }
810 }
811
812 toPos++;
813 }
814 }
815
816 return true;
817 } // Search best fit offsets for each hunk based on the previous ones
818
819
820 for (var i = 0; i < hunks.length; i++) {
821 var hunk = hunks[i],
822 maxLine = lines.length - hunk.oldLines,
823 localOffset = 0,
824 toPos = offset + hunk.oldStart - 1;
825 var iterator = distanceIterator(toPos, minLine, maxLine);
826
827 for (; localOffset !== undefined; localOffset = iterator()) {
828 if (hunkFits(hunk, toPos + localOffset)) {
829 hunk.offset = offset += localOffset;
830 break;
831 }
832 }
833
834 if (localOffset === undefined) {
835 return false;
836 } // Set lower text limit to end of the current hunk, so next ones don't try
837 // to fit over already patched text
838
839
840 minLine = hunk.offset + hunk.oldStart + hunk.oldLines;
841 } // Apply patch hunks
842
843
844 var diffOffset = 0;
845
846 for (var _i = 0; _i < hunks.length; _i++) {
847 var _hunk = hunks[_i],
848 _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1;
849
850 diffOffset += _hunk.newLines - _hunk.oldLines;
851
852 if (_toPos < 0) {
853 // Creating a new file
854 _toPos = 0;
855 }
856
857 for (var j = 0; j < _hunk.lines.length; j++) {
858 var line = _hunk.lines[j],
859 operation = line.length > 0 ? line[0] : ' ',
860 content = line.length > 0 ? line.substr(1) : line,
861 delimiter = _hunk.linedelimiters[j];
862
863 if (operation === ' ') {
864 _toPos++;
865 } else if (operation === '-') {
866 lines.splice(_toPos, 1);
867 delimiters.splice(_toPos, 1);
868 /* istanbul ignore else */
869 } else if (operation === '+') {
870 lines.splice(_toPos, 0, content);
871 delimiters.splice(_toPos, 0, delimiter);
872 _toPos++;
873 } else if (operation === '\\') {
874 var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null;
875
876 if (previousOperation === '+') {
877 removeEOFNL = true;
878 } else if (previousOperation === '-') {
879 addEOFNL = true;
880 }
881 }
882 }
883 } // Handle EOFNL insertion/removal
884
885
886 if (removeEOFNL) {
887 while (!lines[lines.length - 1]) {
888 lines.pop();
889 delimiters.pop();
890 }
891 } else if (addEOFNL) {
892 lines.push('');
893 delimiters.push('\n');
894 }
895
896 for (var _k = 0; _k < lines.length - 1; _k++) {
897 lines[_k] = lines[_k] + delimiters[_k];
898 }
899
900 return lines.join('');
901 } // Wrapper that supports multiple file patches via callbacks.
902
903 function applyPatches(uniDiff, options) {
904 if (typeof uniDiff === 'string') {
905 uniDiff = parsePatch(uniDiff);
906 }
907
908 var currentIndex = 0;
909
910 function processIndex() {
911 var index = uniDiff[currentIndex++];
912
913 if (!index) {
914 return options.complete();
915 }
916
917 options.loadFile(index, function (err, data) {
918 if (err) {
919 return options.complete(err);
920 }
921
922 var updatedContent = applyPatch(data, index, options);
923 options.patched(index, updatedContent, function (err) {
924 if (err) {
925 return options.complete(err);
926 }
927
928 processIndex();
929 });
930 });
931 }
932
933 processIndex();
934 }
935
936 function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
937 if (!options) {
938 options = {};
939 }
940
941 if (typeof options.context === 'undefined') {
942 options.context = 4;
943 }
944
945 var diff = diffLines(oldStr, newStr, options);
946 diff.push({
947 value: '',
948 lines: []
949 }); // Append an empty value to make cleanup easier
950
951 function contextLines(lines) {
952 return lines.map(function (entry) {
953 return ' ' + entry;
954 });
955 }
956
957 var hunks = [];
958 var oldRangeStart = 0,
959 newRangeStart = 0,
960 curRange = [],
961 oldLine = 1,
962 newLine = 1;
963
964 var _loop = function _loop(i) {
965 var current = diff[i],
966 lines = current.lines || current.value.replace(/\n$/, '').split('\n');
967 current.lines = lines;
968
969 if (current.added || current.removed) {
970 var _curRange;
971
972 // If we have previous context, start with that
973 if (!oldRangeStart) {
974 var prev = diff[i - 1];
975 oldRangeStart = oldLine;
976 newRangeStart = newLine;
977
978 if (prev) {
979 curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : [];
980 oldRangeStart -= curRange.length;
981 newRangeStart -= curRange.length;
982 }
983 } // Output our changes
984
985
986 (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) {
987 return (current.added ? '+' : '-') + entry;
988 }))); // Track the updated file position
989
990
991 if (current.added) {
992 newLine += lines.length;
993 } else {
994 oldLine += lines.length;
995 }
996 } else {
997 // Identical context lines. Track line changes
998 if (oldRangeStart) {
999 // Close out any changes that have been output (or join overlapping)
1000 if (lines.length <= options.context * 2 && i < diff.length - 2) {
1001 var _curRange2;
1002
1003 // Overlapping
1004 (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines)));
1005 } else {
1006 var _curRange3;
1007
1008 // end the range and output
1009 var contextSize = Math.min(lines.length, options.context);
1010
1011 (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize))));
1012
1013 var hunk = {
1014 oldStart: oldRangeStart,
1015 oldLines: oldLine - oldRangeStart + contextSize,
1016 newStart: newRangeStart,
1017 newLines: newLine - newRangeStart + contextSize,
1018 lines: curRange
1019 };
1020
1021 if (i >= diff.length - 2 && lines.length <= options.context) {
1022 // EOF is inside this hunk
1023 var oldEOFNewline = /\n$/.test(oldStr);
1024 var newEOFNewline = /\n$/.test(newStr);
1025 var noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines;
1026
1027 if (!oldEOFNewline && noNlBeforeAdds) {
1028 // special case: old has no eol and no trailing context; no-nl can end up before adds
1029 curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file');
1030 }
1031
1032 if (!oldEOFNewline && !noNlBeforeAdds || !newEOFNewline) {
1033 curRange.push('\\ No newline at end of file');
1034 }
1035 }
1036
1037 hunks.push(hunk);
1038 oldRangeStart = 0;
1039 newRangeStart = 0;
1040 curRange = [];
1041 }
1042 }
1043
1044 oldLine += lines.length;
1045 newLine += lines.length;
1046 }
1047 };
1048
1049 for (var i = 0; i < diff.length; i++) {
1050 _loop(i);
1051 }
1052
1053 return {
1054 oldFileName: oldFileName,
1055 newFileName: newFileName,
1056 oldHeader: oldHeader,
1057 newHeader: newHeader,
1058 hunks: hunks
1059 };
1060 }
1061 function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
1062 var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
1063 var ret = [];
1064
1065 if (oldFileName == newFileName) {
1066 ret.push('Index: ' + oldFileName);
1067 }
1068
1069 ret.push('===================================================================');
1070 ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader));
1071 ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader));
1072
1073 for (var i = 0; i < diff.hunks.length; i++) {
1074 var hunk = diff.hunks[i];
1075 ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@');
1076 ret.push.apply(ret, hunk.lines);
1077 }
1078
1079 return ret.join('\n') + '\n';
1080 }
1081 function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
1082 return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
1083 }
1084
1085 function arrayEqual(a, b) {
1086 if (a.length !== b.length) {
1087 return false;
1088 }
1089
1090 return arrayStartsWith(a, b);
1091 }
1092 function arrayStartsWith(array, start) {
1093 if (start.length > array.length) {
1094 return false;
1095 }
1096
1097 for (var i = 0; i < start.length; i++) {
1098 if (start[i] !== array[i]) {
1099 return false;
1100 }
1101 }
1102
1103 return true;
1104 }
1105
1106 function calcLineCount(hunk) {
1107 var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines),
1108 oldLines = _calcOldNewLineCount.oldLines,
1109 newLines = _calcOldNewLineCount.newLines;
1110
1111 if (oldLines !== undefined) {
1112 hunk.oldLines = oldLines;
1113 } else {
1114 delete hunk.oldLines;
1115 }
1116
1117 if (newLines !== undefined) {
1118 hunk.newLines = newLines;
1119 } else {
1120 delete hunk.newLines;
1121 }
1122 }
1123 function merge(mine, theirs, base) {
1124 mine = loadPatch(mine, base);
1125 theirs = loadPatch(theirs, base);
1126 var ret = {}; // For index we just let it pass through as it doesn't have any necessary meaning.
1127 // Leaving sanity checks on this to the API consumer that may know more about the
1128 // meaning in their own context.
1129
1130 if (mine.index || theirs.index) {
1131 ret.index = mine.index || theirs.index;
1132 }
1133
1134 if (mine.newFileName || theirs.newFileName) {
1135 if (!fileNameChanged(mine)) {
1136 // No header or no change in ours, use theirs (and ours if theirs does not exist)
1137 ret.oldFileName = theirs.oldFileName || mine.oldFileName;
1138 ret.newFileName = theirs.newFileName || mine.newFileName;
1139 ret.oldHeader = theirs.oldHeader || mine.oldHeader;
1140 ret.newHeader = theirs.newHeader || mine.newHeader;
1141 } else if (!fileNameChanged(theirs)) {
1142 // No header or no change in theirs, use ours
1143 ret.oldFileName = mine.oldFileName;
1144 ret.newFileName = mine.newFileName;
1145 ret.oldHeader = mine.oldHeader;
1146 ret.newHeader = mine.newHeader;
1147 } else {
1148 // Both changed... figure it out
1149 ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName);
1150 ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName);
1151 ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader);
1152 ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader);
1153 }
1154 }
1155
1156 ret.hunks = [];
1157 var mineIndex = 0,
1158 theirsIndex = 0,
1159 mineOffset = 0,
1160 theirsOffset = 0;
1161
1162 while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) {
1163 var mineCurrent = mine.hunks[mineIndex] || {
1164 oldStart: Infinity
1165 },
1166 theirsCurrent = theirs.hunks[theirsIndex] || {
1167 oldStart: Infinity
1168 };
1169
1170 if (hunkBefore(mineCurrent, theirsCurrent)) {
1171 // This patch does not overlap with any of the others, yay.
1172 ret.hunks.push(cloneHunk(mineCurrent, mineOffset));
1173 mineIndex++;
1174 theirsOffset += mineCurrent.newLines - mineCurrent.oldLines;
1175 } else if (hunkBefore(theirsCurrent, mineCurrent)) {
1176 // This patch does not overlap with any of the others, yay.
1177 ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset));
1178 theirsIndex++;
1179 mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines;
1180 } else {
1181 // Overlap, merge as best we can
1182 var mergedHunk = {
1183 oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart),
1184 oldLines: 0,
1185 newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset),
1186 newLines: 0,
1187 lines: []
1188 };
1189 mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines);
1190 theirsIndex++;
1191 mineIndex++;
1192 ret.hunks.push(mergedHunk);
1193 }
1194 }
1195
1196 return ret;
1197 }
1198
1199 function loadPatch(param, base) {
1200 if (typeof param === 'string') {
1201 if (/^@@/m.test(param) || /^Index:/m.test(param)) {
1202 return parsePatch(param)[0];
1203 }
1204
1205 if (!base) {
1206 throw new Error('Must provide a base reference or pass in a patch');
1207 }
1208
1209 return structuredPatch(undefined, undefined, base, param);
1210 }
1211
1212 return param;
1213 }
1214
1215 function fileNameChanged(patch) {
1216 return patch.newFileName && patch.newFileName !== patch.oldFileName;
1217 }
1218
1219 function selectField(index, mine, theirs) {
1220 if (mine === theirs) {
1221 return mine;
1222 } else {
1223 index.conflict = true;
1224 return {
1225 mine: mine,
1226 theirs: theirs
1227 };
1228 }
1229 }
1230
1231 function hunkBefore(test, check) {
1232 return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart;
1233 }
1234
1235 function cloneHunk(hunk, offset) {
1236 return {
1237 oldStart: hunk.oldStart,
1238 oldLines: hunk.oldLines,
1239 newStart: hunk.newStart + offset,
1240 newLines: hunk.newLines,
1241 lines: hunk.lines
1242 };
1243 }
1244
1245 function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
1246 // This will generally result in a conflicted hunk, but there are cases where the context
1247 // is the only overlap where we can successfully merge the content here.
1248 var mine = {
1249 offset: mineOffset,
1250 lines: mineLines,
1251 index: 0
1252 },
1253 their = {
1254 offset: theirOffset,
1255 lines: theirLines,
1256 index: 0
1257 }; // Handle any leading content
1258
1259 insertLeading(hunk, mine, their);
1260 insertLeading(hunk, their, mine); // Now in the overlap content. Scan through and select the best changes from each.
1261
1262 while (mine.index < mine.lines.length && their.index < their.lines.length) {
1263 var mineCurrent = mine.lines[mine.index],
1264 theirCurrent = their.lines[their.index];
1265
1266 if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) {
1267 // Both modified ...
1268 mutualChange(hunk, mine, their);
1269 } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') {
1270 var _hunk$lines;
1271
1272 // Mine inserted
1273 (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine)));
1274 } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') {
1275 var _hunk$lines2;
1276
1277 // Theirs inserted
1278 (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their)));
1279 } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') {
1280 // Mine removed or edited
1281 removal(hunk, mine, their);
1282 } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') {
1283 // Their removed or edited
1284 removal(hunk, their, mine, true);
1285 } else if (mineCurrent === theirCurrent) {
1286 // Context identity
1287 hunk.lines.push(mineCurrent);
1288 mine.index++;
1289 their.index++;
1290 } else {
1291 // Context mismatch
1292 conflict(hunk, collectChange(mine), collectChange(their));
1293 }
1294 } // Now push anything that may be remaining
1295
1296
1297 insertTrailing(hunk, mine);
1298 insertTrailing(hunk, their);
1299 calcLineCount(hunk);
1300 }
1301
1302 function mutualChange(hunk, mine, their) {
1303 var myChanges = collectChange(mine),
1304 theirChanges = collectChange(their);
1305
1306 if (allRemoves(myChanges) && allRemoves(theirChanges)) {
1307 // Special case for remove changes that are supersets of one another
1308 if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) {
1309 var _hunk$lines3;
1310
1311 (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges));
1312
1313 return;
1314 } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) {
1315 var _hunk$lines4;
1316
1317 (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges));
1318
1319 return;
1320 }
1321 } else if (arrayEqual(myChanges, theirChanges)) {
1322 var _hunk$lines5;
1323
1324 (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges));
1325
1326 return;
1327 }
1328
1329 conflict(hunk, myChanges, theirChanges);
1330 }
1331
1332 function removal(hunk, mine, their, swap) {
1333 var myChanges = collectChange(mine),
1334 theirChanges = collectContext(their, myChanges);
1335
1336 if (theirChanges.merged) {
1337 var _hunk$lines6;
1338
1339 (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged));
1340 } else {
1341 conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges);
1342 }
1343 }
1344
1345 function conflict(hunk, mine, their) {
1346 hunk.conflict = true;
1347 hunk.lines.push({
1348 conflict: true,
1349 mine: mine,
1350 theirs: their
1351 });
1352 }
1353
1354 function insertLeading(hunk, insert, their) {
1355 while (insert.offset < their.offset && insert.index < insert.lines.length) {
1356 var line = insert.lines[insert.index++];
1357 hunk.lines.push(line);
1358 insert.offset++;
1359 }
1360 }
1361
1362 function insertTrailing(hunk, insert) {
1363 while (insert.index < insert.lines.length) {
1364 var line = insert.lines[insert.index++];
1365 hunk.lines.push(line);
1366 }
1367 }
1368
1369 function collectChange(state) {
1370 var ret = [],
1371 operation = state.lines[state.index][0];
1372
1373 while (state.index < state.lines.length) {
1374 var line = state.lines[state.index]; // Group additions that are immediately after subtractions and treat them as one "atomic" modify change.
1375
1376 if (operation === '-' && line[0] === '+') {
1377 operation = '+';
1378 }
1379
1380 if (operation === line[0]) {
1381 ret.push(line);
1382 state.index++;
1383 } else {
1384 break;
1385 }
1386 }
1387
1388 return ret;
1389 }
1390
1391 function collectContext(state, matchChanges) {
1392 var changes = [],
1393 merged = [],
1394 matchIndex = 0,
1395 contextChanges = false,
1396 conflicted = false;
1397
1398 while (matchIndex < matchChanges.length && state.index < state.lines.length) {
1399 var change = state.lines[state.index],
1400 match = matchChanges[matchIndex]; // Once we've hit our add, then we are done
1401
1402 if (match[0] === '+') {
1403 break;
1404 }
1405
1406 contextChanges = contextChanges || change[0] !== ' ';
1407 merged.push(match);
1408 matchIndex++; // Consume any additions in the other block as a conflict to attempt
1409 // to pull in the remaining context after this
1410
1411 if (change[0] === '+') {
1412 conflicted = true;
1413
1414 while (change[0] === '+') {
1415 changes.push(change);
1416 change = state.lines[++state.index];
1417 }
1418 }
1419
1420 if (match.substr(1) === change.substr(1)) {
1421 changes.push(change);
1422 state.index++;
1423 } else {
1424 conflicted = true;
1425 }
1426 }
1427
1428 if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) {
1429 conflicted = true;
1430 }
1431
1432 if (conflicted) {
1433 return changes;
1434 }
1435
1436 while (matchIndex < matchChanges.length) {
1437 merged.push(matchChanges[matchIndex++]);
1438 }
1439
1440 return {
1441 merged: merged,
1442 changes: changes
1443 };
1444 }
1445
1446 function allRemoves(changes) {
1447 return changes.reduce(function (prev, change) {
1448 return prev && change[0] === '-';
1449 }, true);
1450 }
1451
1452 function skipRemoveSuperset(state, removeChanges, delta) {
1453 for (var i = 0; i < delta; i++) {
1454 var changeContent = removeChanges[removeChanges.length - delta + i].substr(1);
1455
1456 if (state.lines[state.index + i] !== ' ' + changeContent) {
1457 return false;
1458 }
1459 }
1460
1461 state.index += delta;
1462 return true;
1463 }
1464
1465 function calcOldNewLineCount(lines) {
1466 var oldLines = 0;
1467 var newLines = 0;
1468 lines.forEach(function (line) {
1469 if (typeof line !== 'string') {
1470 var myCount = calcOldNewLineCount(line.mine);
1471 var theirCount = calcOldNewLineCount(line.theirs);
1472
1473 if (oldLines !== undefined) {
1474 if (myCount.oldLines === theirCount.oldLines) {
1475 oldLines += myCount.oldLines;
1476 } else {
1477 oldLines = undefined;
1478 }
1479 }
1480
1481 if (newLines !== undefined) {
1482 if (myCount.newLines === theirCount.newLines) {
1483 newLines += myCount.newLines;
1484 } else {
1485 newLines = undefined;
1486 }
1487 }
1488 } else {
1489 if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) {
1490 newLines++;
1491 }
1492
1493 if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) {
1494 oldLines++;
1495 }
1496 }
1497 });
1498 return {
1499 oldLines: oldLines,
1500 newLines: newLines
1501 };
1502 }
1503
1504 // See: http://code.google.com/p/google-diff-match-patch/wiki/API
1505 function convertChangesToDMP(changes) {
1506 var ret = [],
1507 change,
1508 operation;
1509
1510 for (var i = 0; i < changes.length; i++) {
1511 change = changes[i];
1512
1513 if (change.added) {
1514 operation = 1;
1515 } else if (change.removed) {
1516 operation = -1;
1517 } else {
1518 operation = 0;
1519 }
1520
1521 ret.push([operation, change.value]);
1522 }
1523
1524 return ret;
1525 }
1526
1527 function convertChangesToXML(changes) {
1528 var ret = [];
1529
1530 for (var i = 0; i < changes.length; i++) {
1531 var change = changes[i];
1532
1533 if (change.added) {
1534 ret.push('<ins>');
1535 } else if (change.removed) {
1536 ret.push('<del>');
1537 }
1538
1539 ret.push(escapeHTML(change.value));
1540
1541 if (change.added) {
1542 ret.push('</ins>');
1543 } else if (change.removed) {
1544 ret.push('</del>');
1545 }
1546 }
1547
1548 return ret.join('');
1549 }
1550
1551 function escapeHTML(s) {
1552 var n = s;
1553 n = n.replace(/&/g, '&amp;');
1554 n = n.replace(/</g, '&lt;');
1555 n = n.replace(/>/g, '&gt;');
1556 n = n.replace(/"/g, '&quot;');
1557 return n;
1558 }
1559
1560 /* See LICENSE file for terms of use */
1561
1562 exports.Diff = Diff;
1563 exports.diffChars = diffChars;
1564 exports.diffWords = diffWords;
1565 exports.diffWordsWithSpace = diffWordsWithSpace;
1566 exports.diffLines = diffLines;
1567 exports.diffTrimmedLines = diffTrimmedLines;
1568 exports.diffSentences = diffSentences;
1569 exports.diffCss = diffCss;
1570 exports.diffJson = diffJson;
1571 exports.diffArrays = diffArrays;
1572 exports.structuredPatch = structuredPatch;
1573 exports.createTwoFilesPatch = createTwoFilesPatch;
1574 exports.createPatch = createPatch;
1575 exports.applyPatch = applyPatch;
1576 exports.applyPatches = applyPatches;
1577 exports.parsePatch = parsePatch;
1578 exports.merge = merge;
1579 exports.convertChangesToDMP = convertChangesToDMP;
1580 exports.convertChangesToXML = convertChangesToXML;
1581 exports.canonicalize = canonicalize;
1582
1583 Object.defineProperty(exports, '__esModule', { value: true });
1584
1585}));