Path: blob/main/extensions/copilot/src/util/common/diff.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45/**6* Represents information about a specific difference between two sequences.7*/8export class DiffChange {9/**10* The position of the first element in the original sequence which11* this change affects.12*/13public originalStart: number;1415/**16* The number of elements from the original sequence which were17* affected.18*/19public originalLength: number;2021/**22* The position of the first element in the modified sequence which23* this change affects.24*/25public modifiedStart: number;2627/**28* The number of elements from the modified sequence which were29* affected (added).30*/31public modifiedLength: number;3233/**34* Constructs a new DiffChange with the given sequence information35* and content.36*/37constructor(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number) {38//Debug.Assert(originalLength > 0 || modifiedLength > 0, "originalLength and modifiedLength cannot both be <= 0");39this.originalStart = originalStart;40this.originalLength = originalLength;41this.modifiedStart = modifiedStart;42this.modifiedLength = modifiedLength;43}4445/**46* The end point (exclusive) of the change in the original sequence.47*/48public getOriginalEnd() {49return this.originalStart + this.originalLength;50}5152/**53* The end point (exclusive) of the change in the modified sequence.54*/55public getModifiedEnd() {56return this.modifiedStart + this.modifiedLength;57}58}5960export interface ISequence {61getElements(): string[];62}6364export class LineSequence implements ISequence {6566constructor(67public readonly lines: readonly string[],68public readonly trimWhitespace: boolean = true69) { }7071public getElements(): string[] {72const elements: string[] = [];73for (let i = 0, len = this.lines.length; i < len; i++) {74elements[i] = this.trimWhitespace ? this.lines[i].trim() : this.lines[i];75}76return elements;77}7879public getCharCount(): number {80let cnt = 0;81for (const line of this.lines) {82cnt += line.length;83}84return cnt;85}86}8788export class CharSequence implements ISequence {8990private readonly _chars: string[];9192constructor(str: string) {93this._chars = str.split('');94}9596public getElements(): string[] {97return this._chars;98}99}100101//102// The code below has been ported from a C# implementation in VS103//104105class Debug {106public static Assert(condition: boolean, message: string): void {107if (!condition) {108throw new Error(message);109}110}111}112113class MyArray {114/**115* Copies a range of elements from an Array starting at the specified source index and pastes116* them to another Array starting at the specified destination index. The length and the indexes117* are specified as 64-bit integers.118* sourceArray:119* The Array that contains the data to copy.120* sourceIndex:121* A 64-bit integer that represents the index in the sourceArray at which copying begins.122* destinationArray:123* The Array that receives the data.124* destinationIndex:125* A 64-bit integer that represents the index in the destinationArray at which storing begins.126* length:127* A 64-bit integer that represents the number of elements to copy.128*/129public static Copy(130sourceArray: any[],131sourceIndex: number,132destinationArray: any[],133destinationIndex: number,134length: number135) {136for (let i = 0; i < length; i++) {137destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];138}139}140public static Copy2(141sourceArray: Int32Array,142sourceIndex: number,143destinationArray: Int32Array,144destinationIndex: number,145length: number146) {147for (let i = 0; i < length; i++) {148destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];149}150}151}152153//*****************************************************************************154// LcsDiff.cs155//156// An implementation of the difference algorithm described in157// "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers158//159// Copyright (C) 2008 Microsoft Corporation @minifier_do_not_preserve160//*****************************************************************************161162// Our total memory usage for storing history is (worst-case):163// 2 * [(MaxDifferencesHistory + 1) * (MaxDifferencesHistory + 1) - 1] * sizeof(int)164// 2 * [1448*1448 - 1] * 4 = 16773624 = 16MB165const enum LocalConstants {166MaxDifferencesHistory = 1447,167168/**169* MAX SMI (SMall Integer) as defined in v8.170* one bit is lost for boxing/unboxing flag.171* one bit is lost for sign flag.172* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values173*/174MAX_SAFE_SMALL_INTEGER = 1 << 30,175176/**177* MIN SMI (SMall Integer) as defined in v8.178* one bit is lost for boxing/unboxing flag.179* one bit is lost for sign flag.180* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values181*/182MIN_SAFE_SMALL_INTEGER = -(1 << 30),183}184185/**186* A utility class which helps to create the set of DiffChanges from187* a difference operation. This class accepts original DiffElements and188* modified DiffElements that are involved in a particular change. The189* MarkNextChange() method can be called to mark the separation between190* distinct changes. At the end, the Changes property can be called to retrieve191* the constructed changes.192*/193class DiffChangeHelper {194private m_changes: DiffChange[];195private m_originalStart: number;196private m_modifiedStart: number;197private m_originalCount: number;198private m_modifiedCount: number;199200/**201* Constructs a new DiffChangeHelper for the given DiffSequences.202*/203constructor() {204this.m_changes = [];205this.m_originalStart = LocalConstants.MAX_SAFE_SMALL_INTEGER;206this.m_modifiedStart = LocalConstants.MAX_SAFE_SMALL_INTEGER;207this.m_originalCount = 0;208this.m_modifiedCount = 0;209}210211/**212* Marks the beginning of the next change in the set of differences.213*/214public MarkNextChange(): void {215// Only add to the list if there is something to add216if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {217// Add the new change to our list218this.m_changes.push(219new DiffChange(this.m_originalStart, this.m_originalCount, this.m_modifiedStart, this.m_modifiedCount)220);221}222223// Reset for the next change224this.m_originalCount = 0;225this.m_modifiedCount = 0;226this.m_originalStart = LocalConstants.MAX_SAFE_SMALL_INTEGER;227this.m_modifiedStart = LocalConstants.MAX_SAFE_SMALL_INTEGER;228}229230/**231* Adds the original element at the given position to the elements232* affected by the current change. The modified index gives context233* to the change position with respect to the original sequence.234* @param originalIndex The index of the original element to add.235* @param modifiedIndex The index of the modified element that provides corresponding position in the modified sequence.236*/237public AddOriginalElement(originalIndex: number, modifiedIndex: number) {238// The 'true' start index is the smallest of the ones we've seen239this.m_originalStart = Math.min(this.m_originalStart, originalIndex);240this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);241242this.m_originalCount++;243}244245/**246* Adds the modified element at the given position to the elements247* affected by the current change. The original index gives context248* to the change position with respect to the modified sequence.249* @param originalIndex The index of the original element that provides corresponding position in the original sequence.250* @param modifiedIndex The index of the modified element to add.251*/252public AddModifiedElement(originalIndex: number, modifiedIndex: number): void {253// The 'true' start index is the smallest of the ones we've seen254this.m_originalStart = Math.min(this.m_originalStart, originalIndex);255this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);256257this.m_modifiedCount++;258}259260/**261* Retrieves all of the changes marked by the class.262*/263public getChanges(): DiffChange[] {264if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {265// Finish up on whatever is left266this.MarkNextChange();267}268269return this.m_changes;270}271272/**273* Retrieves all of the changes marked by the class in the reverse order274*/275public getReverseChanges(): DiffChange[] {276if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {277// Finish up on whatever is left278this.MarkNextChange();279}280281this.m_changes.reverse();282return this.m_changes;283}284}285286/**287* An implementation of the difference algorithm described in288* "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers289*/290export class LcsDiff {291private readonly _originalStringElements: string[];292private readonly _originalElementsOrHash: Int32Array;293private readonly _modifiedStringElements: string[];294private readonly _modifiedElementsOrHash: Int32Array;295296private m_forwardHistory: Int32Array[];297private m_reverseHistory: Int32Array[];298299/**300* Constructs the DiffFinder301*/302constructor(originalSequence: ISequence, modifiedSequence: ISequence) {303const [originalStringElements, originalElementsOrHash] = LcsDiff._getElements(originalSequence);304const [modifiedStringElements, modifiedElementsOrHash] = LcsDiff._getElements(modifiedSequence);305306this._originalStringElements = originalStringElements;307this._originalElementsOrHash = originalElementsOrHash;308this._modifiedStringElements = modifiedStringElements;309this._modifiedElementsOrHash = modifiedElementsOrHash;310311this.m_forwardHistory = [];312this.m_reverseHistory = [];313}314315private static _getElements(sequence: ISequence): [string[], Int32Array] {316const elements = sequence.getElements();317const hashes = new Int32Array(elements.length);318for (let i = 0, len = elements.length; i < len; i++) {319hashes[i] = this._stringHash(elements[i], 0);320}321return [elements, hashes];322}323324/**325* Computes a int32 hash code for the given number.326*/327private static _numberHash(val: number, initialHashVal: number): number {328return ((initialHashVal << 5) - initialHashVal + val) | 0; // hashVal * 31 + ch, keep as int32329}330331/**332* Computes a int32 hash code for the given string.333*/334private static _stringHash(s: string, hashVal: number) {335hashVal = this._numberHash(149417, hashVal);336for (let i = 0, length = s.length; i < length; i++) {337hashVal = this._numberHash(s.charCodeAt(i), hashVal);338}339return hashVal;340}341342private ElementsAreEqual(originalIndex: number, newIndex: number): boolean {343if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) {344return false;345}346return this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex];347}348349public ComputeDiff(): DiffChange[] {350return this._ComputeDiff(3510,352this._originalElementsOrHash.length - 1,3530,354this._modifiedElementsOrHash.length - 1355);356}357358/**359* Computes the differences between the original and modified input360* sequences on the bounded range.361* @returns An array of the differences between the two input sequences.362*/363private _ComputeDiff(364originalStart: number,365originalEnd: number,366modifiedStart: number,367modifiedEnd: number368): DiffChange[] {369return this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd);370}371372/**373* Private helper method which computes the differences on the bounded range374* recursively.375* @returns An array of the differences between the two input sequences.376*/377private ComputeDiffRecursive(378originalStart: number,379originalEnd: number,380modifiedStart: number,381modifiedEnd: number382): DiffChange[] {383// Find the start of the differences384while (385originalStart <= originalEnd &&386modifiedStart <= modifiedEnd &&387this.ElementsAreEqual(originalStart, modifiedStart)388) {389originalStart++;390modifiedStart++;391}392393// Find the end of the differences394while (395originalEnd >= originalStart &&396modifiedEnd >= modifiedStart &&397this.ElementsAreEqual(originalEnd, modifiedEnd)398) {399originalEnd--;400modifiedEnd--;401}402403// In the special case where we either have all insertions or all deletions or the sequences are identical404if (originalStart > originalEnd || modifiedStart > modifiedEnd) {405let changes: DiffChange[];406407if (modifiedStart <= modifiedEnd) {408Debug.Assert(409originalStart === originalEnd + 1,410'originalStart should only be one more than originalEnd'411);412413// All insertions414changes = [new DiffChange(originalStart, 0, modifiedStart, modifiedEnd - modifiedStart + 1)];415} else if (originalStart <= originalEnd) {416Debug.Assert(417modifiedStart === modifiedEnd + 1,418'modifiedStart should only be one more than modifiedEnd'419);420421// All deletions422changes = [new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, 0)];423} else {424Debug.Assert(425originalStart === originalEnd + 1,426'originalStart should only be one more than originalEnd'427);428Debug.Assert(429modifiedStart === modifiedEnd + 1,430'modifiedStart should only be one more than modifiedEnd'431);432433// Identical sequences - No differences434changes = [];435}436437return changes;438}439440// This problem can be solved using the Divide-And-Conquer technique.441const midOriginalArr = [0];442const midModifiedArr = [0];443const result = this.ComputeRecursionPoint(444originalStart,445originalEnd,446modifiedStart,447modifiedEnd,448midOriginalArr,449midModifiedArr450);451452const midOriginal = midOriginalArr[0];453const midModified = midModifiedArr[0];454455if (result !== null) {456// Result is not-null when there was enough memory to compute the changes while457// searching for the recursion point458return result;459} else {460// We can break the problem down recursively by finding the changes in the461// First Half: (originalStart, modifiedStart) to (midOriginal, midModified)462// Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd)463// NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point464465const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified);466const rightChanges = this.ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd);467468return this.ConcatenateChanges(leftChanges, rightChanges);469}470}471472private WALKTRACE(473diagonalForwardBase: number,474diagonalForwardStart: number,475diagonalForwardEnd: number,476diagonalForwardOffset: number,477diagonalReverseBase: number,478diagonalReverseStart: number,479diagonalReverseEnd: number,480diagonalReverseOffset: number,481forwardPoints: Int32Array,482reversePoints: Int32Array,483originalIndex: number,484originalEnd: number,485midOriginalArr: number[],486modifiedIndex: number,487modifiedEnd: number,488midModifiedArr: number[],489deltaIsEven: boolean490): DiffChange[] {491let forwardChanges: DiffChange[] | null = null;492let reverseChanges: DiffChange[] | null = null;493494// First, walk backward through the forward diagonals history495let changeHelper = new DiffChangeHelper();496let diagonalMin = diagonalForwardStart;497let diagonalMax = diagonalForwardEnd;498let diagonalRelative = midOriginalArr[0] - midModifiedArr[0] - diagonalForwardOffset;499let lastOriginalIndex = LocalConstants.MIN_SAFE_SMALL_INTEGER;500let historyIndex = this.m_forwardHistory.length - 1;501502do {503// Get the diagonal index from the relative diagonal number504const diagonal = diagonalRelative + diagonalForwardBase;505506// Figure out where we came from507if (508diagonal === diagonalMin ||509(diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])510) {511// Vertical line (the element is an insert)512originalIndex = forwardPoints[diagonal + 1];513modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;514if (originalIndex < lastOriginalIndex) {515changeHelper.MarkNextChange();516}517lastOriginalIndex = originalIndex;518changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex);519diagonalRelative = diagonal + 1 - diagonalForwardBase; //Setup for the next iteration520} else {521// Horizontal line (the element is a deletion)522originalIndex = forwardPoints[diagonal - 1] + 1;523modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;524if (originalIndex < lastOriginalIndex) {525changeHelper.MarkNextChange();526}527lastOriginalIndex = originalIndex - 1;528changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1);529diagonalRelative = diagonal - 1 - diagonalForwardBase; //Setup for the next iteration530}531532if (historyIndex >= 0) {533forwardPoints = this.m_forwardHistory[historyIndex];534diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot535diagonalMin = 1;536diagonalMax = forwardPoints.length - 1;537}538} while (--historyIndex >= -1);539540// Ironically, we get the forward changes as the reverse of the541// order we added them since we technically added them backwards542forwardChanges = changeHelper.getReverseChanges();543544// Now walk backward through the reverse diagonals history545changeHelper = new DiffChangeHelper();546diagonalMin = diagonalReverseStart;547diagonalMax = diagonalReverseEnd;548diagonalRelative = midOriginalArr[0] - midModifiedArr[0] - diagonalReverseOffset;549lastOriginalIndex = LocalConstants.MAX_SAFE_SMALL_INTEGER;550historyIndex = deltaIsEven ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2;551552do {553// Get the diagonal index from the relative diagonal number554const diagonal = diagonalRelative + diagonalReverseBase;555556// Figure out where we came from557if (558diagonal === diagonalMin ||559(diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])560) {561// Horizontal line (the element is a deletion))562originalIndex = reversePoints[diagonal + 1] - 1;563modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;564if (originalIndex > lastOriginalIndex) {565changeHelper.MarkNextChange();566}567lastOriginalIndex = originalIndex + 1;568changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1);569diagonalRelative = diagonal + 1 - diagonalReverseBase; //Setup for the next iteration570} else {571// Vertical line (the element is an insertion)572originalIndex = reversePoints[diagonal - 1];573modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;574if (originalIndex > lastOriginalIndex) {575changeHelper.MarkNextChange();576}577lastOriginalIndex = originalIndex;578changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1);579diagonalRelative = diagonal - 1 - diagonalReverseBase; //Setup for the next iteration580}581582if (historyIndex >= 0) {583reversePoints = this.m_reverseHistory[historyIndex];584diagonalReverseBase = reversePoints[0]; //We stored this in the first spot585diagonalMin = 1;586diagonalMax = reversePoints.length - 1;587}588} while (--historyIndex >= -1);589590// There are cases where the reverse history will find diffs that591// are correct, but not intuitive, so we need shift them.592reverseChanges = changeHelper.getChanges();593594return this.ConcatenateChanges(forwardChanges, reverseChanges);595}596597/**598* Given the range to compute the diff on, this method finds the point:599* (midOriginal, midModified)600* that exists in the middle of the LCS of the two sequences and601* is the point at which the LCS problem may be broken down recursively.602* This method will try to keep the LCS trace in memory. If the LCS recursion603* point is calculated and the full trace is available in memory, then this method604* will return the change list.605* @param originalStart The start bound of the original sequence range606* @param originalEnd The end bound of the original sequence range607* @param modifiedStart The start bound of the modified sequence range608* @param modifiedEnd The end bound of the modified sequence range609* @param midOriginal The middle point of the original sequence range610* @param midModified The middle point of the modified sequence range611* @returns The diff changes, if available, otherwise null612*/613private ComputeRecursionPoint(614originalStart: number,615originalEnd: number,616modifiedStart: number,617modifiedEnd: number,618midOriginalArr: number[],619midModifiedArr: number[]620) {621let originalIndex = 0,622modifiedIndex = 0;623let diagonalForwardStart = 0,624diagonalForwardEnd = 0;625let diagonalReverseStart = 0,626diagonalReverseEnd = 0;627628// To traverse the edit graph and produce the proper LCS, our actual629// start position is just outside the given boundary630originalStart--;631modifiedStart--;632633// We set these up to make the compiler happy, but they will634// be replaced before we return with the actual recursion point635midOriginalArr[0] = 0;636midModifiedArr[0] = 0;637638// Clear out the history639this.m_forwardHistory = [];640this.m_reverseHistory = [];641642// Each cell in the two arrays corresponds to a diagonal in the edit graph.643// The integer value in the cell represents the originalIndex of the furthest644// reaching point found so far that ends in that diagonal.645// The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number.646const maxDifferences = originalEnd - originalStart + (modifiedEnd - modifiedStart);647const numDiagonals = maxDifferences + 1;648const forwardPoints = new Int32Array(numDiagonals);649const reversePoints = new Int32Array(numDiagonals);650// diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart)651// diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd)652const diagonalForwardBase = modifiedEnd - modifiedStart;653const diagonalReverseBase = originalEnd - originalStart;654// diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the655// diagonal number (relative to diagonalForwardBase)656// diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the657// diagonal number (relative to diagonalReverseBase)658const diagonalForwardOffset = originalStart - modifiedStart;659const diagonalReverseOffset = originalEnd - modifiedEnd;660661// delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers662// relative to the start diagonal with diagonal numbers relative to the end diagonal.663// The Even/Oddn-ness of this delta is important for determining when we should check for overlap664const delta = diagonalReverseBase - diagonalForwardBase;665const deltaIsEven = delta % 2 === 0;666667// Here we set up the start and end points as the furthest points found so far668// in both the forward and reverse directions, respectively669forwardPoints[diagonalForwardBase] = originalStart;670reversePoints[diagonalReverseBase] = originalEnd;671672// A couple of points:673// --With this method, we iterate on the number of differences between the two sequences.674// The more differences there actually are, the longer this will take.675// --Also, as the number of differences increases, we have to search on diagonals further676// away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse).677// --We extend on even diagonals (relative to the reference diagonal) only when numDifferences678// is even and odd diagonals only when numDifferences is odd.679for (let numDifferences = 1; numDifferences <= maxDifferences / 2 + 1; numDifferences++) {680let furthestOriginalIndex = 0;681let furthestModifiedIndex = 0;682683// Run the algorithm in the forward direction684diagonalForwardStart = this.ClipDiagonalBound(685diagonalForwardBase - numDifferences,686numDifferences,687diagonalForwardBase,688numDiagonals689);690diagonalForwardEnd = this.ClipDiagonalBound(691diagonalForwardBase + numDifferences,692numDifferences,693diagonalForwardBase,694numDiagonals695);696for (let diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) {697// STEP 1: We extend the furthest reaching point in the present diagonal698// by looking at the diagonals above and below and picking the one whose point699// is further away from the start point (originalStart, modifiedStart)700if (701diagonal === diagonalForwardStart ||702(diagonal < diagonalForwardEnd && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])703) {704originalIndex = forwardPoints[diagonal + 1];705} else {706originalIndex = forwardPoints[diagonal - 1] + 1;707}708modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset;709710// Save the current originalIndex so we can test for false overlap in step 3711const tempOriginalIndex = originalIndex;712713// STEP 2: We can continue to extend the furthest reaching point in the present diagonal714// so long as the elements are equal.715while (716originalIndex < originalEnd &&717modifiedIndex < modifiedEnd &&718this.ElementsAreEqual(originalIndex + 1, modifiedIndex + 1)719) {720originalIndex++;721modifiedIndex++;722}723forwardPoints[diagonal] = originalIndex;724725if (originalIndex + modifiedIndex > furthestOriginalIndex + furthestModifiedIndex) {726furthestOriginalIndex = originalIndex;727furthestModifiedIndex = modifiedIndex;728}729730// STEP 3: If delta is odd (overlap first happens on forward when delta is odd)731// and diagonal is in the range of reverse diagonals computed for numDifferences-1732// (the previous iteration; we haven't computed reverse diagonals for numDifferences yet)733// then check for overlap.734if (!deltaIsEven && Math.abs(diagonal - diagonalReverseBase) <= numDifferences - 1) {735if (originalIndex >= reversePoints[diagonal]) {736midOriginalArr[0] = originalIndex;737midModifiedArr[0] = modifiedIndex;738739if (740tempOriginalIndex <= reversePoints[diagonal] &&741LocalConstants.MaxDifferencesHistory > 0 &&742numDifferences <= LocalConstants.MaxDifferencesHistory + 1743) {744// BINGO! We overlapped, and we have the full trace in memory!745return this.WALKTRACE(746diagonalForwardBase,747diagonalForwardStart,748diagonalForwardEnd,749diagonalForwardOffset,750diagonalReverseBase,751diagonalReverseStart,752diagonalReverseEnd,753diagonalReverseOffset,754forwardPoints,755reversePoints,756originalIndex,757originalEnd,758midOriginalArr,759modifiedIndex,760modifiedEnd,761midModifiedArr,762deltaIsEven763);764} else {765// Either false overlap, or we didn't have enough memory for the full trace766// Just return the recursion point767return null;768}769}770}771}772773// Run the algorithm in the reverse direction774diagonalReverseStart = this.ClipDiagonalBound(775diagonalReverseBase - numDifferences,776numDifferences,777diagonalReverseBase,778numDiagonals779);780diagonalReverseEnd = this.ClipDiagonalBound(781diagonalReverseBase + numDifferences,782numDifferences,783diagonalReverseBase,784numDiagonals785);786for (let diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) {787// STEP 1: We extend the furthest reaching point in the present diagonal788// by looking at the diagonals above and below and picking the one whose point789// is further away from the start point (originalEnd, modifiedEnd)790if (791diagonal === diagonalReverseStart ||792(diagonal < diagonalReverseEnd && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])793) {794originalIndex = reversePoints[diagonal + 1] - 1;795} else {796originalIndex = reversePoints[diagonal - 1];797}798modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset;799800// Save the current originalIndex so we can test for false overlap801const tempOriginalIndex = originalIndex;802803// STEP 2: We can continue to extend the furthest reaching point in the present diagonal804// as long as the elements are equal.805while (806originalIndex > originalStart &&807modifiedIndex > modifiedStart &&808this.ElementsAreEqual(originalIndex, modifiedIndex)809) {810originalIndex--;811modifiedIndex--;812}813reversePoints[diagonal] = originalIndex;814815// STEP 4: If delta is even (overlap first happens on reverse when delta is even)816// and diagonal is in the range of forward diagonals computed for numDifferences817// then check for overlap.818if (deltaIsEven && Math.abs(diagonal - diagonalForwardBase) <= numDifferences) {819if (originalIndex <= forwardPoints[diagonal]) {820midOriginalArr[0] = originalIndex;821midModifiedArr[0] = modifiedIndex;822823if (824tempOriginalIndex >= forwardPoints[diagonal] &&825LocalConstants.MaxDifferencesHistory > 0 &&826numDifferences <= LocalConstants.MaxDifferencesHistory + 1827) {828// BINGO! We overlapped, and we have the full trace in memory!829return this.WALKTRACE(830diagonalForwardBase,831diagonalForwardStart,832diagonalForwardEnd,833diagonalForwardOffset,834diagonalReverseBase,835diagonalReverseStart,836diagonalReverseEnd,837diagonalReverseOffset,838forwardPoints,839reversePoints,840originalIndex,841originalEnd,842midOriginalArr,843modifiedIndex,844modifiedEnd,845midModifiedArr,846deltaIsEven847);848} else {849// Either false overlap, or we didn't have enough memory for the full trace850// Just return the recursion point851return null;852}853}854}855}856857// Save current vectors to history before the next iteration858if (numDifferences <= LocalConstants.MaxDifferencesHistory) {859// We are allocating space for one extra int, which we fill with860// the index of the diagonal base index861let temp = new Int32Array(diagonalForwardEnd - diagonalForwardStart + 2);862temp[0] = diagonalForwardBase - diagonalForwardStart + 1;863MyArray.Copy2(864forwardPoints,865diagonalForwardStart,866temp,8671,868diagonalForwardEnd - diagonalForwardStart + 1869);870this.m_forwardHistory.push(temp);871872temp = new Int32Array(diagonalReverseEnd - diagonalReverseStart + 2);873temp[0] = diagonalReverseBase - diagonalReverseStart + 1;874MyArray.Copy2(875reversePoints,876diagonalReverseStart,877temp,8781,879diagonalReverseEnd - diagonalReverseStart + 1880);881this.m_reverseHistory.push(temp);882}883}884885// If we got here, then we have the full trace in history. We just have to convert it to a change list886// NOTE: This part is a bit messy887return this.WALKTRACE(888diagonalForwardBase,889diagonalForwardStart,890diagonalForwardEnd,891diagonalForwardOffset,892diagonalReverseBase,893diagonalReverseStart,894diagonalReverseEnd,895diagonalReverseOffset,896forwardPoints,897reversePoints,898originalIndex,899originalEnd,900midOriginalArr,901modifiedIndex,902modifiedEnd,903midModifiedArr,904deltaIsEven905);906}907908/**909* Concatenates the two input DiffChange lists and returns the resulting910* list.911* @param The left changes912* @param The right changes913* @returns The concatenated list914*/915private ConcatenateChanges(left: DiffChange[], right: DiffChange[]): DiffChange[] {916const mergedChangeArr: DiffChange[] = [];917918if (left.length === 0 || right.length === 0) {919return right.length > 0 ? right : left;920} else if (this.ChangesOverlap(left[left.length - 1], right[0], mergedChangeArr)) {921// Since we break the problem down recursively, it is possible that we922// might recurse in the middle of a change thereby splitting it into923// two changes. Here in the combining stage, we detect and fuse those924// changes back together925const result = new Array<DiffChange>(left.length + right.length - 1);926MyArray.Copy(left, 0, result, 0, left.length - 1);927result[left.length - 1] = mergedChangeArr[0];928MyArray.Copy(right, 1, result, left.length, right.length - 1);929930return result;931} else {932const result = new Array<DiffChange>(left.length + right.length);933MyArray.Copy(left, 0, result, 0, left.length);934MyArray.Copy(right, 0, result, left.length, right.length);935936return result;937}938}939940/**941* Returns true if the two changes overlap and can be merged into a single942* change943* @param left The left change944* @param right The right change945* @param mergedChange The merged change if the two overlap, null otherwise946* @returns True if the two changes overlap947*/948private ChangesOverlap(left: DiffChange, right: DiffChange, mergedChangeArr: Array<DiffChange | null>): boolean {949Debug.Assert(950left.originalStart <= right.originalStart,951'Left change is not less than or equal to right change'952);953Debug.Assert(954left.modifiedStart <= right.modifiedStart,955'Left change is not less than or equal to right change'956);957958if (959left.originalStart + left.originalLength >= right.originalStart ||960left.modifiedStart + left.modifiedLength >= right.modifiedStart961) {962const originalStart = left.originalStart;963let originalLength = left.originalLength;964const modifiedStart = left.modifiedStart;965let modifiedLength = left.modifiedLength;966967if (left.originalStart + left.originalLength >= right.originalStart) {968originalLength = right.originalStart + right.originalLength - left.originalStart;969}970if (left.modifiedStart + left.modifiedLength >= right.modifiedStart) {971modifiedLength = right.modifiedStart + right.modifiedLength - left.modifiedStart;972}973974mergedChangeArr[0] = new DiffChange(originalStart, originalLength, modifiedStart, modifiedLength);975return true;976} else {977mergedChangeArr[0] = null;978return false;979}980}981982/**983* Helper method used to clip a diagonal index to the range of valid984* diagonals. This also decides whether or not the diagonal index,985* if it exceeds the boundary, should be clipped to the boundary or clipped986* one inside the boundary depending on the Even/Odd status of the boundary987* and numDifferences.988* @param diagonal The index of the diagonal to clip.989* @param numDifferences The current number of differences being iterated upon.990* @param diagonalBaseIndex The base reference diagonal.991* @param numDiagonals The total number of diagonals.992* @returns The clipped diagonal index.993*/994private ClipDiagonalBound(995diagonal: number,996numDifferences: number,997diagonalBaseIndex: number,998numDiagonals: number999): number {1000if (diagonal >= 0 && diagonal < numDiagonals) {1001// Nothing to clip, its in range1002return diagonal;1003}10041005// diagonalsBelow: The number of diagonals below the reference diagonal1006// diagonalsAbove: The number of diagonals above the reference diagonal1007const diagonalsBelow = diagonalBaseIndex;1008const diagonalsAbove = numDiagonals - diagonalBaseIndex - 1;1009const diffEven = numDifferences % 2 === 0;10101011if (diagonal < 0) {1012const lowerBoundEven = diagonalsBelow % 2 === 0;1013return diffEven === lowerBoundEven ? 0 : 1;1014} else {1015const upperBoundEven = diagonalsAbove % 2 === 0;1016return diffEven === upperBoundEven ? numDiagonals - 1 : numDiagonals - 2;1017}1018}1019}102010211022