changesets
Advanced tools
Comparing version 0.2.5 to 0.2.6
@@ -5,3 +5,3 @@ { | ||
"description": "A Changeset library incorporating an operational transformation (OT) algorithm -- for node and the browser!", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"keywords": [ | ||
@@ -8,0 +8,0 @@ "operational transformation", |
@@ -154,3 +154,3 @@ /*! | ||
// Hack that sorts out no-ops as well as changesets | ||
// Hack that sorts out no-ops and cracks up changesets (prevents nested changesets!) | ||
Changeset.prototype.push = function() { | ||
@@ -184,9 +184,10 @@ var that = this | ||
, pos = (op.pos).toString(36) | ||
, initialLength = (op.initialLength).toString(36) | ||
, accessory = (op.accessory).toString(36) | ||
if(op instanceof Delete) { | ||
return '-'+pos+':'+text+':'+accessory | ||
return '-'+pos+':'+initialLength+':'+text+':'+accessory | ||
} | ||
if(op instanceof Insert) { | ||
return '+'+pos+':'+text+':'+accessory | ||
return '+'+pos+':'+initialLength+':'+text+':'+accessory | ||
} | ||
@@ -208,3 +209,3 @@ }).join('') | ||
if(packed == '') return new Changeset | ||
var matches = packed.match(/(\+|-)\w+?:[^:]+?:\w+/g) | ||
var matches = packed.match(/(\+|-)\w+?:\w+?:[^:]+?:\w+/g) | ||
if(!matches) throw new Error('Cannot unpack invalid serialized changeset string') | ||
@@ -217,8 +218,9 @@ | ||
var pos = parseInt(props[0], 36) | ||
, text = props[1].replace(/%25/g, '%').replace(/%3A/gi, ':') | ||
, accessory = parseInt(props[2], 36) | ||
if(type == '-') return cs.push(new Delete(pos, text, accessory)) | ||
if(type == '+') return cs.push(new Insert(pos, text, accessory)) | ||
, initialLength = parseInt(props[1], 36) | ||
, text = props[2].replace(/%3A/gi, ':').replace(/%25/g, '%') | ||
, accessory = parseInt(props[3], 36) | ||
if(type == '-') return cs.push(new Delete(initialLength, pos, text, accessory)) | ||
if(type == '+') return cs.push(new Insert(initialLength, pos, text, accessory)) | ||
}) | ||
return cs | ||
} |
@@ -53,5 +53,6 @@ /*! | ||
, cs = new Changeset | ||
, initialLength = text1.length | ||
diff.forEach(function(d) { | ||
if (dmp.DIFF_DELETE == d[0]) { | ||
cs.push(new Delete(i, d[1], accessory)) | ||
cs.push(new Delete(initialLength, i, d[1], accessory)) | ||
i += d[1].length | ||
@@ -61,3 +62,3 @@ } | ||
if (dmp.DIFF_INSERT == d[0]) { | ||
cs.push(new Insert(i, d[1], accessory)) | ||
cs.push(new Insert(initialLength, i, d[1], accessory)) | ||
} | ||
@@ -64,0 +65,0 @@ |
@@ -35,5 +35,6 @@ /*! | ||
*/ | ||
function Delete(pos, text, accessory) { | ||
function Delete(initialLength, pos, text, accessory) { | ||
this.initialLength = initialLength | ||
this.accessory = accessory || 0 | ||
this.pos = pos | ||
this.pos = pos|0 | ||
this.len = text.length | ||
@@ -62,2 +63,3 @@ this.text = text | ||
if (change instanceof Delete) { | ||
var newInitialLength = this.initialLength-change.len | ||
@@ -70,3 +72,3 @@ // 'abc' => 0:-2('c') | 1:-1('ac') | ||
var startOfOther = Math.min(change.pos - this.pos, this.len) | ||
return new Delete(this.pos, this.text.substr(0, startOfOther) + this.text.substr(startOfOther + change.len), this.accessory) | ||
return new Delete(newInitialLength, this.pos, this.text.substr(0, startOfOther) + this.text.substr(startOfOther + change.len), this.accessory) | ||
} | ||
@@ -82,3 +84,3 @@ | ||
// the other deletion's range is shorter than mine | ||
return new Delete(this.pos, this.text.substr(change.len), this.accessory) | ||
return new Delete(newInitialLength, this.pos, this.text.substr(change.len), this.accessory) | ||
} | ||
@@ -91,4 +93,4 @@ | ||
if(overlap >= this.len) return new Equal | ||
if(overlap > 0) return new Delete(change.pos, this.text.substr(overlap), this.accessory) | ||
return new Delete(this.pos-change.len, this.text, this.accessory) | ||
if(overlap > 0) return new Delete(newInitialLength, change.pos, this.text.substr(overlap), this.accessory) | ||
return new Delete(newInitialLength, this.pos-change.len, this.text, this.accessory) | ||
} | ||
@@ -99,3 +101,3 @@ } | ||
if (change instanceof Insert) { | ||
var newInitialLength = this.initialLength+change.len | ||
// 'abc' => 0:-1('bc') | 3:+x('abcx') | ||
@@ -109,6 +111,6 @@ // 'bcx' | ||
return new Changeset( | ||
new Delete(this.pos, this.text.substr(0, firstHalfLength), this.accessory) | ||
, new Delete(change.pos+change.len, this.text.substr(firstHalfLength), this.accessory)) | ||
new Delete(newInitialLength, this.pos, this.text.substr(0, firstHalfLength), this.accessory) | ||
, new Delete(newInitialLength, change.pos+change.len, this.text.substr(firstHalfLength), this.accessory)) | ||
} | ||
return new Delete(this.pos, this.text, this.accessory) | ||
return new Delete(newInitialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -119,3 +121,3 @@ | ||
if (this.pos == change.pos) { | ||
return new Delete(this.pos+change.len, this.text, this.accessory) | ||
return new Delete(newInitialLength, this.pos+change.len, this.text, this.accessory) | ||
} | ||
@@ -126,3 +128,3 @@ | ||
if (change.pos < this.pos) { | ||
return new Delete(this.pos+change.len, this.text, this.accessory) | ||
return new Delete(newInitialLength, this.pos+change.len, this.text, this.accessory) | ||
} | ||
@@ -157,3 +159,3 @@ } | ||
Delete.prototype.invert = function() { | ||
return new Insert(this.pos, this.text, this.accessory) | ||
return new Insert(this.initialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -167,4 +169,5 @@ | ||
Delete.prototype.apply = function(text) { | ||
if(text.length != this.initialLength) throw new Error('Text length doesn\'t match expected length. It\'s most likely you have missed a transformation: expected:'+this.initialLength+', actual:'+text.length) | ||
if(text.substr(this.pos, this.len) != this.text) throw new Error('Applying delete operation: Passed context doesn\'t match assumed context: '+JSON.stringify(this)+', actual context: "'+text.substr(this.pos, this.len)+'"') | ||
return text.slice(0, this.pos) + text.slice(this.pos+this.len) | ||
} |
@@ -35,3 +35,4 @@ /*! | ||
*/ | ||
function Insert(pos, text, accessory) { | ||
function Insert(initialLength, pos, text, accessory) { | ||
this.initialLength = initialLength | ||
this.accessory = accessory || 0 | ||
@@ -59,7 +60,7 @@ this.pos = pos | ||
if (change instanceof Insert) { | ||
var newInitialLength = this.initialLength+change.len | ||
// 'abc' => 0:+x('xabc') | 3:+x('abcx') | ||
// 'xabcx' | ||
if (this.pos < change.pos) { | ||
return new Insert(this.pos, this.text, this.accessory) | ||
return new Insert(newInitialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -70,3 +71,3 @@ | ||
if (this.pos == change.pos && this.accessory < change.accessory) { | ||
return new Insert(this.pos, this.text, this.accessory) | ||
return new Insert(newInitialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -77,3 +78,3 @@ | ||
if (change.pos <= this.pos) { | ||
return new Insert(this.pos+change.len, this.text, this.accessory) | ||
return new Insert(newInitialLength, this.pos+change.len, this.text, this.accessory) | ||
} | ||
@@ -84,7 +85,8 @@ } | ||
if (change instanceof Delete) { | ||
var newInitialLength = this.initialLength-change.len | ||
// 'abc'=> 1:+x('axbc') | 2:-1('ab') | ||
// 'axb' | ||
if (this.pos < change.pos) { | ||
return new Insert(this.pos, this.text, this.accessory) | ||
return new Insert(newInitialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -95,3 +97,3 @@ | ||
if (this.pos == change.pos) { | ||
return new Insert(this.pos, this.text, this.accessory) | ||
return new Insert(newInitialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -103,3 +105,3 @@ | ||
// Shift this back by `change.len`, but not more than `change.pos` | ||
return new Insert(Math.max(this.pos - change.len, change.pos), this.text, this.accessory) | ||
return new Insert(newInitialLength, Math.max(this.pos - change.len, change.pos), this.text, this.accessory) | ||
} | ||
@@ -134,3 +136,3 @@ } | ||
Insert.prototype.invert = function() { | ||
return new Delete(this.pos, this.text, this.accessory) | ||
return new Delete(this.initialLength, this.pos, this.text, this.accessory) | ||
} | ||
@@ -144,3 +146,4 @@ | ||
Insert.prototype.apply = function(text) { | ||
if(text.length != this.initialLength) throw new Error('Text length doesn\'t match expected length. It\'s most likely you have missed a transformation: expected:'+this.initialLength+', actual:'+text.length) | ||
return text.slice(0, this.pos) + this.text + text.slice(this.pos) | ||
} |
{ | ||
"name": "changesets", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"description": "A Changeset library incorporating an operational transformation (OT) algorithm -- for node and the browser!", | ||
@@ -5,0 +5,0 @@ "repository": { |
@@ -129,11 +129,9 @@ | ||
A Changeset, in the context of this lib, is defined as a group of context-equivalent operations. This means, they can be applied in any possible order as long as they're transformed against the previous ones to match the current document state. | ||
When you call Changeset#apply(), the method first transforms all contained operations on top of each other in a certain order, and then applies them all in sequence on the passed document. | ||
When you call Changeset#apply(), the method first transforms all contained operations on top of each other in a certain order, and then applies them all in sequence on the passed document. Each operation keeps track of the expected initial text length, so it's easy to detect inconsistencies. | ||
# Todo | ||
* What happens, if you apply the same CS multiple times, with or without transforming it? | ||
* Perhaps add text length diff to `Operation`s in order to be able validate them | ||
* Simplify anyundo (large numbers of changesets have to be transformed against each other and an undo changseset) | ||
* Use best effort (aka Fuzzy Patch -- see http://neil.fraser.name/writing/patch/) when applying a changeset, but allow people to check whether the changeset fits neatly, so they can still refuse changesets that don't fit neatly | ||
* Use best effort (aka Fuzzy Patch -- see http://neil.fraser.name/writing/patch/) when applying a changeset, but allow people to check whether the changeset fits neatly, so they can still refuse changesets that don't fit neatly (?) | ||
# License | ||
MIT |
@@ -175,2 +175,15 @@ /*! | ||
suite.addBatch({ | ||
'validation': | ||
{ topic: function() { | ||
var cs = engine.constructChangeset("1234", "1234b") | ||
cs.apply(cs.apply("1234")) | ||
} | ||
, 'should error if you apply the same cs twice, without transforming it': function(er) { | ||
console.log(er) | ||
assert.throws(er) | ||
} | ||
} | ||
}) | ||
suite.export(module) |
Sorry, the diff of this file is too big to display
151791
3774
136