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