@ai-matrx/content-ir 0.11.7 → 0.13.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/source.js ADDED
@@ -0,0 +1,1653 @@
1
+ // source/offsets.ts
2
+ function isHigh(code) {
3
+ return code >= 55296 && code <= 56319;
4
+ }
5
+ function isLow(code) {
6
+ return code >= 56320 && code <= 57343;
7
+ }
8
+ function splitsSurrogatePair(text, offset) {
9
+ if (offset <= 0 || offset >= text.length) return false;
10
+ return isHigh(text.charCodeAt(offset - 1)) && isLow(text.charCodeAt(offset));
11
+ }
12
+ function buildCodePointIndex(text) {
13
+ const pairStarts = [];
14
+ for (let i = 0; i < text.length - 1; i++) {
15
+ if (isHigh(text.charCodeAt(i)) && isLow(text.charCodeAt(i + 1))) {
16
+ pairStarts.push(i);
17
+ i++;
18
+ }
19
+ }
20
+ const countBelow = (limit) => {
21
+ let lo = 0;
22
+ let hi = pairStarts.length;
23
+ while (lo < hi) {
24
+ const mid = lo + hi >>> 1;
25
+ if (pairStarts[mid] < limit) lo = mid + 1;
26
+ else hi = mid;
27
+ }
28
+ return lo;
29
+ };
30
+ const toCodePoint = (utf16) => {
31
+ return utf16 - countBelow(utf16 - 1);
32
+ };
33
+ const toUtf16 = (codePoint) => {
34
+ let lo = 0;
35
+ let hi = pairStarts.length;
36
+ while (lo < hi) {
37
+ const mid = lo + hi >>> 1;
38
+ const start = pairStarts[mid];
39
+ if (start - mid < codePoint) lo = mid + 1;
40
+ else hi = mid;
41
+ }
42
+ return codePoint + lo;
43
+ };
44
+ return {
45
+ toCodePoint,
46
+ toUtf16,
47
+ lengthCp: text.length - pairStarts.length
48
+ };
49
+ }
50
+ function toCodePointOffset(text, utf16) {
51
+ return buildCodePointIndex(text).toCodePoint(utf16);
52
+ }
53
+ function toUtf16Offset(text, codePoint) {
54
+ return buildCodePointIndex(text).toUtf16(codePoint);
55
+ }
56
+
57
+ // source/constants.ts
58
+ var XML_TAG_BLOCK_NAMES = [
59
+ "thinking",
60
+ "think",
61
+ "reasoning",
62
+ "info",
63
+ "task",
64
+ "database",
65
+ "private",
66
+ "plan",
67
+ "event",
68
+ "tool",
69
+ "questionnaire",
70
+ "flashcards",
71
+ "cooking_recipe",
72
+ "timeline",
73
+ "progress_tracker",
74
+ "troubleshooting",
75
+ "resources",
76
+ "research"
77
+ ];
78
+ var THINKING_FAMILY = ["thinking", "think", "reasoning"];
79
+ var ATTRIBUTE_XML_NAMES = [
80
+ "decision",
81
+ "artifact",
82
+ "editor_error",
83
+ "editor_code_snippet",
84
+ "audiocite"
85
+ ];
86
+ var KNOWN_XML_TAG_NAMES = /* @__PURE__ */ new Set([
87
+ ...XML_TAG_BLOCK_NAMES,
88
+ ...ATTRIBUTE_XML_NAMES
89
+ ]);
90
+ var ALLOWED_RAW_HTML_TAGS = /* @__PURE__ */ new Set([
91
+ "img",
92
+ "a",
93
+ "br",
94
+ "hr",
95
+ "span",
96
+ "p",
97
+ "strong",
98
+ "b",
99
+ "em",
100
+ "i",
101
+ "u",
102
+ "s",
103
+ "del",
104
+ "ins",
105
+ "sup",
106
+ "sub",
107
+ "mark",
108
+ "kbd",
109
+ "abbr",
110
+ "small",
111
+ "code",
112
+ "pre",
113
+ "ul",
114
+ "ol",
115
+ "li",
116
+ "blockquote",
117
+ "h1",
118
+ "h2",
119
+ "h3",
120
+ "h4",
121
+ "h5",
122
+ "h6",
123
+ "table",
124
+ "thead",
125
+ "tbody",
126
+ "tfoot",
127
+ "tr",
128
+ "th",
129
+ "td",
130
+ "caption",
131
+ "colgroup",
132
+ "col",
133
+ "figure",
134
+ "figcaption"
135
+ ]);
136
+ var HTML_BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
137
+ "p",
138
+ "hr",
139
+ "ul",
140
+ "ol",
141
+ "li",
142
+ "blockquote",
143
+ "h1",
144
+ "h2",
145
+ "h3",
146
+ "h4",
147
+ "h5",
148
+ "h6",
149
+ "table",
150
+ "thead",
151
+ "tbody",
152
+ "tfoot",
153
+ "tr",
154
+ "th",
155
+ "td",
156
+ "caption",
157
+ "colgroup",
158
+ "col",
159
+ "figure",
160
+ "figcaption"
161
+ ]);
162
+ var SPECIAL_CODE_LANGUAGES = /* @__PURE__ */ new Set([
163
+ "transcript",
164
+ "tasks",
165
+ "structured_info",
166
+ "questionnaire",
167
+ "flashcards",
168
+ "cooking_recipe",
169
+ "mermaid",
170
+ "svg",
171
+ "chart",
172
+ "map",
173
+ "stats",
174
+ "diff",
175
+ "html",
176
+ "react",
177
+ "matrx"
178
+ ]);
179
+ var CODE_LANGUAGE_ALIASES = {
180
+ mmd: "mermaid",
181
+ jsx: "react",
182
+ tsx: "react"
183
+ };
184
+ var TYPED_JSON_ROOT_KEYS = {
185
+ quiz_title: "quiz",
186
+ presentation: "presentation",
187
+ decision_tree: "decision_tree",
188
+ comparison: "comparison_table",
189
+ diagram: "diagram",
190
+ math_problem: "math_problem",
191
+ item_presentation: "item_presentation",
192
+ name: "schema_proposal"
193
+ };
194
+ var TREE_GLYPHS = /[├└│┌┐┘┬┴┤┼─]/;
195
+ var PINNED_ANCHOR_RE = /^<!--@a:([A-Za-z0-9_-]+)-->$/;
196
+ var STRICT_VARIABLE_RE = /^\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}$/;
197
+ var CITE_RE = /^<matrxcite\s+n="(\d+)"\s*\/>/;
198
+ function normalizeFenceLanguage(language) {
199
+ const lower = language.toLowerCase();
200
+ return CODE_LANGUAGE_ALIASES[lower] ?? lower;
201
+ }
202
+
203
+ // source/fence-nesting.ts
204
+ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
205
+ "markdown",
206
+ "md",
207
+ "mdx"
208
+ ]);
209
+ function fenceNestsInnerFences(language) {
210
+ return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
211
+ }
212
+ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
213
+ let ticks = 0;
214
+ while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
215
+ if (ticks < 3) return "content";
216
+ const info = trimmed.slice(ticks).trim();
217
+ if (info === "") {
218
+ if (ticks < openTicks) return "content";
219
+ if (nests && nestedDepth > 0) return "close-nested";
220
+ return "close-outer";
221
+ }
222
+ if (nests && ticks >= openTicks && !info.includes("`")) return "open-nested";
223
+ return "content";
224
+ }
225
+
226
+ // source/math.ts
227
+ var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
228
+ var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
229
+ function isSingleDollarMath(content, after) {
230
+ if (!content || content.length > 400) return false;
231
+ if (/^\s/.test(content) || /\s$/.test(content)) return false;
232
+ if (after !== void 0 && /[0-9]/.test(after)) return false;
233
+ if (LONE_VARIABLE.test(content)) return true;
234
+ if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
235
+ return TEX_SIGNAL.test(content);
236
+ }
237
+ function singleDollarMathEnd(text, open, limit = text.length) {
238
+ const bound = Math.min(limit, open + 403);
239
+ let close = -1;
240
+ for (let k = open + 1; k < bound; k += 1) {
241
+ const c = text[k];
242
+ if (c === "\n") break;
243
+ if (c === "\\") {
244
+ k += 1;
245
+ continue;
246
+ }
247
+ if (c === "$") {
248
+ close = k;
249
+ break;
250
+ }
251
+ }
252
+ if (close === -1 || text[close + 1] === "$") return -1;
253
+ const content = text.slice(open + 1, close);
254
+ return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
255
+ }
256
+
257
+ // source/math-pairs.ts
258
+ var MAX_MATH_SPAN = 600;
259
+ var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
260
+ var LATEX_COMMAND = /\\[a-zA-Z]/;
261
+ var PROSE_WORD = /[A-Za-z]{3,}/g;
262
+ var PROSE_WORD_LIMIT = 6;
263
+ function looksLikeDisplayMath(inner) {
264
+ const s = inner.trim();
265
+ if (!s) return false;
266
+ if (STRUCTURAL_MARKDOWN.test(s)) return false;
267
+ if (s.length > MAX_MATH_SPAN) return false;
268
+ if (LATEX_COMMAND.test(s)) {
269
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
270
+ }
271
+ if (/\n[ \t]*\n/.test(s)) return false;
272
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
273
+ }
274
+ function codeRanges(text) {
275
+ const ranges = [];
276
+ for (const re of [/```[\s\S]*?(?:```|$)/g, /~~~[\s\S]*?(?:~~~|$)/g, /`[^`\n]*`/g]) {
277
+ let m;
278
+ while ((m = re.exec(text)) !== null) {
279
+ ranges.push([m.index, m.index + m[0].length]);
280
+ if (m[0].length === 0) re.lastIndex++;
281
+ }
282
+ }
283
+ return ranges.sort((a, b) => a[0] - b[0]);
284
+ }
285
+ function pairDisplayMath(text) {
286
+ const pairs = /* @__PURE__ */ new Map();
287
+ if (!text.includes("$$")) return pairs;
288
+ const ranges = codeRanges(text);
289
+ const tokens = [];
290
+ let r = 0;
291
+ let maxEnd = -1;
292
+ for (let i = text.indexOf("$$"); i !== -1 && i < text.length - 1; i = text.indexOf("$$", i)) {
293
+ while (r < ranges.length && ranges[r][0] <= i) {
294
+ maxEnd = Math.max(maxEnd, ranges[r][1]);
295
+ r++;
296
+ }
297
+ if (maxEnd <= i) tokens.push(i);
298
+ i += 2;
299
+ }
300
+ let j = 0;
301
+ while (j < tokens.length) {
302
+ const open = tokens[j];
303
+ const close = tokens[j + 1];
304
+ if (close === void 0) break;
305
+ if (looksLikeDisplayMath(text.slice(open + 2, close))) {
306
+ pairs.set(open, close);
307
+ j += 2;
308
+ continue;
309
+ }
310
+ j += 1;
311
+ }
312
+ return pairs;
313
+ }
314
+
315
+ // source/xml-tag.ts
316
+ var XML_NAME_START = /[A-Za-z_]/;
317
+ var XML_NAME_CHARACTER = /[\w.:-]/;
318
+ var WHITESPACE = /\s/;
319
+ function readXmlTag(content, start, find = (needle, from) => content.indexOf(needle, from)) {
320
+ let cursor = start + 1;
321
+ const isClosing = content[cursor] === "/";
322
+ if (isClosing) cursor++;
323
+ if (!XML_NAME_START.test(content[cursor] ?? "")) return null;
324
+ const nameStart = cursor;
325
+ while (XML_NAME_CHARACTER.test(content[cursor] ?? "")) cursor++;
326
+ const tagName = content.slice(nameStart, cursor);
327
+ const attributes = [];
328
+ if (isClosing) {
329
+ while (WHITESPACE.test(content[cursor] ?? "")) cursor++;
330
+ if (content[cursor] !== ">") return null;
331
+ return {
332
+ tagName,
333
+ raw: content.slice(start, cursor + 1),
334
+ isClosing,
335
+ isSelfClosing: false,
336
+ attributes
337
+ };
338
+ }
339
+ while (cursor < content.length) {
340
+ while (WHITESPACE.test(content[cursor] ?? "")) cursor++;
341
+ if (content[cursor] === ">")
342
+ return {
343
+ tagName,
344
+ raw: content.slice(start, cursor + 1),
345
+ isClosing,
346
+ isSelfClosing: false,
347
+ attributes
348
+ };
349
+ if (content[cursor] === "/") {
350
+ cursor++;
351
+ while (WHITESPACE.test(content[cursor] ?? "")) cursor++;
352
+ if (content[cursor] !== ">") return null;
353
+ return {
354
+ tagName,
355
+ raw: content.slice(start, cursor + 1),
356
+ isClosing,
357
+ isSelfClosing: true,
358
+ attributes
359
+ };
360
+ }
361
+ if (!XML_NAME_START.test(content[cursor] ?? "")) return null;
362
+ const attributeStart = cursor;
363
+ while (XML_NAME_CHARACTER.test(content[cursor] ?? "")) cursor++;
364
+ const name = content.slice(attributeStart, cursor);
365
+ while (WHITESPACE.test(content[cursor] ?? "")) cursor++;
366
+ if (content[cursor] !== "=") return null;
367
+ cursor++;
368
+ while (WHITESPACE.test(content[cursor] ?? "")) cursor++;
369
+ const quote = content[cursor];
370
+ if (quote !== '"' && quote !== "'") return null;
371
+ const valueStart = ++cursor;
372
+ const valueEnd = find(quote, valueStart);
373
+ if (valueEnd === -1) return null;
374
+ cursor = valueEnd;
375
+ attributes.push({ name, value: content.slice(valueStart, cursor) });
376
+ cursor++;
377
+ }
378
+ return null;
379
+ }
380
+ function isEscapedBacktick(line, offset) {
381
+ let slashes = 0;
382
+ for (let i = offset - 1; line[i] === "\\"; i--) slashes++;
383
+ return slashes % 2 === 1;
384
+ }
385
+ var XmlContainerTracker = class {
386
+ constructor(rootTag) {
387
+ this.rootTag = rootTag;
388
+ }
389
+ rootTag;
390
+ depth = 0;
391
+ inComment = false;
392
+ inCdata = false;
393
+ inlineTicks = 0;
394
+ fenceMarker = null;
395
+ consumeLine(line, startOffset = 0) {
396
+ const fence = /^[ \t]*(`{3,}|~{3,})(.*)$/.exec(line);
397
+ if (!this.inComment && !this.inCdata && this.inlineTicks === 0 && fence) {
398
+ const marker = fence[1];
399
+ const char = marker[0];
400
+ const suffix = fence[2];
401
+ if (marker.length >= 3) {
402
+ if (!this.fenceMarker) {
403
+ this.fenceMarker = { char, ticks: marker.length };
404
+ return null;
405
+ }
406
+ if (char === this.fenceMarker.char && marker.length >= this.fenceMarker.ticks && suffix.trim() === "") {
407
+ this.fenceMarker = null;
408
+ }
409
+ return null;
410
+ }
411
+ }
412
+ if (this.fenceMarker) return null;
413
+ for (let i = startOffset; i < line.length; i++) {
414
+ if (this.inlineTicks > 0) {
415
+ if (line[i] === "`") {
416
+ let end = i;
417
+ while (line[end] === "`") end++;
418
+ if (end - i === this.inlineTicks) this.inlineTicks = 0;
419
+ i = end - 1;
420
+ }
421
+ continue;
422
+ }
423
+ if (this.inComment) {
424
+ const end = line.indexOf("-->", i);
425
+ if (end === -1) return null;
426
+ this.inComment = false;
427
+ i = end + 2;
428
+ continue;
429
+ }
430
+ if (this.inCdata) {
431
+ const end = line.indexOf("]]>", i);
432
+ if (end === -1) return null;
433
+ this.inCdata = false;
434
+ i = end + 2;
435
+ continue;
436
+ }
437
+ if (line.startsWith("<!--", i)) {
438
+ this.inComment = true;
439
+ i += 3;
440
+ continue;
441
+ }
442
+ if (line.startsWith("<![CDATA[", i)) {
443
+ this.inCdata = true;
444
+ i += 8;
445
+ continue;
446
+ }
447
+ if (line[i] === "`" && !isEscapedBacktick(line, i)) {
448
+ let end = i;
449
+ while (line[end] === "`") end++;
450
+ this.inlineTicks = end - i;
451
+ i = end - 1;
452
+ continue;
453
+ }
454
+ if (line[i] !== "<") continue;
455
+ const tag = readXmlTag(line, i);
456
+ if (!tag) continue;
457
+ const tagEnd = i + tag.raw.length;
458
+ if (tag.tagName === this.rootTag) {
459
+ if (tag.isClosing) this.depth--;
460
+ else if (!tag.isSelfClosing) this.depth++;
461
+ if (this.depth === 0) return tagEnd;
462
+ }
463
+ i = tagEnd - 1;
464
+ }
465
+ return null;
466
+ }
467
+ };
468
+
469
+ // source/tokenize.ts
470
+ var NOT_WHITESPACE = /\S/;
471
+ function countStructuralBraces(source) {
472
+ let opens = 0;
473
+ let closes = 0;
474
+ let inString = false;
475
+ let escaped = false;
476
+ for (let i = 0; i < source.length; i++) {
477
+ const char = source[i];
478
+ if (inString) {
479
+ if (escaped) escaped = false;
480
+ else if (char === "\\") escaped = true;
481
+ else if (char === '"') inString = false;
482
+ continue;
483
+ }
484
+ if (char === '"') inString = true;
485
+ else if (char === "{") opens++;
486
+ else if (char === "}") closes++;
487
+ }
488
+ return { opens, closes };
489
+ }
490
+ function firstJsonKey(content) {
491
+ const match = /^\{\s*"([^"]+)"/.exec(content.trimStart());
492
+ return match ? match[1] : null;
493
+ }
494
+ function jsonMeta(content) {
495
+ const meta = {};
496
+ const key = firstJsonKey(content);
497
+ if (key) meta.firstKey = key;
498
+ try {
499
+ const parsed = JSON.parse(content.trim());
500
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
501
+ const kind = parsed.__kind;
502
+ if (typeof kind === "string" && kind.trim()) meta.kind = kind;
503
+ }
504
+ } catch {
505
+ const head = /^\{\s*"__kind"\s*:\s*"([^"]*)"/.exec(content.trimStart());
506
+ if (head) meta.kind = head[1];
507
+ }
508
+ if (typeof meta.kind === "string" && meta.kind.startsWith("directive_v")) {
509
+ meta.directive = true;
510
+ }
511
+ if (key === "matrx_version") meta.directive = true;
512
+ if (key && TYPED_JSON_ROOT_KEYS[key]) meta.typed = TYPED_JSON_ROOT_KEYS[key];
513
+ return meta;
514
+ }
515
+ function isRecognizedJsonHead(content) {
516
+ const key = firstJsonKey(content);
517
+ if (!key) return false;
518
+ return key === "__kind" || key === "matrx_version" || key in TYPED_JSON_ROOT_KEYS;
519
+ }
520
+ function matchingJsonObjectEnd(source, start, limit) {
521
+ if (source[start] !== "{") return null;
522
+ const stack = ["{"];
523
+ let inString = false;
524
+ let escaped = false;
525
+ for (let index = start + 1; index < limit; index++) {
526
+ const char = source[index];
527
+ if (inString) {
528
+ if (escaped) escaped = false;
529
+ else if (char === "\\") escaped = true;
530
+ else if (char === '"') inString = false;
531
+ continue;
532
+ }
533
+ if (char === '"') {
534
+ inString = true;
535
+ continue;
536
+ }
537
+ if (char === "{" || char === "[") {
538
+ stack.push(char);
539
+ continue;
540
+ }
541
+ if (char !== "}" && char !== "]") continue;
542
+ const opening = stack.pop();
543
+ if (char === "}" && opening !== "{" || char === "]" && opening !== "[") return null;
544
+ if (stack.length === 0) return index + 1;
545
+ }
546
+ return null;
547
+ }
548
+ function declaredKind(candidate) {
549
+ try {
550
+ const parsed = JSON.parse(candidate);
551
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
552
+ const kind = parsed.__kind;
553
+ return typeof kind === "string" && kind.trim() ? kind : null;
554
+ } catch {
555
+ return null;
556
+ }
557
+ }
558
+ function isUnclosedGenericXmlOpening(source) {
559
+ const text = source.trimStart();
560
+ const prefix = /^<([A-Za-z_][\w.:-]*)(?=\s|\/|$)/.exec(text);
561
+ if (!prefix || KNOWN_XML_TAG_NAMES.has(prefix[1]) || ALLOWED_RAW_HTML_TAGS.has(prefix[1].toLowerCase()))
562
+ return false;
563
+ let quote = null;
564
+ for (let i = prefix[0].length; i < text.length; i++) {
565
+ if (quote) {
566
+ if (text[i] === quote) quote = null;
567
+ } else if (text[i] === "'" || text[i] === '"') quote = text[i];
568
+ else if (text[i] === ">") return false;
569
+ }
570
+ return true;
571
+ }
572
+ function jsonStringState(line, from, upTo, state) {
573
+ let { inString, escaped } = state;
574
+ for (let idx = from; idx < upTo; idx++) {
575
+ const ch = line[idx];
576
+ if (escaped) {
577
+ escaped = false;
578
+ continue;
579
+ }
580
+ if (ch === "\\" && inString) {
581
+ escaped = true;
582
+ continue;
583
+ }
584
+ if (ch === '"') inString = !inString;
585
+ }
586
+ return { inString, escaped };
587
+ }
588
+ function backtickRunLength(str, pos) {
589
+ let n = 0;
590
+ while (pos + n < str.length && str[pos + n] === "`") n++;
591
+ return n;
592
+ }
593
+ function isSplitterTreeLine(line) {
594
+ return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
595
+ }
596
+ var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
597
+ var Tokenizer = class {
598
+ constructor(text) {
599
+ this.text = text;
600
+ this.n = text.length;
601
+ this.mathPairs = pairDisplayMath(text);
602
+ let start = 0;
603
+ for (let i = 0; i < text.length; i++) {
604
+ if (text.charCodeAt(i) === 10) {
605
+ this.lineStarts.push(start);
606
+ this.contentEnds.push(i > start && text.charCodeAt(i - 1) === 13 ? i - 1 : i);
607
+ this.lineEnds.push(i + 1);
608
+ start = i + 1;
609
+ }
610
+ }
611
+ if (start < text.length || text.length === 0) {
612
+ this.lineStarts.push(start);
613
+ this.contentEnds.push(text.length);
614
+ this.lineEnds.push(text.length);
615
+ }
616
+ }
617
+ text;
618
+ n;
619
+ lineStarts = [];
620
+ contentEnds = [];
621
+ lineEnds = [];
622
+ blocks = [];
623
+ blockCache = /* @__PURE__ */ new Map();
624
+ /** Lazily built: for line i, first line j >= i where brace net from i drops to <= 0. */
625
+ braceStop = null;
626
+ braceNet = null;
627
+ /** Every occurrence offset of each needle searched so far (overlapping, like indexOf). */
628
+ occurrences = /* @__PURE__ */ new Map();
629
+ /** `$$` opener → closer for the pairs the core renders as math. */
630
+ mathPairs;
631
+ finder = (needle, from) => this.next(needle, from);
632
+ /**
633
+ * `text.indexOf(needle, from)` in O(log k): the first query for a needle
634
+ * indexes all of its occurrences once, so repeated searches for a closer
635
+ * that never comes stay linear over the whole text.
636
+ */
637
+ next(needle, from) {
638
+ let list = this.occurrences.get(needle);
639
+ if (!list) {
640
+ list = [];
641
+ for (let at = this.text.indexOf(needle); at !== -1; at = this.text.indexOf(needle, at + 1)) {
642
+ list.push(at);
643
+ }
644
+ this.occurrences.set(needle, list);
645
+ }
646
+ let lo = 0;
647
+ let hi = list.length;
648
+ while (lo < hi) {
649
+ const mid = lo + hi >>> 1;
650
+ if (list[mid] < from) lo = mid + 1;
651
+ else hi = mid;
652
+ }
653
+ return lo < list.length ? list[lo] : -1;
654
+ }
655
+ get lineCount() {
656
+ return this.lineStarts.length;
657
+ }
658
+ lineOf(pos) {
659
+ let lo = 0;
660
+ let hi = this.lineStarts.length - 1;
661
+ while (lo < hi) {
662
+ const mid = lo + hi + 1 >>> 1;
663
+ if (this.lineStarts[mid] <= pos) lo = mid;
664
+ else hi = mid - 1;
665
+ }
666
+ return lo;
667
+ }
668
+ isBlank(from, to) {
669
+ return !NOT_WHITESPACE.test(this.text.slice(from, to));
670
+ }
671
+ lineIsBlank(li) {
672
+ return this.isBlank(this.lineStarts[li], this.contentEnds[li]);
673
+ }
674
+ lineText(li) {
675
+ return this.text.slice(this.lineStarts[li], this.contentEnds[li]);
676
+ }
677
+ run() {
678
+ let pos = 0;
679
+ while (pos < this.n) {
680
+ const li = this.lineOf(pos);
681
+ const ce = this.contentEnds[li];
682
+ if (this.isBlank(pos, ce)) {
683
+ pos = this.gap(pos, li);
684
+ continue;
685
+ }
686
+ const island = this.detectBlock(pos, li, true);
687
+ if (island) {
688
+ this.push({ kind: "island", start: island.start, end: island.end, island, inlines: [] });
689
+ pos = island.end;
690
+ continue;
691
+ }
692
+ pos = this.paragraph(pos, li);
693
+ }
694
+ return this.blocks;
695
+ }
696
+ push(block) {
697
+ if (block.end <= block.start) return;
698
+ if (block.kind === "prose" && this.isBlank(block.start, block.end)) {
699
+ block = { kind: "gap", start: block.start, end: block.end, island: null, inlines: [] };
700
+ }
701
+ const last = this.blocks[this.blocks.length - 1];
702
+ if (block.kind === "gap" && last && last.kind === "gap" && last.end === block.start) {
703
+ last.end = block.end;
704
+ return;
705
+ }
706
+ this.blocks.push(block);
707
+ }
708
+ gap(pos, li) {
709
+ let end = this.lineEnds[li];
710
+ let k = li + 1;
711
+ while (k < this.lineCount && this.lineIsBlank(k)) {
712
+ end = this.lineEnds[k];
713
+ k++;
714
+ }
715
+ this.push({ kind: "gap", start: pos, end, island: null, inlines: [] });
716
+ return end;
717
+ }
718
+ // ── block detection ────────────────────────────────────────────────────
719
+ detectBlock(p, li, atParaStart) {
720
+ const cacheKey = atParaStart ? -1 - p : p;
721
+ if (this.blockCache.has(cacheKey)) return this.blockCache.get(cacheKey) ?? null;
722
+ const found = this.detectBlockUncached(p, li, atParaStart);
723
+ this.blockCache.set(cacheKey, found);
724
+ return found;
725
+ }
726
+ detectBlockUncached(p, li, atParaStart) {
727
+ const ce = this.contentEnds[li];
728
+ const seg = this.text.slice(p, ce);
729
+ const t = seg.trimStart();
730
+ const indent = seg.length - t.length;
731
+ const tp = p + indent;
732
+ if (p === 0) {
733
+ const fm = this.frontMatter(li, t);
734
+ if (fm) return fm;
735
+ }
736
+ if (t.startsWith("```")) return this.backtickFence(p, li, t);
737
+ if (indent <= 3 && t.startsWith("~~~")) {
738
+ const tilde = this.tildeFence(p, li, t);
739
+ if (tilde) return tilde;
740
+ }
741
+ if (t.startsWith("$$")) {
742
+ const math = this.mathBlock(p, tp);
743
+ if (math) return math;
744
+ }
745
+ if (t.startsWith("<")) {
746
+ const attr = this.attributeXml(p, tp, t);
747
+ if (attr) return attr;
748
+ const region = this.xmlRegion(p, tp, t);
749
+ if (region) return region;
750
+ const container = this.genericXml(p, li, seg, indent);
751
+ if (container) return container;
752
+ if (t.startsWith("<!--")) {
753
+ const comment = this.blockComment(p, tp, li);
754
+ if (comment) return comment;
755
+ }
756
+ const html = this.htmlBlock(p, li, t, tp, atParaStart);
757
+ if (html) return html;
758
+ }
759
+ if (t.startsWith("{")) {
760
+ const json = this.bareJson(p, li, tp);
761
+ if (json) return json;
762
+ }
763
+ if (TREE_GLYPHS.test(t)) {
764
+ const tree = this.tree(p, li);
765
+ if (tree) return tree;
766
+ }
767
+ return null;
768
+ }
769
+ island(type, start, end, complete, meta = {}) {
770
+ return { type, start, end, complete, meta };
771
+ }
772
+ frontMatter(li, t) {
773
+ if (this.lineText(li) !== "---" || t !== "---") return null;
774
+ let sawContent = false;
775
+ for (let k = li + 1; k < this.lineCount; k++) {
776
+ const line = this.lineText(k);
777
+ if (line === "---" || line === "...") {
778
+ return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true) : null;
779
+ }
780
+ if (!NOT_WHITESPACE.test(line)) continue;
781
+ if (!/^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s)/.test(line)) return null;
782
+ sawContent = true;
783
+ }
784
+ return null;
785
+ }
786
+ /**
787
+ * Backtick fence — the renderer splitter's close rules: JSON-string aware for
788
+ * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
789
+ * fences, with the splitter's strict-CommonMark retry when a nested fence is
790
+ * still open at the end of the text.
791
+ */
792
+ backtickFence(p, li, t) {
793
+ const openTicks = backtickRunLength(t, 0);
794
+ const info = t.slice(openTicks).trim();
795
+ const lang = info.split(/\s/)[0] ?? "";
796
+ const isJson = lang === "json";
797
+ const normalized = normalizeFenceLanguage(lang);
798
+ const meta = { fence: "`", ticks: openTicks, lang };
799
+ if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
800
+ const nesting = fenceNestsInnerFences(lang);
801
+ let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
802
+ if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
803
+ if (typeof close === "number") {
804
+ return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
805
+ }
806
+ return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
807
+ }
808
+ /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
809
+ backtickFenceClose(li, openTicks, isJson, nesting) {
810
+ let state = { inString: false, escaped: false };
811
+ let nestedDepth = 0;
812
+ let sawNested = false;
813
+ for (let k = li + 1; k < this.lineCount; k++) {
814
+ const line = this.lineText(k);
815
+ const trimmedLine = line.trim();
816
+ if (trimmedLine.startsWith("```")) {
817
+ const at2 = line.indexOf("```");
818
+ if (isJson) {
819
+ const probe = jsonStringState(line, 0, at2, state);
820
+ if (probe.inString) {
821
+ state = jsonStringState(line, at2, line.length, probe);
822
+ continue;
823
+ }
824
+ }
825
+ const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
826
+ if (kind === "close-outer") return k;
827
+ if (kind === "open-nested") {
828
+ nestedDepth++;
829
+ sawNested = true;
830
+ }
831
+ if (kind === "close-nested") nestedDepth--;
832
+ if (isJson) state = jsonStringState(line, 0, line.length, state);
833
+ continue;
834
+ }
835
+ const at = line.indexOf("```");
836
+ if (at !== -1) {
837
+ if (isJson) {
838
+ const probe = jsonStringState(line, 0, at, state);
839
+ if (probe.inString) {
840
+ state = jsonStringState(line, at, line.length, probe);
841
+ continue;
842
+ }
843
+ }
844
+ const closeTicks = backtickRunLength(line, at);
845
+ const after = line.slice(at + closeTicks).trim();
846
+ if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
847
+ if (isJson) state = jsonStringState(line, 0, line.length, state);
848
+ continue;
849
+ }
850
+ if (isJson) state = jsonStringState(line, 0, line.length, state);
851
+ }
852
+ return sawNested && nestedDepth > 0 ? "retry-strict" : null;
853
+ }
854
+ fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
855
+ if (isJson && openLine + 1 < this.lineCount) {
856
+ const bodyEnd = closeLine < this.lineCount ? this.lineStarts[closeLine] : this.n;
857
+ const body = this.text.slice(this.lineStarts[openLine + 1], Math.max(bodyEnd, this.lineStarts[openLine + 1]));
858
+ if (body.trimStart().startsWith("{")) Object.assign(meta, jsonMeta(body));
859
+ }
860
+ return this.island("fence", p, end, complete, meta);
861
+ }
862
+ tildeFence(p, li, t) {
863
+ const open = /^(~{3,})(.*)$/.exec(t);
864
+ if (!open) return null;
865
+ const ticks = open[1].length;
866
+ const lang = open[2].trim().split(/\s/)[0] ?? "";
867
+ const meta = { fence: "~", ticks, lang };
868
+ const normalized = normalizeFenceLanguage(lang);
869
+ if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
870
+ const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
871
+ for (let k = li + 1; k < this.lineCount; k++) {
872
+ if (close.test(this.lineText(k))) {
873
+ return this.island("fence", p, this.contentEnds[k], true, meta);
874
+ }
875
+ }
876
+ return this.island("fence", p, this.n, false, meta);
877
+ }
878
+ /**
879
+ * A line-leading `$$` that OPENS a math pair (source/math-pairs.ts — the
880
+ * core's rule). A closer, an unpaired `$$`, or a prose pair is never a block:
881
+ * a lone `$$` renders as literal text and locks nothing.
882
+ */
883
+ mathBlock(p, tp) {
884
+ const close = this.mathPairs.get(tp);
885
+ if (close === void 0) return null;
886
+ const end = close + 2;
887
+ const li = this.lineOf(tp);
888
+ const ce = this.contentEnds[li];
889
+ if (end <= ce && !this.isBlank(end, ce)) return null;
890
+ const endLine = this.lineOf(close);
891
+ const endCe = this.contentEnds[endLine];
892
+ if (endLine !== li && !this.isBlank(end, endCe)) return null;
893
+ return this.island("math_block", p, end, true, { delimiter: "$$" });
894
+ }
895
+ /** `<artifact …>`, `<decision …>`, … at the start of a line. */
896
+ attributeXml(p, tp, t) {
897
+ for (const name of ATTRIBUTE_XML_NAMES) {
898
+ const prefix = `<${name}`;
899
+ if (!t.startsWith(prefix)) continue;
900
+ const next = t[prefix.length];
901
+ if (next !== " " && next !== ">" && next !== "/" && next !== " ") continue;
902
+ return this.attributeElement(name, p, tp, "xml_attr");
903
+ }
904
+ return null;
905
+ }
906
+ /** Opening tag at `tp` through its `</name>` (or to EOF when still streaming). */
907
+ attributeElement(name, p, tp, type) {
908
+ const li = this.lineOf(tp);
909
+ const ce = this.contentEnds[li];
910
+ const tag = readXmlTag(this.text, tp, this.finder);
911
+ let openEnd;
912
+ if (tag && !tag.isClosing) {
913
+ if (tag.isSelfClosing) {
914
+ return this.island(type, p, tp + tag.raw.length, true, { tag: name, selfClosing: true });
915
+ }
916
+ openEnd = tp + tag.raw.length;
917
+ } else {
918
+ const gt = this.next(">", tp);
919
+ if (gt === -1) return this.island(type, p, this.n, false, { tag: name });
920
+ if (gt >= ce) return null;
921
+ openEnd = gt + 1;
922
+ }
923
+ const closer = `</${name}>`;
924
+ const close = this.next(closer, openEnd);
925
+ if (close === -1) return this.island(type, p, this.n, false, { tag: name });
926
+ return this.island(type, p, close + closer.length, true, { tag: name });
927
+ }
928
+ /** `<thinking>`, `<flashcards>`, … — exact attribute-less openers. */
929
+ xmlRegion(p, tp, t) {
930
+ for (const name of XML_TAG_BLOCK_NAMES) {
931
+ const opener = `<${name}>`;
932
+ if (!t.startsWith(opener)) continue;
933
+ const closer = `</${name}>`;
934
+ const close = this.next(closer, tp + opener.length);
935
+ if (close === -1) return this.island("xml_region", p, this.n, false, { tag: name });
936
+ return this.island("xml_region", p, close + closer.length, true, { tag: name });
937
+ }
938
+ return null;
939
+ }
940
+ /** Line-leading unknown XML element — balanced by the renderer's own tracker. */
941
+ genericXml(p, li, seg, indent) {
942
+ const first = seg.slice(indent);
943
+ const opening = readXmlTag(first, 0);
944
+ if (opening && !opening.isClosing) {
945
+ const root = opening.tagName;
946
+ if (KNOWN_XML_TAG_NAMES.has(root) || ALLOWED_RAW_HTML_TAGS.has(root.toLowerCase())) {
947
+ return null;
948
+ }
949
+ const tracker = new XmlContainerTracker(root);
950
+ const firstEnd = tracker.consumeLine(seg, indent);
951
+ if (firstEnd !== null) {
952
+ return this.island("xml_container", p, p + firstEnd, true, { tag: root });
953
+ }
954
+ for (let k = li + 1; k < this.lineCount; k++) {
955
+ const end = tracker.consumeLine(this.lineText(k));
956
+ if (end !== null) {
957
+ return this.island("xml_container", p, this.lineStarts[k] + end, true, {
958
+ tag: root
959
+ });
960
+ }
961
+ }
962
+ return this.island("xml_container", p, this.n, false, { tag: root });
963
+ }
964
+ if (isUnclosedGenericXmlOpening(seg)) {
965
+ return this.island("xml_container", p, this.n, false, {});
966
+ }
967
+ return null;
968
+ }
969
+ blockComment(p, tp, li) {
970
+ const close = this.next("-->", tp + 4);
971
+ if (close === -1) return this.island("html_comment", p, this.n, false);
972
+ const end = close + 3;
973
+ const raw = this.text.slice(tp, end);
974
+ const anchor = PINNED_ANCHOR_RE.exec(raw);
975
+ const ce = this.contentEnds[li];
976
+ if (anchor) {
977
+ if (end <= ce && !this.isBlank(end, ce)) return null;
978
+ return this.island("anchor", p, end, true, { id: anchor[1] });
979
+ }
980
+ return this.island("html_comment", p, end, true);
981
+ }
982
+ htmlBlock(p, li, t, tp, atParaStart) {
983
+ const tag = readXmlTag(t, 0);
984
+ if (!tag) return null;
985
+ const name = tag.tagName.toLowerCase();
986
+ if (!ALLOWED_RAW_HTML_TAGS.has(name)) return null;
987
+ if (name === "pre" && !tag.isClosing) {
988
+ const closer = /<\/pre\s*>/gi;
989
+ closer.lastIndex = tp;
990
+ const close = closer.exec(this.text);
991
+ if (!close) return this.island("html_block", p, this.n, false, { tag: name });
992
+ return this.island("html_block", p, close.index + close[0].length, true, { tag: name });
993
+ }
994
+ const typeSix = HTML_BLOCK_LEVEL_TAGS.has(name);
995
+ const typeSeven = atParaStart && t.trimEnd() === tag.raw;
996
+ if (!typeSix && !typeSeven) return null;
997
+ let end = this.contentEnds[li];
998
+ for (let k = li + 1; k < this.lineCount && !this.lineIsBlank(k); k++) {
999
+ end = this.contentEnds[k];
1000
+ }
1001
+ return this.island("html_block", p, end, true, { tag: name });
1002
+ }
1003
+ buildBraceTables() {
1004
+ const count = this.lineCount;
1005
+ const prefix = new Int32Array(count + 1);
1006
+ for (let i = 0; i < count; i++) {
1007
+ const { opens, closes } = countStructuralBraces(this.lineText(i));
1008
+ prefix[i + 1] = prefix[i] + opens - closes;
1009
+ }
1010
+ const stop = new Int32Array(count).fill(-1);
1011
+ const stack = [];
1012
+ for (let m = 0; m <= count; m++) {
1013
+ const value = prefix[m];
1014
+ while (stack.length && prefix[stack[stack.length - 1]] >= value) {
1015
+ const i = stack.pop();
1016
+ if (i < count) stop[i] = m - 1;
1017
+ }
1018
+ stack.push(m);
1019
+ }
1020
+ this.braceNet = prefix;
1021
+ this.braceStop = stop;
1022
+ }
1023
+ /** A line-leading bare JSON object — the splitter's structural brace walk. */
1024
+ bareJson(p, li, tp) {
1025
+ let lastLine = null;
1026
+ let net = 0;
1027
+ if (p === this.lineStarts[li]) {
1028
+ if (!this.braceStop || !this.braceNet) this.buildBraceTables();
1029
+ const stop = this.braceStop[li];
1030
+ if (stop !== -1) {
1031
+ lastLine = stop;
1032
+ net = this.braceNet[stop + 1] - this.braceNet[li];
1033
+ }
1034
+ } else {
1035
+ let running = 0;
1036
+ for (let k = li; k < this.lineCount; k++) {
1037
+ const line = k === li ? this.text.slice(tp, this.contentEnds[li]) : this.lineText(k);
1038
+ const { opens, closes } = countStructuralBraces(line);
1039
+ running += opens - closes;
1040
+ if (running <= 0) {
1041
+ lastLine = k;
1042
+ net = running;
1043
+ break;
1044
+ }
1045
+ }
1046
+ }
1047
+ if (lastLine === null) {
1048
+ if (isRecognizedJsonHead(this.text.slice(tp, tp + 512))) {
1049
+ return this.island("json", p, this.n, false, jsonMeta(this.text.slice(tp)));
1050
+ }
1051
+ return null;
1052
+ }
1053
+ if (net !== 0) return null;
1054
+ const end = this.contentEnds[lastLine];
1055
+ const content = this.text.slice(tp, end).trim();
1056
+ try {
1057
+ const parsed = JSON.parse(content);
1058
+ if (parsed === null || typeof parsed !== "object") return null;
1059
+ } catch {
1060
+ return null;
1061
+ }
1062
+ return this.island("json", p, end, true, jsonMeta(content));
1063
+ }
1064
+ tree(p, li) {
1065
+ let treeLines = 0;
1066
+ let lastTree = li;
1067
+ let k = li;
1068
+ while (k < this.lineCount) {
1069
+ if (this.lineIsBlank(k)) {
1070
+ k++;
1071
+ continue;
1072
+ }
1073
+ if (!TREE_GLYPHS.test(this.lineText(k))) break;
1074
+ treeLines++;
1075
+ lastTree = k;
1076
+ k++;
1077
+ }
1078
+ if (treeLines < 3) return null;
1079
+ return this.island("tree", p, this.contentEnds[lastTree], true);
1080
+ }
1081
+ // ── prose ──────────────────────────────────────────────────────────────
1082
+ paragraph(pos, li) {
1083
+ let end = this.contentEnds[li];
1084
+ let breakLine = -1;
1085
+ for (let k = li + 1; k < this.lineCount; k++) {
1086
+ if (this.lineIsBlank(k)) break;
1087
+ if (this.detectBlock(this.lineStarts[k], k, false)) {
1088
+ breakLine = k;
1089
+ break;
1090
+ }
1091
+ end = this.contentEnds[k];
1092
+ }
1093
+ if (breakLine !== -1) {
1094
+ const breakIsland = this.detectBlock(this.lineStarts[breakLine], breakLine, false);
1095
+ if (breakIsland?.type === "tree") {
1096
+ let root = breakLine;
1097
+ for (let j = breakLine - 1; j >= li; j--) {
1098
+ const trimmed = this.lineText(j).trim();
1099
+ if (!trimmed || isSplitterTreeLine(trimmed) || /^#{1,6}\s/.test(trimmed)) break;
1100
+ root = j;
1101
+ }
1102
+ if (root < breakLine) {
1103
+ const treeStart = root === li ? pos : this.lineStarts[root];
1104
+ const tree = { ...breakIsland, start: treeStart };
1105
+ if (treeStart > pos) {
1106
+ const proseEnd = this.contentEnds[root - 1];
1107
+ const scan2 = this.scanInline(pos, proseEnd);
1108
+ if (!scan2.breakIsland) {
1109
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
1110
+ this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
1111
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
1112
+ return tree.end;
1113
+ }
1114
+ } else {
1115
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
1116
+ return tree.end;
1117
+ }
1118
+ }
1119
+ }
1120
+ }
1121
+ const scan = this.scanInline(pos, end);
1122
+ if (!scan.breakIsland) {
1123
+ this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
1124
+ return end;
1125
+ }
1126
+ const br = scan.breakIsland;
1127
+ if (scan.orphan) {
1128
+ let start = pos;
1129
+ while (this.blocks.length) {
1130
+ const last = this.blocks[this.blocks.length - 1];
1131
+ if (last.kind === "island") break;
1132
+ this.blocks.pop();
1133
+ start = last.start;
1134
+ }
1135
+ const island = { ...br, start };
1136
+ this.push({ kind: "island", start, end: island.end, island, inlines: [] });
1137
+ return island.end;
1138
+ }
1139
+ this.push({
1140
+ kind: "prose",
1141
+ start: pos,
1142
+ end: br.start,
1143
+ island: null,
1144
+ inlines: scan.inlines.filter((inline) => inline.end <= br.start)
1145
+ });
1146
+ this.push({ kind: "island", start: br.start, end: br.end, island: br, inlines: [] });
1147
+ return br.end;
1148
+ }
1149
+ scanInline(ps, pe) {
1150
+ const text = this.text;
1151
+ const inlines = [];
1152
+ const hasKind = text.slice(ps, pe).includes("__kind");
1153
+ let kindBudget = 4 * (pe - ps) + 65536;
1154
+ const stop = (island, orphan = false) => ({ inlines, breakIsland: island, orphan });
1155
+ const within = (needle, from) => {
1156
+ const at = this.next(needle, from);
1157
+ return at !== -1 && at + needle.length <= pe ? at : -1;
1158
+ };
1159
+ let runs = null;
1160
+ const codeSpanClose = (n, from) => {
1161
+ if (!runs) {
1162
+ runs = /* @__PURE__ */ new Map();
1163
+ for (let k = ps; k < pe; ) {
1164
+ if (text.charCodeAt(k) !== 96) {
1165
+ k++;
1166
+ continue;
1167
+ }
1168
+ const len = backtickRunLength(text, k);
1169
+ const list2 = runs.get(len);
1170
+ if (list2) list2.push(k);
1171
+ else runs.set(len, [k]);
1172
+ k += len;
1173
+ }
1174
+ }
1175
+ const list = runs.get(n);
1176
+ if (!list) return -1;
1177
+ let lo = 0;
1178
+ let hi = list.length;
1179
+ while (lo < hi) {
1180
+ const mid = lo + hi >>> 1;
1181
+ if (list[mid] < from) lo = mid + 1;
1182
+ else hi = mid;
1183
+ }
1184
+ const at = list[lo];
1185
+ return at !== void 0 && at + n <= pe ? at : -1;
1186
+ };
1187
+ let i = ps;
1188
+ while (i < pe) {
1189
+ const ch = text[i];
1190
+ if (ch === "\\") {
1191
+ const nextCh = text[i + 1];
1192
+ if (nextCh === "(" || nextCh === "[") {
1193
+ const close = within(nextCh === "(" ? "\\)" : "\\]", i + 2);
1194
+ if (close !== -1) {
1195
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: nextCh === "(" ? "\\(" : "\\[" }));
1196
+ i = close + 2;
1197
+ continue;
1198
+ }
1199
+ }
1200
+ i += 2;
1201
+ continue;
1202
+ }
1203
+ if (ch === "`") {
1204
+ const n = backtickRunLength(text, i);
1205
+ const close = codeSpanClose(n, i + n);
1206
+ i = close === -1 ? i + n : close + n;
1207
+ continue;
1208
+ }
1209
+ if (ch === "$") {
1210
+ if (text[i + 1] === "$") {
1211
+ const close = this.mathPairs.get(i);
1212
+ if (close === void 0) {
1213
+ i += 2;
1214
+ continue;
1215
+ }
1216
+ if (close + 2 > pe) return stop(this.island("math_block", i, close + 2, true, { delimiter: "$$" }));
1217
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: "$$" }));
1218
+ i = close + 2;
1219
+ continue;
1220
+ }
1221
+ const end = singleDollarMathEnd(text, i, pe);
1222
+ if (end > 0) {
1223
+ inlines.push(this.island("math_inline", i, end, true, { delimiter: "$" }));
1224
+ i = end;
1225
+ continue;
1226
+ }
1227
+ i++;
1228
+ continue;
1229
+ }
1230
+ if (ch === "{") {
1231
+ if (text[i + 1] === "{") {
1232
+ const close = this.next("}}", i + 2);
1233
+ if (close !== -1 && close + 2 <= pe && close - i <= 200 && !text.slice(i, close).includes("\n")) {
1234
+ const raw = text.slice(i, close + 2);
1235
+ const strict = STRICT_VARIABLE_RE.exec(raw);
1236
+ inlines.push(
1237
+ this.island("variable", i, close + 2, true, strict ? { name: strict[1] } : { template: true })
1238
+ );
1239
+ i = close + 2;
1240
+ continue;
1241
+ }
1242
+ i += 2;
1243
+ continue;
1244
+ }
1245
+ if (hasKind && kindBudget > 0 && /^\{\s*"/.test(text.slice(i, i + 64))) {
1246
+ const limit = Math.min(pe, i + 65536);
1247
+ const end = matchingJsonObjectEnd(text, i, limit);
1248
+ kindBudget -= (end ?? limit) - i;
1249
+ if (end !== null) {
1250
+ const kind = declaredKind(text.slice(i, end));
1251
+ if (kind) {
1252
+ inlines.push(this.island("kind_json", i, end, true, jsonMeta(text.slice(i, end))));
1253
+ i = end;
1254
+ continue;
1255
+ }
1256
+ }
1257
+ }
1258
+ i++;
1259
+ continue;
1260
+ }
1261
+ if (ch === "[") {
1262
+ const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
1263
+ if (media) {
1264
+ const close = within("]", i + media.length);
1265
+ const newline = this.next("\n", i);
1266
+ if (close !== -1 && (newline === -1 || newline > close)) {
1267
+ inlines.push(this.island("media_ref", i, close + 1, true, { media: media.slice(1, 6).toLowerCase() }));
1268
+ i = close + 1;
1269
+ continue;
1270
+ }
1271
+ }
1272
+ i++;
1273
+ continue;
1274
+ }
1275
+ if (ch === "<") {
1276
+ if (text.startsWith("<!--", i)) {
1277
+ const close = this.next("-->", i + 4);
1278
+ if (close === -1) return stop(this.island("html_comment", i, this.n, false));
1279
+ const end = close + 3;
1280
+ const anchor = end - i <= 64 ? PINNED_ANCHOR_RE.exec(text.slice(i, end)) : null;
1281
+ const type = anchor ? "anchor" : "html_comment";
1282
+ const meta = anchor ? { id: anchor[1] } : {};
1283
+ if (end > pe) return stop(this.island(type, i, end, true, meta));
1284
+ inlines.push(this.island(type, i, end, true, meta));
1285
+ i = end;
1286
+ continue;
1287
+ }
1288
+ const cite = CITE_RE.exec(text.slice(i, i + 64));
1289
+ if (cite) {
1290
+ inlines.push(this.island("cite", i, i + cite[0].length, true, { n: Number(cite[1]) }));
1291
+ i += cite[0].length;
1292
+ continue;
1293
+ }
1294
+ const tag = readXmlTag(text, i, this.finder);
1295
+ if (tag && i + tag.raw.length <= pe) {
1296
+ const name = tag.tagName;
1297
+ const tagEnd = i + tag.raw.length;
1298
+ const isAttr = ATTRIBUTE_XML_NAMES.includes(name);
1299
+ const isThinking = THINKING_FAMILY.includes(name) && tag.raw === `<${name}>`;
1300
+ if (!tag.isClosing && (isAttr || isThinking)) {
1301
+ if (tag.isSelfClosing) {
1302
+ inlines.push(this.island("xml_inline", i, tagEnd, true, { tag: name, selfClosing: true }));
1303
+ i = tagEnd;
1304
+ continue;
1305
+ }
1306
+ const closer = `</${name}>`;
1307
+ const close = this.next(closer, tagEnd);
1308
+ const blockType = isAttr ? "xml_attr" : "xml_region";
1309
+ if (close === -1) return stop(this.island(blockType, i, this.n, false, { tag: name }));
1310
+ const end = close + closer.length;
1311
+ if (end > pe) return stop(this.island(blockType, i, end, true, { tag: name }));
1312
+ inlines.push(this.island("xml_inline", i, end, true, { tag: name }));
1313
+ i = end;
1314
+ continue;
1315
+ }
1316
+ if (tag.isClosing && THINKING_FAMILY.includes(name)) {
1317
+ return stop(this.island("xml_region", i, tagEnd, true, { tag: name, continuation: true }), true);
1318
+ }
1319
+ const lower = name.toLowerCase();
1320
+ inlines.push(
1321
+ this.island(ALLOWED_RAW_HTML_TAGS.has(lower) ? "html_tag" : "xml_tag", i, tagEnd, true, {
1322
+ tag: name
1323
+ })
1324
+ );
1325
+ i = tagEnd;
1326
+ continue;
1327
+ }
1328
+ const pending = ATTRIBUTE_XML_NAMES.find(
1329
+ (name) => text.startsWith(`<${name}`, i) && /\s/.test(text[i + name.length + 1] ?? "")
1330
+ );
1331
+ if (pending && this.next(">", i) === -1) {
1332
+ return stop(this.island("xml_attr", i, this.n, false, { tag: pending }));
1333
+ }
1334
+ i++;
1335
+ continue;
1336
+ }
1337
+ i++;
1338
+ }
1339
+ return { inlines, breakIsland: null, orphan: false };
1340
+ }
1341
+ };
1342
+ function tokenizeSource(text) {
1343
+ const raw = new Tokenizer(text).run();
1344
+ const cp = buildCodePointIndex(text);
1345
+ const inline = (island) => ({
1346
+ islandType: island.type,
1347
+ inline: true,
1348
+ complete: island.complete,
1349
+ meta: island.meta,
1350
+ start: island.start,
1351
+ end: island.end,
1352
+ startCp: cp.toCodePoint(island.start),
1353
+ endCp: cp.toCodePoint(island.end),
1354
+ raw: text.slice(island.start, island.end)
1355
+ });
1356
+ return raw.map(
1357
+ (block) => ({
1358
+ kind: block.kind,
1359
+ islandType: block.island?.type ?? null,
1360
+ complete: block.island ? block.island.complete : true,
1361
+ meta: block.island?.meta ?? {},
1362
+ inlines: block.inlines.map(inline),
1363
+ start: block.start,
1364
+ end: block.end,
1365
+ startCp: cp.toCodePoint(block.start),
1366
+ endCp: cp.toCodePoint(block.end),
1367
+ raw: text.slice(block.start, block.end)
1368
+ })
1369
+ );
1370
+ }
1371
+ function joinSource(blocks) {
1372
+ let out = "";
1373
+ for (const block of blocks) out += block.raw;
1374
+ return out;
1375
+ }
1376
+ function listIslands(blocks) {
1377
+ const out = [];
1378
+ for (const block of blocks) {
1379
+ if (block.kind === "island" && block.islandType) {
1380
+ out.push({
1381
+ islandType: block.islandType,
1382
+ inline: false,
1383
+ complete: block.complete,
1384
+ meta: block.meta,
1385
+ start: block.start,
1386
+ end: block.end,
1387
+ startCp: block.startCp,
1388
+ endCp: block.endCp,
1389
+ raw: block.raw
1390
+ });
1391
+ } else {
1392
+ out.push(...block.inlines);
1393
+ }
1394
+ }
1395
+ return out;
1396
+ }
1397
+ function blockAt(blocks, offset) {
1398
+ let lo = 0;
1399
+ let hi = blocks.length - 1;
1400
+ while (lo <= hi) {
1401
+ const mid = lo + hi >>> 1;
1402
+ const block = blocks[mid];
1403
+ if (offset < block.start) hi = mid - 1;
1404
+ else if (offset >= block.end) lo = mid + 1;
1405
+ else return block;
1406
+ }
1407
+ return null;
1408
+ }
1409
+
1410
+ // source/splice.ts
1411
+ var SourceSpliceError = class extends Error {
1412
+ constructor(code, message) {
1413
+ super(message);
1414
+ this.code = code;
1415
+ this.name = "SourceSpliceError";
1416
+ }
1417
+ code;
1418
+ };
1419
+ function blockEdit(block, text) {
1420
+ return { start: block.start, end: block.end, text };
1421
+ }
1422
+ function islandEdit(island, text) {
1423
+ return { start: island.start, end: island.end, text, island: true };
1424
+ }
1425
+ function blockRangeEdit(first, last, text) {
1426
+ return { start: first.start, end: last.end, text };
1427
+ }
1428
+ function locateIsland(text, raw, from, expected, expectedFromEnd) {
1429
+ if (expected >= from && text.startsWith(raw, expected)) return expected;
1430
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) return expectedFromEnd;
1431
+ const after = text.indexOf(raw, Math.max(from, expected));
1432
+ const before = expected > from ? text.lastIndexOf(raw, expected) : -1;
1433
+ const beforeOk = before >= from ? before : -1;
1434
+ if (after === -1) return beforeOk;
1435
+ if (beforeOk === -1) return after;
1436
+ return expected - beforeOk <= after - expected ? beforeOk : after;
1437
+ }
1438
+ function commonPrefix(a, b) {
1439
+ const max = Math.min(a.length, b.length);
1440
+ let i = 0;
1441
+ while (i < max && a.charCodeAt(i) === b.charCodeAt(i)) i++;
1442
+ if (i > 0 && (splitsSurrogatePair(a, i) || splitsSurrogatePair(b, i))) i--;
1443
+ return i;
1444
+ }
1445
+ function commonSuffix(a, b, prefix) {
1446
+ const max = Math.min(a.length, b.length) - prefix;
1447
+ let i = 0;
1448
+ while (i < max && a.charCodeAt(a.length - 1 - i) === b.charCodeAt(b.length - 1 - i)) i++;
1449
+ if (i > 0 && (splitsSurrogatePair(a, a.length - i) || splitsSurrogatePair(b, b.length - i))) i--;
1450
+ return i;
1451
+ }
1452
+ function spliceSave(original, edits, options = {}) {
1453
+ const blocks = options.blocks ?? tokenizeSource(original);
1454
+ if (options.blocks && joinSource(blocks) !== original) {
1455
+ throw new SourceSpliceError("stale_blocks", "the blocks passed in were not cut from this original text");
1456
+ }
1457
+ const boundaries = /* @__PURE__ */ new Set([0, original.length]);
1458
+ for (const block of blocks) {
1459
+ boundaries.add(block.start);
1460
+ boundaries.add(block.end);
1461
+ }
1462
+ const islands = listIslands(blocks);
1463
+ const islandAt = /* @__PURE__ */ new Map();
1464
+ for (const island of islands) islandAt.set(`${island.start}:${island.end}`, island);
1465
+ const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
1466
+ let previousEnd = -1;
1467
+ for (const edit of sorted) {
1468
+ if (edit.start < 0 || edit.end > original.length || edit.start > edit.end) {
1469
+ throw new SourceSpliceError("out_of_range", `edit [${edit.start}, ${edit.end}) is outside the text`);
1470
+ }
1471
+ if (edit.island) {
1472
+ if (!islandAt.has(`${edit.start}:${edit.end}`)) {
1473
+ throw new SourceSpliceError(
1474
+ "misaligned",
1475
+ `islandEdit [${edit.start}, ${edit.end}) does not name an island's exact span`
1476
+ );
1477
+ }
1478
+ } else if (!boundaries.has(edit.start) || !boundaries.has(edit.end)) {
1479
+ throw new SourceSpliceError(
1480
+ "misaligned",
1481
+ `edit [${edit.start}, ${edit.end}) does not start and end on block boundaries`
1482
+ );
1483
+ }
1484
+ if (edit.start < previousEnd) {
1485
+ throw new SourceSpliceError("overlap", `edit [${edit.start}, ${edit.end}) overlaps the previous edit`);
1486
+ }
1487
+ previousEnd = edit.end;
1488
+ }
1489
+ const effective = [];
1490
+ const replacedIslands = /* @__PURE__ */ new Set();
1491
+ const pushSegment = (oldStart, oldEnd, replacement) => {
1492
+ const old = original.slice(oldStart, oldEnd);
1493
+ if (old === replacement) return;
1494
+ const prefix = commonPrefix(old, replacement);
1495
+ const suffix = commonSuffix(old, replacement, prefix);
1496
+ effective.push({
1497
+ oldStart: oldStart + prefix,
1498
+ oldEnd: oldEnd - suffix,
1499
+ replacement: replacement.slice(prefix, replacement.length - suffix)
1500
+ });
1501
+ };
1502
+ for (const edit of sorted) {
1503
+ if (edit.island) {
1504
+ replacedIslands.add(`${edit.start}:${edit.end}`);
1505
+ pushSegment(edit.start, edit.end, edit.text);
1506
+ continue;
1507
+ }
1508
+ if (edit.text === original.slice(edit.start, edit.end)) continue;
1509
+ const inside = islands.filter((island) => island.start >= edit.start && island.end <= edit.end);
1510
+ let oldCursor2 = edit.start;
1511
+ let newCursor2 = 0;
1512
+ for (const island of inside) {
1513
+ const at = locateIsland(edit.text, island.raw, newCursor2, newCursor2 + (island.start - oldCursor2), edit.text.length - (edit.end - island.start));
1514
+ if (at === -1) {
1515
+ const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
1516
+ throw new SourceSpliceError(
1517
+ "island_edit",
1518
+ `edit [${edit.start}, ${edit.end}) changes or removes the protected ${island.islandType}${name ? ` (${String(name)})` : ""} island at [${island.start}, ${island.end}); islands change only through islandEdit()`
1519
+ );
1520
+ }
1521
+ pushSegment(oldCursor2, island.start, edit.text.slice(newCursor2, at));
1522
+ oldCursor2 = island.end;
1523
+ newCursor2 = at + island.raw.length;
1524
+ }
1525
+ pushSegment(oldCursor2, edit.end, edit.text.slice(newCursor2));
1526
+ }
1527
+ if (effective.length === 0) {
1528
+ return {
1529
+ text: original,
1530
+ changed: false,
1531
+ changes: [],
1532
+ integrity: { ok: true, bytesOutsideEditsIdentical: true, disturbed: [] },
1533
+ blocks
1534
+ };
1535
+ }
1536
+ let text = "";
1537
+ let cursor = 0;
1538
+ let delta = 0;
1539
+ const pending = [];
1540
+ for (const edit of effective) {
1541
+ text += original.slice(cursor, edit.oldStart);
1542
+ const newStart = edit.oldStart + delta;
1543
+ text += edit.replacement;
1544
+ const newEnd = newStart + edit.replacement.length;
1545
+ const owner = sorted.find((e) => e.start <= edit.oldStart && e.end >= edit.oldEnd);
1546
+ pending.push({
1547
+ blockStart: owner?.start ?? edit.oldStart,
1548
+ blockEnd: owner?.end ?? edit.oldEnd,
1549
+ oldStart: edit.oldStart,
1550
+ oldEnd: edit.oldEnd,
1551
+ newStart,
1552
+ newEnd
1553
+ });
1554
+ delta += edit.replacement.length - (edit.oldEnd - edit.oldStart);
1555
+ cursor = edit.oldEnd;
1556
+ }
1557
+ text += original.slice(cursor);
1558
+ const oldCp = buildCodePointIndex(original);
1559
+ const newCp = buildCodePointIndex(text);
1560
+ const changes = pending.map((change) => ({
1561
+ ...change,
1562
+ oldStartCp: oldCp.toCodePoint(change.oldStart),
1563
+ oldEndCp: oldCp.toCodePoint(change.oldEnd),
1564
+ newStartCp: newCp.toCodePoint(change.newStart),
1565
+ newEndCp: newCp.toCodePoint(change.newEnd)
1566
+ }));
1567
+ let bytesOutsideEditsIdentical = true;
1568
+ let oldCursor = 0;
1569
+ let newCursor = 0;
1570
+ for (const change of changes) {
1571
+ if (original.slice(oldCursor, change.oldStart) !== text.slice(newCursor, change.newStart)) {
1572
+ bytesOutsideEditsIdentical = false;
1573
+ }
1574
+ oldCursor = change.oldEnd;
1575
+ newCursor = change.newEnd;
1576
+ }
1577
+ if (original.slice(oldCursor) !== text.slice(newCursor)) bytesOutsideEditsIdentical = false;
1578
+ const newBlocks = tokenizeSource(text);
1579
+ const newIslandsByStart = /* @__PURE__ */ new Map();
1580
+ for (const island of listIslands(newBlocks)) {
1581
+ const list = newIslandsByStart.get(island.start);
1582
+ if (list) list.push(island.raw);
1583
+ else newIslandsByStart.set(island.start, [island.raw]);
1584
+ }
1585
+ const disturbed = [];
1586
+ for (const island of islands) {
1587
+ if (replacedIslands.has(`${island.start}:${island.end}`)) continue;
1588
+ const mappedStart = mapPosition(changes, island.start, { assoc: 1 }).pos;
1589
+ const candidates = newIslandsByStart.get(mappedStart);
1590
+ if (!candidates || !candidates.includes(island.raw)) {
1591
+ disturbed.push({
1592
+ islandType: island.islandType,
1593
+ inline: island.inline,
1594
+ start: island.start,
1595
+ end: island.end,
1596
+ mappedStart
1597
+ });
1598
+ }
1599
+ }
1600
+ const integrity = {
1601
+ ok: bytesOutsideEditsIdentical && disturbed.length === 0,
1602
+ bytesOutsideEditsIdentical,
1603
+ disturbed
1604
+ };
1605
+ if ((options.requireIntegrity ?? true) && !integrity.ok) {
1606
+ const named = disturbed.slice(0, 5).map((island) => `${island.islandType}@${island.start}`).join(", ");
1607
+ throw new SourceSpliceError(
1608
+ "integrity",
1609
+ `integrity: the save disturbed ${disturbed.length} protected island(s) it did not replace${named ? ` (${named}${disturbed.length > 5 ? ", \u2026" : ""})` : ""}${bytesOutsideEditsIdentical ? "" : "; bytes outside the edits changed"}`
1610
+ );
1611
+ }
1612
+ return { text, changed: true, changes, integrity, blocks: newBlocks };
1613
+ }
1614
+ function mapPosition(changes, pos, options = {}) {
1615
+ const assoc = options.assoc ?? 1;
1616
+ const cp = options.unit === "codepoint";
1617
+ let delta = 0;
1618
+ for (const change of changes) {
1619
+ const oldStart = cp ? change.oldStartCp : change.oldStart;
1620
+ const oldEnd = cp ? change.oldEndCp : change.oldEnd;
1621
+ const newStart = cp ? change.newStartCp : change.newStart;
1622
+ const newEnd = cp ? change.newEndCp : change.newEnd;
1623
+ if (pos < oldStart) break;
1624
+ if (pos > oldEnd) {
1625
+ delta = newEnd - oldEnd;
1626
+ continue;
1627
+ }
1628
+ if (oldStart === oldEnd) {
1629
+ return { pos: assoc < 0 ? newStart : newEnd, deleted: false };
1630
+ }
1631
+ if (pos === oldStart) return { pos: newStart, deleted: false };
1632
+ if (pos === oldEnd) return { pos: newEnd, deleted: false };
1633
+ return { pos: assoc < 0 ? newStart : newEnd, deleted: true };
1634
+ }
1635
+ return { pos: pos + delta, deleted: false };
1636
+ }
1637
+ function mapRange(changes, start, end, unit = "utf16") {
1638
+ const cp = unit === "codepoint";
1639
+ const mappedStart = mapPosition(changes, start, { assoc: 1, unit });
1640
+ const mappedEnd = mapPosition(changes, end, { assoc: -1, unit });
1641
+ const touched = changes.some((change) => {
1642
+ const oldStart = cp ? change.oldStartCp : change.oldStart;
1643
+ const oldEnd = cp ? change.oldEndCp : change.oldEnd;
1644
+ return oldStart < end && oldEnd > start;
1645
+ });
1646
+ const newStart = mappedStart.pos;
1647
+ const newEnd = Math.max(newStart, mappedEnd.pos);
1648
+ return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
1649
+ }
1650
+
1651
+ export { NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource };
1652
+ //# sourceMappingURL=source.js.map
1653
+ //# sourceMappingURL=source.js.map