@fuzdev/mdz 0.1.0

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/Mdz.svelte +39 -0
  4. package/dist/Mdz.svelte.d.ts +17 -0
  5. package/dist/Mdz.svelte.d.ts.map +1 -0
  6. package/dist/MdzNodeView.svelte +140 -0
  7. package/dist/MdzNodeView.svelte.d.ts +9 -0
  8. package/dist/MdzNodeView.svelte.d.ts.map +1 -0
  9. package/dist/MdzPrecompiled.svelte +33 -0
  10. package/dist/MdzPrecompiled.svelte.d.ts +17 -0
  11. package/dist/MdzPrecompiled.svelte.d.ts.map +1 -0
  12. package/dist/MdzRoot.svelte +40 -0
  13. package/dist/MdzRoot.svelte.d.ts +14 -0
  14. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  15. package/dist/MdzStream.svelte +37 -0
  16. package/dist/MdzStream.svelte.d.ts +18 -0
  17. package/dist/MdzStream.svelte.d.ts.map +1 -0
  18. package/dist/MdzStreamNodeView.svelte +150 -0
  19. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  20. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  21. package/dist/context_helpers.d.ts +23 -0
  22. package/dist/context_helpers.d.ts.map +1 -0
  23. package/dist/context_helpers.js +19 -0
  24. package/dist/mdz.d.ts +152 -0
  25. package/dist/mdz.d.ts.map +1 -0
  26. package/dist/mdz.js +61 -0
  27. package/dist/mdz_contexts.d.ts +106 -0
  28. package/dist/mdz_contexts.d.ts.map +1 -0
  29. package/dist/mdz_contexts.js +42 -0
  30. package/dist/mdz_helpers.d.ts +321 -0
  31. package/dist/mdz_helpers.d.ts.map +1 -0
  32. package/dist/mdz_helpers.js +668 -0
  33. package/dist/mdz_lexer.d.ts +123 -0
  34. package/dist/mdz_lexer.d.ts.map +1 -0
  35. package/dist/mdz_lexer.js +1405 -0
  36. package/dist/mdz_opcodes.d.ts +193 -0
  37. package/dist/mdz_opcodes.d.ts.map +1 -0
  38. package/dist/mdz_opcodes.js +14 -0
  39. package/dist/mdz_opcodes_to_nodes.d.ts +26 -0
  40. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  41. package/dist/mdz_opcodes_to_nodes.js +410 -0
  42. package/dist/mdz_stream_parser.d.ts +109 -0
  43. package/dist/mdz_stream_parser.d.ts.map +1 -0
  44. package/dist/mdz_stream_parser.js +699 -0
  45. package/dist/mdz_stream_parser_block.d.ts +99 -0
  46. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  47. package/dist/mdz_stream_parser_block.js +437 -0
  48. package/dist/mdz_stream_parser_inline.d.ts +98 -0
  49. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  50. package/dist/mdz_stream_parser_inline.js +481 -0
  51. package/dist/mdz_stream_parser_link.d.ts +29 -0
  52. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  53. package/dist/mdz_stream_parser_link.js +227 -0
  54. package/dist/mdz_stream_parser_list.d.ts +106 -0
  55. package/dist/mdz_stream_parser_list.d.ts.map +1 -0
  56. package/dist/mdz_stream_parser_list.js +438 -0
  57. package/dist/mdz_stream_parser_state.d.ts +476 -0
  58. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  59. package/dist/mdz_stream_parser_state.js +651 -0
  60. package/dist/mdz_stream_parser_text.d.ts +25 -0
  61. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  62. package/dist/mdz_stream_parser_text.js +67 -0
  63. package/dist/mdz_stream_parser_url.d.ts +76 -0
  64. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  65. package/dist/mdz_stream_parser_url.js +328 -0
  66. package/dist/mdz_stream_state.svelte.d.ts +53 -0
  67. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  68. package/dist/mdz_stream_state.svelte.js +360 -0
  69. package/dist/mdz_to_svelte.d.ts +71 -0
  70. package/dist/mdz_to_svelte.d.ts.map +1 -0
  71. package/dist/mdz_to_svelte.js +127 -0
  72. package/dist/mdz_token_parser.d.ts +20 -0
  73. package/dist/mdz_token_parser.d.ts.map +1 -0
  74. package/dist/mdz_token_parser.js +449 -0
  75. package/dist/svelte_preprocess_mdz.d.ts +80 -0
  76. package/dist/svelte_preprocess_mdz.d.ts.map +1 -0
  77. package/dist/svelte_preprocess_mdz.js +543 -0
  78. package/dist/tsdoc_mdz.d.ts +45 -0
  79. package/dist/tsdoc_mdz.d.ts.map +1 -0
  80. package/dist/tsdoc_mdz.js +88 -0
  81. package/package.json +131 -0
  82. package/src/lib/context_helpers.ts +52 -0
  83. package/src/lib/mdz.ts +190 -0
  84. package/src/lib/mdz_contexts.ts +99 -0
  85. package/src/lib/mdz_helpers.ts +727 -0
  86. package/src/lib/mdz_lexer.ts +1761 -0
  87. package/src/lib/mdz_opcodes.ts +227 -0
  88. package/src/lib/mdz_opcodes_to_nodes.ts +470 -0
  89. package/src/lib/mdz_stream_parser.ts +766 -0
  90. package/src/lib/mdz_stream_parser_block.ts +490 -0
  91. package/src/lib/mdz_stream_parser_inline.ts +527 -0
  92. package/src/lib/mdz_stream_parser_link.ts +283 -0
  93. package/src/lib/mdz_stream_parser_list.ts +525 -0
  94. package/src/lib/mdz_stream_parser_state.ts +957 -0
  95. package/src/lib/mdz_stream_parser_text.ts +95 -0
  96. package/src/lib/mdz_stream_parser_url.ts +386 -0
  97. package/src/lib/mdz_stream_state.svelte.ts +383 -0
  98. package/src/lib/mdz_to_svelte.ts +213 -0
  99. package/src/lib/mdz_token_parser.ts +544 -0
  100. package/src/lib/svelte_preprocess_mdz.ts +777 -0
  101. package/src/lib/tsdoc_mdz.ts +97 -0
@@ -0,0 +1,525 @@
1
+ /**
2
+ * List handlers for the streaming mdz parser — column-0 list starts and the
3
+ * line classifier that drives structure inside open lists.
4
+ *
5
+ * The streaming analogue of the sync lexer's `#tokenize_list`: the level
6
+ * stack lives in `state.list_levels`, and List/ListItem frames sit on the
7
+ * main stack (never reverted, like Paragraph). Every line inside an open
8
+ * list classifies from bounded input — leading whitespace plus a few marker
9
+ * chars for most lines, the whole line for empty markers and fence openers —
10
+ * and blank-run containment consumes one blank line at a time, holding only
11
+ * the structural close-vs-contain decision, never renderable content.
12
+ *
13
+ * @module
14
+ */
15
+
16
+ import {
17
+ BACKTICK,
18
+ HYPHEN,
19
+ MAX_LIST_NUMBER_DIGITS,
20
+ MIN_CODEBLOCK_BACKTICKS,
21
+ NEWLINE,
22
+ NINE,
23
+ PERIOD,
24
+ RIGHT_ANGLE,
25
+ SPACE,
26
+ ZERO,
27
+ is_line_whitespace,
28
+ mdz_match_blockquote_prefix,
29
+ mdz_blockquote_line_has_content,
30
+ } from './mdz_helpers.js';
31
+ import {
32
+ type MdzStreamParserState,
33
+ type TryResult,
34
+ accumulate_text,
35
+ alloc_id,
36
+ close_all_list_levels,
37
+ close_list_item,
38
+ close_list_level,
39
+ close_list_run,
40
+ close_paragraph,
41
+ emit,
42
+ extend_accumulated_end,
43
+ flush_text,
44
+ offset,
45
+ push_stack_entry,
46
+ } from './mdz_stream_parser_state.js';
47
+
48
+ /**
49
+ * A matched list marker: `- ` (unordered) or `N. ` (ordered, 1-9 digits).
50
+ * `empty` markers have nothing but whitespace after the marker — valid only
51
+ * as mid-list empty items, never starting a list. Positions are local buffer
52
+ * indices.
53
+ *
54
+ * @nodocs
55
+ */
56
+ export interface MdzStreamListMarker {
57
+ ordered: boolean;
58
+ /** Parsed integer for ordered markers (`007.` → 7); `0` for unordered. */
59
+ number: number;
60
+ /** Local buffer position of the marker character. */
61
+ start: number;
62
+ /** Index just past the marker chars (`-` or `N.`), before the space. */
63
+ marker_end: number;
64
+ /** Index just past the marker space — the inline run's content. */
65
+ content_start: number;
66
+ empty: boolean;
67
+ }
68
+
69
+ /**
70
+ * Whether only line whitespace remains between `from` and the line's end —
71
+ * `'pending'` when the buffer ends inside the run while streaming (the line
72
+ * hasn't resolved); in forced mode EOF is a line end.
73
+ */
74
+ const rest_is_blank = (
75
+ state: MdzStreamParserState,
76
+ from: number,
77
+ forced: boolean,
78
+ ): boolean | 'pending' => {
79
+ let i = from;
80
+ while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
81
+ i++;
82
+ }
83
+ if (i >= state.buffer.length) return forced ? true : 'pending';
84
+ return state.buffer.charCodeAt(i) === NEWLINE;
85
+ };
86
+
87
+ /**
88
+ * Match a list marker at `i` — the streaming analogue of the sync lexer's
89
+ * `#match_list_marker`, with `'pending'` for a marker that can't resolve
90
+ * until more input arrives (digits cut off at buffer end, a possible empty
91
+ * marker whose line hasn't ended). Returns null for anything that's
92
+ * definitely not a marker (`-x`, `--`, a tab after the marker, 10+ digits).
93
+ *
94
+ * @nodocs
95
+ */
96
+ export const match_list_marker = (
97
+ state: MdzStreamParserState,
98
+ i: number,
99
+ forced: boolean,
100
+ ): MdzStreamListMarker | 'pending' | null => {
101
+ const {buffer} = state;
102
+ const c = buffer.charCodeAt(i);
103
+ if (c === HYPHEN) {
104
+ const after = i + 1;
105
+ const blank = rest_is_blank(state, after, forced);
106
+ if (blank === 'pending') return 'pending';
107
+ if (blank) {
108
+ return {
109
+ ordered: false,
110
+ number: 0,
111
+ start: i,
112
+ marker_end: after,
113
+ content_start: after,
114
+ empty: true,
115
+ };
116
+ }
117
+ // in bounds: `blank === false` means the scan found a char at or past `after`
118
+ if (buffer.charCodeAt(after) !== SPACE) return null;
119
+ const content_start = after + 1;
120
+ const empty = rest_is_blank(state, content_start, forced);
121
+ if (empty === 'pending') return 'pending';
122
+ return {ordered: false, number: 0, start: i, marker_end: after, content_start, empty};
123
+ }
124
+ if (c >= ZERO && c <= NINE) {
125
+ let j = i + 1;
126
+ while (j < buffer.length && buffer.charCodeAt(j) >= ZERO && buffer.charCodeAt(j) <= NINE) {
127
+ j++;
128
+ }
129
+ if (j - i > MAX_LIST_NUMBER_DIGITS) return null;
130
+ if (j >= buffer.length) return forced ? null : 'pending'; // digits could grow, `.` could follow
131
+ if (buffer.charCodeAt(j) !== PERIOD) return null;
132
+ const number = Number(buffer.slice(i, j));
133
+ const after = j + 1;
134
+ const blank = rest_is_blank(state, after, forced);
135
+ if (blank === 'pending') return 'pending';
136
+ if (blank) {
137
+ return {
138
+ ordered: true,
139
+ number,
140
+ start: i,
141
+ marker_end: after,
142
+ content_start: after,
143
+ empty: true,
144
+ };
145
+ }
146
+ // in bounds: `blank === false` means the scan found a char at or past `after`
147
+ if (buffer.charCodeAt(after) !== SPACE) return null;
148
+ const content_start = after + 1;
149
+ const empty = rest_is_blank(state, content_start, forced);
150
+ if (empty === 'pending') return 'pending';
151
+ return {ordered: true, number, start: i, marker_end: after, content_start, empty};
152
+ }
153
+ return null;
154
+ };
155
+
156
+ /**
157
+ * A matched fence opener line (see `match_fence_opener`).
158
+ *
159
+ * @nodocs
160
+ */
161
+ export interface MdzStreamFenceOpener {
162
+ backtick_count: number;
163
+ lang: string | null;
164
+ /** Local position just past the opener's newline. */
165
+ content_start: number;
166
+ }
167
+
168
+ /**
169
+ * Match a complete fence opener line at `i`: >=3 backticks, an optional
170
+ * language word, only whitespace to a terminating newline. `'pending'` while
171
+ * the line could still become one; null when it definitely isn't — including
172
+ * an opener cut off at EOF without its newline (forced), which stays text,
173
+ * matching the sync parser's opener rule.
174
+ *
175
+ * @nodocs
176
+ */
177
+ export const match_fence_opener = (
178
+ state: MdzStreamParserState,
179
+ i: number,
180
+ forced: boolean,
181
+ ): MdzStreamFenceOpener | 'pending' | null => {
182
+ const {buffer} = state;
183
+ let j = i;
184
+ let count = 0;
185
+ while (j < buffer.length && buffer.charCodeAt(j) === BACKTICK) {
186
+ count++;
187
+ j++;
188
+ }
189
+ if (j >= buffer.length && !forced) return 'pending'; // run could still grow
190
+ if (count < MIN_CODEBLOCK_BACKTICKS) return null;
191
+ const lang_start = j;
192
+ while (j < buffer.length) {
193
+ const c = buffer.charCodeAt(j);
194
+ if (c === NEWLINE || is_line_whitespace(c)) break;
195
+ j++;
196
+ }
197
+ const lang = j > lang_start ? buffer.slice(lang_start, j) : null;
198
+ while (j < buffer.length && is_line_whitespace(buffer.charCodeAt(j))) {
199
+ j++;
200
+ }
201
+ if (j >= buffer.length) return forced ? null : 'pending'; // the opener needs its newline
202
+ if (buffer.charCodeAt(j) !== NEWLINE) return null;
203
+ return {backtick_count: count, lang, content_start: j + 1};
204
+ };
205
+
206
+ /** Emit a `ListItem` open for `marker` and push its frame. */
207
+ const open_list_item = (state: MdzStreamParserState, marker: MdzStreamListMarker): void => {
208
+ const id = alloc_id(state);
209
+ const start = offset(state, marker.start);
210
+ if (marker.ordered) {
211
+ emit(state, {type: 'open', id, node_type: 'ListItem', start, number: marker.number});
212
+ } else {
213
+ emit(state, {type: 'open', id, node_type: 'ListItem', start});
214
+ }
215
+ push_stack_entry(state, id, 'ListItem', start);
216
+ if (marker.empty) {
217
+ // a childless close uses this as the item's end (sync's `open.end`)
218
+ state.last_empty_item_end = offset(state, marker.marker_end);
219
+ }
220
+ state.active_text_id = null;
221
+ };
222
+
223
+ /** Open a new list level — its `List` frame and first item — at `indent`. */
224
+ const open_list_level = (
225
+ state: MdzStreamParserState,
226
+ indent: number,
227
+ marker: MdzStreamListMarker,
228
+ ): void => {
229
+ const id = alloc_id(state);
230
+ const start = offset(state, marker.start);
231
+ if (marker.ordered) {
232
+ emit(state, {
233
+ type: 'open',
234
+ id,
235
+ node_type: 'List',
236
+ start,
237
+ ordered: true,
238
+ start_number: marker.number,
239
+ });
240
+ } else {
241
+ emit(state, {type: 'open', id, node_type: 'List', start, ordered: false});
242
+ }
243
+ push_stack_entry(state, id, 'List', start);
244
+ open_list_item(state, marker);
245
+ state.list_levels.push({indent, ordered: marker.ordered});
246
+ };
247
+
248
+ /**
249
+ * Begin a non-empty item's inline run at the marker line's content. Extra
250
+ * whitespace after the marker space is content-leading slop, trimmed like
251
+ * the sync lexer's `#list_run` — the marker guaranteed a non-whitespace char
252
+ * before the line end, so the scan can't hit the buffer end.
253
+ */
254
+ const begin_item_run = (
255
+ state: MdzStreamParserState,
256
+ marker: MdzStreamListMarker,
257
+ line_start: number,
258
+ ): void => {
259
+ let i = marker.content_start;
260
+ while (i < state.buffer.length && is_line_whitespace(state.buffer.charCodeAt(i))) {
261
+ i++;
262
+ }
263
+ state.in_list_item_run = true;
264
+ state.pos = i;
265
+ state.column = i - line_start;
266
+ state.prev_char = SPACE;
267
+ };
268
+
269
+ /**
270
+ * The level index of the deepest open item a line at `indent` can attach to —
271
+ * the deepest level with marker indent strictly less than `indent` — or -1.
272
+ * Levels above it get popped by the caller.
273
+ */
274
+ const list_continuation_target = (state: MdzStreamParserState, indent: number): number => {
275
+ for (let i = state.list_levels.length - 1; i >= 0; i--) {
276
+ if (state.list_levels[i]!.indent < indent) return i;
277
+ }
278
+ return -1;
279
+ };
280
+
281
+ /**
282
+ * Continue the current run across a soft break: the newline joins the run's
283
+ * text, the continuation line's structural indent strips (its span folds
284
+ * into the preceding text's source end, matching the sync lexer's
285
+ * `#list_run_strip`), and inline processing resumes at the line's content.
286
+ */
287
+ const continue_run = (
288
+ state: MdzStreamParserState,
289
+ nl_pos: number,
290
+ content_pos: number,
291
+ ): boolean => {
292
+ accumulate_text(state, '\n', offset(state, nl_pos));
293
+ extend_accumulated_end(state, offset(state, content_pos));
294
+ state.pos = content_pos;
295
+ state.column = content_pos - (nl_pos + 1);
296
+ state.prev_char = NEWLINE;
297
+ return true;
298
+ };
299
+
300
+ /**
301
+ * Try to start a list at a column-0 marker line — `try_hr` has already
302
+ * rejected `---` when the char is `-`. Mirrors the sync lexer's
303
+ * `#tokenize_list` entry conditions: empty markers never start a list, the
304
+ * marker line must show content before the open confirms (a line-bounded
305
+ * hold, the heading has-content pattern), and a non-`1.` ordered marker
306
+ * never interrupts a current paragraph (the hard-wrap `2024.` guard).
307
+ *
308
+ * @nodocs
309
+ */
310
+ export const try_list_start = (state: MdzStreamParserState, forced: boolean): TryResult => {
311
+ const line_start = state.pos;
312
+ const marker = match_list_marker(state, line_start, forced);
313
+ if (marker === 'pending') return 'need_more';
314
+ if (marker === null || marker.empty) return 'not_match';
315
+ if (marker.ordered && marker.number !== 1 && state.in_paragraph) return 'not_match';
316
+
317
+ flush_text(state);
318
+ close_paragraph(state);
319
+ open_list_level(state, 0, marker);
320
+ begin_item_run(state, marker, line_start);
321
+ return 'consumed';
322
+ };
323
+
324
+ /**
325
+ * Classify the line after a newline inside an open list and apply its
326
+ * structural effect — the streaming analogue of the sync lexer's
327
+ * `#tokenize_list` line loop plus `#list_run_end`'s continuation rules.
328
+ * Called with `pos` at the newline; returns `false` to hold until the line
329
+ * classifies (never in forced mode). Dispatch, in order:
330
+ *
331
+ * - blank line: the current run closes; the close-vs-contain decision waits
332
+ * on the next non-blank line (one blank consumes per call, so long blank
333
+ * runs make incremental progress across chunks)
334
+ * - marker lines: nest when deeper (subject to the ordered interrupt guard
335
+ * against a current run), else snap to the nearest surviving level as a
336
+ * sibling or a marker-type switch; empty markers at an exactly-matching
337
+ * level form empty items
338
+ * - deeper fence opener lines: a code block child of the item they'd
339
+ * otherwise continue (`process_codeblock_in_item` streams it)
340
+ * - deeper quote opener lines (a prefix with content after it): the levels
341
+ * pop to the attach target and `'blockquote'` returns with `pos` at the prefix —
342
+ * the main loop opens the blockquote child (the quote machinery lives
343
+ * there, beside the nested-parser driving)
344
+ * - deeper non-marker lines: continuation of a current run (soft break,
345
+ * indent stripped), else a new `Paragraph` block child
346
+ * - anything else: the whole list closes and the line re-dispatches at the
347
+ * top level
348
+ *
349
+ * @nodocs
350
+ */
351
+ export const process_list_line = (
352
+ state: MdzStreamParserState,
353
+ forced: boolean,
354
+ ): boolean | 'blockquote' => {
355
+ const {buffer} = state;
356
+ const nl_pos = state.pos;
357
+ const line_start = nl_pos + 1;
358
+ let j = line_start;
359
+ while (j < buffer.length && is_line_whitespace(buffer.charCodeAt(j))) {
360
+ j++;
361
+ }
362
+ if (j >= buffer.length && !forced) return false; // unresolved line — hold
363
+
364
+ if (j >= buffer.length || buffer.charCodeAt(j) === NEWLINE) {
365
+ // blank line (or blank to EOF): the current run is over either way
366
+ close_list_run(state);
367
+ if (j >= buffer.length) {
368
+ // forced: blank to EOF — the list closes
369
+ close_all_list_levels(state);
370
+ }
371
+ state.pos = j; // at the next newline (re-enter here) or EOF
372
+ state.column = 0;
373
+ state.prev_char = NEWLINE;
374
+ return true;
375
+ }
376
+
377
+ const indent = j - line_start;
378
+ const run_current = state.in_list_item_run || state.in_paragraph;
379
+ const deepest = state.list_levels[state.list_levels.length - 1]!;
380
+
381
+ const marker = match_list_marker(state, j, forced);
382
+ if (marker === 'pending') return false;
383
+
384
+ if (marker !== null && !marker.empty) {
385
+ if (indent > deepest.indent) {
386
+ // deeper marker nests — except a non-`1.` ordered marker against a
387
+ // current run, which is hard-wrapped continuation text (the `2024.`
388
+ // guard, applied uniformly to item runs)
389
+ if (run_current && marker.ordered && marker.number !== 1) {
390
+ return continue_run(state, nl_pos, j);
391
+ }
392
+ close_list_run(state);
393
+ open_list_level(state, indent, marker);
394
+ } else {
395
+ // dedent snaps to the nearest surviving level
396
+ close_list_run(state);
397
+ while (
398
+ state.list_levels.length > 1 &&
399
+ state.list_levels[state.list_levels.length - 1]!.indent > indent
400
+ ) {
401
+ close_list_level(state);
402
+ }
403
+ if (state.list_levels[state.list_levels.length - 1]!.ordered === marker.ordered) {
404
+ // sibling item
405
+ close_list_item(state);
406
+ open_list_item(state, marker);
407
+ } else {
408
+ // marker-type switch: close the list, open a new one at this indent
409
+ close_list_level(state);
410
+ open_list_level(state, indent, marker);
411
+ }
412
+ }
413
+ begin_item_run(state, marker, line_start);
414
+ return true;
415
+ }
416
+
417
+ if (marker !== null && marker.empty) {
418
+ // empty item: only at an indent exactly matching an open level of the
419
+ // same marker type — anything else degrades below
420
+ const level_idx = state.list_levels.findIndex(
421
+ (l) => l.indent === indent && l.ordered === marker.ordered,
422
+ );
423
+ if (level_idx !== -1) {
424
+ close_list_run(state);
425
+ while (state.list_levels.length - 1 > level_idx) {
426
+ close_list_level(state);
427
+ }
428
+ close_list_item(state);
429
+ open_list_item(state, marker);
430
+ // consume the marker and trailing whitespace, leaving pos at the
431
+ // line's newline (or EOF)
432
+ let k = marker.marker_end;
433
+ while (k < buffer.length && is_line_whitespace(buffer.charCodeAt(k))) {
434
+ k++;
435
+ }
436
+ state.pos = k;
437
+ state.column = k - line_start;
438
+ state.prev_char = NEWLINE;
439
+ return true;
440
+ }
441
+ }
442
+
443
+ // a deeper fence opener line becomes a code block child of the item it
444
+ // would otherwise continue
445
+ if (indent > 0 && buffer.charCodeAt(j) === BACKTICK) {
446
+ const target_idx = list_continuation_target(state, indent);
447
+ if (target_idx !== -1) {
448
+ const fence = match_fence_opener(state, j, forced);
449
+ if (fence === 'pending') return false;
450
+ if (fence !== null) {
451
+ close_list_run(state);
452
+ while (state.list_levels.length - 1 > target_idx) {
453
+ close_list_level(state);
454
+ }
455
+ const id = alloc_id(state);
456
+ const cb_start = offset(state, j);
457
+ emit(state, {type: 'open', id, node_type: 'Codeblock', start: cb_start, lang: fence.lang});
458
+ state.codeblock = {
459
+ id,
460
+ backtick_count: fence.backtick_count,
461
+ text_id: null,
462
+ fence_indent: indent,
463
+ start: cb_start,
464
+ };
465
+ state.active_text_id = null;
466
+ state.pos = fence.content_start;
467
+ state.column = 0;
468
+ state.prev_char = NEWLINE;
469
+ return true;
470
+ }
471
+ }
472
+ }
473
+
474
+ // a deeper quote opener line (prefix with content) becomes a blockquote
475
+ // child of the item it would otherwise continue — the fence precedent
476
+ if (indent > 0 && buffer.charCodeAt(j) === RIGHT_ANGLE) {
477
+ const target_idx = list_continuation_target(state, indent);
478
+ if (target_idx !== -1) {
479
+ const prefix = mdz_match_blockquote_prefix(buffer, j, forced);
480
+ if (prefix === 'pending') return false;
481
+ if (prefix !== null) {
482
+ const has_content = mdz_blockquote_line_has_content(buffer, prefix.content_start, forced);
483
+ if (has_content === 'pending') return false;
484
+ if (has_content) {
485
+ close_list_run(state);
486
+ while (state.list_levels.length - 1 > target_idx) {
487
+ close_list_level(state);
488
+ }
489
+ state.pos = j;
490
+ state.column = indent;
491
+ state.prev_char = NEWLINE;
492
+ return 'blockquote';
493
+ }
494
+ }
495
+ }
496
+ }
497
+
498
+ if (run_current && indent > deepest.indent) {
499
+ // continuation line: soft break, structural indent stripped
500
+ return continue_run(state, nl_pos, j);
501
+ }
502
+
503
+ // the run (if any) is over: a deeper line opens a new Paragraph block
504
+ // child of the item it attaches to; anything else ends the list
505
+ close_list_run(state);
506
+ const target_idx = indent > 0 ? list_continuation_target(state, indent) : -1;
507
+ if (target_idx !== -1) {
508
+ while (state.list_levels.length - 1 > target_idx) {
509
+ close_list_level(state);
510
+ }
511
+ // the Paragraph opens lazily at the first emit (`ensure_paragraph`)
512
+ state.pos = j;
513
+ state.column = indent;
514
+ state.prev_char = NEWLINE;
515
+ return true;
516
+ }
517
+
518
+ // nothing list-shaped — the list ends; the main loop re-dispatches this
519
+ // line (paragraph, heading, HR, fence, or a fresh list)
520
+ close_all_list_levels(state);
521
+ state.pos = line_start;
522
+ state.column = 0;
523
+ state.prev_char = NEWLINE;
524
+ return true;
525
+ };