range.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // @ts-check
  2. class Range {
  3. /**
  4. * @param {number} pos
  5. * @param {number} length
  6. */
  7. constructor(pos, length) {
  8. this.pos = pos
  9. this.length = length
  10. }
  11. get start() {
  12. return this.pos
  13. }
  14. get end() {
  15. return this.pos + this.length
  16. }
  17. /**
  18. * @param {Range} range
  19. * @returns {boolean}
  20. */
  21. startsAfter(range) {
  22. return this.start >= range.end
  23. }
  24. /**
  25. * @param {number} pos
  26. * @returns {boolean}
  27. */
  28. startIsAfter(pos) {
  29. return this.start > pos
  30. }
  31. /**
  32. *
  33. * @returns {boolean}
  34. */
  35. isEmpty() {
  36. return this.length === 0
  37. }
  38. /**
  39. * checks if the range contains a given range
  40. * @param {Range} range
  41. */
  42. contains(range) {
  43. return this.start <= range.start && this.end >= range.end
  44. }
  45. /**
  46. * checks if the range contains a cursor (i.e. is not at the ends of the range)
  47. * @param {number} cursor
  48. */
  49. containsCursor(cursor) {
  50. return this.start <= cursor && this.end >= cursor
  51. }
  52. /**
  53. * @param {Range} range
  54. */
  55. overlaps(range) {
  56. return this.start < range.end && this.end > range.start
  57. }
  58. /**
  59. * checks if the range touches a given range
  60. * @param {Range} range
  61. */
  62. touches(range) {
  63. return this.end === range.start || this.start === range.end
  64. }
  65. /**
  66. * @param {Range} range
  67. * @returns {number} the length of the intersected range
  68. */
  69. subtract(range) {
  70. if (this.contains(range)) {
  71. this.length -= range.length
  72. return range.length
  73. }
  74. if (range.contains(this)) {
  75. const intersectedLength = this.length
  76. this.length = 0
  77. return intersectedLength
  78. }
  79. if (range.overlaps(this)) {
  80. if (range.start < this.start) {
  81. const intersectedLength = range.end - this.start
  82. this.length -= intersectedLength
  83. this.pos = range.pos
  84. return intersectedLength
  85. } else {
  86. const intersectedLength = this.end - range.start
  87. this.length -= intersectedLength
  88. return intersectedLength
  89. }
  90. }
  91. return 0
  92. }
  93. /**
  94. * @param {Range} range
  95. * @returns {boolean}
  96. */
  97. canMerge(range) {
  98. return this.overlaps(range) || this.touches(range)
  99. }
  100. /**
  101. * @param {Range} range
  102. */
  103. merge(range) {
  104. if (!this.canMerge(range)) {
  105. throw new Error('Ranges cannot be merged')
  106. }
  107. const newPos = Math.min(this.pos, range.pos)
  108. const newEnd = Math.max(this.end, range.end)
  109. this.pos = newPos
  110. this.length = newEnd - newPos
  111. }
  112. /**
  113. * Moves the range by a given number
  114. * @param {number} length
  115. */
  116. moveBy(length) {
  117. this.pos += length
  118. }
  119. /**
  120. * Extends the range by a given number
  121. * @param {number} extensionLength
  122. */
  123. extendBy(extensionLength) {
  124. this.length += extensionLength
  125. }
  126. /**
  127. * Splits a range on the cursor and insert a range with the length provided
  128. * @param {number} cursor
  129. * @param {number} length
  130. * @returns {[Range, Range, Range]}
  131. */
  132. insertAt(cursor, length) {
  133. if (!this.containsCursor(cursor)) {
  134. throw new Error('The cursor must be contained in the range')
  135. }
  136. const rangeUpToCursor = new Range(this.pos, cursor - this.pos)
  137. const insertedRange = new Range(cursor, length)
  138. const rangeAfterCursor = new Range(
  139. cursor + length,
  140. this.length - rangeUpToCursor.length
  141. )
  142. return [rangeUpToCursor, insertedRange, rangeAfterCursor]
  143. }
  144. toRaw() {
  145. return {
  146. pos: this.pos,
  147. length: this.length,
  148. }
  149. }
  150. static fromRaw(raw) {
  151. return new Range(raw.pos, raw.length)
  152. }
  153. /**
  154. * Splits a range into two ranges, at a given cursor
  155. * @param {number} cursor
  156. * @returns {[Range, Range]}
  157. */
  158. splitAt(cursor) {
  159. if (!this.containsCursor(cursor)) {
  160. throw new Error('The cursor must be contained in the range')
  161. }
  162. const rangeUpToCursor = new Range(this.pos, cursor - this.pos)
  163. const rangeAfterCursor = new Range(
  164. cursor,
  165. this.length - rangeUpToCursor.length
  166. )
  167. return [rangeUpToCursor, rangeAfterCursor]
  168. }
  169. }
  170. module.exports = Range