118 lines
3.4 KiB
JavaScript
118 lines
3.4 KiB
JavaScript
|
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;
|
||
|
}
|