@ai-matrx/content-ir 0.12.0 → 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/index.cjs CHANGED
@@ -5175,6 +5175,29 @@ function normalizeFenceLanguage(language) {
5175
5175
  return CODE_LANGUAGE_ALIASES[lower] ?? lower;
5176
5176
  }
5177
5177
 
5178
+ // source/fence-nesting.ts
5179
+ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
5180
+ "markdown",
5181
+ "md",
5182
+ "mdx"
5183
+ ]);
5184
+ function fenceNestsInnerFences(language) {
5185
+ return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
5186
+ }
5187
+ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
5188
+ let ticks = 0;
5189
+ while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
5190
+ if (ticks < 3) return "content";
5191
+ const info = trimmed.slice(ticks).trim();
5192
+ if (info === "") {
5193
+ if (ticks < openTicks) return "content";
5194
+ if (nests && nestedDepth > 0) return "close-nested";
5195
+ return "close-outer";
5196
+ }
5197
+ if (nests && ticks >= openTicks && !info.includes("`")) return "open-nested";
5198
+ return "content";
5199
+ }
5200
+
5178
5201
  // source/math.ts
5179
5202
  var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
5180
5203
  var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
@@ -5187,8 +5210,9 @@ function isSingleDollarMath(content, after) {
5187
5210
  return TEX_SIGNAL.test(content);
5188
5211
  }
5189
5212
  function singleDollarMathEnd(text, open, limit = text.length) {
5213
+ const bound = Math.min(limit, open + 403);
5190
5214
  let close = -1;
5191
- for (let k = open + 1; k < limit; k += 1) {
5215
+ for (let k = open + 1; k < bound; k += 1) {
5192
5216
  const c = text[k];
5193
5217
  if (c === "\n") break;
5194
5218
  if (c === "\\") {
@@ -5205,11 +5229,69 @@ function singleDollarMathEnd(text, open, limit = text.length) {
5205
5229
  return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
5206
5230
  }
5207
5231
 
5232
+ // source/math-pairs.ts
5233
+ var MAX_MATH_SPAN = 600;
5234
+ var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
5235
+ var LATEX_COMMAND = /\\[a-zA-Z]/;
5236
+ var PROSE_WORD = /[A-Za-z]{3,}/g;
5237
+ var PROSE_WORD_LIMIT = 6;
5238
+ function looksLikeDisplayMath(inner) {
5239
+ const s = inner.trim();
5240
+ if (!s) return false;
5241
+ if (STRUCTURAL_MARKDOWN.test(s)) return false;
5242
+ if (s.length > MAX_MATH_SPAN) return false;
5243
+ if (LATEX_COMMAND.test(s)) {
5244
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
5245
+ }
5246
+ if (/\n[ \t]*\n/.test(s)) return false;
5247
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
5248
+ }
5249
+ function codeRanges(text) {
5250
+ const ranges = [];
5251
+ for (const re of [/```[\s\S]*?(?:```|$)/g, /~~~[\s\S]*?(?:~~~|$)/g, /`[^`\n]*`/g]) {
5252
+ let m;
5253
+ while ((m = re.exec(text)) !== null) {
5254
+ ranges.push([m.index, m.index + m[0].length]);
5255
+ if (m[0].length === 0) re.lastIndex++;
5256
+ }
5257
+ }
5258
+ return ranges.sort((a, b) => a[0] - b[0]);
5259
+ }
5260
+ function pairDisplayMath(text) {
5261
+ const pairs = /* @__PURE__ */ new Map();
5262
+ if (!text.includes("$$")) return pairs;
5263
+ const ranges = codeRanges(text);
5264
+ const tokens = [];
5265
+ let r = 0;
5266
+ let maxEnd = -1;
5267
+ for (let i = text.indexOf("$$"); i !== -1 && i < text.length - 1; i = text.indexOf("$$", i)) {
5268
+ while (r < ranges.length && ranges[r][0] <= i) {
5269
+ maxEnd = Math.max(maxEnd, ranges[r][1]);
5270
+ r++;
5271
+ }
5272
+ if (maxEnd <= i) tokens.push(i);
5273
+ i += 2;
5274
+ }
5275
+ let j = 0;
5276
+ while (j < tokens.length) {
5277
+ const open = tokens[j];
5278
+ const close = tokens[j + 1];
5279
+ if (close === void 0) break;
5280
+ if (looksLikeDisplayMath(text.slice(open + 2, close))) {
5281
+ pairs.set(open, close);
5282
+ j += 2;
5283
+ continue;
5284
+ }
5285
+ j += 1;
5286
+ }
5287
+ return pairs;
5288
+ }
5289
+
5208
5290
  // source/xml-tag.ts
5209
5291
  var XML_NAME_START = /[A-Za-z_]/;
5210
5292
  var XML_NAME_CHARACTER = /[\w.:-]/;
5211
5293
  var WHITESPACE = /\s/;
5212
- function readXmlTag(content, start) {
5294
+ function readXmlTag(content, start, find = (needle, from) => content.indexOf(needle, from)) {
5213
5295
  let cursor = start + 1;
5214
5296
  const isClosing = content[cursor] === "/";
5215
5297
  if (isClosing) cursor++;
@@ -5262,8 +5344,9 @@ function readXmlTag(content, start) {
5262
5344
  const quote = content[cursor];
5263
5345
  if (quote !== '"' && quote !== "'") return null;
5264
5346
  const valueStart = ++cursor;
5265
- while (cursor < content.length && content[cursor] !== quote) cursor++;
5266
- if (cursor === content.length) return null;
5347
+ const valueEnd = find(quote, valueStart);
5348
+ if (valueEnd === -1) return null;
5349
+ cursor = valueEnd;
5267
5350
  attributes.push({ name, value: content.slice(valueStart, cursor) });
5268
5351
  cursor++;
5269
5352
  }
@@ -5482,11 +5565,15 @@ function backtickRunLength(str2, pos) {
5482
5565
  while (pos + n < str2.length && str2[pos + n] === "`") n++;
5483
5566
  return n;
5484
5567
  }
5568
+ function isSplitterTreeLine(line) {
5569
+ return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
5570
+ }
5485
5571
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
5486
5572
  var Tokenizer = class {
5487
5573
  constructor(text) {
5488
5574
  this.text = text;
5489
5575
  this.n = text.length;
5576
+ this.mathPairs = pairDisplayMath(text);
5490
5577
  let start = 0;
5491
5578
  for (let i = 0; i < text.length; i++) {
5492
5579
  if (text.charCodeAt(i) === 10) {
@@ -5512,6 +5599,34 @@ var Tokenizer = class {
5512
5599
  /** Lazily built: for line i, first line j >= i where brace net from i drops to <= 0. */
5513
5600
  braceStop = null;
5514
5601
  braceNet = null;
5602
+ /** Every occurrence offset of each needle searched so far (overlapping, like indexOf). */
5603
+ occurrences = /* @__PURE__ */ new Map();
5604
+ /** `$$` opener → closer for the pairs the core renders as math. */
5605
+ mathPairs;
5606
+ finder = (needle, from) => this.next(needle, from);
5607
+ /**
5608
+ * `text.indexOf(needle, from)` in O(log k): the first query for a needle
5609
+ * indexes all of its occurrences once, so repeated searches for a closer
5610
+ * that never comes stay linear over the whole text.
5611
+ */
5612
+ next(needle, from) {
5613
+ let list = this.occurrences.get(needle);
5614
+ if (!list) {
5615
+ list = [];
5616
+ for (let at = this.text.indexOf(needle); at !== -1; at = this.text.indexOf(needle, at + 1)) {
5617
+ list.push(at);
5618
+ }
5619
+ this.occurrences.set(needle, list);
5620
+ }
5621
+ let lo = 0;
5622
+ let hi = list.length;
5623
+ while (lo < hi) {
5624
+ const mid = lo + hi >>> 1;
5625
+ if (list[mid] < from) lo = mid + 1;
5626
+ else hi = mid;
5627
+ }
5628
+ return lo < list.length ? list[lo] : -1;
5629
+ }
5515
5630
  get lineCount() {
5516
5631
  return this.lineStarts.length;
5517
5632
  }
@@ -5643,7 +5758,12 @@ var Tokenizer = class {
5643
5758
  }
5644
5759
  return null;
5645
5760
  }
5646
- /** Backtick fence — the splitter's close rules, JSON-string aware for ```json. */
5761
+ /**
5762
+ * Backtick fence — the renderer splitter's close rules: JSON-string aware for
5763
+ * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
5764
+ * fences, with the splitter's strict-CommonMark retry when a nested fence is
5765
+ * still open at the end of the text.
5766
+ */
5647
5767
  backtickFence(p, li, t) {
5648
5768
  const openTicks = backtickRunLength(t, 0);
5649
5769
  const info = t.slice(openTicks).trim();
@@ -5652,7 +5772,19 @@ var Tokenizer = class {
5652
5772
  const normalized = normalizeFenceLanguage(lang);
5653
5773
  const meta = { fence: "`", ticks: openTicks, lang };
5654
5774
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
5775
+ const nesting = fenceNestsInnerFences(lang);
5776
+ let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
5777
+ if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
5778
+ if (typeof close === "number") {
5779
+ return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
5780
+ }
5781
+ return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
5782
+ }
5783
+ /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
5784
+ backtickFenceClose(li, openTicks, isJson, nesting) {
5655
5785
  let state = { inString: false, escaped: false };
5786
+ let nestedDepth = 0;
5787
+ let sawNested = false;
5656
5788
  for (let k = li + 1; k < this.lineCount; k++) {
5657
5789
  const line = this.lineText(k);
5658
5790
  const trimmedLine = line.trim();
@@ -5665,11 +5797,13 @@ var Tokenizer = class {
5665
5797
  continue;
5666
5798
  }
5667
5799
  }
5668
- const closeTicks = backtickRunLength(trimmedLine, 0);
5669
- const isBare = trimmedLine.slice(closeTicks).trim() === "";
5670
- if (isBare && closeTicks >= openTicks) {
5671
- return this.fenceIsland(p, this.contentEnds[k], true, meta, li, k, isJson);
5800
+ const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
5801
+ if (kind === "close-outer") return k;
5802
+ if (kind === "open-nested") {
5803
+ nestedDepth++;
5804
+ sawNested = true;
5672
5805
  }
5806
+ if (kind === "close-nested") nestedDepth--;
5673
5807
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5674
5808
  continue;
5675
5809
  }
@@ -5684,15 +5818,13 @@ var Tokenizer = class {
5684
5818
  }
5685
5819
  const closeTicks = backtickRunLength(line, at);
5686
5820
  const after = line.slice(at + closeTicks).trim();
5687
- if (closeTicks >= openTicks && after === "") {
5688
- return this.fenceIsland(p, this.contentEnds[k], true, meta, li, k, isJson);
5689
- }
5821
+ if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
5690
5822
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5691
5823
  continue;
5692
5824
  }
5693
5825
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5694
5826
  }
5695
- return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
5827
+ return sawNested && nestedDepth > 0 ? "retry-strict" : null;
5696
5828
  }
5697
5829
  fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
5698
5830
  if (isJson && openLine + 1 < this.lineCount) {
@@ -5718,18 +5850,22 @@ var Tokenizer = class {
5718
5850
  }
5719
5851
  return this.island("fence", p, this.n, false, meta);
5720
5852
  }
5853
+ /**
5854
+ * A line-leading `$$` that OPENS a math pair (source/math-pairs.ts — the
5855
+ * core's rule). A closer, an unpaired `$$`, or a prose pair is never a block:
5856
+ * a lone `$$` renders as literal text and locks nothing.
5857
+ */
5721
5858
  mathBlock(p, tp) {
5859
+ const close = this.mathPairs.get(tp);
5860
+ if (close === void 0) return null;
5861
+ const end = close + 2;
5722
5862
  const li = this.lineOf(tp);
5723
5863
  const ce = this.contentEnds[li];
5724
- const sameLine = this.text.indexOf("$$", tp + 2);
5725
- if (sameLine !== -1 && sameLine + 2 <= ce) {
5726
- if (!this.isBlank(sameLine + 2, ce)) return null;
5727
- if (sameLine === tp + 2) return null;
5728
- return this.island("math_block", p, sameLine + 2, true, { delimiter: "$$" });
5729
- }
5730
- const close = this.text.indexOf("$$", tp + 2);
5731
- if (close === -1) return this.island("math_block", p, this.n, false, { delimiter: "$$" });
5732
- return this.island("math_block", p, close + 2, true, { delimiter: "$$" });
5864
+ if (end <= ce && !this.isBlank(end, ce)) return null;
5865
+ const endLine = this.lineOf(close);
5866
+ const endCe = this.contentEnds[endLine];
5867
+ if (endLine !== li && !this.isBlank(end, endCe)) return null;
5868
+ return this.island("math_block", p, end, true, { delimiter: "$$" });
5733
5869
  }
5734
5870
  /** `<artifact …>`, `<decision …>`, … at the start of a line. */
5735
5871
  attributeXml(p, tp, t) {
@@ -5746,7 +5882,7 @@ var Tokenizer = class {
5746
5882
  attributeElement(name, p, tp, type) {
5747
5883
  const li = this.lineOf(tp);
5748
5884
  const ce = this.contentEnds[li];
5749
- const tag = readXmlTag(this.text, tp);
5885
+ const tag = readXmlTag(this.text, tp, this.finder);
5750
5886
  let openEnd;
5751
5887
  if (tag && !tag.isClosing) {
5752
5888
  if (tag.isSelfClosing) {
@@ -5754,13 +5890,13 @@ var Tokenizer = class {
5754
5890
  }
5755
5891
  openEnd = tp + tag.raw.length;
5756
5892
  } else {
5757
- const gt = this.text.indexOf(">", tp);
5893
+ const gt = this.next(">", tp);
5758
5894
  if (gt === -1) return this.island(type, p, this.n, false, { tag: name });
5759
5895
  if (gt >= ce) return null;
5760
5896
  openEnd = gt + 1;
5761
5897
  }
5762
5898
  const closer = `</${name}>`;
5763
- const close = this.text.indexOf(closer, openEnd);
5899
+ const close = this.next(closer, openEnd);
5764
5900
  if (close === -1) return this.island(type, p, this.n, false, { tag: name });
5765
5901
  return this.island(type, p, close + closer.length, true, { tag: name });
5766
5902
  }
@@ -5770,7 +5906,7 @@ var Tokenizer = class {
5770
5906
  const opener = `<${name}>`;
5771
5907
  if (!t.startsWith(opener)) continue;
5772
5908
  const closer = `</${name}>`;
5773
- const close = this.text.indexOf(closer, tp + opener.length);
5909
+ const close = this.next(closer, tp + opener.length);
5774
5910
  if (close === -1) return this.island("xml_region", p, this.n, false, { tag: name });
5775
5911
  return this.island("xml_region", p, close + closer.length, true, { tag: name });
5776
5912
  }
@@ -5806,7 +5942,7 @@ var Tokenizer = class {
5806
5942
  return null;
5807
5943
  }
5808
5944
  blockComment(p, tp, li) {
5809
- const close = this.text.indexOf("-->", tp + 4);
5945
+ const close = this.next("-->", tp + 4);
5810
5946
  if (close === -1) return this.island("html_comment", p, this.n, false);
5811
5947
  const end = close + 3;
5812
5948
  const raw = this.text.slice(tp, end);
@@ -5884,9 +6020,8 @@ var Tokenizer = class {
5884
6020
  }
5885
6021
  }
5886
6022
  if (lastLine === null) {
5887
- const partial = this.text.slice(tp);
5888
- if (isRecognizedJsonHead(partial)) {
5889
- return this.island("json", p, this.n, false, jsonMeta(partial));
6023
+ if (isRecognizedJsonHead(this.text.slice(tp, tp + 512))) {
6024
+ return this.island("json", p, this.n, false, jsonMeta(this.text.slice(tp)));
5890
6025
  }
5891
6026
  return null;
5892
6027
  }
@@ -5921,11 +6056,43 @@ var Tokenizer = class {
5921
6056
  // ── prose ──────────────────────────────────────────────────────────────
5922
6057
  paragraph(pos, li) {
5923
6058
  let end = this.contentEnds[li];
6059
+ let breakLine = -1;
5924
6060
  for (let k = li + 1; k < this.lineCount; k++) {
5925
6061
  if (this.lineIsBlank(k)) break;
5926
- if (this.detectBlock(this.lineStarts[k], k, false)) break;
6062
+ if (this.detectBlock(this.lineStarts[k], k, false)) {
6063
+ breakLine = k;
6064
+ break;
6065
+ }
5927
6066
  end = this.contentEnds[k];
5928
6067
  }
6068
+ if (breakLine !== -1) {
6069
+ const breakIsland = this.detectBlock(this.lineStarts[breakLine], breakLine, false);
6070
+ if (breakIsland?.type === "tree") {
6071
+ let root = breakLine;
6072
+ for (let j = breakLine - 1; j >= li; j--) {
6073
+ const trimmed = this.lineText(j).trim();
6074
+ if (!trimmed || isSplitterTreeLine(trimmed) || /^#{1,6}\s/.test(trimmed)) break;
6075
+ root = j;
6076
+ }
6077
+ if (root < breakLine) {
6078
+ const treeStart = root === li ? pos : this.lineStarts[root];
6079
+ const tree = { ...breakIsland, start: treeStart };
6080
+ if (treeStart > pos) {
6081
+ const proseEnd = this.contentEnds[root - 1];
6082
+ const scan2 = this.scanInline(pos, proseEnd);
6083
+ if (!scan2.breakIsland) {
6084
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
6085
+ this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
6086
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
6087
+ return tree.end;
6088
+ }
6089
+ } else {
6090
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
6091
+ return tree.end;
6092
+ }
6093
+ }
6094
+ }
6095
+ }
5929
6096
  const scan = this.scanInline(pos, end);
5930
6097
  if (!scan.breakIsland) {
5931
6098
  this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
@@ -5958,17 +6125,49 @@ var Tokenizer = class {
5958
6125
  const text = this.text;
5959
6126
  const inlines = [];
5960
6127
  const hasKind = text.slice(ps, pe).includes("__kind");
6128
+ let kindBudget = 4 * (pe - ps) + 65536;
5961
6129
  const stop = (island, orphan = false) => ({ inlines, breakIsland: island, orphan });
6130
+ const within = (needle, from) => {
6131
+ const at = this.next(needle, from);
6132
+ return at !== -1 && at + needle.length <= pe ? at : -1;
6133
+ };
6134
+ let runs = null;
6135
+ const codeSpanClose = (n, from) => {
6136
+ if (!runs) {
6137
+ runs = /* @__PURE__ */ new Map();
6138
+ for (let k = ps; k < pe; ) {
6139
+ if (text.charCodeAt(k) !== 96) {
6140
+ k++;
6141
+ continue;
6142
+ }
6143
+ const len = backtickRunLength(text, k);
6144
+ const list2 = runs.get(len);
6145
+ if (list2) list2.push(k);
6146
+ else runs.set(len, [k]);
6147
+ k += len;
6148
+ }
6149
+ }
6150
+ const list = runs.get(n);
6151
+ if (!list) return -1;
6152
+ let lo = 0;
6153
+ let hi = list.length;
6154
+ while (lo < hi) {
6155
+ const mid = lo + hi >>> 1;
6156
+ if (list[mid] < from) lo = mid + 1;
6157
+ else hi = mid;
6158
+ }
6159
+ const at = list[lo];
6160
+ return at !== void 0 && at + n <= pe ? at : -1;
6161
+ };
5962
6162
  let i = ps;
5963
6163
  while (i < pe) {
5964
6164
  const ch = text[i];
5965
6165
  if (ch === "\\") {
5966
- const next = text[i + 1];
5967
- if (next === "(" || next === "[") {
5968
- const closer = next === "(" ? "\\)" : "\\]";
5969
- const close = text.indexOf(closer, i + 2);
5970
- if (close !== -1 && close + 2 <= pe) {
5971
- inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: next === "(" ? "\\(" : "\\[" }));
6166
+ const nextCh = text[i + 1];
6167
+ if (nextCh === "(" || nextCh === "[") {
6168
+ const close = within(nextCh === "(" ? "\\)" : "\\]", i + 2);
6169
+ if (close !== -1) {
6170
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: nextCh === "(" ? "\\(" : "\\[" }));
5972
6171
  i = close + 2;
5973
6172
  continue;
5974
6173
  }
@@ -5978,33 +6177,20 @@ var Tokenizer = class {
5978
6177
  }
5979
6178
  if (ch === "`") {
5980
6179
  const n = backtickRunLength(text, i);
5981
- const run = "`".repeat(n);
5982
- let k = i + n;
5983
- let close = -1;
5984
- while (k < pe) {
5985
- const idx = text.indexOf(run, k);
5986
- if (idx === -1 || idx >= pe) break;
5987
- if (text[idx + n] === "`") {
5988
- let m = idx;
5989
- while (text[m] === "`") m++;
5990
- k = m;
5991
- continue;
5992
- }
5993
- close = idx;
5994
- break;
5995
- }
6180
+ const close = codeSpanClose(n, i + n);
5996
6181
  i = close === -1 ? i + n : close + n;
5997
6182
  continue;
5998
6183
  }
5999
6184
  if (ch === "$") {
6000
6185
  if (text[i + 1] === "$") {
6001
- const close = text.indexOf("$$", i + 2);
6002
- if (close !== -1 && close + 2 <= pe && close > i + 2) {
6003
- inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: "$$" }));
6004
- i = close + 2;
6005
- } else {
6186
+ const close = this.mathPairs.get(i);
6187
+ if (close === void 0) {
6006
6188
  i += 2;
6189
+ continue;
6007
6190
  }
6191
+ if (close + 2 > pe) return stop(this.island("math_block", i, close + 2, true, { delimiter: "$$" }));
6192
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: "$$" }));
6193
+ i = close + 2;
6008
6194
  continue;
6009
6195
  }
6010
6196
  const end = singleDollarMathEnd(text, i, pe);
@@ -6018,7 +6204,7 @@ var Tokenizer = class {
6018
6204
  }
6019
6205
  if (ch === "{") {
6020
6206
  if (text[i + 1] === "{") {
6021
- const close = text.indexOf("}}", i + 2);
6207
+ const close = this.next("}}", i + 2);
6022
6208
  if (close !== -1 && close + 2 <= pe && close - i <= 200 && !text.slice(i, close).includes("\n")) {
6023
6209
  const raw = text.slice(i, close + 2);
6024
6210
  const strict = STRICT_VARIABLE_RE.exec(raw);
@@ -6031,8 +6217,10 @@ var Tokenizer = class {
6031
6217
  i += 2;
6032
6218
  continue;
6033
6219
  }
6034
- if (hasKind) {
6035
- const end = matchingJsonObjectEnd(text, i, pe);
6220
+ if (hasKind && kindBudget > 0 && /^\{\s*"/.test(text.slice(i, i + 64))) {
6221
+ const limit = Math.min(pe, i + 65536);
6222
+ const end = matchingJsonObjectEnd(text, i, limit);
6223
+ kindBudget -= (end ?? limit) - i;
6036
6224
  if (end !== null) {
6037
6225
  const kind = declaredKind(text.slice(i, end));
6038
6226
  if (kind) {
@@ -6048,8 +6236,9 @@ var Tokenizer = class {
6048
6236
  if (ch === "[") {
6049
6237
  const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
6050
6238
  if (media) {
6051
- const close = text.indexOf("]", i + media.length);
6052
- if (close !== -1 && close < pe && !text.slice(i, close).includes("\n")) {
6239
+ const close = within("]", i + media.length);
6240
+ const newline = this.next("\n", i);
6241
+ if (close !== -1 && (newline === -1 || newline > close)) {
6053
6242
  inlines.push(this.island("media_ref", i, close + 1, true, { media: media.slice(1, 6).toLowerCase() }));
6054
6243
  i = close + 1;
6055
6244
  continue;
@@ -6060,10 +6249,10 @@ var Tokenizer = class {
6060
6249
  }
6061
6250
  if (ch === "<") {
6062
6251
  if (text.startsWith("<!--", i)) {
6063
- const close = text.indexOf("-->", i + 4);
6252
+ const close = this.next("-->", i + 4);
6064
6253
  if (close === -1) return stop(this.island("html_comment", i, this.n, false));
6065
6254
  const end = close + 3;
6066
- const anchor = PINNED_ANCHOR_RE.exec(text.slice(i, end));
6255
+ const anchor = end - i <= 64 ? PINNED_ANCHOR_RE.exec(text.slice(i, end)) : null;
6067
6256
  const type = anchor ? "anchor" : "html_comment";
6068
6257
  const meta = anchor ? { id: anchor[1] } : {};
6069
6258
  if (end > pe) return stop(this.island(type, i, end, true, meta));
@@ -6077,7 +6266,7 @@ var Tokenizer = class {
6077
6266
  i += cite[0].length;
6078
6267
  continue;
6079
6268
  }
6080
- const tag = readXmlTag(text, i);
6269
+ const tag = readXmlTag(text, i, this.finder);
6081
6270
  if (tag && i + tag.raw.length <= pe) {
6082
6271
  const name = tag.tagName;
6083
6272
  const tagEnd = i + tag.raw.length;
@@ -6090,7 +6279,7 @@ var Tokenizer = class {
6090
6279
  continue;
6091
6280
  }
6092
6281
  const closer = `</${name}>`;
6093
- const close = text.indexOf(closer, tagEnd);
6282
+ const close = this.next(closer, tagEnd);
6094
6283
  const blockType = isAttr ? "xml_attr" : "xml_region";
6095
6284
  if (close === -1) return stop(this.island(blockType, i, this.n, false, { tag: name }));
6096
6285
  const end = close + closer.length;
@@ -6114,7 +6303,7 @@ var Tokenizer = class {
6114
6303
  const pending = ATTRIBUTE_XML_NAMES.find(
6115
6304
  (name) => text.startsWith(`<${name}`, i) && /\s/.test(text[i + name.length + 1] ?? "")
6116
6305
  );
6117
- if (pending && text.indexOf(">", i) === -1) {
6306
+ if (pending && this.next(">", i) === -1) {
6118
6307
  return stop(this.island("xml_attr", i, this.n, false, { tag: pending }));
6119
6308
  }
6120
6309
  i++;
@@ -6205,9 +6394,22 @@ var SourceSpliceError = class extends Error {
6205
6394
  function blockEdit(block, text) {
6206
6395
  return { start: block.start, end: block.end, text };
6207
6396
  }
6397
+ function islandEdit(island, text) {
6398
+ return { start: island.start, end: island.end, text, island: true };
6399
+ }
6208
6400
  function blockRangeEdit(first, last, text) {
6209
6401
  return { start: first.start, end: last.end, text };
6210
6402
  }
6403
+ function locateIsland(text, raw, from, expected, expectedFromEnd) {
6404
+ if (expected >= from && text.startsWith(raw, expected)) return expected;
6405
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) return expectedFromEnd;
6406
+ const after = text.indexOf(raw, Math.max(from, expected));
6407
+ const before = expected > from ? text.lastIndexOf(raw, expected) : -1;
6408
+ const beforeOk = before >= from ? before : -1;
6409
+ if (after === -1) return beforeOk;
6410
+ if (beforeOk === -1) return after;
6411
+ return expected - beforeOk <= after - expected ? beforeOk : after;
6412
+ }
6211
6413
  function commonPrefix(a, b) {
6212
6414
  const max = Math.min(a.length, b.length);
6213
6415
  let i = 0;
@@ -6232,13 +6434,23 @@ function spliceSave(original, edits, options = {}) {
6232
6434
  boundaries.add(block.start);
6233
6435
  boundaries.add(block.end);
6234
6436
  }
6437
+ const islands = listIslands(blocks);
6438
+ const islandAt = /* @__PURE__ */ new Map();
6439
+ for (const island of islands) islandAt.set(`${island.start}:${island.end}`, island);
6235
6440
  const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
6236
6441
  let previousEnd = -1;
6237
6442
  for (const edit of sorted) {
6238
6443
  if (edit.start < 0 || edit.end > original.length || edit.start > edit.end) {
6239
6444
  throw new SourceSpliceError("out_of_range", `edit [${edit.start}, ${edit.end}) is outside the text`);
6240
6445
  }
6241
- if (!boundaries.has(edit.start) || !boundaries.has(edit.end)) {
6446
+ if (edit.island) {
6447
+ if (!islandAt.has(`${edit.start}:${edit.end}`)) {
6448
+ throw new SourceSpliceError(
6449
+ "misaligned",
6450
+ `islandEdit [${edit.start}, ${edit.end}) does not name an island's exact span`
6451
+ );
6452
+ }
6453
+ } else if (!boundaries.has(edit.start) || !boundaries.has(edit.end)) {
6242
6454
  throw new SourceSpliceError(
6243
6455
  "misaligned",
6244
6456
  `edit [${edit.start}, ${edit.end}) does not start and end on block boundaries`
@@ -6250,18 +6462,42 @@ function spliceSave(original, edits, options = {}) {
6250
6462
  previousEnd = edit.end;
6251
6463
  }
6252
6464
  const effective = [];
6253
- for (const edit of sorted) {
6254
- const old = original.slice(edit.start, edit.end);
6255
- if (old === edit.text) continue;
6256
- const prefix = commonPrefix(old, edit.text);
6257
- const suffix = commonSuffix(old, edit.text, prefix);
6465
+ const replacedIslands = /* @__PURE__ */ new Set();
6466
+ const pushSegment = (oldStart, oldEnd, replacement) => {
6467
+ const old = original.slice(oldStart, oldEnd);
6468
+ if (old === replacement) return;
6469
+ const prefix = commonPrefix(old, replacement);
6470
+ const suffix = commonSuffix(old, replacement, prefix);
6258
6471
  effective.push({
6259
- blockStart: edit.start,
6260
- blockEnd: edit.end,
6261
- oldStart: edit.start + prefix,
6262
- oldEnd: edit.end - suffix,
6263
- replacement: edit.text.slice(prefix, edit.text.length - suffix)
6472
+ oldStart: oldStart + prefix,
6473
+ oldEnd: oldEnd - suffix,
6474
+ replacement: replacement.slice(prefix, replacement.length - suffix)
6264
6475
  });
6476
+ };
6477
+ for (const edit of sorted) {
6478
+ if (edit.island) {
6479
+ replacedIslands.add(`${edit.start}:${edit.end}`);
6480
+ pushSegment(edit.start, edit.end, edit.text);
6481
+ continue;
6482
+ }
6483
+ if (edit.text === original.slice(edit.start, edit.end)) continue;
6484
+ const inside = islands.filter((island) => island.start >= edit.start && island.end <= edit.end);
6485
+ let oldCursor2 = edit.start;
6486
+ let newCursor2 = 0;
6487
+ for (const island of inside) {
6488
+ const at = locateIsland(edit.text, island.raw, newCursor2, newCursor2 + (island.start - oldCursor2), edit.text.length - (edit.end - island.start));
6489
+ if (at === -1) {
6490
+ const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
6491
+ throw new SourceSpliceError(
6492
+ "island_edit",
6493
+ `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()`
6494
+ );
6495
+ }
6496
+ pushSegment(oldCursor2, island.start, edit.text.slice(newCursor2, at));
6497
+ oldCursor2 = island.end;
6498
+ newCursor2 = at + island.raw.length;
6499
+ }
6500
+ pushSegment(oldCursor2, edit.end, edit.text.slice(newCursor2));
6265
6501
  }
6266
6502
  if (effective.length === 0) {
6267
6503
  return {
@@ -6281,9 +6517,10 @@ function spliceSave(original, edits, options = {}) {
6281
6517
  const newStart = edit.oldStart + delta;
6282
6518
  text += edit.replacement;
6283
6519
  const newEnd = newStart + edit.replacement.length;
6520
+ const owner = sorted.find((e) => e.start <= edit.oldStart && e.end >= edit.oldEnd);
6284
6521
  pending.push({
6285
- blockStart: edit.blockStart,
6286
- blockEnd: edit.blockEnd,
6522
+ blockStart: owner?.start ?? edit.oldStart,
6523
+ blockEnd: owner?.end ?? edit.oldEnd,
6287
6524
  oldStart: edit.oldStart,
6288
6525
  oldEnd: edit.oldEnd,
6289
6526
  newStart,
@@ -6321,11 +6558,8 @@ function spliceSave(original, edits, options = {}) {
6321
6558
  else newIslandsByStart.set(island.start, [island.raw]);
6322
6559
  }
6323
6560
  const disturbed = [];
6324
- for (const island of listIslands(blocks)) {
6325
- const touched = effective.some(
6326
- (edit) => island.start < edit.blockEnd && island.end > edit.blockStart
6327
- );
6328
- if (touched) continue;
6561
+ for (const island of islands) {
6562
+ if (replacedIslands.has(`${island.start}:${island.end}`)) continue;
6329
6563
  const mappedStart = mapPosition(changes, island.start, { assoc: 1 }).pos;
6330
6564
  const candidates = newIslandsByStart.get(mappedStart);
6331
6565
  if (!candidates || !candidates.includes(island.raw)) {
@@ -6343,10 +6577,11 @@ function spliceSave(original, edits, options = {}) {
6343
6577
  bytesOutsideEditsIdentical,
6344
6578
  disturbed
6345
6579
  };
6346
- if (options.requireIntegrity && !integrity.ok) {
6580
+ if ((options.requireIntegrity ?? true) && !integrity.ok) {
6581
+ const named = disturbed.slice(0, 5).map((island) => `${island.islandType}@${island.start}`).join(", ");
6347
6582
  throw new SourceSpliceError(
6348
6583
  "integrity",
6349
- `the edit disturbed ${disturbed.length} protected island(s) outside the edited blocks`
6584
+ `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"}`
6350
6585
  );
6351
6586
  }
6352
6587
  return { text, changed: true, changes, integrity, blocks: newBlocks };
@@ -6404,6 +6639,7 @@ exports.JsonStreamTokenizer = JsonStreamTokenizer;
6404
6639
  exports.KIND_KEY = KIND_KEY;
6405
6640
  exports.KindStorageError = KindStorageError;
6406
6641
  exports.KindStreamParser = KindStreamParser;
6642
+ exports.NESTING_FENCE_LANGUAGES = NESTING_FENCE_LANGUAGES;
6407
6643
  exports.NODE_OUTCOME_KIND = NODE_OUTCOME_KIND;
6408
6644
  exports.ParseSession = ParseSession;
6409
6645
  exports.RESERVED_PREFIX = RESERVED_PREFIX;
@@ -6425,6 +6661,7 @@ exports.buildDirectiveSlug = buildDirectiveSlug;
6425
6661
  exports.buildKindDirective = buildKindDirective;
6426
6662
  exports.capabilityOf = capabilityOf;
6427
6663
  exports.classifyInboundEnvelopeMetadata = classifyInboundEnvelopeMetadata;
6664
+ exports.classifyInnerFenceLine = classifyInnerFenceLine;
6428
6665
  exports.collectReferencedKinds = collectReferencedKinds;
6429
6666
  exports.collectSchemaReferencedKinds = collectSchemaReferencedKinds;
6430
6667
  exports.compareWithExistingKindSchema = compareWithExistingKindSchema;
@@ -6444,6 +6681,7 @@ exports.envelopeCacheFromEnvelopes = envelopeCacheFromEnvelopes;
6444
6681
  exports.envelopeFromCompleteValue = envelopeFromCompleteValue;
6445
6682
  exports.executesAtOutputRoot = executesAtOutputRoot;
6446
6683
  exports.fenceDiscriminator = fenceDiscriminator;
6684
+ exports.fenceNestsInnerFences = fenceNestsInnerFences;
6447
6685
  exports.fieldsToDbPayload = fieldsToDbPayload;
6448
6686
  exports.fingerprintText = fingerprintText;
6449
6687
  exports.formatBlockLabel = formatBlockLabel;
@@ -6466,6 +6704,7 @@ exports.isReservedDirectiveSlug = isReservedDirectiveSlug;
6466
6704
  exports.isScalarArrayType = isScalarArrayType;
6467
6705
  exports.isSingleDollarMath = isSingleDollarMath;
6468
6706
  exports.isTerminalKindEvent = isTerminalKindEvent;
6707
+ exports.islandEdit = islandEdit;
6469
6708
  exports.itemFacts = itemFacts;
6470
6709
  exports.itemSubtitle = itemSubtitle;
6471
6710
  exports.itemTitle = itemTitle;
@@ -6477,6 +6716,7 @@ exports.kindVerdictOf = kindVerdictOf;
6477
6716
  exports.legacyShellUses = legacyShellUses;
6478
6717
  exports.listIslands = listIslands;
6479
6718
  exports.looksLikeDirectiveHead = looksLikeDirectiveHead;
6719
+ exports.looksLikeDisplayMath = looksLikeDisplayMath;
6480
6720
  exports.makePartialKindStalenessGate = makePartialKindStalenessGate;
6481
6721
  exports.mapPosition = mapPosition;
6482
6722
  exports.mapRange = mapRange;
@@ -6487,6 +6727,7 @@ exports.nounFamily = nounFamily;
6487
6727
  exports.nounLabel = nounLabel;
6488
6728
  exports.nounTitleColumn = nounTitleColumn;
6489
6729
  exports.openParseSession = openParseSession;
6730
+ exports.pairDisplayMath = pairDisplayMath;
6490
6731
  exports.parseDirectiveSlug = parseDirectiveSlug;
6491
6732
  exports.readEnvelope = readEnvelope;
6492
6733
  exports.readNodeOutcomeValue = readNodeOutcomeValue;