@promptctl/rich-js 0.24.0 → 0.24.1

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.
Files changed (95) hide show
  1. package/THIRD-PARTY-NOTICES +29 -1
  2. package/dist/core/animation.d.ts +78 -0
  3. package/dist/core/animation.js +107 -0
  4. package/dist/core/cell-widths.d.ts +9 -0
  5. package/dist/core/cell-widths.js +140 -0
  6. package/dist/core/cells.d.ts +17 -11
  7. package/dist/core/cells.js +100 -25
  8. package/dist/core/console.js +5 -5
  9. package/dist/core/easing.d.ts +110 -10
  10. package/dist/core/easing.js +269 -36
  11. package/dist/core/json.js +2 -1
  12. package/dist/core/measure.js +2 -2
  13. package/dist/core/place.js +2 -1
  14. package/dist/core/pretty.js +2 -2
  15. package/dist/core/protocol.d.ts +73 -19
  16. package/dist/core/protocol.js +56 -15
  17. package/dist/core/render.js +2 -1
  18. package/dist/core/segment.d.ts +18 -1
  19. package/dist/core/segment.js +45 -44
  20. package/dist/core/strip.d.ts +3 -2
  21. package/dist/core/strip.js +11 -7
  22. package/dist/core/style.js +17 -6
  23. package/dist/core/text.js +22 -14
  24. package/dist/core/timeline.d.ts +84 -0
  25. package/dist/core/timeline.js +144 -0
  26. package/dist/core/tween.d.ts +49 -0
  27. package/dist/core/tween.js +69 -0
  28. package/dist/host/app.js +2 -2
  29. package/dist/index.d.ts +10 -7
  30. package/dist/index.js +13 -4
  31. package/dist/renderables/align.d.ts +3 -2
  32. package/dist/renderables/align.js +3 -0
  33. package/dist/renderables/columns.d.ts +3 -2
  34. package/dist/renderables/columns.js +7 -2
  35. package/dist/renderables/constrain.d.ts +3 -2
  36. package/dist/renderables/constrain.js +5 -1
  37. package/dist/renderables/effect.d.ts +43 -58
  38. package/dist/renderables/effect.js +58 -64
  39. package/dist/renderables/effects.d.ts +16 -14
  40. package/dist/renderables/effects.js +64 -45
  41. package/dist/renderables/embed.js +3 -2
  42. package/dist/renderables/flexStrip.d.ts +3 -2
  43. package/dist/renderables/flexStrip.js +6 -1
  44. package/dist/renderables/frames.d.ts +40 -19
  45. package/dist/renderables/frames.js +48 -21
  46. package/dist/renderables/group.d.ts +3 -2
  47. package/dist/renderables/group.js +5 -2
  48. package/dist/renderables/html-entities.d.ts +8 -0
  49. package/dist/renderables/html-entities.js +386 -0
  50. package/dist/renderables/layout.d.ts +3 -2
  51. package/dist/renderables/layout.js +15 -2
  52. package/dist/renderables/list-view.d.ts +3 -2
  53. package/dist/renderables/list-view.js +12 -5
  54. package/dist/renderables/live.js +2 -2
  55. package/dist/renderables/markdown-parse.d.ts +109 -0
  56. package/dist/renderables/markdown-parse.js +1421 -0
  57. package/dist/renderables/markdown-url.d.ts +19 -0
  58. package/dist/renderables/markdown-url.js +314 -0
  59. package/dist/renderables/markdown.d.ts +5 -7
  60. package/dist/renderables/markdown.js +356 -683
  61. package/dist/renderables/padding.d.ts +8 -20
  62. package/dist/renderables/padding.js +32 -50
  63. package/dist/renderables/panel.d.ts +26 -26
  64. package/dist/renderables/panel.js +67 -103
  65. package/dist/renderables/progress.d.ts +4 -2
  66. package/dist/renderables/progress.js +11 -2
  67. package/dist/renderables/progressBar.js +2 -2
  68. package/dist/renderables/python-space.d.ts +13 -0
  69. package/dist/renderables/python-space.js +24 -0
  70. package/dist/renderables/spinner.d.ts +26 -14
  71. package/dist/renderables/spinner.js +49 -37
  72. package/dist/renderables/syntax.js +2 -1
  73. package/dist/renderables/table.d.ts +19 -11
  74. package/dist/renderables/table.js +140 -156
  75. package/dist/renderables/traceback.d.ts +1 -1
  76. package/dist/renderables/traceback.js +6 -5
  77. package/dist/renderables/tree.d.ts +6 -5
  78. package/dist/renderables/tree.js +11 -7
  79. package/dist/renderables/viewport.d.ts +13 -5
  80. package/dist/renderables/viewport.js +41 -20
  81. package/dist/template-bindings/animation-funcs.js +2 -2
  82. package/dist/template-bindings/index.js +2 -1
  83. package/dist/themes/ramp.d.ts +18 -3
  84. package/dist/themes/ramp.js +23 -2
  85. package/dist/widgets/focus-manager.d.ts +21 -14
  86. package/dist/widgets/focus-manager.js +28 -29
  87. package/dist/widgets/index.d.ts +1 -1
  88. package/dist/widgets/static-item.d.ts +4 -3
  89. package/dist/widgets/static-item.js +9 -7
  90. package/dist/widgets/text-input.d.ts +28 -18
  91. package/dist/widgets/text-input.js +144 -101
  92. package/dist/widgets/widget-app.d.ts +19 -3
  93. package/dist/widgets/widget-app.js +39 -21
  94. package/dist/widgets/widget-base.js +0 -3
  95. package/package.json +2 -5
@@ -0,0 +1,1421 @@
1
+ /**
2
+ * Markdown's parse, with no dependency: a source read into blocks, then the
3
+ * text of each block read into inlines — the two passes markdown-it, Rich's
4
+ * parser, makes. Nothing here reads a `RenderOptions`, so a `Markdown` parses
5
+ * its source once, when it is made, as Rich's does, and every render after
6
+ * only walks the tokens.
7
+ *
8
+ * Block structure is read as CommonMark reads it: a blockquote and a list
9
+ * item are both containers, their lines tokenized as Markdown of their own
10
+ * once the container's marker or indentation is cut off. A table, which
11
+ * CommonMark has none of, and an HTML block are read as markdown-it reads
12
+ * them.
13
+ */
14
+ import { linkHref, normalizeLinkText } from "./markdown-url.js";
15
+ import { PY_WHITESPACE, pyStrip } from "./python-space.js";
16
+ import { namedReference } from "./html-entities.js";
17
+ /** A line of the source as the top level holds it. */
18
+ function sourceLine(text) {
19
+ return { text, marked: 0, quoteStart: 0, margin: 0, lead: advance(0, LEAD.exec(text)[0]), lazy: false };
20
+ }
21
+ /**
22
+ * A container's lines, each made the first time its block pass reads it, as
23
+ * markdown-it's block pass reads a container's lines in place. A container
24
+ * costs the lines its block pass reads, not every line it might take: a quote
25
+ * whose block pass stops at the first lazy continuation line it does not take
26
+ * reads that line, not the rest of the document after it.
27
+ */
28
+ class Lines {
29
+ source;
30
+ made = [];
31
+ constructor(source) {
32
+ this.source = source;
33
+ }
34
+ /** The line at `i`, or nothing where the container ends before it. */
35
+ at(i) {
36
+ while (this.made.length <= i) {
37
+ const next = this.source.next();
38
+ if (next.done)
39
+ return undefined;
40
+ this.made.push(next.value);
41
+ }
42
+ return this.made[i];
43
+ }
44
+ /** The lines from `start` up to `end`. */
45
+ slice(start, end) {
46
+ this.at(end - 1);
47
+ return this.made.slice(start, end);
48
+ }
49
+ /** How many lines the container holds, every one of them made. */
50
+ count() {
51
+ while (this.at(this.made.length) !== undefined)
52
+ ;
53
+ return this.made.length;
54
+ }
55
+ }
56
+ /** An ATX heading's opening: one to six `#`, then a space, a tab or the end of the line. */
57
+ const HEADING = /^#{1,6}(?=[ \t]|$)/;
58
+ /**
59
+ * An ATX heading's closing sequence, as markdown-it cuts one: a run of `#` at
60
+ * the end of the line, spaces and tabs after it allowed, with a space or tab
61
+ * before it — so `# x#` keeps its `#` and `# #` is an empty heading.
62
+ */
63
+ const HEADING_CLOSE = /(?<=[ \t])#+[ \t]*$/;
64
+ /** A thematic break: three or more of one of `*`, `-` or `_`, spaces and tabs between them allowed. */
65
+ const RULE = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
66
+ const SETEXT = /^(=+|-+)[ \t]*$/;
67
+ /**
68
+ * A code fence's opening, as markdown-it's fence rule reads one: three or more
69
+ * backticks, the info string after them holding no backtick, or three or more
70
+ * tildes, the info string free to hold anything. The run is captured whole, so
71
+ * its closing can be read off it.
72
+ */
73
+ const FENCE = /^(`{3,}(?=[^`]*$)|~{3,})/;
74
+ const QUOTE = /^>/;
75
+ const ITEM_MARKER = /^(?:[*+-]|(\d{1,9})[.)])/;
76
+ /**
77
+ * A line's leading space and tab: CommonMark's whitespace, and the only
78
+ * whitespace markdown-it's block pass reads — what indents a line, what makes
79
+ * one blank. A no-break space or any other Unicode space is text to it.
80
+ */
81
+ const LEAD = /^[ \t]*/;
82
+ const BLOCK_STARTS = [HEADING, FENCE, QUOTE, RULE];
83
+ /** A line that may be a table's delimiter row, screened as markdown-it screens one: pipes, dashes, colons and space only, and not a dash then a space, which opens a list item. */
84
+ const DELIMITER_ROW = /^(?!-[ \t])[-:|][-:| \t]+$/;
85
+ const DELIMITER_CELL = /^:?-+:?$/;
86
+ /**
87
+ * How many empty cells a table's short rows may be filled out with before the
88
+ * table ends, as markdown-it caps them: a header of many columns over many
89
+ * one-cell rows would otherwise grow a few kilobytes of source into millions of cells.
90
+ */
91
+ const MAX_AUTOCOMPLETED_CELLS = 0x10000;
92
+ /** Indentation that makes a line indented code when it opens a block, as CommonMark counts it. */
93
+ const CODE_INDENT = 4;
94
+ /**
95
+ * markdown-it's `maxNesting`, 20 in the commonmark preset Rich parses with: how
96
+ * deep its blocks nest — a quote one level, a list and its item one each —
97
+ * and how deep it follows a link label holding a bracket holding another.
98
+ * Past it the block parser reads nothing more of a container, and the label
99
+ * scan stops looking, so neither pass recurses further however deep the
100
+ * source goes.
101
+ */
102
+ const MAX_NESTING = 20;
103
+ /** The block-level element names an HTML block of the sixth kind opens with, as markdown-it-py lists them. */
104
+ const HTML_BLOCK_NAMES = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|" +
105
+ "fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|" +
106
+ "main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|" +
107
+ "title|tr|track|ul";
108
+ // The tag grammar of markdown-it-py's common/html_re, shared by the HTML block a lone tag opens and the inline HTML it reads in text.
109
+ const HTML_ATTRIBUTE = String.raw `(?:${PY_WHITESPACE}+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:${PY_WHITESPACE}*=${PY_WHITESPACE}*(?:[^"'=<>\`\x00-\x20]+|'[^']*'|"[^"]*"))?)`;
110
+ const HTML_OPEN_TAG = String.raw `<[A-Za-z][A-Za-z0-9-]*${HTML_ATTRIBUTE}*${PY_WHITESPACE}*\/?>`;
111
+ const HTML_CLOSE_TAG = String.raw `<\/[A-Za-z][A-Za-z0-9-]*${PY_WHITESPACE}*>`;
112
+ const HTML_BLOCKS = [
113
+ { start: new RegExp(String.raw `^<(?:script|pre|style|textarea)(?=${PY_WHITESPACE}|>|$)`, "i"), end: /<\/(?:script|pre|style|textarea)>/i, interrupts: true },
114
+ { start: /^<!--/, end: /-->/, interrupts: true },
115
+ { start: /^<\?/, end: /\?>/, interrupts: true },
116
+ // Uppercase only, as markdown-it-py has it, where markdown-it in JS and CommonMark 0.31 take `<!doctype` too.
117
+ { start: /^<![A-Z]/, end: />/, interrupts: true },
118
+ { start: /^<!\[CDATA\[/, end: /\]\]>/, interrupts: true },
119
+ { start: new RegExp(String.raw `^<\/?(?:${HTML_BLOCK_NAMES})(?=${PY_WHITESPACE}|\/?>|$)`, "i"), end: "blank", interrupts: true },
120
+ {
121
+ start: new RegExp(String.raw `^(?:${HTML_OPEN_TAG}|${HTML_CLOSE_TAG})${PY_WHITESPACE}*$`),
122
+ end: "blank",
123
+ interrupts: false,
124
+ },
125
+ ];
126
+ /** The column `ws` ends at when it starts at column `from`, a tab reaching the next stop of 4 as CommonMark counts it. */
127
+ function advance(from, ws) {
128
+ return [...ws].reduce((col, ch) => (ch === "\t" ? col + 4 - (col % 4) : col + 1), from);
129
+ }
130
+ /** How far `line` is indented inside its container. */
131
+ function indentOf(line) {
132
+ return Math.max(0, line.lead - line.margin);
133
+ }
134
+ /** `line` from its first character that is not leading whitespace. */
135
+ function body(line) {
136
+ const unmarked = line.text.slice(line.marked);
137
+ return unmarked.slice(LEAD.exec(unmarked)[0].length);
138
+ }
139
+ function isBlank(line) {
140
+ return body(line) === "";
141
+ }
142
+ /** Whether `line` sits less than a code indent into its container and its text matches `re`. */
143
+ function startsWith(line, re) {
144
+ return indentOf(line) < CODE_INDENT && re.test(body(line));
145
+ }
146
+ /**
147
+ * `line` from `n` columns into its container on: a block's own indentation
148
+ * cut off, the rest as written. A tab straddling the cut leaves the columns
149
+ * past it as spaces.
150
+ */
151
+ function dropColumns(line, n) {
152
+ const to = line.margin + n;
153
+ let col = 0;
154
+ let i = 0;
155
+ for (; i < line.text.length && col < to; i++) {
156
+ const ch = i < line.marked && line.text[i] !== "\t" ? " " : line.text[i];
157
+ if (ch !== " " && ch !== "\t")
158
+ break;
159
+ const next = advance(col, ch);
160
+ if (next > to)
161
+ return " ".repeat(next - to) + line.text.slice(i + 1);
162
+ col = next;
163
+ }
164
+ return line.text.slice(i);
165
+ }
166
+ function itemStart(line) {
167
+ const text = body(line);
168
+ const m = ITEM_MARKER.exec(text);
169
+ if (!m || indentOf(line) >= CODE_INDENT || RULE.test(text))
170
+ return undefined;
171
+ const after = text.slice(m[0].length);
172
+ const spacing = LEAD.exec(after)[0];
173
+ const rest = after.slice(spacing.length);
174
+ if (spacing === "" && rest !== "")
175
+ return undefined;
176
+ const markerEnd = line.lead + m[0].length;
177
+ const spaced = advance(markerEnd, spacing) - markerEnd;
178
+ const content = rest === "" || spaced > CODE_INDENT ? markerEnd + 1 : markerEnd + spaced;
179
+ return {
180
+ marker: m[1] === undefined ? { bullet: m[0] } : { delimiter: m[0].slice(-1), start: parseInt(m[1], 10) },
181
+ indent: content - line.margin,
182
+ listIndent: line.margin - line.quoteStart,
183
+ first: rest === "" ? undefined : { ...line, marked: line.text.length - after.length, margin: content, lead: markerEnd + spaced, lazy: false },
184
+ };
185
+ }
186
+ /** Whether `line` may be a table's row: a line of its container's own, neither lazy paragraph text nor indented code. */
187
+ function tableLine(line) {
188
+ return !line.lazy && indentOf(line) < CODE_INDENT;
189
+ }
190
+ /**
191
+ * A table row's cells, as markdown-it splits one: at every `|` no backslash escapes,
192
+ * a pipe at either end of the row closing it rather than opening an empty
193
+ * cell, and the row and each cell stripped. An escaped `\|` is a `|` in its cell, inside a
194
+ * code span too.
195
+ */
196
+ function rowCells(row) {
197
+ const cells = [""];
198
+ for (const ch of pyStrip(row)) {
199
+ const last = cells.length - 1;
200
+ if (ch !== "|")
201
+ cells[last] += ch;
202
+ else if (cells[last].endsWith("\\"))
203
+ cells[last] = `${cells[last].slice(0, -1)}|`;
204
+ else
205
+ cells.push("");
206
+ }
207
+ const from = cells[0] === "" ? 1 : 0;
208
+ const to = cells.length > from && cells[cells.length - 1] === "" ? -1 : undefined;
209
+ return cells.slice(from, to).map(pyStrip);
210
+ }
211
+ /** The alignment of each column `line` declares, if it is a table's delimiter row: a run of dashes per column, a colon on the side its cells sit. */
212
+ function alignments(line) {
213
+ const text = body(line);
214
+ if (!tableLine(line) || !DELIMITER_ROW.test(text))
215
+ return undefined;
216
+ const cells = rowCells(text);
217
+ if (!cells.every((cell) => DELIMITER_CELL.test(cell)))
218
+ return undefined;
219
+ return cells.map((cell) => (cell.endsWith(":") ? (cell.startsWith(":") ? "center" : "right") : cell.startsWith(":") ? "left" : undefined));
220
+ }
221
+ /**
222
+ * Whether `line` may be a table's header row: a `tableLine`, or a line lazy
223
+ * only to list items. markdown-it tries a table at a list item's lazy line to
224
+ * end the paragraph open before it, and measures it against the item, which
225
+ * it is short of, so it is never code; a quote's lazy line is never tried.
226
+ */
227
+ function headerLine(line) {
228
+ return line.lazy === "item" || tableLine(line);
229
+ }
230
+ /**
231
+ * The columns of the table opening at `start`, if one does, as markdown-it
232
+ * reads one: a header row with a pipe in it, then a delimiter row naming as
233
+ * many columns.
234
+ */
235
+ function tableAt(lines, start) {
236
+ const head = lines.at(start);
237
+ const delimiter = lines.at(start + 1);
238
+ if (delimiter === undefined || !headerLine(head) || !body(head).includes("|"))
239
+ return undefined;
240
+ const aligns = alignments(delimiter);
241
+ if (!aligns)
242
+ return undefined;
243
+ const headers = rowCells(body(head));
244
+ return headers.length === aligns.length ? headers.map((header, n) => ({ header, align: aligns[n] })) : undefined;
245
+ }
246
+ /**
247
+ * Whether two item starts belong to one list: the same bullet, or numbers with
248
+ * the same delimiter — markdown-it's marker character, the two kinds' characters
249
+ * never coinciding.
250
+ */
251
+ function sameList(a, b) {
252
+ const markerChar = ({ marker }) => ("bullet" in marker ? marker.bullet : marker.delimiter);
253
+ return markerChar(a) === markerChar(b);
254
+ }
255
+ /**
256
+ * A `>` line as a line of its quote, as markdown-it's blockquote reads one:
257
+ * the quote's content starts past the marker and the one column of space that
258
+ * may follow it — one column of a tab, when a tab follows it, the rest of the
259
+ * tab left in the content unless that column was all of it. The whitespace
260
+ * after the marker is measured in `line`'s own columns, and the content is
261
+ * then counted from where the content of `line`'s quote starts, not from
262
+ * `line`'s column 0.
263
+ */
264
+ function quoted(line) {
265
+ const after = body(line).slice(1);
266
+ const ws = LEAD.exec(after)[0];
267
+ const marked = line.lead + 1;
268
+ const space = ws === "" ? 0 : 1;
269
+ const start = line.lead - line.quoteStart + 1 + space;
270
+ const whole = after[0] === " " || (after[0] === "\t" && marked % 4 === 3);
271
+ const rest = whole ? after.slice(1) : after;
272
+ const text = " ".repeat(start) + rest;
273
+ return { text, marked: 0, quoteStart: start, margin: start, lead: start + advance(marked, ws) - marked - space, lazy: false };
274
+ }
275
+ /** The kind of HTML block `line` opens, if it opens one: the first kind whose opening it matches, as markdown-it tries them in order. */
276
+ function htmlBlockAt(line) {
277
+ return HTML_BLOCKS.find((kind) => startsWith(line, kind.start));
278
+ }
279
+ /**
280
+ * Whether `line` opens a block of its own that may cut short the block before
281
+ * it — a heading, a fence, a quote, a rule, a list item or an HTML block of
282
+ * any kind but a lone tag. A line that does not is text, and under an open
283
+ * paragraph it continues that paragraph.
284
+ */
285
+ function opensBlock(line) {
286
+ return BLOCK_STARTS.some((re) => startsWith(line, re)) || itemStart(line) !== undefined || htmlBlockAt(line)?.interrupts === true;
287
+ }
288
+ /**
289
+ * Whether `line` ends a paragraph: `opensBlock`, read as CommonMark reads it
290
+ * under a paragraph. Indented code cannot interrupt one; a list item does only
291
+ * when it has text and, numbered, counts from 1, so prose wrapped onto "2024.
292
+ * That year" stays prose; and a line of `=` or `-` is the paragraph's heading
293
+ * underline.
294
+ */
295
+ function endsParagraph(line) {
296
+ if (underline(line))
297
+ return true;
298
+ const item = itemStart(line);
299
+ return item ? item.first !== undefined && ("bullet" in item.marker || item.marker.start === 1) : opensBlock(line);
300
+ }
301
+ /** The setext underline `line` is — a line of `=` or `-` under a paragraph — if it is one. A lazy line never is. */
302
+ function underline(line) {
303
+ return line && !line.lazy && indentOf(line) < CODE_INDENT ? SETEXT.exec(body(line)) : null;
304
+ }
305
+ /**
306
+ * Whether `line` has text and opens no block of its own: what a container
307
+ * takes short of its marker or indentation as a lazy continuation line, and
308
+ * what a definition reads on through, as markdown-it's `getNextLine` hands one
309
+ * over. A line outside a container is read without the paragraph under it, so
310
+ * any list item — whatever it counts from — ends the container rather than
311
+ * continuing it. Whether a paragraph or a definition open before a lazy line
312
+ * takes it is the container's own block pass to find, which ends the
313
+ * container where neither does.
314
+ */
315
+ function continuesText(line) {
316
+ return !isBlank(line) && !opensBlock(line);
317
+ }
318
+ /**
319
+ * The text of the paragraph begun at `start`, every line after it up to a
320
+ * blank, a line that ends a paragraph or a table's header row, and the index of the line that ended
321
+ * it. The lines are joined at their line endings as written, each cut only
322
+ * to its container's margin, as markdown-it's `getLines` cuts paragraph
323
+ * content: the indentation past it is the inline pass's to read, which drops
324
+ * it after a line ending and keeps it in a code span, as is whether a line
325
+ * ending breaks soft or hard, since an escape or a code span decides it.
326
+ */
327
+ function continuation(lines, start) {
328
+ let i = start + 1;
329
+ for (let line = lines.at(i); line !== undefined && !isBlank(line) && !endsParagraph(line) && !tableAt(lines, i); line = lines.at(++i))
330
+ ;
331
+ return { text: pyStrip(lines.slice(start, i).map((line) => dropColumns(line, 0)).join("\n")), next: i };
332
+ }
333
+ /**
334
+ * markdown-it's `normalizeReference`, which makes a definition's label and a
335
+ * reference's the same key where CommonMark matches them: stripped, each run
336
+ * of whitespace one space, as Python reads whitespace, then case-folded as
337
+ * `lower().upper()` folds it.
338
+ */
339
+ function normalizeReference(label) {
340
+ return pyStrip(label).replace(WHITESPACE_RUN, " ").toLowerCase().toUpperCase();
341
+ }
342
+ const WHITESPACE_RUN = new RegExp(`${PY_WHITESPACE}+`, "g");
343
+ /**
344
+ * The link reference definition opening at `start`, if one does, as
345
+ * markdown-it's reference rule reads it: `[label]:`, space, a destination, and
346
+ * a title only if space came before it, each of them free to run onto the
347
+ * lines after it that `getNextLine` hands over — up to a blank line, or a line
348
+ * that opens a block of its own. The title and the space after it must end
349
+ * the line; where a title is followed by more, the definition is read again
350
+ * without it, ending with its destination's line. `next` is the index of the
351
+ * line after it.
352
+ */
353
+ function definitionAt(lines, start) {
354
+ if (!body(lines.at(start)).startsWith("["))
355
+ return undefined;
356
+ // The definition's text so far, each line from its first character that is
357
+ // not leading whitespace and ended by its line ending. markdown-it takes a
358
+ // line on only as a read reaches the end of the last; this takes them on in
359
+ // runs that double, which changes no read but the destination's — every
360
+ // other read that reaches the end takes more on — and copies the text of a
361
+ // definition running over n lines log n times rather than n.
362
+ let text = `${body(lines.at(start))}\n`;
363
+ let taken = start + 1;
364
+ let run = 1;
365
+ const takeLines = () => {
366
+ const more = [];
367
+ for (; more.length < run && continuesDefinition(lines, taken); taken++)
368
+ more.push(`${body(lines.at(taken))}\n`);
369
+ run *= 2;
370
+ return (text += more.join(""));
371
+ };
372
+ // markdown-it's space between the parts: spaces and tabs, and line endings, taking lines on at each.
373
+ const spaceEnd = (from) => {
374
+ let i = from;
375
+ for (; i < text.length && (text[i] === " " || text[i] === "\t" || text[i] === "\n"); i++)
376
+ if (i === text.length - 1)
377
+ takeLines();
378
+ return i;
379
+ };
380
+ // The label: up to the first `]` no backslash escapes, with no `[` in it.
381
+ let labelEnd = 1;
382
+ for (; labelEnd < text.length && text[labelEnd] !== "]"; labelEnd++) {
383
+ if (text[labelEnd] === "[")
384
+ return undefined;
385
+ if (text[labelEnd] === "\\")
386
+ labelEnd++;
387
+ if (labelEnd === text.length - 1)
388
+ takeLines();
389
+ }
390
+ if (text[labelEnd + 1] !== ":")
391
+ return undefined;
392
+ const destinationStart = spaceEnd(labelEnd + 2);
393
+ // markdown-it reads the destination in the text it holds, which ends with the destination's own line.
394
+ const held = text.slice(0, text.indexOf("\n", destinationStart) + 1);
395
+ const destination = destinationAt(held, destinationStart);
396
+ // An empty destination outside angle brackets is none, as markdown-it's `parseLinkDestination` reads it.
397
+ if (destination === undefined || destination.end === destinationStart)
398
+ return undefined;
399
+ const url = linkHref(unescapeAll(destination.raw));
400
+ if (url === undefined)
401
+ return undefined;
402
+ // The line ending the definition ends at.
403
+ let end = held.length - 1;
404
+ // A destination that took its line's ending, behind a backslash, ends the
405
+ // definition at it: markdown-it holds no text past it to read a title in.
406
+ if (destination.end < held.length) {
407
+ const titleStart = spaceEnd(destination.end);
408
+ const title = titleStart > destination.end ? titleAt(text, titleStart, takeLines) : undefined;
409
+ // The definition ends at its line's end, past the spaces and tabs there.
410
+ const lineEnd = (from) => from + matchAt(LINE_INDENT, text, from)[0].length;
411
+ end = lineEnd(title?.end ?? destination.end);
412
+ // markdown-it gives up only a title with text in it for what follows it on its line, so an empty one followed by more ends no definition.
413
+ if (text[end] !== "\n" && title?.text === true)
414
+ end = lineEnd(destination.end);
415
+ if (text[end] !== "\n")
416
+ return undefined;
417
+ }
418
+ const label = normalizeReference(text.slice(1, labelEnd));
419
+ if (label === "")
420
+ return undefined;
421
+ // The line after it: one past the line its end is on.
422
+ let next = start + 1;
423
+ for (let i = text.indexOf("\n"); i < end; i = text.indexOf("\n", i + 1))
424
+ next++;
425
+ return { label, url, next };
426
+ }
427
+ /**
428
+ * Whether the line at `at` continues a definition, as markdown-it's
429
+ * `getNextLine` hands one over: text, and not a table's header row.
430
+ */
431
+ function continuesDefinition(lines, at) {
432
+ const line = lines.at(at);
433
+ return line !== undefined && continuesText(line) && !tableAt(lines, at);
434
+ }
435
+ /** The character that closes a link title, by the one that opens it. */
436
+ const TITLE_CLOSE = { '"': '"', "'": "'", "(": ")" };
437
+ /**
438
+ * The link title opening at `start` in `text`, as markdown-it's
439
+ * `parseLinkTitle` reads one in a definition: in `"`, `'` or `(…)`, a
440
+ * backslash taking the character after it and a `(` refusing a `(…)` title,
441
+ * reading on to the lines `takeLines` takes on — handing back the text grown
442
+ * by them, or as it was where it takes none — until it closes. `end` is the
443
+ * index past it, and `text` whether it holds any.
444
+ */
445
+ function titleAt(text, start, takeLines) {
446
+ const close = TITLE_CLOSE[text[start]];
447
+ if (close === undefined)
448
+ return undefined;
449
+ for (let i = start + 1;; i++) {
450
+ if (i === text.length)
451
+ text = takeLines();
452
+ if (i === text.length)
453
+ return undefined;
454
+ if (text[i] === close)
455
+ return { end: i + 1, text: i > start + 1 };
456
+ if (text[i] === "(" && close === ")")
457
+ return undefined;
458
+ if (text[i] === "\\")
459
+ i++;
460
+ }
461
+ }
462
+ /**
463
+ * The lines of the list item opened at `start`: its first line's text, then
464
+ * every line after it indented to the content column, each line with text in
465
+ * it short of that a lazy continuation line, and each blank line with a line
466
+ * indented to the content column after it. Blank lines trailing the item are
467
+ * left to whatever follows it, and an item that opens with a blank line ends
468
+ * at the next one: it is empty.
469
+ */
470
+ function* itemLines(item, lines, start) {
471
+ const held = (line) => !line.lazy && indentOf(line) >= item.indent;
472
+ const inItem = (line) => ({ ...line, margin: line.margin + item.indent, lazy: false });
473
+ const short = (line) => ({ ...line, margin: line.quoteStart + item.listIndent, lazy: "item" });
474
+ // Whether the item holds a line with text yet, which a blank line or a lazy one must come after.
475
+ let text = item.first !== undefined;
476
+ if (item.first !== undefined)
477
+ yield item.first;
478
+ for (let i = start + 1;; i++) {
479
+ let line = lines.at(i);
480
+ let after = i;
481
+ while (line !== undefined && isBlank(line))
482
+ line = lines.at(++after);
483
+ if (line === undefined || (after > i && !(text && held(line))))
484
+ return;
485
+ for (; i < after; i++)
486
+ yield inItem(lines.at(i));
487
+ if (held(line))
488
+ yield inItem(line);
489
+ // A quote's lazy line the item's paragraph takes whatever it holds; any other is lazy to the item.
490
+ else if (text && line.lazy === "quote")
491
+ yield line;
492
+ else if (text && continuesText(outdented(short(line))))
493
+ yield short(line);
494
+ else
495
+ return;
496
+ text = true;
497
+ }
498
+ }
499
+ /**
500
+ * A lazy `line` as a block inside its container reads it, indented less than
501
+ * any block — but a list item on an `"item"` line, which markdown-it's list
502
+ * refuses at a line short of the item and a code indent past the list's
503
+ * container.
504
+ */
505
+ function outdented(line) {
506
+ const read = { ...line, margin: line.lead };
507
+ return line.lazy === "item" && itemStart(read) !== undefined ? line : read;
508
+ }
509
+ /**
510
+ * The lines of the quote opening at `start`: its `>` lines with the marker
511
+ * blanked, and each unmarked line after one with text a lazy continuation line.
512
+ * markdown-it's quote takes a `>` line after its first however far it is
513
+ * indented. A line already lazy where the quote sits is short of its
514
+ * container's indentation, so the quote reads it as markdown-it does, indented
515
+ * less than any block: never a `>` line of its own, and opening whatever block
516
+ * its text starts, however far it is indented — but a list item on a line lazy
517
+ * only to items, which markdown-it's list opens only where the line sits.
518
+ */
519
+ function* quoteLines(lines, start) {
520
+ // Whether the line before has text, which a lazy line must come after.
521
+ let text = false;
522
+ for (let j = start, line = lines.at(j); line !== undefined; line = lines.at(++j)) {
523
+ if (!line.lazy && QUOTE.test(body(line))) {
524
+ const inQuote = quoted(line);
525
+ text = !isBlank(inQuote);
526
+ yield inQuote;
527
+ }
528
+ else if (text && continuesText(line.lazy ? outdented(line) : line))
529
+ yield { ...line, lazy: "quote" };
530
+ else
531
+ return;
532
+ }
533
+ }
534
+ /**
535
+ * The list item opened at `start`, at `level`: its blocks, read from its
536
+ * lines up to where its own block pass stops — and the index of the line
537
+ * that ended it.
538
+ */
539
+ function listItem(item, level, lines, start) {
540
+ const { tokens, end } = tokenize(new Lines(itemLines(item, lines, start)), level);
541
+ // An item with no text on its first line holds its lines from the line after it.
542
+ return { children: tokens, next: (item.first === undefined ? start + 1 : start) + end };
543
+ }
544
+ /**
545
+ * The line that closes the fence opened by the run `open`: a run of the same
546
+ * marker at least as long, then nothing but spaces and tabs.
547
+ */
548
+ function closingFence(open) {
549
+ return new RegExp(`^${open}${open[0]}*[ \\t]*$`);
550
+ }
551
+ /**
552
+ * Whether a list item holding `children` swallows the rest of the lines its
553
+ * list sits in, as markdown-it's does when its content is past `MAX_NESTING`:
554
+ * an item's block pass is handed every line to the end of the enclosing quote
555
+ * or document and stops at the first line short of its indentation, and the
556
+ * pass that skips unread content skips to that end. An item whose last block
557
+ * is a list that swallowed them ends in the `unread` that list leaves, and so
558
+ * swallows them too.
559
+ */
560
+ function swallowsRest(children) {
561
+ return children[children.length - 1]?.type === "unread";
562
+ }
563
+ /**
564
+ * The list opened at `start`, at `level`: each item holding its blocks — and,
565
+ * where an item swallowed the rest of `lines`, the `unread` they are, ending
566
+ * where they do.
567
+ */
568
+ function list(first, level, lines, start) {
569
+ const items = [];
570
+ let i = start;
571
+ // Where the last item's lines end.
572
+ let end = start;
573
+ let swallowed = false;
574
+ for (let item = first; item && sameList(first, item); item = lines.at(i) === undefined ? undefined : itemStart(lines.at(i))) {
575
+ // A list and its item each nest a level, as markdown-it's tokens do.
576
+ const { children, next } = listItem(item, level + 2, lines, i);
577
+ items.push(children);
578
+ swallowed = swallowsRest(children);
579
+ end = swallowed ? lines.count() : next;
580
+ // An empty item with a blank line after it sends markdown-it's list rule two
581
+ // lines on, past no more blank lines, so a second blank line ends the list.
582
+ const after = lines.at(i + 1);
583
+ if (item.first === undefined && after !== undefined && isBlank(after))
584
+ i += 2;
585
+ else
586
+ for (i = end; lines.at(i) !== undefined && isBlank(lines.at(i)); i++)
587
+ ;
588
+ }
589
+ const token = { type: "list", start: "start" in first.marker ? first.marker.start : undefined, items };
590
+ if (swallowed)
591
+ return { tokens: [token, { type: "unread" }], next: end };
592
+ // The blank lines after the last item belong to whatever follows the list.
593
+ return { tokens: [token], next: end };
594
+ }
595
+ /**
596
+ * `lines` read into blocks at `level`, how deep in containers they sit, and
597
+ * the index of the line the read stopped at: the first lazy continuation
598
+ * line no block before it took, where markdown-it's block pass stops at a
599
+ * line short of its container's indentation, or else the end. At
600
+ * `MAX_NESTING` markdown-it's block pass reads nothing, and neither does this:
601
+ * it skips blank lines and stops at a lazy one before it checks the level, so
602
+ * only content with text in it is left unread.
603
+ */
604
+ function tokenize(lines, level) {
605
+ const tokens = [];
606
+ let i = 0;
607
+ for (let line = lines.at(i); line !== undefined; line = lines.at(i)) {
608
+ if (isBlank(line)) {
609
+ i++;
610
+ continue;
611
+ }
612
+ if (line.lazy)
613
+ break;
614
+ if (level >= MAX_NESTING)
615
+ return { tokens: [{ type: "unread" }], end: lines.count() };
616
+ // Table: before every other block, as markdown-it tries it, so a header
617
+ // row that reads as a heading or a list item is still a header. Its body
618
+ // rows run to a line `pyStrip` empties or a line that opens a block.
619
+ const columns = tableAt(lines, i);
620
+ if (columns) {
621
+ const rows = [];
622
+ let autocompleted = 0;
623
+ for (let row = lines.at((i += 2)); row !== undefined && pyStrip(row.text.slice(row.marked)) !== "" && tableLine(row) && !opensBlock(row); row = lines.at(++i)) {
624
+ const cells = rowCells(body(row));
625
+ autocompleted += columns.length - cells.length;
626
+ if (autocompleted > MAX_AUTOCOMPLETED_CELLS)
627
+ break;
628
+ rows.push(columns.map((_, n) => cells[n] ?? ""));
629
+ }
630
+ tokens.push({ type: "table", columns, rows });
631
+ continue;
632
+ }
633
+ // Indented code: its lines, and the blank lines between them, with the indent cut off
634
+ if (indentOf(line) >= CODE_INDENT) {
635
+ let end = i + 1;
636
+ for (let j = end, code = lines.at(j); code !== undefined && (isBlank(code) || (!code.lazy && indentOf(code) >= CODE_INDENT)); code = lines.at(++j)) {
637
+ if (!isBlank(code))
638
+ end = j + 1;
639
+ }
640
+ tokens.push({ type: "code_block", code: lines.slice(i, end).map((code) => dropColumns(code, CODE_INDENT)).join("\n") });
641
+ i = end;
642
+ continue;
643
+ }
644
+ // Heading
645
+ const headingMatch = HEADING.exec(body(line));
646
+ if (headingMatch) {
647
+ // [LAW:parse-dont-validate] `HEADING` matches one to six `#`s, so its length is a level.
648
+ tokens.push({ type: "heading", level: headingMatch[0].length, text: pyStrip(body(line).slice(headingMatch[0].length).replace(HEADING_CLOSE, "")) });
649
+ i++;
650
+ continue;
651
+ }
652
+ // Horizontal rule
653
+ if (startsWith(line, RULE)) {
654
+ tokens.push({ type: "hr" });
655
+ i++;
656
+ continue;
657
+ }
658
+ // Fenced code block: its lines with the fence's own indentation cut off,
659
+ // to a closing fence or to the end of its container
660
+ const fence = FENCE.exec(body(line));
661
+ if (fence) {
662
+ const indent = indentOf(line);
663
+ const close = closingFence(fence[1]);
664
+ const codeLines = [];
665
+ for (let code = lines.at(++i); code !== undefined && !code.lazy && !startsWith(code, close); code = lines.at(++i))
666
+ codeLines.push(dropColumns(code, indent));
667
+ if (lines.at(i)?.lazy === false)
668
+ i++; // skip the closing fence
669
+ tokens.push({ type: "code_block", code: codeLines.join("\n") });
670
+ continue;
671
+ }
672
+ // Blockquote: Markdown of its own — its `>` lines from the quote's content
673
+ // on, and each unmarked line after one with text a lazy continuation line,
674
+ // up to where its own block pass stops
675
+ if (startsWith(line, QUOTE)) {
676
+ const { tokens: children, end } = tokenize(new Lines(quoteLines(lines, i)), level + 1);
677
+ tokens.push({ type: "blockquote", children });
678
+ i += end;
679
+ continue;
680
+ }
681
+ // List: its items, each Markdown of its own, as a quote is
682
+ const item = itemStart(line);
683
+ if (item) {
684
+ const { tokens: listed, next } = list(item, level, lines, i);
685
+ tokens.push(...listed);
686
+ i = next;
687
+ continue;
688
+ }
689
+ // Link reference definition
690
+ const definition = definitionAt(lines, i);
691
+ if (definition) {
692
+ tokens.push({ type: "definition", label: definition.label, url: definition.url });
693
+ i = definition.next;
694
+ continue;
695
+ }
696
+ // HTML block: to the line that closes its kind, or to the end of its
697
+ // container when none does; a closing blank line is left to what follows
698
+ const html = htmlBlockAt(line);
699
+ if (html) {
700
+ const { end } = html;
701
+ const closes = end === "blank" ? isBlank : (closing) => end.test(body(closing));
702
+ for (let at = lines.at(i); at !== undefined && !at.lazy && !closes(at); at = lines.at(++i))
703
+ ;
704
+ if (lines.at(i)?.lazy === false && end !== "blank")
705
+ i++;
706
+ tokens.push({ type: "html_block" });
707
+ continue;
708
+ }
709
+ // Paragraph, or a setext heading when a line of `=` or `-` underlines it
710
+ const { text, next } = continuation(lines, i);
711
+ const setext = underline(lines.at(next));
712
+ tokens.push(setext
713
+ ? { type: "heading", level: setext[1].startsWith("=") ? 1 : 2, text }
714
+ : { type: "paragraph", text });
715
+ i = setext ? next + 1 : next;
716
+ }
717
+ return { tokens, end: i };
718
+ }
719
+ /** markdown-it's `isWhiteSpace`: Unicode's space separators, and tab, line feed, vertical tab, form feed and carriage return. */
720
+ const WHITESPACE = /^[\t\n\v\f\r \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000]$/;
721
+ /** markdown-it's `isPunctChar`, which takes in all of ASCII's punctuation: Unicode's punctuation and symbols. */
722
+ const PUNCTUATION = /^[\p{P}\p{S}]$/u;
723
+ const TICKS = /`+/y;
724
+ const TICK_RUNS = /`+/g;
725
+ const LINE_INDENT = /[ \t]*/y;
726
+ /** markdown-it's autolink: `<`, then text holding no `<` up to the first `>`. */
727
+ const AUTOLINK = /<([^<>]*)>/y;
728
+ /** What an autolink's text must be: a URI, scheme first, or an email address. */
729
+ const AUTOLINK_URI = /^[a-zA-Z][a-zA-Z0-9+.\-]{1,31}:[^<>\x00-\x20]*$/;
730
+ const AUTOLINK_EMAIL = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
731
+ /** Space as markdown-it's link rule skips it around a destination and title: a space, tab or line ending, not Unicode's. */
732
+ const LINK_SPACE = /[ \t\n]*/y;
733
+ /**
734
+ * A link title, as markdown-it's `parseLinkTitle` reads one: in `"`, `'` or
735
+ * `(…)`, a backslash taking the character after it, and a `(` refusing a
736
+ * `(…)` title.
737
+ */
738
+ const LINK_TITLE = /"(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|\((?:\\[\s\S]|[^()\\])*\)/y;
739
+ /** How many parentheses markdown-it's `parseLinkDestination` lets a bare destination hold open: one more and it reads none. */
740
+ const MAX_DESTINATION_NESTING = 32;
741
+ /** A backslash escape, as markdown-it reads one: the ASCII punctuation character after it, standing for itself. */
742
+ const ESCAPE = /\\([!-\/:-@[-`{-~])/y;
743
+ /** A numeric character reference, as markdown-it reads one in text: up to seven decimal digits, or up to six hex. */
744
+ const NUMERIC_REFERENCE = /&#([xX][0-9a-fA-F]{1,6}|[0-9]{1,7});/y;
745
+ /** A named character reference, as markdown-it reads one in text: a letter, then one to 31 letters or digits. */
746
+ const NAMED_REFERENCE = /&([A-Za-z][A-Za-z0-9]{1,31});/y;
747
+ /**
748
+ * A backslash escape or a character reference, as markdown-it's `unescapeAll`
749
+ * reads them in a link's destination: the reference read more widely than in
750
+ * text, a name or a `#` then one to 31 letters or digits.
751
+ */
752
+ const UNESCAPE_ALL = new RegExp(`${ESCAPE.source}|&[A-Za-z#][A-Za-z0-9]{1,31};`, "g");
753
+ /** A numeric reference as `unescapeAll` reads one: up to eight digits of either base. */
754
+ const DESTINATION_NUMERIC = /^&#([xX][0-9a-fA-F]{1,8}|[0-9]{1,8});$/;
755
+ /** Inline HTML, as markdown-it-py's HTML_TAG_RE reads it: a tag, a comment, a processing instruction, a declaration or a CDATA section. */
756
+ const HTML_TAG = new RegExp([
757
+ HTML_OPEN_TAG,
758
+ HTML_CLOSE_TAG,
759
+ String.raw `<!---?>|<!--(?:[^-]|-[^-]|--[^>])*-->`,
760
+ String.raw `<[?][\s\S]*?[?]>`,
761
+ String.raw `<![A-Za-z][^>]*>`,
762
+ String.raw `<!\[CDATA\[[\s\S]*?\]\]>`,
763
+ ].join("|"), "y");
764
+ /** `re`, a sticky pattern, matched exactly at `i` in `text`. */
765
+ function matchAt(re, text, i) {
766
+ re.lastIndex = i;
767
+ return re.exec(text);
768
+ }
769
+ /**
770
+ * The character a numeric reference to `code` names, unless markdown-it's
771
+ * `isValidEntityCode` refuses it: a surrogate, a noncharacter, DEL, a C0 or
772
+ * C1 control other than tab, line feed, form feed and carriage return, or
773
+ * anything past U+10FFFF.
774
+ */
775
+ function referencedCharacter(code) {
776
+ const refused = (code >= 0xd800 && code <= 0xdfff) ||
777
+ (code >= 0xfdd0 && code <= 0xfdef) ||
778
+ (code & 0xfffe) === 0xfffe ||
779
+ code <= 0x08 ||
780
+ code === 0x0b ||
781
+ (code >= 0x0e && code <= 0x1f) ||
782
+ (code >= 0x7f && code <= 0x9f) ||
783
+ code > 0x10ffff;
784
+ return refused ? undefined : String.fromCodePoint(code);
785
+ }
786
+ /** The code point a numeric reference's `digits` name: hex after an `x`, decimal without one. */
787
+ function referencedCode(digits) {
788
+ return /^[xX]/.test(digits) ? parseInt(digits.slice(1), 16) : parseInt(digits, 10);
789
+ }
790
+ /**
791
+ * The character reference at `i` in text, as markdown-it's entity rule reads
792
+ * one: a numeric reference as the character it names, or U+FFFD where that is
793
+ * no character it accepts, and a named one only where HTML names it.
794
+ */
795
+ function referenceAt(text, i) {
796
+ const numeric = matchAt(NUMERIC_REFERENCE, text, i);
797
+ if (numeric) {
798
+ const chars = referencedCharacter(referencedCode(numeric[1])) ?? "\uFFFD";
799
+ return { kind: "text", text: chars, end: i + numeric[0].length };
800
+ }
801
+ const named = matchAt(NAMED_REFERENCE, text, i);
802
+ if (!named)
803
+ return undefined;
804
+ const chars = namedReference(named[1]);
805
+ return chars === undefined ? undefined : { kind: "text", text: chars, end: i + named[0].length };
806
+ }
807
+ /**
808
+ * markdown-it's `unescapeAll`, which a link's destination is read through:
809
+ * each backslash escape as the character it escapes, and each reference
810
+ * `UNESCAPE_ALL` finds as the characters it names — a reference naming
811
+ * nothing, or no character `isValidEntityCode` accepts, left as written.
812
+ */
813
+ function unescapeAll(text) {
814
+ return text.replace(UNESCAPE_ALL, (whole, escaped) => {
815
+ if (escaped !== undefined)
816
+ return escaped;
817
+ const numeric = DESTINATION_NUMERIC.exec(whole);
818
+ const chars = numeric ? referencedCharacter(referencedCode(numeric[1])) : namedReference(whole.slice(1, -1));
819
+ return chars ?? whole;
820
+ });
821
+ }
822
+ /**
823
+ * markdown-it's inline state, as much of it as outlives one construct: the
824
+ * document's `definitions` a label is resolved against, the backtick runs of
825
+ * one block's text, and the link-label scan — `parseLinkLabel` stepping over
826
+ * the text a token at a time with `skipToken` — with the cache those steps
827
+ * share across that text. Every text the state is handed is that block's text
828
+ * or a link's label cut from its start at the label's `]`, so a position means
829
+ * the same character in each. A link's label may hold no link: a `[` it meets that
830
+ * opens one refuses the label, so only the innermost of nested links links.
831
+ * Each `[` the scan meets is tried as a link one level deeper, and at
832
+ * `MAX_NESTING` a step lands past the end of the text, as markdown-it's does —
833
+ * which is what keeps the scan shallow and, with the cache, linear in a run of
834
+ * brackets however long.
835
+ */
836
+ class InlineState {
837
+ definitions;
838
+ // markdown-it's `state.cache`: where a step from a position lands, kept
839
+ // whatever level it was taken at and whatever text it was taken in, as there.
840
+ steps = new Map();
841
+ backticks;
842
+ constructor(definitions, text) {
843
+ this.definitions = definitions;
844
+ this.backticks = new BacktickRuns(text);
845
+ }
846
+ /**
847
+ * The index of the `]` closing the label whose `[` is at `open` in `text`,
848
+ * scanned at `level` — or nothing, where no `]` does or, for a link's
849
+ * label, where the label holds a link.
850
+ */
851
+ labelClose(text, open, level, refusesLinks) {
852
+ let depth = 1;
853
+ for (let i = open + 1; i < text.length;) {
854
+ if (text[i] === "]" && --depth === 0)
855
+ return i;
856
+ const next = this.step(text, i, level);
857
+ if (text[i] === "[") {
858
+ // A `[` stepped over alone is text; one that took more opened a link.
859
+ if (next === i + 1)
860
+ depth++;
861
+ else if (refusesLinks)
862
+ return undefined;
863
+ }
864
+ i = next;
865
+ }
866
+ return undefined;
867
+ }
868
+ /** Where markdown-it's `skipToken` lands from `i` in `text` at `level`: past the construct opening there, if one does, tried a level deeper. */
869
+ step(text, i, level) {
870
+ const cached = this.steps.get(i);
871
+ if (cached !== undefined)
872
+ return cached;
873
+ const next = level >= MAX_NESTING ? text.length + 1 : (inlineAt(text, i, this, level + 1)?.end ?? textRunEnd(text, i));
874
+ this.steps.set(i, next);
875
+ return next;
876
+ }
877
+ /**
878
+ * Where the first whole run of `length` backticks at or after `from` in `text` starts, if one does: the closer of a code span `length` long.
879
+ * A label's cut falls on a `]`, never inside a run, so the runs of `text` are the block's runs that start before its end.
880
+ */
881
+ codeClose(text, from, length) {
882
+ const close = this.backticks.after(from, length);
883
+ return close !== undefined && close < text.length ? close : undefined;
884
+ }
885
+ }
886
+ /**
887
+ * Every whole backtick run in a text, read once: markdown-it-py's backticks
888
+ * rule keeps `state.backticks` so that an opener with no closer does not
889
+ * rescan the rest of the text, and a run of openers each left unclosed reads
890
+ * the text once, not once each.
891
+ */
892
+ class BacktickRuns {
893
+ // Where each run starts, by its length, in order.
894
+ starts = new Map();
895
+ constructor(text) {
896
+ for (const run of text.matchAll(TICK_RUNS)) {
897
+ const starts = this.starts.get(run[0].length);
898
+ if (starts)
899
+ starts.push(run.index);
900
+ else
901
+ this.starts.set(run[0].length, [run.index]);
902
+ }
903
+ }
904
+ /** Where the first run of `length` at or after `from` starts, if one does. */
905
+ after(from, length) {
906
+ const starts = this.starts.get(length) ?? [];
907
+ let low = 0;
908
+ for (let high = starts.length; low < high;) {
909
+ const middle = (low + high) >> 1;
910
+ if (starts[middle] < from)
911
+ low = middle + 1;
912
+ else
913
+ high = middle;
914
+ }
915
+ return starts[low];
916
+ }
917
+ }
918
+ /** Every character `inlineAt` opens a construct at, and the `]` a link label ends at. */
919
+ const STEP_STOPS = new Set(["`", "*", "_", "~", "&", "\n", "\\", "<", "!", "[", "]"]);
920
+ /**
921
+ * Where markdown-it's `text` rule leaves a step from `i` in `text` that opens
922
+ * nothing: past the whole run of characters that cannot, so a label scan
923
+ * crosses a paragraph of plain text in one step, or past `i` alone where it
924
+ * is a character that could and did not.
925
+ */
926
+ function textRunEnd(text, i) {
927
+ if (STEP_STOPS.has(text[i]))
928
+ return i + 1;
929
+ let end = i + 1;
930
+ while (end < text.length && !STEP_STOPS.has(text[end]))
931
+ end++;
932
+ return end;
933
+ }
934
+ /**
935
+ * The `(destination "title")` opening at `open`, as markdown-it's link rule
936
+ * reads it: space, the destination `destinationAt` reads, a title only if
937
+ * space came before it, space, then the `)`. The destination, its backslash
938
+ * escapes and character references read and then made an href as markdown-it
939
+ * makes one, and the index past the `)`. Where markdown-it refuses either,
940
+ * `resume` is one past where its read stopped, which is where its link rule
941
+ * looks for a reference's label instead; and where only space follows the
942
+ * `(`, there is nothing, and the brackets before it are neither.
943
+ */
944
+ function linkDestination(text, open) {
945
+ const start = linkSpaceEnd(text, open + 1);
946
+ if (start >= text.length)
947
+ return undefined;
948
+ const destination = destinationAt(text, start);
949
+ const href = destination && linkHref(unescapeAll(destination.raw));
950
+ if (destination === undefined || href === undefined)
951
+ return { resume: start + 1 };
952
+ let i = linkSpaceEnd(text, destination.end);
953
+ const title = i > destination.end ? matchAt(LINK_TITLE, text, i) : null;
954
+ if (title)
955
+ i = linkSpaceEnd(text, i + title[0].length);
956
+ return text[i] === ")" ? { url: href, end: i + 1 } : { resume: i + 1 };
957
+ }
958
+ /**
959
+ * Where the link or image whose label opens at `open` and closes at `close`
960
+ * links, as markdown-it's link and image rules read it: the
961
+ * `(destination "title")` after it, or else a reference — and, for a link,
962
+ * a reference also where that inline form is refused, read from where the
963
+ * refusal leaves it. markdown-it's image rule takes no reference there.
964
+ */
965
+ function linkTarget(text, open, close, state, level, image) {
966
+ if (text[close + 1] !== "(")
967
+ return reference(text, open, close, close + 1, state, level);
968
+ const destination = linkDestination(text, close + 1);
969
+ if (destination === undefined || "url" in destination)
970
+ return destination;
971
+ return image ? undefined : reference(text, open, close, destination.resume, state, level);
972
+ }
973
+ /**
974
+ * The definition a reference names, as markdown-it's link and image rules
975
+ * resolve one: a full reference's own label, in the brackets at `from` — or,
976
+ * where those are empty or no brackets close there, the label from `open` to
977
+ * `close`, a collapsed or shortcut reference. The url its definition gives, and
978
+ * the index past the reference; nothing where no definition has its label.
979
+ */
980
+ function reference(text, open, close, from, state, level) {
981
+ const fullClose = text[from] === "[" ? state.labelClose(text, from, level, false) : undefined;
982
+ const full = fullClose === undefined ? "" : text.slice(from + 1, fullClose);
983
+ const url = state.definitions.get(normalizeReference(full || text.slice(open + 1, close)));
984
+ return url === undefined ? undefined : { url, end: fullClose === undefined ? close + 1 : fullClose + 1 };
985
+ }
986
+ /** The index past the `LINK_SPACE` at `i` in `text`. */
987
+ function linkSpaceEnd(text, i) {
988
+ return i + matchAt(LINK_SPACE, text, i)[0].length;
989
+ }
990
+ /**
991
+ * The link destination at `start` in `text`, as markdown-it's
992
+ * `parseLinkDestination` reads one, its escapes still in it, and the index
993
+ * past it. In angle brackets it is anything up to the `>` but a line ending or
994
+ * a `<`; bare, it runs to a space or an ASCII control character, or a `)` it
995
+ * did not open, and its parentheses must balance and nest no deeper than
996
+ * `MAX_DESTINATION_NESTING` — the cap that keeps a run of `](` from each
997
+ * scanning to the end of the text. A backslash takes the character after it,
998
+ * but for a space, which ends a bare destination before the backslash. An
999
+ * empty one reads as `""`, where markdown-it reads none: its link rule takes
1000
+ * the `)` after it all the same, with the same empty href.
1001
+ */
1002
+ function destinationAt(text, start) {
1003
+ if (text[start] === "<") {
1004
+ for (let i = start + 1; i < text.length; i++) {
1005
+ if (text[i] === "\n" || text[i] === "<")
1006
+ return undefined;
1007
+ if (text[i] === ">")
1008
+ return { raw: text.slice(start + 1, i), end: i + 1 };
1009
+ if (text[i] === "\\")
1010
+ i++;
1011
+ }
1012
+ return undefined;
1013
+ }
1014
+ let level = 0;
1015
+ let i = start;
1016
+ for (; i < text.length; i++) {
1017
+ const code = text.charCodeAt(i);
1018
+ if (code <= 0x20 || code === 0x7f)
1019
+ break;
1020
+ if (code === 0x5c && i + 1 < text.length) {
1021
+ if (text[i + 1] === " ")
1022
+ break;
1023
+ i++;
1024
+ }
1025
+ else if (code === 0x28) {
1026
+ level++;
1027
+ if (level > MAX_DESTINATION_NESTING)
1028
+ return undefined;
1029
+ }
1030
+ else if (code === 0x29) {
1031
+ if (level === 0)
1032
+ break;
1033
+ level--;
1034
+ }
1035
+ }
1036
+ return level === 0 ? { raw: text.slice(start, i), end: i } : undefined;
1037
+ }
1038
+ /** The character before `i` in `text`, a surrogate pair whole, or a space at its start, as `scanDelims` reads the line. */
1039
+ function charBefore(text, i) {
1040
+ if (i === 0)
1041
+ return " ";
1042
+ return i >= 2 && text.codePointAt(i - 2) > 0xffff ? text.slice(i - 2, i) : text[i - 1];
1043
+ }
1044
+ /**
1045
+ * The run of `*`, `_` or `~` starting at `i` in `text`, as markdown-it's
1046
+ * `scanDelims` reads it. A run is left-flanking where what follows it is no
1047
+ * space, and is punctuation only if what precedes it is a space or
1048
+ * punctuation too; right-flanking is the mirror. A `*` or `~` run opens where
1049
+ * it is left-flanking and closes where it is right-flanking; a `_` run may
1050
+ * not do either inside a word. A `~` run is strikethrough's, which takes its
1051
+ * tildes two at a time and none at all from a run of one — that `~` is text.
1052
+ */
1053
+ function delimiterRun(text, i) {
1054
+ const marker = text[i];
1055
+ let end = i;
1056
+ while (text[end] === marker)
1057
+ end++;
1058
+ const before = charBefore(text, i);
1059
+ const after = end < text.length ? String.fromCodePoint(text.codePointAt(end)) : " ";
1060
+ const [spaceBefore, spaceAfter] = [WHITESPACE.test(before), WHITESPACE.test(after)];
1061
+ const [punctBefore, punctAfter] = [PUNCTUATION.test(before), PUNCTUATION.test(after)];
1062
+ const left = !spaceAfter && (!punctAfter || spaceBefore || punctBefore);
1063
+ const right = !spaceBefore && (!punctBefore || spaceAfter || punctAfter);
1064
+ const inWord = marker !== "_";
1065
+ const length = end - i;
1066
+ const strike = marker === "~";
1067
+ if (strike && length < 2)
1068
+ return undefined;
1069
+ return {
1070
+ kind: "delimiters",
1071
+ marker,
1072
+ unit: strike ? "~~" : marker,
1073
+ count: strike ? Math.floor(length / 2) : length,
1074
+ length: strike ? 0 : length,
1075
+ lone: strike && length % 2 === 1,
1076
+ canOpen: left && (inWord || !right || punctBefore),
1077
+ canClose: right && (inWord || !left || punctAfter),
1078
+ end,
1079
+ };
1080
+ }
1081
+ /**
1082
+ * Each delimiter that can close paired with the nearest one before it that
1083
+ * can open, as markdown-it's `balance_pairs` pairs them, so the pairs nest:
1084
+ * the same marker, never one of its own run, and not where either can do both
1085
+ * and the runs' lengths sum to a multiple of 3, unless both lengths are.
1086
+ * `jumps` skips what a pairing has enclosed and `openersBottom` the openers a
1087
+ * closer like this one already failed to find, so the pass is linear.
1088
+ */
1089
+ function balancePairs(delimiters) {
1090
+ const openersBottom = { "*": [-1, -1, -1, -1, -1, -1], _: [-1, -1, -1, -1, -1, -1], "~": [-1, -1, -1, -1, -1, -1] };
1091
+ const jumps = [];
1092
+ let headerIdx = 0;
1093
+ let lastPiece = -2;
1094
+ for (const [closerIdx, closer] of delimiters.entries()) {
1095
+ jumps.push(0);
1096
+ // headerIdx is the first delimiter of the closer's own run.
1097
+ if (delimiters[headerIdx].marker !== closer.marker || lastPiece !== closer.piece - 1)
1098
+ headerIdx = closerIdx;
1099
+ lastPiece = closer.piece;
1100
+ if (!closer.close)
1101
+ continue;
1102
+ const bottom = (closer.open ? 3 : 0) + (closer.length % 3);
1103
+ let openerIdx = headerIdx - jumps[headerIdx] - 1;
1104
+ let newBottom = openerIdx;
1105
+ for (; openerIdx > openersBottom[closer.marker][bottom]; openerIdx -= jumps[openerIdx] + 1) {
1106
+ const opener = delimiters[openerIdx];
1107
+ if (opener.marker !== closer.marker || !opener.open || opener.end >= 0)
1108
+ continue;
1109
+ const oddMatch = (opener.close || closer.open) &&
1110
+ (opener.length + closer.length) % 3 === 0 &&
1111
+ (opener.length % 3 !== 0 || closer.length % 3 !== 0);
1112
+ if (oddMatch)
1113
+ continue;
1114
+ const lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? jumps[openerIdx - 1] + 1 : 0;
1115
+ jumps[closerIdx] = closerIdx - openerIdx + lastJump;
1116
+ jumps[openerIdx] = lastJump;
1117
+ closer.open = false;
1118
+ opener.end = closerIdx;
1119
+ opener.close = false;
1120
+ newBottom = -1;
1121
+ lastPiece = -2;
1122
+ break;
1123
+ }
1124
+ if (newBottom !== -1)
1125
+ openersBottom[closer.marker][bottom] = newBottom;
1126
+ }
1127
+ }
1128
+ /**
1129
+ * `pieces` with each pair of delimiters `balancePairs` made into emphasis, as
1130
+ * markdown-it's strikethrough and emphasis `postProcess` make them: a `~~`
1131
+ * pair `s`; and, innermost pair first, a `*` or `_` pair `strong` where the
1132
+ * pair enclosing it is of the same marker and touches it at both ends, so the
1133
+ * two are spent as one, and `em` otherwise. The `lone` `~` of an odd run, met
1134
+ * just before a `~~` that closes, is moved past every `~~` closing beside it,
1135
+ * as strikethrough's is, so `x ~~~a~~~` strikes `a` between two plain `~`. A
1136
+ * delimiter left unpaired is the text it is, joined to the text beside it as
1137
+ * markdown-it's `fragments_join` joins it, so a run of text between line
1138
+ * endings is one piece.
1139
+ */
1140
+ function emphasized(pieces, delimiters, lone) {
1141
+ const roles = new Map();
1142
+ const strikeCloses = new Set();
1143
+ for (let i = delimiters.length - 1; i >= 0; i--) {
1144
+ const opener = delimiters[i];
1145
+ if (opener.end < 0)
1146
+ continue;
1147
+ const closer = delimiters[opener.end];
1148
+ const outer = delimiters[i - 1];
1149
+ const strike = opener.marker === "~";
1150
+ const strong = !strike &&
1151
+ outer !== undefined &&
1152
+ outer.end === opener.end + 1 &&
1153
+ outer.marker === opener.marker &&
1154
+ outer.piece === opener.piece - 1 &&
1155
+ delimiters[opener.end + 1].piece === closer.piece + 1;
1156
+ roles.set(opener.piece, strike ? "s" : strong ? "strong" : "em").set(closer.piece, "close");
1157
+ if (strike)
1158
+ strikeCloses.add(closer.piece);
1159
+ if (strong) {
1160
+ roles.set(outer.piece, "spent").set(delimiters[opener.end + 1].piece, "spent");
1161
+ i--;
1162
+ }
1163
+ }
1164
+ // The pieces in the order they are read, each lone `~` swapped with the
1165
+ // last `~~` of the closing run after it, as strikethrough's postProcess swaps
1166
+ // its tokens.
1167
+ const order = pieces.map((_, p) => p);
1168
+ for (const p of [...lone].filter((p) => strikeCloses.has(p + 1))) {
1169
+ let last = p + 1;
1170
+ while (strikeCloses.has(last + 1))
1171
+ last++;
1172
+ [order[p], order[last]] = [order[last], order[p]];
1173
+ }
1174
+ const open = [[]];
1175
+ const tags = [];
1176
+ for (const p of order) {
1177
+ const piece = pieces[p];
1178
+ const role = roles.get(p);
1179
+ switch (role) {
1180
+ case "em":
1181
+ case "strong":
1182
+ case "s":
1183
+ tags.push(role);
1184
+ open.push([]);
1185
+ break;
1186
+ case "close": {
1187
+ const children = open.pop();
1188
+ open.at(-1).push({ kind: "emphasis", tag: tags.pop(), children });
1189
+ break;
1190
+ }
1191
+ case "spent":
1192
+ break;
1193
+ case undefined: {
1194
+ const into = open.at(-1);
1195
+ const last = into.at(-1);
1196
+ if (piece.kind === "text" && last?.kind === "text")
1197
+ into[into.length - 1] = { kind: "text", text: last.text + piece.text };
1198
+ else
1199
+ into.push(piece);
1200
+ }
1201
+ }
1202
+ }
1203
+ return open[0];
1204
+ }
1205
+ /**
1206
+ * The autolink whose `<` is at `i` in `text`, as markdown-it reads one: the
1207
+ * text up to the first `>`, holding no `<`, a URI or an email address that
1208
+ * links to `mailto:` it — and so long as markdown-it accepts that href.
1209
+ */
1210
+ function autolinkAt(text, i) {
1211
+ const angled = matchAt(AUTOLINK, text, i);
1212
+ if (!angled)
1213
+ return undefined;
1214
+ const source = angled[1];
1215
+ const href = AUTOLINK_URI.test(source)
1216
+ ? linkHref(source)
1217
+ : AUTOLINK_EMAIL.test(source)
1218
+ ? linkHref(`mailto:${source}`)
1219
+ : undefined;
1220
+ return href === undefined ? undefined : { kind: "autolink", source, url: href, end: i + angled[0].length };
1221
+ }
1222
+ /** Where the text of the line starting at `from` starts: past its spaces and tabs, which markdown-it's newline and escape rules skip after a line ending. */
1223
+ function nextLine(text, from) {
1224
+ return from + matchAt(LINE_INDENT, text, from)[0].length;
1225
+ }
1226
+ /** The inline construct opening at `i` in `text`, if one does, read at `level` in `state`. */
1227
+ function inlineAt(text, i, state, level) {
1228
+ switch (text[i]) {
1229
+ case "`": {
1230
+ const ticks = matchAt(TICKS, text, i)[0];
1231
+ const close = state.codeClose(text, i + ticks.length, ticks.length);
1232
+ if (close === undefined)
1233
+ return { kind: "text", text: ticks, end: i + ticks.length };
1234
+ // A code span's line endings are spaces, and a space each side pads it unless `str.strip` empties it, as markdown-it-py's backticks rule has them. markdown-it in JS strips by `/^ (.+) $/` and so pads an all-space span too.
1235
+ const code = text.slice(i + ticks.length, close).replaceAll("\n", " ");
1236
+ const padded = code.startsWith(" ") && code.endsWith(" ") && pyStrip(code) !== "";
1237
+ return { kind: "code", code: padded ? code.slice(1, -1) : code, end: close + ticks.length };
1238
+ }
1239
+ case "*":
1240
+ case "_":
1241
+ case "~":
1242
+ return delimiterRun(text, i);
1243
+ case "&":
1244
+ return referenceAt(text, i);
1245
+ case "\n":
1246
+ return { kind: "line_ending", end: nextLine(text, i + 1) };
1247
+ case "\\": {
1248
+ // markdown-it's escape rule: a backslash before a line ending breaks it
1249
+ // hard, and one before anything else takes that character with it, the
1250
+ // pair standing as written unless it escapes — so a space after it
1251
+ // counts toward no hard break.
1252
+ if (text[i + 1] === "\n")
1253
+ return { kind: "hardbreak", end: nextLine(text, i + 2) };
1254
+ const escape = matchAt(ESCAPE, text, i);
1255
+ if (escape)
1256
+ return { kind: "text", text: escape[1], end: i + escape[0].length };
1257
+ if (i + 1 === text.length)
1258
+ return undefined;
1259
+ const next = String.fromCodePoint(text.codePointAt(i + 1));
1260
+ return { kind: "text", text: `\\${next}`, end: i + 1 + next.length };
1261
+ }
1262
+ case "<": {
1263
+ // markdown-it tries an autolink before inline HTML.
1264
+ const autolink = autolinkAt(text, i);
1265
+ if (autolink)
1266
+ return autolink;
1267
+ const tag = matchAt(HTML_TAG, text, i);
1268
+ return tag ? { kind: "html", content: tag[0], end: i + tag[0].length } : undefined;
1269
+ }
1270
+ case "!":
1271
+ case "[": {
1272
+ const image = text[i] === "!";
1273
+ const open = image ? i + 1 : i;
1274
+ if (text[open] !== "[")
1275
+ return undefined;
1276
+ const close = state.labelClose(text, open, level, !image);
1277
+ if (close === undefined)
1278
+ return undefined;
1279
+ const target = linkTarget(text, open, close, state, level, image);
1280
+ if (!target)
1281
+ return undefined;
1282
+ return image
1283
+ ? { kind: "image", alt: text.slice(open + 1, close), src: target.url, end: target.end }
1284
+ : { kind: "link", label: open + 1, close, url: target.url, end: target.end };
1285
+ }
1286
+ default:
1287
+ return undefined;
1288
+ }
1289
+ }
1290
+ /**
1291
+ * `text` from `from` to its end read into inlines, as markdown-it's inline
1292
+ * pass reads it: each construct `inlineAt` finds, the text between them as it
1293
+ * is, and its delimiters then paired into emphasis. `text` runs on before
1294
+ * `from` as the block's own text does, so a delimiter run reads what borders
1295
+ * it there; `level` is how many links the text sits in, where a label scan
1296
+ * starts counting.
1297
+ */
1298
+ function inlines(text, from, state, level) {
1299
+ const pieces = [];
1300
+ const delimiters = [];
1301
+ // The pieces that are an odd `~` run's lone `~`.
1302
+ const lone = new Set();
1303
+ let plain = from;
1304
+ for (let i = from; i < text.length;) {
1305
+ const found = inlineAt(text, i, state, level);
1306
+ if (!found) {
1307
+ i++;
1308
+ continue;
1309
+ }
1310
+ // markdown-it's newline rule: the spaces ending the text before a line
1311
+ // ending are dropped, and two or more of them make the break hard.
1312
+ let kept = i;
1313
+ if (found.kind === "line_ending")
1314
+ while (kept > plain && text[kept - 1] === " ")
1315
+ kept--;
1316
+ if (plain < kept)
1317
+ pieces.push({ kind: "text", text: text.slice(plain, kept) });
1318
+ if (found.kind === "line_ending")
1319
+ pieces.push({ kind: i - kept >= 2 ? "hardbreak" : "softbreak" });
1320
+ else if (found.kind === "delimiters") {
1321
+ if (found.lone)
1322
+ lone.add(pieces.push({ kind: "text", text: "~" }) - 1);
1323
+ for (let k = 0; k < found.count; k++) {
1324
+ delimiters.push({ marker: found.marker, length: found.length, piece: pieces.length, open: found.canOpen, close: found.canClose, end: -1 });
1325
+ pieces.push({ kind: "text", text: found.unit });
1326
+ }
1327
+ }
1328
+ else
1329
+ pieces.push(matched(found, text, state, level));
1330
+ i = plain = found.end;
1331
+ }
1332
+ if (plain < text.length)
1333
+ pieces.push({ kind: "text", text: text.slice(plain) });
1334
+ balancePairs(delimiters);
1335
+ return emphasized(pieces, delimiters, lone);
1336
+ }
1337
+ /** `text` read into inlines from its start, its references resolved against `definitions`. */
1338
+ function parseInline(text, definitions) {
1339
+ return inlines(text, 0, new InlineState(definitions, text), 0);
1340
+ }
1341
+ /**
1342
+ * The inline `found` in `text` is, a link's label read into inlines of its
1343
+ * own: the block's text up to the label's `]`, so the delimiters inside it
1344
+ * pair only with each other, as markdown-it's are. The text ends where
1345
+ * markdown-it's `posMax` does, so a run at the label's end borders the end of
1346
+ * the text, as `scanDelims` reads it, and not the `]`.
1347
+ */
1348
+ function matched(found, text, state, level) {
1349
+ switch (found.kind) {
1350
+ case "link":
1351
+ return { kind: "link", children: inlines(text.slice(0, found.close), found.label, state, level + 1), url: found.url };
1352
+ case "autolink":
1353
+ return { kind: "autolink", text: normalizeLinkText(found.source), url: found.url };
1354
+ case "code":
1355
+ return { kind: "code", code: found.code };
1356
+ case "image":
1357
+ return { kind: "image", alt: found.alt, src: found.src };
1358
+ case "text":
1359
+ return { kind: "text", text: found.text };
1360
+ case "html":
1361
+ return { kind: "html", content: found.content };
1362
+ case "hardbreak":
1363
+ return { kind: "hardbreak" };
1364
+ }
1365
+ }
1366
+ /**
1367
+ * The link reference definitions among `tokens` and every container they
1368
+ * hold, in the order the source gives them, each label defined by the first
1369
+ * definition of it, as markdown-it keeps them.
1370
+ */
1371
+ function definitionsIn(tokens, into = new Map()) {
1372
+ for (const token of tokens) {
1373
+ switch (token.type) {
1374
+ case "definition":
1375
+ if (!into.has(token.label))
1376
+ into.set(token.label, token.url);
1377
+ break;
1378
+ case "list":
1379
+ for (const item of token.items)
1380
+ definitionsIn(item, into);
1381
+ break;
1382
+ case "blockquote":
1383
+ definitionsIn(token.children, into);
1384
+ break;
1385
+ }
1386
+ }
1387
+ return into;
1388
+ }
1389
+ /** The blocks among `tokens`, their marks dropped, with their text read into inlines: markdown-it's inline pass. */
1390
+ function inlined(tokens, definitions) {
1391
+ return tokens
1392
+ .filter((token) => token.type !== "unread" && token.type !== "definition")
1393
+ .map((token) => inlinedBlock(token, definitions));
1394
+ }
1395
+ /** `token` with its text, and the text of every block inside it, read into inlines. */
1396
+ function inlinedBlock(token, definitions) {
1397
+ const parse = (text) => parseInline(text, definitions);
1398
+ switch (token.type) {
1399
+ case "heading":
1400
+ case "paragraph":
1401
+ return { ...token, text: parse(token.text) };
1402
+ case "list":
1403
+ return { ...token, items: token.items.map((item) => inlined(item, definitions)) };
1404
+ case "blockquote":
1405
+ return { ...token, children: inlined(token.children, definitions) };
1406
+ case "table":
1407
+ return {
1408
+ ...token,
1409
+ columns: token.columns.map((column) => ({ ...column, header: parse(column.header) })),
1410
+ rows: token.rows.map((row) => row.map(parse)),
1411
+ };
1412
+ default:
1413
+ return token;
1414
+ }
1415
+ }
1416
+ /** `source` read as Markdown: its blocks, and the text in each read into inlines. A line ends at `\r\n`, `\r` or `\n`, as markdown-it normalizes them. */
1417
+ export function parseMarkdown(source) {
1418
+ const lines = source.split(/\r\n?|\n/).map(sourceLine);
1419
+ const { tokens } = tokenize(new Lines(lines.values()), 0);
1420
+ return inlined(tokens, definitionsIn(tokens));
1421
+ }