forked from codemirror/view
/
buildview.ts
129 lines (115 loc) · 4.41 KB
/
buildview.ts
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
import {SpanIterator, RangeSet} from "@codemirror/rangeset"
import {DecorationSet, Decoration, PointDecoration, LineDecoration, MarkDecoration, BlockType, WidgetType} from "./decoration"
import {BlockView, LineView, BlockWidgetView} from "./blockview"
import {InlineView, WidgetView, TextView, MarkView} from "./inlineview"
import {Text, TextIterator} from "@codemirror/text"
const enum T { Chunk = 512 }
export class ContentBuilder implements SpanIterator<Decoration> {
content: BlockView[] = []
curLine: LineView | null = null
breakAtStart = 0
openStart = -1
openEnd = -1
cursor: TextIterator
text: string = ""
skip: number
textOff: number = 0
constructor(private doc: Text, public pos: number, public end: number) {
this.cursor = doc.iter()
this.skip = pos
}
posCovered() {
if (this.content.length == 0)
return !this.breakAtStart && this.doc.lineAt(this.pos).from != this.pos
let last = this.content[this.content.length - 1]
return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore)
}
getLine() {
if (!this.curLine)
this.content.push(this.curLine = new LineView)
return this.curLine
}
addWidget(view: BlockWidgetView) {
this.curLine = null
this.content.push(view)
}
finish() {
if (!this.posCovered()) this.getLine()
}
wrapMarks(view: InlineView, active: readonly MarkDecoration[]) {
for (let i = active.length - 1; i >= 0; i--)
view = new MarkView(active[i], [view], view.length)
return view
}
buildText(length: number, active: readonly MarkDecoration[], openStart: number) {
while (length > 0) {
if (this.textOff == this.text.length) {
let {value, lineBreak, done} = this.cursor.next(this.skip)
this.skip = 0
if (done) throw new Error("Ran out of text content when drawing inline views")
if (lineBreak) {
if (!this.posCovered()) this.getLine()
if (this.content.length) this.content[this.content.length - 1].breakAfter = 1
else this.breakAtStart = 1
this.curLine = null
length--
continue
} else {
this.text = value
this.textOff = 0
}
}
let take = Math.min(this.text.length - this.textOff, length, T.Chunk)
this.getLine().append(this.wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart)
this.textOff += take
length -= take
openStart = 0
}
}
span(from: number, to: number, active: MarkDecoration[], openStart: number) {
this.buildText(to - from, active, openStart)
this.pos = to
if (this.openStart < 0) this.openStart = openStart
}
point(from: number, to: number, deco: Decoration, active: MarkDecoration[], openStart: number) {
let len = to - from
if (deco instanceof PointDecoration) {
if (deco.block) {
let {type} = deco
if (type == BlockType.WidgetAfter && !this.posCovered()) this.getLine()
this.addWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type))
} else {
let widget = this.wrapMarks(WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide), active)
this.getLine().append(widget, openStart)
}
} else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
this.getLine().addLineDeco(deco as LineDecoration)
}
if (len) {
// Advance the iterator past the replaced content
if (this.textOff + len <= this.text.length) {
this.textOff += len
} else {
this.skip += len - (this.text.length - this.textOff)
this.text = ""
this.textOff = 0
}
this.pos = to
}
if (this.openStart < 0) this.openStart = openStart
}
static build(text: Text, from: number, to: number, decorations: readonly DecorationSet[]):
{content: BlockView[], breakAtStart: number, openStart: number, openEnd: number} {
let builder = new ContentBuilder(text, from, to)
builder.openEnd = RangeSet.spans(decorations, from, to, builder)
if (builder.openStart < 0) builder.openStart = builder.openEnd
builder.finish()
return builder
}
}
class NullWidget extends WidgetType {
constructor(readonly tag: string) { super() }
eq(other: NullWidget) { return other.tag == this.tag }
toDOM() { return document.createElement(this.tag) }
updateDOM(elt: HTMLElement) { return elt.localName === this.tag }
}