use-monaco
Version:
[](https://npm.im/use-monaco)
1,232 lines (1,201 loc) • 305 kB
JavaScript
'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