@fuzdev/fuz_ui 0.198.1 → 0.200.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/dist/ApiDeclarationList.svelte +2 -1
- package/dist/ApiDeclarationList.svelte.d.ts.map +1 -1
- package/dist/ApiIndex.svelte +2 -2
- package/dist/ApiModule.svelte +3 -3
- package/dist/DeclarationDetail.svelte +145 -90
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
- package/dist/LibraryDetail.svelte +14 -16
- package/dist/LibraryDetail.svelte.d.ts.map +1 -1
- package/dist/LibrarySummary.svelte +12 -12
- package/dist/Mdz.svelte +2 -2
- package/dist/MdzNodeView.svelte +8 -6
- package/dist/MdzNodeView.svelte.d.ts.map +1 -1
- package/dist/MdzRoot.svelte +30 -0
- package/dist/MdzRoot.svelte.d.ts +12 -0
- package/dist/MdzRoot.svelte.d.ts.map +1 -0
- package/dist/MdzStream.svelte +32 -0
- package/dist/MdzStream.svelte.d.ts +12 -0
- package/dist/MdzStream.svelte.d.ts.map +1 -0
- package/dist/MdzStreamNodeView.svelte +106 -0
- package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
- package/dist/ProjectLinks.svelte +1 -1
- package/dist/api_search.svelte.d.ts.map +1 -1
- package/dist/api_search.svelte.js +4 -2
- package/dist/declaration.svelte.d.ts +11 -0
- package/dist/declaration.svelte.d.ts.map +1 -1
- package/dist/declaration.svelte.js +15 -3
- package/dist/library.svelte.d.ts +9 -70
- package/dist/library.svelte.d.ts.map +1 -1
- package/dist/library.svelte.js +24 -18
- package/dist/library_helpers.d.ts +3 -3
- package/dist/library_helpers.js +3 -3
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +37 -29
- package/dist/mdz_components.d.ts +37 -23
- package/dist/mdz_components.d.ts.map +1 -1
- package/dist/mdz_components.js +16 -4
- package/dist/mdz_helpers.d.ts +46 -14
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +189 -56
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +18 -22
- package/dist/mdz_opcodes.d.ts +174 -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 +20 -0
- package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
- package/dist/mdz_opcodes_to_nodes.js +332 -0
- package/dist/mdz_stream_parser.d.ts +80 -0
- package/dist/mdz_stream_parser.d.ts.map +1 -0
- package/dist/mdz_stream_parser.js +354 -0
- package/dist/mdz_stream_parser_block.d.ts +76 -0
- package/dist/mdz_stream_parser_block.d.ts.map +1 -0
- package/dist/mdz_stream_parser_block.js +458 -0
- package/dist/mdz_stream_parser_inline.d.ts +52 -0
- package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
- package/dist/mdz_stream_parser_inline.js +378 -0
- package/dist/mdz_stream_parser_link.d.ts +22 -0
- package/dist/mdz_stream_parser_link.d.ts.map +1 -0
- package/dist/mdz_stream_parser_link.js +215 -0
- package/dist/mdz_stream_parser_state.d.ts +187 -0
- package/dist/mdz_stream_parser_state.d.ts.map +1 -0
- package/dist/mdz_stream_parser_state.js +398 -0
- package/dist/mdz_stream_parser_text.d.ts +14 -0
- package/dist/mdz_stream_parser_text.d.ts.map +1 -0
- package/dist/mdz_stream_parser_text.js +50 -0
- package/dist/mdz_stream_parser_url.d.ts +60 -0
- package/dist/mdz_stream_parser_url.d.ts.map +1 -0
- package/dist/mdz_stream_parser_url.js +307 -0
- package/dist/mdz_stream_state.svelte.d.ts +44 -0
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
- package/dist/mdz_stream_state.svelte.js +357 -0
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +8 -39
- package/dist/module.svelte.d.ts +2 -0
- package/dist/module.svelte.d.ts.map +1 -1
- package/dist/module.svelte.js +3 -1
- package/dist/site.svelte.d.ts +13 -0
- package/dist/site.svelte.d.ts.map +1 -1
- package/dist/site.svelte.js +8 -2
- package/dist/tsdoc_mdz.d.ts +2 -2
- package/dist/tsdoc_mdz.js +2 -2
- package/dist/vite_plugin_pkg_json.d.ts +63 -0
- package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
- package/dist/vite_plugin_pkg_json.js +126 -0
- package/package.json +8 -7
- package/src/lib/api_search.svelte.ts +4 -2
- package/src/lib/declaration.svelte.ts +18 -3
- package/src/lib/library.svelte.ts +37 -20
- package/src/lib/library_helpers.ts +3 -3
- package/src/lib/mdz.ts +38 -29
- package/src/lib/mdz_components.ts +40 -19
- package/src/lib/mdz_helpers.ts +199 -56
- package/src/lib/mdz_lexer.ts +18 -20
- package/src/lib/mdz_opcodes.ts +205 -0
- package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
- package/src/lib/mdz_stream_parser.ts +415 -0
- package/src/lib/mdz_stream_parser_block.ts +532 -0
- package/src/lib/mdz_stream_parser_inline.ts +414 -0
- package/src/lib/mdz_stream_parser_link.ts +271 -0
- package/src/lib/mdz_stream_parser_state.ts +539 -0
- package/src/lib/mdz_stream_parser_text.ts +77 -0
- package/src/lib/mdz_stream_parser_url.ts +365 -0
- package/src/lib/mdz_stream_state.svelte.ts +387 -0
- package/src/lib/mdz_token_parser.ts +13 -40
- package/src/lib/module.svelte.ts +5 -1
- package/src/lib/site.svelte.ts +17 -2
- package/src/lib/tsdoc_mdz.ts +2 -2
- package/src/lib/vite_plugin_pkg_json.ts +142 -0
- package/dist/package_helpers.d.ts +0 -150
- package/dist/package_helpers.d.ts.map +0 -1
- package/dist/package_helpers.js +0 -179
- package/src/lib/package_helpers.ts +0 -186
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming opcode parser for mdz.
|
|
3
|
+
*
|
|
4
|
+
* Fed chunks of text (e.g., from LLM output), emits opcodes as rendering
|
|
5
|
+
* instructions. Makes optimistic assumptions about ambiguous syntax and
|
|
6
|
+
* emits `revert` opcodes to correct when wrong. Never re-parses.
|
|
7
|
+
*
|
|
8
|
+
* The design was independently arrived at but shares goals with
|
|
9
|
+
* {@link https://bsky.app/profile/pngwn.at/post/3mi527zntb22n @pngwn.at}'s
|
|
10
|
+
* Penguin-Flavoured Markdown (PFM): restrict the syntax so streaming is tractable,
|
|
11
|
+
* render optimistically and correct when wrong, emit serializable opcodes
|
|
12
|
+
* to avoid re-parsing, and keep the opcodes target-agnostic so any renderer
|
|
13
|
+
* can consume them. mdz diverges in one respect: the Svelte consumer
|
|
14
|
+
* (`MdzStreamState`) does build a reactive tree from opcodes — the platform
|
|
15
|
+
* dictates this — but mutations are fine-grained via `$state`, not diffed.
|
|
16
|
+
*
|
|
17
|
+
* The parser is split across sibling modules: this file holds the public
|
|
18
|
+
* `MdzStreamParser` class and the `process_loop` / `process_inline`
|
|
19
|
+
* orchestrators. Per-category handlers (block / inline / link / url / text)
|
|
20
|
+
* live in `mdz_stream_parser_*.ts` as free functions taking the shared
|
|
21
|
+
* `MdzStreamParserState` as first argument.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const parser = new MdzStreamParser();
|
|
26
|
+
* parser.feed('hello **bold');
|
|
27
|
+
* const ops1 = parser.take_opcodes(); // open Paragraph, text "hello ", open Bold, text "bold"
|
|
28
|
+
* parser.feed('** world');
|
|
29
|
+
* const ops2 = parser.take_opcodes(); // close Bold, text " world"
|
|
30
|
+
* parser.finish();
|
|
31
|
+
* const ops3 = parser.take_opcodes(); // close Paragraph
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, LEFT_ANGLE, SLASH, LEFT_BRACKET, RIGHT_BRACKET, H_LOWER, H_UPPER, PERIOD, is_word_char, } from './mdz_helpers.js';
|
|
37
|
+
import { accumulate_text, close_codeblock_at_eof, close_heading, close_paragraph, create_state, ensure_paragraph, find_open, flush_text, handle_paragraph_break, offset, } from './mdz_stream_parser_state.js';
|
|
38
|
+
import { process_codeblock, process_codeblock_forced, try_codeblock_open, try_heading, try_hr, } from './mdz_stream_parser_block.js';
|
|
39
|
+
import { check_close_word_boundary, close_bold, close_single_delimiter, process_code_content, try_bold, try_code, try_italic, try_strikethrough, } from './mdz_stream_parser_inline.js';
|
|
40
|
+
import { try_close_tag, try_complete_link, try_link_open, try_tag_open, } from './mdz_stream_parser_link.js';
|
|
41
|
+
import { complete_pending_url, process_url_content, start_speculative_url, try_auto_path_absolute, try_auto_path_relative, try_auto_url_forced, } from './mdz_stream_parser_url.js';
|
|
42
|
+
import { consume_text_run } from './mdz_stream_parser_text.js';
|
|
43
|
+
/**
|
|
44
|
+
* Streaming opcode parser for mdz content.
|
|
45
|
+
* Feed chunks via `feed()`, retrieve opcodes via `take_opcodes()`, call `finish()` at end.
|
|
46
|
+
*
|
|
47
|
+
* The opcode sequence is not deterministic across chunk boundaries — the same input
|
|
48
|
+
* fed in different chunk sizes may produce different `text`/`append_text` splits and
|
|
49
|
+
* different optimistic/revert sequences. The final tree (via `mdz_opcodes_to_nodes`)
|
|
50
|
+
* matches the one-shot result for all input except one case: italic (`_..._`) where
|
|
51
|
+
* the opening and closing delimiters straddle a chunk boundary. Italic is
|
|
52
|
+
* non-optimistic — it requires a confirmed closer in the buffer — so when the
|
|
53
|
+
* closer arrives only in a later chunk, the opening `_` has already been emitted
|
|
54
|
+
* as text and italic cannot apply retroactively. See `try_italic` in
|
|
55
|
+
* `mdz_stream_parser_inline.ts`.
|
|
56
|
+
*/
|
|
57
|
+
export class MdzStreamParser {
|
|
58
|
+
#state = create_state();
|
|
59
|
+
/**
|
|
60
|
+
* Feed a chunk of text to the parser.
|
|
61
|
+
* Opcodes are accumulated and retrieved via `take_opcodes()`.
|
|
62
|
+
*/
|
|
63
|
+
feed(chunk) {
|
|
64
|
+
const state = this.#state;
|
|
65
|
+
state.buffer += chunk;
|
|
66
|
+
process_loop(state, false);
|
|
67
|
+
// flush any accumulated text so the renderer shows it immediately
|
|
68
|
+
flush_text(state);
|
|
69
|
+
// drain processed bytes from the buffer
|
|
70
|
+
if (state.pos > 0) {
|
|
71
|
+
state.base_offset += state.pos;
|
|
72
|
+
state.buffer = state.buffer.slice(state.pos);
|
|
73
|
+
state.pos = 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Signal end of input. Resolves all pending state: closes open blocks,
|
|
78
|
+
* reverts unclosed optimistic opens, trims trailing newlines.
|
|
79
|
+
*
|
|
80
|
+
* Trailing-newline trimming is handled in one place: `trim_trailing_newline()`
|
|
81
|
+
* called at the top of `close_paragraph()` and `close_codeblock_at_eof()`,
|
|
82
|
+
* before either function reverts its inner stack. The trim sees the
|
|
83
|
+
* just-flushed text node's `last_text_id` (or a still-accumulated `\n`) and
|
|
84
|
+
* emits a `trim_text` opcode. Revert opcodes only fire after.
|
|
85
|
+
*
|
|
86
|
+
* Optimistic-container revert is handled by `close_paragraph` and
|
|
87
|
+
* `close_heading` (each pops its own inner stack), so no separate
|
|
88
|
+
* `revert_all_optimistic` is needed — optimistic containers can only exist
|
|
89
|
+
* inside an open Paragraph or Heading (parser invariant).
|
|
90
|
+
*/
|
|
91
|
+
finish() {
|
|
92
|
+
const state = this.#state;
|
|
93
|
+
// process any remaining buffer — `forced=true` treats ambiguous trailing
|
|
94
|
+
// bytes as concrete content rather than waiting for more input
|
|
95
|
+
if (state.buffer.length > 0) {
|
|
96
|
+
process_loop(state, true);
|
|
97
|
+
}
|
|
98
|
+
// finalize pending URL if any (text-first auto-links)
|
|
99
|
+
if (state.pending_url !== null) {
|
|
100
|
+
flush_text(state);
|
|
101
|
+
complete_pending_url(state);
|
|
102
|
+
}
|
|
103
|
+
flush_text(state);
|
|
104
|
+
// close heading if open — internally reverts its inner optimistic stack
|
|
105
|
+
if (state.in_heading) {
|
|
106
|
+
close_heading(state);
|
|
107
|
+
}
|
|
108
|
+
// close paragraph if open — internally trims trailing \n, reverts its
|
|
109
|
+
// inner optimistic stack, then pops
|
|
110
|
+
close_paragraph(state);
|
|
111
|
+
// close codeblock if open (unclosed at EOF) — internally trims trailing \n
|
|
112
|
+
if (state.codeblock) {
|
|
113
|
+
close_codeblock_at_eof(state);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Drain and return all accumulated opcodes.
|
|
118
|
+
* Destructive — empties the internal queue. The returned array is owned by the caller.
|
|
119
|
+
*/
|
|
120
|
+
take_opcodes() {
|
|
121
|
+
const ops = this.#state.opcodes;
|
|
122
|
+
this.#state.opcodes = [];
|
|
123
|
+
return ops;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// -- Processing loop --
|
|
127
|
+
/**
|
|
128
|
+
* Core processing loop. When `forced` is false (normal streaming), returns
|
|
129
|
+
* when more input is needed. When `forced` is true (EOF), treats buffer end
|
|
130
|
+
* as content end and never waits for more input.
|
|
131
|
+
*/
|
|
132
|
+
const process_loop = (state, forced) => {
|
|
133
|
+
// absorb leading newlines at start of input or left over from a block element
|
|
134
|
+
// close in a prior chunk. Only clear the flag once a non-newline char arrives —
|
|
135
|
+
// otherwise an all-newline chunk would prematurely drop the flag and let the
|
|
136
|
+
// next chunk's leading content stick to a `\n` text node.
|
|
137
|
+
if (state.skip_leading_newlines) {
|
|
138
|
+
while (state.pos < state.buffer.length && state.buffer.charCodeAt(state.pos) === NEWLINE) {
|
|
139
|
+
state.pos++;
|
|
140
|
+
}
|
|
141
|
+
if (state.pos < state.buffer.length) {
|
|
142
|
+
state.skip_leading_newlines = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
while (state.pos < state.buffer.length) {
|
|
146
|
+
// codeblock mode
|
|
147
|
+
if (state.codeblock) {
|
|
148
|
+
if (forced) {
|
|
149
|
+
process_codeblock_forced(state);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!process_codeblock(state))
|
|
153
|
+
return; // need more input
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// optimistic inline code mode — raw scanning for close/revert
|
|
157
|
+
if (state.in_code) {
|
|
158
|
+
process_code_content(state);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// text-first URL/path scanning mode
|
|
162
|
+
if (state.pending_url !== null) {
|
|
163
|
+
process_url_content(state, forced);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const char_code = state.buffer.charCodeAt(state.pos);
|
|
167
|
+
// newline handling
|
|
168
|
+
if (char_code === NEWLINE) {
|
|
169
|
+
if (state.in_heading) {
|
|
170
|
+
// newline ends heading
|
|
171
|
+
flush_text(state);
|
|
172
|
+
close_heading(state);
|
|
173
|
+
state.pos++;
|
|
174
|
+
// absorb consecutive newlines after heading
|
|
175
|
+
while (state.pos < state.buffer.length && state.buffer.charCodeAt(state.pos) === NEWLINE) {
|
|
176
|
+
state.pos++;
|
|
177
|
+
}
|
|
178
|
+
if (state.pos >= state.buffer.length) {
|
|
179
|
+
state.skip_leading_newlines = true;
|
|
180
|
+
}
|
|
181
|
+
state.column = 0;
|
|
182
|
+
state.prev_char = NEWLINE;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// hold trailing \n when streaming (could be start of \n\n)
|
|
186
|
+
if (!forced && state.pos + 1 >= state.buffer.length)
|
|
187
|
+
return;
|
|
188
|
+
// check for paragraph break (\n\n)
|
|
189
|
+
if (state.pos + 1 < state.buffer.length &&
|
|
190
|
+
state.buffer.charCodeAt(state.pos + 1) === NEWLINE) {
|
|
191
|
+
handle_paragraph_break(state);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
// single newline: accumulate as text
|
|
195
|
+
accumulate_text(state, '\n', offset(state));
|
|
196
|
+
state.pos++;
|
|
197
|
+
state.column = 0;
|
|
198
|
+
state.prev_char = NEWLINE;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// block elements at column 0
|
|
202
|
+
if (state.column === 0 && !state.in_heading) {
|
|
203
|
+
if (char_code === HASH) {
|
|
204
|
+
const r = try_heading(state);
|
|
205
|
+
if (r === 'consumed')
|
|
206
|
+
continue;
|
|
207
|
+
if (!forced && r === 'need_more')
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
else if (char_code === HYPHEN) {
|
|
211
|
+
const r = try_hr(state, forced);
|
|
212
|
+
if (r === 'consumed')
|
|
213
|
+
continue;
|
|
214
|
+
if (!forced && r === 'need_more')
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
else if (char_code === BACKTICK) {
|
|
218
|
+
const r = try_codeblock_open(state);
|
|
219
|
+
if (r === 'consumed')
|
|
220
|
+
continue;
|
|
221
|
+
if (!forced && r === 'need_more')
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// inline processing
|
|
226
|
+
if (!process_inline(state, forced))
|
|
227
|
+
return; // need more input
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
// -- Inline processing --
|
|
231
|
+
/**
|
|
232
|
+
* Process one inline element. Returns false if more input is needed.
|
|
233
|
+
* When `forced` is true (EOF processing), skips optimistic opening constructs
|
|
234
|
+
* (no new bold/code/link/tag opens) but still handles italic (non-optimistic,
|
|
235
|
+
* safe to try), all closing constructs, and auto-links.
|
|
236
|
+
* Never returns false in forced mode.
|
|
237
|
+
*/
|
|
238
|
+
const process_inline = (state, forced) => {
|
|
239
|
+
const char_code = state.buffer.charCodeAt(state.pos);
|
|
240
|
+
// check for closing delimiters first (if matching open exists)
|
|
241
|
+
if (char_code === ASTERISK &&
|
|
242
|
+
state.pos + 1 < state.buffer.length &&
|
|
243
|
+
state.buffer.charCodeAt(state.pos + 1) === ASTERISK) {
|
|
244
|
+
const bold_idx = find_open(state, 'Bold');
|
|
245
|
+
if (bold_idx !== -1) {
|
|
246
|
+
return close_bold(state, bold_idx);
|
|
247
|
+
}
|
|
248
|
+
if (!forced)
|
|
249
|
+
return try_bold(state);
|
|
250
|
+
}
|
|
251
|
+
if (char_code === ASTERISK && !forced) {
|
|
252
|
+
// could be start of ** — need next char to decide
|
|
253
|
+
if (state.pos + 1 >= state.buffer.length)
|
|
254
|
+
return false; // hold for next chunk
|
|
255
|
+
// single asterisk is text (next char is not *)
|
|
256
|
+
ensure_paragraph(state);
|
|
257
|
+
accumulate_text(state, '*', offset(state));
|
|
258
|
+
state.prev_char = ASTERISK;
|
|
259
|
+
state.column++;
|
|
260
|
+
state.pos++;
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (char_code === UNDERSCORE) {
|
|
264
|
+
const open_idx = find_open(state, 'Italic');
|
|
265
|
+
if (open_idx !== -1 && check_close_word_boundary(state)) {
|
|
266
|
+
return close_single_delimiter(state, open_idx);
|
|
267
|
+
}
|
|
268
|
+
// Italic is non-optimistic — only opens when closer is confirmed in
|
|
269
|
+
// the buffer, so it's safe (and necessary) to try in forced mode too.
|
|
270
|
+
return try_italic(state, forced);
|
|
271
|
+
}
|
|
272
|
+
if (char_code === TILDE) {
|
|
273
|
+
const open_idx = find_open(state, 'Strikethrough');
|
|
274
|
+
if (open_idx !== -1 && check_close_word_boundary(state)) {
|
|
275
|
+
return close_single_delimiter(state, open_idx);
|
|
276
|
+
}
|
|
277
|
+
if (!forced)
|
|
278
|
+
return try_strikethrough(state);
|
|
279
|
+
}
|
|
280
|
+
if (char_code === BACKTICK && !forced) {
|
|
281
|
+
return try_code(state);
|
|
282
|
+
}
|
|
283
|
+
if (char_code === LEFT_BRACKET && !forced) {
|
|
284
|
+
return try_link_open(state);
|
|
285
|
+
}
|
|
286
|
+
if (char_code === RIGHT_BRACKET && !forced) {
|
|
287
|
+
const link_idx = find_open(state, 'Link');
|
|
288
|
+
if (link_idx !== -1) {
|
|
289
|
+
return try_complete_link(state, link_idx);
|
|
290
|
+
}
|
|
291
|
+
// no open link — treat as text
|
|
292
|
+
ensure_paragraph(state);
|
|
293
|
+
accumulate_text(state, ']', offset(state));
|
|
294
|
+
state.prev_char = RIGHT_BRACKET;
|
|
295
|
+
state.column++;
|
|
296
|
+
state.pos++;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (char_code === LEFT_ANGLE) {
|
|
300
|
+
// closing tags are handled even in forced mode
|
|
301
|
+
if (state.pos + 1 < state.buffer.length && state.buffer.charCodeAt(state.pos + 1) === SLASH) {
|
|
302
|
+
const result = try_close_tag(state);
|
|
303
|
+
if (result === 'consumed')
|
|
304
|
+
return true;
|
|
305
|
+
// in forced mode, need_more falls through to text
|
|
306
|
+
if (!forced && result === 'need_more')
|
|
307
|
+
return false;
|
|
308
|
+
// not_match — fall through
|
|
309
|
+
}
|
|
310
|
+
if (!forced) {
|
|
311
|
+
const tag_result = try_tag_open(state);
|
|
312
|
+
if (tag_result === 'consumed')
|
|
313
|
+
return true;
|
|
314
|
+
if (tag_result === 'need_more')
|
|
315
|
+
return false;
|
|
316
|
+
// not_match — fall through
|
|
317
|
+
}
|
|
318
|
+
// not a valid tag — fall through to text
|
|
319
|
+
}
|
|
320
|
+
// auto-detected URLs (text-first speculative prefix matching).
|
|
321
|
+
// Fires on `h` and `H` — scheme matching is case-insensitive (RFC 3986).
|
|
322
|
+
if (char_code === H_LOWER || char_code === H_UPPER) {
|
|
323
|
+
if (forced) {
|
|
324
|
+
const result = try_auto_url_forced(state);
|
|
325
|
+
if (result === 'consumed')
|
|
326
|
+
return true;
|
|
327
|
+
if (result === 'need_more')
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
else if (state.prev_char === -1 || !is_word_char(state.prev_char)) {
|
|
331
|
+
// word boundary — start speculative URL prefix matching
|
|
332
|
+
start_speculative_url(state);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// auto-detected paths
|
|
337
|
+
if (char_code === SLASH) {
|
|
338
|
+
const result = try_auto_path_absolute(state, forced);
|
|
339
|
+
if (result === 'consumed')
|
|
340
|
+
return true;
|
|
341
|
+
if (result === 'need_more')
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
if (char_code === PERIOD) {
|
|
345
|
+
const result = try_auto_path_relative(state, forced);
|
|
346
|
+
if (result === 'consumed')
|
|
347
|
+
return true;
|
|
348
|
+
if (result === 'need_more')
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
// plain text
|
|
352
|
+
consume_text_run(state);
|
|
353
|
+
return true;
|
|
354
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
import { type CodeblockState, type MdzStreamParserState, type TryResult } from './mdz_stream_parser_state.js';
|
|
10
|
+
/**
|
|
11
|
+
* Try to parse a heading at column 0.
|
|
12
|
+
* Returns true if consumed, false if needs more input, null if definitely not a heading.
|
|
13
|
+
*/
|
|
14
|
+
export declare const try_heading: (state: MdzStreamParserState) => TryResult;
|
|
15
|
+
/**
|
|
16
|
+
* Try to parse a horizontal rule at column 0.
|
|
17
|
+
* Returns true if consumed, false if needs more input, null if definitely not an HR.
|
|
18
|
+
* When `forced` is true, treats buffer end as EOF (no more input coming).
|
|
19
|
+
*/
|
|
20
|
+
export declare const try_hr: (state: MdzStreamParserState, forced?: boolean) => TryResult;
|
|
21
|
+
/**
|
|
22
|
+
* Try to open a code block at column 0.
|
|
23
|
+
*
|
|
24
|
+
* Opens the codeblock optimistically when possible. When the current paragraph
|
|
25
|
+
* has no prior content, opens immediately on `\`\`\`lang\n` without lookahead,
|
|
26
|
+
* enabling streaming. When the paragraph has content (text before the fence on
|
|
27
|
+
* a prior line), falls back to lookahead via `find_closing_fence` to avoid
|
|
28
|
+
* splitting the paragraph on revert.
|
|
29
|
+
*/
|
|
30
|
+
export declare const try_codeblock_open: (state: MdzStreamParserState) => TryResult;
|
|
31
|
+
/**
|
|
32
|
+
* Scan buffer from `start` for a valid closing fence with `backtick_count` backticks.
|
|
33
|
+
* Used as a fallback when the current paragraph has prior content (to avoid
|
|
34
|
+
* splitting the paragraph on revert). Returns 'found' if a valid closing fence
|
|
35
|
+
* exists, 'not_found' if the buffer ends before we can determine, 'invalid' if
|
|
36
|
+
* we can see the full remaining content and there's no valid closing fence.
|
|
37
|
+
*/
|
|
38
|
+
export declare const find_closing_fence: (state: MdzStreamParserState, start: number, backtick_count: number) => "found" | "not_found" | "invalid";
|
|
39
|
+
/**
|
|
40
|
+
* Emit codeblock content as `text` (first chunk) or `append_text` (subsequent
|
|
41
|
+
* chunks), updating `cb.text_id` on first emit. `content_start` is the local
|
|
42
|
+
* buffer position where the content slice began — only used to compute the
|
|
43
|
+
* global byte offset for the first `text` opcode.
|
|
44
|
+
*/
|
|
45
|
+
export declare const emit_codeblock_text: (state: MdzStreamParserState, cb: CodeblockState, content: string, content_start: number) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Drain the remaining buffer as codeblock content at EOF, looking for a closing
|
|
48
|
+
* fence whose terminator is EOF rather than `\n`. Matches `mdz_parse`'s
|
|
49
|
+
* `#match_code_block`, which allows a fence at the end of the document.
|
|
50
|
+
*
|
|
51
|
+
* If a fence is found, the codeblock closes normally. Otherwise the remaining
|
|
52
|
+
* buffer becomes content and `close_codeblock_at_eof` (in state.ts) handles
|
|
53
|
+
* the revert-and-wrap dance after process completes.
|
|
54
|
+
*/
|
|
55
|
+
export declare const process_codeblock_forced: (state: MdzStreamParserState) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Process bytes while in codeblock mode.
|
|
58
|
+
* Returns true if processing should continue, false if more input needed.
|
|
59
|
+
*/
|
|
60
|
+
export declare const process_codeblock: (state: MdzStreamParserState) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Check if the buffer at current position has a codeblock closing fence.
|
|
63
|
+
* Returns the position after the fence (including trailing newline) on success,
|
|
64
|
+
* `-1` when definitely not a fence, or `-2` when more input is needed to decide.
|
|
65
|
+
*
|
|
66
|
+
* When `forced` is true, EOF after the fence counts as a valid terminator
|
|
67
|
+
* (matches `mdz_parse`'s handling of code blocks at end of input).
|
|
68
|
+
*/
|
|
69
|
+
export declare const match_codeblock_close: (state: MdzStreamParserState, backtick_count: number, forced?: boolean) => number;
|
|
70
|
+
/**
|
|
71
|
+
* Revert an empty codeblock (closing fence found but no content was emitted).
|
|
72
|
+
* Wraps the opening fence delimiter in a new paragraph and accumulates the
|
|
73
|
+
* closing fence text for normal paragraph processing.
|
|
74
|
+
*/
|
|
75
|
+
export declare const revert_empty_codeblock: (state: MdzStreamParserState, cb: CodeblockState) => boolean;
|
|
76
|
+
//# sourceMappingURL=mdz_stream_parser_block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdz_stream_parser_block.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_block.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAQd,MAAM,8BAA8B,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,oBAAoB,KAAG,SAgDzD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,oBAAoB,EAAE,gBAAc,KAAG,SAqDpE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,oBAAoB,KAAG,SA4DhE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,oBAAoB,EAC3B,OAAO,MAAM,EACb,gBAAgB,MAAM,KACpB,OAAO,GAAG,WAAW,GAAG,SA0C1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAO,oBAAoB,EAC3B,IAAI,cAAc,EAClB,SAAS,MAAM,EACf,eAAe,MAAM,KACnB,IAgBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,oBAAoB,KAAG,IA8CtE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,KAAG,OAsE/D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,oBAAoB,EAC3B,gBAAgB,MAAM,EACtB,SAAQ,OAAe,KACrB,MA+BF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAClC,OAAO,oBAAoB,EAC3B,IAAI,cAAc,KAChB,OA4CF,CAAC"}
|