| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- // @ts-check
- class Range {
- /**
- * @param {number} pos
- * @param {number} length
- */
- constructor(pos, length) {
- /** @readonly */
- this.pos = pos
- /** @readonly */
- this.length = length
- }
- get start() {
- return this.pos
- }
- get end() {
- return this.pos + this.length
- }
- /**
- * @param {Range} range
- * @returns {boolean}
- */
- startsAfter(range) {
- return this.start >= range.end
- }
- /**
- * @param {number} pos
- * @returns {boolean}
- */
- startIsAfter(pos) {
- return this.start > pos
- }
- /**
- *
- * @returns {boolean}
- */
- isEmpty() {
- return this.length === 0
- }
- /**
- * checks if the range contains a given range
- * @param {Range} range
- */
- contains(range) {
- return this.start <= range.start && this.end >= range.end
- }
- /**
- * checks if the range contains a cursor (i.e. is not at the ends of the range)
- * @param {number} cursor
- */
- containsCursor(cursor) {
- return this.start <= cursor && this.end >= cursor
- }
- /**
- * @param {Range} range
- */
- overlaps(range) {
- return this.start < range.end && this.end > range.start
- }
- /**
- * checks if the range touches a given range
- * @param {Range} range
- */
- touches(range) {
- return this.end === range.start || this.start === range.end
- }
- /**
- * @param {Range} range
- * @returns {Range}
- */
- subtract(range) {
- if (this.contains(range)) {
- return this.shrinkBy(range.length)
- }
- if (range.contains(this)) {
- return new Range(this.pos, 0)
- }
- if (range.overlaps(this)) {
- if (range.start < this.start) {
- const intersectedLength = range.end - this.start
- return new Range(range.pos, this.length - intersectedLength)
- } else {
- const intersectedLength = this.end - range.start
- return new Range(this.pos, this.length - intersectedLength)
- }
- }
- return new Range(this.pos, this.length)
- }
- /**
- * @param {Range} range
- * @returns {boolean}
- */
- canMerge(range) {
- return this.overlaps(range) || this.touches(range)
- }
- /**
- * @param {Range} range
- */
- merge(range) {
- if (!this.canMerge(range)) {
- throw new Error('Ranges cannot be merged')
- }
- const newPos = Math.min(this.pos, range.pos)
- const newEnd = Math.max(this.end, range.end)
- return new Range(newPos, newEnd - newPos)
- }
- /**
- * Moves the range by a given number
- * @param {number} length
- */
- moveBy(length) {
- return new Range(this.pos + length, this.length)
- }
- /**
- * Extends the range by a given number
- * @param {number} extensionLength
- */
- extendBy(extensionLength) {
- return new Range(this.pos, this.length + extensionLength)
- }
- /**
- * Shrinks the range by a given number
- * @param {number} shrinkLength
- */
- shrinkBy(shrinkLength) {
- const newLength = this.length - shrinkLength
- if (newLength < 0) {
- throw new Error('Cannot shrink range by more than its length')
- }
- return new Range(this.pos, newLength)
- }
- /**
- * Splits a range on the cursor and insert a range with the length provided
- * @param {number} cursor
- * @param {number} length
- * @returns {[Range, Range, Range]}
- */
- insertAt(cursor, length) {
- if (!this.containsCursor(cursor)) {
- throw new Error('The cursor must be contained in the range')
- }
- const rangeUpToCursor = new Range(this.pos, cursor - this.pos)
- const insertedRange = new Range(cursor, length)
- const rangeAfterCursor = new Range(
- cursor + length,
- this.length - rangeUpToCursor.length
- )
- return [rangeUpToCursor, insertedRange, rangeAfterCursor]
- }
- toRaw() {
- return {
- pos: this.pos,
- length: this.length,
- }
- }
- static fromRaw(raw) {
- return new Range(raw.pos, raw.length)
- }
- /**
- * Splits a range into two ranges, at a given cursor
- * @param {number} cursor
- * @returns {[Range, Range]}
- */
- splitAt(cursor) {
- if (!this.containsCursor(cursor)) {
- throw new Error('The cursor must be contained in the range')
- }
- const rangeUpToCursor = new Range(this.pos, cursor - this.pos)
- const rangeAfterCursor = new Range(
- cursor,
- this.length - rangeUpToCursor.length
- )
- return [rangeUpToCursor, rangeAfterCursor]
- }
- }
- module.exports = Range
|