6070133fdd2d7b82f4a050ba399b7fccc32598f8f6a07f86d14ad8cd4a59b78a6085e626b156076b5044fc8b351bdfc7858f0b2a3a7e94c066f6a64dca2e14 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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 { lengthAdd, lengthZero, lengthLessThan } from './length.js';
  6. /**
  7. * Allows to efficiently find a longest child at a given offset in a fixed node.
  8. * The requested offsets must increase monotonously.
  9. */
  10. export class NodeReader {
  11. constructor(node) {
  12. this.lastOffset = lengthZero;
  13. this.nextNodes = [node];
  14. this.offsets = [lengthZero];
  15. this.idxs = [];
  16. }
  17. /**
  18. * Returns the longest node at `offset` that satisfies the predicate.
  19. * @param offset must be greater than or equal to the last offset this method has been called with!
  20. */
  21. readLongestNodeAt(offset, predicate) {
  22. if (lengthLessThan(offset, this.lastOffset)) {
  23. throw new Error('Invalid offset');
  24. }
  25. this.lastOffset = offset;
  26. // Find the longest node of all those that are closest to the current offset.
  27. while (true) {
  28. const curNode = lastOrUndefined(this.nextNodes);
  29. if (!curNode) {
  30. return undefined;
  31. }
  32. const curNodeOffset = lastOrUndefined(this.offsets);
  33. if (lengthLessThan(offset, curNodeOffset)) {
  34. // The next best node is not here yet.
  35. // The reader must advance before a cached node is hit.
  36. return undefined;
  37. }
  38. if (lengthLessThan(curNodeOffset, offset)) {
  39. // The reader is ahead of the current node.
  40. if (lengthAdd(curNodeOffset, curNode.length) <= offset) {
  41. // The reader is after the end of the current node.
  42. this.nextNodeAfterCurrent();
  43. }
  44. else {
  45. // The reader is somewhere in the current node.
  46. const nextChildIdx = getNextChildIdx(curNode);
  47. if (nextChildIdx !== -1) {
  48. // Go to the first child and repeat.
  49. this.nextNodes.push(curNode.getChild(nextChildIdx));
  50. this.offsets.push(curNodeOffset);
  51. this.idxs.push(nextChildIdx);
  52. }
  53. else {
  54. // We don't have children
  55. this.nextNodeAfterCurrent();
  56. }
  57. }
  58. }
  59. else {
  60. // readerOffsetBeforeChange === curNodeOffset
  61. if (predicate(curNode)) {
  62. this.nextNodeAfterCurrent();
  63. return curNode;
  64. }
  65. else {
  66. const nextChildIdx = getNextChildIdx(curNode);
  67. // look for shorter node
  68. if (nextChildIdx === -1) {
  69. // There is no shorter node.
  70. this.nextNodeAfterCurrent();
  71. return undefined;
  72. }
  73. else {
  74. // Descend into first child & repeat.
  75. this.nextNodes.push(curNode.getChild(nextChildIdx));
  76. this.offsets.push(curNodeOffset);
  77. this.idxs.push(nextChildIdx);
  78. }
  79. }
  80. }
  81. }
  82. }
  83. // Navigates to the longest node that continues after the current node.
  84. nextNodeAfterCurrent() {
  85. while (true) {
  86. const currentOffset = lastOrUndefined(this.offsets);
  87. const currentNode = lastOrUndefined(this.nextNodes);
  88. this.nextNodes.pop();
  89. this.offsets.pop();
  90. if (this.idxs.length === 0) {
  91. // We just popped the root node, there is no next node.
  92. break;
  93. }
  94. // Parent is not undefined, because idxs is not empty
  95. const parent = lastOrUndefined(this.nextNodes);
  96. const nextChildIdx = getNextChildIdx(parent, this.idxs[this.idxs.length - 1]);
  97. if (nextChildIdx !== -1) {
  98. this.nextNodes.push(parent.getChild(nextChildIdx));
  99. this.offsets.push(lengthAdd(currentOffset, currentNode.length));
  100. this.idxs[this.idxs.length - 1] = nextChildIdx;
  101. break;
  102. }
  103. else {
  104. this.idxs.pop();
  105. }
  106. // We fully consumed the parent.
  107. // Current node is now parent, so call nextNodeAfterCurrent again
  108. }
  109. }
  110. }
  111. function getNextChildIdx(node, curIdx = -1) {
  112. while (true) {
  113. curIdx++;
  114. if (curIdx >= node.childrenLength) {
  115. return -1;
  116. }
  117. if (node.getChild(curIdx)) {
  118. return curIdx;
  119. }
  120. }
  121. }
  122. function lastOrUndefined(arr) {
  123. return arr.length > 0 ? arr[arr.length - 1] : undefined;
  124. }