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