@rezi-ui/core 0.1.0-beta.1 → 0.1.0-beta.2

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 (45) hide show
  1. package/dist/abi.d.ts +1 -1
  2. package/dist/abi.js +1 -1
  3. package/dist/app/createApp.js +32 -2
  4. package/dist/app/createApp.js.map +1 -1
  5. package/dist/app/inspectorOverlayHelper.d.ts.map +1 -1
  6. package/dist/app/inspectorOverlayHelper.js +3 -0
  7. package/dist/app/inspectorOverlayHelper.js.map +1 -1
  8. package/dist/app/types.d.ts +6 -0
  9. package/dist/app/types.d.ts.map +1 -1
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/renderer/renderToDrawlist/boxBorder.d.ts.map +1 -1
  15. package/dist/renderer/renderToDrawlist/boxBorder.js +5 -2
  16. package/dist/renderer/renderToDrawlist/boxBorder.js.map +1 -1
  17. package/dist/widgets/markdown/ast.d.ts +71 -0
  18. package/dist/widgets/markdown/ast.d.ts.map +1 -0
  19. package/dist/widgets/markdown/ast.js +10 -0
  20. package/dist/widgets/markdown/ast.js.map +1 -0
  21. package/dist/widgets/markdown/index.d.ts +25 -0
  22. package/dist/widgets/markdown/index.d.ts.map +1 -0
  23. package/dist/widgets/markdown/index.js +32 -0
  24. package/dist/widgets/markdown/index.js.map +1 -0
  25. package/dist/widgets/markdown/parse.d.ts +39 -0
  26. package/dist/widgets/markdown/parse.d.ts.map +1 -0
  27. package/dist/widgets/markdown/parse.js +791 -0
  28. package/dist/widgets/markdown/parse.js.map +1 -0
  29. package/dist/widgets/markdown/render.d.ts +33 -0
  30. package/dist/widgets/markdown/render.d.ts.map +1 -0
  31. package/dist/widgets/markdown/render.js +308 -0
  32. package/dist/widgets/markdown/render.js.map +1 -0
  33. package/dist/widgets/markdown/stream.d.ts +46 -0
  34. package/dist/widgets/markdown/stream.d.ts.map +1 -0
  35. package/dist/widgets/markdown/stream.js +124 -0
  36. package/dist/widgets/markdown/stream.js.map +1 -0
  37. package/dist/widgets/types/base.d.ts +11 -0
  38. package/dist/widgets/types/base.d.ts.map +1 -1
  39. package/dist/widgets/types.d.ts +1 -1
  40. package/dist/widgets/types.d.ts.map +1 -1
  41. package/dist/widgets/ui.d.ts +2 -0
  42. package/dist/widgets/ui.d.ts.map +1 -1
  43. package/dist/widgets/ui.js +2 -0
  44. package/dist/widgets/ui.js.map +1 -1
  45. package/package.json +2 -2
@@ -0,0 +1,791 @@
1
+ /**
2
+ * packages/core/src/widgets/markdown/parse.ts — GFM-subset markdown parser.
3
+ *
4
+ * Why: ui.markdown() needs a dependency-free, deterministic parser that is
5
+ * safe on untrusted input (PR bodies, agent output). The grammar is a
6
+ * pragmatic GitHub-Flavored-Markdown subset:
7
+ *
8
+ * blocks: ATX headings, paragraphs, fenced code, indented code,
9
+ * blockquotes, ordered/unordered lists (nested), task items,
10
+ * thematic breaks, pipe tables
11
+ * inlines: **strong**, *em*, ~~del~~, `code`, [text](url), <autolinks>,
12
+ * bare http(s) URLs, hard breaks, backslash escapes, and basic
13
+ * HTML entities
14
+ *
15
+ * Intentional divergences from full GFM, kept simple on purpose:
16
+ * - no setext headings, reference links, images, footnotes, or raw HTML
17
+ * (HTML tags render as literal text)
18
+ * - no lazy paragraph continuation inside blockquotes or list items
19
+ * - the CommonMark emphasis algorithm is approximated with flanking checks
20
+ * - table delimiter rows must contain at least one `|`
21
+ *
22
+ * The parser never throws. Malformed constructs degrade to literal text,
23
+ * nesting depth is bounded, and inline scanning runs on a work budget so
24
+ * adversarial input (for example long runs of `*` or `[`) cannot trigger
25
+ * quadratic blowups.
26
+ */
27
+ const MAX_INLINE_DEPTH = 16;
28
+ const MAX_BLOCK_DEPTH = 16;
29
+ /** Multiplier for the per-call inline scanning budget (see module docs). */
30
+ const INLINE_BUDGET_PER_CHAR = 64;
31
+ const ASCII_PUNCT = new Set("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
32
+ const ATX_RE = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
33
+ const HR_RE = /^ {0,3}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
34
+ const FENCE_OPEN_RE = /^( {0,3})(`{3,}|~{3,})[ \t]*(.*)$/;
35
+ const BLOCKQUOTE_RE = /^ {0,3}> ?(.*)$/;
36
+ const LIST_RE = /^( {0,3})(?:([-*+])|(\d{1,9})([.)]))( +)(.*)$/;
37
+ const TABLE_DELIM_RE = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
38
+ const INDENTED_CODE_RE = /^(?: {4}|\t)/;
39
+ const TASK_ITEM_RE = /^\[( |x|X)\][ \t]+(.*)$/;
40
+ const NAMED_ENTITIES = Object.freeze({
41
+ amp: "&",
42
+ lt: "<",
43
+ gt: ">",
44
+ quot: '"',
45
+ apos: "'",
46
+ nbsp: " ",
47
+ });
48
+ function isWhitespace(ch) {
49
+ return ch === " " || ch === "\t" || ch === "\n";
50
+ }
51
+ function isWordChar(ch) {
52
+ return ch !== undefined && /[A-Za-z0-9]/.test(ch);
53
+ }
54
+ function leadingSpaces(line) {
55
+ let n = 0;
56
+ while (n < line.length && line[n] === " ")
57
+ n++;
58
+ return n;
59
+ }
60
+ function decodeEntities(text) {
61
+ if (!text.includes("&"))
62
+ return text;
63
+ return text.replace(/&(#[xX]?[0-9a-fA-F]{1,6}|[a-zA-Z]{2,6});/g, (match, body) => {
64
+ if (body.startsWith("#")) {
65
+ const hex = body[1] === "x" || body[1] === "X";
66
+ const digits = hex ? body.slice(2) : body.slice(1);
67
+ if (digits.length === 0)
68
+ return match;
69
+ if (!hex && !/^[0-9]+$/.test(digits))
70
+ return match;
71
+ const code = Number.parseInt(digits, hex ? 16 : 10);
72
+ if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff)
73
+ return "�";
74
+ if (code >= 0xd800 && code <= 0xdfff)
75
+ return "�";
76
+ return String.fromCodePoint(code);
77
+ }
78
+ const named = NAMED_ENTITIES[body.toLowerCase()];
79
+ return named ?? match;
80
+ });
81
+ }
82
+ function pushText(out, text) {
83
+ if (text.length === 0)
84
+ return;
85
+ const last = out[out.length - 1];
86
+ if (last !== undefined && last.kind === "text") {
87
+ out[out.length - 1] = { kind: "text", text: last.text + text };
88
+ return;
89
+ }
90
+ out.push({ kind: "text", text });
91
+ }
92
+ function scanRun(input, start, ch) {
93
+ let i = start;
94
+ while (i < input.length && input[i] === ch)
95
+ i++;
96
+ return i - start;
97
+ }
98
+ function findBacktickClose(input, from, runLen, ctx) {
99
+ let i = from;
100
+ while (i < input.length) {
101
+ if (ctx.budget-- <= 0)
102
+ return -1;
103
+ if (input[i] === "`") {
104
+ const len = scanRun(input, i, "`");
105
+ if (len === runLen)
106
+ return i;
107
+ i += len;
108
+ continue;
109
+ }
110
+ i++;
111
+ }
112
+ return -1;
113
+ }
114
+ /**
115
+ * Finds a valid emphasis closing run at or after `from`. Closer validity is
116
+ * position-local (it does not depend on the opener), so a failed search is
117
+ * memoized per marker+need to keep adversarial inputs linear.
118
+ */
119
+ function findEmphasisClose(input, from, marker, need, ctx) {
120
+ const memoKey = `${marker}${need}`;
121
+ const knownEmptyFrom = ctx.noCloserFrom.get(memoKey);
122
+ if (knownEmptyFrom !== undefined && from >= knownEmptyFrom)
123
+ return -1;
124
+ let i = from;
125
+ while (i < input.length) {
126
+ if (ctx.budget-- <= 0)
127
+ return -1;
128
+ const ch = input[i];
129
+ if (ch === "\\") {
130
+ i += 2;
131
+ continue;
132
+ }
133
+ if (ch === "`") {
134
+ const run = scanRun(input, i, "`");
135
+ const close = findBacktickClose(input, i + run, run, ctx);
136
+ i = close >= 0 ? close + run : i + run;
137
+ continue;
138
+ }
139
+ if (ch === marker) {
140
+ const run = scanRun(input, i, marker);
141
+ const prev = input[i - 1];
142
+ const next = input[i + run];
143
+ const prevOk = prev !== undefined && !isWhitespace(prev);
144
+ const nextOk = marker !== "_" || !isWordChar(next);
145
+ // Consume the LAST `need` markers of a longer closing run so leading
146
+ // extras stay inside the content (closes `**bold *nested***` cleanly).
147
+ if (run >= need && prevOk && nextOk && i > from)
148
+ return i + (run - need);
149
+ i += run;
150
+ continue;
151
+ }
152
+ i++;
153
+ }
154
+ ctx.noCloserFrom.set(memoKey, from);
155
+ return -1;
156
+ }
157
+ function tryParseEmphasis(input, start, marker, depth, ctx) {
158
+ const run = scanRun(input, start, marker);
159
+ const after = input[start + run];
160
+ if (after === undefined || isWhitespace(after))
161
+ return null;
162
+ if (marker === "_" && isWordChar(input[start - 1]))
163
+ return null;
164
+ // Opening markers beyond the consumed delimiter re-emit as literal text
165
+ // (full CommonMark would nest them; this subset keeps them visible).
166
+ if (run >= 3) {
167
+ const close = findEmphasisClose(input, start + run, marker, 3, ctx);
168
+ if (close > start + run) {
169
+ const inner = parseInlines(input.slice(start + run, close), depth + 1, ctx);
170
+ const strong = { kind: "strong", children: inner };
171
+ return {
172
+ node: { kind: "em", children: [strong] },
173
+ end: close + 3,
174
+ prefix: marker.repeat(run - 3),
175
+ };
176
+ }
177
+ }
178
+ if (run >= 2) {
179
+ const close = findEmphasisClose(input, start + run, marker, 2, ctx);
180
+ if (close > start + run) {
181
+ const inner = parseInlines(input.slice(start + run, close), depth + 1, ctx);
182
+ return {
183
+ node: { kind: "strong", children: inner },
184
+ end: close + 2,
185
+ prefix: marker.repeat(run - 2),
186
+ };
187
+ }
188
+ }
189
+ const close = findEmphasisClose(input, start + run, marker, 1, ctx);
190
+ if (close > start + run) {
191
+ const inner = parseInlines(input.slice(start + run, close), depth + 1, ctx);
192
+ return {
193
+ node: { kind: "em", children: inner },
194
+ end: close + 1,
195
+ prefix: marker.repeat(run - 1),
196
+ };
197
+ }
198
+ return null;
199
+ }
200
+ function tryParseDel(input, start, depth, ctx) {
201
+ const contentStart = start + 2;
202
+ const after = input[contentStart];
203
+ if (after === undefined || isWhitespace(after))
204
+ return null;
205
+ let i = contentStart;
206
+ while (i < input.length) {
207
+ if (ctx.budget-- <= 0)
208
+ return null;
209
+ const ch = input[i];
210
+ if (ch === "\\") {
211
+ i += 2;
212
+ continue;
213
+ }
214
+ if (ch === "`") {
215
+ const run = scanRun(input, i, "`");
216
+ const close = findBacktickClose(input, i + run, run, ctx);
217
+ i = close >= 0 ? close + run : i + run;
218
+ continue;
219
+ }
220
+ if (ch === "~" && input[i + 1] === "~") {
221
+ const prev = input[i - 1];
222
+ if (prev !== undefined && !isWhitespace(prev) && i > contentStart) {
223
+ const inner = parseInlines(input.slice(contentStart, i), depth + 1, ctx);
224
+ return { node: { kind: "del", children: inner }, end: i + 2 };
225
+ }
226
+ i += 2;
227
+ continue;
228
+ }
229
+ i++;
230
+ }
231
+ return null;
232
+ }
233
+ function tryParseLink(input, start, depth, ctx) {
234
+ let i = start + 1;
235
+ let bracketDepth = 1;
236
+ while (i < input.length) {
237
+ if (ctx.budget-- <= 0)
238
+ return null;
239
+ const ch = input[i];
240
+ if (ch === "\\") {
241
+ i += 2;
242
+ continue;
243
+ }
244
+ if (ch === "[")
245
+ bracketDepth++;
246
+ else if (ch === "]") {
247
+ bracketDepth--;
248
+ if (bracketDepth === 0)
249
+ break;
250
+ }
251
+ i++;
252
+ }
253
+ if (i >= input.length || bracketDepth !== 0)
254
+ return null;
255
+ const labelEnd = i;
256
+ if (input[labelEnd + 1] !== "(")
257
+ return null;
258
+ let j = labelEnd + 2;
259
+ let parenDepth = 1;
260
+ while (j < input.length) {
261
+ if (ctx.budget-- <= 0)
262
+ return null;
263
+ const ch = input[j];
264
+ if (ch === "\\") {
265
+ j += 2;
266
+ continue;
267
+ }
268
+ if (ch === "(")
269
+ parenDepth++;
270
+ else if (ch === ")") {
271
+ parenDepth--;
272
+ if (parenDepth === 0)
273
+ break;
274
+ }
275
+ j++;
276
+ }
277
+ if (j >= input.length || parenDepth !== 0)
278
+ return null;
279
+ let target = input.slice(labelEnd + 2, j).trim();
280
+ const titled = /^(\S+)[ \t]+["'][^"']*["']$/.exec(target);
281
+ const titledTarget = titled?.[1];
282
+ if (titledTarget !== undefined)
283
+ target = titledTarget;
284
+ if (target.startsWith("<") && target.endsWith(">") && target.length >= 2) {
285
+ target = target.slice(1, -1);
286
+ }
287
+ const label = input.slice(start + 1, labelEnd);
288
+ const children = label.length === 0
289
+ ? [{ kind: "text", text: target }]
290
+ : parseInlines(label, depth + 1, ctx);
291
+ return { node: { kind: "link", href: target, children }, end: j + 1 };
292
+ }
293
+ function countChar(text, ch) {
294
+ let n = 0;
295
+ for (const c of text)
296
+ if (c === ch)
297
+ n++;
298
+ return n;
299
+ }
300
+ /** Strips trailing punctuation that is conventionally not part of a bare URL. */
301
+ function trimUrlTrailing(url) {
302
+ let out = url;
303
+ for (;;) {
304
+ const last = out[out.length - 1];
305
+ if (last === undefined)
306
+ break;
307
+ if (/[.,;:!?'"]/.test(last)) {
308
+ out = out.slice(0, -1);
309
+ continue;
310
+ }
311
+ if (last === ")" && countChar(out, ")") > countChar(out, "(")) {
312
+ out = out.slice(0, -1);
313
+ continue;
314
+ }
315
+ break;
316
+ }
317
+ return out;
318
+ }
319
+ function tryParseBareUrl(input, start) {
320
+ if (!input.startsWith("http://", start) && !input.startsWith("https://", start))
321
+ return null;
322
+ if (isWordChar(input[start - 1]))
323
+ return null;
324
+ let end = start;
325
+ while (end < input.length) {
326
+ const ch = input[end];
327
+ if (ch === undefined || isWhitespace(ch) || ch === "<" || ch === ">")
328
+ break;
329
+ end++;
330
+ }
331
+ const url = trimUrlTrailing(input.slice(start, end));
332
+ if (!/^https?:\/\/\S+$/.test(url) || url.endsWith("//"))
333
+ return null;
334
+ return {
335
+ node: { kind: "link", href: url, children: [{ kind: "text", text: url }] },
336
+ end: start + url.length,
337
+ };
338
+ }
339
+ function parseInlines(input, depth, ctx) {
340
+ const out = [];
341
+ if (depth > MAX_INLINE_DEPTH) {
342
+ pushText(out, decodeEntities(input));
343
+ return out;
344
+ }
345
+ let plain = "";
346
+ let i = 0;
347
+ const flush = () => {
348
+ if (plain.length > 0) {
349
+ pushText(out, decodeEntities(plain));
350
+ plain = "";
351
+ }
352
+ };
353
+ while (i < input.length) {
354
+ const ch = input[i];
355
+ if (ch === undefined)
356
+ break;
357
+ if (ctx.budget-- <= 0) {
358
+ plain += input.slice(i);
359
+ break;
360
+ }
361
+ if (ch === "\\" && i + 1 < input.length) {
362
+ const next = input[i + 1];
363
+ if (next !== undefined && ASCII_PUNCT.has(next)) {
364
+ plain += next;
365
+ i += 2;
366
+ continue;
367
+ }
368
+ }
369
+ if (ch === "`") {
370
+ const run = scanRun(input, i, "`");
371
+ const close = findBacktickClose(input, i + run, run, ctx);
372
+ if (close >= 0) {
373
+ flush();
374
+ let content = input.slice(i + run, close).replace(/\n/g, " ");
375
+ if (content.length >= 2 &&
376
+ content.startsWith(" ") &&
377
+ content.endsWith(" ") &&
378
+ content.trim().length > 0) {
379
+ content = content.slice(1, -1);
380
+ }
381
+ out.push({ kind: "code", text: content });
382
+ i = close + run;
383
+ continue;
384
+ }
385
+ plain += input.slice(i, i + run);
386
+ i += run;
387
+ continue;
388
+ }
389
+ if (ch === "<") {
390
+ const m = /^<(https?:\/\/[^\s<>]+)>/.exec(input.slice(i));
391
+ const href = m?.[1];
392
+ if (m !== null && href !== undefined) {
393
+ flush();
394
+ out.push({ kind: "link", href, children: [{ kind: "text", text: href }] });
395
+ i += m[0].length;
396
+ continue;
397
+ }
398
+ }
399
+ if (ch === "[") {
400
+ const link = tryParseLink(input, i, depth, ctx);
401
+ if (link !== null) {
402
+ flush();
403
+ out.push(link.node);
404
+ i = link.end;
405
+ continue;
406
+ }
407
+ }
408
+ if (ch === "*" || ch === "_") {
409
+ const em = tryParseEmphasis(input, i, ch, depth, ctx);
410
+ if (em !== null) {
411
+ plain += em.prefix;
412
+ flush();
413
+ out.push(em.node);
414
+ i = em.end;
415
+ continue;
416
+ }
417
+ const run = scanRun(input, i, ch);
418
+ plain += input.slice(i, i + run);
419
+ i += run;
420
+ continue;
421
+ }
422
+ if (ch === "~" && input[i + 1] === "~") {
423
+ const del = tryParseDel(input, i, depth, ctx);
424
+ if (del !== null) {
425
+ flush();
426
+ out.push(del.node);
427
+ i = del.end;
428
+ continue;
429
+ }
430
+ plain += "~~";
431
+ i += 2;
432
+ continue;
433
+ }
434
+ if (ch === "h") {
435
+ const bare = tryParseBareUrl(input, i);
436
+ if (bare !== null) {
437
+ flush();
438
+ out.push(bare.node);
439
+ i = bare.end;
440
+ continue;
441
+ }
442
+ }
443
+ plain += ch;
444
+ i++;
445
+ }
446
+ flush();
447
+ return out;
448
+ }
449
+ function newInlineCtx(input) {
450
+ return { budget: input.length * INLINE_BUDGET_PER_CHAR + 1024, noCloserFrom: new Map() };
451
+ }
452
+ /** Parses one inline run with a fresh work budget. */
453
+ function parseInlineRun(input, depth) {
454
+ return parseInlines(input, depth, newInlineCtx(input));
455
+ }
456
+ function parseParagraphInlines(rawLines, depth) {
457
+ const out = [];
458
+ for (let idx = 0; idx < rawLines.length; idx++) {
459
+ const raw = rawLines[idx] ?? "";
460
+ const isLast = idx === rawLines.length - 1;
461
+ let content = raw.trim();
462
+ let hardBreak = false;
463
+ if (!isLast) {
464
+ if (/ {2,}$/.test(raw))
465
+ hardBreak = true;
466
+ else if (content.endsWith("\\") && !content.endsWith("\\\\")) {
467
+ hardBreak = true;
468
+ content = content.slice(0, -1).trimEnd();
469
+ }
470
+ }
471
+ for (const node of parseInlineRun(content, depth)) {
472
+ if (node.kind === "text")
473
+ pushText(out, node.text);
474
+ else
475
+ out.push(node);
476
+ }
477
+ if (!isLast) {
478
+ if (hardBreak)
479
+ out.push({ kind: "break" });
480
+ else
481
+ pushText(out, " ");
482
+ }
483
+ }
484
+ return out;
485
+ }
486
+ function dedentUpTo(line, columns) {
487
+ let removed = 0;
488
+ while (removed < columns && line[removed] === " ")
489
+ removed++;
490
+ return line.slice(removed);
491
+ }
492
+ function splitTableCells(line) {
493
+ const trimmed = line.trim();
494
+ const cells = [];
495
+ let current = "";
496
+ let i = trimmed[0] === "|" ? 1 : 0;
497
+ while (i < trimmed.length) {
498
+ const ch = trimmed[i];
499
+ if (ch === "\\" && trimmed[i + 1] === "|") {
500
+ current += "|";
501
+ i += 2;
502
+ continue;
503
+ }
504
+ if (ch === "\\" && i + 1 < trimmed.length) {
505
+ current += ch;
506
+ current += trimmed[i + 1] ?? "";
507
+ i += 2;
508
+ continue;
509
+ }
510
+ if (ch === "|") {
511
+ cells.push(current.trim());
512
+ current = "";
513
+ i++;
514
+ continue;
515
+ }
516
+ current += ch ?? "";
517
+ i++;
518
+ }
519
+ if (current.trim().length > 0 || !trimmed.endsWith("|") || trimmed.length === 0) {
520
+ cells.push(current.trim());
521
+ }
522
+ return cells;
523
+ }
524
+ function parseTable(lines, start, depth) {
525
+ const header = lines[start] ?? "";
526
+ const delim = lines[start + 1] ?? "";
527
+ if (!header.includes("|"))
528
+ return null;
529
+ if (!delim.includes("|"))
530
+ return null;
531
+ if (!TABLE_DELIM_RE.test(delim))
532
+ return null;
533
+ const headCells = splitTableCells(header);
534
+ const align = splitTableCells(delim).map((cell) => {
535
+ const left = cell.startsWith(":");
536
+ const right = cell.endsWith(":");
537
+ if (left && right)
538
+ return "center";
539
+ if (right)
540
+ return "right";
541
+ return "left";
542
+ });
543
+ if (headCells.length !== align.length || headCells.length === 0)
544
+ return null;
545
+ const rows = [];
546
+ let i = start + 2;
547
+ while (i < lines.length) {
548
+ const line = lines[i] ?? "";
549
+ if (line.trim().length === 0 || !line.includes("|"))
550
+ break;
551
+ const cells = splitTableCells(line).slice(0, headCells.length);
552
+ while (cells.length < headCells.length)
553
+ cells.push("");
554
+ rows.push(cells.map((cell) => parseInlineRun(cell, depth + 1)));
555
+ i++;
556
+ }
557
+ return {
558
+ block: {
559
+ kind: "table",
560
+ align,
561
+ head: headCells.map((cell) => parseInlineRun(cell, depth + 1)),
562
+ rows,
563
+ },
564
+ next: i,
565
+ };
566
+ }
567
+ function parseList(lines, start, depth) {
568
+ const first = LIST_RE.exec(lines[start] ?? "");
569
+ if (first === null)
570
+ return null;
571
+ const ordered = first[3] !== undefined;
572
+ const firstNumber = Number.parseInt(first[3] ?? "1", 10);
573
+ const startNumber = ordered && Number.isFinite(firstNumber) ? firstNumber : 1;
574
+ const items = [];
575
+ let i = start;
576
+ while (i < lines.length) {
577
+ const m = LIST_RE.exec(lines[i] ?? "");
578
+ if (m === null)
579
+ break;
580
+ if ((m[3] !== undefined) !== ordered)
581
+ break;
582
+ const indent = (m[1] ?? "").length;
583
+ const markerLength = ordered ? (m[3] ?? "1").length + 1 : 1;
584
+ const gap = Math.min((m[5] ?? " ").length, 4);
585
+ const contentIndent = indent + markerLength + gap;
586
+ const itemLines = [m[6] ?? ""];
587
+ i++;
588
+ while (i < lines.length) {
589
+ const line = lines[i] ?? "";
590
+ if (line.trim().length === 0) {
591
+ let j = i + 1;
592
+ while (j < lines.length && (lines[j] ?? "").trim().length === 0)
593
+ j++;
594
+ const upcoming = lines[j];
595
+ if (upcoming !== undefined && leadingSpaces(upcoming) >= contentIndent) {
596
+ itemLines.push("");
597
+ i++;
598
+ continue;
599
+ }
600
+ break;
601
+ }
602
+ if (leadingSpaces(line) >= contentIndent) {
603
+ itemLines.push(line.slice(contentIndent));
604
+ i++;
605
+ continue;
606
+ }
607
+ break;
608
+ }
609
+ let checked = null;
610
+ const task = TASK_ITEM_RE.exec(itemLines[0] ?? "");
611
+ if (task !== null) {
612
+ checked = task[1] !== " ";
613
+ itemLines[0] = task[2] ?? "";
614
+ }
615
+ items.push({ checked, blocks: parseBlocks(itemLines, depth + 1) });
616
+ while (i < lines.length && (lines[i] ?? "").trim().length === 0)
617
+ i++;
618
+ }
619
+ if (items.length === 0)
620
+ return null;
621
+ return {
622
+ block: { kind: "list", ordered, start: startNumber, items },
623
+ next: i,
624
+ };
625
+ }
626
+ function isParagraphInterrupter(line) {
627
+ if (line.trim().length === 0)
628
+ return true;
629
+ if (ATX_RE.test(line))
630
+ return true;
631
+ if (FENCE_OPEN_RE.test(line))
632
+ return true;
633
+ if (HR_RE.test(line))
634
+ return true;
635
+ if (BLOCKQUOTE_RE.test(line))
636
+ return true;
637
+ if (LIST_RE.test(line))
638
+ return true;
639
+ return false;
640
+ }
641
+ function parseBlocks(lines, depth, starts) {
642
+ const blocks = [];
643
+ if (depth > MAX_BLOCK_DEPTH) {
644
+ const text = lines.join(" ").trim();
645
+ if (text.length > 0) {
646
+ starts?.push(0);
647
+ blocks.push({ kind: "paragraph", children: [{ kind: "text", text }] });
648
+ }
649
+ return blocks;
650
+ }
651
+ let i = 0;
652
+ while (i < lines.length) {
653
+ const blockStart = i;
654
+ const line = lines[i] ?? "";
655
+ if (line.trim().length === 0) {
656
+ i++;
657
+ continue;
658
+ }
659
+ const fence = FENCE_OPEN_RE.exec(line);
660
+ if (fence !== null) {
661
+ const fenceIndent = (fence[1] ?? "").length;
662
+ const fenceRun = fence[2] ?? "```";
663
+ const fenceChar = fenceRun.startsWith("~") ? "~" : "`";
664
+ const info = (fence[3] ?? "").trim();
665
+ if (fenceChar === "~" || !info.includes("`")) {
666
+ const language = (info.split(/\s+/)[0] ?? "").toLowerCase();
667
+ const closeRe = new RegExp(`^ {0,3}${fenceChar}{${fenceRun.length},}[ \\t]*$`);
668
+ const content = [];
669
+ i++;
670
+ while (i < lines.length && !closeRe.test(lines[i] ?? "")) {
671
+ content.push(dedentUpTo(lines[i] ?? "", fenceIndent));
672
+ i++;
673
+ }
674
+ if (i < lines.length)
675
+ i++;
676
+ starts?.push(blockStart);
677
+ blocks.push({ kind: "codeBlock", language, text: content.join("\n") });
678
+ continue;
679
+ }
680
+ }
681
+ const atx = ATX_RE.exec(line);
682
+ if (atx !== null) {
683
+ const level = Math.min(Math.max((atx[1] ?? "#").length, 1), 6);
684
+ const content = (atx[2] ?? "").replace(/[ \t]+#+$/, "").trim();
685
+ starts?.push(blockStart);
686
+ blocks.push({ kind: "heading", level, children: parseInlineRun(content, depth) });
687
+ i++;
688
+ continue;
689
+ }
690
+ if (HR_RE.test(line)) {
691
+ starts?.push(blockStart);
692
+ blocks.push({ kind: "hr" });
693
+ i++;
694
+ continue;
695
+ }
696
+ if (BLOCKQUOTE_RE.test(line)) {
697
+ const inner = [];
698
+ while (i < lines.length) {
699
+ const m = BLOCKQUOTE_RE.exec(lines[i] ?? "");
700
+ if (m === null)
701
+ break;
702
+ inner.push(m[1] ?? "");
703
+ i++;
704
+ }
705
+ starts?.push(blockStart);
706
+ blocks.push({ kind: "blockquote", children: parseBlocks(inner, depth + 1) });
707
+ continue;
708
+ }
709
+ const list = parseList(lines, i, depth);
710
+ if (list !== null) {
711
+ starts?.push(blockStart);
712
+ blocks.push(list.block);
713
+ i = list.next;
714
+ continue;
715
+ }
716
+ const table = parseTable(lines, i, depth);
717
+ if (table !== null) {
718
+ starts?.push(blockStart);
719
+ blocks.push(table.block);
720
+ i = table.next;
721
+ continue;
722
+ }
723
+ if (INDENTED_CODE_RE.test(line)) {
724
+ const content = [];
725
+ while (i < lines.length) {
726
+ const current = lines[i] ?? "";
727
+ if (INDENTED_CODE_RE.test(current)) {
728
+ content.push(current.replace(INDENTED_CODE_RE, ""));
729
+ i++;
730
+ continue;
731
+ }
732
+ if (current.trim().length === 0) {
733
+ let j = i + 1;
734
+ while (j < lines.length && (lines[j] ?? "").trim().length === 0)
735
+ j++;
736
+ const upcoming = lines[j];
737
+ if (upcoming !== undefined && INDENTED_CODE_RE.test(upcoming)) {
738
+ for (let k = i; k < j; k++)
739
+ content.push("");
740
+ i = j;
741
+ continue;
742
+ }
743
+ }
744
+ break;
745
+ }
746
+ starts?.push(blockStart);
747
+ blocks.push({ kind: "codeBlock", language: "", text: content.join("\n") });
748
+ continue;
749
+ }
750
+ const paragraph = [line];
751
+ i++;
752
+ while (i < lines.length && !isParagraphInterrupter(lines[i] ?? "")) {
753
+ paragraph.push(lines[i] ?? "");
754
+ i++;
755
+ }
756
+ starts?.push(blockStart);
757
+ blocks.push({ kind: "paragraph", children: parseParagraphInlines(paragraph, depth) });
758
+ }
759
+ return blocks;
760
+ }
761
+ function deepFreeze(value) {
762
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
763
+ Object.freeze(value);
764
+ for (const key of Object.keys(value)) {
765
+ deepFreeze(value[key]);
766
+ }
767
+ }
768
+ return value;
769
+ }
770
+ /**
771
+ * Parses pre-normalized lines into deeply frozen top-level blocks. Internal
772
+ * surface shared by parseMarkdown() and the streaming parser; when `starts`
773
+ * is provided it receives the source line index of every top-level block.
774
+ */
775
+ export function parseMarkdownLines(lines, starts) {
776
+ const blocks = parseBlocks(lines, 0, starts);
777
+ for (const block of blocks)
778
+ deepFreeze(block);
779
+ return blocks;
780
+ }
781
+ /**
782
+ * Parses a GFM-subset markdown document. Never throws: malformed constructs
783
+ * degrade to literal text and the result is deeply frozen.
784
+ */
785
+ export function parseMarkdown(source) {
786
+ const text = typeof source === "string" ? source : "";
787
+ const normalized = text.replace(/\r\n?/g, "\n").split("\u0000").join("\uFFFD");
788
+ const blocks = parseMarkdownLines(normalized.split("\n"));
789
+ return Object.freeze({ blocks: Object.freeze(blocks) });
790
+ }
791
+ //# sourceMappingURL=parse.js.map