UNPKG

use-monaco

Version:

[![npm](https://img.shields.io/npm/v/use-monaco)](https://npm.im/use-monaco)

1,232 lines (1,201 loc) 305 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var MonacoWorker = /*#__PURE__*/function () { function MonacoWorker(_ctx, _options) { this.ctx = void 0; this.options = void 0; this.ctx = _ctx; this.options = _options; } var _proto = MonacoWorker.prototype; _proto.getModels = function getModels() { return this.ctx.getMirrorModels(); }; _proto.getModel = function getModel(uri) { var models = this.getModels(); var _loop = function _loop(index) { var model = models[index]; if (model.uri.toString() === uri) { Object.assign(model, { getFullModelRange: function getFullModelRange() { return { startLineNumber: 1, endLineNumber: model.getLineCount(), startColumn: 1, endColumn: model.getLineContent(model.getLineCount()).length + 1 }; } }); return { v: model }; } }; for (var index = 0; index < models.length; index++) { var _ret = _loop(index); if (typeof _ret === "object") return _ret.v; } return null; }; _proto.getText = function getText(uri) { return this.getModel(uri).getValue() || ''; } // proxy function used by the client to delegate different kind of providers ; _proto.provide = function provide(provider, uri) { var providerFunc = 'provide' + provider.charAt(0).toUpperCase() + provider.slice(1); if (this[providerFunc]) { var _ref; for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } return (_ref = this)[providerFunc].apply(_ref, [this.getModel(uri)].concat(args)); } else { console.error("No provider for " + provider); return null; } } // proxy function used by the client to delegate different kind of resolvers ; _proto.resolve = function resolve(resolver, uri) { var resolverFunc = 'resolve' + resolver.charAt(0).toUpperCase() + resolver.slice(1); if (this[resolverFunc]) { var _ref2; for (var _len2 = arguments.length, args = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { args[_key2 - 2] = arguments[_key2]; } return (_ref2 = this)[resolverFunc].apply(_ref2, [this.getModel(uri)].concat(args)); } else { console.error("No resolver for " + resolver); return null; } }; return MonacoWorker; }(); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Avoid circular dependency on EventEmitter by implementing a subset of the interface. class ErrorHandler { constructor() { this.listeners = []; this.unexpectedErrorHandler = function (e) { setTimeout(() => { if (e.stack) { throw new Error(e.message + '\n\n' + e.stack); } throw e; }, 0); }; } emit(e) { this.listeners.forEach((listener) => { listener(e); }); } onUnexpectedError(e) { this.unexpectedErrorHandler(e); this.emit(e); } // For external errors, we don't want the listeners to be called onUnexpectedExternalError(e) { this.unexpectedErrorHandler(e); } } const errorHandler = new ErrorHandler(); function onUnexpectedError(e) { // ignore errors from cancelled promises if (!isPromiseCanceledError(e)) { errorHandler.onUnexpectedError(e); } return undefined; } function transformErrorForSerialization(error) { if (error instanceof Error) { let { name, message } = error; const stack = error.stacktrace || error.stack; return { $isError: true, name, message, stack }; } // return as is return error; } const canceledName = 'Canceled'; /** * Checks if the given error is a promise in canceled state */ function isPromiseCanceledError(error) { return error instanceof Error && error.name === canceledName && error.message === canceledName; } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var Iterable; (function (Iterable) { function is(thing) { return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; } Iterable.is = is; const _empty = Object.freeze([]); function empty() { return _empty; } Iterable.empty = empty; function* single(element) { yield element; } Iterable.single = single; function from(iterable) { return iterable || _empty; } Iterable.from = from; function first(iterable) { return iterable[Symbol.iterator]().next().value; } Iterable.first = first; function some(iterable, predicate) { for (const element of iterable) { if (predicate(element)) { return true; } } return false; } Iterable.some = some; function* filter(iterable, predicate) { for (const element of iterable) { if (predicate(element)) { yield element; } } } Iterable.filter = filter; function* map(iterable, fn) { for (const element of iterable) { yield fn(element); } } Iterable.map = map; function* concat(...iterables) { for (const iterable of iterables) { for (const element of iterable) { yield element; } } } Iterable.concat = concat; /** * Consumes `atMost` elements from iterable and returns the consumed elements, * and an iterable for the rest of the elements. */ function consume(iterable, atMost = Number.POSITIVE_INFINITY) { const consumed = []; if (atMost === 0) { return [consumed, iterable]; } const iterator = iterable[Symbol.iterator](); for (let i = 0; i < atMost; i++) { const next = iterator.next(); if (next.done) { return [consumed, Iterable.empty()]; } consumed.push(next.value); } return [consumed, { [Symbol.iterator]() { return iterator; } }]; } Iterable.consume = consume; })(Iterable || (Iterable = {})); function markTracked(x) { { return; } } function trackDisposable(x) { { return x; } } class MultiDisposeError extends Error { constructor(errors) { super(`Encounter errors while disposing of store. Errors: [${errors.join(', ')}]`); this.errors = errors; } } function dispose(arg) { if (Iterable.is(arg)) { let errors = []; for (const d of arg) { if (d) { try { d.dispose(); } catch (e) { errors.push(e); } } } if (errors.length === 1) { throw errors[0]; } else if (errors.length > 1) { throw new MultiDisposeError(errors); } return Array.isArray(arg) ? [] : arg; } else if (arg) { arg.dispose(); return arg; } } function combinedDisposable(...disposables) { disposables.forEach(markTracked); return trackDisposable({ dispose: () => dispose(disposables) }); } class DisposableStore { constructor() { this._toDispose = new Set(); this._isDisposed = false; } /** * Dispose of all registered disposables and mark this object as disposed. * * Any future disposables added to this object will be disposed of on `add`. */ dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this.clear(); } /** * Dispose of all registered disposables but do not mark this object as disposed. */ clear() { try { dispose(this._toDispose.values()); } finally { this._toDispose.clear(); } } add(t) { if (!t) { return t; } if (t === this) { throw new Error('Cannot register a disposable on itself!'); } if (this._isDisposed) { if (!DisposableStore.DISABLE_DISPOSED_WARNING) { console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); } } else { this._toDispose.add(t); } return t; } } DisposableStore.DISABLE_DISPOSED_WARNING = false; class Disposable { constructor() { this._store = new DisposableStore(); } dispose() { this._store.dispose(); } _register(t) { if (t === this) { throw new Error('Cannot register a disposable on itself!'); } return this._store.add(t); } } Disposable.None = Object.freeze({ dispose() { } }); /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const LANGUAGE_DEFAULT = 'en'; let _isWindows = false; let _isMacintosh = false; let _isLinux = false; let _isIOS = false; let _locale = undefined; let _language = LANGUAGE_DEFAULT; let _translationsConfigFile = undefined; let _userAgent = undefined; const isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer'); // OS detection if (typeof navigator === 'object' && !isElectronRenderer) { _userAgent = navigator.userAgent; _isWindows = _userAgent.indexOf('Windows') >= 0; _isMacintosh = _userAgent.indexOf('Macintosh') >= 0; _isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0; _isLinux = _userAgent.indexOf('Linux') >= 0; _locale = navigator.language; _language = _locale; } else if (typeof process === 'object') { _isWindows = (process.platform === 'win32'); _isMacintosh = (process.platform === 'darwin'); _isLinux = (process.platform === 'linux'); _locale = LANGUAGE_DEFAULT; _language = LANGUAGE_DEFAULT; const rawNlsConfig = process.env['VSCODE_NLS_CONFIG']; if (rawNlsConfig) { try { const nlsConfig = JSON.parse(rawNlsConfig); const resolved = nlsConfig.availableLanguages['*']; _locale = nlsConfig.locale; // VSCode's default language is 'en' _language = resolved ? resolved : LANGUAGE_DEFAULT; _translationsConfigFile = nlsConfig._translationsConfigFile; } catch (e) { } } } const isWindows = _isWindows; const isMacintosh = _isMacintosh; const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); const globals = _globals; const setImmediate = (function defineSetImmediate() { if (globals.setImmediate) { return globals.setImmediate.bind(globals); } if (typeof globals.postMessage === 'function' && !globals.importScripts) { let pending = []; globals.addEventListener('message', (e) => { if (e.data && e.data.vscodeSetImmediateId) { for (let i = 0, len = pending.length; i < len; i++) { const candidate = pending[i]; if (candidate.id === e.data.vscodeSetImmediateId) { pending.splice(i, 1); candidate.callback(); return; } } } }); let lastId = 0; return (callback) => { const myId = ++lastId; pending.push({ id: myId, callback: callback }); globals.postMessage({ vscodeSetImmediateId: myId }, '*'); }; } if (typeof process !== 'undefined' && typeof process.nextTick === 'function') { return process.nextTick.bind(process); } const _promise = Promise.resolve(); return (callback) => _promise.then(callback); })(); /** * @returns whether the provided parameter is a JavaScript Array or not. */ function getAllPropertyNames(obj) { let res = []; let proto = Object.getPrototypeOf(obj); while (Object.prototype !== proto) { res = res.concat(Object.getOwnPropertyNames(proto)); proto = Object.getPrototypeOf(proto); } return res; } function getAllMethodNames(obj) { const methods = []; for (const prop of getAllPropertyNames(obj)) { if (typeof obj[prop] === 'function') { methods.push(prop); } } return methods; } function createProxyObject(methodNames, invoke) { const createProxyMethod = (method) => { return function () { const args = Array.prototype.slice.call(arguments, 0); return invoke(method, args); }; }; let result = {}; for (const methodName of methodNames) { result[methodName] = createProxyMethod(methodName); } return result; } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const INITIALIZE = '$initialize'; class SimpleWorkerProtocol { constructor(handler) { this._workerId = -1; this._handler = handler; this._lastSentReq = 0; this._pendingReplies = Object.create(null); } setWorkerId(workerId) { this._workerId = workerId; } sendMessage(method, args) { let req = String(++this._lastSentReq); return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject }; this._send({ vsWorker: this._workerId, req: req, method: method, args: args }); }); } handleMessage(message) { if (!message || !message.vsWorker) { return; } if (this._workerId !== -1 && message.vsWorker !== this._workerId) { return; } this._handleMessage(message); } _handleMessage(msg) { if (msg.seq) { let replyMessage = msg; if (!this._pendingReplies[replyMessage.seq]) { console.warn('Got reply to unknown seq'); return; } let reply = this._pendingReplies[replyMessage.seq]; delete this._pendingReplies[replyMessage.seq]; if (replyMessage.err) { let err = replyMessage.err; if (replyMessage.err.$isError) { err = new Error(); err.name = replyMessage.err.name; err.message = replyMessage.err.message; err.stack = replyMessage.err.stack; } reply.reject(err); return; } reply.resolve(replyMessage.res); return; } let requestMessage = msg; let req = requestMessage.req; let result = this._handler.handleMessage(requestMessage.method, requestMessage.args); result.then((r) => { this._send({ vsWorker: this._workerId, seq: req, res: r, err: undefined }); }, (e) => { if (e.detail instanceof Error) { // Loading errors have a detail property that points to the actual error e.detail = transformErrorForSerialization(e.detail); } this._send({ vsWorker: this._workerId, seq: req, res: undefined, err: transformErrorForSerialization(e) }); }); } _send(msg) { let transfer = []; if (msg.req) { const m = msg; for (let i = 0; i < m.args.length; i++) { if (m.args[i] instanceof ArrayBuffer) { transfer.push(m.args[i]); } } } else { const m = msg; if (m.res instanceof ArrayBuffer) { transfer.push(m.res); } } this._handler.sendMessage(msg, transfer); } } /** * Worker side */ class SimpleWorkerServer { constructor(postMessage, requestHandlerFactory) { this._requestHandlerFactory = requestHandlerFactory; this._requestHandler = null; this._protocol = new SimpleWorkerProtocol({ sendMessage: (msg, transfer) => { postMessage(msg, transfer); }, handleMessage: (method, args) => this._handleMessage(method, args) }); } onmessage(msg) { this._protocol.handleMessage(msg); } _handleMessage(method, args) { if (method === INITIALIZE) { return this.initialize(args[0], args[1], args[2], args[3]); } if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') { return Promise.reject(new Error('Missing requestHandler or method: ' + method)); } try { return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args)); } catch (e) { return Promise.reject(e); } } initialize(workerId, loaderConfig, moduleId, hostMethods) { this._protocol.setWorkerId(workerId); const proxyMethodRequest = (method, args) => { return this._protocol.sendMessage(method, args); }; const hostProxy = createProxyObject(hostMethods, proxyMethodRequest); if (this._requestHandlerFactory) { // static request handler this._requestHandler = this._requestHandlerFactory(hostProxy); return Promise.resolve(getAllMethodNames(this._requestHandler)); } if (loaderConfig) { // Remove 'baseUrl', handling it is beyond scope for now if (typeof loaderConfig.baseUrl !== 'undefined') { delete loaderConfig['baseUrl']; } if (typeof loaderConfig.paths !== 'undefined') { if (typeof loaderConfig.paths.vs !== 'undefined') { delete loaderConfig.paths['vs']; } } // Since this is in a web worker, enable catching errors loaderConfig.catchError = true; self.require.config(loaderConfig); } return new Promise((resolve, reject) => { // Use the global require to be sure to get the global config self.require([moduleId], (module) => { this._requestHandler = module.create(hostProxy); if (!this._requestHandler) { reject(new Error(`No RequestHandler!`)); return; } resolve(getAllMethodNames(this._requestHandler)); }, reject); }); } } /** * Returns the last element of an array. * @param array The array. * @param n Which element from the end (default is zero). */ /** * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` * so only use this when actually needing stable sort. */ function mergeSort(data, compare) { _sort(data, compare, 0, data.length - 1, []); return data; } function _merge(a, compare, lo, mid, hi, aux) { let leftIdx = lo, rightIdx = mid + 1; for (let i = lo; i <= hi; i++) { aux[i] = a[i]; } for (let i = lo; i <= hi; i++) { if (leftIdx > mid) { // left side consumed a[i] = aux[rightIdx++]; } else if (rightIdx > hi) { // right side consumed a[i] = aux[leftIdx++]; } else if (compare(aux[rightIdx], aux[leftIdx]) < 0) { // right element is less -> comes first a[i] = aux[rightIdx++]; } else { // left element comes first (less or equal) a[i] = aux[leftIdx++]; } } } function _sort(a, compare, lo, hi, aux) { if (hi <= lo) { return; } const mid = lo + ((hi - lo) / 2) | 0; _sort(a, compare, lo, mid, aux); _sort(a, compare, mid + 1, hi, aux); if (compare(a[mid], a[mid + 1]) <= 0) { // left and right are sorted and if the last-left element is less // or equals than the first-right element there is nothing else // to do return; } _merge(a, compare, lo, mid, hi, aux); } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /** * Represents information about a specific difference between two sequences. */ class DiffChange { /** * Constructs a new DiffChange with the given sequence information * and content. */ constructor(originalStart, originalLength, modifiedStart, modifiedLength) { //Debug.Assert(originalLength > 0 || modifiedLength > 0, "originalLength and modifiedLength cannot both be <= 0"); this.originalStart = originalStart; this.originalLength = originalLength; this.modifiedStart = modifiedStart; this.modifiedLength = modifiedLength; } /** * The end point (exclusive) of the change in the original sequence. */ getOriginalEnd() { return this.originalStart + this.originalLength; } /** * The end point (exclusive) of the change in the modified sequence. */ getModifiedEnd() { return this.modifiedStart + this.modifiedLength; } } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /** * Returns first index of the string that is not whitespace. * If string is empty or contains only whitespaces, returns -1 */ function firstNonWhitespaceIndex(str) { for (let i = 0, len = str.length; i < len; i++) { const chCode = str.charCodeAt(i); if (chCode !== 32 /* Space */ && chCode !== 9 /* Tab */) { return i; } } return -1; } /** * Returns last index of the string that is not whitespace. * If string is empty or contains only whitespaces, returns -1 */ function lastNonWhitespaceIndex(str, startIndex = str.length - 1) { for (let i = startIndex; i >= 0; i--) { const chCode = str.charCodeAt(i); if (chCode !== 32 /* Space */ && chCode !== 9 /* Tab */) { return i; } } return -1; } //#endregion /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ function numberHash(val, initialHashVal) { return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32 } function stringHash(s, hashVal) { hashVal = numberHash(149417, hashVal); for (let i = 0, length = s.length; i < length; i++) { hashVal = numberHash(s.charCodeAt(i), hashVal); } return hashVal; } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ class StringDiffSequence { constructor(source) { this.source = source; } getElements() { const source = this.source; const characters = new Int32Array(source.length); for (let i = 0, len = source.length; i < len; i++) { characters[i] = source.charCodeAt(i); } return characters; } } function stringDiff(original, modified, pretty) { return new LcsDiff(new StringDiffSequence(original), new StringDiffSequence(modified)).ComputeDiff(pretty).changes; } // // The code below has been ported from a C# implementation in VS // class Debug { static Assert(condition, message) { if (!condition) { throw new Error(message); } } } class MyArray { /** * Copies a range of elements from an Array starting at the specified source index and pastes * them to another Array starting at the specified destination index. The length and the indexes * are specified as 64-bit integers. * sourceArray: * The Array that contains the data to copy. * sourceIndex: * A 64-bit integer that represents the index in the sourceArray at which copying begins. * destinationArray: * The Array that receives the data. * destinationIndex: * A 64-bit integer that represents the index in the destinationArray at which storing begins. * length: * A 64-bit integer that represents the number of elements to copy. */ static Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length) { for (let i = 0; i < length; i++) { destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i]; } } static Copy2(sourceArray, sourceIndex, destinationArray, destinationIndex, length) { for (let i = 0; i < length; i++) { destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i]; } } } /** * A utility class which helps to create the set of DiffChanges from * a difference operation. This class accepts original DiffElements and * modified DiffElements that are involved in a particular change. The * MarktNextChange() method can be called to mark the separation between * distinct changes. At the end, the Changes property can be called to retrieve * the constructed changes. */ class DiffChangeHelper { /** * Constructs a new DiffChangeHelper for the given DiffSequences. */ constructor() { this.m_changes = []; this.m_originalStart = 1073741824 /* MAX_SAFE_SMALL_INTEGER */; this.m_modifiedStart = 1073741824 /* MAX_SAFE_SMALL_INTEGER */; this.m_originalCount = 0; this.m_modifiedCount = 0; } /** * Marks the beginning of the next change in the set of differences. */ MarkNextChange() { // Only add to the list if there is something to add if (this.m_originalCount > 0 || this.m_modifiedCount > 0) { // Add the new change to our list this.m_changes.push(new DiffChange(this.m_originalStart, this.m_originalCount, this.m_modifiedStart, this.m_modifiedCount)); } // Reset for the next change this.m_originalCount = 0; this.m_modifiedCount = 0; this.m_originalStart = 1073741824 /* MAX_SAFE_SMALL_INTEGER */; this.m_modifiedStart = 1073741824 /* MAX_SAFE_SMALL_INTEGER */; } /** * Adds the original element at the given position to the elements * affected by the current change. The modified index gives context * to the change position with respect to the original sequence. * @param originalIndex The index of the original element to add. * @param modifiedIndex The index of the modified element that provides corresponding position in the modified sequence. */ AddOriginalElement(originalIndex, modifiedIndex) { // The 'true' start index is the smallest of the ones we've seen this.m_originalStart = Math.min(this.m_originalStart, originalIndex); this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex); this.m_originalCount++; } /** * Adds the modified element at the given position to the elements * affected by the current change. The original index gives context * to the change position with respect to the modified sequence. * @param originalIndex The index of the original element that provides corresponding position in the original sequence. * @param modifiedIndex The index of the modified element to add. */ AddModifiedElement(originalIndex, modifiedIndex) { // The 'true' start index is the smallest of the ones we've seen this.m_originalStart = Math.min(this.m_originalStart, originalIndex); this.m_modifiedStart = Math.min(this.m_modifiedStart, modifiedIndex); this.m_modifiedCount++; } /** * Retrieves all of the changes marked by the class. */ getChanges() { if (this.m_originalCount > 0 || this.m_modifiedCount > 0) { // Finish up on whatever is left this.MarkNextChange(); } return this.m_changes; } /** * Retrieves all of the changes marked by the class in the reverse order */ getReverseChanges() { if (this.m_originalCount > 0 || this.m_modifiedCount > 0) { // Finish up on whatever is left this.MarkNextChange(); } this.m_changes.reverse(); return this.m_changes; } } /** * An implementation of the difference algorithm described in * "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers */ class LcsDiff { /** * Constructs the DiffFinder */ constructor(originalSequence, modifiedSequence, continueProcessingPredicate = null) { this.ContinueProcessingPredicate = continueProcessingPredicate; const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence); const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence); this._hasStrings = (originalHasStrings && modifiedHasStrings); this._originalStringElements = originalStringElements; this._originalElementsOrHash = originalElementsOrHash; this._modifiedStringElements = modifiedStringElements; this._modifiedElementsOrHash = modifiedElementsOrHash; this.m_forwardHistory = []; this.m_reverseHistory = []; } static _isStringArray(arr) { return (arr.length > 0 && typeof arr[0] === 'string'); } static _getElements(sequence) { const elements = sequence.getElements(); if (LcsDiff._isStringArray(elements)) { const hashes = new Int32Array(elements.length); for (let i = 0, len = elements.length; i < len; i++) { hashes[i] = stringHash(elements[i], 0); } return [elements, hashes, true]; } if (elements instanceof Int32Array) { return [[], elements, false]; } return [[], new Int32Array(elements), false]; } ElementsAreEqual(originalIndex, newIndex) { if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) { return false; } return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true); } OriginalElementsAreEqual(index1, index2) { if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) { return false; } return (this._hasStrings ? this._originalStringElements[index1] === this._originalStringElements[index2] : true); } ModifiedElementsAreEqual(index1, index2) { if (this._modifiedElementsOrHash[index1] !== this._modifiedElementsOrHash[index2]) { return false; } return (this._hasStrings ? this._modifiedStringElements[index1] === this._modifiedStringElements[index2] : true); } ComputeDiff(pretty) { return this._ComputeDiff(0, this._originalElementsOrHash.length - 1, 0, this._modifiedElementsOrHash.length - 1, pretty); } /** * Computes the differences between the original and modified input * sequences on the bounded range. * @returns An array of the differences between the two input sequences. */ _ComputeDiff(originalStart, originalEnd, modifiedStart, modifiedEnd, pretty) { const quitEarlyArr = [false]; let changes = this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr); if (pretty) { // We have to clean up the computed diff to be more intuitive // but it turns out this cannot be done correctly until the entire set // of diffs have been computed changes = this.PrettifyChanges(changes); } return { quitEarly: quitEarlyArr[0], changes: changes }; } /** * Private helper method which computes the differences on the bounded range * recursively. * @returns An array of the differences between the two input sequences. */ ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr) { quitEarlyArr[0] = false; // Find the start of the differences while (originalStart <= originalEnd && modifiedStart <= modifiedEnd && this.ElementsAreEqual(originalStart, modifiedStart)) { originalStart++; modifiedStart++; } // Find the end of the differences while (originalEnd >= originalStart && modifiedEnd >= modifiedStart && this.ElementsAreEqual(originalEnd, modifiedEnd)) { originalEnd--; modifiedEnd--; } // In the special case where we either have all insertions or all deletions or the sequences are identical if (originalStart > originalEnd || modifiedStart > modifiedEnd) { let changes; if (modifiedStart <= modifiedEnd) { Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd'); // All insertions changes = [ new DiffChange(originalStart, 0, modifiedStart, modifiedEnd - modifiedStart + 1) ]; } else if (originalStart <= originalEnd) { Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd'); // All deletions changes = [ new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, 0) ]; } else { Debug.Assert(originalStart === originalEnd + 1, 'originalStart should only be one more than originalEnd'); Debug.Assert(modifiedStart === modifiedEnd + 1, 'modifiedStart should only be one more than modifiedEnd'); // Identical sequences - No differences changes = []; } return changes; } // This problem can be solved using the Divide-And-Conquer technique. const midOriginalArr = [0]; const midModifiedArr = [0]; const result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr); const midOriginal = midOriginalArr[0]; const midModified = midModifiedArr[0]; if (result !== null) { // Result is not-null when there was enough memory to compute the changes while // searching for the recursion point return result; } else if (!quitEarlyArr[0]) { // We can break the problem down recursively by finding the changes in the // First Half: (originalStart, modifiedStart) to (midOriginal, midModified) // Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd) // NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr); let rightChanges = []; if (!quitEarlyArr[0]) { rightChanges = this.ComputeDiffRecursive(midOriginal + 1, originalEnd, midModified + 1, modifiedEnd, quitEarlyArr); } else { // We did't have time to finish the first half, so we don't have time to compute this half. // Consider the entire rest of the sequence different. rightChanges = [ new DiffChange(midOriginal + 1, originalEnd - (midOriginal + 1) + 1, midModified + 1, modifiedEnd - (midModified + 1) + 1) ]; } return this.ConcatenateChanges(leftChanges, rightChanges); } // If we hit here, we quit early, and so can't return anything meaningful return [ new DiffChange(originalStart, originalEnd - originalStart + 1, modifiedStart, modifiedEnd - modifiedStart + 1) ]; } WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, forwardPoints, reversePoints, originalIndex, originalEnd, midOriginalArr, modifiedIndex, modifiedEnd, midModifiedArr, deltaIsEven, quitEarlyArr) { let forwardChanges = null; let reverseChanges = null; // First, walk backward through the forward diagonals history let changeHelper = new DiffChangeHelper(); let diagonalMin = diagonalForwardStart; let diagonalMax = diagonalForwardEnd; let diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalForwardOffset; let lastOriginalIndex = -1073741824 /* MIN_SAFE_SMALL_INTEGER */; let historyIndex = this.m_forwardHistory.length - 1; do { // Get the diagonal index from the relative diagonal number const diagonal = diagonalRelative + diagonalForwardBase; // Figure out where we came from if (diagonal === diagonalMin || (diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) { // Vertical line (the element is an insert) originalIndex = forwardPoints[diagonal + 1]; modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset; if (originalIndex < lastOriginalIndex) { changeHelper.MarkNextChange(); } lastOriginalIndex = originalIndex; changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex); diagonalRelative = (diagonal + 1) - diagonalForwardBase; //Setup for the next iteration } else { // Horizontal line (the element is a deletion) originalIndex = forwardPoints[diagonal - 1] + 1; modifiedIndex = originalIndex - diagonalRelative - diagonalForwardOffset; if (originalIndex < lastOriginalIndex) { changeHelper.MarkNextChange(); } lastOriginalIndex = originalIndex - 1; changeHelper.AddOriginalElement(originalIndex, modifiedIndex + 1); diagonalRelative = (diagonal - 1) - diagonalForwardBase; //Setup for the next iteration } if (historyIndex >= 0) { forwardPoints = this.m_forwardHistory[historyIndex]; diagonalForwardBase = forwardPoints[0]; //We stored this in the first spot diagonalMin = 1; diagonalMax = forwardPoints.length - 1; } } while (--historyIndex >= -1); // Ironically, we get the forward changes as the reverse of the // order we added them since we technically added them backwards forwardChanges = changeHelper.getReverseChanges(); if (quitEarlyArr[0]) { // TODO: Calculate a partial from the reverse diagonals. // For now, just assume everything after the midOriginal/midModified point is a diff let originalStartPoint = midOriginalArr[0] + 1; let modifiedStartPoint = midModifiedArr[0] + 1; if (forwardChanges !== null && forwardChanges.length > 0) { const lastForwardChange = forwardChanges[forwardChanges.length - 1]; originalStartPoint = Math.max(originalStartPoint, lastForwardChange.getOriginalEnd()); modifiedStartPoint = Math.max(modifiedStartPoint, lastForwardChange.getModifiedEnd()); } reverseChanges = [ new DiffChange(originalStartPoint, originalEnd - originalStartPoint + 1, modifiedStartPoint, modifiedEnd - modifiedStartPoint + 1) ]; } else { // Now walk backward through the reverse diagonals history changeHelper = new DiffChangeHelper(); diagonalMin = diagonalReverseStart; diagonalMax = diagonalReverseEnd; diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalReverseOffset; lastOriginalIndex = 1073741824 /* MAX_SAFE_SMALL_INTEGER */; historyIndex = (deltaIsEven) ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2; do { // Get the diagonal index from the relative diagonal number const diagonal = diagonalRelative + diagonalReverseBase; // Figure out where we came from if (diagonal === diagonalMin || (diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) { // Horizontal line (the element is a deletion)) originalIndex = reversePoints[diagonal + 1] - 1; modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset; if (originalIndex > lastOriginalIndex) { changeHelper.MarkNextChange(); } lastOriginalIndex = originalIndex + 1; changeHelper.AddOriginalElement(originalIndex + 1, modifiedIndex + 1); diagonalRelative = (diagonal + 1) - diagonalReverseBase; //Setup for the next iteration } else { // Vertical line (the element is an insertion) originalIndex = reversePoints[diagonal - 1]; modifiedIndex = originalIndex - diagonalRelative - diagonalReverseOffset; if (originalIndex > lastOriginalIndex) { changeHelper.MarkNextChange(); } lastOriginalIndex = originalIndex; changeHelper.AddModifiedElement(originalIndex + 1, modifiedIndex + 1); diagonalRelative = (diagonal - 1) - diagonalReverseBase; //Setup for the next iteration } if (historyIndex >= 0) { reversePoints = this.m_reverseHistory[historyIndex]; diagonalReverseBase = reversePoints[0]; //We stored this in the first spot diagonalMin = 1; diagonalMax = reversePoints.length - 1; } } while (--historyIndex >= -1); // There are cases where the reverse history will find diffs that // are correct, but not intuitive, so we need shift them. reverseChanges = changeHelper.getChanges(); } return this.ConcatenateChanges(forwardChanges, reverseChanges); } /** * Given the range to compute the diff on, this method finds the point: * (midOriginal, midModified) * that exists in the middle of the LCS of the two sequences and * is the point at which the LCS problem may be broken down recursively. * This method will try to keep the LCS trace in memory. If the LCS recursion * point is calculated and the full trace is available in memory, then this method * will return the change list. * @param originalStart The start bound of the original sequence range * @param originalEnd The end bound of the original sequence range * @param modifiedStart The start bound of the modified sequence range * @param modifiedEnd The end bound of the modified sequence range * @param midOriginal The middle point of the original sequence range * @param midModified The middle point of the modified sequence range * @returns The diff changes, if available, otherwise null */ ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr) { let originalIndex = 0, modifiedIndex = 0; let diagonalForwardStart = 0, diagonalForwardEnd = 0; let diagonalReverseStart = 0, diagonalReverseEnd = 0; // To traverse the edit graph and produce the proper LCS, our actual // start position is just outside the given boundary originalStart--; modifiedStart--; // We set these up to make the compiler happy, but they will // be replaced before we return with the actual recursion point midOriginalArr[0] = 0; midModifiedArr[0] = 0; // Clear out the history this.m_forwardHistory = []; this.m_reverseHistory = []; // Each cell in the two arrays corresponds to a diagonal in the edit graph. // The integer value in the cell represents the originalIndex of the furthest // reaching point found so far that ends in that diagonal. // The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number. const maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart); const numDiagonals = maxDifferences + 1; const forwardPoints = new Int32Array(numDiagonals); const reversePoints = new Int32Array(numDiagonals); // diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart) // diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd) const diagonalForwardBase = (modifiedEnd - modifiedStart); const diagonalReverseBase = (originalEnd - originalStart); // diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the // diagonal number (relative to diagonalForwardBase) // diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the // diagonal number (relative to diagonalReverseBase) const diagonalForwardOffset = (originalStart - modifiedStart); const diagonalReverseOffset = (originalEnd - modifiedEnd); // delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers // relative to the start diagonal with diagonal numbers relative to the end diagonal. // The Even/Oddn-ness of this delta is important for determining when we should check for overlap const delta = diagonalReverseBase - diagonalForwardBase; const deltaIsEven = (delta % 2 === 0); // Here we set up the start and end points as the furthest points found so far // in both the forward and reverse directions, respectively forwardPoints[diagonalForwardBase] = originalStart; reversePoints[diagonalReverseBase] = originalEnd; // Remember if we quit early, and thus need to do a best-effort result instead of a real result. quitEarlyArr[0] = false; // A couple of points: // --With this method, we iterate on the number of differences between the two sequences. // The more differences there actually are, the longer this will take. // --Also, as the