Merge a4c83482ca02bf9a54a1ae004d9309f50307762e into 4feaf6c6fb507432aefa1cbfedd602218add81ab

This commit is contained in:
Corentin Cadiou 2015-07-28 13:33:47 +00:00
commit f9fdc916ad
15 changed files with 24284 additions and 41062 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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!"
*/ */

View File

@ -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 {

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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

View File

@ -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",

4
y.js

File diff suppressed because one or more lines are too long