diff --git a/build/text/utils.js b/build/text/utils.js index 0bd636c..fc896d9 100644 --- a/build/text/utils.js +++ b/build/text/utils.js @@ -79,45 +79,58 @@ function selectionUpdate(selection, op) { return selection; } if (op instanceof insert_1.Insert) { - if (op.at < selection.at) { - return selection.moveRightBy(op.length); + // Don't move cursor when insert is done at the same position + if (selection.isCursor() && op.at === selection.at) { + return selection; } - else if (op.at === selection.at) { - return selection.isCursor() - ? selection - : selection.moveRightBy(op.length); + // is after selection: + // sssss + // iii + // iiiiii + if (op.at >= selection.endsAt) { + return selection; } - else if (selection.isInside(op.at)) { - return selection.expandBy(op.length); + // is before selection or on the same position: + // sssss + // iii + // iiii + // iiiiii + if (op.at <= selection.at) { + return selection.moveRightBy(op.length); } - return selection; + // is inside selection: + // sssss + // i + // iiii + return selection.expandBy(op.length); } if (op instanceof delete_1.Delete) { - if (op.at < selection.at) { - if (selection.isInside(op.endsAt)) { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(selection.at - op.endsAt); - } - else if (op.endsAt < selection.at) { - return selection - .moveRightBy(-op.length); - } - else { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(-selection.length); - } + // is before selection: + // ssssss + // ddd + if (op.endsAt < selection.at) { + return selection.moveRightBy(-op.length); } - else if (op.at === selection.at) { - return selection - .expandBy(selection.at - op.endsAt); + // is after selection: + // ssssss + // ddddd + if (op.at > selection.endsAt) { + return selection; } - else if (selection.isInside(op.at)) { - return selection - .expandBy(op.at - selection.endsAt); + // starts inside selection block: + // ssssss + // dddddddddd + // ddd + // ddd + // ddddddddd + if (op.at >= selection.at) { + return selection.expandBy(-Math.min(selection.endsAt - op.at, op.length)); } - return selection; + // ends inside selection: + // ssssss + // dddd + // dddddddd + return selection.expandBy(selection.at - op.endsAt).moveRightBy(op.at - selection.at); } return selection; } diff --git a/build/text/utils.js.map b/build/text/utils.js.map index 7fd938b..7c15b3b 100644 --- a/build/text/utils.js.map +++ b/build/text/utils.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/text/utils.ts"],"names":[],"mappings":";;AAAA,qCAAgC;AAChC,qCAAgC;AAChC,2CAAsC;AAGtC,2EAAoE;AAEpE,kBAAyB,IAAU;IACjC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAFD,4BAEC;AAED,iBAAwB,IAAU;IAChC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAqB,EAAE,IAAuB;QAChE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAC/D,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAJD,0BAIC;AAED,2BAAqC,KAAU,EAAE,GAAW;IAC1D,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;QACvB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC;AACf,CAAC;AAND,8CAMC;AAED,qBAAqB;AACrB,0BAAiC,IAAc,EAAE,EAAa;IAC5D,EAAE,CAAC,CAAC,EAAE,YAAY,eAAM,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC;IACd,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,eAAM,CAAC,CAAC,CAAC;QAChC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;YACd,MAAM,CAAC,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,IAAI,CAAC;AACd,CAAC;AAjBD,4CAiBC;AAED,kBAAyB,KAAe;IACtC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC;AAFD,4BAEC;AAED,sBAA6B,IAAU;IACrC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACjC,CAAC;AAFD,oCAEC;AAED,sBAA6B,IAAU,EAAE,QAAmB;IAC1D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAY,EAAE,EAAqB;QACrD,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAY,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC,EAAE,QAAQ,CAAC,CAAC;AACf,CAAC;AAJD,oCAIC;AAQD,uBAA8B,IAAU,EAAE,QAAmB;IAC3D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAoC,EAAE,EAAqB;QAC7E,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,GAAoC,EAAE,CAAY;YAC7E,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAoC,EAAE,CAAY,EAAE,GAAW;gBAChF,EAAE,CAAC,CAAC,CAAC,YAAY,qBAAS,CAAC,CAAC,CAAC;oBAC3B,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;wBACvB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;oBAC9B,CAAC;gBACH,CAAC;gBAED,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACnC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACpC,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC,EAAE,IAAI,uCAAiB,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC7D,CAAC;AAfD,sCAeC;AAED,yBAAgC,SAAoB,EAAE,EAAa;IACjE,EAAE,CAAC,CAAC,EAAE,YAAY,qBAAS,CAAC,CAAC,CAAC;QAC5B,EAAE,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,CAAC,SAAS,CAAC;IACnB,CAAC;IAED,EAAE,CAAC,CAAC,EAAE,YAAY,eAAM,CAAC,CAAC,CAAC;QACzB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YACzB,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1C,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;kBACvB,SAAS;kBACT,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,CAAC,SAAS,CAAC;IACnB,CAAC;IAED,EAAE,CAAC,CAAC,EAAE,YAAY,eAAM,CAAC,CAAC,CAAC;QACzB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YACzB,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAClC,MAAM,CAAC,SAAS;qBACb,WAAW,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;qBACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;YACxC,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;gBACpC,MAAM,CAAC,SAAS;qBACb,WAAW,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,MAAM,CAAC,SAAS;qBACb,WAAW,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;qBACjC,QAAQ,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,SAAS;iBACb,QAAQ,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrC,MAAM,CAAC,SAAS;iBACb,QAAQ,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,CAAC,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC;AACnB,CAAC;AAjDD,0CAiDC"} \ No newline at end of file +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/text/utils.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/crdt.js b/dist/crdt.js index bd71bb5..9b7b464 100644 --- a/dist/crdt.js +++ b/dist/crdt.js @@ -627,45 +627,58 @@ function selectionUpdate(selection, op) { return selection; } if (op instanceof insert_1.Insert) { - if (op.at < selection.at) { - return selection.moveRightBy(op.length); + // Don't move cursor when insert is done at the same position + if (selection.isCursor() && op.at === selection.at) { + return selection; } - else if (op.at === selection.at) { - return selection.isCursor() - ? selection - : selection.moveRightBy(op.length); + // is after selection: + // sssss + // iii + // iiiiii + if (op.at >= selection.endsAt) { + return selection; } - else if (selection.isInside(op.at)) { - return selection.expandBy(op.length); + // is before selection or on the same position: + // sssss + // iii + // iiii + // iiiiii + if (op.at <= selection.at) { + return selection.moveRightBy(op.length); } - return selection; + // is inside selection: + // sssss + // i + // iiii + return selection.expandBy(op.length); } if (op instanceof delete_1.Delete) { - if (op.at < selection.at) { - if (selection.isInside(op.endsAt)) { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(selection.at - op.endsAt); - } - else if (op.endsAt < selection.at) { - return selection - .moveRightBy(-op.length); - } - else { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(-selection.length); - } + // is before selection: + // ssssss + // ddd + if (op.endsAt < selection.at) { + return selection.moveRightBy(-op.length); } - else if (op.at === selection.at) { - return selection - .expandBy(selection.at - op.endsAt); + // is after selection: + // ssssss + // ddddd + if (op.at > selection.endsAt) { + return selection; } - else if (selection.isInside(op.at)) { - return selection - .expandBy(op.at - selection.endsAt); + // starts inside selection block: + // ssssss + // dddddddddd + // ddd + // ddd + // ddddddddd + if (op.at >= selection.at) { + return selection.expandBy(-Math.min(selection.endsAt - op.at, op.length)); } - return selection; + // ends inside selection: + // ssssss + // dddd + // dddddddd + return selection.expandBy(selection.at - op.endsAt).moveRightBy(op.at - selection.at); } return selection; } diff --git a/dist/crdt.min.js b/dist/crdt.min.js index f2cfa6e..d405032 100644 --- a/dist/crdt.min.js +++ b/dist/crdt.min.js @@ -1 +1 @@ -!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).crdt=f()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n||e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o=max)},exports.axioms=function(assert,a,b,c){assert(equal(merge(a,b),merge(b,a)),"is not commutative"),assert(equal(merge(a,merge(b,c)),merge(merge(a,b),c)),"is not associative"),assert(equal(merge(a,a),a),"is not idempotent")}},{}],2:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Increment{constructor(value){this.value=value,this.value=value}merge(b){return new Increment(Math.max(this.value,b.value))}equal(b){return this.value===b.value}increment(){return new Increment(this.value+1)}}exports.Increment=Increment},{}],3:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const functions=require("./functions"),increment=require("./increment"),order=require("./order"),structures=require("./structures"),text=require("./text");exports.default={functions:functions,increment:increment,order:order,structures:structures,text:text}},{"./functions":1,"./increment":2,"./order":5,"./structures":7,"./text":15}],4:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const naive_array_list_1=require("../structures/naive-array-list"),sorted_set_array_1=require("../structures/sorted-set-array"),vector_clock_1=require("./vector-clock"),emptyVector=new sorted_set_array_1.SortedSetArray(new naive_array_list_1.NaiveArrayList([]));exports.createVectorClock=function(id,version,vector){return new vector_clock_1.VectorClock(new vector_clock_1.Id(id,version||0),vector||emptyVector)}},{"../structures/naive-array-list":8,"../structures/sorted-set-array":12,"./vector-clock":6}],5:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./vector-clock")),__export(require("./factory"))},{"./factory":4,"./vector-clock":6}],6:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Id{constructor(node,version){this.node=node,this.version=version,this.node=node,this.version=version}next(){return new Id(this.node,this.version+1)}compare(b){return this.node.localeCompare(b.node)}toString(){return`Id(${this.node},${this.version})`}}exports.Id=Id;class VectorClock{constructor(id,vector){this.id=id,this.vector=vector,this.id=id;let{result:result,value:value}=vector.add(id);result===vector&&id.version>value.version&&(result=vector.remove(value).result.add(id).result),this.vector=result}toString(){const a=this.vector.reduce((r,i)=>r+i.toString(),"");return`VectorClock(${this.id},${a})`}next(){return new VectorClock(this.id.next(),this.vector.remove(this.id).result.add(this.id.next()).result)}equal(b){return this.vector.size()===b.vector.size()&&this.vector.reduce((eq,item)=>{if(eq){const{result:result,value:value}=b.vector.add(item);if(result===b.vector)return value.version===item.version}return!1},!0)}compare(b){return this.lessThan(b)?-1:b.lessThan(this)?1:this.equal(b)?0:this.id.compare(b.id)}lessThan(b){const{everyLEQ:everyLEQ,anyLT:anyLT}=this.vector.intersect(b.vector).reduce(({everyLEQ:everyLEQ,anyLT:anyLT},item)=>{const rA=this.vector.add(item).value,rB=b.vector.add(item).value;return anyLT=anyLT||rA.version{const{result:result,value:value}=b.vector.add(item);return result===b.vector&&value.version>item.version?vector.add(value).result:vector.add(item).result},this.vector.mempty());return new VectorClock(this.id,vector.union(b.vector))}}exports.VectorClock=VectorClock},{}],7:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./naive-array-list")),__export(require("./naive-immutable-map")),__export(require("./set-axioms")),__export(require("./ordered-map")),__export(require("./sorted-set-array"))},{"./naive-array-list":8,"./naive-immutable-map":9,"./ordered-map":10,"./set-axioms":11,"./sorted-set-array":12}],8:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class NaiveArrayList{constructor(array){this.array=array||[]}insert(at,item){const clone=this.array.slice(0);return clone.splice(at,0,item),new NaiveArrayList(clone)}remove(at){const clone=this.array.slice(0);return clone.splice(at,1),new NaiveArrayList(clone)}get(at){return this.array[at]}size(){return this.array.length}reduce(fn,aggregator){return this.array.reduce(fn,aggregator)}mempty(){return new NaiveArrayList}}exports.NaiveArrayList=NaiveArrayList},{}],9:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class NaiveImmutableMap{constructor(data){this.data=data,this.data=data||{}}set(key,value){const clone=Object.keys(this.data).reduce((clone,k)=>(clone[k]=this.data[k],clone),{});return clone[key]=value,new NaiveImmutableMap(clone)}get(key){return this.data[key]}reduce(fn,aggregator){return Object.keys(this.data).reduce((aggregator,key)=>fn(aggregator,this.data[key],key),aggregator)}}exports.NaiveImmutableMap=NaiveImmutableMap},{}],10:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Indexed{constructor(value,index){this.value=value,this.index=index}compare(b){return this.value.compare(b.value)}}exports.Indexed=Indexed;class OrderedMap{constructor(keys,values){this.keys=keys,this.values=values}set(key,value){const result=this.keys.add(new Indexed(key,this.keys.size()));return new OrderedMap(result.result,this.values.set(result.value.index,value))}get(key){const result=this.keys.add(new Indexed(key,this.keys.size()));return result.result!==this.keys?null:this.values.get(result.value.index)}merge(b){return b.reduce((aggregator,item,key)=>aggregator.set(key,item),this)}reduce(fn,aggregator){return this.keys.reduce((aggregator,key)=>fn(aggregator,this.values.get(key.index),key.value),aggregator)}}exports.OrderedMap=OrderedMap},{}],11:[function(require,module,exports){"use strict";function equal(a,b){return a.equal(b)}function union(a,b){return a.union(b)}function intersect(a,b){return a.intersect(b)}function difference(a,b){return a.difference(b)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.axioms=function(assert,a,b,c){assert(equal(union(union(a,b),c),union(a,union(b,c))),"associative union"),assert(equal(intersect(intersect(a,b),c),intersect(a,intersect(b,c))),"associative intersect"),assert(equal(union(a,intersect(b,c)),union(intersect(a,b),intersect(a,c))),"union distributes over intersection"),assert(equal(intersect(a,union(b,c)),intersect(union(a,b),union(a,c))),"intersection distributes over union"),assert(equal(difference(a,union(b,c)),intersect(difference(a,b),difference(a,c))),"De Morgan's law for union"),assert(equal(difference(a,intersect(b,c)),union(difference(a,b),difference(a,c))),"De Morgan's law for intersect")}},{}],12:[function(require,module,exports){"use strict";function divide(lower,upper,elements,item,onNew,onExists){const step=upper-lower;if(step<1)return onNew(item,elements,lower);const half=Math.trunc(step/2),idx=lower+half,elm=elements.get(idx),cmp=elm.compare(item);return cmp<0?divide(half?lower+half:upper,upper,elements,item,onNew,onExists):cmp>0?divide(lower,half?upper-half:lower,elements,item,onNew,onExists):onExists(elm,elements,idx)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.divide=divide;class Tuple{constructor(result,value){this.result=result,this.value=value}}exports.Tuple=Tuple;class SortedSetArray{constructor(elements){this.elements=elements}mempty(){return new SortedSetArray(this.elements.mempty())}size(){return this.elements.size()}add(value){return divide(0,this.elements.size(),this.elements,value,(item,elements,lower)=>new Tuple(new SortedSetArray(elements.insert(lower,item)),item),(item,elements)=>new Tuple(this,item))}remove(value){return divide(0,this.elements.size(),this.elements,value,(item,elements,lower)=>new Tuple(this,value),(item,elements,index)=>new Tuple(new SortedSetArray(elements.remove(index)),item))}has(value){return divide(0,this.elements.size(),this.elements,value,()=>!1,()=>!0)}union(b){return b.reduce((result,item)=>result.add(item).result,this)}intersect(b){return this.reduce((result,item)=>b.has(item)?result.add(item).result:result,this.mempty())}difference(b){return this.reduce((result,item)=>b.has(item)?result:result.add(item).result,this.mempty())}equal(b){return this.size()===b.size()&&b.reduce((equal,item)=>equal?this.has(item):equal,!0)}reduce(fn,accumulator){return this.elements.reduce(fn,accumulator)}}exports.SortedSetArray=SortedSetArray},{}],13:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Delete{constructor(at,length){this.at=at,this.length=length,this.at=at,this.length=length,this.endsAt=this.at+length}}exports.Delete=Delete},{}],14:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const naive_array_list_1=require("../structures/naive-array-list"),naive_immutable_map_1=require("../structures/naive-immutable-map"),ordered_map_1=require("../structures/ordered-map"),sorted_set_array_1=require("../structures/sorted-set-array"),text_1=require("./text");exports.createFromOrderer=function(order){return new text_1.Text(order,new ordered_map_1.OrderedMap(new sorted_set_array_1.SortedSetArray(new naive_array_list_1.NaiveArrayList([])),new naive_immutable_map_1.NaiveImmutableMap))}},{"../structures/naive-array-list":8,"../structures/naive-immutable-map":9,"../structures/ordered-map":10,"../structures/sorted-set-array":12,"./text":18}],15:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./insert")),__export(require("./delete")),__export(require("./selection")),__export(require("./text")),__export(require("./utils")),__export(require("./factory"))},{"./delete":13,"./factory":14,"./insert":16,"./selection":17,"./text":18,"./utils":19}],16:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Insert{constructor(at,value){this.at=at,this.value=value,this.at=at<0?0:at,this.value=String(value),this.length=this.value.length,this.endsAt=this.at+this.length}}exports.Insert=Insert},{}],17:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Selection{constructor(origin,at,length){this.origin=origin,this.at=at,this.length=length,this.origin=origin,this.at=at<0?0:at,this.length=length<0?0:length,this.endsAt=this.at+this.length}isCursor(){return 0===this.length}hasSameOrgin(b){return this.origin===b.origin}moveRightBy(step){return new Selection(this.origin,this.at+step,this.length)}expandBy(length){return new Selection(this.origin,this.at,this.length+length)}isInside(position){return this.atposition}}exports.Selection=Selection},{}],18:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const functions_1=require("../functions");class Text{constructor(order,setMap){this.order=order,this.setMap=setMap}next(){return new Text(this.order.next(),this.setMap)}apply(operation){let operations=this.setMap.get(this.order);return operations||(operations=[]),operations.push(operation),this.setMap=this.setMap.set(this.order,operations),{operations:operations,order:this.order}}mergeOperations(o){return new Text(functions_1.merge(this.order,o.order),this.setMap.set(o.order,o.operations))}merge(b){return new Text(functions_1.merge(this.order,b.order),functions_1.merge(this.setMap,b.setMap))}equal(b){return functions_1.equal(this.order,b.order)}reduce(fn,accumulator){return this.setMap.reduce((accumulator,operations,order)=>fn(accumulator,{operations:operations,order:order}),accumulator)}}exports.Text=Text},{"../functions":1}],19:[function(require,module,exports){"use strict";function toArray(text){return text.reduce((accumulator,item)=>item.operations.reduce(operationToArray,accumulator),[])}function ensureArrayLength(array,len){return array.lengthoo.operations.reduce(selectionUpdate,s),fallback)},exports.getSelections=function(text,fallback){return text.reduce((map,oo)=>oo.operations.reduce((map,o)=>map.reduce((map,s,key)=>{if(o instanceof selection_1.Selection&&!map.get(o.origin))return map.set(o.origin,o);const next=selectionUpdate(s,o);return map.set(next.origin,next)},map),map),(new naive_immutable_map_1.NaiveImmutableMap).set(fallback.origin,fallback))},exports.selectionUpdate=selectionUpdate},{"../structures/naive-immutable-map":9,"./delete":13,"./insert":16,"./selection":17}]},{},[3])(3)}); \ No newline at end of file +!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).crdt=f()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n||e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o=max)},exports.axioms=function(assert,a,b,c){assert(equal(merge(a,b),merge(b,a)),"is not commutative"),assert(equal(merge(a,merge(b,c)),merge(merge(a,b),c)),"is not associative"),assert(equal(merge(a,a),a),"is not idempotent")}},{}],2:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Increment{constructor(value){this.value=value,this.value=value}merge(b){return new Increment(Math.max(this.value,b.value))}equal(b){return this.value===b.value}increment(){return new Increment(this.value+1)}}exports.Increment=Increment},{}],3:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const functions=require("./functions"),increment=require("./increment"),order=require("./order"),structures=require("./structures"),text=require("./text");exports.default={functions:functions,increment:increment,order:order,structures:structures,text:text}},{"./functions":1,"./increment":2,"./order":5,"./structures":7,"./text":15}],4:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const naive_array_list_1=require("../structures/naive-array-list"),sorted_set_array_1=require("../structures/sorted-set-array"),vector_clock_1=require("./vector-clock"),emptyVector=new sorted_set_array_1.SortedSetArray(new naive_array_list_1.NaiveArrayList([]));exports.createVectorClock=function(id,version,vector){return new vector_clock_1.VectorClock(new vector_clock_1.Id(id,version||0),vector||emptyVector)}},{"../structures/naive-array-list":8,"../structures/sorted-set-array":12,"./vector-clock":6}],5:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./vector-clock")),__export(require("./factory"))},{"./factory":4,"./vector-clock":6}],6:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Id{constructor(node,version){this.node=node,this.version=version,this.node=node,this.version=version}next(){return new Id(this.node,this.version+1)}compare(b){return this.node.localeCompare(b.node)}toString(){return`Id(${this.node},${this.version})`}}exports.Id=Id;class VectorClock{constructor(id,vector){this.id=id,this.vector=vector,this.id=id;let{result:result,value:value}=vector.add(id);result===vector&&id.version>value.version&&(result=vector.remove(value).result.add(id).result),this.vector=result}toString(){const a=this.vector.reduce((r,i)=>r+i.toString(),"");return`VectorClock(${this.id},${a})`}next(){return new VectorClock(this.id.next(),this.vector.remove(this.id).result.add(this.id.next()).result)}equal(b){return this.vector.size()===b.vector.size()&&this.vector.reduce((eq,item)=>{if(eq){const{result:result,value:value}=b.vector.add(item);if(result===b.vector)return value.version===item.version}return!1},!0)}compare(b){return this.lessThan(b)?-1:b.lessThan(this)?1:this.equal(b)?0:this.id.compare(b.id)}lessThan(b){const{everyLEQ:everyLEQ,anyLT:anyLT}=this.vector.intersect(b.vector).reduce(({everyLEQ:everyLEQ,anyLT:anyLT},item)=>{const rA=this.vector.add(item).value,rB=b.vector.add(item).value;return anyLT=anyLT||rA.version{const{result:result,value:value}=b.vector.add(item);return result===b.vector&&value.version>item.version?vector.add(value).result:vector.add(item).result},this.vector.mempty());return new VectorClock(this.id,vector.union(b.vector))}}exports.VectorClock=VectorClock},{}],7:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./naive-array-list")),__export(require("./naive-immutable-map")),__export(require("./set-axioms")),__export(require("./ordered-map")),__export(require("./sorted-set-array"))},{"./naive-array-list":8,"./naive-immutable-map":9,"./ordered-map":10,"./set-axioms":11,"./sorted-set-array":12}],8:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class NaiveArrayList{constructor(array){this.array=array||[]}insert(at,item){const clone=this.array.slice(0);return clone.splice(at,0,item),new NaiveArrayList(clone)}remove(at){const clone=this.array.slice(0);return clone.splice(at,1),new NaiveArrayList(clone)}get(at){return this.array[at]}size(){return this.array.length}reduce(fn,aggregator){return this.array.reduce(fn,aggregator)}mempty(){return new NaiveArrayList}}exports.NaiveArrayList=NaiveArrayList},{}],9:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class NaiveImmutableMap{constructor(data){this.data=data,this.data=data||{}}set(key,value){const clone=Object.keys(this.data).reduce((clone,k)=>(clone[k]=this.data[k],clone),{});return clone[key]=value,new NaiveImmutableMap(clone)}get(key){return this.data[key]}reduce(fn,aggregator){return Object.keys(this.data).reduce((aggregator,key)=>fn(aggregator,this.data[key],key),aggregator)}}exports.NaiveImmutableMap=NaiveImmutableMap},{}],10:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Indexed{constructor(value,index){this.value=value,this.index=index}compare(b){return this.value.compare(b.value)}}exports.Indexed=Indexed;class OrderedMap{constructor(keys,values){this.keys=keys,this.values=values}set(key,value){const result=this.keys.add(new Indexed(key,this.keys.size()));return new OrderedMap(result.result,this.values.set(result.value.index,value))}get(key){const result=this.keys.add(new Indexed(key,this.keys.size()));return result.result!==this.keys?null:this.values.get(result.value.index)}merge(b){return b.reduce((aggregator,item,key)=>aggregator.set(key,item),this)}reduce(fn,aggregator){return this.keys.reduce((aggregator,key)=>fn(aggregator,this.values.get(key.index),key.value),aggregator)}}exports.OrderedMap=OrderedMap},{}],11:[function(require,module,exports){"use strict";function equal(a,b){return a.equal(b)}function union(a,b){return a.union(b)}function intersect(a,b){return a.intersect(b)}function difference(a,b){return a.difference(b)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.axioms=function(assert,a,b,c){assert(equal(union(union(a,b),c),union(a,union(b,c))),"associative union"),assert(equal(intersect(intersect(a,b),c),intersect(a,intersect(b,c))),"associative intersect"),assert(equal(union(a,intersect(b,c)),union(intersect(a,b),intersect(a,c))),"union distributes over intersection"),assert(equal(intersect(a,union(b,c)),intersect(union(a,b),union(a,c))),"intersection distributes over union"),assert(equal(difference(a,union(b,c)),intersect(difference(a,b),difference(a,c))),"De Morgan's law for union"),assert(equal(difference(a,intersect(b,c)),union(difference(a,b),difference(a,c))),"De Morgan's law for intersect")}},{}],12:[function(require,module,exports){"use strict";function divide(lower,upper,elements,item,onNew,onExists){const step=upper-lower;if(step<1)return onNew(item,elements,lower);const half=Math.trunc(step/2),idx=lower+half,elm=elements.get(idx),cmp=elm.compare(item);return cmp<0?divide(half?lower+half:upper,upper,elements,item,onNew,onExists):cmp>0?divide(lower,half?upper-half:lower,elements,item,onNew,onExists):onExists(elm,elements,idx)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.divide=divide;class Tuple{constructor(result,value){this.result=result,this.value=value}}exports.Tuple=Tuple;class SortedSetArray{constructor(elements){this.elements=elements}mempty(){return new SortedSetArray(this.elements.mempty())}size(){return this.elements.size()}add(value){return divide(0,this.elements.size(),this.elements,value,(item,elements,lower)=>new Tuple(new SortedSetArray(elements.insert(lower,item)),item),(item,elements)=>new Tuple(this,item))}remove(value){return divide(0,this.elements.size(),this.elements,value,(item,elements,lower)=>new Tuple(this,value),(item,elements,index)=>new Tuple(new SortedSetArray(elements.remove(index)),item))}has(value){return divide(0,this.elements.size(),this.elements,value,()=>!1,()=>!0)}union(b){return b.reduce((result,item)=>result.add(item).result,this)}intersect(b){return this.reduce((result,item)=>b.has(item)?result.add(item).result:result,this.mempty())}difference(b){return this.reduce((result,item)=>b.has(item)?result:result.add(item).result,this.mempty())}equal(b){return this.size()===b.size()&&b.reduce((equal,item)=>equal?this.has(item):equal,!0)}reduce(fn,accumulator){return this.elements.reduce(fn,accumulator)}}exports.SortedSetArray=SortedSetArray},{}],13:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Delete{constructor(at,length){this.at=at,this.length=length,this.at=at,this.length=length,this.endsAt=this.at+length}}exports.Delete=Delete},{}],14:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const naive_array_list_1=require("../structures/naive-array-list"),naive_immutable_map_1=require("../structures/naive-immutable-map"),ordered_map_1=require("../structures/ordered-map"),sorted_set_array_1=require("../structures/sorted-set-array"),text_1=require("./text");exports.createFromOrderer=function(order){return new text_1.Text(order,new ordered_map_1.OrderedMap(new sorted_set_array_1.SortedSetArray(new naive_array_list_1.NaiveArrayList([])),new naive_immutable_map_1.NaiveImmutableMap))}},{"../structures/naive-array-list":8,"../structures/naive-immutable-map":9,"../structures/ordered-map":10,"../structures/sorted-set-array":12,"./text":18}],15:[function(require,module,exports){"use strict";function __export(m){for(var p in m)exports.hasOwnProperty(p)||(exports[p]=m[p])}Object.defineProperty(exports,"__esModule",{value:!0}),__export(require("./insert")),__export(require("./delete")),__export(require("./selection")),__export(require("./text")),__export(require("./utils")),__export(require("./factory"))},{"./delete":13,"./factory":14,"./insert":16,"./selection":17,"./text":18,"./utils":19}],16:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Insert{constructor(at,value){this.at=at,this.value=value,this.at=at<0?0:at,this.value=String(value),this.length=this.value.length,this.endsAt=this.at+this.length}}exports.Insert=Insert},{}],17:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class Selection{constructor(origin,at,length){this.origin=origin,this.at=at,this.length=length,this.origin=origin,this.at=at<0?0:at,this.length=length<0?0:length,this.endsAt=this.at+this.length}isCursor(){return 0===this.length}hasSameOrgin(b){return this.origin===b.origin}moveRightBy(step){return new Selection(this.origin,this.at+step,this.length)}expandBy(length){return new Selection(this.origin,this.at,this.length+length)}isInside(position){return this.atposition}}exports.Selection=Selection},{}],18:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const functions_1=require("../functions");class Text{constructor(order,setMap){this.order=order,this.setMap=setMap}next(){return new Text(this.order.next(),this.setMap)}apply(operation){let operations=this.setMap.get(this.order);return operations||(operations=[]),operations.push(operation),this.setMap=this.setMap.set(this.order,operations),{operations:operations,order:this.order}}mergeOperations(o){return new Text(functions_1.merge(this.order,o.order),this.setMap.set(o.order,o.operations))}merge(b){return new Text(functions_1.merge(this.order,b.order),functions_1.merge(this.setMap,b.setMap))}equal(b){return functions_1.equal(this.order,b.order)}reduce(fn,accumulator){return this.setMap.reduce((accumulator,operations,order)=>fn(accumulator,{operations:operations,order:order}),accumulator)}}exports.Text=Text},{"../functions":1}],19:[function(require,module,exports){"use strict";function toArray(text){return text.reduce((accumulator,item)=>item.operations.reduce(operationToArray,accumulator),[])}function ensureArrayLength(array,len){return array.length=selection.endsAt?selection:op.at<=selection.at?selection.moveRightBy(op.length):selection.expandBy(op.length):op instanceof delete_1.Delete?op.endsAtselection.endsAt?selection:op.at>=selection.at?selection.expandBy(-Math.min(selection.endsAt-op.at,op.length)):selection.expandBy(selection.at-op.endsAt).moveRightBy(op.at-selection.at):selection}Object.defineProperty(exports,"__esModule",{value:!0});const delete_1=require("./delete"),insert_1=require("./insert"),selection_1=require("./selection"),naive_immutable_map_1=require("../structures/naive-immutable-map");exports.snapshot=function(text){return text.next()},exports.toArray=toArray,exports.ensureArrayLength=ensureArrayLength,exports.operationToArray=operationToArray,exports.toString=toString,exports.renderString=function(text){return toString(toArray(text))},exports.getSelection=function(text,fallback){return text.reduce((s,oo)=>oo.operations.reduce(selectionUpdate,s),fallback)},exports.getSelections=function(text,fallback){return text.reduce((map,oo)=>oo.operations.reduce((map,o)=>map.reduce((map,s,key)=>{if(o instanceof selection_1.Selection&&!map.get(o.origin))return map.set(o.origin,o);const next=selectionUpdate(s,o);return map.set(next.origin,next)},map),map),(new naive_immutable_map_1.NaiveImmutableMap).set(fallback.origin,fallback))},exports.selectionUpdate=selectionUpdate},{"../structures/naive-immutable-map":9,"./delete":13,"./insert":16,"./selection":17}]},{},[3])(3)}); \ No newline at end of file diff --git a/package.json b/package.json index 768d08e..f48ac41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "js-crdt", - "version": "1.5.3", + "version": "1.5.4", "description": "CRDT Conflict-free Replication Data Type", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/text/utils.ts b/src/text/utils.ts index 3c7191a..5cc9ead 100644 --- a/src/text/utils.ts +++ b/src/text/utils.ts @@ -90,42 +90,65 @@ export function selectionUpdate(selection: Selection, op: Operation): Selection } if (op instanceof Insert) { - if (op.at < selection.at) { + // Don't move cursor when insert is done at the same position + if (selection.isCursor() && op.at === selection.at) { + return selection; + } + + // is after selection: + // sssss + // iii + // iiiiii + if (op.at >= selection.endsAt) { + return selection; + } + + // is before selection or on the same position: + // sssss + // iii + // iiii + // iiiiii + if (op.at <= selection.at) { return selection.moveRightBy(op.length); - } else if (op.at === selection.at) { - return selection.isCursor() - ? selection - : selection.moveRightBy(op.length); - } else if (selection.isInside(op.at)) { - return selection.expandBy(op.length); } - return selection; + // is inside selection: + // sssss + // i + // iiii + return selection.expandBy(op.length); } if (op instanceof Delete) { - if (op.at < selection.at) { - if (selection.isInside(op.endsAt)) { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(selection.at - op.endsAt); - } else if (op.endsAt < selection.at) { - return selection - .moveRightBy(-op.length); - } else { - return selection - .moveRightBy(op.at - selection.at) - .expandBy(-selection.length); - } - } else if (op.at === selection.at) { - return selection - .expandBy(selection.at - op.endsAt); - } else if (selection.isInside(op.at)) { - return selection - .expandBy(op.at - selection.endsAt); + // is before selection: + // ssssss + // ddd + if (op.endsAt < selection.at) { + return selection.moveRightBy(-op.length); } - return selection; + // is after selection: + // ssssss + // ddddd + if (op.at > selection.endsAt) { + return selection; + } + + // starts inside selection block: + // ssssss + // dddddddddd + // ddd + // ddd + // ddddddddd + if (op.at >= selection.at) { + return selection.expandBy(-Math.min(selection.endsAt - op.at, op.length)); + } + + // ends inside selection: + // ssssss + // dddd + // dddddddd + return selection.expandBy(selection.at - op.endsAt).moveRightBy(op.at - selection.at); } return selection; diff --git a/test/text/text.spec.js b/test/text/text.spec.js index 3f45bf5..79013c6 100644 --- a/test/text/text.spec.js +++ b/test/text/text.spec.js @@ -122,173 +122,203 @@ describe('text.Text', () => { let fallback = new Selection("new", 0, 0); describe('selection-cursor', () => { - it('shoud fallback to default selection-cursor when there is no operations', () => { - let result = getSelection(doc, fallback); - let expected = new Selection("new", 0, 0); - assert.deepEqual(result, expected); + describe('insert', () => { + it('shoud fallback to default selection-cursor when there is no operations', () => { + let result = getSelection(doc, fallback); + let expected = new Selection("new", 0, 0); + assert.deepEqual(result, expected); + }); + it('shoud move selection-cursor when insert is done before cursor position', () => { + let next = doc.next() + + next.apply(new Selection("new", 2, 0)); + next.apply(new Insert(1, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 5, 0); + assert.deepEqual(result, expected); + }); + it('shoud leave selection-cursor at current position when insert is done on the same position', () => { + let next = doc.next() + + next.apply(new Selection("new", 1, 0)); + next.apply(new Insert(1, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 1, 0); + assert.deepEqual(result, expected); + }); }); - it('shoud move selection-cursor when insert is done before cursor position', () => { - let next = doc.next() - next.apply(new Selection("new", 2, 0)); - next.apply(new Insert(1, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 5, 0); - assert.deepEqual(result, expected); - }); - it('shoud leave selection-cursor at current position when insert is done on the same position', () => { - let next = doc.next() - - next.apply(new Selection("new", 1, 0)); - next.apply(new Insert(1, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 1, 0); - assert.deepEqual(result, expected); - }); - it('shoud move selection-cursor when delete done before seletion', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 0)); - next.apply(new Delete(0, 2)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 0); - assert.deepEqual(result, expected); - }); - it('shoud leave selection-cursor at current position when delete is done on the same position', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 0)); - next.apply(new Delete(4, 2)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 4, 0); - assert.deepEqual(result, expected); - }); - it('shoud leave selection-cursor at current position when delete is done after cursor position', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 0)); - next.apply(new Delete(5, 2)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 4, 0); - assert.deepEqual(result, expected); - }); - it('shoud move chose mose recent selection-cursor if available', () => { - let next = doc.next() - - next.apply(new Insert(0, 'abc')); - next.apply(new Selection("new", 2, 0)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 0); - assert.deepEqual(result, expected); + describe('delete', () => { + it('shoud move selection-cursor when delete done before seletion', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 0)); + next.apply(new Delete(0, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 0); + assert.deepEqual(result, expected); + }); + it('shoud leave selection-cursor at current position when delete is done on the same position', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 0)); + next.apply(new Delete(4, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 4, 0); + assert.deepEqual(result, expected); + }); + it('shoud leave selection-cursor at current position when delete is done after cursor position', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 0)); + next.apply(new Delete(5, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 4, 0); + assert.deepEqual(result, expected); + }); + it('shoud move chose mose recent selection-cursor if available', () => { + let next = doc.next() + + next.apply(new Insert(0, 'abc')); + next.apply(new Selection("new", 2, 0)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 0); + assert.deepEqual(result, expected); + }); }); }); describe('selection-range', () => { - it('shoud move selection-range when insert done before selection', () => { - let next = doc.next() - - next.apply(new Selection("new", 5, 4)); - next.apply(new Insert(1, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 8, 4); - assert.deepEqual(result, expected); + describe('insert', () => { + it('shoud move selection-range when insert done before selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 5, 4)); + next.apply(new Insert(1, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 8, 4); + assert.deepEqual(result, expected); + }); + it('shoud do not move selection-range when insert done after selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 2, 4)); + next.apply(new Insert(10, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 4); + assert.deepEqual(result, expected); + }); + it('shoud do not move selection-range when insert at the end of selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 2, 4)); + next.apply(new Insert(6, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 4); + assert.deepEqual(result, expected); + }); + it('shoud move selection-range at current position when insert is done on the same position', () => { + let next = doc.next() + + next.apply(new Selection("new", 5, 4)); + next.apply(new Insert(5, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 8, 4); + assert.deepEqual(result, expected); + }); + it('shoud expand selection-range when insert done between selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 2, 4)); + next.apply(new Insert(3, 'abc')); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 7); + assert.deepEqual(result, expected); + }); }); - it('shoud move selection-range at current position when insert is done on the same position', () => { - let next = doc.next() - - next.apply(new Selection("new", 5, 4)); - next.apply(new Insert(5, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 8, 4); - assert.deepEqual(result, expected); - }); - it('shoud move selection-range when insert done between selection', () => { - let next = doc.next() - - next.apply(new Selection("new", 2, 4)); - next.apply(new Insert(3, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 7); - assert.deepEqual(result, expected); - }); - it('shoud do not move selection-range when insert done after selection', () => { - let next = doc.next() - - next.apply(new Selection("new", 2, 4)); - next.apply(new Insert(10, 'abc')); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 4); - assert.deepEqual(result, expected); - }); - it('shoud collaps selection-range when delete done between selection', () => { - let next = doc.next() - - next.apply(new Selection("new", 2, 6)); - next.apply(new Delete(2, 2)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 4); - assert.deepEqual(result, expected); - }); - it('shoud move and reduce selection-range when delete done before selection but ends inside selection', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 8)); - next.apply(new Delete(2, 6)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 4); - assert.deepEqual(result, expected); - }); - it('shoud reduce selection-range when delete starts inside selection but ends outside of it', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 8)); - next.apply(new Delete(6, 12)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 4, 2); - assert.deepEqual(result, expected); - }); - it('shoud reduce selection-range to cursor when whole selected text is deleted', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 2)); - next.apply(new Delete(4, 2)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 4, 0); - assert.deepEqual(result, expected); - }); - it('shoud reduce selection-range to cursor and move it to deletion position when whole selected text is deleted', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 2)); - next.apply(new Delete(2, 8)); - - let result = getSelection(next, fallback); - let expected = new Selection("new", 2, 0); - assert.deepEqual(result, expected); - }); - it('shoud do not move selection-range when delete done after', () => { - let next = doc.next() - - next.apply(new Selection("new", 4, 2)); - next.apply(new Delete(9, 2)); - let result = getSelection(next, fallback); - let expected = new Selection("new", 4, 2); - assert.deepEqual(result, expected); + describe('delete', () => { + it('shoud collaps selection-range when delete done between selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 2, 6)); + next.apply(new Delete(2, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 4); + assert.deepEqual(result, expected); + }); + it('shoud move and reduce selection-range when delete done before selection but ends inside selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 8)); + next.apply(new Delete(2, 6)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 4); + assert.deepEqual(result, expected); + }); + it('shoud reduce selection-range when delete starts inside selection but ends outside of it', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 3)); + next.apply(new Delete(5, 5)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 4, 1); + assert.deepEqual(result, expected); + }); + it('shoud reduce selection-range to cursor when whole selected text is deleted', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 2)); + next.apply(new Delete(4, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 4, 0); + assert.deepEqual(result, expected); + }); + it('shoud reduce selection-range to cursor and move it to deletion position when whole selected text is deleted outside selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 2)); + next.apply(new Delete(2, 8)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 0); + assert.deepEqual(result, expected); + }); + it('shoud do not move selection-range when delete done after', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 2)); + next.apply(new Delete(9, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 4, 2); + assert.deepEqual(result, expected); + }); + it('shoud move selection-range when delete done before selection', () => { + let next = doc.next() + + next.apply(new Selection("new", 4, 2)); + next.apply(new Delete(1, 2)); + + let result = getSelection(next, fallback); + let expected = new Selection("new", 2, 2); + assert.deepEqual(result, expected); + }); }); }); });