@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.
Files changed (113) hide show
  1. package/dist/ApiDeclarationList.svelte +2 -1
  2. package/dist/ApiDeclarationList.svelte.d.ts.map +1 -1
  3. package/dist/ApiIndex.svelte +2 -2
  4. package/dist/ApiModule.svelte +3 -3
  5. package/dist/DeclarationDetail.svelte +145 -90
  6. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  7. package/dist/LibraryDetail.svelte +14 -16
  8. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  9. package/dist/LibrarySummary.svelte +12 -12
  10. package/dist/Mdz.svelte +2 -2
  11. package/dist/MdzNodeView.svelte +8 -6
  12. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  13. package/dist/MdzRoot.svelte +30 -0
  14. package/dist/MdzRoot.svelte.d.ts +12 -0
  15. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  16. package/dist/MdzStream.svelte +32 -0
  17. package/dist/MdzStream.svelte.d.ts +12 -0
  18. package/dist/MdzStream.svelte.d.ts.map +1 -0
  19. package/dist/MdzStreamNodeView.svelte +106 -0
  20. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  21. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  22. package/dist/ProjectLinks.svelte +1 -1
  23. package/dist/api_search.svelte.d.ts.map +1 -1
  24. package/dist/api_search.svelte.js +4 -2
  25. package/dist/declaration.svelte.d.ts +11 -0
  26. package/dist/declaration.svelte.d.ts.map +1 -1
  27. package/dist/declaration.svelte.js +15 -3
  28. package/dist/library.svelte.d.ts +9 -70
  29. package/dist/library.svelte.d.ts.map +1 -1
  30. package/dist/library.svelte.js +24 -18
  31. package/dist/library_helpers.d.ts +3 -3
  32. package/dist/library_helpers.js +3 -3
  33. package/dist/mdz.d.ts.map +1 -1
  34. package/dist/mdz.js +37 -29
  35. package/dist/mdz_components.d.ts +37 -23
  36. package/dist/mdz_components.d.ts.map +1 -1
  37. package/dist/mdz_components.js +16 -4
  38. package/dist/mdz_helpers.d.ts +46 -14
  39. package/dist/mdz_helpers.d.ts.map +1 -1
  40. package/dist/mdz_helpers.js +189 -56
  41. package/dist/mdz_lexer.d.ts.map +1 -1
  42. package/dist/mdz_lexer.js +18 -22
  43. package/dist/mdz_opcodes.d.ts +174 -0
  44. package/dist/mdz_opcodes.d.ts.map +1 -0
  45. package/dist/mdz_opcodes.js +14 -0
  46. package/dist/mdz_opcodes_to_nodes.d.ts +20 -0
  47. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  48. package/dist/mdz_opcodes_to_nodes.js +332 -0
  49. package/dist/mdz_stream_parser.d.ts +80 -0
  50. package/dist/mdz_stream_parser.d.ts.map +1 -0
  51. package/dist/mdz_stream_parser.js +354 -0
  52. package/dist/mdz_stream_parser_block.d.ts +76 -0
  53. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  54. package/dist/mdz_stream_parser_block.js +458 -0
  55. package/dist/mdz_stream_parser_inline.d.ts +52 -0
  56. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  57. package/dist/mdz_stream_parser_inline.js +378 -0
  58. package/dist/mdz_stream_parser_link.d.ts +22 -0
  59. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  60. package/dist/mdz_stream_parser_link.js +215 -0
  61. package/dist/mdz_stream_parser_state.d.ts +187 -0
  62. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  63. package/dist/mdz_stream_parser_state.js +398 -0
  64. package/dist/mdz_stream_parser_text.d.ts +14 -0
  65. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  66. package/dist/mdz_stream_parser_text.js +50 -0
  67. package/dist/mdz_stream_parser_url.d.ts +60 -0
  68. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  69. package/dist/mdz_stream_parser_url.js +307 -0
  70. package/dist/mdz_stream_state.svelte.d.ts +44 -0
  71. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  72. package/dist/mdz_stream_state.svelte.js +357 -0
  73. package/dist/mdz_token_parser.d.ts.map +1 -1
  74. package/dist/mdz_token_parser.js +8 -39
  75. package/dist/module.svelte.d.ts +2 -0
  76. package/dist/module.svelte.d.ts.map +1 -1
  77. package/dist/module.svelte.js +3 -1
  78. package/dist/site.svelte.d.ts +13 -0
  79. package/dist/site.svelte.d.ts.map +1 -1
  80. package/dist/site.svelte.js +8 -2
  81. package/dist/tsdoc_mdz.d.ts +2 -2
  82. package/dist/tsdoc_mdz.js +2 -2
  83. package/dist/vite_plugin_pkg_json.d.ts +63 -0
  84. package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
  85. package/dist/vite_plugin_pkg_json.js +126 -0
  86. package/package.json +8 -7
  87. package/src/lib/api_search.svelte.ts +4 -2
  88. package/src/lib/declaration.svelte.ts +18 -3
  89. package/src/lib/library.svelte.ts +37 -20
  90. package/src/lib/library_helpers.ts +3 -3
  91. package/src/lib/mdz.ts +38 -29
  92. package/src/lib/mdz_components.ts +40 -19
  93. package/src/lib/mdz_helpers.ts +199 -56
  94. package/src/lib/mdz_lexer.ts +18 -20
  95. package/src/lib/mdz_opcodes.ts +205 -0
  96. package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
  97. package/src/lib/mdz_stream_parser.ts +415 -0
  98. package/src/lib/mdz_stream_parser_block.ts +532 -0
  99. package/src/lib/mdz_stream_parser_inline.ts +414 -0
  100. package/src/lib/mdz_stream_parser_link.ts +271 -0
  101. package/src/lib/mdz_stream_parser_state.ts +539 -0
  102. package/src/lib/mdz_stream_parser_text.ts +77 -0
  103. package/src/lib/mdz_stream_parser_url.ts +365 -0
  104. package/src/lib/mdz_stream_state.svelte.ts +387 -0
  105. package/src/lib/mdz_token_parser.ts +13 -40
  106. package/src/lib/module.svelte.ts +5 -1
  107. package/src/lib/site.svelte.ts +17 -2
  108. package/src/lib/tsdoc_mdz.ts +2 -2
  109. package/src/lib/vite_plugin_pkg_json.ts +142 -0
  110. package/dist/package_helpers.d.ts +0 -150
  111. package/dist/package_helpers.d.ts.map +0 -1
  112. package/dist/package_helpers.js +0 -179
  113. package/src/lib/package_helpers.ts +0 -186
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Streaming parser state and low-level operations.
3
+ *
4
+ * Free functions take a `MdzStreamParserState` first parameter so handlers can
5
+ * live in separate sibling modules — JS private (`#`) fields can't cross
6
+ * module boundaries.
7
+ *
8
+ * @module
9
+ */
10
+ import type { MdzContainerNodeType, MdzNodeId, MdzOpcode } from './mdz_opcodes.js';
11
+ /**
12
+ * Tri-state result for `try_*` parser handlers.
13
+ * - `'consumed'`: input matched and the parser advanced
14
+ * - `'need_more'`: input is potentially valid but more bytes are required to decide
15
+ * - `'not_match'`: input definitely doesn't match — caller falls through to the next handler
16
+ */
17
+ export type TryResult = 'consumed' | 'need_more' | 'not_match';
18
+ export interface StackEntry {
19
+ id: MdzNodeId;
20
+ node_type: MdzContainerNodeType;
21
+ /** Whether this was opened speculatively (will be reverted if not closed). */
22
+ optimistic: boolean;
23
+ /** The opening delimiter text, used as `replacement_text` on revert. */
24
+ delimiter: string;
25
+ /** Tag name for Element/Component entries, `undefined` for all others. */
26
+ tag_name: string | undefined;
27
+ /** Whether any child content has been emitted inside this container. */
28
+ has_children: boolean;
29
+ /**
30
+ * Whether any non-whitespace text or non-text child has been emitted
31
+ * inside this container. Only tracked for `Paragraph` entries to detect
32
+ * whitespace-only paragraphs that should be dropped at close.
33
+ */
34
+ has_non_whitespace_content: boolean;
35
+ /** Global byte offset of the opening delimiter. */
36
+ start: number;
37
+ }
38
+ export interface CodeblockState {
39
+ id: MdzNodeId;
40
+ backtick_count: number;
41
+ text_id: MdzNodeId | null;
42
+ /** The full opening fence line (e.g. "```ts\n"), used as `replacement_text` on revert. */
43
+ delimiter: string;
44
+ /** Global byte offset of the opening fence. */
45
+ start: number;
46
+ }
47
+ /**
48
+ * Pending auto-link URL/path state for text-first rendering.
49
+ * When set, URL chars flow as visible text. On terminator, a `wrap` opcode
50
+ * retroactively wraps the text node in a Link.
51
+ *
52
+ * For URLs, `confirmed` starts false during speculative prefix matching
53
+ * (chars stream as text while we verify `https://` or `http://`).
54
+ * For paths, `confirmed` starts true (prefix already validated by hold).
55
+ */
56
+ export interface PendingUrl {
57
+ url_text: string;
58
+ start: number;
59
+ link_type: 'external' | 'internal';
60
+ /** Whether the URL/path prefix has been fully confirmed. */
61
+ confirmed: boolean;
62
+ /** Prefix match tracking — only used when `!confirmed`. */
63
+ viable_https: boolean;
64
+ viable_http: boolean;
65
+ }
66
+ /**
67
+ * Mutable state for the streaming parser. One instance per `MdzStreamParser`.
68
+ * Handlers in sibling modules take this as their first parameter — the streaming
69
+ * parser uses free functions, not class methods, so state crosses module
70
+ * boundaries.
71
+ */
72
+ export interface MdzStreamParserState {
73
+ buffer: string;
74
+ pos: number;
75
+ opcodes: Array<MdzOpcode>;
76
+ next_id: MdzNodeId;
77
+ stack: Array<StackEntry>;
78
+ column: number;
79
+ prev_char: number;
80
+ active_text_id: MdzNodeId | null;
81
+ accumulated_text: string;
82
+ accumulated_text_start: number;
83
+ codeblock: CodeblockState | null;
84
+ /** Global byte offset of the start of `buffer`. */
85
+ base_offset: number;
86
+ /** Whether we're inside a heading (newline ends it). */
87
+ in_heading: boolean;
88
+ /** Whether we're inside an optimistic inline Code container. */
89
+ in_code: boolean;
90
+ /** Cached flag: whether a Paragraph is open on the stack. */
91
+ in_paragraph: boolean;
92
+ pending_url: PendingUrl | null;
93
+ /**
94
+ * Stack of text segments for heading ID computation.
95
+ * Each open container inside a heading pushes a new segment.
96
+ * On close: pop and append to parent (children's text is part of heading).
97
+ * On revert: pop, prepend replacement_text, append to parent
98
+ * (document order: delimiter text comes before children's text).
99
+ */
100
+ heading_text_parts: Array<string>;
101
+ /**
102
+ * When true, skip leading newlines at the start of the next processing pass.
103
+ * Set after block element closes (codeblock, heading, HR) when trailing
104
+ * newlines couldn't be absorbed within the same buffer — needed for
105
+ * char-by-char streaming where post-block newlines arrive in later chunks.
106
+ */
107
+ skip_leading_newlines: boolean;
108
+ /**
109
+ * Stack index of the innermost open `Paragraph`, or `-1` when none.
110
+ * Lets `mark_paragraph_non_whitespace` skip the stack walk that runs on
111
+ * every non-whitespace emit. Updated only on Paragraph push/pop (block
112
+ * boundaries — only one of `Paragraph`/`Heading` can be on the stack
113
+ * at a time, so the cache is stable across inline pushes/pops).
114
+ */
115
+ paragraph_stack_idx: number;
116
+ /**
117
+ * Id of the most-recent `text`/`append_text` emission, used by
118
+ * `trim_trailing_newline` to target the right opcode after `take_opcodes()`
119
+ * has drained the queue. `null` after a structural emit (open/close/revert)
120
+ * sealed the prior text run.
121
+ */
122
+ last_text_id: MdzNodeId | null;
123
+ /** Whether the most recent text emission's content ended in `'\n'`. */
124
+ last_text_ended_with_newline: boolean;
125
+ /**
126
+ * Whether the most recent text emission was a `text` opcode (not
127
+ * `append_text`) with content exactly `'\n'` — trimming the trailing \n
128
+ * would leave the text node empty, so `active_text_id` must be cleared
129
+ * to keep subsequent content from merging into a deleted node.
130
+ */
131
+ last_text_was_singleton_newline: boolean;
132
+ }
133
+ export declare const create_state: () => MdzStreamParserState;
134
+ export declare const alloc_id: (state: MdzStreamParserState) => MdzNodeId;
135
+ /**
136
+ * Push a new container frame onto the stack. Fills the boilerplate fields
137
+ * (`has_children`, `has_non_whitespace_content`) so call sites only spell out
138
+ * what varies per container type. Keeping the object literal in one place
139
+ * also gives V8 a single monomorphic creation site for `StackEntry`.
140
+ */
141
+ export declare const push_stack_entry: (state: MdzStreamParserState, id: MdzNodeId, node_type: MdzContainerNodeType, start: number, optimistic?: boolean, delimiter?: string, tag_name?: string) => void;
142
+ /** Global byte offset for a local buffer position. */
143
+ export declare const offset: (state: MdzStreamParserState, pos?: number) => number;
144
+ export declare const emit: (state: MdzStreamParserState, op: MdzOpcode) => void;
145
+ /** Accumulate text, tracking the start offset for the first character. */
146
+ export declare const accumulate_text: (state: MdzStreamParserState, text: string, start_offset: number) => void;
147
+ /**
148
+ * Flush accumulated text as a text or append_text opcode.
149
+ */
150
+ export declare const flush_text: (state: MdzStreamParserState) => void;
151
+ export declare const ensure_paragraph: (state: MdzStreamParserState) => void;
152
+ /**
153
+ * Find the innermost open container of a given type.
154
+ * Returns stack index, or -1 if not found.
155
+ * Does not cross block boundaries (Paragraph, Heading).
156
+ */
157
+ export declare const find_open: (state: MdzStreamParserState, type: MdzContainerNodeType) => number;
158
+ /**
159
+ * Revert all stack entries above the given index.
160
+ */
161
+ export declare const revert_above: (state: MdzStreamParserState, target_idx: number) => void;
162
+ /**
163
+ * Revert all optimistic inline containers in the current block context.
164
+ */
165
+ export declare const revert_all_optimistic: (state: MdzStreamParserState) => void;
166
+ /**
167
+ * Trim a trailing newline from paragraph content.
168
+ * Checks unflushed accumulated text first; otherwise emits a `trim_text`
169
+ * opcode targeting the most recent text/append_text via the tracking fields
170
+ * on `state` (which survive `take_opcodes()` drains). The opcode stream
171
+ * stays append-only — no retroactive opcode mutation.
172
+ */
173
+ export declare const trim_trailing_newline: (state: MdzStreamParserState) => void;
174
+ export declare const close_paragraph: (state: MdzStreamParserState) => void;
175
+ export declare const close_heading: (state: MdzStreamParserState) => void;
176
+ /**
177
+ * Close an unclosed codeblock at EOF by reverting it to a paragraph wrapper.
178
+ *
179
+ * Unlike `revert_empty_codeblock` (which pushes the wrapper onto the stack
180
+ * because parsing continues), this is terminal — called only from `finish()`
181
+ * after no further emits happen — so it skips the `push_stack_entry` /
182
+ * `in_paragraph` / `paragraph_stack_idx` bookkeeping. The wrapper exists only
183
+ * in the opcode stream; the parser state's stack stays untouched.
184
+ */
185
+ export declare const close_codeblock_at_eof: (state: MdzStreamParserState) => void;
186
+ export declare const handle_paragraph_break: (state: MdzStreamParserState) => void;
187
+ //# sourceMappingURL=mdz_stream_parser_state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser_state.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Streaming parser state and low-level operations.
3
+ *
4
+ * Free functions take a `MdzStreamParserState` first parameter so handlers can
5
+ * live in separate sibling modules — JS private (`#`) fields can't cross
6
+ * module boundaries.
7
+ *
8
+ * @module
9
+ */
10
+ import { NEWLINE, mdz_heading_id_from_text } from './mdz_helpers.js';
11
+ export const create_state = () => ({
12
+ buffer: '',
13
+ pos: 0,
14
+ opcodes: [],
15
+ next_id: 0,
16
+ stack: [],
17
+ column: 0,
18
+ prev_char: -1,
19
+ active_text_id: null,
20
+ accumulated_text: '',
21
+ accumulated_text_start: 0,
22
+ codeblock: null,
23
+ base_offset: 0,
24
+ in_heading: false,
25
+ in_code: false,
26
+ in_paragraph: false,
27
+ pending_url: null,
28
+ heading_text_parts: [],
29
+ // matches mdz_parse: leading newlines before any block are skipped, not
30
+ // preserved as paragraph content
31
+ skip_leading_newlines: true,
32
+ paragraph_stack_idx: -1,
33
+ last_text_id: null,
34
+ last_text_ended_with_newline: false,
35
+ last_text_was_singleton_newline: false,
36
+ });
37
+ export const alloc_id = (state) => state.next_id++;
38
+ /**
39
+ * Whether `text` contains any non-whitespace character. Hot path — called from
40
+ * `emit` on every text emission. Char-loop checking common whitespace (space,
41
+ * tab, newline, carriage return, form feed, vertical tab) avoids the regex
42
+ * allocation that `/\S/.test()` incurs per call.
43
+ *
44
+ * Deliberately ASCII-only. `/\S/` and `String.prototype.trim()` (which
45
+ * `mdz_parse` uses to detect whitespace-only paragraphs) recognize Unicode
46
+ * whitespace too (U+00A0 NBSP, U+2028/U+2029, U+3000, etc.). A paragraph
47
+ * containing only Unicode whitespace would be kept by the streaming parser
48
+ * but dropped by `mdz_parse` — no fixture currently hits this.
49
+ */
50
+ const has_non_whitespace = (text) => {
51
+ for (let i = 0; i < text.length; i++) {
52
+ const c = text.charCodeAt(i);
53
+ // 0x09=tab, 0x0a=LF, 0x0b=VT, 0x0c=FF, 0x0d=CR, 0x20=space
54
+ if (c !== 0x20 && (c < 0x09 || c > 0x0d))
55
+ return true;
56
+ }
57
+ return false;
58
+ };
59
+ /**
60
+ * Push a new container frame onto the stack. Fills the boilerplate fields
61
+ * (`has_children`, `has_non_whitespace_content`) so call sites only spell out
62
+ * what varies per container type. Keeping the object literal in one place
63
+ * also gives V8 a single monomorphic creation site for `StackEntry`.
64
+ */
65
+ export const push_stack_entry = (state, id, node_type, start, optimistic = false, delimiter = '', tag_name) => {
66
+ state.stack.push({
67
+ id,
68
+ node_type,
69
+ optimistic,
70
+ delimiter,
71
+ tag_name,
72
+ has_children: false,
73
+ has_non_whitespace_content: false,
74
+ start,
75
+ });
76
+ if (node_type === 'Paragraph') {
77
+ state.paragraph_stack_idx = state.stack.length - 1;
78
+ }
79
+ };
80
+ /**
81
+ * Mark the innermost open `Paragraph` as having non-whitespace content,
82
+ * via the `paragraph_stack_idx` cache (O(1) instead of an O(stack) walk).
83
+ *
84
+ * No-op when no Paragraph is open — that includes the Heading case, since
85
+ * block boundaries are mutually exclusive (a Heading on stack means
86
+ * `paragraph_stack_idx === -1`).
87
+ */
88
+ const mark_paragraph_non_whitespace = (state) => {
89
+ if (state.paragraph_stack_idx === -1)
90
+ return;
91
+ state.stack[state.paragraph_stack_idx].has_non_whitespace_content = true;
92
+ };
93
+ /** Global byte offset for a local buffer position. */
94
+ export const offset = (state, pos = state.pos) => state.base_offset + pos;
95
+ export const emit = (state, op) => {
96
+ state.opcodes.push(op);
97
+ // mark the innermost container as having children
98
+ if (op.type === 'text' || op.type === 'append_text' || op.type === 'void') {
99
+ const top = state.stack[state.stack.length - 1];
100
+ if (top) {
101
+ top.has_children = true;
102
+ // propagate non-whitespace marker to the nearest Paragraph so
103
+ // whitespace-only paragraphs can be discarded at close
104
+ if (op.type === 'void' || has_non_whitespace(op.content)) {
105
+ mark_paragraph_non_whitespace(state);
106
+ }
107
+ }
108
+ }
109
+ else if (op.type === 'open') {
110
+ // opening a child container counts as content for the parent
111
+ const parent = state.stack[state.stack.length - 1];
112
+ if (parent) {
113
+ parent.has_children = true;
114
+ mark_paragraph_non_whitespace(state);
115
+ }
116
+ }
117
+ else if (op.type === 'wrap') {
118
+ // retroactive Link wrap of a text node — the resulting Link is non-whitespace
119
+ mark_paragraph_non_whitespace(state);
120
+ }
121
+ // track the most recent text run so `trim_trailing_newline` can target it
122
+ // after `take_opcodes()` has drained the queue
123
+ if (op.type === 'text') {
124
+ state.last_text_id = op.id;
125
+ state.last_text_ended_with_newline = op.content.endsWith('\n');
126
+ state.last_text_was_singleton_newline = op.content === '\n';
127
+ }
128
+ else if (op.type === 'append_text') {
129
+ state.last_text_id = op.id;
130
+ state.last_text_ended_with_newline = op.content.endsWith('\n');
131
+ // append_text builds on a prior text node; the underlying node can't be
132
+ // singleton even if this appended chunk is just '\n'
133
+ state.last_text_was_singleton_newline = false;
134
+ }
135
+ else if (op.type === 'open' || op.type === 'close' || op.type === 'revert') {
136
+ // structural opcode seals the prior text run
137
+ state.last_text_id = null;
138
+ state.last_text_ended_with_newline = false;
139
+ state.last_text_was_singleton_newline = false;
140
+ }
141
+ // track text content for heading ID computation
142
+ if (state.in_heading && state.heading_text_parts.length > 0) {
143
+ if (op.type === 'text' || op.type === 'append_text') {
144
+ state.heading_text_parts[state.heading_text_parts.length - 1] += op.content;
145
+ }
146
+ else if (op.type === 'open') {
147
+ // child container: push a new segment for its text content
148
+ state.heading_text_parts.push('');
149
+ }
150
+ else if (op.type === 'close') {
151
+ // normal close: pop child's text and merge into parent
152
+ if (state.heading_text_parts.length > 1) {
153
+ const child_text = state.heading_text_parts.pop();
154
+ state.heading_text_parts[state.heading_text_parts.length - 1] += child_text;
155
+ }
156
+ }
157
+ else if (op.type === 'revert') {
158
+ // revert: pop child's text, prepend replacement (document order), merge into parent
159
+ if (state.heading_text_parts.length > 1) {
160
+ const child_text = state.heading_text_parts.pop();
161
+ state.heading_text_parts[state.heading_text_parts.length - 1] +=
162
+ (op.replacement_text || '') + child_text;
163
+ }
164
+ }
165
+ }
166
+ };
167
+ /** Accumulate text, tracking the start offset for the first character. */
168
+ export const accumulate_text = (state, text, start_offset) => {
169
+ if (state.accumulated_text.length === 0) {
170
+ state.accumulated_text_start = start_offset;
171
+ }
172
+ state.accumulated_text += text;
173
+ };
174
+ /**
175
+ * Flush accumulated text as a text or append_text opcode.
176
+ */
177
+ export const flush_text = (state) => {
178
+ if (state.accumulated_text.length === 0)
179
+ return;
180
+ if (state.active_text_id !== null) {
181
+ emit(state, {
182
+ type: 'append_text',
183
+ id: state.active_text_id,
184
+ content: state.accumulated_text,
185
+ });
186
+ }
187
+ else {
188
+ ensure_paragraph(state);
189
+ const id = alloc_id(state);
190
+ const start = state.accumulated_text_start;
191
+ emit(state, {
192
+ type: 'text',
193
+ id,
194
+ content: state.accumulated_text,
195
+ text_type: 'Text',
196
+ start,
197
+ end: start + state.accumulated_text.length,
198
+ });
199
+ state.active_text_id = id;
200
+ }
201
+ state.accumulated_text = '';
202
+ };
203
+ export const ensure_paragraph = (state) => {
204
+ if (state.in_heading || state.in_paragraph)
205
+ return;
206
+ const id = alloc_id(state);
207
+ const start = offset(state);
208
+ emit(state, { type: 'open', id, node_type: 'Paragraph', start });
209
+ push_stack_entry(state, id, 'Paragraph', start);
210
+ state.in_paragraph = true;
211
+ };
212
+ /**
213
+ * Find the innermost open container of a given type.
214
+ * Returns stack index, or -1 if not found.
215
+ * Does not cross block boundaries (Paragraph, Heading).
216
+ */
217
+ export const find_open = (state, type) => {
218
+ for (let i = state.stack.length - 1; i >= 0; i--) {
219
+ const entry = state.stack[i];
220
+ if (entry.node_type === type)
221
+ return i;
222
+ // don't cross block boundaries
223
+ if (entry.node_type === 'Paragraph' || entry.node_type === 'Heading')
224
+ return -1;
225
+ }
226
+ return -1;
227
+ };
228
+ /**
229
+ * Revert all stack entries above the given index.
230
+ */
231
+ export const revert_above = (state, target_idx) => {
232
+ while (state.stack.length - 1 > target_idx) {
233
+ const entry = state.stack.pop();
234
+ if (entry.node_type === 'Code')
235
+ state.in_code = false;
236
+ if (entry.optimistic) {
237
+ emit(state, {
238
+ type: 'revert',
239
+ id: entry.id,
240
+ replacement_text: entry.delimiter,
241
+ start: entry.start,
242
+ });
243
+ }
244
+ else {
245
+ emit(state, { type: 'close', id: entry.id, end: offset(state) });
246
+ }
247
+ }
248
+ state.active_text_id = null;
249
+ };
250
+ /**
251
+ * Revert all optimistic inline containers in the current block context.
252
+ */
253
+ export const revert_all_optimistic = (state) => {
254
+ while (state.stack.length > 0) {
255
+ const top = state.stack[state.stack.length - 1];
256
+ if (top.node_type === 'Paragraph' || top.node_type === 'Heading')
257
+ break;
258
+ state.stack.pop();
259
+ if (top.node_type === 'Code')
260
+ state.in_code = false;
261
+ if (top.optimistic) {
262
+ emit(state, {
263
+ type: 'revert',
264
+ id: top.id,
265
+ replacement_text: top.delimiter,
266
+ start: top.start,
267
+ });
268
+ }
269
+ else {
270
+ emit(state, { type: 'close', id: top.id, end: offset(state) });
271
+ }
272
+ }
273
+ state.active_text_id = null;
274
+ };
275
+ /**
276
+ * Trim a trailing newline from paragraph content.
277
+ * Checks unflushed accumulated text first; otherwise emits a `trim_text`
278
+ * opcode targeting the most recent text/append_text via the tracking fields
279
+ * on `state` (which survive `take_opcodes()` drains). The opcode stream
280
+ * stays append-only — no retroactive opcode mutation.
281
+ */
282
+ export const trim_trailing_newline = (state) => {
283
+ if (state.accumulated_text.endsWith('\n')) {
284
+ state.accumulated_text = state.accumulated_text.slice(0, -1);
285
+ return;
286
+ }
287
+ if (state.last_text_id === null || !state.last_text_ended_with_newline)
288
+ return;
289
+ // emit trim opcode; consumer adjusts content and removes empty nodes
290
+ emit(state, { type: 'trim_text', id: state.last_text_id, count: 1 });
291
+ // if this trim empties a singleton text node, clear active_text_id so
292
+ // subsequent content doesn't merge into a deleted node via append_text
293
+ if (state.last_text_was_singleton_newline) {
294
+ state.active_text_id = null;
295
+ }
296
+ // after trim, the prior text no longer ends in \n — guard against re-trim
297
+ state.last_text_ended_with_newline = false;
298
+ state.last_text_was_singleton_newline = false;
299
+ };
300
+ export const close_paragraph = (state) => {
301
+ // find and close the paragraph on the stack
302
+ for (let i = state.stack.length - 1; i >= 0; i--) {
303
+ if (state.stack[i].node_type === 'Paragraph') {
304
+ // trim trailing newline from paragraph content
305
+ trim_trailing_newline(state);
306
+ // revert everything above the paragraph
307
+ while (state.stack.length - 1 > i) {
308
+ const entry = state.stack.pop();
309
+ if (entry.node_type === 'Code')
310
+ state.in_code = false;
311
+ emit(state, {
312
+ type: 'revert',
313
+ id: entry.id,
314
+ replacement_text: entry.delimiter,
315
+ start: entry.start,
316
+ });
317
+ }
318
+ const entry = state.stack.pop();
319
+ // drop whitespace-only paragraphs to match `mdz_parse`'s output;
320
+ // consumers (`mdz_opcodes_to_nodes`, `MdzStreamState`) honor `discard`
321
+ // by removing the node and its descendants from the tree
322
+ const discard = !entry.has_non_whitespace_content;
323
+ emit(state, discard
324
+ ? { type: 'close', id: entry.id, end: offset(state), discard: true }
325
+ : { type: 'close', id: entry.id, end: offset(state) });
326
+ state.active_text_id = null;
327
+ state.in_paragraph = false;
328
+ state.paragraph_stack_idx = -1;
329
+ return;
330
+ }
331
+ }
332
+ };
333
+ export const close_heading = (state) => {
334
+ // revert optimistic containers inside the heading (before clearing in_heading,
335
+ // so revert replacement text is captured in heading_text_parts)
336
+ revert_all_optimistic(state);
337
+ // compute heading ID from accumulated text before clearing heading state
338
+ const heading_text = state.heading_text_parts.join('');
339
+ state.heading_text_parts = [];
340
+ state.in_heading = false;
341
+ const heading_id = mdz_heading_id_from_text(heading_text);
342
+ const end = offset(state);
343
+ // find and close the heading
344
+ for (let i = state.stack.length - 1; i >= 0; i--) {
345
+ if (state.stack[i].node_type === 'Heading') {
346
+ const entry = state.stack[i];
347
+ state.stack.splice(i, 1);
348
+ emit(state, { type: 'close', id: entry.id, end, heading_id });
349
+ state.active_text_id = null;
350
+ return;
351
+ }
352
+ }
353
+ };
354
+ /**
355
+ * Close an unclosed codeblock at EOF by reverting it to a paragraph wrapper.
356
+ *
357
+ * Unlike `revert_empty_codeblock` (which pushes the wrapper onto the stack
358
+ * because parsing continues), this is terminal — called only from `finish()`
359
+ * after no further emits happen — so it skips the `push_stack_entry` /
360
+ * `in_paragraph` / `paragraph_stack_idx` bookkeeping. The wrapper exists only
361
+ * in the opcode stream; the parser state's stack stays untouched.
362
+ */
363
+ export const close_codeblock_at_eof = (state) => {
364
+ if (!state.codeblock)
365
+ return;
366
+ const cb = state.codeblock;
367
+ // trim trailing newline from codeblock content (mirrors close_paragraph behavior)
368
+ trim_trailing_newline(state);
369
+ const wrap_id = alloc_id(state);
370
+ emit(state, {
371
+ type: 'revert',
372
+ id: cb.id,
373
+ replacement_text: cb.delimiter,
374
+ start: cb.start,
375
+ wrap_node_type: 'Paragraph',
376
+ wrap_id,
377
+ });
378
+ emit(state, { type: 'close', id: wrap_id, end: offset(state) });
379
+ state.codeblock = null;
380
+ };
381
+ export const handle_paragraph_break = (state) => {
382
+ flush_text(state);
383
+ // revert all unclosed optimistic inline containers
384
+ revert_all_optimistic(state);
385
+ close_paragraph(state);
386
+ // skip all consecutive newlines
387
+ while (state.pos < state.buffer.length && state.buffer.charCodeAt(state.pos) === NEWLINE) {
388
+ state.pos++;
389
+ }
390
+ // if we ran out of buffer while skipping, carry the absorb across chunks —
391
+ // otherwise a `\n\n\n\n` split mid-run would leak a leading `\n` into the
392
+ // next paragraph's text. Mirrors the heading-newline absorb in `process_inline`.
393
+ if (state.pos >= state.buffer.length) {
394
+ state.skip_leading_newlines = true;
395
+ }
396
+ state.column = 0;
397
+ state.prev_char = NEWLINE;
398
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Plain-text run consumption for the streaming mdz parser.
3
+ *
4
+ * @module
5
+ */
6
+ import { type MdzStreamParserState } from './mdz_stream_parser_state.js';
7
+ /**
8
+ * Consume a run of plain text characters. Scans ahead to the next
9
+ * structurally interesting character and accumulates the whole run
10
+ * as a single slice, avoiding per-character string concatenation
11
+ * and dispatch overhead.
12
+ */
13
+ export declare const consume_text_run: (state: MdzStreamParserState) => void;
14
+ //# sourceMappingURL=mdz_stream_parser_text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser_text.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,OAAO,EACN,KAAK,oBAAoB,EAIzB,MAAM,8BAA8B,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,oBAAoB,KAAG,IAsC9D,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Plain-text run consumption for the streaming mdz parser.
3
+ *
4
+ * @module
5
+ */
6
+ import { ASTERISK, BACKTICK, HTTPS_PREFIX_LENGTH, H_LOWER, H_UPPER, LEFT_ANGLE, LEFT_BRACKET, NEWLINE, PERIOD, RIGHT_BRACKET, SLASH, SPACE, TAB, TILDE, UNDERSCORE, is_word_char, match_url_prefix_case_insensitive, } from './mdz_helpers.js';
7
+ import { accumulate_text, ensure_paragraph, offset, } from './mdz_stream_parser_state.js';
8
+ /**
9
+ * Consume a run of plain text characters. Scans ahead to the next
10
+ * structurally interesting character and accumulates the whole run
11
+ * as a single slice, avoiding per-character string concatenation
12
+ * and dispatch overhead.
13
+ */
14
+ export const consume_text_run = (state) => {
15
+ ensure_paragraph(state);
16
+ const start = state.pos;
17
+ state.pos++;
18
+ while (state.pos < state.buffer.length) {
19
+ const c = state.buffer.charCodeAt(state.pos);
20
+ if (c === BACKTICK ||
21
+ c === ASTERISK ||
22
+ c === UNDERSCORE ||
23
+ c === TILDE ||
24
+ c === LEFT_BRACKET ||
25
+ c === RIGHT_BRACKET ||
26
+ c === LEFT_ANGLE ||
27
+ c === NEWLINE ||
28
+ // URL/path: only break when actually at a URL or path start.
29
+ // Most h/H/./slash in prose are not URLs/paths — continuing the scan
30
+ // avoids the full dispatch cycle through process_loop → process_inline.
31
+ // For `h`/`H` at a word boundary, also break when there isn't enough
32
+ // buffer left to confirm `https://` / `http://` — process_inline's
33
+ // speculator then carries the prefix match across chunk boundaries.
34
+ // Scheme matching is case-insensitive (RFC 3986).
35
+ ((c === H_LOWER || c === H_UPPER) &&
36
+ (match_url_prefix_case_insensitive(state.buffer, state.pos) > 0 ||
37
+ (state.buffer.length - state.pos < HTTPS_PREFIX_LENGTH &&
38
+ !is_word_char(state.buffer.charCodeAt(state.pos - 1))))) ||
39
+ ((c === SLASH || c === PERIOD) &&
40
+ (state.buffer.charCodeAt(state.pos - 1) === SPACE ||
41
+ state.buffer.charCodeAt(state.pos - 1) === NEWLINE ||
42
+ state.buffer.charCodeAt(state.pos - 1) === TAB))) {
43
+ break;
44
+ }
45
+ state.pos++;
46
+ }
47
+ accumulate_text(state, state.buffer.slice(start, state.pos), offset(state, start));
48
+ state.prev_char = state.buffer.charCodeAt(state.pos - 1);
49
+ state.column += state.pos - start;
50
+ };