@fuzdev/fuz_ui 0.204.0 → 0.205.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 (123) hide show
  1. package/dist/ApiModule.svelte +14 -1
  2. package/dist/ApiModule.svelte.d.ts.map +1 -1
  3. package/dist/Breadcrumb.svelte +2 -2
  4. package/dist/Card.svelte +1 -7
  5. package/dist/ColorSchemeInput.svelte +1 -2
  6. package/dist/ContextmenuEntry.svelte +1 -3
  7. package/dist/ContextmenuLinkEntry.svelte +1 -4
  8. package/dist/ContextmenuSeparator.svelte +1 -1
  9. package/dist/ContextmenuSubmenu.svelte +1 -2
  10. package/dist/CopyToClipboard.svelte +5 -5
  11. package/dist/CopyToClipboard.svelte.d.ts.map +1 -1
  12. package/dist/DeclarationDetail.svelte +15 -4
  13. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  14. package/dist/Dialog.svelte +1 -2
  15. package/dist/DialogContent.svelte +1 -1
  16. package/dist/DocsList.svelte +1 -1
  17. package/dist/DocsMenu.svelte +1 -1
  18. package/dist/DocsPageLinks.svelte +9 -8
  19. package/dist/DocsPageLinks.svelte.d.ts.map +1 -1
  20. package/dist/DocsPrimaryNav.svelte +1 -1
  21. package/dist/DocsSecondaryNav.svelte +1 -1
  22. package/dist/EcosystemLinks.svelte +1 -1
  23. package/dist/LibraryDetail.svelte +10 -7
  24. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  25. package/dist/LibrarySummary.svelte +1 -2
  26. package/dist/PendingAnimation.svelte +6 -4
  27. package/dist/PendingAnimation.svelte.d.ts.map +1 -1
  28. package/dist/PendingButton.svelte +1 -2
  29. package/dist/StyleVariableButton.svelte +1 -2
  30. package/dist/Svg.svelte +1 -1
  31. package/dist/ThemeInput.svelte +1 -2
  32. package/dist/TomeSectionHeader.svelte +1 -1
  33. package/package.json +14 -38
  34. package/dist/Mdz.svelte +0 -34
  35. package/dist/Mdz.svelte.d.ts +0 -11
  36. package/dist/Mdz.svelte.d.ts.map +0 -1
  37. package/dist/MdzNodeView.svelte +0 -107
  38. package/dist/MdzNodeView.svelte.d.ts +0 -9
  39. package/dist/MdzNodeView.svelte.d.ts.map +0 -1
  40. package/dist/MdzPrecompiled.svelte +0 -30
  41. package/dist/MdzPrecompiled.svelte.d.ts +0 -11
  42. package/dist/MdzPrecompiled.svelte.d.ts.map +0 -1
  43. package/dist/MdzRoot.svelte +0 -30
  44. package/dist/MdzRoot.svelte.d.ts +0 -12
  45. package/dist/MdzRoot.svelte.d.ts.map +0 -1
  46. package/dist/MdzStream.svelte +0 -32
  47. package/dist/MdzStream.svelte.d.ts +0 -12
  48. package/dist/MdzStream.svelte.d.ts.map +0 -1
  49. package/dist/MdzStreamNodeView.svelte +0 -106
  50. package/dist/MdzStreamNodeView.svelte.d.ts +0 -9
  51. package/dist/MdzStreamNodeView.svelte.d.ts.map +0 -1
  52. package/dist/mdz.d.ts +0 -112
  53. package/dist/mdz.d.ts.map +0 -1
  54. package/dist/mdz.js +0 -1186
  55. package/dist/mdz_components.d.ts +0 -63
  56. package/dist/mdz_components.d.ts.map +0 -1
  57. package/dist/mdz_components.js +0 -32
  58. package/dist/mdz_helpers.d.ts +0 -164
  59. package/dist/mdz_helpers.d.ts.map +0 -1
  60. package/dist/mdz_helpers.js +0 -424
  61. package/dist/mdz_lexer.d.ts +0 -93
  62. package/dist/mdz_lexer.d.ts.map +0 -1
  63. package/dist/mdz_lexer.js +0 -732
  64. package/dist/mdz_opcodes.d.ts +0 -174
  65. package/dist/mdz_opcodes.d.ts.map +0 -1
  66. package/dist/mdz_opcodes.js +0 -14
  67. package/dist/mdz_opcodes_to_nodes.d.ts +0 -20
  68. package/dist/mdz_opcodes_to_nodes.d.ts.map +0 -1
  69. package/dist/mdz_opcodes_to_nodes.js +0 -332
  70. package/dist/mdz_stream_parser.d.ts +0 -80
  71. package/dist/mdz_stream_parser.d.ts.map +0 -1
  72. package/dist/mdz_stream_parser.js +0 -354
  73. package/dist/mdz_stream_parser_block.d.ts +0 -76
  74. package/dist/mdz_stream_parser_block.d.ts.map +0 -1
  75. package/dist/mdz_stream_parser_block.js +0 -458
  76. package/dist/mdz_stream_parser_inline.d.ts +0 -52
  77. package/dist/mdz_stream_parser_inline.d.ts.map +0 -1
  78. package/dist/mdz_stream_parser_inline.js +0 -378
  79. package/dist/mdz_stream_parser_link.d.ts +0 -22
  80. package/dist/mdz_stream_parser_link.d.ts.map +0 -1
  81. package/dist/mdz_stream_parser_link.js +0 -215
  82. package/dist/mdz_stream_parser_state.d.ts +0 -187
  83. package/dist/mdz_stream_parser_state.d.ts.map +0 -1
  84. package/dist/mdz_stream_parser_state.js +0 -398
  85. package/dist/mdz_stream_parser_text.d.ts +0 -14
  86. package/dist/mdz_stream_parser_text.d.ts.map +0 -1
  87. package/dist/mdz_stream_parser_text.js +0 -50
  88. package/dist/mdz_stream_parser_url.d.ts +0 -60
  89. package/dist/mdz_stream_parser_url.d.ts.map +0 -1
  90. package/dist/mdz_stream_parser_url.js +0 -307
  91. package/dist/mdz_stream_state.svelte.d.ts +0 -44
  92. package/dist/mdz_stream_state.svelte.d.ts.map +0 -1
  93. package/dist/mdz_stream_state.svelte.js +0 -357
  94. package/dist/mdz_to_svelte.d.ts +0 -41
  95. package/dist/mdz_to_svelte.d.ts.map +0 -1
  96. package/dist/mdz_to_svelte.js +0 -100
  97. package/dist/mdz_token_parser.d.ts +0 -14
  98. package/dist/mdz_token_parser.d.ts.map +0 -1
  99. package/dist/mdz_token_parser.js +0 -344
  100. package/dist/svelte_preprocess_mdz.d.ts +0 -65
  101. package/dist/svelte_preprocess_mdz.d.ts.map +0 -1
  102. package/dist/svelte_preprocess_mdz.js +0 -529
  103. package/dist/tsdoc_mdz.d.ts +0 -45
  104. package/dist/tsdoc_mdz.d.ts.map +0 -1
  105. package/dist/tsdoc_mdz.js +0 -88
  106. package/src/lib/mdz.ts +0 -1532
  107. package/src/lib/mdz_components.ts +0 -64
  108. package/src/lib/mdz_helpers.ts +0 -449
  109. package/src/lib/mdz_lexer.ts +0 -1012
  110. package/src/lib/mdz_opcodes.ts +0 -205
  111. package/src/lib/mdz_opcodes_to_nodes.ts +0 -375
  112. package/src/lib/mdz_stream_parser.ts +0 -415
  113. package/src/lib/mdz_stream_parser_block.ts +0 -532
  114. package/src/lib/mdz_stream_parser_inline.ts +0 -414
  115. package/src/lib/mdz_stream_parser_link.ts +0 -271
  116. package/src/lib/mdz_stream_parser_state.ts +0 -539
  117. package/src/lib/mdz_stream_parser_text.ts +0 -77
  118. package/src/lib/mdz_stream_parser_url.ts +0 -365
  119. package/src/lib/mdz_stream_state.svelte.ts +0 -387
  120. package/src/lib/mdz_to_svelte.ts +0 -141
  121. package/src/lib/mdz_token_parser.ts +0 -434
  122. package/src/lib/svelte_preprocess_mdz.ts +0 -742
  123. package/src/lib/tsdoc_mdz.ts +0 -97
@@ -1,532 +0,0 @@
1
- /**
2
- * Block-element handlers for the streaming mdz parser.
3
- *
4
- * Each `try_*` returns {@link TryResult} so the orchestrator in
5
- * {@link ./mdz_stream_parser.ts} can fall through or wait for more input.
6
- *
7
- * @module
8
- */
9
-
10
- import {
11
- BACKTICK,
12
- HASH,
13
- HR_HYPHEN_COUNT,
14
- HYPHEN,
15
- MAX_HEADING_LEVEL,
16
- MIN_CODEBLOCK_BACKTICKS,
17
- NEWLINE,
18
- SPACE,
19
- } from './mdz_helpers.js';
20
- import {
21
- type CodeblockState,
22
- type MdzStreamParserState,
23
- type TryResult,
24
- accumulate_text,
25
- alloc_id,
26
- close_paragraph,
27
- emit,
28
- flush_text,
29
- offset,
30
- push_stack_entry,
31
- } from './mdz_stream_parser_state.js';
32
-
33
- /**
34
- * Try to parse a heading at column 0.
35
- * Returns true if consumed, false if needs more input, null if definitely not a heading.
36
- */
37
- export const try_heading = (state: MdzStreamParserState): TryResult => {
38
- const start = state.pos;
39
- let i = start;
40
-
41
- // count hashes
42
- let hash_count = 0;
43
- while (
44
- i < state.buffer.length &&
45
- state.buffer.charCodeAt(i) === HASH &&
46
- hash_count <= MAX_HEADING_LEVEL
47
- ) {
48
- hash_count++;
49
- i++;
50
- }
51
-
52
- if (hash_count === 0 || hash_count > MAX_HEADING_LEVEL) return 'not_match';
53
-
54
- // need at least one more char to check for space
55
- if (i >= state.buffer.length) return 'need_more';
56
-
57
- // must have space after hashes
58
- if (state.buffer.charCodeAt(i) !== SPACE) return 'not_match';
59
- i++; // consume space
60
-
61
- // must have at least one non-whitespace char (but we might not have it yet)
62
- // for streaming, we'll open the heading optimistically when we see `# `
63
- // if the content turns out empty, that's an edge case we can handle on close
64
-
65
- // close any open paragraph first
66
- flush_text(state);
67
- close_paragraph(state);
68
-
69
- const id = alloc_id(state);
70
- const heading_start = offset(state, start);
71
- emit(state, {
72
- type: 'open',
73
- id,
74
- node_type: 'Heading',
75
- start: heading_start,
76
- level: hash_count as 1 | 2 | 3 | 4 | 5 | 6,
77
- });
78
- push_stack_entry(state, id, 'Heading', heading_start);
79
- state.in_heading = true;
80
- state.heading_text_parts = [''];
81
- state.pos = i;
82
- state.column = i - start;
83
- state.prev_char = SPACE;
84
- return 'consumed';
85
- };
86
-
87
- /**
88
- * Try to parse a horizontal rule at column 0.
89
- * Returns true if consumed, false if needs more input, null if definitely not an HR.
90
- * When `forced` is true, treats buffer end as EOF (no more input coming).
91
- */
92
- export const try_hr = (state: MdzStreamParserState, forced = false): TryResult => {
93
- const start = state.pos;
94
- let i = start;
95
-
96
- // must have exactly 3 hyphens
97
- if (i + HR_HYPHEN_COUNT > state.buffer.length) return 'need_more';
98
- for (let j = 0; j < HR_HYPHEN_COUNT; j++) {
99
- if (state.buffer.charCodeAt(i + j) !== HYPHEN) return 'not_match';
100
- }
101
- i += HR_HYPHEN_COUNT;
102
-
103
- // after hyphens, only spaces allowed before newline/EOF
104
- while (i < state.buffer.length) {
105
- const c = state.buffer.charCodeAt(i);
106
- if (c === NEWLINE) break;
107
- if (c !== SPACE) return 'not_match';
108
- i++;
109
- }
110
-
111
- // if buffer ends without newline/EOF confirmation, need more input (unless forced)
112
- if (i >= state.buffer.length && !forced) {
113
- return 'need_more';
114
- }
115
-
116
- flush_text(state);
117
- close_paragraph(state);
118
-
119
- const id = alloc_id(state);
120
- emit(state, {
121
- type: 'void',
122
- id,
123
- node_type: 'Hr',
124
- start: offset(state, start),
125
- end: offset(state, i),
126
- });
127
- if (forced) {
128
- state.pos = state.buffer.length;
129
- } else {
130
- state.pos = i;
131
- if (i < state.buffer.length && state.buffer.charCodeAt(i) === NEWLINE) {
132
- state.pos++; // consume the newline
133
- }
134
- // skip additional newlines
135
- while (state.pos < state.buffer.length && state.buffer.charCodeAt(state.pos) === NEWLINE) {
136
- state.pos++;
137
- }
138
- if (state.pos >= state.buffer.length) {
139
- state.skip_leading_newlines = true;
140
- }
141
- }
142
- state.column = 0;
143
- state.prev_char = NEWLINE;
144
- return 'consumed';
145
- };
146
-
147
- /**
148
- * Try to open a code block at column 0.
149
- *
150
- * Opens the codeblock optimistically when possible. When the current paragraph
151
- * has no prior content, opens immediately on `\`\`\`lang\n` without lookahead,
152
- * enabling streaming. When the paragraph has content (text before the fence on
153
- * a prior line), falls back to lookahead via `find_closing_fence` to avoid
154
- * splitting the paragraph on revert.
155
- */
156
- export const try_codeblock_open = (state: MdzStreamParserState): TryResult => {
157
- const start = state.pos;
158
- let i = start;
159
-
160
- // count backticks
161
- let backtick_count = 0;
162
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
163
- backtick_count++;
164
- i++;
165
- }
166
-
167
- if (backtick_count < MIN_CODEBLOCK_BACKTICKS) {
168
- // if buffer ended mid-backtick-sequence, need more input
169
- if (i >= state.buffer.length) return 'need_more';
170
- // definitely not enough backticks
171
- return 'not_match';
172
- }
173
-
174
- // find end of opening fence line (need newline)
175
- const lang_start = i;
176
- while (i < state.buffer.length) {
177
- const c = state.buffer.charCodeAt(i);
178
- if (c === NEWLINE) break;
179
- if (c === SPACE) break;
180
- i++;
181
- }
182
- const lang = i > lang_start ? state.buffer.slice(lang_start, i) : null;
183
-
184
- // skip trailing spaces on opening fence line
185
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === SPACE) {
186
- i++;
187
- }
188
-
189
- // must find newline
190
- if (i >= state.buffer.length) return 'need_more';
191
- if (state.buffer.charCodeAt(i) !== NEWLINE) return 'not_match';
192
- i++; // consume newline
193
-
194
- // when the current paragraph has content, closing it would split text on
195
- // revert — fall back to lookahead to avoid that
196
- if (state.in_paragraph && (state.accumulated_text.length > 0 || state.active_text_id !== null)) {
197
- const closing_result = find_closing_fence(state, i, backtick_count);
198
- if (closing_result === 'not_found') return 'need_more';
199
- if (closing_result === 'invalid') return 'not_match';
200
- }
201
-
202
- // build delimiter for potential revert (e.g. "```ts\n" or "```\n")
203
- const delimiter = state.buffer.slice(start, i);
204
-
205
- flush_text(state);
206
- close_paragraph(state);
207
-
208
- const id = alloc_id(state);
209
- const cb_start = offset(state, start);
210
- emit(state, {type: 'open', id, node_type: 'Codeblock', start: cb_start, lang});
211
- state.codeblock = {id, backtick_count, text_id: null, delimiter, start: cb_start};
212
- state.pos = i;
213
- state.column = 0;
214
- state.prev_char = NEWLINE;
215
- return 'consumed';
216
- };
217
-
218
- /**
219
- * Scan buffer from `start` for a valid closing fence with `backtick_count` backticks.
220
- * Used as a fallback when the current paragraph has prior content (to avoid
221
- * splitting the paragraph on revert). Returns 'found' if a valid closing fence
222
- * exists, 'not_found' if the buffer ends before we can determine, 'invalid' if
223
- * we can see the full remaining content and there's no valid closing fence.
224
- */
225
- export const find_closing_fence = (
226
- state: MdzStreamParserState,
227
- start: number,
228
- backtick_count: number,
229
- ): 'found' | 'not_found' | 'invalid' => {
230
- let i = start;
231
- const content_start = start;
232
- let at_col0 = true;
233
-
234
- while (i < state.buffer.length) {
235
- if (at_col0 && state.buffer.charCodeAt(i) === BACKTICK) {
236
- // count backticks
237
- let count = 0;
238
- const fence_start = i;
239
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
240
- count++;
241
- i++;
242
- }
243
- if (count === backtick_count) {
244
- // skip trailing spaces
245
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === SPACE) {
246
- i++;
247
- }
248
- // must be followed by newline or EOF
249
- if (i >= state.buffer.length || state.buffer.charCodeAt(i) === NEWLINE) {
250
- // check for empty content
251
- const content = state.buffer.slice(content_start, fence_start);
252
- const final_content = content.endsWith('\n') ? content.slice(0, -1) : content;
253
- if (final_content.length === 0) return 'invalid'; // empty codeblock
254
- return 'found';
255
- }
256
- }
257
- // not a valid fence — continue scanning
258
- continue;
259
- }
260
-
261
- if (state.buffer.charCodeAt(i) === NEWLINE) {
262
- at_col0 = true;
263
- } else {
264
- at_col0 = false;
265
- }
266
- i++;
267
- }
268
-
269
- // reached end of buffer without finding closing fence
270
- return 'not_found';
271
- };
272
-
273
- /**
274
- * Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
275
- * chunks), updating `cb.text_id` on first emit. `content_start` is the local
276
- * buffer position where the content slice began — only used to compute the
277
- * global byte offset for the first `text` opcode.
278
- */
279
- export const emit_codeblock_text = (
280
- state: MdzStreamParserState,
281
- cb: CodeblockState,
282
- content: string,
283
- content_start: number,
284
- ): void => {
285
- if (cb.text_id !== null) {
286
- emit(state, {type: 'append_text', id: cb.text_id, content});
287
- } else {
288
- const id = alloc_id(state);
289
- const text_start = offset(state, content_start);
290
- emit(state, {
291
- type: 'text',
292
- id,
293
- content,
294
- text_type: 'Text',
295
- start: text_start,
296
- end: text_start + content.length,
297
- });
298
- cb.text_id = id;
299
- }
300
- };
301
-
302
- /**
303
- * Drain the remaining buffer as codeblock content at EOF, looking for a closing
304
- * fence whose terminator is EOF rather than `\n`. Matches `mdz_parse`'s
305
- * `#match_code_block`, which allows a fence at the end of the document.
306
- *
307
- * If a fence is found, the codeblock closes normally. Otherwise the remaining
308
- * buffer becomes content and `close_codeblock_at_eof` (in state.ts) handles
309
- * the revert-and-wrap dance after process completes.
310
- */
311
- export const process_codeblock_forced = (state: MdzStreamParserState): void => {
312
- const cb = state.codeblock!;
313
- const start = state.pos;
314
-
315
- while (state.pos < state.buffer.length) {
316
- if (state.column === 0) {
317
- const fence_match = match_codeblock_close(state, cb.backtick_count, true);
318
- if (fence_match !== -1) {
319
- // emit content up to the closing fence (excluding trailing newline before fence)
320
- let content_end = state.pos;
321
- if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
322
- content_end--;
323
- }
324
- const content = state.buffer.slice(start, content_end);
325
- if (content.length > 0) {
326
- emit_codeblock_text(state, cb, content, start);
327
- }
328
-
329
- // empty codeblock — revert (matches process_codeblock behavior)
330
- if (cb.text_id === null && content.length === 0) {
331
- revert_empty_codeblock(state, cb);
332
- return;
333
- }
334
-
335
- emit(state, {type: 'close', id: cb.id, end: offset(state, fence_match)});
336
- state.pos = fence_match;
337
- state.codeblock = null;
338
- return;
339
- }
340
- }
341
-
342
- const c = state.buffer.charCodeAt(state.pos);
343
- if (c === NEWLINE) {
344
- state.column = 0;
345
- } else {
346
- state.column++;
347
- }
348
- state.pos++;
349
- }
350
-
351
- // no closing fence — dump the rest as codeblock content; caller's
352
- // `close_codeblock_at_eof` then reverts the (now-unclosed) codeblock.
353
- const remaining = state.buffer.slice(start, state.pos);
354
- if (remaining.length > 0) {
355
- emit_codeblock_text(state, cb, remaining, start);
356
- }
357
- };
358
-
359
- /**
360
- * Process bytes while in codeblock mode.
361
- * Returns true if processing should continue, false if more input needed.
362
- */
363
- export const process_codeblock = (state: MdzStreamParserState): boolean => {
364
- const cb = state.codeblock!;
365
- const start = state.pos;
366
-
367
- while (state.pos < state.buffer.length) {
368
- // check for closing fence at column 0
369
- if (state.column === 0) {
370
- const fence_match = match_codeblock_close(state, cb.backtick_count);
371
- if (fence_match === -2) {
372
- // need more input — hold from this position (don't consume potential fence chars)
373
- break;
374
- }
375
- if (fence_match !== -1) {
376
- // emit content up to the closing fence (excluding trailing newline before fence)
377
- let content_end = state.pos;
378
- if (content_end > start && state.buffer.charCodeAt(content_end - 1) === NEWLINE) {
379
- content_end--;
380
- }
381
- const content = state.buffer.slice(start, content_end);
382
- if (content.length > 0) {
383
- emit_codeblock_text(state, cb, content, start);
384
- }
385
-
386
- // empty codeblock: no content was ever emitted → invalid, revert
387
- if (cb.text_id === null && content.length === 0) {
388
- return revert_empty_codeblock(state, cb);
389
- }
390
-
391
- // codeblock end = position of newline after closing fence (exclusive)
392
- emit(state, {type: 'close', id: cb.id, end: offset(state, fence_match - 1)});
393
- state.pos = fence_match;
394
- state.codeblock = null;
395
- state.column = 0;
396
- state.prev_char = NEWLINE;
397
- // skip newlines after codeblock
398
- while (state.pos < state.buffer.length && state.buffer.charCodeAt(state.pos) === NEWLINE) {
399
- state.pos++;
400
- }
401
- // if buffer ended before absorbing all trailing newlines, continue in next chunk
402
- if (state.pos >= state.buffer.length) {
403
- state.skip_leading_newlines = true;
404
- }
405
- return true;
406
- }
407
- }
408
-
409
- // not a closing fence — advance
410
- const c = state.buffer.charCodeAt(state.pos);
411
- if (c === NEWLINE) {
412
- state.column = 0;
413
- } else {
414
- state.column++;
415
- }
416
- state.pos++;
417
- }
418
-
419
- // buffer exhausted or broke for potential fence — emit content so far, hold trailing data.
420
- // Hold trailing \n (could be before a closing fence in next chunk), and also hold
421
- // \n + backticks when we broke mid-fence-check (the \n precedes the backticks and
422
- // would be stripped by fence close, so must not be emitted as content yet).
423
- let emit_end = state.pos;
424
- if (emit_end > start && state.buffer.charCodeAt(emit_end - 1) === NEWLINE) {
425
- emit_end--;
426
- state.pos = emit_end; // leave \n (+ any held backticks after it) in buffer
427
- }
428
- const content = state.buffer.slice(start, emit_end);
429
- if (content.length > 0) {
430
- emit_codeblock_text(state, cb, content, start);
431
- }
432
- return false; // need more input
433
- };
434
-
435
- /**
436
- * Check if the buffer at current position has a codeblock closing fence.
437
- * Returns the position after the fence (including trailing newline) on success,
438
- * `-1` when definitely not a fence, or `-2` when more input is needed to decide.
439
- *
440
- * When `forced` is true, EOF after the fence counts as a valid terminator
441
- * (matches `mdz_parse`'s handling of code blocks at end of input).
442
- */
443
- export const match_codeblock_close = (
444
- state: MdzStreamParserState,
445
- backtick_count: number,
446
- forced: boolean = false,
447
- ): number => {
448
- let i = state.pos;
449
-
450
- // count backticks
451
- let count = 0;
452
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === BACKTICK) {
453
- count++;
454
- i++;
455
- }
456
- if (count !== backtick_count) {
457
- // buffer ended mid-backtick-sequence — could still become a fence
458
- if (!forced && i >= state.buffer.length && count < backtick_count) return -2;
459
- return -1;
460
- }
461
-
462
- // trailing spaces allowed
463
- while (i < state.buffer.length && state.buffer.charCodeAt(i) === SPACE) {
464
- i++;
465
- }
466
-
467
- // must be followed by newline or EOF
468
- if (i < state.buffer.length && state.buffer.charCodeAt(i) === NEWLINE) {
469
- return i + 1;
470
- }
471
- if (i >= state.buffer.length) {
472
- // in forced mode, EOF terminates the fence
473
- if (forced) return i;
474
- // exact backtick count but buffer ended (could be spaces or newline coming)
475
- return -2;
476
- }
477
- return -1;
478
- };
479
-
480
- /**
481
- * Revert an empty codeblock (closing fence found but no content was emitted).
482
- * Wraps the opening fence delimiter in a new paragraph and accumulates the
483
- * closing fence text for normal paragraph processing.
484
- */
485
- export const revert_empty_codeblock = (
486
- state: MdzStreamParserState,
487
- cb: CodeblockState,
488
- ): boolean => {
489
- const wrap_id = alloc_id(state);
490
- emit(state, {
491
- type: 'revert',
492
- id: cb.id,
493
- replacement_text: cb.delimiter,
494
- start: cb.start,
495
- wrap_node_type: 'Paragraph',
496
- wrap_id,
497
- });
498
-
499
- // enter paragraph mode with the wrapper
500
- push_stack_entry(state, wrap_id, 'Paragraph', cb.start);
501
- state.in_paragraph = true;
502
- state.active_text_id = null;
503
- state.codeblock = null;
504
-
505
- // accumulate the closing fence text (backticks + spaces, without trailing newline)
506
- const fence_start = state.pos;
507
- let fence_content_end = fence_start;
508
- while (
509
- fence_content_end < state.buffer.length &&
510
- state.buffer.charCodeAt(fence_content_end) === BACKTICK
511
- ) {
512
- fence_content_end++;
513
- }
514
- while (
515
- fence_content_end < state.buffer.length &&
516
- state.buffer.charCodeAt(fence_content_end) === SPACE
517
- ) {
518
- fence_content_end++;
519
- }
520
- accumulate_text(
521
- state,
522
- state.buffer.slice(fence_start, fence_content_end),
523
- offset(state, fence_start),
524
- );
525
-
526
- // position past the closing fence content, leaving any trailing newline for normal processing
527
- state.pos = fence_content_end;
528
- state.column = fence_content_end - fence_start;
529
- state.prev_char =
530
- fence_content_end > fence_start ? state.buffer.charCodeAt(fence_content_end - 1) : NEWLINE;
531
- return true;
532
- };