Change ListManager implementation

This commit is contained in:
Corentin STG_CADIOU 2015-07-10 15:37:16 +02:00
parent 2723fae12c
commit 9252c9f78a
12 changed files with 950 additions and 1956 deletions

File diff suppressed because one or more lines are too long

View File

@ -519,9 +519,9 @@ module.exports = function() {
o = this.prev_cl.next_cl; o = this.prev_cl.next_cl;
i = distance_to_origin; i = distance_to_origin;
while (true) { while (true) {
oDistance = o.getDistanceToOrigin();
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;

View File

@ -124,6 +124,7 @@ 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();
ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations); ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations);
} }
@ -183,107 +184,72 @@ module.exports = function() {
return this.beginning.next_cl; return this.beginning.next_cl;
}; };
ListManager.prototype.getNext = function(start) { ListManager.prototype.getNextNonDeleted = function(start) {
var o; var operation;
o = start.next_cl; if (start.isDeleted()) {
while (!(o instanceof ops.Delimiter)) { operation = start.next_cl;
if (o.is_deleted) { while (!(operation instanceof ops.Delimiter)) {
o = o.next_cl; if (operation.is_deleted) {
} else if (o instanceof ops.Delimiter) { operation = operation.next_cl;
} else if (operation instanceof ops.Delimiter) {
return false;
} else {
break;
}
}
} else {
operation = start.node.next().node;
if (!operation) {
return false; return false;
} else {
break;
} }
} }
return o; 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; var ref;
if (pos != null) { if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
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; var ref;
if (pos != null) { if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
o = this.getOperationByPosition(pos + 1); return this.shortTree.find(pos);
if (!(o instanceof ops.Delimiter)) {
return o;
} else {
return null;
}
} else { } else {
throw new Error("you must specify a position parameter"); return this.shortTree.map(function(operation) {
return operation;
});
} }
}; };
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) {
@ -301,14 +267,20 @@ module.exports = function() {
}; };
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.data;
} else {
rightNode = left.node.nextNode();
leftNode = left.node;
right = rightNode.data;
} }
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)).execute();
this.shortTree.insert_between(leftNode, rightNode, tmp);
} 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];
@ -316,6 +288,7 @@ module.exports = function() {
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)).execute();
leftNode = this.shortTree.insert_between(leftNode, rightNode, tmp);
left = tmp; left = tmp;
} }
} }
@ -328,36 +301,35 @@ module.exports = function() {
return this.insertAfter(ith, contents); return this.insertAfter(ith, contents);
}; };
ListManager.prototype.deleteRef = function(o, length) { ListManager.prototype.deleteRef = function(operation, length) {
var d, delete_ops, i, j, ref; var deleteOperation, i, j, ref;
if (length == null) { if (length == null) {
length = 1; length = 1;
} }
delete_ops = [];
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.node.traverse_up(function(node, parent) {
while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) { return parent.deleted_weight = (parent.deleted_weight || 0) + 1;
o = o.next_cl; });
} this.shortTree.remove_node(operation.node);
delete_ops.push(d._encode()); operation = getNextNonDeleted(operation);
} }
return this; return this;
}; };
ListManager.prototype["delete"] = function(position, length) { ListManager.prototype["delete"] = function(position, length) {
var o; var operation;
if (length == null) { if (length == null) {
length = 1; length = 1;
} }
o = this.getOperationByPosition(position + 1); operation = this.getOperationByPosition(position + 1);
return this.deleteRef(o, length); return this.deleteRef(operation, length);
}; };
ListManager.prototype.callOperationSpecificInsertEvents = function(op) { ListManager.prototype.callOperationSpecificInsertEvents = function(operation) {
var getContentType; var getContentType;
getContentType = function(content) { getContentType = function(content) {
if (content instanceof ops.Operation) { if (content instanceof ops.Operation) {
@ -369,25 +341,25 @@ module.exports = function() {
return this.callEvent([ return this.callEvent([
{ {
type: "insert", type: "insert",
reference: op, reference: operation,
position: op.getPosition(), position: operation.getPosition(),
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) {
return this.callEvent([ return this.callEvent([
{ {
type: "delete", type: "delete",
reference: op, reference: operation,
position: op.getPosition(), position: operation.getPosition(),
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()
} }
]); ]);
}; };
@ -441,14 +413,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 {
@ -456,7 +428,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;
} }
@ -468,13 +440,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();
@ -542,40 +514,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()
} }
]); ]);
} }
@ -609,121 +581,6 @@ module.exports = function() {
return ReplaceManager; return ReplaceManager;
})(ops.ListManager);
ops.FastListManager = (function(superClass) {
extend(FastListManager, superClass);
function FastListManager() {
this.shortTree = new RBTreeByIndex();
FastListManager.__super__.constructor.apply(this, arguments);
}
FastListManager.prototype.type = 'FastListManager';
FastListManager.prototype.getNext = function(start) {
return start.node.next().data;
};
FastListManager.prototype.getPrev = function(start) {
return start.node.prev().data;
};
FastListManager.prototype.map = function(fun) {
return this.shortTree.map(function(operation) {
return fun(operation);
});
};
FastListManager.prototype.fold = function(init, f) {
this.shortTree.each(function(operation) {
return init = f(init, operation);
});
return init;
};
FastListManager.prototype.val = function(position) {
if (position != null) {
return (this.shortTree.find(position)).val();
} else {
return this.shortTree.map(function(operation) {
return operation.val();
});
}
};
FastListManager.prototype.ref = function(position) {
if (position != null) {
return this.shortTree.find(position);
} else {
return this.shortTree.map(function(operation) {
return operation;
});
}
};
FastListManager.prototype.push = function(content) {
return this.insertAfter(this.end.prev_cl, [content]);
};
FastListManager.prototype.insertAfter = function(left, contents) {
var nodeOnLeft, nodeOnRight, operation, right;
nodeOnLeft = left === this.beginning ? null : left.node;
nodeOnRight = nodeOnLeft ? nodeOnLeft.next() : this.shortTree.find(0);
right = nodeOnRight ? nodeOnRight.data : this.end;
left = right.prev_cl;
if (contents instanceof ops.Operation) {
operation = new ops.Insert(null, content, null, void 0, void 0, left, right);
operation.node = this.shortTree.insertAfter(nodeOnLeft, operation);
operation.execute();
} else {
contents.forEach(function(content) {
if ((content != null) && (content._name != null) && (content._getModel != null)) {
content = content._getModel(this.custom_types, this.operations);
}
operation = new ops.Insert(null, c, null, void 0, void 0, left, right);
operation.node = this.shortTree.insertAfter(nodeOnLeft, operation);
operation.execute();
left = operation;
return nodeOnLeft = operation.node;
});
}
return this;
};
FastListManager.prototype.insert = function(position, contents) {
var left;
left = (this.shortTree.find(position - 1)) || this.beginning;
return this.insertAfter(left, contents);
};
FastListManager.prototype["delete"] = function(position, length) {
var deleteOp, delete_ops, i, j, nextNode, operation, ref;
if (length == null) {
length = 1;
}
delete_ops = [];
operation = this.shortTree.find(position);
for (i = j = 0, ref = length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
if (operation instanceof ops.Delimiter) {
break;
}
deleteOp = new ops.Delete(null, void 0, operation);
deleteOp.execute();
nextNode = operation.node.next();
operation = nextNode ? nextNode.data : this.end;
this.shortTree.remove_node(operation.node);
operation.node = null;
delete_ops.push(d._encode());
}
return this;
};
FastListManager.prototype.getLength = function() {
return this.tree.size;
};
return FastListManager;
})(ops.ListManager); })(ops.ListManager);
return basic_ops; return basic_ops;
}; };

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

@ -520,10 +520,10 @@ module.exports = ()->
# case 3: $origin > $o.origin # case 3: $origin > $o.origin
# $this insert_position is to the left of $o (forever!) # $this insert_position is to the left of $o (forever!)
while true while true
oDistance = o.getDistanceToOrigin()
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

View File

@ -108,6 +108,8 @@ module.exports = ()->
@beginning.next_cl = @end @beginning.next_cl = @end
@beginning.execute() @beginning.execute()
@end.execute() @end.execute()
@shortTree = new RBTreeByIndex()
super custom_type, uid, content, content_operations super custom_type, uid, content, content_operations
type: "ListManager" type: "ListManager"
@ -157,66 +159,47 @@ module.exports = ()->
@beginning.next_cl @beginning.next_cl
# get the next non-deleted operation # get the next non-deleted operation
getNext: (start)-> getNextNonDeleted: (start)->
o = start.next_cl if start.isDeleted()
while not ((o instanceof ops.Delimiter)) operation = start.next_cl
if o.is_deleted while not ((operation instanceof ops.Delimiter))
o = o.next_cl if operation.is_deleted
else if o instanceof ops.Delimiter operation = operation.next_cl
else if operation instanceof ops.Delimiter
return false
else break
else
operation = start.node.next().node
if not operation
return false return false
else break
o operation
# Transforms the list to an array
# Transforms the 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 0 <= pos? < @shortTree.size
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 0 <= pos? < @shortTree.size
o = @getOperationByPosition(pos+1) @shortTree.find(pos)
if not (o instanceof ops.Delimiter)
o
else
null
# throw new Error "this position does not exist"
else else
throw new Error "you must specify a position parameter" @shortTree.map (operation) ->
operation
# #
# Retrieves the x-th not deleted element. # Retrieves the x-th not deleted element.
@ -224,24 +207,12 @@ 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]
@ -255,19 +226,28 @@ module.exports = ()->
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 = rightNode.data
else
rightNode = left.node.nextNode()
leftNode = left.node
right = rightNode.data
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).execute()
@shortTree.insert_between leftNode, rightNode, tmp
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).execute()
leftNode = @shortTree.insert_between leftNode, rightNode, tmp
left = tmp left = tmp
@ @
@ -288,26 +268,25 @@ module.exports = ()->
# #
# @return {ListManager Type} This String object # @return {ListManager Type} This String object
# #
deleteRef: (operation, length = 1) ->
deleteRef: (o, length = 1) ->
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 operation.node.traverse_up (node, parent) ->
while (not (o instanceof ops.Delimiter)) and o.isDeleted() parent.deleted_weight = (parent.deleted_weight or 0) + 1
o = o.next_cl @shortTree.remove_node operation.node
delete_ops.push d._encode()
operation = getNextNonDeleted operation
@ @
delete: (position, length = 1)-> delete: (position, length = 1)->
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character operation = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
@deleteRef o, length @deleteRef operation, length
callOperationSpecificInsertEvents: (op)-> callOperationSpecificInsertEvents: (operation)->
getContentType = (content)-> getContentType = (content)->
if content instanceof ops.Operation if content instanceof ops.Operation
content.getCustomType() content.getCustomType()
@ -315,22 +294,22 @@ module.exports = ()->
content content
@callEvent [ @callEvent [
type: "insert" type: "insert"
reference: op reference: operation
position: op.getPosition() position: operation.getPosition()
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)->
@callEvent [ @callEvent [
type: "delete" type: "delete"
reference: op reference: operation
position: op.getPosition() position: operation.getPosition()
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)->
@ -381,19 +360,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
@ -403,11 +382,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
# #
@ -492,34 +471,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()
] ]
@ -554,103 +533,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)
class ops.FastListManager extends ops.ListManager
constructor: () ->
@shortTree = new RBTreeByIndex()
super arguments...
type: 'FastListManager'
getNext: (start)->
start.node.next().data
getPrev: (start)->
start.node.prev().data
map: (fun)->
@shortTree.map (operation) ->
fun operation
fold: (init, f) ->
@shortTree.each (operation) ->
init = f(init, operation)
init
val: (position)->
if position?
(@shortTree.find position).val()
else
@shortTree.map (operation) ->
operation.val()
ref: (position)->
if position?
@shortTree.find position
else
@shortTree.map (operation) ->
operation
push: (content) ->
@insertAfter @end.prev_cl, [content]
insertAfter: (left, contents) ->
# the operation is inserted just before the next non deleted insertion
# the operation is stored in the tree just after left
nodeOnLeft = if left is @beginning then null else left.node
nodeOnRight = if nodeOnLeft then nodeOnLeft.next() else @shortTree.find 0
right = if nodeOnRight then nodeOnRight.data else @end
left = right.prev_cl
if contents instanceof ops.Operation
operation = new ops.Insert null, content, null, undefined, undefined, left, right
operation.node = @shortTree.insertAfter nodeOnLeft, operation
operation.execute()
else
contents.forEach (content) ->
if content? and content._name? and content._getModel?
content = content._getModel(@custom_types, @operations)
#TODO: override Insert.prototype.getDistanceToOrigin (in Insert.prototype.execute)
operation = new ops.Insert null, c, null, undefined, undefined, left, right
operation.node = @shortTree.insertAfter nodeOnLeft, operation
operation.execute()
left = operation
nodeOnLeft = operation.node
@
insert: (position, contents) ->
left = (@shortTree.find (position-1)) or @beginning
@insertAfter left, contents
delete: (position, length = 1) ->
delete_ops = []
operation = @shortTree.find position
for i in [0...length]
if operation instanceof ops.Delimiter
break
deleteOp = new ops.Delete null, undefined, operation
# execute the delete operation
deleteOp.execute()
# get the next operation from the shortTree
nextNode = operation.node.next()
operation = if nextNode then nextNode.data else @end
# remove the operation from the shortTree
@shortTree.remove_node operation.node
operation.node = null
delete_ops.push d._encode()
@
getLength: () ->
@tree.size
basic_ops basic_ops

4
y.js

File diff suppressed because one or more lines are too long