chunk.js
5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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
}
}
}