Merge a4c83482ca02bf9a54a1ae004d9309f50307762e into 4feaf6c6fb507432aefa1cbfedd602218add81ab
This commit is contained in:
commit
f9fdc916ad
File diff suppressed because one or more lines are too long
1062
build/browser/y.js
1062
build/browser/y.js
File diff suppressed because one or more lines are too long
@ -137,13 +137,13 @@ module.exports = {
|
|||||||
/*
|
/*
|
||||||
* Broadcast a message to all connected peers.
|
* Broadcast a message to all connected peers.
|
||||||
* @param message {Object} The message to broadcast.
|
* @param message {Object} The message to broadcast.
|
||||||
#
|
*
|
||||||
broadcast: (message)->
|
broadcast: (message)->
|
||||||
throw new Error "You must implement broadcast!"
|
throw new Error "You must implement broadcast!"
|
||||||
|
|
||||||
#
|
*
|
||||||
* Send a message to a peer, or set of peers
|
* Send a message to a peer, or set of peers
|
||||||
#
|
*
|
||||||
send: (peer_s, message)->
|
send: (peer_s, message)->
|
||||||
throw new Error "You must implement send!"
|
throw new Error "You must implement send!"
|
||||||
*/
|
*/
|
||||||
|
@ -490,7 +490,7 @@ module.exports = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Insert.prototype.execute = function() {
|
Insert.prototype.execute = function() {
|
||||||
var base1, distance_to_origin, i, o;
|
var base1, distance_to_origin, i, o, oDistance;
|
||||||
if (!this.validateSavedOperations()) {
|
if (!this.validateSavedOperations()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@ -520,15 +520,16 @@ module.exports = function() {
|
|||||||
i = distance_to_origin;
|
i = distance_to_origin;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (o !== this.next_cl) {
|
if (o !== this.next_cl) {
|
||||||
if (o.getDistanceToOrigin() === i) {
|
oDistance = o.getDistanceToOrigin();
|
||||||
|
if (oDistance === i) {
|
||||||
if (o.uid.creator < this.uid.creator) {
|
if (o.uid.creator < this.uid.creator) {
|
||||||
this.prev_cl = o;
|
this.prev_cl = o;
|
||||||
distance_to_origin = i + 1;
|
distance_to_origin = i + 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (o.getDistanceToOrigin() < i) {
|
} else if (oDistance < i) {
|
||||||
if (i - distance_to_origin <= o.getDistanceToOrigin()) {
|
if (i - distance_to_origin <= oDistance) {
|
||||||
this.prev_cl = o;
|
this.prev_cl = o;
|
||||||
distance_to_origin = i + 1;
|
distance_to_origin = i + 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
var basic_ops_uninitialized,
|
var RBTReeByIndex, basic_ops_uninitialized,
|
||||||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
hasProp = {}.hasOwnProperty;
|
hasProp = {}.hasOwnProperty;
|
||||||
|
|
||||||
basic_ops_uninitialized = require("./Basic");
|
basic_ops_uninitialized = require("./Basic");
|
||||||
|
|
||||||
|
RBTReeByIndex = require('bintrees/lib/rbtree_by_index');
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
var basic_ops, ops;
|
var basic_ops, ops;
|
||||||
basic_ops = basic_ops_uninitialized();
|
basic_ops = basic_ops_uninitialized();
|
||||||
@ -122,6 +124,8 @@ module.exports = function() {
|
|||||||
this.beginning.next_cl = this.end;
|
this.beginning.next_cl = this.end;
|
||||||
this.beginning.execute();
|
this.beginning.execute();
|
||||||
this.end.execute();
|
this.end.execute();
|
||||||
|
this.shortTree = new RBTreeByIndex();
|
||||||
|
this.completeTree = new RBTreeByIndex();
|
||||||
ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations);
|
ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,114 +185,128 @@ module.exports = function() {
|
|||||||
return this.beginning.next_cl;
|
return this.beginning.next_cl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ListManager.prototype.getNextNonDeleted = function(start) {
|
||||||
|
var operation;
|
||||||
|
if (start.isDeleted() || (start.node == null)) {
|
||||||
|
operation = start.next_cl;
|
||||||
|
while (!(operation instanceof ops.Delimiter)) {
|
||||||
|
if (operation.is_deleted) {
|
||||||
|
operation = operation.next_cl;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
operation = start.node.next.node;
|
||||||
|
if (!operation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return operation;
|
||||||
|
};
|
||||||
|
|
||||||
|
ListManager.prototype.getPrevNonDeleted = function(start) {
|
||||||
|
var operation;
|
||||||
|
if (start.isDeleted() || (start.node == null)) {
|
||||||
|
operation = start.prev_cl;
|
||||||
|
while (!(operation instanceof ops.Delimiter)) {
|
||||||
|
if (operation.is_deleted) {
|
||||||
|
operation = operation.prev_cl;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
operation = start.node.prev.node;
|
||||||
|
if (!operation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return operation;
|
||||||
|
};
|
||||||
|
|
||||||
ListManager.prototype.toArray = function() {
|
ListManager.prototype.toArray = function() {
|
||||||
var o, result;
|
return this.shortTree.map(function(operation) {
|
||||||
o = this.beginning.next_cl;
|
return operation.val();
|
||||||
result = [];
|
});
|
||||||
while (o !== this.end) {
|
|
||||||
if (!o.is_deleted) {
|
|
||||||
result.push(o.val());
|
|
||||||
}
|
|
||||||
o = o.next_cl;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.map = function(f) {
|
ListManager.prototype.map = function(fun) {
|
||||||
var o, result;
|
return this.shortTree.map(fun);
|
||||||
o = this.beginning.next_cl;
|
|
||||||
result = [];
|
|
||||||
while (o !== this.end) {
|
|
||||||
if (!o.is_deleted) {
|
|
||||||
result.push(f(o));
|
|
||||||
}
|
|
||||||
o = o.next_cl;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.fold = function(init, f) {
|
ListManager.prototype.fold = function(init, fun) {
|
||||||
var o;
|
return this.shortTree.map(function(operation) {
|
||||||
o = this.beginning.next_cl;
|
return init = fun(init, operation);
|
||||||
while (o !== this.end) {
|
});
|
||||||
if (!o.is_deleted) {
|
|
||||||
init = f(init, o);
|
|
||||||
}
|
|
||||||
o = o.next_cl;
|
|
||||||
}
|
|
||||||
return init;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.val = function(pos) {
|
ListManager.prototype.val = function(pos) {
|
||||||
var o;
|
|
||||||
if (pos != null) {
|
if (pos != null) {
|
||||||
o = this.getOperationByPosition(pos + 1);
|
return this.shortTree.find(pos).val();
|
||||||
if (!(o instanceof ops.Delimiter)) {
|
|
||||||
return o.val();
|
|
||||||
} else {
|
|
||||||
throw new Error("this position does not exist");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return this.toArray();
|
return this.toArray();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.ref = function(pos) {
|
ListManager.prototype.ref = function(pos) {
|
||||||
var o;
|
|
||||||
if (pos != null) {
|
if (pos != null) {
|
||||||
o = this.getOperationByPosition(pos + 1);
|
return this.shortTree.find(pos);
|
||||||
if (!(o instanceof ops.Delimiter)) {
|
|
||||||
return o;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return this.shortTree.map(function(operation) {
|
||||||
}
|
return operation;
|
||||||
} else {
|
});
|
||||||
throw new Error("you must specify a position parameter");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.getOperationByPosition = function(position) {
|
ListManager.prototype.getOperationByPosition = function(position) {
|
||||||
var o;
|
if (position === 0) {
|
||||||
o = this.beginning;
|
return this.beginning;
|
||||||
while (true) {
|
} else if (position === this.shortTree.size + 1) {
|
||||||
if (o instanceof ops.Delimiter && (o.prev_cl != null)) {
|
return this.end;
|
||||||
o = o.prev_cl;
|
} else {
|
||||||
while (o.isDeleted() && (o.prev_cl != null)) {
|
return this.shortTree.find(position - 1);
|
||||||
o = o.prev_cl;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (position <= 0 && !o.isDeleted()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
o = o.next_cl;
|
|
||||||
if (!o.isDeleted()) {
|
|
||||||
position -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.push = function(content) {
|
ListManager.prototype.push = function(content) {
|
||||||
return this.insertAfter(this.end.prev_cl, [content]);
|
return this.insertAfter(this.end.prev_cl, [content]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ListManager.prototype.insertAfterHelper = function(root, content) {
|
||||||
|
var right;
|
||||||
|
if (!root.right) {
|
||||||
|
root.bt.right = content;
|
||||||
|
return content.bt.parent = root;
|
||||||
|
} else {
|
||||||
|
return right = root.next_cl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ListManager.prototype.insertAfter = function(left, contents) {
|
ListManager.prototype.insertAfter = function(left, contents) {
|
||||||
var c, j, len, right, tmp;
|
var c, j, leftNode, len, right, rightNode, tmp;
|
||||||
right = left.next_cl;
|
if (left === this.beginning) {
|
||||||
while (right.isDeleted()) {
|
leftNode = null;
|
||||||
right = right.next_cl;
|
rightNode = this.shortTree.findNode(0);
|
||||||
|
right = rightNode ? rightNode.data : this.end;
|
||||||
|
} else {
|
||||||
|
rightNode = left.node.next;
|
||||||
|
leftNode = left.node;
|
||||||
|
right = rightNode ? rightNode.data : this.end;
|
||||||
}
|
}
|
||||||
left = right.prev_cl;
|
left = right.prev_cl;
|
||||||
if (contents instanceof ops.Operation) {
|
if (contents instanceof ops.Operation) {
|
||||||
(new ops.Insert(null, content, null, void 0, void 0, left, right)).execute();
|
tmp = new ops.Insert(null, content, null, void 0, void 0, left, right);
|
||||||
|
tmp.execute();
|
||||||
} else {
|
} else {
|
||||||
for (j = 0, len = contents.length; j < len; j++) {
|
for (j = 0, len = contents.length; j < len; j++) {
|
||||||
c = contents[j];
|
c = contents[j];
|
||||||
if ((c != null) && (c._name != null) && (c._getModel != null)) {
|
if ((c != null) && (c._name != null) && (c._getModel != null)) {
|
||||||
c = c._getModel(this.custom_types, this.operations);
|
c = c._getModel(this.custom_types, this.operations);
|
||||||
}
|
}
|
||||||
tmp = (new ops.Insert(null, c, null, void 0, void 0, left, right)).execute();
|
tmp = new ops.Insert(null, c, null, void 0, void 0, left, right);
|
||||||
|
tmp.execute();
|
||||||
|
leftNode = tmp.node;
|
||||||
left = tmp;
|
left = tmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,29 +319,50 @@ module.exports = function() {
|
|||||||
return this.insertAfter(ith, contents);
|
return this.insertAfter(ith, contents);
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype["delete"] = function(position, length) {
|
ListManager.prototype.deleteRef = function(operation, length, dir) {
|
||||||
var d, delete_ops, i, j, o, ref;
|
var deleteOperation, i, j, nextOperation, ref;
|
||||||
if (length == null) {
|
if (length == null) {
|
||||||
length = 1;
|
length = 1;
|
||||||
}
|
}
|
||||||
o = this.getOperationByPosition(position + 1);
|
if (dir == null) {
|
||||||
delete_ops = [];
|
dir = 'right';
|
||||||
|
}
|
||||||
|
nextOperation = (function(_this) {
|
||||||
|
return function(operation) {
|
||||||
|
if (dir === 'right') {
|
||||||
|
return _this.getNextNonDeleted(operation);
|
||||||
|
} else {
|
||||||
|
return _this.getPrevNonDeleted(operation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this);
|
||||||
for (i = j = 0, ref = length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
|
for (i = j = 0, ref = length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
|
||||||
if (o instanceof ops.Delimiter) {
|
if (operation instanceof ops.Delimiter) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
d = (new ops.Delete(null, void 0, o)).execute();
|
deleteOperation = (new ops.Delete(null, void 0, operation)).execute();
|
||||||
o = o.next_cl;
|
operation = nextOperation(operation);
|
||||||
while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) {
|
|
||||||
o = o.next_cl;
|
|
||||||
}
|
|
||||||
delete_ops.push(d._encode());
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.callOperationSpecificInsertEvents = function(op) {
|
ListManager.prototype["delete"] = function(position, length) {
|
||||||
var getContentType;
|
var operation;
|
||||||
|
if (length == null) {
|
||||||
|
length = 1;
|
||||||
|
}
|
||||||
|
operation = this.getOperationByPosition(position + length);
|
||||||
|
return this.deleteRef(operation, length, 'left');
|
||||||
|
};
|
||||||
|
|
||||||
|
ListManager.prototype.callOperationSpecificInsertEvents = function(operation) {
|
||||||
|
var getContentType, next, nextNode, prev, prevNode;
|
||||||
|
prev = (this.getPrevNonDeleted(operation)) || this.beginning;
|
||||||
|
prevNode = prev ? prev.node : null;
|
||||||
|
next = (this.getNextNonDeleted(operation)) || this.end;
|
||||||
|
nextNode = next ? next.node : null;
|
||||||
|
operation.node = operation.node || (this.shortTree.insert_between(prevNode, nextNode, operation));
|
||||||
|
operation.completeNode = operation.completeNode || (this.completeTree.insert_between(operation.prev_cl.completeNode, operation.next_cl.completeNode, operation));
|
||||||
getContentType = function(content) {
|
getContentType = function(content) {
|
||||||
if (content instanceof ops.Operation) {
|
if (content instanceof ops.Operation) {
|
||||||
return content.getCustomType();
|
return content.getCustomType();
|
||||||
@ -334,25 +373,31 @@ module.exports = function() {
|
|||||||
return this.callEvent([
|
return this.callEvent([
|
||||||
{
|
{
|
||||||
type: "insert",
|
type: "insert",
|
||||||
reference: op,
|
reference: operation,
|
||||||
position: op.getPosition(),
|
position: operation.node.position(),
|
||||||
object: this.getCustomType(),
|
object: this.getCustomType(),
|
||||||
changedBy: op.uid.creator,
|
changedBy: operation.uid.creator,
|
||||||
value: getContentType(op.val())
|
value: getContentType(operation.val())
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
ListManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
|
ListManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
|
||||||
|
var position;
|
||||||
|
if (operation.node) {
|
||||||
|
position = operation.node.position();
|
||||||
|
this.shortTree.remove_node(operation.node);
|
||||||
|
operation.node = null;
|
||||||
|
}
|
||||||
return this.callEvent([
|
return this.callEvent([
|
||||||
{
|
{
|
||||||
type: "delete",
|
type: "delete",
|
||||||
reference: op,
|
reference: operation,
|
||||||
position: op.getPosition(),
|
position: position,
|
||||||
object: this.getCustomType(),
|
object: this.getCustomType(),
|
||||||
length: 1,
|
length: 1,
|
||||||
changedBy: del_op.uid.creator,
|
changedBy: del_op.uid.creator,
|
||||||
oldValue: op.val()
|
oldValue: operation.val()
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@ -406,14 +451,14 @@ module.exports = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Composition.prototype.callOperationSpecificInsertEvents = function(op) {
|
Composition.prototype.callOperationSpecificInsertEvents = function(operation) {
|
||||||
var o;
|
var o;
|
||||||
if (this.tmp_composition_ref != null) {
|
if (this.tmp_composition_ref != null) {
|
||||||
if (op.uid.creator === this.tmp_composition_ref.creator && op.uid.op_number === this.tmp_composition_ref.op_number) {
|
if (operation.uid.creator === this.tmp_composition_ref.creator && operation.uid.op_number === this.tmp_composition_ref.op_number) {
|
||||||
this.composition_ref = op;
|
this.composition_ref = operation;
|
||||||
delete this.tmp_composition_ref;
|
delete this.tmp_composition_ref;
|
||||||
op = op.next_cl;
|
operation = operation.next_cl;
|
||||||
if (op === this.end) {
|
if (operation === this.end) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -421,7 +466,7 @@ module.exports = function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
o = this.end.prev_cl;
|
o = this.end.prev_cl;
|
||||||
while (o !== op) {
|
while (o !== operation) {
|
||||||
this.getCustomType()._unapply(o.undo_delta);
|
this.getCustomType()._unapply(o.undo_delta);
|
||||||
o = o.prev_cl;
|
o = o.prev_cl;
|
||||||
}
|
}
|
||||||
@ -433,13 +478,13 @@ module.exports = function() {
|
|||||||
return this.callEvent([
|
return this.callEvent([
|
||||||
{
|
{
|
||||||
type: "update",
|
type: "update",
|
||||||
changedBy: op.uid.creator,
|
changedBy: operation.uid.creator,
|
||||||
newValue: this.val()
|
newValue: this.val()
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
Composition.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {};
|
Composition.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {};
|
||||||
|
|
||||||
Composition.prototype.applyDelta = function(delta, operations) {
|
Composition.prototype.applyDelta = function(delta, operations) {
|
||||||
(new ops.Insert(null, delta, operations, this, null, this.end.prev_cl, this.end)).execute();
|
(new ops.Insert(null, delta, operations, this, null, this.end.prev_cl, this.end)).execute();
|
||||||
@ -507,40 +552,40 @@ module.exports = function() {
|
|||||||
return void 0;
|
return void 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(op) {
|
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(operation) {
|
||||||
var old_value;
|
var old_value;
|
||||||
if (op.next_cl.type === "Delimiter" && op.prev_cl.type !== "Delimiter") {
|
if (operation.next_cl.type === "Delimiter" && operation.prev_cl.type !== "Delimiter") {
|
||||||
if (!op.is_deleted) {
|
if (!operation.is_deleted) {
|
||||||
old_value = op.prev_cl.val();
|
old_value = operation.prev_cl.val();
|
||||||
this.callEventDecorator([
|
this.callEventDecorator([
|
||||||
{
|
{
|
||||||
type: "update",
|
type: "update",
|
||||||
changedBy: op.uid.creator,
|
changedBy: operation.uid.creator,
|
||||||
oldValue: old_value
|
oldValue: old_value
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
op.prev_cl.applyDelete();
|
operation.prev_cl.applyDelete();
|
||||||
} else if (op.next_cl.type !== "Delimiter") {
|
} else if (operation.next_cl.type !== "Delimiter") {
|
||||||
op.applyDelete();
|
operation.applyDelete();
|
||||||
} else {
|
} else {
|
||||||
this.callEventDecorator([
|
this.callEventDecorator([
|
||||||
{
|
{
|
||||||
type: "add",
|
type: "add",
|
||||||
changedBy: op.uid.creator
|
changedBy: operation.uid.creator
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
|
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
|
||||||
if (op.next_cl.type === "Delimiter") {
|
if (operation.next_cl.type === "Delimiter") {
|
||||||
return this.callEventDecorator([
|
return this.callEventDecorator([
|
||||||
{
|
{
|
||||||
type: "delete",
|
type: "delete",
|
||||||
changedBy: del_op.uid.creator,
|
changedBy: del_op.uid.creator,
|
||||||
oldValue: op.val()
|
oldValue: operation.val()
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
35262
build/test/xml-test.js
35262
build/test/xml-test.js
File diff suppressed because one or more lines are too long
@ -521,17 +521,18 @@ module.exports = ()->
|
|||||||
# $this insert_position is to the left of $o (forever!)
|
# $this insert_position is to the left of $o (forever!)
|
||||||
while true
|
while true
|
||||||
if o isnt @next_cl
|
if o isnt @next_cl
|
||||||
|
oDistance = o.getDistanceToOrigin()
|
||||||
# $o happened concurrently
|
# $o happened concurrently
|
||||||
if o.getDistanceToOrigin() is i
|
if oDistance is i
|
||||||
# case 1
|
# case 1
|
||||||
if o.uid.creator < @uid.creator
|
if o.uid.creator < @uid.creator
|
||||||
@prev_cl = o
|
@prev_cl = o
|
||||||
distance_to_origin = i + 1
|
distance_to_origin = i + 1
|
||||||
else
|
else
|
||||||
# nop
|
# nop
|
||||||
else if o.getDistanceToOrigin() < i
|
else if oDistance < i
|
||||||
# case 2
|
# case 2
|
||||||
if i - distance_to_origin <= o.getDistanceToOrigin()
|
if i - distance_to_origin <= oDistance
|
||||||
@prev_cl = o
|
@prev_cl = o
|
||||||
distance_to_origin = i + 1
|
distance_to_origin = i + 1
|
||||||
else
|
else
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
basic_ops_uninitialized = require "./Basic"
|
basic_ops_uninitialized = require "./Basic"
|
||||||
|
RBTReeByIndex = require 'bintrees/lib/rbtree_by_index'
|
||||||
|
|
||||||
module.exports = ()->
|
module.exports = ()->
|
||||||
basic_ops = basic_ops_uninitialized()
|
basic_ops = basic_ops_uninitialized()
|
||||||
@ -107,6 +108,13 @@ module.exports = ()->
|
|||||||
@beginning.next_cl = @end
|
@beginning.next_cl = @end
|
||||||
@beginning.execute()
|
@beginning.execute()
|
||||||
@end.execute()
|
@end.execute()
|
||||||
|
|
||||||
|
|
||||||
|
# The short tree is storing the non deleted operations
|
||||||
|
@shortTree = new RBTreeByIndex()
|
||||||
|
# The complete tree is storing all the operations
|
||||||
|
@completeTree = new RBTreeByIndex()
|
||||||
|
|
||||||
super custom_type, uid, content, content_operations
|
super custom_type, uid, content, content_operations
|
||||||
|
|
||||||
type: "ListManager"
|
type: "ListManager"
|
||||||
@ -155,54 +163,63 @@ module.exports = ()->
|
|||||||
getFirstOperation: ()->
|
getFirstOperation: ()->
|
||||||
@beginning.next_cl
|
@beginning.next_cl
|
||||||
|
|
||||||
# Transforms the the list to an array
|
# get the next non-deleted operation
|
||||||
|
getNextNonDeleted: (start)->
|
||||||
|
if start.isDeleted() or not start.node?
|
||||||
|
operation = start.next_cl
|
||||||
|
while not ((operation instanceof ops.Delimiter))
|
||||||
|
if operation.is_deleted
|
||||||
|
operation = operation.next_cl
|
||||||
|
else
|
||||||
|
break
|
||||||
|
else
|
||||||
|
operation = start.node.next.node
|
||||||
|
if not operation
|
||||||
|
return false
|
||||||
|
|
||||||
|
operation
|
||||||
|
|
||||||
|
getPrevNonDeleted: (start) ->
|
||||||
|
if start.isDeleted() or not start.node?
|
||||||
|
operation = start.prev_cl
|
||||||
|
while not ((operation instanceof ops.Delimiter))
|
||||||
|
if operation.is_deleted
|
||||||
|
operation = operation.prev_cl
|
||||||
|
else
|
||||||
|
break
|
||||||
|
else
|
||||||
|
operation = start.node.prev.node
|
||||||
|
if not operation
|
||||||
|
return false
|
||||||
|
|
||||||
|
operation
|
||||||
|
|
||||||
|
|
||||||
|
# Transforms the list to an array
|
||||||
# Doesn't return left-right delimiter.
|
# Doesn't return left-right delimiter.
|
||||||
toArray: ()->
|
toArray: ()->
|
||||||
o = @beginning.next_cl
|
@shortTree.map (operation) ->
|
||||||
result = []
|
operation.val()
|
||||||
while o isnt @end
|
|
||||||
if not o.is_deleted
|
|
||||||
result.push o.val()
|
|
||||||
o = o.next_cl
|
|
||||||
result
|
|
||||||
|
|
||||||
map: (f)->
|
map: (fun)->
|
||||||
o = @beginning.next_cl
|
@shortTree.map fun
|
||||||
result = []
|
|
||||||
while o isnt @end
|
|
||||||
if not o.is_deleted
|
|
||||||
result.push f(o)
|
|
||||||
o = o.next_cl
|
|
||||||
result
|
|
||||||
|
|
||||||
fold: (init, f)->
|
fold: (init, fun)->
|
||||||
o = @beginning.next_cl
|
@shortTree.map (operation) ->
|
||||||
while o isnt @end
|
init = fun(init, operation)
|
||||||
if not o.is_deleted
|
|
||||||
init = f(init, o)
|
|
||||||
o = o.next_cl
|
|
||||||
init
|
|
||||||
|
|
||||||
val: (pos)->
|
val: (pos)->
|
||||||
if pos?
|
if pos?
|
||||||
o = @getOperationByPosition(pos+1)
|
@shortTree.find(pos).val()
|
||||||
if not (o instanceof ops.Delimiter)
|
|
||||||
o.val()
|
|
||||||
else
|
|
||||||
throw new Error "this position does not exist"
|
|
||||||
else
|
else
|
||||||
@toArray()
|
@toArray()
|
||||||
|
|
||||||
ref: (pos)->
|
ref: (pos)->
|
||||||
if pos?
|
if pos?
|
||||||
o = @getOperationByPosition(pos+1)
|
@shortTree.find(pos)
|
||||||
if not (o instanceof ops.Delimiter)
|
|
||||||
o
|
|
||||||
else
|
else
|
||||||
null
|
@shortTree.map (operation) ->
|
||||||
# throw new Error "this position does not exist"
|
operation
|
||||||
else
|
|
||||||
throw new Error "you must specify a position parameter"
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Retrieves the x-th not deleted element.
|
# Retrieves the x-th not deleted element.
|
||||||
@ -210,42 +227,49 @@ module.exports = ()->
|
|||||||
# the 0th character is the left Delimiter
|
# the 0th character is the left Delimiter
|
||||||
#
|
#
|
||||||
getOperationByPosition: (position)->
|
getOperationByPosition: (position)->
|
||||||
o = @beginning
|
if position == 0
|
||||||
while true
|
@beginning
|
||||||
# find the i-th op
|
else if position == @shortTree.size + 1
|
||||||
if o instanceof ops.Delimiter and o.prev_cl?
|
@end
|
||||||
# the user or you gave a position parameter that is to big
|
else
|
||||||
# for the current array. Therefore we reach a Delimiter.
|
@shortTree.find (position-1)
|
||||||
# Then, we'll just return the last character.
|
|
||||||
o = o.prev_cl
|
|
||||||
while o.isDeleted() and o.prev_cl?
|
|
||||||
o = o.prev_cl
|
|
||||||
break
|
|
||||||
if position <= 0 and not o.isDeleted()
|
|
||||||
break
|
|
||||||
|
|
||||||
o = o.next_cl
|
|
||||||
if not o.isDeleted()
|
|
||||||
position -= 1
|
|
||||||
o
|
|
||||||
|
|
||||||
push: (content)->
|
push: (content)->
|
||||||
@insertAfter @end.prev_cl, [content]
|
@insertAfter @end.prev_cl, [content]
|
||||||
|
|
||||||
|
insertAfterHelper: (root, content)->
|
||||||
|
if !root.right
|
||||||
|
root.bt.right = content
|
||||||
|
content.bt.parent = root
|
||||||
|
else
|
||||||
|
right = root.next_cl
|
||||||
|
|
||||||
|
|
||||||
insertAfter: (left, contents)->
|
insertAfter: (left, contents)->
|
||||||
right = left.next_cl
|
if left is @beginning
|
||||||
while right.isDeleted()
|
leftNode = null
|
||||||
right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
|
rightNode = @shortTree.findNode 0
|
||||||
|
right = if rightNode then rightNode.data else @end
|
||||||
|
else
|
||||||
|
# left.node should always exist (insert after a non-deleted element)
|
||||||
|
rightNode = left.node.next
|
||||||
|
leftNode = left.node
|
||||||
|
right = if rightNode then rightNode.data else @end
|
||||||
|
|
||||||
left = right.prev_cl
|
left = right.prev_cl
|
||||||
|
|
||||||
# TODO: always expect an array as content. Then you can combine this with the other option (else)
|
# TODO: always expect an array as content. Then you can combine this with the other option (else)
|
||||||
if contents instanceof ops.Operation
|
if contents instanceof ops.Operation
|
||||||
(new ops.Insert null, content, null, undefined, undefined, left, right).execute()
|
tmp = new ops.Insert null, content, null, undefined, undefined, left, right
|
||||||
|
tmp.execute()
|
||||||
else
|
else
|
||||||
for c in contents
|
for c in contents
|
||||||
if c? and c._name? and c._getModel?
|
if c? and c._name? and c._getModel?
|
||||||
c = c._getModel(@custom_types, @operations)
|
c = c._getModel(@custom_types, @operations)
|
||||||
tmp = (new ops.Insert null, c, null, undefined, undefined, left, right).execute()
|
tmp = new ops.Insert null, c, null, undefined, undefined, left, right
|
||||||
|
tmp.execute()
|
||||||
|
leftNode = tmp.node
|
||||||
|
|
||||||
left = tmp
|
left = tmp
|
||||||
@
|
@
|
||||||
|
|
||||||
@ -266,45 +290,62 @@ module.exports = ()->
|
|||||||
#
|
#
|
||||||
# @return {ListManager Type} This String object
|
# @return {ListManager Type} This String object
|
||||||
#
|
#
|
||||||
delete: (position, length = 1)->
|
deleteRef: (operation, length = 1, dir = 'right') ->
|
||||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
nextOperation = (operation) =>
|
||||||
|
if dir == 'right' then @getNextNonDeleted operation else @getPrevNonDeleted operation
|
||||||
|
|
||||||
delete_ops = []
|
|
||||||
for i in [0...length]
|
for i in [0...length]
|
||||||
if o instanceof ops.Delimiter
|
if operation instanceof ops.Delimiter
|
||||||
break
|
break
|
||||||
d = (new ops.Delete null, undefined, o).execute()
|
deleteOperation = (new ops.Delete null, undefined, operation).execute()
|
||||||
o = o.next_cl
|
|
||||||
while (not (o instanceof ops.Delimiter)) and o.isDeleted()
|
operation = nextOperation operation
|
||||||
o = o.next_cl
|
|
||||||
delete_ops.push d._encode()
|
|
||||||
@
|
@
|
||||||
|
|
||||||
|
delete: (position, length = 1)->
|
||||||
|
operation = @getOperationByPosition(position+length) # position 0 in this case is the deletion of the first character
|
||||||
|
|
||||||
|
@deleteRef operation, length, 'left'
|
||||||
|
|
||||||
|
|
||||||
|
callOperationSpecificInsertEvents: (operation)->
|
||||||
|
prev = (@getPrevNonDeleted operation) or @beginning
|
||||||
|
prevNode = if prev then prev.node else null
|
||||||
|
|
||||||
|
next = (@getNextNonDeleted operation) or @end
|
||||||
|
nextNode = if next then next.node else null
|
||||||
|
operation.node = operation.node or (@shortTree.insert_between prevNode, nextNode, operation)
|
||||||
|
operation.completeNode = operation.completeNode or (@completeTree.insert_between operation.prev_cl.completeNode, operation.next_cl.completeNode, operation)
|
||||||
|
|
||||||
callOperationSpecificInsertEvents: (op)->
|
|
||||||
getContentType = (content)->
|
getContentType = (content)->
|
||||||
if content instanceof ops.Operation
|
if content instanceof ops.Operation
|
||||||
content.getCustomType()
|
content.getCustomType()
|
||||||
else
|
else
|
||||||
content
|
content
|
||||||
|
|
||||||
@callEvent [
|
@callEvent [
|
||||||
type: "insert"
|
type: "insert"
|
||||||
reference: op
|
reference: operation
|
||||||
position: op.getPosition()
|
position: operation.node.position()
|
||||||
object: @getCustomType()
|
object: @getCustomType()
|
||||||
changedBy: op.uid.creator
|
changedBy: operation.uid.creator
|
||||||
value: getContentType op.val()
|
value: getContentType operation.val()
|
||||||
]
|
]
|
||||||
|
|
||||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
callOperationSpecificDeleteEvents: (operation, del_op)->
|
||||||
|
if operation.node
|
||||||
|
position = operation.node.position()
|
||||||
|
@shortTree.remove_node operation.node
|
||||||
|
operation.node = null
|
||||||
|
|
||||||
@callEvent [
|
@callEvent [
|
||||||
type: "delete"
|
type: "delete"
|
||||||
reference: op
|
reference: operation
|
||||||
position: op.getPosition()
|
position: position
|
||||||
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
|
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
|
||||||
length: 1
|
length: 1
|
||||||
changedBy: del_op.uid.creator
|
changedBy: del_op.uid.creator
|
||||||
oldValue: op.val()
|
oldValue: operation.val()
|
||||||
]
|
]
|
||||||
|
|
||||||
ops.ListManager.parse = (json)->
|
ops.ListManager.parse = (json)->
|
||||||
@ -355,19 +396,19 @@ module.exports = ()->
|
|||||||
#
|
#
|
||||||
# This is called, when the Insert-operation was successfully executed.
|
# This is called, when the Insert-operation was successfully executed.
|
||||||
#
|
#
|
||||||
callOperationSpecificInsertEvents: (op)->
|
callOperationSpecificInsertEvents: (operation)->
|
||||||
if @tmp_composition_ref?
|
if @tmp_composition_ref?
|
||||||
if op.uid.creator is @tmp_composition_ref.creator and op.uid.op_number is @tmp_composition_ref.op_number
|
if operation.uid.creator is @tmp_composition_ref.creator and operation.uid.op_number is @tmp_composition_ref.op_number
|
||||||
@composition_ref = op
|
@composition_ref = operation
|
||||||
delete @tmp_composition_ref
|
delete @tmp_composition_ref
|
||||||
op = op.next_cl
|
operation = operation.next_cl
|
||||||
if op is @end
|
if operation is @end
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
|
|
||||||
o = @end.prev_cl
|
o = @end.prev_cl
|
||||||
while o isnt op
|
while o isnt operation
|
||||||
@getCustomType()._unapply o.undo_delta
|
@getCustomType()._unapply o.undo_delta
|
||||||
o = o.prev_cl
|
o = o.prev_cl
|
||||||
while o isnt @end
|
while o isnt @end
|
||||||
@ -377,11 +418,11 @@ module.exports = ()->
|
|||||||
|
|
||||||
@callEvent [
|
@callEvent [
|
||||||
type: "update"
|
type: "update"
|
||||||
changedBy: op.uid.creator
|
changedBy: operation.uid.creator
|
||||||
newValue: @val()
|
newValue: @val()
|
||||||
]
|
]
|
||||||
|
|
||||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
callOperationSpecificDeleteEvents: (operation, del_op)->
|
||||||
return
|
return
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -466,34 +507,34 @@ module.exports = ()->
|
|||||||
# TODO: consider doing this in a more consistent manner. This could also be
|
# TODO: consider doing this in a more consistent manner. This could also be
|
||||||
# done with execute. But currently, there are no specital Insert-ops for ListManager.
|
# done with execute. But currently, there are no specital Insert-ops for ListManager.
|
||||||
#
|
#
|
||||||
callOperationSpecificInsertEvents: (op)->
|
callOperationSpecificInsertEvents: (operation)->
|
||||||
if op.next_cl.type is "Delimiter" and op.prev_cl.type isnt "Delimiter"
|
if operation.next_cl.type is "Delimiter" and operation.prev_cl.type isnt "Delimiter"
|
||||||
# this replaces another Replaceable
|
# this replaces another Replaceable
|
||||||
if not op.is_deleted # When this is received from the HB, this could already be deleted!
|
if not operation.is_deleted # When this is received from the HB, this could already be deleted!
|
||||||
old_value = op.prev_cl.val()
|
old_value = operation.prev_cl.val()
|
||||||
@callEventDecorator [
|
@callEventDecorator [
|
||||||
type: "update"
|
type: "update"
|
||||||
changedBy: op.uid.creator
|
changedBy: operation.uid.creator
|
||||||
oldValue: old_value
|
oldValue: old_value
|
||||||
]
|
]
|
||||||
op.prev_cl.applyDelete()
|
operation.prev_cl.applyDelete()
|
||||||
else if op.next_cl.type isnt "Delimiter"
|
else if operation.next_cl.type isnt "Delimiter"
|
||||||
# This won't be recognized by the user, because another
|
# This won't be recognized by the user, because another
|
||||||
# concurrent operation is set as the current value of the RM
|
# concurrent operation is set as the current value of the RM
|
||||||
op.applyDelete()
|
operation.applyDelete()
|
||||||
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
|
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
|
||||||
@callEventDecorator [
|
@callEventDecorator [
|
||||||
type: "add"
|
type: "add"
|
||||||
changedBy: op.uid.creator
|
changedBy: operation.uid.creator
|
||||||
]
|
]
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
callOperationSpecificDeleteEvents: (operation, del_op)->
|
||||||
if op.next_cl.type is "Delimiter"
|
if operation.next_cl.type is "Delimiter"
|
||||||
@callEventDecorator [
|
@callEventDecorator [
|
||||||
type: "delete"
|
type: "delete"
|
||||||
changedBy: del_op.uid.creator
|
changedBy: del_op.uid.creator
|
||||||
oldValue: op.val()
|
oldValue: operation.val()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -528,6 +569,4 @@ module.exports = ()->
|
|||||||
# throw new Error "Replace Manager doesn't contain anything."
|
# throw new Error "Replace Manager doesn't contain anything."
|
||||||
o.val?() # ? - for the case that (currently) the RM does not contain anything (then o is a Delimiter)
|
o.val?() # ? - for the case that (currently) the RM does not contain anything (then o is a Delimiter)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
basic_ops
|
basic_ops
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://dadamonad.github.io/yjs/",
|
"homepage": "https://dadamonad.github.io/yjs/",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bintrees": "git://github.com/cphyc/js_bintrees#rbtree-by-index"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^2.2.0",
|
"chai": "^2.2.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user