dc7609f23110f7f944b0691dfa512f3d17d4e5a9f00a75f5ac3a7b032a18471a8c828c8f5e759721cdb3fc4db14798a85121fdaf6251526eefa9a8b3a4bd22 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { Range } from '../../../common/range.js';
  6. /**
  7. * Returns the intersection between a ranged group and a range.
  8. * Returns `[]` if the intersection is empty.
  9. */
  10. export function groupIntersect(range, groups) {
  11. const result = [];
  12. for (const r of groups) {
  13. if (range.start >= r.range.end) {
  14. continue;
  15. }
  16. if (range.end < r.range.start) {
  17. break;
  18. }
  19. const intersection = Range.intersect(range, r.range);
  20. if (Range.isEmpty(intersection)) {
  21. continue;
  22. }
  23. result.push({
  24. range: intersection,
  25. size: r.size
  26. });
  27. }
  28. return result;
  29. }
  30. /**
  31. * Shifts a range by that `much`.
  32. */
  33. export function shift({ start, end }, much) {
  34. return { start: start + much, end: end + much };
  35. }
  36. /**
  37. * Consolidates a collection of ranged groups.
  38. *
  39. * Consolidation is the process of merging consecutive ranged groups
  40. * that share the same `size`.
  41. */
  42. export function consolidate(groups) {
  43. const result = [];
  44. let previousGroup = null;
  45. for (const group of groups) {
  46. const start = group.range.start;
  47. const end = group.range.end;
  48. const size = group.size;
  49. if (previousGroup && size === previousGroup.size) {
  50. previousGroup.range.end = end;
  51. continue;
  52. }
  53. previousGroup = { range: { start, end }, size };
  54. result.push(previousGroup);
  55. }
  56. return result;
  57. }
  58. /**
  59. * Concatenates several collections of ranged groups into a single
  60. * collection.
  61. */
  62. function concat(...groups) {
  63. return consolidate(groups.reduce((r, g) => r.concat(g), []));
  64. }
  65. export class RangeMap {
  66. constructor() {
  67. this.groups = [];
  68. this._size = 0;
  69. }
  70. splice(index, deleteCount, items = []) {
  71. const diff = items.length - deleteCount;
  72. const before = groupIntersect({ start: 0, end: index }, this.groups);
  73. const after = groupIntersect({ start: index + deleteCount, end: Number.POSITIVE_INFINITY }, this.groups)
  74. .map(g => ({ range: shift(g.range, diff), size: g.size }));
  75. const middle = items.map((item, i) => ({
  76. range: { start: index + i, end: index + i + 1 },
  77. size: item.size
  78. }));
  79. this.groups = concat(before, middle, after);
  80. this._size = this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
  81. }
  82. /**
  83. * Returns the number of items in the range map.
  84. */
  85. get count() {
  86. const len = this.groups.length;
  87. if (!len) {
  88. return 0;
  89. }
  90. return this.groups[len - 1].range.end;
  91. }
  92. /**
  93. * Returns the sum of the sizes of all items in the range map.
  94. */
  95. get size() {
  96. return this._size;
  97. }
  98. /**
  99. * Returns the index of the item at the given position.
  100. */
  101. indexAt(position) {
  102. if (position < 0) {
  103. return -1;
  104. }
  105. let index = 0;
  106. let size = 0;
  107. for (const group of this.groups) {
  108. const count = group.range.end - group.range.start;
  109. const newSize = size + (count * group.size);
  110. if (position < newSize) {
  111. return index + Math.floor((position - size) / group.size);
  112. }
  113. index += count;
  114. size = newSize;
  115. }
  116. return index;
  117. }
  118. /**
  119. * Returns the index of the item right after the item at the
  120. * index of the given position.
  121. */
  122. indexAfter(position) {
  123. return Math.min(this.indexAt(position) + 1, this.count);
  124. }
  125. /**
  126. * Returns the start position of the item at the given index.
  127. */
  128. positionAt(index) {
  129. if (index < 0) {
  130. return -1;
  131. }
  132. let position = 0;
  133. let count = 0;
  134. for (const group of this.groups) {
  135. const groupCount = group.range.end - group.range.start;
  136. const newCount = count + groupCount;
  137. if (index < newCount) {
  138. return position + ((index - count) * group.size);
  139. }
  140. position += groupCount * group.size;
  141. count = newCount;
  142. }
  143. return -1;
  144. }
  145. }