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