Path: blob/main/extensions/copilot/src/util/vs/base/common/diff/diff.ts
13406 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { DiffChange } from './diffChange';8import { stringHash } from '../hash';9import { Constants } from '../uint';1011export class StringDiffSequence implements ISequence {1213constructor(private source: string) { }1415getElements(): Int32Array | number[] | string[] {16const source = this.source;17const characters = new Int32Array(source.length);18for (let i = 0, len = source.length; i < len; i++) {19characters[i] = source.charCodeAt(i);20}21return characters;22}23}2425export function stringDiff(original: string, modified: string, pretty: boolean): IDiffChange[] {26return new LcsDiff(new StringDiffSequence(original), new StringDiffSequence(modified)).ComputeDiff(pretty).changes;27}2829export interface ISequence {30getElements(): Int32Array | number[] | string[];31getStrictElement?(index: number): string;32}3334export interface IDiffChange {35/**36* The position of the first element in the original sequence which37* this change affects.38*/39originalStart: number;4041/**42* The number of elements from the original sequence which were43* affected.44*/45originalLength: number;4647/**48* The position of the first element in the modified sequence which49* this change affects.50*/51modifiedStart: number;5253/**54* The number of elements from the modified sequence which were55* affected (added).56*/57modifiedLength: number;58}5960export interface IContinueProcessingPredicate {61(furthestOriginalIndex: number, matchLengthOfLongest: number): boolean;62}6364export interface IDiffResult {65quitEarly: boolean;66changes: IDiffChange[];67}6869//70// The code below has been ported from a C# implementation in VS71//7273class Debug {7475public static Assert(condition: boolean, message: string): void {76if (!condition) {77throw new Error(message);78}79}80}8182class MyArray {83/**84* Copies a range of elements from an Array starting at the specified source index and pastes85* them to another Array starting at the specified destination index. The length and the indexes86* are specified as 64-bit integers.87* sourceArray:88* The Array that contains the data to copy.89* sourceIndex:90* A 64-bit integer that represents the index in the sourceArray at which copying begins.91* destinationArray:92* The Array that receives the data.93* destinationIndex:94* A 64-bit integer that represents the index in the destinationArray at which storing begins.95* length:96* A 64-bit integer that represents the number of elements to copy.97*/98public static Copy(sourceArray: unknown[], sourceIndex: number, destinationArray: unknown[], destinationIndex: number, length: number) {99for (let i = 0; i < length; i++) {100destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];101}102}103public static Copy2(sourceArray: Int32Array, sourceIndex: number, destinationArray: Int32Array, destinationIndex: number, length: number) {104for (let i = 0; i < length; i++) {105destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i];106}107}108}109110//*****************************************************************************111// LcsDiff.cs112//113// An implementation of the difference algorithm described in114// "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers115//116// Copyright (C) 2008 Microsoft Corporation @minifier_do_not_preserve117//*****************************************************************************118119// Our total memory usage for storing history is (worst-case):120// 2 * [(MaxDifferencesHistory + 1) * (MaxDifferencesHistory + 1) - 1] * sizeof(int)121// 2 * [1448*1448 - 1] * 4 = 16773624 = 16MB122const enum LocalConstants {123MaxDifferencesHistory = 1447124}125126/**127* A utility class which helps to create the set of DiffChanges from128* a difference operation. This class accepts original DiffElements and129* modified DiffElements that are involved in a particular change. The130* MarkNextChange() method can be called to mark the separation between131* distinct changes. At the end, the Changes property can be called to retrieve132* the constructed changes.133*/134class DiffChangeHelper {135136private m_changes: DiffChange[];137private m_originalStart: number;138private m_modifiedStart: number;139private m_originalCount: number;140private m_modifiedCount: number;141142/**143* Constructs a new DiffChangeHelper for the given DiffSequences.144*/145constructor() {146this.m_changes = [];147this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER;148this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER;149this.m_originalCount = 0;150this.m_modifiedCount = 0;151}152153/**154* Marks the beginning of the next change in the set of differences.155*/156public MarkNextChange(): void {157// Only add to the list if there is something to add158if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {159// Add the new change to our list160this.m_changes.push(new DiffChange(this.m_originalStart, this.m_originalCount,161this.m_modifiedStart, this.m_modifiedCount));162}163164// Reset for the next change165this.m_originalCount = 0;166this.m_modifiedCount = 0;167this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER;168this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER;169}170171/**172* Adds the original element at the given position to the elements173* affected by the current change. The modified index gives context174* to the change position with respect to the original sequence.175* @param originalIndex The index of the original element to add.176* @param modifiedIndex The index of the modified element that provides corresponding position in the modified sequence.177*/178public AddOriginalElement(originalIndex: number, modifiedIndex: number) {179// The 'true' start index is the smallest of the ones we've seen180this.m_originalStart = Math.min(this.m_originalStart, originalIndex);181this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);182183this.m_originalCount++;184}185186/**187* Adds the modified element at the given position to the elements188* affected by the current change. The original index gives context189* to the change position with respect to the modified sequence.190* @param originalIndex The index of the original element that provides corresponding position in the original sequence.191* @param modifiedIndex The index of the modified element to add.192*/193public AddModifiedElement(originalIndex: number, modifiedIndex: number): void {194// The 'true' start index is the smallest of the ones we've seen195this.m_originalStart = Math.min(this.m_originalStart, originalIndex);196this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex);197198this.m_modifiedCount++;199}200201/**202* Retrieves all of the changes marked by the class.203*/204public getChanges(): DiffChange[] {205if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {206// Finish up on whatever is left207this.MarkNextChange();208}209210return this.m_changes;211}212213/**214* Retrieves all of the changes marked by the class in the reverse order215*/216public getReverseChanges(): DiffChange[] {217if (this.m_originalCount > 0 || this.m_modifiedCount > 0) {218// Finish up on whatever is left219this.MarkNextChange();220}221222this.m_changes.reverse();223return this.m_changes;224}225226}227228/**229* An implementation of the difference algorithm described in230* "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers231*/232export class LcsDiff {233234private readonly ContinueProcessingPredicate: IContinueProcessingPredicate | null;235236private readonly _originalSequence: ISequence;237private readonly _modifiedSequence: ISequence;238private readonly _hasStrings: boolean;239private readonly _originalStringElements: string[];240private readonly _originalElementsOrHash: Int32Array;241private readonly _modifiedStringElements: string[];242private readonly _modifiedElementsOrHash: Int32Array;243244private m_forwardHistory: Int32Array[];245private m_reverseHistory: Int32Array[];246247/**248* Constructs the DiffFinder249*/250constructor(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate | null = null) {251this.ContinueProcessingPredicate = continueProcessingPredicate;252253this._originalSequence = originalSequence;254this._modifiedSequence = modifiedSequence;255256const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence);257const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence);258259this._hasStrings = (originalHasStrings && modifiedHasStrings);260this._originalStringElements = originalStringElements;261this._originalElementsOrHash = originalElementsOrHash;262this._modifiedStringElements = modifiedStringElements;263this._modifiedElementsOrHash = modifiedElementsOrHash;264265this.m_forwardHistory = [];266this.m_reverseHistory = [];267}268269private static _isStringArray(arr: Int32Array | number[] | string[]): arr is string[] {270return (arr.length > 0 && typeof arr[0] === 'string');271}272273private static _getElements(sequence: ISequence): [string[], Int32Array, boolean] {274const elements = sequence.getElements();275276if (LcsDiff._isStringArray(elements)) {277const hashes = new Int32Array(elements.length);278for (let i = 0, len = elements.length; i < len; i++) {279hashes[i] = stringHash(elements[i], 0);280}281return [elements, hashes, true];282}283284if (elements instanceof Int32Array) {285return [[], elements, false];286}287288return [[], new Int32Array(elements), false];289}290291private ElementsAreEqual(originalIndex: number, newIndex: number): boolean {292if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) {293return false;294}295return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true);296}297298private ElementsAreStrictEqual(originalIndex: number, newIndex: number): boolean {299if (!this.ElementsAreEqual(originalIndex, newIndex)) {300return false;301}302const originalElement = LcsDiff._getStrictElement(this._originalSequence, originalIndex);303const modifiedElement = LcsDiff._getStrictElement(this._modifiedSequence, newIndex);304return (originalElement === modifiedElement);305}306307private static _getStrictElement(sequence: ISequence, index: number): string | null {308if (typeof sequence.getStrictElement === 'function') {309return sequence.getStrictElement(index);310}311return null;312}313314private OriginalElementsAreEqual(index1: number, index2: number): boolean {315if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) {316return false;317}318return (this._hasStrings ? this._originalStringElements[index1] === this._originalStringElements[index2] : true);319}320321private ModifiedElementsAreEqual(index1: number, index2: number): boolean {322if (this._modifiedElementsOrHash[index1] !== this._modifiedElementsOrHash[index2]) {323return false;324}325return (this._hasStrings ? this._modifiedStringElements[index1] === this._modifiedStringElements[index2] : true);326}327328public ComputeDiff(pretty: boolean): IDiffResult {329return this._ComputeDiff(0, this._originalElementsOrHash.length - 1, 0, this._modifiedElementsOrHash.length - 1, pretty);330}331332/**333* Computes the differences between the original and modified input334* sequences on the bounded range.335* @returns An array of the differences between the two input sequences.336*/337private _ComputeDiff(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, pretty: boolean): IDiffResult {338const quitEarlyArr = [false];339let changes = this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr);340341if (pretty) {342// We have to clean up the computed diff to be more intuitive343// but it turns out this cannot be done correctly until the entire set344// of diffs have been computed345changes = this.PrettifyChanges(changes);346}347348return {349quitEarly: quitEarlyArr[0],350changes: changes351};352}353354/**355* Private helper method which computes the differences on the bounded range356* recursively.357* @returns An array of the differences between the two input sequences.358*/359private ComputeDiffRecursive(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, quitEarlyArr: boolean[]): DiffChange[] {360quitEarlyArr[0] = false;361362// Find the start of the differences363while (originalStart <= originalEnd && modifiedStart <= modifiedEnd && this.ElementsAreEqual(originalStart, modifiedStart)) {364originalStart++;365modifiedStart++;366}367368// Find the end of the differences369while (originalEnd >= originalStart && modifiedEnd >= modifiedStart && this.ElementsAreEqual(originalEnd, modifiedEnd)) {370originalEnd--;371modifiedEnd--;372}373374// In the special case where we either have all insertions or all deletions or the sequences are identical375if (originalStart > originalEnd || modifiedStart > modifiedEnd) {376let changes: DiffChange[];377378if (modifiedStart <= modifiedEnd) {379Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');380381// All insertions382changes = [383new DiffChange(originalStart, 0, modifiedStart, modifiedEnd - modifiedStart + 1)384];385} else if (originalStart <= originalEnd) {386Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');387388// All deletions389changes = [390new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, 0)391];392} else {393Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd');394Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd');395396// Identical sequences - No differences397changes = [];398}399400return changes;401}402403// This problem can be solved using the Divide-And-Conquer technique.404const midOriginalArr = [0];405const midModifiedArr = [0];406const result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr);407408const midOriginal = midOriginalArr[0];409const midModified = midModifiedArr[0];410411if (result !== null) {412// Result is not-null when there was enough memory to compute the changes while413// searching for the recursion point414return result;415} else if (!quitEarlyArr[0]) {416// We can break the problem down recursively by finding the changes in the417// First Half: (originalStart, modifiedStart) to (midOriginal, midModified)418// Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd)419// NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point420421const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr);422let rightChanges: DiffChange[] = [];423424if (!quitEarlyArr[0]) {425rightChanges = this.ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd, quitEarlyArr);426} else {427// We didn't have time to finish the first half, so we don't have time to compute this half.428// Consider the entire rest of the sequence different.429rightChanges = [430new DiffChange(midOriginal + 1, originalEnd - (midOriginal + 1) + 1, midModified + 1, modifiedEnd - (midModified + 1) + 1)431];432}433434return this.ConcatenateChanges(leftChanges, rightChanges);435}436437// If we hit here, we quit early, and so can't return anything meaningful438return [439new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, modifiedEnd - modifiedStart + 1)440];441}442443private WALKTRACE(diagonalForwardBase: number, diagonalForwardStart: number, diagonalForwardEnd: number, diagonalForwardOffset: number,444diagonalReverseBase: number, diagonalReverseStart: number, diagonalReverseEnd: number, diagonalReverseOffset: number,445forwardPoints: Int32Array, reversePoints: Int32Array,446originalIndex: number, originalEnd: number, midOriginalArr: number[],447modifiedIndex: number, modifiedEnd: number, midModifiedArr: number[],448deltaIsEven: boolean, quitEarlyArr: boolean[]449): DiffChange[] {450let forwardChanges: DiffChange[] | null = null;451let reverseChanges: DiffChange[] | null = null;452453// First, walk backward through the forward diagonals history454let changeHelper = new DiffChangeHelper();455let diagonalMin = diagonalForwardStart;456let diagonalMax = diagonalForwardEnd;457let diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalForwardOffset;458let lastOriginalIndex = Constants.MIN_SAFE_SMALL_INTEGER;459let historyIndex = this.m_forwardHistory.length - 1;460461do {462// Get the diagonal index from the relative diagonal number463const diagonal = diagonalRelative + diagonalForwardBase;464465// Figure out where we came from466if (diagonal === diagonalMin || (diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {467// Vertical line (the element is an insert)468originalIndex = forwardPoints[diagonal + 1];469modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;470if (originalIndex < lastOriginalIndex) {471changeHelper.MarkNextChange();472}473lastOriginalIndex = originalIndex;474changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex);475diagonalRelative = (diagonal + 1) - diagonalForwardBase; //Setup for the next iteration476} else {477// Horizontal line (the element is a deletion)478originalIndex = forwardPoints[diagonal - 1] + 1;479modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset;480if (originalIndex < lastOriginalIndex) {481changeHelper.MarkNextChange();482}483lastOriginalIndex = originalIndex - 1;484changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1);485diagonalRelative = (diagonal - 1) - diagonalForwardBase; //Setup for the next iteration486}487488if (historyIndex >= 0) {489forwardPoints = this.m_forwardHistory[historyIndex];490diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot491diagonalMin = 1;492diagonalMax = forwardPoints.length - 1;493}494} while (--historyIndex >= -1);495496// Ironically, we get the forward changes as the reverse of the497// order we added them since we technically added them backwards498forwardChanges = changeHelper.getReverseChanges();499500if (quitEarlyArr[0]) {501// TODO: Calculate a partial from the reverse diagonals.502// For now, just assume everything after the midOriginal/midModified point is a diff503504let originalStartPoint = midOriginalArr[0] + 1;505let modifiedStartPoint = midModifiedArr[0] + 1;506507if (forwardChanges !== null && forwardChanges.length > 0) {508const lastForwardChange = forwardChanges[forwardChanges.length - 1];509originalStartPoint = Math.max(originalStartPoint, lastForwardChange.getOriginalEnd());510modifiedStartPoint = Math.max(modifiedStartPoint, lastForwardChange.getModifiedEnd());511}512513reverseChanges = [514new DiffChange(originalStartPoint, originalEnd - originalStartPoint + 1,515modifiedStartPoint, modifiedEnd - modifiedStartPoint + 1)516];517} else {518// Now walk backward through the reverse diagonals history519changeHelper = new DiffChangeHelper();520diagonalMin = diagonalReverseStart;521diagonalMax = diagonalReverseEnd;522diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalReverseOffset;523lastOriginalIndex = Constants.MAX_SAFE_SMALL_INTEGER;524historyIndex = (deltaIsEven) ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2;525526do {527// Get the diagonal index from the relative diagonal number528const diagonal = diagonalRelative + diagonalReverseBase;529530// Figure out where we came from531if (diagonal === diagonalMin || (diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {532// Horizontal line (the element is a deletion))533originalIndex = reversePoints[diagonal + 1] - 1;534modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;535if (originalIndex > lastOriginalIndex) {536changeHelper.MarkNextChange();537}538lastOriginalIndex = originalIndex + 1;539changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1);540diagonalRelative = (diagonal + 1) - diagonalReverseBase; //Setup for the next iteration541} else {542// Vertical line (the element is an insertion)543originalIndex = reversePoints[diagonal - 1];544modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset;545if (originalIndex > lastOriginalIndex) {546changeHelper.MarkNextChange();547}548lastOriginalIndex = originalIndex;549changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1);550diagonalRelative = (diagonal - 1) - diagonalReverseBase; //Setup for the next iteration551}552553if (historyIndex >= 0) {554reversePoints = this.m_reverseHistory[historyIndex];555diagonalReverseBase = reversePoints[0]; //We stored this in the first spot556diagonalMin = 1;557diagonalMax = reversePoints.length - 1;558}559} while (--historyIndex >= -1);560561// There are cases where the reverse history will find diffs that562// are correct, but not intuitive, so we need shift them.563reverseChanges = changeHelper.getChanges();564}565566return this.ConcatenateChanges(forwardChanges, reverseChanges);567}568569/**570* Given the range to compute the diff on, this method finds the point:571* (midOriginal, midModified)572* that exists in the middle of the LCS of the two sequences and573* is the point at which the LCS problem may be broken down recursively.574* This method will try to keep the LCS trace in memory. If the LCS recursion575* point is calculated and the full trace is available in memory, then this method576* will return the change list.577* @param originalStart The start bound of the original sequence range578* @param originalEnd The end bound of the original sequence range579* @param modifiedStart The start bound of the modified sequence range580* @param modifiedEnd The end bound of the modified sequence range581* @param midOriginal The middle point of the original sequence range582* @param midModified The middle point of the modified sequence range583* @returns The diff changes, if available, otherwise null584*/585private ComputeRecursionPoint(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, midOriginalArr: number[], midModifiedArr: number[], quitEarlyArr: boolean[]) {586let originalIndex = 0, modifiedIndex = 0;587let diagonalForwardStart = 0, diagonalForwardEnd = 0;588let diagonalReverseStart = 0, diagonalReverseEnd = 0;589590// To traverse the edit graph and produce the proper LCS, our actual591// start position is just outside the given boundary592originalStart--;593modifiedStart--;594595// We set these up to make the compiler happy, but they will596// be replaced before we return with the actual recursion point597midOriginalArr[0] = 0;598midModifiedArr[0] = 0;599600// Clear out the history601this.m_forwardHistory = [];602this.m_reverseHistory = [];603604// Each cell in the two arrays corresponds to a diagonal in the edit graph.605// The integer value in the cell represents the originalIndex of the furthest606// reaching point found so far that ends in that diagonal.607// The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number.608const maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart);609const numDiagonals = maxDifferences + 1;610const forwardPoints = new Int32Array(numDiagonals);611const reversePoints = new Int32Array(numDiagonals);612// diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart)613// diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd)614const diagonalForwardBase = (modifiedEnd - modifiedStart);615const diagonalReverseBase = (originalEnd - originalStart);616// diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the617// diagonal number (relative to diagonalForwardBase)618// diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the619// diagonal number (relative to diagonalReverseBase)620const diagonalForwardOffset = (originalStart - modifiedStart);621const diagonalReverseOffset = (originalEnd - modifiedEnd);622623// delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers624// relative to the start diagonal with diagonal numbers relative to the end diagonal.625// The Even/Oddn-ness of this delta is important for determining when we should check for overlap626const delta = diagonalReverseBase - diagonalForwardBase;627const deltaIsEven = (delta % 2 === 0);628629// Here we set up the start and end points as the furthest points found so far630// in both the forward and reverse directions, respectively631forwardPoints[diagonalForwardBase] = originalStart;632reversePoints[diagonalReverseBase] = originalEnd;633634// Remember if we quit early, and thus need to do a best-effort result instead of a real result.635quitEarlyArr[0] = false;636637638639// A couple of points:640// --With this method, we iterate on the number of differences between the two sequences.641// The more differences there actually are, the longer this will take.642// --Also, as the number of differences increases, we have to search on diagonals further643// away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse).644// --We extend on even diagonals (relative to the reference diagonal) only when numDifferences645// is even and odd diagonals only when numDifferences is odd.646for (let numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++) {647let furthestOriginalIndex = 0;648let furthestModifiedIndex = 0;649650// Run the algorithm in the forward direction651diagonalForwardStart = this.ClipDiagonalBound(diagonalForwardBase - numDifferences, numDifferences, diagonalForwardBase, numDiagonals);652diagonalForwardEnd = this.ClipDiagonalBound(diagonalForwardBase + numDifferences, numDifferences, diagonalForwardBase, numDiagonals);653for (let diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) {654// STEP 1: We extend the furthest reaching point in the present diagonal655// by looking at the diagonals above and below and picking the one whose point656// is further away from the start point (originalStart, modifiedStart)657if (diagonal === diagonalForwardStart || (diagonal < diagonalForwardEnd && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) {658originalIndex = forwardPoints[diagonal + 1];659} else {660originalIndex = forwardPoints[diagonal - 1] + 1;661}662modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset;663664// Save the current originalIndex so we can test for false overlap in step 3665const tempOriginalIndex = originalIndex;666667// STEP 2: We can continue to extend the furthest reaching point in the present diagonal668// so long as the elements are equal.669while (originalIndex < originalEnd && modifiedIndex < modifiedEnd && this.ElementsAreEqual(originalIndex + 1, modifiedIndex + 1)) {670originalIndex++;671modifiedIndex++;672}673forwardPoints[diagonal] = originalIndex;674675if (originalIndex + modifiedIndex > furthestOriginalIndex + furthestModifiedIndex) {676furthestOriginalIndex = originalIndex;677furthestModifiedIndex = modifiedIndex;678}679680// STEP 3: If delta is odd (overlap first happens on forward when delta is odd)681// and diagonal is in the range of reverse diagonals computed for numDifferences-1682// (the previous iteration; we haven't computed reverse diagonals for numDifferences yet)683// then check for overlap.684if (!deltaIsEven && Math.abs(diagonal - diagonalReverseBase) <= (numDifferences - 1)) {685if (originalIndex >= reversePoints[diagonal]) {686midOriginalArr[0] = originalIndex;687midModifiedArr[0] = modifiedIndex;688689if (tempOriginalIndex <= reversePoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {690// BINGO! We overlapped, and we have the full trace in memory!691return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,692diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,693forwardPoints, reversePoints,694originalIndex, originalEnd, midOriginalArr,695modifiedIndex, modifiedEnd, midModifiedArr,696deltaIsEven, quitEarlyArr697);698} else {699// Either false overlap, or we didn't have enough memory for the full trace700// Just return the recursion point701return null;702}703}704}705}706707// Check to see if we should be quitting early, before moving on to the next iteration.708const matchLengthOfLongest = ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2;709710if (this.ContinueProcessingPredicate !== null && !this.ContinueProcessingPredicate(furthestOriginalIndex, matchLengthOfLongest)) {711// We can't finish, so skip ahead to generating a result from what we have.712quitEarlyArr[0] = true;713714// Use the furthest distance we got in the forward direction.715midOriginalArr[0] = furthestOriginalIndex;716midModifiedArr[0] = furthestModifiedIndex;717718if (matchLengthOfLongest > 0 && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {719// Enough of the history is in memory to walk it backwards720return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,721diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,722forwardPoints, reversePoints,723originalIndex, originalEnd, midOriginalArr,724modifiedIndex, modifiedEnd, midModifiedArr,725deltaIsEven, quitEarlyArr726);727} else {728// We didn't actually remember enough of the history.729730//Since we are quitting the diff early, we need to shift back the originalStart and modified start731//back into the boundary limits since we decremented their value above beyond the boundary limit.732originalStart++;733modifiedStart++;734735return [736new DiffChange(originalStart, originalEnd - originalStart + 1,737modifiedStart, modifiedEnd - modifiedStart + 1)738];739}740}741742// Run the algorithm in the reverse direction743diagonalReverseStart = this.ClipDiagonalBound(diagonalReverseBase - numDifferences, numDifferences, diagonalReverseBase, numDiagonals);744diagonalReverseEnd = this.ClipDiagonalBound(diagonalReverseBase + numDifferences, numDifferences, diagonalReverseBase, numDiagonals);745for (let diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) {746// STEP 1: We extend the furthest reaching point in the present diagonal747// by looking at the diagonals above and below and picking the one whose point748// is further away from the start point (originalEnd, modifiedEnd)749if (diagonal === diagonalReverseStart || (diagonal < diagonalReverseEnd && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) {750originalIndex = reversePoints[diagonal + 1] - 1;751} else {752originalIndex = reversePoints[diagonal - 1];753}754modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset;755756// Save the current originalIndex so we can test for false overlap757const tempOriginalIndex = originalIndex;758759// STEP 2: We can continue to extend the furthest reaching point in the present diagonal760// as long as the elements are equal.761while (originalIndex > originalStart && modifiedIndex > modifiedStart && this.ElementsAreEqual(originalIndex, modifiedIndex)) {762originalIndex--;763modifiedIndex--;764}765reversePoints[diagonal] = originalIndex;766767// STEP 4: If delta is even (overlap first happens on reverse when delta is even)768// and diagonal is in the range of forward diagonals computed for numDifferences769// then check for overlap.770if (deltaIsEven && Math.abs(diagonal - diagonalForwardBase) <= numDifferences) {771if (originalIndex <= forwardPoints[diagonal]) {772midOriginalArr[0] = originalIndex;773midModifiedArr[0] = modifiedIndex;774775if (tempOriginalIndex >= forwardPoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) {776// BINGO! We overlapped, and we have the full trace in memory!777return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,778diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,779forwardPoints, reversePoints,780originalIndex, originalEnd, midOriginalArr,781modifiedIndex, modifiedEnd, midModifiedArr,782deltaIsEven, quitEarlyArr783);784} else {785// Either false overlap, or we didn't have enough memory for the full trace786// Just return the recursion point787return null;788}789}790}791}792793// Save current vectors to history before the next iteration794if (numDifferences <= LocalConstants.MaxDifferencesHistory) {795// We are allocating space for one extra int, which we fill with796// the index of the diagonal base index797let temp = new Int32Array(diagonalForwardEnd - diagonalForwardStart + 2);798temp[0] = diagonalForwardBase - diagonalForwardStart + 1;799MyArray.Copy2(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1);800this.m_forwardHistory.push(temp);801802temp = new Int32Array(diagonalReverseEnd - diagonalReverseStart + 2);803temp[0] = diagonalReverseBase - diagonalReverseStart + 1;804MyArray.Copy2(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1);805this.m_reverseHistory.push(temp);806}807808}809810// If we got here, then we have the full trace in history. We just have to convert it to a change list811// NOTE: This part is a bit messy812return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset,813diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset,814forwardPoints, reversePoints,815originalIndex, originalEnd, midOriginalArr,816modifiedIndex, modifiedEnd, midModifiedArr,817deltaIsEven, quitEarlyArr818);819}820821/**822* Shifts the given changes to provide a more intuitive diff.823* While the first element in a diff matches the first element after the diff,824* we shift the diff down.825*826* @param changes The list of changes to shift827* @returns The shifted changes828*/829private PrettifyChanges(changes: DiffChange[]): DiffChange[] {830831// Shift all the changes down first832for (let i = 0; i < changes.length; i++) {833const change = changes[i];834const originalStop = (i < changes.length - 1) ? changes[i + 1].originalStart : this._originalElementsOrHash.length;835const modifiedStop = (i < changes.length - 1) ? changes[i + 1].modifiedStart : this._modifiedElementsOrHash.length;836const checkOriginal = change.originalLength > 0;837const checkModified = change.modifiedLength > 0;838839while (840change.originalStart + change.originalLength < originalStop841&& change.modifiedStart + change.modifiedLength < modifiedStop842&& (!checkOriginal || this.OriginalElementsAreEqual(change.originalStart, change.originalStart + change.originalLength))843&& (!checkModified || this.ModifiedElementsAreEqual(change.modifiedStart, change.modifiedStart + change.modifiedLength))844) {845const startStrictEqual = this.ElementsAreStrictEqual(change.originalStart, change.modifiedStart);846const endStrictEqual = this.ElementsAreStrictEqual(change.originalStart + change.originalLength, change.modifiedStart + change.modifiedLength);847if (endStrictEqual && !startStrictEqual) {848// moving the change down would create an equal change, but the elements are not strict equal849break;850}851change.originalStart++;852change.modifiedStart++;853}854855const mergedChangeArr: Array<DiffChange | null> = [null];856if (i < changes.length - 1 && this.ChangesOverlap(changes[i], changes[i + 1], mergedChangeArr)) {857changes[i] = mergedChangeArr[0]!;858changes.splice(i + 1, 1);859i--;860continue;861}862}863864// Shift changes back up until we hit empty or whitespace-only lines865for (let i = changes.length - 1; i >= 0; i--) {866const change = changes[i];867868let originalStop = 0;869let modifiedStop = 0;870if (i > 0) {871const prevChange = changes[i - 1];872originalStop = prevChange.originalStart + prevChange.originalLength;873modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;874}875876const checkOriginal = change.originalLength > 0;877const checkModified = change.modifiedLength > 0;878879let bestDelta = 0;880let bestScore = this._boundaryScore(change.originalStart, change.originalLength, change.modifiedStart, change.modifiedLength);881882for (let delta = 1; ; delta++) {883const originalStart = change.originalStart - delta;884const modifiedStart = change.modifiedStart - delta;885886if (originalStart < originalStop || modifiedStart < modifiedStop) {887break;888}889890if (checkOriginal && !this.OriginalElementsAreEqual(originalStart, originalStart + change.originalLength)) {891break;892}893894if (checkModified && !this.ModifiedElementsAreEqual(modifiedStart, modifiedStart + change.modifiedLength)) {895break;896}897898const touchingPreviousChange = (originalStart === originalStop && modifiedStart === modifiedStop);899const score = (900(touchingPreviousChange ? 5 : 0)901+ this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength)902);903904if (score > bestScore) {905bestScore = score;906bestDelta = delta;907}908}909910change.originalStart -= bestDelta;911change.modifiedStart -= bestDelta;912913const mergedChangeArr: Array<DiffChange | null> = [null];914if (i > 0 && this.ChangesOverlap(changes[i - 1], changes[i], mergedChangeArr)) {915changes[i - 1] = mergedChangeArr[0]!;916changes.splice(i, 1);917i++;918continue;919}920}921922// There could be multiple longest common substrings.923// Give preference to the ones containing longer lines924if (this._hasStrings) {925for (let i = 1, len = changes.length; i < len; i++) {926const aChange = changes[i - 1];927const bChange = changes[i];928const matchedLength = bChange.originalStart - aChange.originalStart - aChange.originalLength;929const aOriginalStart = aChange.originalStart;930const bOriginalEnd = bChange.originalStart + bChange.originalLength;931const abOriginalLength = bOriginalEnd - aOriginalStart;932const aModifiedStart = aChange.modifiedStart;933const bModifiedEnd = bChange.modifiedStart + bChange.modifiedLength;934const abModifiedLength = bModifiedEnd - aModifiedStart;935// Avoid wasting a lot of time with these searches936if (matchedLength < 5 && abOriginalLength < 20 && abModifiedLength < 20) {937const t = this._findBetterContiguousSequence(938aOriginalStart, abOriginalLength,939aModifiedStart, abModifiedLength,940matchedLength941);942if (t) {943const [originalMatchStart, modifiedMatchStart] = t;944if (originalMatchStart !== aChange.originalStart + aChange.originalLength || modifiedMatchStart !== aChange.modifiedStart + aChange.modifiedLength) {945// switch to another sequence that has a better score946aChange.originalLength = originalMatchStart - aChange.originalStart;947aChange.modifiedLength = modifiedMatchStart - aChange.modifiedStart;948bChange.originalStart = originalMatchStart + matchedLength;949bChange.modifiedStart = modifiedMatchStart + matchedLength;950bChange.originalLength = bOriginalEnd - bChange.originalStart;951bChange.modifiedLength = bModifiedEnd - bChange.modifiedStart;952}953}954}955}956}957958return changes;959}960961private _findBetterContiguousSequence(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number, desiredLength: number): [number, number] | null {962if (originalLength < desiredLength || modifiedLength < desiredLength) {963return null;964}965const originalMax = originalStart + originalLength - desiredLength + 1;966const modifiedMax = modifiedStart + modifiedLength - desiredLength + 1;967let bestScore = 0;968let bestOriginalStart = 0;969let bestModifiedStart = 0;970for (let i = originalStart; i < originalMax; i++) {971for (let j = modifiedStart; j < modifiedMax; j++) {972const score = this._contiguousSequenceScore(i, j, desiredLength);973if (score > 0 && score > bestScore) {974bestScore = score;975bestOriginalStart = i;976bestModifiedStart = j;977}978}979}980if (bestScore > 0) {981return [bestOriginalStart, bestModifiedStart];982}983return null;984}985986private _contiguousSequenceScore(originalStart: number, modifiedStart: number, length: number): number {987let score = 0;988for (let l = 0; l < length; l++) {989if (!this.ElementsAreEqual(originalStart + l, modifiedStart + l)) {990return 0;991}992score += this._originalStringElements[originalStart + l].length;993}994return score;995}996997private _OriginalIsBoundary(index: number): boolean {998if (index <= 0 || index >= this._originalElementsOrHash.length - 1) {999return true;1000}1001return (this._hasStrings && /^\s*$/.test(this._originalStringElements[index]));1002}10031004private _OriginalRegionIsBoundary(originalStart: number, originalLength: number): boolean {1005if (this._OriginalIsBoundary(originalStart) || this._OriginalIsBoundary(originalStart - 1)) {1006return true;1007}1008if (originalLength > 0) {1009const originalEnd = originalStart + originalLength;1010if (this._OriginalIsBoundary(originalEnd - 1) || this._OriginalIsBoundary(originalEnd)) {1011return true;1012}1013}1014return false;1015}10161017private _ModifiedIsBoundary(index: number): boolean {1018if (index <= 0 || index >= this._modifiedElementsOrHash.length - 1) {1019return true;1020}1021return (this._hasStrings && /^\s*$/.test(this._modifiedStringElements[index]));1022}10231024private _ModifiedRegionIsBoundary(modifiedStart: number, modifiedLength: number): boolean {1025if (this._ModifiedIsBoundary(modifiedStart) || this._ModifiedIsBoundary(modifiedStart - 1)) {1026return true;1027}1028if (modifiedLength > 0) {1029const modifiedEnd = modifiedStart + modifiedLength;1030if (this._ModifiedIsBoundary(modifiedEnd - 1) || this._ModifiedIsBoundary(modifiedEnd)) {1031return true;1032}1033}1034return false;1035}10361037private _boundaryScore(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number): number {1038const originalScore = (this._OriginalRegionIsBoundary(originalStart, originalLength) ? 1 : 0);1039const modifiedScore = (this._ModifiedRegionIsBoundary(modifiedStart, modifiedLength) ? 1 : 0);1040return (originalScore + modifiedScore);1041}10421043/**1044* Concatenates the two input DiffChange lists and returns the resulting1045* list.1046* @param The left changes1047* @param The right changes1048* @returns The concatenated list1049*/1050private ConcatenateChanges(left: DiffChange[], right: DiffChange[]): DiffChange[] {1051const mergedChangeArr: DiffChange[] = [];10521053if (left.length === 0 || right.length === 0) {1054return (right.length > 0) ? right : left;1055} else if (this.ChangesOverlap(left[left.length - 1], right[0], mergedChangeArr)) {1056// Since we break the problem down recursively, it is possible that we1057// might recurse in the middle of a change thereby splitting it into1058// two changes. Here in the combining stage, we detect and fuse those1059// changes back together1060const result = new Array<DiffChange>(left.length + right.length - 1);1061MyArray.Copy(left, 0, result, 0, left.length - 1);1062result[left.length - 1] = mergedChangeArr[0];1063MyArray.Copy(right, 1, result, left.length, right.length - 1);10641065return result;1066} else {1067const result = new Array<DiffChange>(left.length + right.length);1068MyArray.Copy(left, 0, result, 0, left.length);1069MyArray.Copy(right, 0, result, left.length, right.length);10701071return result;1072}1073}10741075/**1076* Returns true if the two changes overlap and can be merged into a single1077* change1078* @param left The left change1079* @param right The right change1080* @param mergedChange The merged change if the two overlap, null otherwise1081* @returns True if the two changes overlap1082*/1083private ChangesOverlap(left: DiffChange, right: DiffChange, mergedChangeArr: Array<DiffChange | null>): boolean {1084Debug.Assert(left.originalStart <= right.originalStart, 'Left change is not less than or equal to right change');1085Debug.Assert(left.modifiedStart <= right.modifiedStart, 'Left change is not less than or equal to right change');10861087if (left.originalStart + left.originalLength >= right.originalStart || left.modifiedStart + left.modifiedLength >= right.modifiedStart) {1088const originalStart = left.originalStart;1089let originalLength = left.originalLength;1090const modifiedStart = left.modifiedStart;1091let modifiedLength = left.modifiedLength;10921093if (left.originalStart + left.originalLength >= right.originalStart) {1094originalLength = right.originalStart + right.originalLength - left.originalStart;1095}1096if (left.modifiedStart + left.modifiedLength >= right.modifiedStart) {1097modifiedLength = right.modifiedStart + right.modifiedLength - left.modifiedStart;1098}10991100mergedChangeArr[0] = new DiffChange(originalStart, originalLength, modifiedStart, modifiedLength);1101return true;1102} else {1103mergedChangeArr[0] = null;1104return false;1105}1106}11071108/**1109* Helper method used to clip a diagonal index to the range of valid1110* diagonals. This also decides whether or not the diagonal index,1111* if it exceeds the boundary, should be clipped to the boundary or clipped1112* one inside the boundary depending on the Even/Odd status of the boundary1113* and numDifferences.1114* @param diagonal The index of the diagonal to clip.1115* @param numDifferences The current number of differences being iterated upon.1116* @param diagonalBaseIndex The base reference diagonal.1117* @param numDiagonals The total number of diagonals.1118* @returns The clipped diagonal index.1119*/1120private ClipDiagonalBound(diagonal: number, numDifferences: number, diagonalBaseIndex: number, numDiagonals: number): number {1121if (diagonal >= 0 && diagonal < numDiagonals) {1122// Nothing to clip, its in range1123return diagonal;1124}11251126// diagonalsBelow: The number of diagonals below the reference diagonal1127// diagonalsAbove: The number of diagonals above the reference diagonal1128const diagonalsBelow = diagonalBaseIndex;1129const diagonalsAbove = numDiagonals - diagonalBaseIndex - 1;1130const diffEven = (numDifferences % 2 === 0);11311132if (diagonal < 0) {1133const lowerBoundEven = (diagonalsBelow % 2 === 0);1134return (diffEven === lowerBoundEven) ? 0 : 1;1135} else {1136const upperBoundEven = (diagonalsAbove % 2 === 0);1137return (diffEven === upperBoundEven) ? numDiagonals - 1 : numDiagonals - 2;1138}1139}1140}114111421143/**1144* Precomputed equality array for character codes.1145*/1146const precomputedEqualityArray = new Uint32Array(0x10000);11471148/**1149* Computes the Levenshtein distance for strings of length <= 32.1150* @param firstString - The first string.1151* @param secondString - The second string.1152* @returns The Levenshtein distance.1153*/1154const computeLevenshteinDistanceForShortStrings = (firstString: string, secondString: string): number => {1155const firstStringLength = firstString.length;1156const secondStringLength = secondString.length;1157const lastBitMask = 1 << (firstStringLength - 1);1158let positiveVector = -1;1159let negativeVector = 0;1160let distance = firstStringLength;1161let index = firstStringLength;11621163// Initialize precomputedEqualityArray for firstString1164while (index--) {1165precomputedEqualityArray[firstString.charCodeAt(index)] |= 1 << index;1166}11671168// Process each character of secondString1169for (index = 0; index < secondStringLength; index++) {1170let equalityMask = precomputedEqualityArray[secondString.charCodeAt(index)];1171const combinedVector = equalityMask | negativeVector;1172equalityMask |= ((equalityMask & positiveVector) + positiveVector) ^ positiveVector;1173negativeVector |= ~(equalityMask | positiveVector);1174positiveVector &= equalityMask;1175if (negativeVector & lastBitMask) {1176distance++;1177}1178if (positiveVector & lastBitMask) {1179distance--;1180}1181negativeVector = (negativeVector << 1) | 1;1182positiveVector = (positiveVector << 1) | ~(combinedVector | negativeVector);1183negativeVector &= combinedVector;1184}11851186// Reset precomputedEqualityArray1187index = firstStringLength;1188while (index--) {1189precomputedEqualityArray[firstString.charCodeAt(index)] = 0;1190}11911192return distance;1193};11941195/**1196* Computes the Levenshtein distance for strings of length > 32.1197* @param firstString - The first string.1198* @param secondString - The second string.1199* @returns The Levenshtein distance.1200*/1201function computeLevenshteinDistanceForLongStrings(firstString: string, secondString: string): number {1202const firstStringLength = firstString.length;1203const secondStringLength = secondString.length;1204const horizontalBitArray = [];1205const verticalBitArray = [];1206const horizontalSize = Math.ceil(firstStringLength / 32);1207const verticalSize = Math.ceil(secondStringLength / 32);12081209// Initialize horizontal and vertical bit arrays1210for (let i = 0; i < horizontalSize; i++) {1211horizontalBitArray[i] = -1;1212verticalBitArray[i] = 0;1213}12141215let verticalIndex = 0;1216for (; verticalIndex < verticalSize - 1; verticalIndex++) {1217let negativeVector = 0;1218let positiveVector = -1;1219const start = verticalIndex * 32;1220const verticalLength = Math.min(32, secondStringLength) + start;12211222// Initialize precomputedEqualityArray for secondString1223for (let k = start; k < verticalLength; k++) {1224precomputedEqualityArray[secondString.charCodeAt(k)] |= 1 << k;1225}12261227// Process each character of firstString1228for (let i = 0; i < firstStringLength; i++) {1229const equalityMask = precomputedEqualityArray[firstString.charCodeAt(i)];1230const previousBit = (horizontalBitArray[(i / 32) | 0] >>> i) & 1;1231const matchBit = (verticalBitArray[(i / 32) | 0] >>> i) & 1;1232const combinedVector = equalityMask | negativeVector;1233const combinedHorizontalVector = ((((equalityMask | matchBit) & positiveVector) + positiveVector) ^ positiveVector) | equalityMask | matchBit;1234let positiveHorizontalVector = negativeVector | ~(combinedHorizontalVector | positiveVector);1235let negativeHorizontalVector = positiveVector & combinedHorizontalVector;1236if ((positiveHorizontalVector >>> 31) ^ previousBit) {1237horizontalBitArray[(i / 32) | 0] ^= 1 << i;1238}1239if ((negativeHorizontalVector >>> 31) ^ matchBit) {1240verticalBitArray[(i / 32) | 0] ^= 1 << i;1241}1242positiveHorizontalVector = (positiveHorizontalVector << 1) | previousBit;1243negativeHorizontalVector = (negativeHorizontalVector << 1) | matchBit;1244positiveVector = negativeHorizontalVector | ~(combinedVector | positiveHorizontalVector);1245negativeVector = positiveHorizontalVector & combinedVector;1246}12471248// Reset precomputedEqualityArray1249for (let k = start; k < verticalLength; k++) {1250precomputedEqualityArray[secondString.charCodeAt(k)] = 0;1251}1252}12531254let negativeVector = 0;1255let positiveVector = -1;1256const start = verticalIndex * 32;1257const verticalLength = Math.min(32, secondStringLength - start) + start;12581259// Initialize precomputedEqualityArray for secondString1260for (let k = start; k < verticalLength; k++) {1261precomputedEqualityArray[secondString.charCodeAt(k)] |= 1 << k;1262}12631264let distance = secondStringLength;12651266// Process each character of firstString1267for (let i = 0; i < firstStringLength; i++) {1268const equalityMask = precomputedEqualityArray[firstString.charCodeAt(i)];1269const previousBit = (horizontalBitArray[(i / 32) | 0] >>> i) & 1;1270const matchBit = (verticalBitArray[(i / 32) | 0] >>> i) & 1;1271const combinedVector = equalityMask | negativeVector;1272const combinedHorizontalVector = ((((equalityMask | matchBit) & positiveVector) + positiveVector) ^ positiveVector) | equalityMask | matchBit;1273let positiveHorizontalVector = negativeVector | ~(combinedHorizontalVector | positiveVector);1274let negativeHorizontalVector = positiveVector & combinedHorizontalVector;1275distance += (positiveHorizontalVector >>> (secondStringLength - 1)) & 1;1276distance -= (negativeHorizontalVector >>> (secondStringLength - 1)) & 1;1277if ((positiveHorizontalVector >>> 31) ^ previousBit) {1278horizontalBitArray[(i / 32) | 0] ^= 1 << i;1279}1280if ((negativeHorizontalVector >>> 31) ^ matchBit) {1281verticalBitArray[(i / 32) | 0] ^= 1 << i;1282}1283positiveHorizontalVector = (positiveHorizontalVector << 1) | previousBit;1284negativeHorizontalVector = (negativeHorizontalVector << 1) | matchBit;1285positiveVector = negativeHorizontalVector | ~(combinedVector | positiveHorizontalVector);1286negativeVector = positiveHorizontalVector & combinedVector;1287}12881289// Reset precomputedEqualityArray1290for (let k = start; k < verticalLength; k++) {1291precomputedEqualityArray[secondString.charCodeAt(k)] = 0;1292}12931294return distance;1295}12961297/**1298* Computes the Levenshtein distance between two strings.1299* @param firstString - The first string.1300* @param secondString - The second string.1301* @returns The Levenshtein distance.1302*/1303export function computeLevenshteinDistance(firstString: string, secondString: string): number {1304if (firstString.length < secondString.length) {1305const temp = secondString;1306secondString = firstString;1307firstString = temp;1308}1309if (secondString.length === 0) {1310return firstString.length;1311}1312if (firstString.length <= 32) {1313return computeLevenshteinDistanceForShortStrings(firstString, secondString);1314}1315return computeLevenshteinDistanceForLongStrings(firstString, secondString);1316}131713181319