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