comment.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // @ts-check
  2. const Range = require('./range')
  3. /**
  4. * @typedef {import("../types").CommentRawData} CommentRawData
  5. */
  6. class Comment {
  7. /**
  8. * @type {Range[]}
  9. */
  10. ranges = []
  11. /**
  12. * @type {boolean}
  13. */
  14. resolved = false
  15. /**
  16. * @param {Range[]} ranges
  17. * @param {boolean} [resolved]
  18. */
  19. constructor(ranges, resolved = false) {
  20. this.resolved = resolved
  21. for (const range of ranges) {
  22. this.addRange(range)
  23. }
  24. }
  25. /**
  26. *
  27. * @param {Range} range
  28. */
  29. addRange(range) {
  30. this.ranges.push(range)
  31. this.ranges.sort((a, b) => a.start - b.start)
  32. this.mergeRanges()
  33. }
  34. /**
  35. *
  36. * @param {number} cursor
  37. * @param {number} length
  38. * @param {boolean} [extendComment]
  39. */
  40. applyInsert(cursor, length, extendComment = false) {
  41. let existingRangeExtended = false
  42. const newRanges = []
  43. for (const commentRange of this.ranges) {
  44. if (cursor === commentRange.end) {
  45. // insert right after the comment
  46. if (extendComment) {
  47. newRanges.push(commentRange.extendBy(length))
  48. existingRangeExtended = true
  49. } else {
  50. newRanges.push(commentRange)
  51. }
  52. } else if (cursor === commentRange.start) {
  53. // insert at the start of the comment
  54. if (extendComment) {
  55. newRanges.push(commentRange.extendBy(length))
  56. existingRangeExtended = true
  57. } else {
  58. newRanges.push(commentRange.moveBy(length))
  59. }
  60. } else if (commentRange.startIsAfter(cursor)) {
  61. // insert before the comment
  62. newRanges.push(commentRange.moveBy(length))
  63. } else if (commentRange.containsCursor(cursor)) {
  64. // insert is inside the comment
  65. if (extendComment) {
  66. newRanges.push(commentRange.extendBy(length))
  67. existingRangeExtended = true
  68. } else {
  69. const [rangeUpToCursor, , rangeAfterCursor] = commentRange.insertAt(
  70. cursor,
  71. length
  72. )
  73. // use current commentRange for the part before the cursor
  74. newRanges.push(new Range(commentRange.pos, rangeUpToCursor.length))
  75. // add the part after the cursor as a new range
  76. newRanges.push(rangeAfterCursor)
  77. }
  78. } else {
  79. // insert is after the comment
  80. newRanges.push(commentRange)
  81. }
  82. }
  83. this.ranges = newRanges
  84. // if the insert is not inside any range, add a new range
  85. if (extendComment && !existingRangeExtended) {
  86. this.addRange(new Range(cursor, length))
  87. }
  88. }
  89. /**
  90. *
  91. * @param {Range} deletedRange
  92. */
  93. applyDelete(deletedRange) {
  94. const newRanges = []
  95. for (const commentRange of this.ranges) {
  96. if (commentRange.overlaps(deletedRange)) {
  97. newRanges.push(commentRange.subtract(deletedRange))
  98. } else if (commentRange.startsAfter(deletedRange)) {
  99. newRanges.push(commentRange.moveBy(-deletedRange.length))
  100. } else {
  101. newRanges.push(commentRange)
  102. }
  103. }
  104. this.ranges = newRanges
  105. this.mergeRanges()
  106. }
  107. isEmpty() {
  108. return this.ranges.length === 0
  109. }
  110. toRaw() {
  111. return {
  112. resolved: this.resolved,
  113. ranges: this.ranges.map(range => range.toRaw()),
  114. }
  115. }
  116. mergeRanges() {
  117. /** @type {Range[]} */
  118. const mergedRanges = []
  119. for (const range of this.ranges) {
  120. if (range.isEmpty()) {
  121. continue
  122. }
  123. const lastMerged = mergedRanges[mergedRanges.length - 1]
  124. if (lastMerged?.canMerge(range)) {
  125. mergedRanges[mergedRanges.length - 1] = lastMerged.merge(range)
  126. } else {
  127. mergedRanges.push(range)
  128. }
  129. }
  130. this.ranges = mergedRanges
  131. }
  132. /**
  133. * @param {CommentRawData} rawComment
  134. * @returns {Comment}
  135. */
  136. static fromRaw(rawComment) {
  137. return new Comment(
  138. rawComment.ranges.map(range => Range.fromRaw(range)),
  139. rawComment.resolved
  140. )
  141. }
  142. }
  143. module.exports = Comment