@ai-matrx/content-ir 0.13.0 → 0.14.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.cjs CHANGED
@@ -211,11 +211,22 @@ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
211
211
  function fenceNestsInnerFences(language) {
212
212
  return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
213
213
  }
214
+ var FENCE_WHITESPACE = " \n\r\f\v";
215
+ function isFenceWhitespace(ch) {
216
+ return ch !== void 0 && ch !== "" && FENCE_WHITESPACE.includes(ch);
217
+ }
218
+ function trimFenceLine(line) {
219
+ let start = 0;
220
+ let end = line.length;
221
+ while (start < end && isFenceWhitespace(line[start])) start++;
222
+ while (end > start && isFenceWhitespace(line[end - 1])) end--;
223
+ return start === 0 && end === line.length ? line : line.slice(start, end);
224
+ }
214
225
  function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
215
226
  let ticks = 0;
216
227
  while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
217
228
  if (ticks < 3) return "content";
218
- const info = trimmed.slice(ticks).trim();
229
+ const info = trimFenceLine(trimmed.slice(ticks));
219
230
  if (info === "") {
220
231
  if (ticks < openTicks) return "content";
221
232
  if (nests && nestedDepth > 0) return "close-nested";
@@ -228,13 +239,42 @@ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
228
239
  // source/math.ts
229
240
  var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
230
241
  var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
242
+ var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
243
+ var FUNCTION_NAMES = /* @__PURE__ */ new Set([
244
+ "sin",
245
+ "cos",
246
+ "tan",
247
+ "sec",
248
+ "csc",
249
+ "cot",
250
+ "log",
251
+ "ln",
252
+ "exp",
253
+ "lim",
254
+ "max",
255
+ "min",
256
+ "det",
257
+ "gcd",
258
+ "mod",
259
+ "arg",
260
+ "sinh",
261
+ "cosh",
262
+ "tanh"
263
+ ]);
264
+ function isMathShape(content) {
265
+ if (!MATH_SHAPE_CHARS.test(content)) return false;
266
+ const words = content.match(/[A-Za-z]+/g);
267
+ if (!words) return false;
268
+ if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
269
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
270
+ }
231
271
  function isSingleDollarMath(content, after) {
232
272
  if (!content || content.length > 400) return false;
233
273
  if (/^\s/.test(content) || /\s$/.test(content)) return false;
234
274
  if (after !== void 0 && /[0-9]/.test(after)) return false;
235
275
  if (LONE_VARIABLE.test(content)) return true;
236
276
  if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
237
- return TEX_SIGNAL.test(content);
277
+ return TEX_SIGNAL.test(content) || isMathShape(content);
238
278
  }
239
279
  function singleDollarMathEnd(text, open, limit = text.length) {
240
280
  const bound = Math.min(limit, open + 403);
@@ -470,6 +510,10 @@ var XmlContainerTracker = class {
470
510
 
471
511
  // source/tokenize.ts
472
512
  var NOT_WHITESPACE = /\S/;
513
+ var DIRECTIVE_OPEN = /^(:{2,})([A-Za-z][\w-]*)/;
514
+ var MKDOCS_OPEN = /^(!!!|\?\?\?\+?)[ \t]+([A-Za-z][\w-]*)/;
515
+ var CALLOUT_OPEN = /^>[ \t]*\[!([A-Za-z][\w-]*)\]([+-])?/;
516
+ var FOOTNOTE_DEF = /^\[\^([^\]\s]+)\]:/;
473
517
  function countStructuralBraces(source) {
474
518
  let opens = 0;
475
519
  let closes = 0;
@@ -597,8 +641,9 @@ function isSplitterTreeLine(line) {
597
641
  }
598
642
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
599
643
  var Tokenizer = class {
600
- constructor(text) {
644
+ constructor(text, streaming = false) {
601
645
  this.text = text;
646
+ this.streaming = streaming;
602
647
  this.n = text.length;
603
648
  this.mathPairs = pairDisplayMath(text);
604
649
  let start = 0;
@@ -617,6 +662,7 @@ var Tokenizer = class {
617
662
  }
618
663
  }
619
664
  text;
665
+ streaming;
620
666
  n;
621
667
  lineStarts = [];
622
668
  contentEnds = [];
@@ -654,6 +700,10 @@ var Tokenizer = class {
654
700
  }
655
701
  return lo < list.length ? list[lo] : -1;
656
702
  }
703
+ /** Streaming only: the final line, still waiting for its terminator. */
704
+ isOpenTail(k) {
705
+ return this.streaming && k === this.lineStarts.length - 1 && this.lineEnds[k] === this.contentEnds[k];
706
+ }
657
707
  get lineCount() {
658
708
  return this.lineStarts.length;
659
709
  }
@@ -735,6 +785,22 @@ var Tokenizer = class {
735
785
  const fm = this.frontMatter(li, t);
736
786
  if (fm) return fm;
737
787
  }
788
+ if (indent <= 3 && t.startsWith("::")) {
789
+ const directive = this.directive(p, li, t);
790
+ if (directive) return directive;
791
+ }
792
+ if (indent === 0 && (t.startsWith("!!!") || t.startsWith("???"))) {
793
+ const admonition = this.mkdocsAdmonition(p, li, t);
794
+ if (admonition) return admonition;
795
+ }
796
+ if (indent <= 3 && t.startsWith(">")) {
797
+ const callout = this.callout(p, li, t);
798
+ if (callout) return callout;
799
+ }
800
+ if (atParaStart && indent <= 3 && t.startsWith("[^")) {
801
+ const footnote = this.footnoteDefinition(p, li, t);
802
+ if (footnote) return footnote;
803
+ }
738
804
  if (t.startsWith("```")) return this.backtickFence(p, li, t);
739
805
  if (indent <= 3 && t.startsWith("~~~")) {
740
806
  const tilde = this.tildeFence(p, li, t);
@@ -772,19 +838,110 @@ var Tokenizer = class {
772
838
  return { type, start, end, complete, meta };
773
839
  }
774
840
  frontMatter(li, t) {
775
- if (this.lineText(li) !== "---" || t !== "---") return null;
841
+ const fence = this.lineText(li);
842
+ if (fence !== "---" && fence !== "+++" || t !== fence) return null;
843
+ const toml = fence === "+++";
776
844
  let sawContent = false;
777
845
  for (let k = li + 1; k < this.lineCount; k++) {
778
846
  const line = this.lineText(k);
779
- if (line === "---" || line === "...") {
780
- return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true) : null;
847
+ if (line === fence || !toml && line === "...") {
848
+ return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true, { format: toml ? "toml" : "yaml" }) : null;
781
849
  }
782
850
  if (!NOT_WHITESPACE.test(line)) continue;
783
- if (!/^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s)/.test(line)) return null;
851
+ const shaped = toml ? /^\s*(?:[A-Za-z0-9_."-]+\s*=|\[|#)/.test(line) : /^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s|#)/.test(line);
852
+ if (!shaped) return null;
784
853
  sawContent = true;
785
854
  }
786
855
  return null;
787
856
  }
857
+ /**
858
+ * `:::name[label]{attrs}` … closing `:::` (a colon run at least as long as
859
+ * the opener's, nesting counted), or a leaf `::name[…]{…}` line. The whole
860
+ * container is ONE island: its grammar (fence lengths, labels, attributes,
861
+ * nested tabs) is exactly what a visual editor would re-serialize wrongly.
862
+ */
863
+ directive(p, li, t) {
864
+ const open = DIRECTIVE_OPEN.exec(t);
865
+ if (!open) return null;
866
+ const colons = open[1].length;
867
+ const name = open[2];
868
+ if (colons === 2) {
869
+ return this.island("directive", p, this.contentEnds[li], true, { name, leaf: true });
870
+ }
871
+ const stack = [colons];
872
+ for (let k = li + 1; k < this.lineCount; k++) {
873
+ const line = this.lineText(k).trim();
874
+ const inner = DIRECTIVE_OPEN.exec(line);
875
+ if (inner && inner[1].length >= 3) {
876
+ stack.push(inner[1].length);
877
+ continue;
878
+ }
879
+ const close = /^(:{3,})\s*$/.exec(line);
880
+ if (close && close[1].length >= stack[stack.length - 1]) {
881
+ stack.pop();
882
+ if (stack.length === 0) return this.island("directive", p, this.contentEnds[k], true, { name });
883
+ }
884
+ }
885
+ return this.island("directive", p, this.n, false, { name });
886
+ }
887
+ /** MkDocs `!!! type "Title"` / `??? type` / `???+ type` and its indented (or blank-separated indented) body. */
888
+ mkdocsAdmonition(p, li, t) {
889
+ const open = MKDOCS_OPEN.exec(t);
890
+ if (!open) return null;
891
+ let last = li;
892
+ for (let k = li + 1; k < this.lineCount; k++) {
893
+ const line = this.lineText(k);
894
+ if (/^(?: {4}|\t)/.test(line) && NOT_WHITESPACE.test(line)) {
895
+ last = k;
896
+ continue;
897
+ }
898
+ if (!NOT_WHITESPACE.test(line)) continue;
899
+ break;
900
+ }
901
+ return this.island("directive", p, this.contentEnds[last], true, {
902
+ name: open[2].toLowerCase(),
903
+ syntax: "mkdocs"
904
+ });
905
+ }
906
+ /** A blockquote whose first line is `> [!TYPE]…` — every following `>` line belongs to it. */
907
+ callout(p, li, t) {
908
+ const open = CALLOUT_OPEN.exec(t);
909
+ if (!open) return null;
910
+ let last = li;
911
+ for (let k = li + 1; k < this.lineCount; k++) {
912
+ if (!/^ {0,3}>/.test(this.lineText(k))) break;
913
+ last = k;
914
+ }
915
+ return this.island("callout", p, this.contentEnds[last], true, {
916
+ name: open[1].toLowerCase(),
917
+ ...open[2] ? { fold: open[2] === "-" ? "closed" : "open" } : {}
918
+ });
919
+ }
920
+ /** `[^id]: text` plus its continuation lines (indented, or lazy text before a blank line). */
921
+ footnoteDefinition(p, li, t) {
922
+ const open = FOOTNOTE_DEF.exec(t);
923
+ if (!open) return null;
924
+ let last = li;
925
+ for (let k = li + 1; k < this.lineCount; k++) {
926
+ const line = this.lineText(k);
927
+ if (!NOT_WHITESPACE.test(line)) {
928
+ let j = k + 1;
929
+ while (j < this.lineCount && !NOT_WHITESPACE.test(this.lineText(j))) j++;
930
+ if (j < this.lineCount && /^(?: {4}|\t)/.test(this.lineText(j))) {
931
+ k = j - 1;
932
+ continue;
933
+ }
934
+ break;
935
+ }
936
+ if (/^(?: {4}|\t)/.test(line)) {
937
+ last = k;
938
+ continue;
939
+ }
940
+ if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
941
+ last = k;
942
+ }
943
+ return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
944
+ }
788
945
  /**
789
946
  * Backtick fence — the renderer splitter's close rules: JSON-string aware for
790
947
  * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
@@ -813,8 +970,9 @@ var Tokenizer = class {
813
970
  let nestedDepth = 0;
814
971
  let sawNested = false;
815
972
  for (let k = li + 1; k < this.lineCount; k++) {
973
+ if (this.isOpenTail(k)) continue;
816
974
  const line = this.lineText(k);
817
- const trimmedLine = line.trim();
975
+ const trimmedLine = trimFenceLine(line);
818
976
  if (trimmedLine.startsWith("```")) {
819
977
  const at2 = line.indexOf("```");
820
978
  if (isJson) {
@@ -844,7 +1002,7 @@ var Tokenizer = class {
844
1002
  }
845
1003
  }
846
1004
  const closeTicks = backtickRunLength(line, at);
847
- const after = line.slice(at + closeTicks).trim();
1005
+ const after = trimFenceLine(line.slice(at + closeTicks));
848
1006
  if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
849
1007
  if (isJson) state = jsonStringState(line, 0, line.length, state);
850
1008
  continue;
@@ -871,6 +1029,7 @@ var Tokenizer = class {
871
1029
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
872
1030
  const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
873
1031
  for (let k = li + 1; k < this.lineCount; k++) {
1032
+ if (this.isOpenTail(k)) continue;
874
1033
  if (close.test(this.lineText(k))) {
875
1034
  return this.island("fence", p, this.contentEnds[k], true, meta);
876
1035
  }
@@ -1260,6 +1419,21 @@ var Tokenizer = class {
1260
1419
  i++;
1261
1420
  continue;
1262
1421
  }
1422
+ if (ch === "[" && text[i + 1] === "[" || ch === "!" && text[i + 1] === "[" && text[i + 2] === "[") {
1423
+ const open = ch === "!" ? i + 3 : i + 2;
1424
+ const close = within("]]", open);
1425
+ const newline = this.next("\n", open);
1426
+ const inner = close === -1 ? "" : text.slice(open, close);
1427
+ if (close !== -1 && (newline === -1 || newline > close) && inner.trim() && !/[[\]]/.test(inner)) {
1428
+ const cut = inner.search(/\\?\|/);
1429
+ const target = (cut < 0 ? inner : inner.slice(0, cut)).trim();
1430
+ inlines.push(
1431
+ this.island("wikilink", i, close + 2, true, ch === "!" ? { target, embed: true } : { target })
1432
+ );
1433
+ i = close + 2;
1434
+ continue;
1435
+ }
1436
+ }
1263
1437
  if (ch === "[") {
1264
1438
  const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
1265
1439
  if (media) {
@@ -1341,8 +1515,8 @@ var Tokenizer = class {
1341
1515
  return { inlines, breakIsland: null, orphan: false };
1342
1516
  }
1343
1517
  };
1344
- function tokenizeSource(text) {
1345
- const raw = new Tokenizer(text).run();
1518
+ function tokenizeSource(text, options = {}) {
1519
+ const raw = new Tokenizer(text, options.streaming ?? false).run();
1346
1520
  const cp = buildCodePointIndex(text);
1347
1521
  const inline = (island) => ({
1348
1522
  islandType: island.type,
@@ -1391,7 +1565,7 @@ function listIslands(blocks) {
1391
1565
  raw: block.raw
1392
1566
  });
1393
1567
  } else {
1394
- out.push(...block.inlines);
1568
+ for (const inline of block.inlines) out.push(inline);
1395
1569
  }
1396
1570
  }
1397
1571
  return out;
@@ -1427,15 +1601,37 @@ function islandEdit(island, text) {
1427
1601
  function blockRangeEdit(first, last, text) {
1428
1602
  return { start: first.start, end: last.end, text };
1429
1603
  }
1430
- function locateIsland(text, raw, from, expected, expectedFromEnd) {
1431
- if (expected >= from && text.startsWith(raw, expected)) return expected;
1432
- if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) return expectedFromEnd;
1604
+ var CONTEXT_WINDOW = 64;
1605
+ function locateIsland(text, raw, from, expected, expectedFromEnd, oldText, oldStart, oldEnd) {
1606
+ const candidates = /* @__PURE__ */ new Set();
1607
+ if (expected >= from && text.startsWith(raw, expected)) candidates.add(expected);
1608
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) candidates.add(expectedFromEnd);
1433
1609
  const after = text.indexOf(raw, Math.max(from, expected));
1610
+ if (after !== -1) candidates.add(after);
1434
1611
  const before = expected > from ? text.lastIndexOf(raw, expected) : -1;
1435
- const beforeOk = before >= from ? before : -1;
1436
- if (after === -1) return beforeOk;
1437
- if (beforeOk === -1) return after;
1438
- return expected - beforeOk <= after - expected ? beforeOk : after;
1612
+ if (before >= from) candidates.add(before);
1613
+ if (candidates.size === 0) return text.indexOf(raw, from);
1614
+ if (candidates.size === 1) return candidates.values().next().value;
1615
+ let best = -1;
1616
+ let bestScore = -1;
1617
+ for (const at of candidates) {
1618
+ let score = 0;
1619
+ for (let k = 0; k < CONTEXT_WINDOW; k++) {
1620
+ const n = text[at + raw.length + k];
1621
+ if (n === void 0 || n !== oldText[oldEnd + k]) break;
1622
+ score++;
1623
+ }
1624
+ for (let k = 1; k <= CONTEXT_WINDOW; k++) {
1625
+ const n = text[at - k];
1626
+ if (n === void 0 || n !== oldText[oldStart - k]) break;
1627
+ score++;
1628
+ }
1629
+ if (score > bestScore || score === bestScore && at === expected) {
1630
+ best = at;
1631
+ bestScore = score;
1632
+ }
1633
+ }
1634
+ return best;
1439
1635
  }
1440
1636
  function commonPrefix(a, b) {
1441
1637
  const max = Math.min(a.length, b.length);
@@ -1512,7 +1708,16 @@ function spliceSave(original, edits, options = {}) {
1512
1708
  let oldCursor2 = edit.start;
1513
1709
  let newCursor2 = 0;
1514
1710
  for (const island of inside) {
1515
- const at = locateIsland(edit.text, island.raw, newCursor2, newCursor2 + (island.start - oldCursor2), edit.text.length - (edit.end - island.start));
1711
+ const at = locateIsland(
1712
+ edit.text,
1713
+ island.raw,
1714
+ newCursor2,
1715
+ newCursor2 + (island.start - oldCursor2),
1716
+ edit.text.length - (edit.end - island.start),
1717
+ original.slice(edit.start, edit.end),
1718
+ island.start - edit.start,
1719
+ island.end - edit.start
1720
+ );
1516
1721
  if (at === -1) {
1517
1722
  const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
1518
1723
  throw new SourceSpliceError(
@@ -1650,6 +1855,7 @@ function mapRange(changes, start, end, unit = "utf16") {
1650
1855
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
1651
1856
  }
1652
1857
 
1858
+ exports.FENCE_WHITESPACE = FENCE_WHITESPACE;
1653
1859
  exports.NESTING_FENCE_LANGUAGES = NESTING_FENCE_LANGUAGES;
1654
1860
  exports.SourceSpliceError = SourceSpliceError;
1655
1861
  exports.XmlContainerTracker = XmlContainerTracker;
@@ -1674,5 +1880,6 @@ exports.splitsSurrogatePair = splitsSurrogatePair;
1674
1880
  exports.toCodePointOffset = toCodePointOffset;
1675
1881
  exports.toUtf16Offset = toUtf16Offset;
1676
1882
  exports.tokenizeSource = tokenizeSource;
1883
+ exports.trimFenceLine = trimFenceLine;
1677
1884
  //# sourceMappingURL=source.cjs.map
1678
1885
  //# sourceMappingURL=source.cjs.map