cd1c5f3cbddac98dcf2f05d8c6544a1aa0c4301cc3e349ff5e632f22a05e09bdd87fecbe6b653335050043e91f3fe08243b1d08080c940b6822f874da8a885 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { cleanUpLine } from "../line/line_data.js"
  2. import { indexOf } from "../util/misc.js"
  3. import { signalLater } from "../util/operation_group.js"
  4. // The document is represented as a BTree consisting of leaves, with
  5. // chunk of lines in them, and branches, with up to ten leaves or
  6. // other branch nodes below them. The top node is always a branch
  7. // node, and is the document object itself (meaning it has
  8. // additional methods and properties).
  9. //
  10. // All nodes have parent links. The tree is used both to go from
  11. // line numbers to line objects, and to go from objects to numbers.
  12. // It also indexes by height, and is used to convert between height
  13. // and line object, and to find the total height of the document.
  14. //
  15. // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
  16. export function LeafChunk(lines) {
  17. this.lines = lines
  18. this.parent = null
  19. let height = 0
  20. for (let i = 0; i < lines.length; ++i) {
  21. lines[i].parent = this
  22. height += lines[i].height
  23. }
  24. this.height = height
  25. }
  26. LeafChunk.prototype = {
  27. chunkSize() { return this.lines.length },
  28. // Remove the n lines at offset 'at'.
  29. removeInner(at, n) {
  30. for (let i = at, e = at + n; i < e; ++i) {
  31. let line = this.lines[i]
  32. this.height -= line.height
  33. cleanUpLine(line)
  34. signalLater(line, "delete")
  35. }
  36. this.lines.splice(at, n)
  37. },
  38. // Helper used to collapse a small branch into a single leaf.
  39. collapse(lines) {
  40. lines.push.apply(lines, this.lines)
  41. },
  42. // Insert the given array of lines at offset 'at', count them as
  43. // having the given height.
  44. insertInner(at, lines, height) {
  45. this.height += height
  46. this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at))
  47. for (let i = 0; i < lines.length; ++i) lines[i].parent = this
  48. },
  49. // Used to iterate over a part of the tree.
  50. iterN(at, n, op) {
  51. for (let e = at + n; at < e; ++at)
  52. if (op(this.lines[at])) return true
  53. }
  54. }
  55. export function BranchChunk(children) {
  56. this.children = children
  57. let size = 0, height = 0
  58. for (let i = 0; i < children.length; ++i) {
  59. let ch = children[i]
  60. size += ch.chunkSize(); height += ch.height
  61. ch.parent = this
  62. }
  63. this.size = size
  64. this.height = height
  65. this.parent = null
  66. }
  67. BranchChunk.prototype = {
  68. chunkSize() { return this.size },
  69. removeInner(at, n) {
  70. this.size -= n
  71. for (let i = 0; i < this.children.length; ++i) {
  72. let child = this.children[i], sz = child.chunkSize()
  73. if (at < sz) {
  74. let rm = Math.min(n, sz - at), oldHeight = child.height
  75. child.removeInner(at, rm)
  76. this.height -= oldHeight - child.height
  77. if (sz == rm) { this.children.splice(i--, 1); child.parent = null }
  78. if ((n -= rm) == 0) break
  79. at = 0
  80. } else at -= sz
  81. }
  82. // If the result is smaller than 25 lines, ensure that it is a
  83. // single leaf node.
  84. if (this.size - n < 25 &&
  85. (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
  86. let lines = []
  87. this.collapse(lines)
  88. this.children = [new LeafChunk(lines)]
  89. this.children[0].parent = this
  90. }
  91. },
  92. collapse(lines) {
  93. for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines)
  94. },
  95. insertInner(at, lines, height) {
  96. this.size += lines.length
  97. this.height += height
  98. for (let i = 0; i < this.children.length; ++i) {
  99. let child = this.children[i], sz = child.chunkSize()
  100. if (at <= sz) {
  101. child.insertInner(at, lines, height)
  102. if (child.lines && child.lines.length > 50) {
  103. // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
  104. // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
  105. let remaining = child.lines.length % 25 + 25
  106. for (let pos = remaining; pos < child.lines.length;) {
  107. let leaf = new LeafChunk(child.lines.slice(pos, pos += 25))
  108. child.height -= leaf.height
  109. this.children.splice(++i, 0, leaf)
  110. leaf.parent = this
  111. }
  112. child.lines = child.lines.slice(0, remaining)
  113. this.maybeSpill()
  114. }
  115. break
  116. }
  117. at -= sz
  118. }
  119. },
  120. // When a node has grown, check whether it should be split.
  121. maybeSpill() {
  122. if (this.children.length <= 10) return
  123. let me = this
  124. do {
  125. let spilled = me.children.splice(me.children.length - 5, 5)
  126. let sibling = new BranchChunk(spilled)
  127. if (!me.parent) { // Become the parent node
  128. let copy = new BranchChunk(me.children)
  129. copy.parent = me
  130. me.children = [copy, sibling]
  131. me = copy
  132. } else {
  133. me.size -= sibling.size
  134. me.height -= sibling.height
  135. let myIndex = indexOf(me.parent.children, me)
  136. me.parent.children.splice(myIndex + 1, 0, sibling)
  137. }
  138. sibling.parent = me.parent
  139. } while (me.children.length > 10)
  140. me.parent.maybeSpill()
  141. },
  142. iterN(at, n, op) {
  143. for (let i = 0; i < this.children.length; ++i) {
  144. let child = this.children[i], sz = child.chunkSize()
  145. if (at < sz) {
  146. let used = Math.min(n, sz - at)
  147. if (child.iterN(at, used, op)) return true
  148. if ((n -= used) == 0) break
  149. at = 0
  150. } else at -= sz
  151. }
  152. }
  153. }