@farvardin/lezer-parser-markdown 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1966 @@
1
+ import {Tree, TreeBuffer, NodeType, NodeProp, NodePropSource, TreeFragment, NodeSet, TreeCursor,
2
+ Input, Parser, PartialParse, SyntaxNode, ParseWrapper} from "@lezer/common"
3
+ import {styleTags, tags as t, Tag} from "@lezer/highlight"
4
+
5
+ class CompositeBlock {
6
+ static create(type: number, value: number, from: number, parentHash: number, end: number) {
7
+ let hash = (parentHash + (parentHash << 8) + type + (value << 4)) | 0
8
+ return new CompositeBlock(type, value, from, hash, end, [], [])
9
+ }
10
+
11
+ /// @internal
12
+ hashProp: [NodeProp<any>, any][]
13
+
14
+ constructor(readonly type: number,
15
+ // Used for indentation in list items, markup character in lists
16
+ readonly value: number,
17
+ readonly from: number,
18
+ readonly hash: number,
19
+ public end: number,
20
+ readonly children: (Tree | TreeBuffer)[],
21
+ readonly positions: number[]) {
22
+ this.hashProp = [[NodeProp.contextHash, hash]]
23
+ }
24
+
25
+ addChild(child: Tree, pos: number) {
26
+ if (child.prop(NodeProp.contextHash) != this.hash)
27
+ child = new Tree(child.type, child.children, child.positions, child.length, this.hashProp)
28
+ this.children.push(child)
29
+ this.positions.push(pos)
30
+ }
31
+
32
+ toTree(nodeSet: NodeSet, end = this.end) {
33
+ let last = this.children.length - 1
34
+ if (last >= 0) end = Math.max(end, this.positions[last] + this.children[last].length + this.from)
35
+ return new Tree(nodeSet.types[this.type], this.children, this.positions, end - this.from).balance({
36
+ makeTree: (children, positions, length) => new Tree(NodeType.none, children, positions, length, this.hashProp)
37
+ })
38
+ }
39
+ }
40
+
41
+ export enum Type {
42
+ Document = 1,
43
+
44
+ CodeBlock,
45
+ FencedCode,
46
+ Blockquote,
47
+ HorizontalRule,
48
+ BulletList,
49
+ OrderedList,
50
+ ListItem,
51
+ ATXHeading1,
52
+ ATXHeading2,
53
+ ATXHeading3,
54
+ ATXHeading4,
55
+ ATXHeading5,
56
+ ATXHeading6,
57
+ SetextHeading1,
58
+ SetextHeading2,
59
+ HTMLBlock,
60
+ LinkReference,
61
+ Paragraph,
62
+ CommentBlock,
63
+ ProcessingInstructionBlock,
64
+
65
+ // Inline
66
+ Escape,
67
+ Entity,
68
+ HardBreak,
69
+ Emphasis,
70
+ StrongEmphasis,
71
+ Link,
72
+ Image,
73
+ InlineCode,
74
+ HTMLTag,
75
+ Comment,
76
+ ProcessingInstruction,
77
+ Autolink,
78
+
79
+ // Smaller tokens
80
+ HeaderMark,
81
+ QuoteMark,
82
+ ListMark,
83
+ LinkMark,
84
+ EmphasisMark,
85
+ CodeMark,
86
+ CodeText,
87
+ CodeInfo,
88
+ LinkTitle,
89
+ LinkLabel,
90
+ URL
91
+ }
92
+
93
+ /// Data structure used to accumulate a block's content during [leaf
94
+ /// block parsing](#BlockParser.leaf).
95
+ export class LeafBlock {
96
+ /// @internal
97
+ marks: Element[] = []
98
+ /// The block parsers active for this block.
99
+ parsers: LeafBlockParser[] = []
100
+
101
+ /// @internal
102
+ constructor(
103
+ /// The start position of the block.
104
+ readonly start: number,
105
+ /// The block's text content.
106
+ public content: string
107
+ ) {}
108
+ }
109
+
110
+ /// Data structure used during block-level per-line parsing.
111
+ export class Line {
112
+ /// The line's full text.
113
+ text = ""
114
+ /// The base indent provided by the composite contexts (that have
115
+ /// been handled so far).
116
+ baseIndent = 0
117
+ /// The string position corresponding to the base indent.
118
+ basePos = 0
119
+ /// The number of contexts handled @internal
120
+ depth = 0
121
+ /// Any markers (i.e. block quote markers) parsed for the contexts. @internal
122
+ markers: Element[] = []
123
+ /// The position of the next non-whitespace character beyond any
124
+ /// list, blockquote, or other composite block markers.
125
+ pos = 0
126
+ /// The column of the next non-whitespace character.
127
+ indent = 0
128
+ /// The character code of the character after `pos`.
129
+ next = -1
130
+
131
+ /// @internal
132
+ forward() {
133
+ if (this.basePos > this.pos) this.forwardInner()
134
+ }
135
+
136
+ /// @internal
137
+ forwardInner() {
138
+ let newPos = this.skipSpace(this.basePos)
139
+ this.indent = this.countIndent(newPos, this.pos, this.indent)
140
+ this.pos = newPos
141
+ this.next = newPos == this.text.length ? -1 : this.text.charCodeAt(newPos)
142
+ }
143
+
144
+ /// Skip whitespace after the given position, return the position of
145
+ /// the next non-space character or the end of the line if there's
146
+ /// only space after `from`.
147
+ skipSpace(from: number) { return skipSpace(this.text, from) }
148
+
149
+ /// @internal
150
+ reset(text: string) {
151
+ this.text = text
152
+ this.baseIndent = this.basePos = this.pos = this.indent = 0
153
+ this.forwardInner()
154
+ this.depth = 1
155
+ while (this.markers.length) this.markers.pop()
156
+ }
157
+
158
+ /// Move the line's base position forward to the given position.
159
+ /// This should only be called by composite [block
160
+ /// parsers](#BlockParser.parse) or [markup skipping
161
+ /// functions](#NodeSpec.composite).
162
+ moveBase(to: number) {
163
+ this.basePos = to
164
+ this.baseIndent = this.countIndent(to, this.pos, this.indent)
165
+ }
166
+
167
+ /// Move the line's base position forward to the given _column_.
168
+ moveBaseColumn(indent: number) {
169
+ this.baseIndent = indent
170
+ this.basePos = this.findColumn(indent)
171
+ }
172
+
173
+ /// Store a composite-block-level marker. Should be called from
174
+ /// [markup skipping functions](#NodeSpec.composite) when they
175
+ /// consume any non-whitespace characters.
176
+ addMarker(elt: Element) {
177
+ this.markers.push(elt)
178
+ }
179
+
180
+ /// Find the column position at `to`, optionally starting at a given
181
+ /// position and column.
182
+ countIndent(to: number, from = 0, indent = 0) {
183
+ for (let i = from; i < to; i++)
184
+ indent += this.text.charCodeAt(i) == 9 ? 4 - indent % 4 : 1
185
+ return indent
186
+ }
187
+
188
+ /// Find the position corresponding to the given column.
189
+ findColumn(goal: number) {
190
+ let i = 0
191
+ for (let indent = 0; i < this.text.length && indent < goal; i++)
192
+ indent += this.text.charCodeAt(i) == 9 ? 4 - indent % 4 : 1
193
+ return i
194
+ }
195
+
196
+ /// @internal
197
+ scrub() {
198
+ if (!this.baseIndent) return this.text
199
+ let result = ""
200
+ for (let i = 0; i < this.basePos; i++) result += " "
201
+ return result + this.text.slice(this.basePos)
202
+ }
203
+ }
204
+
205
+ function skipForList(bl: CompositeBlock, cx: BlockContext, line: Line) {
206
+ if (line.pos == line.text.length ||
207
+ (bl != cx.block && line.indent >= cx.stack[line.depth + 1].value + line.baseIndent)) return true
208
+ if (line.indent >= line.baseIndent + 4) return false
209
+ let size = (bl.type == Type.OrderedList ? isOrderedList : isBulletList)(line, cx, false)
210
+ return size > 0 &&
211
+ (bl.type != Type.BulletList || isHorizontalRule(line, cx, false) < 0) &&
212
+ line.text.charCodeAt(line.pos + size - 1) == bl.value
213
+ }
214
+
215
+ const DefaultSkipMarkup: {[type: number]: (bl: CompositeBlock, cx: BlockContext, line: Line) => boolean} = {
216
+ [Type.Blockquote](bl, cx, line) {
217
+ if (line.next != 62 /* '>' */) return false
218
+ line.markers.push(elt(Type.QuoteMark, cx.lineStart + line.pos, cx.lineStart + line.pos + 1))
219
+ line.moveBase(line.pos + (space(line.text.charCodeAt(line.pos + 1)) ? 2 : 1))
220
+ bl.end = cx.lineStart + line.text.length
221
+ return true
222
+ },
223
+ [Type.ListItem](bl, _cx, line) {
224
+ if (line.indent < line.baseIndent + bl.value && line.next > -1) return false
225
+ line.moveBaseColumn(line.baseIndent + bl.value)
226
+ return true
227
+ },
228
+ [Type.OrderedList]: skipForList,
229
+ [Type.BulletList]: skipForList,
230
+ [Type.Document]() { return true }
231
+ }
232
+
233
+ export function space(ch: number) { return ch == 32 || ch == 9 || ch == 10 || ch == 13 }
234
+
235
+ function skipSpace(line: string, i = 0) {
236
+ while (i < line.length && space(line.charCodeAt(i))) i++
237
+ return i
238
+ }
239
+
240
+ function skipSpaceBack(line: string, i: number, to: number) {
241
+ while (i > to && space(line.charCodeAt(i - 1))) i--
242
+ return i
243
+ }
244
+
245
+ function isFencedCode(line: Line) {
246
+ if (line.next != 96 && line.next != 126 /* '`~' */) return -1
247
+ let pos = line.pos + 1
248
+ while (pos < line.text.length && line.text.charCodeAt(pos) == line.next) pos++
249
+ if (pos < line.pos + 3) return -1
250
+ if (line.next == 96) for (let i = pos; i < line.text.length; i++) if (line.text.charCodeAt(i) == 96) return -1
251
+ return pos
252
+ }
253
+
254
+ function isBlockquote(line: Line) {
255
+ return line.next != 62 /* '>' */ ? -1 : line.text.charCodeAt(line.pos + 1) == 32 ? 2 : 1
256
+ }
257
+
258
+ function isHorizontalRule(line: Line, cx: BlockContext, breaking: boolean) {
259
+ if (line.next != 42 && line.next != 45 && line.next != 95 /* '_-*' */) return -1
260
+ let count = 1
261
+ for (let pos = line.pos + 1; pos < line.text.length; pos++) {
262
+ let ch = line.text.charCodeAt(pos)
263
+ if (ch == line.next) count++
264
+ else if (!space(ch)) return -1
265
+ }
266
+ // Setext headers take precedence
267
+ if (breaking && line.next == 45 && isSetextUnderline(line) > -1 && line.depth == cx.stack.length &&
268
+ cx.parser.leafBlockParsers.indexOf(DefaultLeafBlocks.SetextHeading) > -1) return -1
269
+ return count < 3 ? -1 : 1
270
+ }
271
+
272
+ function inList(cx: BlockContext, type: Type) {
273
+ for (let i = cx.stack.length - 1; i >= 0; i--)
274
+ if (cx.stack[i].type == type) return true
275
+ return false
276
+ }
277
+
278
+ function isBulletList(line: Line, cx: BlockContext, breaking: boolean) {
279
+ return (line.next == 45 || line.next == 43 || line.next == 42 /* '-+*' */) &&
280
+ (line.pos == line.text.length - 1 || space(line.text.charCodeAt(line.pos + 1))) &&
281
+ (!breaking || inList(cx, Type.BulletList) || line.skipSpace(line.pos + 2) < line.text.length) ? 1 : -1
282
+ }
283
+
284
+ function isOrderedList(line: Line, cx: BlockContext, breaking: boolean) {
285
+ let pos = line.pos, next = line.next
286
+ for (;;) {
287
+ if (next >= 48 && next <= 57 /* '0-9' */) pos++
288
+ else break
289
+ if (pos == line.text.length) return -1
290
+ next = line.text.charCodeAt(pos)
291
+ }
292
+ if (pos == line.pos || pos > line.pos + 9 ||
293
+ (next != 46 && next != 41 /* '.)' */) ||
294
+ (pos < line.text.length - 1 && !space(line.text.charCodeAt(pos + 1))) ||
295
+ breaking && !inList(cx, Type.OrderedList) &&
296
+ (line.skipSpace(pos + 1) == line.text.length || pos > line.pos + 1 || line.next != 49 /* '1' */))
297
+ return -1
298
+ return pos + 1 - line.pos
299
+ }
300
+
301
+ function isAtxHeading(line: Line) {
302
+ if (line.next != 61 /* '=' */) return -1
303
+ let pos = line.pos + 1
304
+ while (pos < line.text.length && line.text.charCodeAt(pos) == 61) pos++
305
+ if (pos < line.text.length && line.text.charCodeAt(pos) != 32) return -1
306
+ let size = pos - line.pos
307
+ return size > 6 ? -1 : size
308
+ }
309
+
310
+ function isSetextUnderline(line: Line) {
311
+ if (line.next != 45 && line.next != 61 /* '-=' */ || line.indent >= line.baseIndent + 4) return -1
312
+ let pos = line.pos + 1
313
+ while (pos < line.text.length && line.text.charCodeAt(pos) == line.next) pos++
314
+ let end = pos
315
+ while (pos < line.text.length && space(line.text.charCodeAt(pos))) pos++
316
+ return pos == line.text.length ? end : -1
317
+ }
318
+
319
+ const EmptyLine = /^[ \t]*$/, CommentEnd = /-->/, ProcessingEnd = /\?>/
320
+ const HTMLBlockStyle = [
321
+ [/^<(?:script|pre|style)(?:\s|>|$)/i, /<\/(?:script|pre|style)>/i],
322
+ [/^\s*<!--/, CommentEnd],
323
+ [/^\s*<\?/, ProcessingEnd],
324
+ [/^\s*<![A-Z]/, />/],
325
+ [/^\s*<!\[CDATA\[/, /\]\]>/],
326
+ [/^\s*<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|\/?>|$)/i, EmptyLine],
327
+ [/^\s*(?:<\/[a-z][\w-]*\s*>|<[a-z][\w-]*(\s+[a-z:_][\w-.]*(?:\s*=\s*(?:[^\s"'=<>`]+|'[^']*'|"[^"]*"))?)*\s*>)\s*$/i, EmptyLine]
328
+ ]
329
+
330
+ function isHTMLBlock(line: Line, _cx: BlockContext, breaking: boolean) {
331
+ if (line.next != 60 /* '<' */) return -1
332
+ let rest = line.text.slice(line.pos)
333
+ for (let i = 0, e = HTMLBlockStyle.length - (breaking ? 1 : 0); i < e; i++)
334
+ if (HTMLBlockStyle[i][0].test(rest)) return i
335
+ return -1
336
+ }
337
+
338
+ function getListIndent(line: Line, pos: number) {
339
+ let indentAfter = line.countIndent(pos, line.pos, line.indent)
340
+ let indented = line.countIndent(line.skipSpace(pos), pos, indentAfter)
341
+ return indented >= indentAfter + 5 ? indentAfter + 1 : indented
342
+ }
343
+
344
+ // Return type for block parsing functions. Can be either:
345
+ //
346
+ // - false to indicate that nothing was matched and lower-precedence
347
+ // parsers should run.
348
+ //
349
+ // - true to indicate that a leaf block was parsed and the stream
350
+ // was advanced past its content.
351
+ //
352
+ // - null to indicate that a context was opened and block parsing
353
+ // should continue on this line.
354
+ type BlockResult = boolean | null
355
+
356
+ function addCodeText(marks: Element[], from: number, to: number) {
357
+ let last = marks.length - 1
358
+ if (last >= 0 && marks[last].to == from && marks[last].type == Type.CodeText) (marks[last] as any).to = to
359
+ else marks.push(elt(Type.CodeText, from, to))
360
+ }
361
+
362
+ // Rules for parsing blocks. A return value of false means the rule
363
+ // doesn't apply here, true means it does. When true is returned and
364
+ // `p.line` has been updated, the rule is assumed to have consumed a
365
+ // leaf block. Otherwise, it is assumed to have opened a context.
366
+ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => BlockResult) | undefined} = {
367
+ LinkReference: undefined,
368
+
369
+ IndentedCode(cx, line) {
370
+ let base = line.baseIndent + 4
371
+ if (line.indent < base) return false
372
+ let start = line.findColumn(base)
373
+ let from = cx.lineStart + start, to = cx.lineStart + line.text.length
374
+ let marks: Element[] = [], pendingMarks: Element[] = []
375
+ addCodeText(marks, from, to)
376
+ while (cx.nextLine() && line.depth >= cx.stack.length) {
377
+ if (line.pos == line.text.length) { // Empty
378
+ addCodeText(pendingMarks, cx.lineStart - 1, cx.lineStart)
379
+ for (let m of line.markers) pendingMarks.push(m)
380
+ } else if (line.indent < base) {
381
+ break
382
+ } else {
383
+ if (pendingMarks.length) {
384
+ for (let m of pendingMarks) {
385
+ if (m.type == Type.CodeText) addCodeText(marks, m.from, m.to)
386
+ else marks.push(m)
387
+ }
388
+ pendingMarks = []
389
+ }
390
+ addCodeText(marks, cx.lineStart - 1, cx.lineStart)
391
+ for (let m of line.markers) marks.push(m)
392
+ to = cx.lineStart + line.text.length
393
+ let codeStart = cx.lineStart + line.findColumn(line.baseIndent + 4)
394
+ if (codeStart < to) addCodeText(marks, codeStart, to)
395
+ }
396
+ }
397
+ if (pendingMarks.length) {
398
+ pendingMarks = pendingMarks.filter(m => m.type != Type.CodeText)
399
+ if (pendingMarks.length) line.markers = pendingMarks.concat(line.markers)
400
+ }
401
+
402
+ cx.addNode(cx.buffer.writeElements(marks, -from).finish(Type.CodeBlock, to - from), from)
403
+ return true
404
+ },
405
+
406
+ FencedCode(cx, line) {
407
+ let fenceEnd = isFencedCode(line)
408
+ if (fenceEnd < 0) return false
409
+ let from = cx.lineStart + line.pos, ch = line.next, len = fenceEnd - line.pos
410
+ let infoFrom = line.skipSpace(fenceEnd), infoTo = skipSpaceBack(line.text, line.text.length, infoFrom)
411
+ let marks: (Element | TreeElement)[] = [elt(Type.CodeMark, from, from + len)]
412
+ if (infoFrom < infoTo)
413
+ marks.push(elt(Type.CodeInfo, cx.lineStart + infoFrom, cx.lineStart + infoTo))
414
+
415
+ for (let first = true, empty = true, hasLine = false; cx.nextLine() && line.depth >= cx.stack.length; first = false) {
416
+ let i = line.pos
417
+ if (line.indent - line.baseIndent < 4)
418
+ while (i < line.text.length && line.text.charCodeAt(i) == ch) i++
419
+ if (i - line.pos >= len && line.skipSpace(i) == line.text.length) {
420
+ for (let m of line.markers) marks.push(m)
421
+ if (empty && hasLine) addCodeText(marks, cx.lineStart - 1, cx.lineStart)
422
+ marks.push(elt(Type.CodeMark, cx.lineStart + line.pos, cx.lineStart + i))
423
+ cx.nextLine()
424
+ break
425
+ } else {
426
+ hasLine = true
427
+ if (!first) { addCodeText(marks, cx.lineStart - 1, cx.lineStart); empty = false }
428
+ for (let m of line.markers) marks.push(m)
429
+ let textStart = cx.lineStart + line.basePos, textEnd = cx.lineStart + line.text.length
430
+ if (textStart < textEnd) { addCodeText(marks, textStart, textEnd); empty = false }
431
+ }
432
+ }
433
+ cx.addNode(cx.buffer.writeElements(marks, -from)
434
+ .finish(Type.FencedCode, cx.prevLineEnd() - from), from)
435
+ return true
436
+ },
437
+
438
+ Blockquote(cx, line) {
439
+ let size = isBlockquote(line)
440
+ if (size < 0) return false
441
+ cx.startContext(Type.Blockquote, line.pos)
442
+ cx.addNode(Type.QuoteMark, cx.lineStart + line.pos, cx.lineStart + line.pos + 1)
443
+ line.moveBase(line.pos + size)
444
+ return null
445
+ },
446
+
447
+ HorizontalRule(cx, line) {
448
+ if (isHorizontalRule(line, cx, false) < 0) return false
449
+ let from = cx.lineStart + line.pos
450
+ cx.nextLine()
451
+ cx.addNode(Type.HorizontalRule, from)
452
+ return true
453
+ },
454
+
455
+ BulletList(cx, line) {
456
+ let size = isBulletList(line, cx, false)
457
+ if (size < 0) return false
458
+ if (cx.block.type != Type.BulletList)
459
+ cx.startContext(Type.BulletList, line.basePos, line.next)
460
+ let newBase = getListIndent(line, line.pos + 1)
461
+ cx.startContext(Type.ListItem, line.basePos, newBase - line.baseIndent)
462
+ cx.addNode(Type.ListMark, cx.lineStart + line.pos, cx.lineStart + line.pos + size)
463
+ line.moveBaseColumn(newBase)
464
+ return null
465
+ },
466
+
467
+ OrderedList(cx, line) {
468
+ let size = isOrderedList(line, cx, false)
469
+ if (size < 0) return false
470
+ if (cx.block.type != Type.OrderedList)
471
+ cx.startContext(Type.OrderedList, line.basePos, line.text.charCodeAt(line.pos + size - 1))
472
+ let newBase = getListIndent(line, line.pos + size)
473
+ cx.startContext(Type.ListItem, line.basePos, newBase - line.baseIndent)
474
+ cx.addNode(Type.ListMark, cx.lineStart + line.pos, cx.lineStart + line.pos + size)
475
+ line.moveBaseColumn(newBase)
476
+ return null
477
+ },
478
+
479
+ ATXHeading(cx, line) {
480
+ let size = isAtxHeading(line)
481
+ if (size < 0) return false
482
+ let off = line.pos, from = cx.lineStart + off
483
+ let endOfSpace = skipSpaceBack(line.text, line.text.length, off), after = endOfSpace
484
+ while (after > off && line.text.charCodeAt(after - 1) == line.next) after--
485
+ if (after == endOfSpace || after == off || !space(line.text.charCodeAt(after - 1))) after = line.text.length
486
+ let buf = cx.buffer
487
+ .write(Type.HeaderMark, 0, size)
488
+ .writeElements(cx.parser.parseInline(line.text.slice(off + size + 1, after), from + size + 1), -from)
489
+ if (after < line.text.length) buf.write(Type.HeaderMark, after - off, endOfSpace - off)
490
+ let node = buf.finish(Type.ATXHeading1 - 1 + size, line.text.length - off)
491
+ cx.nextLine()
492
+ cx.addNode(node, from)
493
+ return true
494
+ },
495
+
496
+ HTMLBlock(cx, line) {
497
+ let type = isHTMLBlock(line, cx, false)
498
+ if (type < 0) return false
499
+ let from = cx.lineStart + line.pos, end = HTMLBlockStyle[type][1]
500
+ let marks: Element[] = [], trailing = end != EmptyLine
501
+ while (!end.test(line.text) && cx.nextLine()) {
502
+ if (line.depth < cx.stack.length) { trailing = false; break }
503
+ for (let m of line.markers) marks.push(m)
504
+ }
505
+ if (trailing) cx.nextLine()
506
+ let nodeType = end == CommentEnd ? Type.CommentBlock : end == ProcessingEnd ? Type.ProcessingInstructionBlock : Type.HTMLBlock
507
+ let to = cx.prevLineEnd()
508
+ cx.addNode(cx.buffer.writeElements(marks, -from).finish(nodeType, to - from), from)
509
+ return true
510
+ },
511
+
512
+ SetextHeading: undefined // Specifies relative precedence for block-continue function
513
+ }
514
+
515
+ const enum RefStage { Failed = -1, Start, Label, Link, Title }
516
+
517
+ // This implements a state machine that incrementally parses link references. At each
518
+ // next line, it looks ahead to see if the line continues the reference or not. If it
519
+ // doesn't and a valid link is available ending before that line, it finishes that.
520
+ // Similarly, on `finish` (when the leaf is terminated by external circumstances), it
521
+ // creates a link reference if there's a valid reference up to the current point.
522
+ class LinkReferenceParser implements LeafBlockParser {
523
+ stage = RefStage.Start
524
+ elts: Element[] = []
525
+ pos = 0
526
+ start: number
527
+
528
+ constructor(leaf: LeafBlock) {
529
+ this.start = leaf.start
530
+ this.advance(leaf.content)
531
+ }
532
+
533
+ nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) {
534
+ if (this.stage == RefStage.Failed) return false
535
+ let content = leaf.content + "\n" + line.scrub()
536
+ let finish = this.advance(content)
537
+ if (finish > -1 && finish < content.length) return this.complete(cx, leaf, finish)
538
+ return false
539
+ }
540
+
541
+ finish(cx: BlockContext, leaf: LeafBlock) {
542
+ if ((this.stage == RefStage.Link || this.stage == RefStage.Title) && skipSpace(leaf.content, this.pos) == leaf.content.length)
543
+ return this.complete(cx, leaf, leaf.content.length)
544
+ return false
545
+ }
546
+
547
+ complete(cx: BlockContext, leaf: LeafBlock, len: number) {
548
+ cx.addLeafElement(leaf, elt(Type.LinkReference, this.start, this.start + len, this.elts))
549
+ return true
550
+ }
551
+
552
+ nextStage(elt: Element | null | false) {
553
+ if (elt) {
554
+ this.pos = elt.to - this.start
555
+ this.elts.push(elt)
556
+ this.stage++
557
+ return true
558
+ }
559
+ if (elt === false) this.stage = RefStage.Failed
560
+ return false
561
+ }
562
+
563
+ advance(content: string) {
564
+ for (;;) {
565
+ if (this.stage == RefStage.Failed) {
566
+ return -1
567
+ } else if (this.stage == RefStage.Start) {
568
+ if (!this.nextStage(parseLinkLabel(content, this.pos, this.start, true))) return -1
569
+ if (content.charCodeAt(this.pos) != 58 /* ':' */) return this.stage = RefStage.Failed
570
+ this.elts.push(elt(Type.LinkMark, this.pos + this.start, this.pos + this.start + 1))
571
+ this.pos++
572
+ } else if (this.stage == RefStage.Label) {
573
+ if (!this.nextStage(parseURL(content, skipSpace(content, this.pos), this.start))) return -1
574
+ } else if (this.stage == RefStage.Link) {
575
+ let skip = skipSpace(content, this.pos), end = 0
576
+ if (skip > this.pos) {
577
+ let title = parseLinkTitle(content, skip, this.start)
578
+ if (title) {
579
+ let titleEnd = lineEnd(content, title.to - this.start)
580
+ if (titleEnd > 0) { this.nextStage(title); end = titleEnd }
581
+ }
582
+ }
583
+ if (!end) end = lineEnd(content, this.pos)
584
+ return end > 0 && end < content.length ? end : -1
585
+ } else { // RefStage.Title
586
+ return lineEnd(content, this.pos)
587
+ }
588
+ }
589
+ }
590
+ }
591
+
592
+ function lineEnd(text: string, pos: number) {
593
+ for (; pos < text.length; pos++) {
594
+ let next = text.charCodeAt(pos)
595
+ if (next == 10) break
596
+ if (!space(next)) return -1
597
+ }
598
+ return pos
599
+ }
600
+
601
+ class SetextHeadingParser implements LeafBlockParser {
602
+ nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) {
603
+ let underline = line.depth < cx.stack.length ? -1 : isSetextUnderline(line)
604
+ let next = line.next
605
+ if (underline < 0) return false
606
+ let underlineMark = elt(Type.HeaderMark, cx.lineStart + line.pos, cx.lineStart + underline)
607
+ cx.nextLine()
608
+ cx.addLeafElement(leaf, elt(next == 61 ? Type.SetextHeading1 : Type.SetextHeading2, leaf.start, cx.prevLineEnd(), [
609
+ ...cx.parser.parseInline(leaf.content, leaf.start),
610
+ underlineMark
611
+ ]))
612
+ return true
613
+ }
614
+
615
+ finish() {
616
+ return false
617
+ }
618
+ }
619
+
620
+ const DefaultLeafBlocks: {[name: string]: (cx: BlockContext, leaf: LeafBlock) => LeafBlockParser | null} = {
621
+ LinkReference(_, leaf) { return leaf.content.charCodeAt(0) == 91 /* '[' */ ? new LinkReferenceParser(leaf) : null },
622
+ SetextHeading() { return new SetextHeadingParser }
623
+ }
624
+
625
+ const DefaultEndLeaf: readonly ((cx: BlockContext, line: Line) => boolean)[] = [
626
+ (_, line) => isAtxHeading(line) >= 0,
627
+ (_, line) => isFencedCode(line) >= 0,
628
+ (_, line) => isBlockquote(line) >= 0,
629
+ (p, line) => isBulletList(line, p, true) >= 0,
630
+ (p, line) => isOrderedList(line, p, true) >= 0,
631
+ (p, line) => isHorizontalRule(line, p, true) >= 0,
632
+ (p, line) => isHTMLBlock(line, p, true) >= 0
633
+ ]
634
+
635
+ const scanLineResult = {text: "", end: 0}
636
+
637
+ /// Block-level parsing functions get access to this context object.
638
+ export class BlockContext implements PartialParse {
639
+ /// @internal
640
+ block: CompositeBlock
641
+ /// @internal
642
+ stack: CompositeBlock[]
643
+ private line = new Line()
644
+ private atEnd = false
645
+ private fragments: FragmentCursor | null
646
+ private to: number
647
+ /// For reused nodes on gaps, we can't directly put the original
648
+ /// node into the tree, since that may be bigger than its parent.
649
+ /// When this happens, we create a dummy tree that is replaced by
650
+ /// the proper node in `injectGaps` @internal
651
+ reusePlaceholders: Map<Tree, Tree> = new Map
652
+ stoppedAt: number | null = null
653
+
654
+ /// The start of the current line.
655
+ lineStart: number
656
+ /// The absolute (non-gap-adjusted) position of the line @internal
657
+ absoluteLineStart: number
658
+ /// The range index that absoluteLineStart points into @internal
659
+ rangeI = 0
660
+ /// @internal
661
+ absoluteLineEnd: number
662
+
663
+ /// @internal
664
+ constructor(
665
+ /// The parser configuration used.
666
+ readonly parser: MarkdownParser,
667
+ /// @internal
668
+ readonly input: Input,
669
+ fragments: readonly TreeFragment[],
670
+ /// @internal
671
+ readonly ranges: readonly {from: number, to: number}[],
672
+ ) {
673
+ this.to = ranges[ranges.length - 1].to
674
+ this.lineStart = this.absoluteLineStart = this.absoluteLineEnd = ranges[0].from
675
+ this.block = CompositeBlock.create(Type.Document, 0, this.lineStart, 0, 0)
676
+ this.stack = [this.block]
677
+ this.fragments = fragments.length ? new FragmentCursor(fragments, input) : null
678
+ this.readLine()
679
+ }
680
+
681
+ get parsedPos() {
682
+ return this.absoluteLineStart
683
+ }
684
+
685
+ advance() {
686
+ if (this.stoppedAt != null && this.absoluteLineStart > this.stoppedAt)
687
+ return this.finish()
688
+
689
+ let {line} = this
690
+ for (;;) {
691
+ for (let markI = 0;;) {
692
+ let next = line.depth < this.stack.length ? this.stack[this.stack.length - 1] : null
693
+ while (markI < line.markers.length && (!next || line.markers[markI].from < next.end)) {
694
+ let mark = line.markers[markI++]
695
+ this.addNode(mark.type, mark.from, mark.to)
696
+ }
697
+ if (!next) break
698
+ this.finishContext()
699
+ }
700
+ if (line.pos < line.text.length) break
701
+ // Empty line
702
+ if (!this.nextLine()) return this.finish()
703
+ }
704
+
705
+ if (this.fragments && this.reuseFragment(line.basePos)) return null
706
+
707
+ start: for (;;) {
708
+ for (let type of this.parser.blockParsers) if (type) {
709
+ let result = type(this, line)
710
+ if (result != false) {
711
+ if (result == true) return null
712
+ line.forward()
713
+ continue start
714
+ }
715
+ }
716
+ break
717
+ }
718
+
719
+ let leaf = new LeafBlock(this.lineStart + line.pos, line.text.slice(line.pos))
720
+ for (let parse of this.parser.leafBlockParsers) if (parse) {
721
+ let parser = parse!(this, leaf)
722
+ if (parser) leaf.parsers.push(parser!)
723
+ }
724
+ lines: while (this.nextLine()) {
725
+ if (line.pos == line.text.length) break
726
+ if (line.indent < line.baseIndent + 4) {
727
+ for (let stop of this.parser.endLeafBlock) if (stop(this, line, leaf)) break lines
728
+ }
729
+ for (let parser of leaf.parsers) if (parser.nextLine(this, line, leaf)) return null
730
+ leaf.content += "\n" + line.scrub()
731
+ for (let m of line.markers) leaf.marks.push(m)
732
+ }
733
+ this.finishLeaf(leaf)
734
+ return null
735
+ }
736
+
737
+ stopAt(pos: number) {
738
+ if (this.stoppedAt != null && this.stoppedAt < pos) throw new RangeError("Can't move stoppedAt forward")
739
+ this.stoppedAt = pos
740
+ }
741
+
742
+ private reuseFragment(start: number) {
743
+ if (!this.fragments!.moveTo(this.absoluteLineStart + start, this.absoluteLineStart) ||
744
+ !this.fragments!.matches(this.block.hash)) return false
745
+ let taken = this.fragments!.takeNodes(this)
746
+ if (!taken) return false
747
+ this.absoluteLineStart += taken
748
+ this.lineStart = toRelative(this.absoluteLineStart, this.ranges)
749
+ this.moveRangeI()
750
+ if (this.absoluteLineStart < this.to) {
751
+ this.lineStart++
752
+ this.absoluteLineStart++
753
+ this.readLine()
754
+ } else {
755
+ this.atEnd = true
756
+ this.readLine()
757
+ }
758
+ return true
759
+ }
760
+
761
+ /// The number of parent blocks surrounding the current block.
762
+ get depth() {
763
+ return this.stack.length
764
+ }
765
+
766
+ /// Get the type of the parent block at the given depth. When no
767
+ /// depth is passed, return the type of the innermost parent.
768
+ parentType(depth = this.depth - 1) {
769
+ return this.parser.nodeSet.types[this.stack[depth].type]
770
+ }
771
+
772
+ /// Move to the next input line. This should only be called by
773
+ /// (non-composite) [block parsers](#BlockParser.parse) that consume
774
+ /// the line directly, or leaf block parser
775
+ /// [`nextLine`](#LeafBlockParser.nextLine) methods when they
776
+ /// consume the current line (and return true).
777
+ nextLine() {
778
+ this.lineStart += this.line.text.length
779
+ if (this.absoluteLineEnd >= this.to) {
780
+ this.absoluteLineStart = this.absoluteLineEnd
781
+ this.atEnd = true
782
+ this.readLine()
783
+ return false
784
+ } else {
785
+ this.lineStart++
786
+ this.absoluteLineStart = this.absoluteLineEnd + 1
787
+ this.moveRangeI()
788
+ this.readLine()
789
+ return true
790
+ }
791
+ }
792
+
793
+ /// Retrieve the text of the line after the current one, without
794
+ /// actually moving the context's current line forward.
795
+ peekLine() {
796
+ return this.scanLine(this.absoluteLineEnd + 1).text
797
+ }
798
+
799
+ private moveRangeI() {
800
+ while (this.rangeI < this.ranges.length - 1 && this.absoluteLineStart >= this.ranges[this.rangeI].to) {
801
+ this.rangeI++
802
+ this.absoluteLineStart = Math.max(this.absoluteLineStart, this.ranges[this.rangeI].from)
803
+ }
804
+ }
805
+
806
+ /// @internal
807
+ /// Collect the text for the next line.
808
+ scanLine(start: number) {
809
+ let r = scanLineResult
810
+ r.end = start
811
+ if (start >= this.to) {
812
+ r.text = ""
813
+ } else {
814
+ r.text = this.lineChunkAt(start)
815
+ r.end += r.text.length
816
+ if (this.ranges.length > 1) {
817
+ let textOffset = this.absoluteLineStart, rangeI = this.rangeI
818
+ while (this.ranges[rangeI].to < r.end) {
819
+ rangeI++
820
+ let nextFrom = this.ranges[rangeI].from
821
+ let after = this.lineChunkAt(nextFrom)
822
+ r.end = nextFrom + after.length
823
+ r.text = r.text.slice(0, this.ranges[rangeI - 1].to - textOffset) + after
824
+ textOffset = r.end - r.text.length
825
+ }
826
+ }
827
+ }
828
+ return r
829
+ }
830
+
831
+ /// @internal
832
+ /// Populate this.line with the content of the next line. Skip
833
+ /// leading characters covered by composite blocks.
834
+ readLine() {
835
+ let {line} = this, {text, end} = this.scanLine(this.absoluteLineStart)
836
+ this.absoluteLineEnd = end
837
+ line.reset(text)
838
+ for (; line.depth < this.stack.length; line.depth++) {
839
+ let cx = this.stack[line.depth], handler = this.parser.skipContextMarkup[cx.type]
840
+ if (!handler) throw new Error("Unhandled block context " + Type[cx.type])
841
+ let marks = this.line.markers.length
842
+ if (!handler(cx, this, line)) {
843
+ if (this.line.markers.length > marks)
844
+ cx.end = this.line.markers[this.line.markers.length - 1].to
845
+ line.forward()
846
+ break
847
+ }
848
+ line.forward()
849
+ }
850
+ }
851
+
852
+ private lineChunkAt(pos: number) {
853
+ let next = this.input.chunk(pos), text
854
+ if (!this.input.lineChunks) {
855
+ let eol = next.indexOf("\n")
856
+ text = eol < 0 ? next : next.slice(0, eol)
857
+ } else {
858
+ text = next == "\n" ? "" : next
859
+ }
860
+ return pos + text.length > this.to ? text.slice(0, this.to - pos) : text
861
+ }
862
+
863
+ /// The end position of the previous line.
864
+ prevLineEnd() { return this.atEnd ? this.lineStart : this.lineStart - 1 }
865
+
866
+ /// @internal
867
+ startContext(type: Type, start: number, value = 0) {
868
+ this.block = CompositeBlock.create(type, value, this.lineStart + start, this.block.hash, this.lineStart + this.line.text.length)
869
+ this.stack.push(this.block)
870
+ }
871
+
872
+ /// Start a composite block. Should only be called from [block
873
+ /// parser functions](#BlockParser.parse) that return null.
874
+ startComposite(type: string, start: number, value = 0) {
875
+ this.startContext(this.parser.getNodeType(type), start, value)
876
+ }
877
+
878
+ /// @internal
879
+ addNode(block: Type | Tree, from: number, to?: number) {
880
+ if (typeof block == "number") block = new Tree(this.parser.nodeSet.types[block], none, none, (to ?? this.prevLineEnd()) - from)
881
+ this.block.addChild(block, from - this.block.from)
882
+ }
883
+
884
+ /// Add a block element. Can be called by [block
885
+ /// parsers](#BlockParser.parse).
886
+ addElement(elt: Element) {
887
+ this.block.addChild(elt.toTree(this.parser.nodeSet), elt.from - this.block.from)
888
+ }
889
+
890
+ /// Add a block element from a [leaf parser](#LeafBlockParser). This
891
+ /// makes sure any extra composite block markup (such as blockquote
892
+ /// markers) inside the block are also added to the syntax tree.
893
+ addLeafElement(leaf: LeafBlock, elt: Element) {
894
+ this.addNode(this.buffer
895
+ .writeElements(injectMarks(elt.children, leaf.marks), -elt.from)
896
+ .finish(elt.type, elt.to - elt.from), elt.from)
897
+ }
898
+
899
+ /// @internal
900
+ finishContext() {
901
+ let cx = this.stack.pop()!
902
+ let top = this.stack[this.stack.length - 1]
903
+ top.addChild(cx.toTree(this.parser.nodeSet), cx.from - top.from)
904
+ this.block = top
905
+ }
906
+
907
+ private finish() {
908
+ while (this.stack.length > 1) this.finishContext()
909
+ return this.addGaps(this.block.toTree(this.parser.nodeSet, this.lineStart))
910
+ }
911
+
912
+ private addGaps(tree: Tree) {
913
+ return this.ranges.length > 1 ?
914
+ injectGaps(this.ranges, 0, tree.topNode, this.ranges[0].from, this.reusePlaceholders) : tree
915
+ }
916
+
917
+ /// @internal
918
+ finishLeaf(leaf: LeafBlock) {
919
+ for (let parser of leaf.parsers) if (parser.finish(this, leaf)) return
920
+ let inline = injectMarks(this.parser.parseInline(leaf.content, leaf.start), leaf.marks)
921
+ this.addNode(this.buffer
922
+ .writeElements(inline, -leaf.start)
923
+ .finish(Type.Paragraph, leaf.content.length), leaf.start)
924
+ }
925
+
926
+ /// Create an [`Element`](#Element) object to represent some syntax
927
+ /// node.
928
+ elt(type: string, from: number, to: number, children?: readonly Element[]): Element
929
+ elt(tree: Tree, at: number): Element
930
+ elt(type: string | Tree, from: number, to?: number, children?: readonly Element[]): Element {
931
+ if (typeof type == "string") return elt(this.parser.getNodeType(type), from, to!, children)
932
+ return new TreeElement(type, from)
933
+ }
934
+
935
+ /// @internal
936
+ get buffer() { return new Buffer(this.parser.nodeSet) }
937
+ }
938
+
939
+ function injectGaps(
940
+ ranges: readonly {from: number, to: number}[], rangeI: number,
941
+ tree: SyntaxNode, offset: number, dummies: Map<Tree, Tree>
942
+ ): Tree {
943
+ let rangeEnd = ranges[rangeI].to
944
+ let children = [], positions = [], start = tree.from + offset
945
+ function movePastNext(upto: number, inclusive: boolean) {
946
+ while (inclusive ? upto >= rangeEnd : upto > rangeEnd) {
947
+ let size = ranges[rangeI + 1].from - rangeEnd
948
+ offset += size
949
+ upto += size
950
+ rangeI++
951
+ rangeEnd = ranges[rangeI].to
952
+ }
953
+ }
954
+ for (let ch = tree.firstChild; ch; ch = ch.nextSibling) {
955
+ movePastNext(ch.from + offset, true)
956
+ let from = ch.from + offset, node, reuse = dummies.get(ch.tree!)
957
+ if (reuse) {
958
+ node = reuse
959
+ } else if (ch.to + offset > rangeEnd) {
960
+ node = injectGaps(ranges, rangeI, ch, offset, dummies)
961
+ movePastNext(ch.to + offset, false)
962
+ } else {
963
+ node = ch.toTree()
964
+ }
965
+ children.push(node)
966
+ positions.push(from - start)
967
+ }
968
+ movePastNext(tree.to + offset, false)
969
+ return new Tree(tree.type, children, positions, tree.to + offset - start, tree.tree ? tree.tree.propValues : undefined)
970
+ }
971
+
972
+ /// Used in the [configuration](#MarkdownConfig.defineNodes) to define
973
+ /// new [syntax node
974
+ /// types](https://lezer.codemirror.net/docs/ref/#common.NodeType).
975
+ export interface NodeSpec {
976
+ /// The node's name.
977
+ name: string
978
+ /// Should be set to true if this type represents a block node.
979
+ block?: boolean
980
+ /// If this is a composite block, this should hold a function that,
981
+ /// at the start of a new line where that block is active, checks
982
+ /// whether the composite block should continue (return value) and
983
+ /// optionally [adjusts](#Line.moveBase) the line's base position
984
+ /// and [registers](#Line.addMarker) nodes for any markers involved
985
+ /// in the block's syntax.
986
+ composite?(cx: BlockContext, line: Line, value: number): boolean
987
+ /// Add highlighting tag information for this node. The value of
988
+ /// this property may either by a tag or array of tags to assign
989
+ /// directly to this node, or an object in the style of
990
+ /// [`styleTags`](https://lezer.codemirror.net/docs/ref/#highlight.styleTags)'s
991
+ /// argument to assign more complicated rules.
992
+ style?: Tag | readonly Tag[] | {[selector: string]: Tag | readonly Tag[]}
993
+ }
994
+
995
+ /// Inline parsers are called for every character of parts of the
996
+ /// document that are parsed as inline content.
997
+ export interface InlineParser {
998
+ /// This parser's name, which can be used by other parsers to
999
+ /// [indicate](#InlineParser.before) a relative precedence.
1000
+ name: string
1001
+ /// The parse function. Gets the next character and its position as
1002
+ /// arguments. Should return -1 if it doesn't handle the character,
1003
+ /// or add some [element](#InlineContext.addElement) or
1004
+ /// [delimiter](#InlineContext.addDelimiter) and return the end
1005
+ /// position of the content it parsed if it can.
1006
+ parse(cx: InlineContext, next: number, pos: number): number
1007
+ /// When given, this parser will be installed directly before the
1008
+ /// parser with the given name. The default configuration defines
1009
+ /// inline parsers with names Escape, Entity, InlineCode, HTMLTag,
1010
+ /// Emphasis, HardBreak, Link, and Image. When no `before` or
1011
+ /// `after` property is given, the parser is added to the end of the
1012
+ /// list.
1013
+ before?: string
1014
+ /// When given, the parser will be installed directly _after_ the
1015
+ /// parser with the given name.
1016
+ after?: string
1017
+ }
1018
+
1019
+ /// Block parsers handle block-level structure. There are three
1020
+ /// general types of block parsers:
1021
+ ///
1022
+ /// - Composite block parsers, which handle things like lists and
1023
+ /// blockquotes. These define a [`parse`](#BlockParser.parse) method
1024
+ /// that [starts](#BlockContext.startComposite) a composite block
1025
+ /// and returns null when it recognizes its syntax. The node type
1026
+ /// used by such a block must define a
1027
+ /// [`composite`](#NodeSpec.composite) function as well.
1028
+ ///
1029
+ /// - Eager leaf block parsers, used for things like code or HTML
1030
+ /// blocks. These can unambiguously recognize their content from its
1031
+ /// first line. They define a [`parse`](#BlockParser.parse) method
1032
+ /// that, if it recognizes the construct,
1033
+ /// [moves](#BlockContext.nextLine) the current line forward to the
1034
+ /// line beyond the end of the block,
1035
+ /// [add](#BlockContext.addElement) a syntax node for the block, and
1036
+ /// return true.
1037
+ ///
1038
+ /// - Leaf block parsers that observe a paragraph-like construct as it
1039
+ /// comes in, and optionally decide to handle it at some point. This
1040
+ /// is used for "setext" (underlined) headings and link references.
1041
+ /// These define a [`leaf`](#BlockParser.leaf) method that checks
1042
+ /// the first line of the block and returns a
1043
+ /// [`LeafBlockParser`](#LeafBlockParser) object if it wants to
1044
+ /// observe that block.
1045
+ export interface BlockParser {
1046
+ /// The name of the parser. Can be used by other block parsers to
1047
+ /// [specify](#BlockParser.before) precedence.
1048
+ name: string
1049
+ /// The eager parse function, which can look at the block's first
1050
+ /// line and return `false` to do nothing, `true` if it has parsed
1051
+ /// (and [moved past](#BlockContext.nextLine) a block), or `null` if
1052
+ /// it has [started](#BlockContext.startComposite) a composite block.
1053
+ parse?(cx: BlockContext, line: Line): BlockResult
1054
+ /// A leaf parse function. If no [regular](#BlockParser.parse) parse
1055
+ /// functions match for a given line, its content will be
1056
+ /// accumulated for a paragraph-style block. This method can return
1057
+ /// an [object](#LeafBlockParser) that overrides that style of
1058
+ /// parsing in some situations.
1059
+ leaf?(cx: BlockContext, leaf: LeafBlock): LeafBlockParser | null
1060
+ /// Some constructs, such as code blocks or newly started
1061
+ /// blockquotes, can interrupt paragraphs even without a blank line.
1062
+ /// If your construct can do this, provide a predicate here that
1063
+ /// recognizes lines that should end a paragraph (or other non-eager
1064
+ /// [leaf block](#BlockParser.leaf)).
1065
+ endLeaf?(cx: BlockContext, line: Line, leaf: LeafBlock): boolean
1066
+ /// When given, this parser will be installed directly before the
1067
+ /// block parser with the given name. The default configuration
1068
+ /// defines block parsers with names LinkReference, IndentedCode,
1069
+ /// FencedCode, Blockquote, HorizontalRule, BulletList, OrderedList,
1070
+ /// ATXHeading, HTMLBlock, and SetextHeading.
1071
+ before?: string
1072
+ /// When given, the parser will be installed directly _after_ the
1073
+ /// parser with the given name.
1074
+ after?: string
1075
+ }
1076
+
1077
+ /// Objects that are used to [override](#BlockParser.leaf)
1078
+ /// paragraph-style blocks should conform to this interface.
1079
+ export interface LeafBlockParser {
1080
+ /// Update the parser's state for the next line, and optionally
1081
+ /// finish the block. This is not called for the first line (the
1082
+ /// object is constructed at that line), but for any further lines.
1083
+ /// When it returns `true`, the block is finished. It is okay for
1084
+ /// the function to [consume](#BlockContext.nextLine) the current
1085
+ /// line or any subsequent lines when returning true.
1086
+ nextLine(cx: BlockContext, line: Line, leaf: LeafBlock): boolean
1087
+ /// Called when the block is finished by external circumstances
1088
+ /// (such as a blank line or the [start](#BlockParser.endLeaf) of
1089
+ /// another construct). If this parser can handle the block up to
1090
+ /// its current position, it should
1091
+ /// [finish](#BlockContext.addLeafElement) the block and return
1092
+ /// true.
1093
+ finish(cx: BlockContext, leaf: LeafBlock): boolean
1094
+ }
1095
+
1096
+ /// Objects of this type are used to
1097
+ /// [configure](#MarkdownParser.configure) the Markdown parser.
1098
+ export interface MarkdownConfig {
1099
+ /// Node props to add to the parser's node set.
1100
+ props?: readonly NodePropSource[]
1101
+ /// Define new [node types](#NodeSpec) for use in parser extensions.
1102
+ defineNodes?: readonly (string | NodeSpec)[]
1103
+ /// Define additional [block parsing](#BlockParser) logic.
1104
+ parseBlock?: readonly BlockParser[]
1105
+ /// Define new [inline parsing](#InlineParser) logic.
1106
+ parseInline?: readonly InlineParser[]
1107
+ /// Remove the named parsers from the configuration.
1108
+ remove?: readonly string[]
1109
+ /// Add a parse wrapper (such as a [mixed-language
1110
+ /// parser](#common.parseMixed)) to this parser.
1111
+ wrap?: ParseWrapper
1112
+ }
1113
+
1114
+ /// To make it possible to group extensions together into bigger
1115
+ /// extensions (such as the [Github-flavored Markdown](#GFM)
1116
+ /// extension), [reconfiguration](#MarkdownParser.configure) accepts
1117
+ /// nested arrays of [config](#MarkdownConfig) objects.
1118
+ export type MarkdownExtension = MarkdownConfig | readonly MarkdownExtension[]
1119
+
1120
+ /// A Markdown parser configuration.
1121
+ export class MarkdownParser extends Parser {
1122
+ /// @internal
1123
+ nodeTypes: {[name: string]: number} = Object.create(null)
1124
+
1125
+ /// @internal
1126
+ constructor(
1127
+ /// The parser's syntax [node
1128
+ /// types](https://lezer.codemirror.net/docs/ref/#common.NodeSet).
1129
+ readonly nodeSet: NodeSet,
1130
+ /// @internal
1131
+ readonly blockParsers: readonly (((cx: BlockContext, line: Line) => BlockResult) | undefined)[],
1132
+ /// @internal
1133
+ readonly leafBlockParsers: readonly (((cx: BlockContext, leaf: LeafBlock) => LeafBlockParser | null) | undefined)[],
1134
+ /// @internal
1135
+ readonly blockNames: readonly string[],
1136
+ /// @internal
1137
+ readonly endLeafBlock: readonly ((cx: BlockContext, line: Line, leaf: LeafBlock) => boolean)[],
1138
+ /// @internal
1139
+ readonly skipContextMarkup: {readonly [type: number]: (bl: CompositeBlock, cx: BlockContext, line: Line) => boolean},
1140
+ /// @internal
1141
+ readonly inlineParsers: readonly (((cx: InlineContext, next: number, pos: number) => number) | undefined)[],
1142
+ /// @internal
1143
+ readonly inlineNames: readonly string[],
1144
+ /// @internal
1145
+ readonly wrappers: readonly ParseWrapper[]
1146
+ ) {
1147
+ super()
1148
+ for (let t of nodeSet.types) this.nodeTypes[t.name] = t.id
1149
+ }
1150
+
1151
+ createParse(input: Input, fragments: readonly TreeFragment[], ranges: readonly {from: number, to: number}[]): PartialParse {
1152
+ let parse: PartialParse = new BlockContext(this, input, fragments, ranges)
1153
+ for (let w of this.wrappers) parse = w(parse, input, fragments, ranges)
1154
+ return parse
1155
+ }
1156
+
1157
+ /// Reconfigure the parser.
1158
+ configure(spec: MarkdownExtension) {
1159
+ let config = resolveConfig(spec)
1160
+ if (!config) return this
1161
+ let {nodeSet, skipContextMarkup} = this
1162
+ let blockParsers = this.blockParsers.slice(), leafBlockParsers = this.leafBlockParsers.slice(),
1163
+ blockNames = this.blockNames.slice(), inlineParsers = this.inlineParsers.slice(),
1164
+ inlineNames = this.inlineNames.slice(), endLeafBlock = this.endLeafBlock.slice(),
1165
+ wrappers = this.wrappers
1166
+
1167
+ if (nonEmpty(config.defineNodes)) {
1168
+ skipContextMarkup = Object.assign({}, skipContextMarkup)
1169
+ let nodeTypes = nodeSet.types.slice(), styles: {[selector: string]: Tag | readonly Tag[]} | undefined
1170
+ for (let s of config.defineNodes) {
1171
+ let {name, block, composite, style} = typeof s == "string" ? {name: s} as NodeSpec : s
1172
+ if (nodeTypes.some(t => t.name == name)) continue
1173
+ if (composite) (skipContextMarkup as any)[nodeTypes.length] =
1174
+ (bl: CompositeBlock, cx: BlockContext, line: Line) => composite!(cx, line, bl.value)
1175
+ let id = nodeTypes.length
1176
+ let group = composite ? ["Block", "BlockContext"] : !block ? undefined
1177
+ : id >= Type.ATXHeading1 && id <= Type.SetextHeading2 ? ["Block", "LeafBlock", "Heading"] : ["Block", "LeafBlock"]
1178
+ nodeTypes.push(NodeType.define({
1179
+ id,
1180
+ name,
1181
+ props: group && [[NodeProp.group, group]]
1182
+ }))
1183
+ if (style) {
1184
+ if (!styles) styles = {}
1185
+ if (Array.isArray(style) || style instanceof Tag) styles[name] = style
1186
+ else Object.assign(styles, style)
1187
+ }
1188
+ }
1189
+ nodeSet = new NodeSet(nodeTypes)
1190
+ if (styles) nodeSet = nodeSet.extend(styleTags(styles))
1191
+ }
1192
+
1193
+ if (nonEmpty(config.props)) nodeSet = nodeSet.extend(...config.props)
1194
+
1195
+ if (nonEmpty(config.remove)) {
1196
+ for (let rm of config.remove) {
1197
+ let block = this.blockNames.indexOf(rm), inline = this.inlineNames.indexOf(rm)
1198
+ if (block > -1) blockParsers[block] = leafBlockParsers[block] = undefined
1199
+ if (inline > -1) inlineParsers[inline] = undefined
1200
+ }
1201
+ }
1202
+
1203
+ if (nonEmpty(config.parseBlock)) {
1204
+ for (let spec of config.parseBlock) {
1205
+ let found = blockNames.indexOf(spec.name)
1206
+ if (found > -1) {
1207
+ blockParsers[found] = spec.parse
1208
+ leafBlockParsers[found] = spec.leaf
1209
+ } else {
1210
+ let pos = spec.before ? findName(blockNames, spec.before)
1211
+ : spec.after ? findName(blockNames, spec.after) + 1 : blockNames.length - 1
1212
+ blockParsers.splice(pos, 0, spec.parse)
1213
+ leafBlockParsers.splice(pos, 0, spec.leaf)
1214
+ blockNames.splice(pos, 0, spec.name)
1215
+ }
1216
+ if (spec.endLeaf) endLeafBlock.push(spec.endLeaf)
1217
+ }
1218
+ }
1219
+
1220
+ if (nonEmpty(config.parseInline)) {
1221
+ for (let spec of config.parseInline) {
1222
+ let found = inlineNames.indexOf(spec.name)
1223
+ if (found > -1) {
1224
+ inlineParsers[found] = spec.parse
1225
+ } else {
1226
+ let pos = spec.before ? findName(inlineNames, spec.before)
1227
+ : spec.after ? findName(inlineNames, spec.after) + 1 : inlineNames.length - 1
1228
+ inlineParsers.splice(pos, 0, spec.parse)
1229
+ inlineNames.splice(pos, 0, spec.name)
1230
+ }
1231
+ }
1232
+ }
1233
+
1234
+ if (config.wrap) wrappers = wrappers.concat(config.wrap)
1235
+
1236
+ return new MarkdownParser(nodeSet,
1237
+ blockParsers, leafBlockParsers, blockNames,
1238
+ endLeafBlock, skipContextMarkup,
1239
+ inlineParsers, inlineNames, wrappers)
1240
+ }
1241
+
1242
+ /// @internal
1243
+ getNodeType(name: string) {
1244
+ let found = this.nodeTypes[name]
1245
+ if (found == null) throw new RangeError(`Unknown node type '${name}'`)
1246
+ return found
1247
+ }
1248
+
1249
+ /// Parse the given piece of inline text at the given offset,
1250
+ /// returning an array of [`Element`](#Element) objects representing
1251
+ /// the inline content.
1252
+ parseInline(text: string, offset: number) {
1253
+ let cx = new InlineContext(this, text, offset)
1254
+ outer: for (let pos = offset; pos < cx.end;) {
1255
+ let next = cx.char(pos)
1256
+ for (let token of this.inlineParsers) if (token) {
1257
+ let result = token(cx, next, pos)
1258
+ if (result >= 0) { pos = result; continue outer }
1259
+ }
1260
+ pos++
1261
+ }
1262
+ return cx.resolveMarkers(0)
1263
+ }
1264
+ }
1265
+
1266
+ function nonEmpty<T>(a: undefined | readonly T[]): a is readonly T[] {
1267
+ return a != null && a.length > 0
1268
+ }
1269
+
1270
+ function resolveConfig(spec: MarkdownExtension): MarkdownConfig | null {
1271
+ if (!Array.isArray(spec)) return spec as MarkdownConfig
1272
+ if (spec.length == 0) return null
1273
+ let conf = resolveConfig(spec[0])
1274
+ if (spec.length == 1) return conf
1275
+ let rest = resolveConfig(spec.slice(1))
1276
+ if (!rest || !conf) return conf || rest
1277
+ let conc: <T>(a: readonly T[] | undefined, b: readonly T[] | undefined) => readonly T[] =
1278
+ (a, b) => (a || none).concat(b || none)
1279
+ let wrapA = conf.wrap, wrapB = rest.wrap
1280
+ return {
1281
+ props: conc(conf.props, rest.props),
1282
+ defineNodes: conc(conf.defineNodes, rest.defineNodes),
1283
+ parseBlock: conc(conf.parseBlock, rest.parseBlock),
1284
+ parseInline: conc(conf.parseInline, rest.parseInline),
1285
+ remove: conc(conf.remove, rest.remove),
1286
+ wrap: !wrapA ? wrapB : !wrapB ? wrapA :
1287
+ (inner, input, fragments, ranges) => wrapA!(wrapB!(inner, input, fragments, ranges), input, fragments, ranges)
1288
+ }
1289
+ }
1290
+
1291
+ function findName(names: readonly string[], name: string) {
1292
+ let found = names.indexOf(name)
1293
+ if (found < 0) throw new RangeError(`Position specified relative to unknown parser ${name}`)
1294
+ return found
1295
+ }
1296
+
1297
+ let nodeTypes = [NodeType.none]
1298
+ for (let i = 1, name; name = Type[i]; i++) {
1299
+ nodeTypes[i] = NodeType.define({
1300
+ id: i,
1301
+ name,
1302
+ props: i >= Type.Escape ? [] : [[NodeProp.group, i in DefaultSkipMarkup ? ["Block", "BlockContext"] : ["Block", "LeafBlock"]]],
1303
+ top: name == "Document"
1304
+ })
1305
+ }
1306
+
1307
+ const none: readonly any[] = []
1308
+
1309
+ class Buffer {
1310
+ content: number[] = []
1311
+ nodes: Tree[] = []
1312
+ constructor(readonly nodeSet: NodeSet) {}
1313
+
1314
+ write(type: Type, from: number, to: number, children = 0) {
1315
+ this.content.push(type, from, to, 4 + children * 4)
1316
+ return this
1317
+ }
1318
+
1319
+ writeElements(elts: readonly (Element | TreeElement)[], offset = 0) {
1320
+ for (let e of elts) e.writeTo(this, offset)
1321
+ return this
1322
+ }
1323
+
1324
+ finish(type: Type, length: number) {
1325
+ return Tree.build({
1326
+ buffer: this.content,
1327
+ nodeSet: this.nodeSet,
1328
+ reused: this.nodes,
1329
+ topID: type,
1330
+ length
1331
+ })
1332
+ }
1333
+ }
1334
+
1335
+ /// Elements are used to compose syntax nodes during parsing.
1336
+ export class Element {
1337
+ /// @internal
1338
+ constructor(
1339
+ /// The node's
1340
+ /// [id](https://lezer.codemirror.net/docs/ref/#common.NodeType.id).
1341
+ readonly type: number,
1342
+ /// The start of the node, as an offset from the start of the document.
1343
+ readonly from: number,
1344
+ /// The end of the node.
1345
+ readonly to: number,
1346
+ /// The node's child nodes @internal
1347
+ readonly children: readonly (Element | TreeElement)[] = none
1348
+ ) {}
1349
+
1350
+ /// @internal
1351
+ writeTo(buf: Buffer, offset: number) {
1352
+ let startOff = buf.content.length
1353
+ buf.writeElements(this.children, offset)
1354
+ buf.content.push(this.type, this.from + offset, this.to + offset, buf.content.length + 4 - startOff)
1355
+ }
1356
+
1357
+ /// @internal
1358
+ toTree(nodeSet: NodeSet): Tree {
1359
+ return new Buffer(nodeSet).writeElements(this.children, -this.from).finish(this.type, this.to - this.from)
1360
+ }
1361
+ }
1362
+
1363
+ class TreeElement {
1364
+ constructor(readonly tree: Tree, readonly from: number) {}
1365
+
1366
+ get to() { return this.from + this.tree.length }
1367
+
1368
+ get type() { return this.tree.type.id }
1369
+
1370
+ get children() { return none }
1371
+
1372
+ writeTo(buf: Buffer, offset: number) {
1373
+ buf.nodes.push(this.tree)
1374
+ buf.content.push(buf.nodes.length - 1, this.from + offset, this.to + offset, -1)
1375
+ }
1376
+
1377
+ toTree(): Tree { return this.tree }
1378
+ }
1379
+
1380
+ function elt(type: Type, from: number, to: number, children?: readonly (Element | TreeElement)[]) {
1381
+ return new Element(type, from, to, children)
1382
+ }
1383
+
1384
+ const enum Mark { None = 0, Open = 1, Close = 2 }
1385
+
1386
+ /// Delimiters are used during inline parsing to store the positions
1387
+ /// of things that _might_ be delimiters, if another matching
1388
+ /// delimiter is found. They are identified by objects with these
1389
+ /// properties.
1390
+ export interface DelimiterType {
1391
+ /// If this is given, the delimiter should be matched automatically
1392
+ /// when a piece of inline content is finished. Such delimiters will
1393
+ /// be matched with delimiters of the same type according to their
1394
+ /// [open and close](#InlineContext.addDelimiter) properties. When a
1395
+ /// match is found, the content between the delimiters is wrapped in
1396
+ /// a node whose name is given by the value of this property.
1397
+ ///
1398
+ /// When this isn't given, you need to match the delimiter eagerly
1399
+ /// using the [`findOpeningDelimiter`](#InlineContext.findOpeningDelimiter)
1400
+ /// and [`takeContent`](#InlineContext.takeContent) methods.
1401
+ resolve?: string
1402
+ /// If the delimiter itself should, when matched, create a syntax
1403
+ /// node, set this to the name of the syntax node.
1404
+ mark?: string
1405
+ }
1406
+
1407
+ const EmphasisUnderscore: DelimiterType = {resolve: "Emphasis", mark: "EmphasisMark"}
1408
+ const EmphasisAsterisk: DelimiterType = {resolve: "Emphasis", mark: "EmphasisMark"}
1409
+ const LinkStart: DelimiterType = {}, ImageStart: DelimiterType = {}
1410
+
1411
+ class InlineDelimiter {
1412
+ constructor(readonly type: DelimiterType,
1413
+ readonly from: number,
1414
+ readonly to: number,
1415
+ public side: Mark) {}
1416
+ }
1417
+
1418
+ const Escapable = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
1419
+
1420
+ export let Punctuation = /[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\u2010-\u2027]/
1421
+ try { Punctuation = new RegExp("[\\p{S}|\\p{P}]", "u") } catch (_) {}
1422
+
1423
+ const DefaultInline: {[name: string]: (cx: InlineContext, next: number, pos: number) => number} = {
1424
+ Escape(cx, next, start) {
1425
+ if (next != 92 /* '\\' */ || start == cx.end - 1) return -1
1426
+ let escaped = cx.char(start + 1)
1427
+ for (let i = 0; i < Escapable.length; i++) if (Escapable.charCodeAt(i) == escaped)
1428
+ return cx.append(elt(Type.Escape, start, start + 2))
1429
+ return -1
1430
+ },
1431
+
1432
+ Entity(cx, next, start) {
1433
+ if (next != 38 /* '&' */) return -1
1434
+ let m = /^(?:#\d+|#x[a-f\d]+|\w+);/i.exec(cx.slice(start + 1, start + 31))
1435
+ return m ? cx.append(elt(Type.Entity, start, start + 1 + m[0].length)) : -1
1436
+ },
1437
+
1438
+ InlineCode(cx, next, start) {
1439
+ if (next != 96 /* '`' */ || start && cx.char(start - 1) == 96) return -1
1440
+ let pos = start + 1
1441
+ while (pos < cx.end && cx.char(pos) == 96) pos++
1442
+ let size = pos - start, curSize = 0
1443
+ for (; pos < cx.end; pos++) {
1444
+ if (cx.char(pos) == 96) {
1445
+ curSize++
1446
+ if (curSize == size && cx.char(pos + 1) != 96)
1447
+ return cx.append(elt(Type.InlineCode, start, pos + 1, [
1448
+ elt(Type.CodeMark, start, start + size),
1449
+ elt(Type.CodeMark, pos + 1 - size, pos + 1)
1450
+ ]))
1451
+ } else {
1452
+ curSize = 0
1453
+ }
1454
+ }
1455
+ return -1
1456
+ },
1457
+
1458
+ HTMLTag(cx, next, start) { // or URL
1459
+ if (next != 60 /* '<' */ || start == cx.end - 1) return -1
1460
+ let after = cx.slice(start + 1, cx.end)
1461
+ let url = /^(?:[a-z][-\w+.]+:[^\s>]+|[a-z\d.!#$%&'*+/=?^_`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*)>/i.exec(after)
1462
+ if (url) {
1463
+ return cx.append(elt(Type.Autolink, start, start + 1 + url[0].length, [
1464
+ elt(Type.LinkMark, start, start + 1),
1465
+ // url[0] includes the closing bracket, so exclude it from this slice
1466
+ elt(Type.URL, start + 1, start + url[0].length),
1467
+ elt(Type.LinkMark, start + url[0].length, start + 1 + url[0].length)
1468
+ ]))
1469
+ }
1470
+ let comment = /^!--[^>](?:-[^-]|[^-])*?-->/i.exec(after)
1471
+ if (comment) return cx.append(elt(Type.Comment, start, start + 1 + comment[0].length))
1472
+ let procInst = /^\?[^]*?\?>/.exec(after)
1473
+ if (procInst) return cx.append(elt(Type.ProcessingInstruction, start, start + 1 + procInst[0].length))
1474
+ let m = /^(?:![A-Z][^]*?>|!\[CDATA\[[^]*?\]\]>|\/\s*[a-zA-Z][\w-]*\s*>|\s*[a-zA-Z][\w-]*(\s+[a-zA-Z:_][\w-.:]*(?:\s*=\s*(?:[^\s"'=<>`]+|'[^']*'|"[^"]*"))?)*\s*(\/\s*)?>)/.exec(after)
1475
+ if (!m) return -1
1476
+ return cx.append(elt(Type.HTMLTag, start, start + 1 + m[0].length))
1477
+ },
1478
+
1479
+ Emphasis(cx, next, start) {
1480
+ if (next != 47 && next != 42) return -1
1481
+ let pos = start + 1
1482
+ while (cx.char(pos) == next) pos++
1483
+ let before = cx.slice(start - 1, start), after = cx.slice(pos, pos + 1)
1484
+ let pBefore = Punctuation.test(before), pAfter = Punctuation.test(after)
1485
+ let sBefore = /\s|^$/.test(before), sAfter = /\s|^$/.test(after)
1486
+ let leftFlanking = !sAfter && (!pAfter || sBefore || pBefore)
1487
+ let rightFlanking = !sBefore && (!pBefore || sAfter || pAfter)
1488
+ let canOpen = leftFlanking && (next == 42 || !rightFlanking || pBefore)
1489
+ let canClose = rightFlanking && (next == 42 || !leftFlanking || pAfter)
1490
+ // 95 = _ 5F
1491
+ // 47 = / 2F
1492
+ // 42 = * 2A
1493
+ // 35 = # 23
1494
+ // 61 = = 3D
1495
+ return cx.append(new InlineDelimiter(next == 47 ? EmphasisUnderscore : EmphasisAsterisk, start, pos,
1496
+ (canOpen ? Mark.Open : Mark.None) | (canClose ? Mark.Close : Mark.None)))
1497
+ },
1498
+
1499
+ HardBreak(cx, next, start) {
1500
+ if (next == 92 /* '\\' */ && cx.char(start + 1) == 10 /* '\n' */)
1501
+ return cx.append(elt(Type.HardBreak, start, start + 2))
1502
+ if (next == 32) {
1503
+ let pos = start + 1
1504
+ while (cx.char(pos) == 32) pos++
1505
+ if (cx.char(pos) == 10 && pos >= start + 2)
1506
+ return cx.append(elt(Type.HardBreak, start, pos + 1))
1507
+ }
1508
+ return -1
1509
+ },
1510
+
1511
+ Link(cx, next, start) {
1512
+ return next == 91 /* '[' */ ? cx.append(new InlineDelimiter(LinkStart, start, start + 1, Mark.Open)) : -1
1513
+ },
1514
+
1515
+ Image(cx, next, start) {
1516
+ return next == 33 /* '!' */ && cx.char(start + 1) == 91 /* '[' */
1517
+ ? cx.append(new InlineDelimiter(ImageStart, start, start + 2, Mark.Open)) : -1
1518
+ },
1519
+
1520
+ LinkEnd(cx, next, start) {
1521
+ if (next != 93 /* ']' */) return -1
1522
+ // Scanning back to the next link/image start marker
1523
+ for (let i = cx.parts.length - 1; i >= 0; i--) {
1524
+ let part = cx.parts[i]
1525
+ if (part instanceof InlineDelimiter && (part.type == LinkStart || part.type == ImageStart)) {
1526
+ // If this one has been set invalid (because it would produce
1527
+ // a nested link) or there's no valid link here ignore both.
1528
+ if (!part.side || cx.skipSpace(part.to) == start && !/[(\[]/.test(cx.slice(start + 1, start + 2))) {
1529
+ cx.parts[i] = null
1530
+ return -1
1531
+ }
1532
+ // Finish the content and replace the entire range in
1533
+ // this.parts with the link/image node.
1534
+ let content = cx.takeContent(i)
1535
+ let link = cx.parts[i] = finishLink(cx, content, part.type == LinkStart ? Type.Link : Type.Image, part.from, start + 1)
1536
+ // Set any open-link markers before this link to invalid.
1537
+ if (part.type == LinkStart) for (let j = 0; j < i; j++) {
1538
+ let p = cx.parts[j]
1539
+ if (p instanceof InlineDelimiter && p.type == LinkStart) p.side = Mark.None
1540
+ }
1541
+ return link.to
1542
+ }
1543
+ }
1544
+ return -1
1545
+ }
1546
+ }
1547
+
1548
+ function finishLink(cx: InlineContext, content: Element[], type: Type, start: number, startPos: number) {
1549
+ let {text} = cx, next = cx.char(startPos), endPos = startPos
1550
+ content.unshift(elt(Type.LinkMark, start, start + (type == Type.Image ? 2 : 1)))
1551
+ content.push(elt(Type.LinkMark, startPos - 1, startPos))
1552
+ if (next == 40 /* '(' */) {
1553
+ let pos = cx.skipSpace(startPos + 1)
1554
+ let dest = parseURL(text, pos - cx.offset, cx.offset), title
1555
+ if (dest) {
1556
+ pos = cx.skipSpace(dest.to)
1557
+ // The destination and title must be separated by whitespace
1558
+ if (pos != dest.to) {
1559
+ title = parseLinkTitle(text, pos - cx.offset, cx.offset)
1560
+ if (title) pos = cx.skipSpace(title.to)
1561
+ }
1562
+ }
1563
+ if (cx.char(pos) == 41 /* ')' */) {
1564
+ content.push(elt(Type.LinkMark, startPos, startPos + 1))
1565
+ endPos = pos + 1
1566
+ if (dest) content.push(dest)
1567
+ if (title) content.push(title)
1568
+ content.push(elt(Type.LinkMark, pos, endPos))
1569
+ }
1570
+ } else if (next == 91 /* '[' */) {
1571
+ let label = parseLinkLabel(text, startPos - cx.offset, cx.offset, false)
1572
+ if (label) {
1573
+ content.push(label)
1574
+ endPos = label.to
1575
+ }
1576
+ }
1577
+ return elt(type, start, endPos, content)
1578
+ }
1579
+
1580
+ // These return `null` when falling off the end of the input, `false`
1581
+ // when parsing fails otherwise (for use in the incremental link
1582
+ // reference parser).
1583
+
1584
+ function parseURL(text: string, start: number, offset: number): null | false | Element {
1585
+ let next = text.charCodeAt(start)
1586
+ if (next == 60 /* '<' */) {
1587
+ for (let pos = start + 1; pos < text.length; pos++) {
1588
+ let ch = text.charCodeAt(pos)
1589
+ if (ch == 62 /* '>' */) return elt(Type.URL, start + offset, pos + 1 + offset)
1590
+ if (ch == 60 || ch == 10 /* '<\n' */) return false
1591
+ }
1592
+ return null
1593
+ } else {
1594
+ let depth = 0, pos = start
1595
+ for (let escaped = false; pos < text.length; pos++) {
1596
+ let ch = text.charCodeAt(pos)
1597
+ if (space(ch)) {
1598
+ break
1599
+ } else if (escaped) {
1600
+ escaped = false
1601
+ } else if (ch == 40 /* '(' */) {
1602
+ depth++
1603
+ } else if (ch == 41 /* ')' */) {
1604
+ if (!depth) break
1605
+ depth--
1606
+ } else if (ch == 92 /* '\\' */) {
1607
+ escaped = true
1608
+ }
1609
+ }
1610
+ return pos > start ? elt(Type.URL, start + offset, pos + offset) : pos == text.length ? null : false
1611
+ }
1612
+ }
1613
+
1614
+ function parseLinkTitle(text: string, start: number, offset: number): null | false | Element {
1615
+ let next = text.charCodeAt(start)
1616
+ if (next != 39 && next != 34 && next != 40 /* '"\'(' */) return false
1617
+ let end = next == 40 ? 41 : next
1618
+ for (let pos = start + 1, escaped = false; pos < text.length; pos++) {
1619
+ let ch = text.charCodeAt(pos)
1620
+ if (escaped) escaped = false
1621
+ else if (ch == end) return elt(Type.LinkTitle, start + offset, pos + 1 + offset)
1622
+ else if (ch == 92 /* '\\' */) escaped = true
1623
+ }
1624
+ return null
1625
+ }
1626
+
1627
+ function parseLinkLabel(text: string, start: number, offset: number, requireNonWS: boolean): null | false | Element {
1628
+ for (let escaped = false, pos = start + 1, end = Math.min(text.length, pos + 999); pos < end; pos++) {
1629
+ let ch = text.charCodeAt(pos)
1630
+ if (escaped) escaped = false
1631
+ else if (ch == 93 /* ']' */) return requireNonWS ? false : elt(Type.LinkLabel, start + offset, pos + 1 + offset)
1632
+ else {
1633
+ if (requireNonWS && !space(ch)) requireNonWS = false
1634
+ if (ch == 91 /* '[' */) return false
1635
+ else if (ch == 92 /* '\\' */) escaped = true
1636
+ }
1637
+ }
1638
+ return null
1639
+ }
1640
+
1641
+ /// Inline parsing functions get access to this context, and use it to
1642
+ /// read the content and emit syntax nodes.
1643
+ export class InlineContext {
1644
+ /// @internal
1645
+ parts: (Element | InlineDelimiter | null)[] = []
1646
+
1647
+ /// @internal
1648
+ constructor(
1649
+ /// The parser that is being used.
1650
+ readonly parser: MarkdownParser,
1651
+ /// The text of this inline section.
1652
+ readonly text: string,
1653
+ /// The starting offset of the section in the document.
1654
+ readonly offset: number
1655
+ ) {}
1656
+
1657
+ /// Get the character code at the given (document-relative)
1658
+ /// position.
1659
+ char(pos: number) { return pos >= this.end ? -1 : this.text.charCodeAt(pos - this.offset) }
1660
+
1661
+ /// The position of the end of this inline section.
1662
+ get end() { return this.offset + this.text.length }
1663
+
1664
+ /// Get a substring of this inline section. Again uses
1665
+ /// document-relative positions.
1666
+ slice(from: number, to: number) { return this.text.slice(from - this.offset, to - this.offset) }
1667
+
1668
+ /// @internal
1669
+ append(elt: Element | InlineDelimiter) {
1670
+ this.parts.push(elt)
1671
+ return elt.to
1672
+ }
1673
+
1674
+ /// Add a [delimiter](#DelimiterType) at this given position. `open`
1675
+ /// and `close` indicate whether this delimiter is opening, closing,
1676
+ /// or both. Returns the end of the delimiter, for convenient
1677
+ /// returning from [parse functions](#InlineParser.parse).
1678
+ addDelimiter(type: DelimiterType, from: number, to: number, open: boolean, close: boolean) {
1679
+ return this.append(new InlineDelimiter(type, from, to, (open ? Mark.Open : Mark.None) | (close ? Mark.Close : Mark.None)))
1680
+ }
1681
+
1682
+ /// Returns true when there is an unmatched link or image opening
1683
+ /// token before the current position.
1684
+ get hasOpenLink() {
1685
+ for (let i = this.parts.length - 1; i >= 0; i--) {
1686
+ let part = this.parts[i]
1687
+ if (part instanceof InlineDelimiter && (part.type == LinkStart || part.type == ImageStart)) return true
1688
+ }
1689
+ return false
1690
+ }
1691
+
1692
+ /// Add an inline element. Returns the end of the element.
1693
+ addElement(elt: Element) {
1694
+ return this.append(elt)
1695
+ }
1696
+
1697
+ /// Resolve markers between this.parts.length and from, wrapping matched markers in the
1698
+ /// appropriate node and updating the content of this.parts. @internal
1699
+ resolveMarkers(from: number) {
1700
+ // Scan forward, looking for closing tokens
1701
+ for (let i = from; i < this.parts.length; i++) {
1702
+ let close = this.parts[i]
1703
+ if (!(close instanceof InlineDelimiter && close.type.resolve && (close.side & Mark.Close))) continue
1704
+
1705
+ let emp = close.type == EmphasisUnderscore || close.type == EmphasisAsterisk
1706
+ let closeSize = close.to - close.from
1707
+ let open: InlineDelimiter | undefined, j = i - 1
1708
+ // Continue scanning for a matching opening token
1709
+ for (; j >= from; j--) {
1710
+ let part = this.parts[j]
1711
+ if (part instanceof InlineDelimiter && (part.side & Mark.Open) && part.type == close.type &&
1712
+ // Ignore emphasis delimiters where the character count doesn't match
1713
+ !(emp && ((close.side & Mark.Open) || (part.side & Mark.Close)) &&
1714
+ (part.to - part.from + closeSize) % 3 == 0 && ((part.to - part.from) % 3 || closeSize % 3))) {
1715
+ open = part
1716
+ break
1717
+ }
1718
+ }
1719
+ if (!open) continue
1720
+
1721
+ let type = close.type.resolve, content = []
1722
+ let start = open.from, end = close.to
1723
+ // Emphasis marker effect depends on the character count. Size consumed is minimum of the two
1724
+ // markers.
1725
+ if (emp) {
1726
+ let size = Math.min(2, open.to - open.from, closeSize)
1727
+ start = open.to - size
1728
+ end = close.from + size
1729
+ type = size == 1 ? "Emphasis" : "StrongEmphasis"
1730
+ }
1731
+ // Move the covered region into content, optionally adding marker nodes
1732
+ if (open.type.mark) content.push(this.elt(open.type.mark, start, open.to))
1733
+ for (let k = j + 1; k < i; k++) {
1734
+ if (this.parts[k] instanceof Element) content.push(this.parts[k] as Element)
1735
+ this.parts[k] = null
1736
+ }
1737
+ if (close.type.mark) content.push(this.elt(close.type.mark, close.from, end))
1738
+ let element = this.elt(type, start, end, content)
1739
+ // If there are leftover emphasis marker characters, shrink the close/open markers. Otherwise, clear them.
1740
+ this.parts[j] = emp && open.from != start ? new InlineDelimiter(open.type, open.from, start, open.side) : null
1741
+ let keep = this.parts[i] = emp && close.to != end ? new InlineDelimiter(close.type, end, close.to, close.side) : null
1742
+ // Insert the new element in this.parts
1743
+ if (keep) this.parts.splice(i, 0, element)
1744
+ else this.parts[i] = element
1745
+ }
1746
+
1747
+ // Collect the elements remaining in this.parts into an array.
1748
+ let result = []
1749
+ for (let i = from; i < this.parts.length; i++) {
1750
+ let part = this.parts[i]
1751
+ if (part instanceof Element) result.push(part)
1752
+ }
1753
+ return result
1754
+ }
1755
+
1756
+ /// Find an opening delimiter of the given type. Returns `null` if
1757
+ /// no delimiter is found, or an index that can be passed to
1758
+ /// [`takeContent`](#InlineContext.takeContent) otherwise.
1759
+ findOpeningDelimiter(type: DelimiterType) {
1760
+ for (let i = this.parts.length - 1; i >= 0; i--) {
1761
+ let part = this.parts[i]
1762
+ if (part instanceof InlineDelimiter && part.type == type && (part.side & Mark.Open)) return i
1763
+ }
1764
+ return null
1765
+ }
1766
+
1767
+ /// Remove all inline elements and delimiters starting from the
1768
+ /// given index (which you should get from
1769
+ /// [`findOpeningDelimiter`](#InlineContext.findOpeningDelimiter),
1770
+ /// resolve delimiters inside of them, and return them as an array
1771
+ /// of elements.
1772
+ takeContent(startIndex: number) {
1773
+ let content = this.resolveMarkers(startIndex)
1774
+ this.parts.length = startIndex
1775
+ return content
1776
+ }
1777
+
1778
+ /// Return the delimiter at the given index. Mostly useful to get
1779
+ /// additional info out of a delimiter index returned by
1780
+ /// [`findOpeningDelimiter`](#InlineContext.findOpeningDelimiter).
1781
+ /// Returns null if there is no delimiter at this index.
1782
+ getDelimiterAt(index: number): {from: number, to: number, type: DelimiterType} | null {
1783
+ let part = this.parts[index]
1784
+ return part instanceof InlineDelimiter ? part : null
1785
+ }
1786
+
1787
+ /// Skip space after the given (document) position, returning either
1788
+ /// the position of the next non-space character or the end of the
1789
+ /// section.
1790
+ skipSpace(from: number) { return skipSpace(this.text, from - this.offset) + this.offset }
1791
+
1792
+ /// Create an [`Element`](#Element) for a syntax node.
1793
+ elt(type: string, from: number, to: number, children?: readonly Element[]): Element
1794
+ elt(tree: Tree, at: number): Element
1795
+ elt(type: string | Tree, from: number, to?: number, children?: readonly Element[]): Element {
1796
+ if (typeof type == "string") return elt(this.parser.getNodeType(type), from, to!, children)
1797
+ return new TreeElement(type, from)
1798
+ }
1799
+
1800
+ /// The opening delimiter type used by the standard link parser.
1801
+ static linkStart = LinkStart
1802
+
1803
+ /// Opening delimiter type used for standard images.
1804
+ static imageStart = ImageStart
1805
+ }
1806
+
1807
+ function injectMarks(elements: readonly (Element | TreeElement)[], marks: Element[]) {
1808
+ if (!marks.length) return elements
1809
+ if (!elements.length) return marks
1810
+ let elts = elements.slice(), eI = 0
1811
+ for (let mark of marks) {
1812
+ while (eI < elts.length && elts[eI].to < mark.to) eI++
1813
+ if (eI < elts.length && elts[eI].from < mark.from) {
1814
+ let e = elts[eI]
1815
+ if (e instanceof Element)
1816
+ elts[eI] = new Element(e.type, e.from, e.to, injectMarks(e.children, [mark]))
1817
+ } else {
1818
+ elts.splice(eI++, 0, mark)
1819
+ }
1820
+ }
1821
+ return elts
1822
+ }
1823
+
1824
+ // These are blocks that can span blank lines, and should thus only be
1825
+ // reused if their next sibling is also being reused.
1826
+ const NotLast = [Type.CodeBlock, Type.ListItem, Type.OrderedList, Type.BulletList]
1827
+
1828
+ class FragmentCursor {
1829
+ // Index into fragment array
1830
+ i = 0
1831
+ // Active fragment
1832
+ fragment: TreeFragment | null = null
1833
+ fragmentEnd = -1
1834
+ // Cursor into the current fragment, if any. When `moveTo` returns
1835
+ // true, this points at the first block after `pos`.
1836
+ cursor: TreeCursor | null = null
1837
+
1838
+ constructor(readonly fragments: readonly TreeFragment[], readonly input: Input) {
1839
+ if (fragments.length) this.fragment = fragments[this.i++]
1840
+ }
1841
+
1842
+ nextFragment() {
1843
+ this.fragment = this.i < this.fragments.length ? this.fragments[this.i++] : null
1844
+ this.cursor = null
1845
+ this.fragmentEnd = -1
1846
+ }
1847
+
1848
+ moveTo(pos: number, lineStart: number) {
1849
+ while (this.fragment && this.fragment.to <= pos) this.nextFragment()
1850
+ if (!this.fragment || this.fragment.from > (pos ? pos - 1 : 0)) return false
1851
+ if (this.fragmentEnd < 0) {
1852
+ let end = this.fragment.to
1853
+ while (end > 0 && this.input.read(end - 1, end) != "\n") end--
1854
+ this.fragmentEnd = end ? end - 1 : 0
1855
+ }
1856
+
1857
+ let c = this.cursor
1858
+ if (!c) {
1859
+ c = this.cursor = this.fragment.tree.cursor()
1860
+ c.firstChild()
1861
+ }
1862
+
1863
+ let rPos = pos + this.fragment.offset
1864
+ while (c.to <= rPos) if (!c.parent()) return false
1865
+ for (;;) {
1866
+ if (c.from >= rPos) return this.fragment.from <= lineStart
1867
+ if (!c.childAfter(rPos)) return false
1868
+ }
1869
+ }
1870
+
1871
+ matches(hash: number) {
1872
+ let tree = this.cursor!.tree
1873
+ return tree && tree.prop(NodeProp.contextHash) == hash
1874
+ }
1875
+
1876
+ takeNodes(cx: BlockContext) {
1877
+ let cur = this.cursor!, off = this.fragment!.offset, fragEnd = this.fragmentEnd - (this.fragment!.openEnd ? 1 : 0)
1878
+ let start = cx.absoluteLineStart, end = start, blockI = cx.block.children.length
1879
+ let prevEnd = end, prevI = blockI
1880
+ for (;;) {
1881
+ if (cur.to - off > fragEnd) {
1882
+ if (cur.type.isAnonymous && cur.firstChild()) continue
1883
+ break
1884
+ }
1885
+ let pos = toRelative(cur.from - off, cx.ranges)
1886
+ if (cur.to - off <= cx.ranges[cx.rangeI].to) { // Fits in current range
1887
+ cx.addNode(cur.tree!, pos)
1888
+ } else {
1889
+ let dummy = new Tree(cx.parser.nodeSet.types[Type.Paragraph], [], [], 0, cx.block.hashProp)
1890
+ cx.reusePlaceholders.set(dummy, cur.tree!)
1891
+ cx.addNode(dummy, pos)
1892
+ }
1893
+ // Taken content must always end in a block, because incremental
1894
+ // parsing happens on block boundaries. Never stop directly
1895
+ // after an indented code block, since those can continue after
1896
+ // any number of blank lines.
1897
+ if (cur.type.is("Block")) {
1898
+ if (NotLast.indexOf(cur.type.id) < 0) {
1899
+ end = cur.to - off
1900
+ blockI = cx.block.children.length
1901
+ } else {
1902
+ end = prevEnd
1903
+ blockI = prevI
1904
+ }
1905
+ prevEnd = cur.to - off
1906
+ prevI = cx.block.children.length
1907
+ }
1908
+ if (!cur.nextSibling()) break
1909
+ }
1910
+ while (cx.block.children.length > blockI) {
1911
+ cx.block.children.pop()
1912
+ cx.block.positions.pop()
1913
+ }
1914
+ return end - start
1915
+ }
1916
+ }
1917
+
1918
+ // Convert an input-stream-relative position to a
1919
+ // Markdown-doc-relative position by subtracting the size of all input
1920
+ // gaps before `abs`.
1921
+ function toRelative(abs: number, ranges: readonly {from: number, to: number}[]) {
1922
+ let pos = abs
1923
+ for (let i = 1; i < ranges.length; i++) {
1924
+ let gapFrom = ranges[i - 1].to, gapTo = ranges[i].from
1925
+ if (gapFrom < abs) pos -= gapTo - gapFrom
1926
+ }
1927
+ return pos
1928
+ }
1929
+
1930
+ const markdownHighlighting = styleTags({
1931
+ "Blockquote/...": t.quote,
1932
+ HorizontalRule: t.contentSeparator,
1933
+ "ATXHeading1/... SetextHeading1/...": t.heading1,
1934
+ "ATXHeading2/... SetextHeading2/...": t.heading2,
1935
+ "ATXHeading3/...": t.heading3,
1936
+ "ATXHeading4/...": t.heading4,
1937
+ "ATXHeading5/...": t.heading5,
1938
+ "ATXHeading6/...": t.heading6,
1939
+ "Comment CommentBlock": t.comment,
1940
+ Escape: t.escape,
1941
+ Entity: t.character,
1942
+ "Emphasis/...": t.emphasis,
1943
+ "StrongEmphasis/...": t.strong,
1944
+ "Link/... Image/...": t.link,
1945
+ "OrderedList/... BulletList/...": t.list,
1946
+ "BlockQuote/...": t.quote,
1947
+ "InlineCode CodeText": t.monospace,
1948
+ "URL Autolink": t.url,
1949
+ "HeaderMark HardBreak QuoteMark ListMark LinkMark EmphasisMark CodeMark": t.processingInstruction,
1950
+ "CodeInfo LinkLabel": t.labelName,
1951
+ LinkTitle: t.string,
1952
+ Paragraph: t.content
1953
+ })
1954
+
1955
+ /// The default CommonMark parser.
1956
+ export const parser = new MarkdownParser(
1957
+ new NodeSet(nodeTypes).extend(markdownHighlighting),
1958
+ Object.keys(DefaultBlockParsers).map(n => DefaultBlockParsers[n]),
1959
+ Object.keys(DefaultBlockParsers).map(n => DefaultLeafBlocks[n]),
1960
+ Object.keys(DefaultBlockParsers),
1961
+ DefaultEndLeaf,
1962
+ DefaultSkipMarkup,
1963
+ Object.keys(DefaultInline).map(n => DefaultInline[n]),
1964
+ Object.keys(DefaultInline),
1965
+ []
1966
+ )