import { cleanUpLine } from "../line/line_data.js" import { indexOf } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" // The document is represented as a BTree consisting of leaves, with // chunk of lines in them, and branches, with up to ten leaves or // other branch nodes below them. The top node is always a branch // node, and is the document object itself (meaning it has // additional methods and properties). // // All nodes have parent links. The tree is used both to go from // line numbers to line objects, and to go from objects to numbers. // It also indexes by height, and is used to convert between height // and line object, and to find the total height of the document. // // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html export function LeafChunk(lines) { this.lines = lines this.parent = null let height = 0 for (let i = 0; i < lines.length; ++i) { lines[i].parent = this height += lines[i].height } this.height = height } LeafChunk.prototype = { chunkSize() { return this.lines.length }, // Remove the n lines at offset 'at'. removeInner(at, n) { for (let i = at, e = at + n; i < e; ++i) { let line = this.lines[i] this.height -= line.height cleanUpLine(line) signalLater(line, "delete") } this.lines.splice(at, n) }, // Helper used to collapse a small branch into a single leaf. collapse(lines) { lines.push.apply(lines, this.lines) }, // Insert the given array of lines at offset 'at', count them as // having the given height. insertInner(at, lines, height) { this.height += height this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) for (let i = 0; i < lines.length; ++i) lines[i].parent = this }, // Used to iterate over a part of the tree. iterN(at, n, op) { for (let e = at + n; at < e; ++at) if (op(this.lines[at])) return true } } export function BranchChunk(children) { this.children = children let size = 0, height = 0 for (let i = 0; i < children.length; ++i) { let ch = children[i] size += ch.chunkSize(); height += ch.height ch.parent = this } this.size = size this.height = height this.parent = null } BranchChunk.prototype = { chunkSize() { return this.size }, removeInner(at, n) { this.size -= n for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at < sz) { let rm = Math.min(n, sz - at), oldHeight = child.height child.removeInner(at, rm) this.height -= oldHeight - child.height if (sz == rm) { this.children.splice(i--, 1); child.parent = null } if ((n -= rm) == 0) break at = 0 } else at -= sz } // If the result is smaller than 25 lines, ensure that it is a // single leaf node. if (this.size - n < 25 && (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { let lines = [] this.collapse(lines) this.children = [new LeafChunk(lines)] this.children[0].parent = this } }, collapse(lines) { for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines) }, insertInner(at, lines, height) { this.size += lines.length this.height += height for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at <= sz) { child.insertInner(at, lines, height) if (child.lines && child.lines.length > 50) { // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. let remaining = child.lines.length % 25 + 25 for (let pos = remaining; pos < child.lines.length;) { let leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) child.height -= leaf.height this.children.splice(++i, 0, leaf) leaf.parent = this } child.lines = child.lines.slice(0, remaining) this.maybeSpill() } break } at -= sz } }, // When a node has grown, check whether it should be split. maybeSpill() { if (this.children.length <= 10) return let me = this do { let spilled = me.children.splice(me.children.length - 5, 5) let sibling = new BranchChunk(spilled) if (!me.parent) { // Become the parent node let copy = new BranchChunk(me.children) copy.parent = me me.children = [copy, sibling] me = copy } else { me.size -= sibling.size me.height -= sibling.height let myIndex = indexOf(me.parent.children, me) me.parent.children.splice(myIndex + 1, 0, sibling) } sibling.parent = me.parent } while (me.children.length > 10) me.parent.maybeSpill() }, iterN(at, n, op) { for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at < sz) { let used = Math.min(n, sz - at) if (child.iterN(at, used, op)) return true if ((n -= used) == 0) break at = 0 } else at -= sz } } }