annotate .cms/lib/codemirror/src/model/chunk.js @ 1:1d486627aa1e draft default tip

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