atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
525 lines (463 loc) • 16 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
// Represents a concrete change to a string. That is, the result of applying
// the WOp to the local string.
function idLess(idLeft, idRight) {
return idLeft.site < idRight.site || idLeft.site === idRight.site && idLeft.h < idRight.h;
}
var WString = (function () {
function WString(siteId) {
var length = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
_classCallCheck(this, WString);
this._siteId = siteId;
this._localId = 1;
this._string = [WString.start, WString.end];
this._ops = [];
if (length > 0) {
this._localId = length;
this.insert(1, {
startId: {
site: siteId,
h: 1
},
visible: true,
startDegree: 1,
length: length
}, length);
}
}
_createClass(WString, [{
key: 'insert',
value: function insert(pos, c) {
// Find the run that has the previous position in it.
var leftHalfIndex = undefined;
var offset = pos;
for (leftHalfIndex = 0; leftHalfIndex < this._string.length; leftHalfIndex++) {
if (this._string[leftHalfIndex].length >= offset) {
break;
}
offset -= this._string[leftHalfIndex].length;
}
var leftHalf = this._string[leftHalfIndex];
// There are 3 cases we handle. Assume the following run
// [begin][id:1,1; len: 3, vis: 1;][end]
//
// The first case is where we can merely extend the previous run
// insert(4, {id: 1,4; len: 1; vis: 1})
//
// [begin][id:1,1; len: 3, vis: 1;]*insert here*[end]
// =>
// [begin][id:1,1; len: *4*, vis: 1;][end]
//
// The next case is where we are at the end but cannont extend.
//
// insert(4, {id: *2,7*; len: 1; vis: 1})
//
// [begin][id:1,1; len: 3, vis: 1;]*insert here*[end]
// =>
// [begin][id:1,1; len: 3, vis: 1;][id:2,7; len: 1, vis: 1;][end]
//
// The last case is where we split the previous run.
// insert(3, {id: 1,4; len: 1; vis: 1})
//
// [begin][id:1,1; len: 4, vis: 1;]*<= insert inside there*[end]
// =>
// [begin][id:1,1; len: 2, vis: 1;][id:1,4; len: 1, vis: 1;][id:1,3; len: 2, vis: 1;][end]
if (leftHalf.startId.site === c.startId.site && leftHalf.startId.h === c.startId.h - leftHalf.length && offset === leftHalf.length && c.visible === leftHalf.visible) {
leftHalf.length += c.length;
} else if (offset === leftHalf.length) {
this._string.splice(leftHalfIndex + 1, 0, c);
} else {
var rightHalf = {
startId: {
site: leftHalf.startId.site,
h: leftHalf.startId.h + offset
},
visible: leftHalf.visible,
length: leftHalf.length - offset,
startDegree: leftHalf.startDegree + offset
};
leftHalf.length -= leftHalf.length - offset;
this._string.splice(leftHalfIndex + 1, 0, c, rightHalf);
}
}
}, {
key: 'canMergeRight',
value: function canMergeRight(i) {
(0, (_assert2 || _assert()).default)(i < this._string.length - 1);
return this._string[i].startId.site === this._string[i + 1].startId.site && this._string[i].startId.h === this._string[i + 1].startId.h - this._string[i].length && this._string[i].visible === this._string[i + 1].visible;
}
}, {
key: 'mergeRuns',
value: function mergeRuns() {
var newString = [];
newString.push(this._string[0]);
for (var i = 0; i < this._string.length - 1; i++) {
if (this.canMergeRight(i)) {
newString[newString.length - 1].length += this._string[i + 1].length;
} else {
newString.push(this._string[i + 1]);
}
}
this._string = newString;
}
}, {
key: 'integrateDelete',
value: function integrateDelete(pos) {
var _string;
var originalIndex = undefined;
var offset = pos;
// Find the index of the WString run containing this position
for (originalIndex = 0; originalIndex < this._string.length; originalIndex++) {
if (this._string[originalIndex].length > offset && this._string[originalIndex].visible) {
break;
}
if (this._string[originalIndex].visible) {
offset -= this._string[originalIndex].length;
}
}
var runs = [];
var original = this._string[originalIndex];
if (offset > 0) {
runs.push({
startId: {
site: original.startId.site,
h: original.startId.h
},
visible: original.visible,
length: offset,
startDegree: original.startDegree
});
}
runs.push({
startId: {
site: original.startId.site,
h: original.startId.h + offset
},
visible: false,
length: 1,
startDegree: original.startDegree + offset
});
if (offset < original.length - 1) {
runs.push({
startId: {
site: original.startId.site,
h: original.startId.h + offset + 1
},
visible: original.visible,
length: original.length - (offset + 1),
startDegree: original.startDegree + offset + 1
});
}
(_string = this._string).splice.apply(_string, [originalIndex, 1].concat(runs));
this.mergeRuns();
}
}, {
key: 'pos',
value: function pos(c) {
var visibleOnly = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var currentOffset = 0;
for (var i = 0; i < this._string.length; i++) {
var currentRun = this._string[i];
if (currentRun.startId.site === c.id.site && currentRun.startId.h <= c.id.h && currentRun.startId.h + currentRun.length > c.id.h && (!visibleOnly || this._string[i].visible)) {
return currentOffset + (c.id.h - currentRun.startId.h);
}
if (!visibleOnly || this._string[i].visible) {
currentOffset += currentRun.length;
}
}
return -1;
}
}, {
key: 'charFromRun',
value: function charFromRun(run, offset) {
return {
id: {
site: run.startId.site,
h: run.startId.h + offset
},
degree: run.startDegree + offset,
visible: run.visible
};
}
}, {
key: 'ith',
value: function ith(pos) {
var visibleOnly = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
var i = undefined;
var offset = pos;
for (i = 0; i < this._string.length; i++) {
if (this._string[i].length > offset && (!visibleOnly || this._string[i].visible)) {
break;
}
if (!visibleOnly || this._string[i].visible) {
offset -= this._string[i].length;
}
}
return this.charFromRun(this._string[i], offset);
}
/**
* Returns the subset (left, right) of the string sequence (exlusive on both sides)
*/
}, {
key: 'subseq',
value: function subseq(left, right) {
var sub = [];
if (left == null || right == null) {
throw new Error('asdf');
}
var start = this.pos(left, false);
var end = this.pos(right, false);
for (var i = start + 1; i < end; i++) {
sub.push(this.ith(i, false));
}
return sub;
}
}, {
key: 'genInsert',
value: function genInsert(pos, text) {
var prevChar = this.ith(pos);
var nextChar = this.ith(pos + 1);
if (prevChar == null || nextChar == null) {
throw new Error('Position ' + pos + ' invalid within wstring');
}
var c = {
startId: {
site: this._siteId,
h: this._localId
},
visible: true,
startDegree: Math.max(prevChar.degree, nextChar.degree) + 1,
length: text.length
};
this._localId += text.length;
this.integrateIns(c, prevChar, nextChar);
return { type: 'INS', char: _extends({}, c), prev: prevChar, next: nextChar, text: text };
}
// Main wooto algorithm. see: "Wooki: a P2P Wiki-based Collaborative Writing Tool"
// returns the visible position of the string that this text is inserted into
}, {
key: 'integrateIns',
value: function integrateIns(c, cp, cn) {
// Consider the sequence of characters between cp, and cn
var sub = this.subseq(cp, cn);
// If this is an empty sequence just insert the character
if (sub.length === 0) {
var _pos = this.pos(cn);
this.insert(_pos, c);
return this.pos(this.charFromRun(c, 0), true);
}
// Else, only consider the characters with minimum degree. Other characters
// positions in the sequence are determing by the order relations.
var minDegree = Math.min.apply(Math, _toConsumableArray(sub.map(function (c2) {
return c2.degree;
})));
var idOrderedSubset = [cp].concat(_toConsumableArray(sub.filter(function (c2) {
return c2.degree === minDegree;
})), [cn]);
// Find the position of the new character in this sequence of characters
// ordered by the ids
var i = idOrderedSubset.findIndex(function (elm) {
return !idLess(elm.id, c.startId);
});
if (i === -1) {
i = idOrderedSubset.length - 1;
}
return this.integrateIns(c, idOrderedSubset[i - 1], idOrderedSubset[i]);
}
}, {
key: 'charToRun',
value: function charToRun(char, visible) {
return {
startId: {
site: char.id.site,
h: char.id.h
},
startDegree: char.degree,
visible: visible,
length: 1
};
}
}, {
key: 'canExtendRun',
value: function canExtendRun(run, char) {
return run.startId.site === char.id.site && run.startId.h + run.length === char.id.h && run.startDegree + run.length === char.degree;
}
}, {
key: 'charsToRuns',
value: function charsToRuns(chars) {
if (chars.length === 0) {
return [];
}
var runs = [];
var curRun = this.charToRun(chars[0], false);
for (var i = 1; i < chars.length; i++) {
if (this.canExtendRun(curRun, chars[i])) {
curRun.length += 1;
} else {
runs.push(curRun);
curRun = this.charToRun(chars[i], false);
}
}
runs.push(curRun);
return runs;
}
}, {
key: 'genDelete',
value: function genDelete(pos) {
var count = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];
var chars = [];
for (var i = 0; i < count; i++) {
chars.push(this.ith(pos + 1));
this.integrateDelete(pos + 1);
}
return { type: 'DEL', runs: this.charsToRuns(chars) };
}
}, {
key: 'visibleRanges',
value: function visibleRanges(runs) {
var pos = -1;
var count = 1;
var ranges = [];
for (var i = 0; i < runs.length; i++) {
for (var j = 0; j < runs[i].length; j++) {
var wchar = this.charFromRun(runs[i], j);
var newPos = this.pos(wchar, /* visibleOnly */true);
// Skip invisible characters
if (newPos === -1) {
continue;
}
if (pos + count === newPos) {
count += 1;
} else {
if (pos > 0) {
ranges.push({ pos: pos, count: count });
}
count = 1;
pos = newPos;
}
}
}
if (pos > 0) {
ranges.push({ pos: pos, count: count });
}
return ranges;
}
}, {
key: 'applyOps',
value: function applyOps() {
var _this = this;
var changes = [];
var lastCount = this._ops.length + 1;
while (lastCount > this._ops.length) {
lastCount = this._ops.length;
this._ops = this._ops.filter(function (op) {
if (_this.canApplyOp(op)) {
changes.push(_this.execute(op));
return false;
}
return true;
});
}
return changes;
}
}, {
key: 'receive',
value: function receive(op) {
if (op.type === 'INS') {
(0, (_assert2 || _assert()).default)(op.char != null);
if (this.contains(this.charFromRun(op.char, 0))) {
return [];
}
}
this._ops.push(op);
return this.applyOps();
}
}, {
key: 'canApplyOp',
value: function canApplyOp(op) {
if (op.type === 'INS') {
var _prev = op.prev;
var _next = op.next;
(0, (_assert2 || _assert()).default)(_prev != null && _next != null);
return this.contains(_prev) && this.contains(_next);
} else {
// DEL
(0, (_assert2 || _assert()).default)(op.runs != null);
for (var i = 0; i < op.runs.length; i++) {
for (var j = 0; j < op.runs[i].length; j++) {
if (!this.contains(this.charFromRun(op.runs[i], j))) {
return false;
}
}
}
return true;
}
}
}, {
key: 'contains',
value: function contains(c) {
if (this.pos(c, false) !== -1) {
return true;
}
return false;
}
}, {
key: 'execute',
value: function execute(op) {
var next = op.next;
var prev = op.prev;
if (op.type === 'INS') {
if (next == null || prev == null || op.char == null || op.text == null) {
throw new Error('INS type operation invalid.');
}
var _pos2 = this.integrateIns(op.char, prev, next);
(0, (_assert2 || _assert()).default)(op.text);
return { addition: { pos: _pos2, text: op.text } };
} else {
// DEL
if (op.runs == null) {
throw new Error('DEL operation invalid');
}
var ranges = this.visibleRanges(op.runs);
for (var i = 0; i < ranges.length; i++) {
for (var j = 0; j < ranges[i].count; j++) {
this.integrateDelete(ranges[i].pos);
}
}
return { removals: ranges };
}
}
}]);
return WString;
})();
exports.WString = WString;
WString.start = {
startId: { site: -1, h: 0 },
visible: true,
startDegree: 0,
length: 1
};
WString.end = {
startId: { site: -1, h: 1 },
visible: true,
startDegree: 0,
length: 1
};