summaryrefslogtreecommitdiff
path: root/lib/ot/selection.js
diff options
context:
space:
mode:
authorWu Cheng-Han2015-07-11 12:43:08 +0800
committerWu Cheng-Han2015-07-11 12:43:08 +0800
commit556338a9c6964d110c1351a402b425c71c2571fa (patch)
treed5b6d2071e554e65c7bfaa4f2c84ddb034598e01 /lib/ot/selection.js
parent4702b83adc35f384e214a2a6e9199d08e4494093 (diff)
Added support of operational transformation
Diffstat (limited to 'lib/ot/selection.js')
-rw-r--r--lib/ot/selection.js117
1 files changed, 117 insertions, 0 deletions
diff --git a/lib/ot/selection.js b/lib/ot/selection.js
new file mode 100644
index 00000000..72bf8bd6
--- /dev/null
+++ b/lib/ot/selection.js
@@ -0,0 +1,117 @@
+if (typeof ot === 'undefined') {
+ // Export for browsers
+ var ot = {};
+}
+
+ot.Selection = (function (global) {
+ 'use strict';
+
+ var TextOperation = global.ot ? global.ot.TextOperation : require('./text-operation');
+
+ // Range has `anchor` and `head` properties, which are zero-based indices into
+ // the document. The `anchor` is the side of the selection that stays fixed,
+ // `head` is the side of the selection where the cursor is. When both are
+ // equal, the range represents a cursor.
+ function Range (anchor, head) {
+ this.anchor = anchor;
+ this.head = head;
+ }
+
+ Range.fromJSON = function (obj) {
+ return new Range(obj.anchor, obj.head);
+ };
+
+ Range.prototype.equals = function (other) {
+ return this.anchor === other.anchor && this.head === other.head;
+ };
+
+ Range.prototype.isEmpty = function () {
+ return this.anchor === this.head;
+ };
+
+ Range.prototype.transform = function (other) {
+ function transformIndex (index) {
+ var newIndex = index;
+ var ops = other.ops;
+ for (var i = 0, l = other.ops.length; i < l; i++) {
+ if (TextOperation.isRetain(ops[i])) {
+ index -= ops[i];
+ } else if (TextOperation.isInsert(ops[i])) {
+ newIndex += ops[i].length;
+ } else {
+ newIndex -= Math.min(index, -ops[i]);
+ index += ops[i];
+ }
+ if (index < 0) { break; }
+ }
+ return newIndex;
+ }
+
+ var newAnchor = transformIndex(this.anchor);
+ if (this.anchor === this.head) {
+ return new Range(newAnchor, newAnchor);
+ }
+ return new Range(newAnchor, transformIndex(this.head));
+ };
+
+ // A selection is basically an array of ranges. Every range represents a real
+ // selection or a cursor in the document (when the start position equals the
+ // end position of the range). The array must not be empty.
+ function Selection (ranges) {
+ this.ranges = ranges || [];
+ }
+
+ Selection.Range = Range;
+
+ // Convenience method for creating selections only containing a single cursor
+ // and no real selection range.
+ Selection.createCursor = function (position) {
+ return new Selection([new Range(position, position)]);
+ };
+
+ Selection.fromJSON = function (obj) {
+ var objRanges = obj.ranges || obj;
+ for (var i = 0, ranges = []; i < objRanges.length; i++) {
+ ranges[i] = Range.fromJSON(objRanges[i]);
+ }
+ return new Selection(ranges);
+ };
+
+ Selection.prototype.equals = function (other) {
+ if (this.position !== other.position) { return false; }
+ if (this.ranges.length !== other.ranges.length) { return false; }
+ // FIXME: Sort ranges before comparing them?
+ for (var i = 0; i < this.ranges.length; i++) {
+ if (!this.ranges[i].equals(other.ranges[i])) { return false; }
+ }
+ return true;
+ };
+
+ Selection.prototype.somethingSelected = function () {
+ for (var i = 0; i < this.ranges.length; i++) {
+ if (!this.ranges[i].isEmpty()) { return true; }
+ }
+ return false;
+ };
+
+ // Return the more current selection information.
+ Selection.prototype.compose = function (other) {
+ return other;
+ };
+
+ // Update the selection with respect to an operation.
+ Selection.prototype.transform = function (other) {
+ for (var i = 0, newRanges = []; i < this.ranges.length; i++) {
+ newRanges[i] = this.ranges[i].transform(other);
+ }
+ return new Selection(newRanges);
+ };
+
+ return Selection;
+
+}(this));
+
+// Export for CommonJS
+if (typeof module === 'object') {
+ module.exports = ot.Selection;
+}