@ai-matrx/content-ir 0.13.0 → 0.14.1

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.d.cts CHANGED
@@ -51,12 +51,25 @@ type BlockIslandType =
51
51
  | "anchor"
52
52
  /** `$$ … $$` display math on its own lines. */
53
53
  | "math_block"
54
- /** YAML front matter at the start of the text. */
54
+ /** YAML (`---`) or TOML (`+++`) front matter at the start of the text (meta.format). */
55
55
  | "front_matter"
56
56
  /** A box-drawing tree diagram. */
57
- | "tree";
57
+ | "tree"
58
+ /**
59
+ * A generic-directive container `:::name[label]{attrs}` … `:::` (tabs,
60
+ * columns, details, figures, callouts, TOC — meta.name), a leaf directive
61
+ * `::name` on its own line (meta.leaf), or a MkDocs admonition
62
+ * `!!! type "Title"` / `??? type` with its indented body (meta.syntax "mkdocs").
63
+ */
64
+ | "directive"
65
+ /** A GitHub alert / Obsidian callout: a blockquote opening `> [!TYPE]` (meta.name). */
66
+ | "callout"
67
+ /** A footnote definition `[^id]: …` with its indented continuation lines (meta.id). */
68
+ | "footnote_def";
58
69
  /** Island types that live inside a prose block. */
59
- type InlineIslandType = "variable" | "cite" | "math_inline" | "html_tag" | "xml_tag" | "xml_inline" | "html_comment" | "anchor" | "kind_json" | "media_ref";
70
+ type InlineIslandType = "variable" | "cite" | "math_inline" | "html_tag" | "xml_tag" | "xml_inline" | "html_comment" | "anchor" | "kind_json" | "media_ref"
71
+ /** `[[Page]]`, `[[Page|alias]]` (meta.target) — or `![[Page]]` (meta.embed). */
72
+ | "wikilink";
60
73
  type IslandType = BlockIslandType | InlineIslandType;
61
74
  type IslandMeta = Readonly<Record<string, string | number | boolean>>;
62
75
  interface SourceSpan {
@@ -92,7 +105,16 @@ interface SourceBlock extends SourceSpan {
92
105
  * Cut `text` into blocks whose raw concatenation is exactly `text`.
93
106
  * Never throws; unclosed constructs are islands with `complete: false`.
94
107
  */
95
- declare function tokenizeSource(text: string): SourceBlock[];
108
+ interface TokenizeOptions {
109
+ /**
110
+ * The text is a prefix of a message still arriving. The final line (no
111
+ * terminator yet) can still grow — a bare ``` there may become ```bash — so
112
+ * it closes no fence until its newline arrives (verify-RC-B3 residual R2).
113
+ * Omit for stored text: a complete document's last line is final.
114
+ */
115
+ readonly streaming?: boolean;
116
+ }
117
+ declare function tokenizeSource(text: string, options?: TokenizeOptions): SourceBlock[];
96
118
  /** The exact source the blocks were cut from. */
97
119
  declare function joinSource(blocks: readonly SourceBlock[]): string;
98
120
  /** Every island — block-level and inline — in document order. */
@@ -272,8 +294,9 @@ declare function toUtf16Offset(text: string, codePoint: number): number;
272
294
  *
273
295
  * Pandoc's rule — the opening `$` is not followed by whitespace, the closing
274
296
  * `$` is not preceded by whitespace and not followed by a digit — plus a math
275
- * signal: a TeX command or operator (`\alpha`, `^`, `_`, `{`, `=`, …), or a
276
- * lone variable (`$x$`, `$n$`, `$x'$`). Content that opens with a digit needs
297
+ * signal: a TeX command or operator (`\alpha`, `^`, `_`, `{`, `=`, …), a
298
+ * lone variable (`$x$`, `$n$`, `$x'$`), or a single-letter math shape
299
+ * (`$f(g(x))$`, `$u v$`, `$f'(x)$` — see `isMathShape`). Content that opens with a digit needs
277
300
  * a real TeX signal, so a price range can never read as math.
278
301
  */
279
302
  declare function isSingleDollarMath(content: string, after: string | undefined): boolean;
@@ -342,13 +365,26 @@ type InnerFenceLine =
342
365
  /** Bare ``` that closes the OUTER fence. */
343
366
  | "close-outer";
344
367
  /**
345
- * Classify one TRIMMED line inside an open backtick fence.
368
+ * Classify one line inside an open backtick fence, already trimmed with
369
+ * {@link trimFenceLine} (never a language's own `trim()`).
346
370
  *
347
371
  * @param trimmed the line with surrounding whitespace removed
348
372
  * @param openTicks backtick count of the outer fence's opener
349
373
  * @param nests whether the outer fence follows the nesting rule
350
374
  * @param nestedDepth how many nested fences are open right now
351
375
  */
376
+ /**
377
+ * The whitespace a fence line may carry around its backticks and info string:
378
+ * CommonMark's ASCII whitespace — space, tab, LF, CR, form feed, vertical tab.
379
+ * Defined HERE, not by a language's `trim()`: JavaScript's `trim()` also strips
380
+ * U+FEFF, U+00A0 and the Unicode spaces, while Python's `strip()` also strips
381
+ * U+0085 and U+001C–U+001F — which made the two twins disagree on lines such
382
+ * as ```` ```\uFEFF ```` (verify-RC-B3 residual R4). Every caller trims a line
383
+ * with `trimFenceLine` before classifying it; the Python twin mirrors both.
384
+ */
385
+ declare const FENCE_WHITESPACE = " \t\n\r\f\v";
386
+ /** Strip {@link FENCE_WHITESPACE} (and nothing else) from both ends of a line. */
387
+ declare function trimFenceLine(line: string): string;
352
388
  declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nests: boolean, nestedDepth: number): InnerFenceLine;
353
389
 
354
390
  /**
@@ -371,4 +407,4 @@ declare function looksLikeDisplayMath(inner: string): boolean;
371
407
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
372
408
  declare function pairDisplayMath(text: string): Map<number, number>;
373
409
 
374
- export { type BlockIslandType, type CodePointIndex, type DisturbedIsland, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource };
410
+ export { type BlockIslandType, type CodePointIndex, type DisturbedIsland, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.d.ts CHANGED
@@ -51,12 +51,25 @@ type BlockIslandType =
51
51
  | "anchor"
52
52
  /** `$$ … $$` display math on its own lines. */
53
53
  | "math_block"
54
- /** YAML front matter at the start of the text. */
54
+ /** YAML (`---`) or TOML (`+++`) front matter at the start of the text (meta.format). */
55
55
  | "front_matter"
56
56
  /** A box-drawing tree diagram. */
57
- | "tree";
57
+ | "tree"
58
+ /**
59
+ * A generic-directive container `:::name[label]{attrs}` … `:::` (tabs,
60
+ * columns, details, figures, callouts, TOC — meta.name), a leaf directive
61
+ * `::name` on its own line (meta.leaf), or a MkDocs admonition
62
+ * `!!! type "Title"` / `??? type` with its indented body (meta.syntax "mkdocs").
63
+ */
64
+ | "directive"
65
+ /** A GitHub alert / Obsidian callout: a blockquote opening `> [!TYPE]` (meta.name). */
66
+ | "callout"
67
+ /** A footnote definition `[^id]: …` with its indented continuation lines (meta.id). */
68
+ | "footnote_def";
58
69
  /** Island types that live inside a prose block. */
59
- type InlineIslandType = "variable" | "cite" | "math_inline" | "html_tag" | "xml_tag" | "xml_inline" | "html_comment" | "anchor" | "kind_json" | "media_ref";
70
+ type InlineIslandType = "variable" | "cite" | "math_inline" | "html_tag" | "xml_tag" | "xml_inline" | "html_comment" | "anchor" | "kind_json" | "media_ref"
71
+ /** `[[Page]]`, `[[Page|alias]]` (meta.target) — or `![[Page]]` (meta.embed). */
72
+ | "wikilink";
60
73
  type IslandType = BlockIslandType | InlineIslandType;
61
74
  type IslandMeta = Readonly<Record<string, string | number | boolean>>;
62
75
  interface SourceSpan {
@@ -92,7 +105,16 @@ interface SourceBlock extends SourceSpan {
92
105
  * Cut `text` into blocks whose raw concatenation is exactly `text`.
93
106
  * Never throws; unclosed constructs are islands with `complete: false`.
94
107
  */
95
- declare function tokenizeSource(text: string): SourceBlock[];
108
+ interface TokenizeOptions {
109
+ /**
110
+ * The text is a prefix of a message still arriving. The final line (no
111
+ * terminator yet) can still grow — a bare ``` there may become ```bash — so
112
+ * it closes no fence until its newline arrives (verify-RC-B3 residual R2).
113
+ * Omit for stored text: a complete document's last line is final.
114
+ */
115
+ readonly streaming?: boolean;
116
+ }
117
+ declare function tokenizeSource(text: string, options?: TokenizeOptions): SourceBlock[];
96
118
  /** The exact source the blocks were cut from. */
97
119
  declare function joinSource(blocks: readonly SourceBlock[]): string;
98
120
  /** Every island — block-level and inline — in document order. */
@@ -272,8 +294,9 @@ declare function toUtf16Offset(text: string, codePoint: number): number;
272
294
  *
273
295
  * Pandoc's rule — the opening `$` is not followed by whitespace, the closing
274
296
  * `$` is not preceded by whitespace and not followed by a digit — plus a math
275
- * signal: a TeX command or operator (`\alpha`, `^`, `_`, `{`, `=`, …), or a
276
- * lone variable (`$x$`, `$n$`, `$x'$`). Content that opens with a digit needs
297
+ * signal: a TeX command or operator (`\alpha`, `^`, `_`, `{`, `=`, …), a
298
+ * lone variable (`$x$`, `$n$`, `$x'$`), or a single-letter math shape
299
+ * (`$f(g(x))$`, `$u v$`, `$f'(x)$` — see `isMathShape`). Content that opens with a digit needs
277
300
  * a real TeX signal, so a price range can never read as math.
278
301
  */
279
302
  declare function isSingleDollarMath(content: string, after: string | undefined): boolean;
@@ -342,13 +365,26 @@ type InnerFenceLine =
342
365
  /** Bare ``` that closes the OUTER fence. */
343
366
  | "close-outer";
344
367
  /**
345
- * Classify one TRIMMED line inside an open backtick fence.
368
+ * Classify one line inside an open backtick fence, already trimmed with
369
+ * {@link trimFenceLine} (never a language's own `trim()`).
346
370
  *
347
371
  * @param trimmed the line with surrounding whitespace removed
348
372
  * @param openTicks backtick count of the outer fence's opener
349
373
  * @param nests whether the outer fence follows the nesting rule
350
374
  * @param nestedDepth how many nested fences are open right now
351
375
  */
376
+ /**
377
+ * The whitespace a fence line may carry around its backticks and info string:
378
+ * CommonMark's ASCII whitespace — space, tab, LF, CR, form feed, vertical tab.
379
+ * Defined HERE, not by a language's `trim()`: JavaScript's `trim()` also strips
380
+ * U+FEFF, U+00A0 and the Unicode spaces, while Python's `strip()` also strips
381
+ * U+0085 and U+001C–U+001F — which made the two twins disagree on lines such
382
+ * as ```` ```\uFEFF ```` (verify-RC-B3 residual R4). Every caller trims a line
383
+ * with `trimFenceLine` before classifying it; the Python twin mirrors both.
384
+ */
385
+ declare const FENCE_WHITESPACE = " \t\n\r\f\v";
386
+ /** Strip {@link FENCE_WHITESPACE} (and nothing else) from both ends of a line. */
387
+ declare function trimFenceLine(line: string): string;
352
388
  declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nests: boolean, nestedDepth: number): InnerFenceLine;
353
389
 
354
390
  /**
@@ -371,4 +407,4 @@ declare function looksLikeDisplayMath(inner: string): boolean;
371
407
  /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
372
408
  declare function pairDisplayMath(text: string): Map<number, number>;
373
409
 
374
- export { type BlockIslandType, type CodePointIndex, type DisturbedIsland, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource };
410
+ export { type BlockIslandType, type CodePointIndex, type DisturbedIsland, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.js CHANGED
@@ -209,11 +209,22 @@ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
209
209
  function fenceNestsInnerFences(language) {
210
210
  return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
211
211
  }
212
+ var FENCE_WHITESPACE = " \n\r\f\v";
213
+ function isFenceWhitespace(ch) {
214
+ return ch !== void 0 && ch !== "" && FENCE_WHITESPACE.includes(ch);
215
+ }
216
+ function trimFenceLine(line) {
217
+ let start = 0;
218
+ let end = line.length;
219
+ while (start < end && isFenceWhitespace(line[start])) start++;
220
+ while (end > start && isFenceWhitespace(line[end - 1])) end--;
221
+ return start === 0 && end === line.length ? line : line.slice(start, end);
222
+ }
212
223
  function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
213
224
  let ticks = 0;
214
225
  while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
215
226
  if (ticks < 3) return "content";
216
- const info = trimmed.slice(ticks).trim();
227
+ const info = trimFenceLine(trimmed.slice(ticks));
217
228
  if (info === "") {
218
229
  if (ticks < openTicks) return "content";
219
230
  if (nests && nestedDepth > 0) return "close-nested";
@@ -226,13 +237,43 @@ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
226
237
  // source/math.ts
227
238
  var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
228
239
  var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
240
+ var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
241
+ var FUNCTION_NAMES = /* @__PURE__ */ new Set([
242
+ "sin",
243
+ "cos",
244
+ "tan",
245
+ "sec",
246
+ "csc",
247
+ "cot",
248
+ "log",
249
+ "ln",
250
+ "exp",
251
+ "lim",
252
+ "max",
253
+ "min",
254
+ "det",
255
+ "gcd",
256
+ "mod",
257
+ "arg",
258
+ "sinh",
259
+ "cosh",
260
+ "tanh"
261
+ ]);
262
+ function isMathShape(content) {
263
+ if (!MATH_SHAPE_CHARS.test(content)) return false;
264
+ const words = content.match(/[A-Za-z]+/g);
265
+ if (!words) return false;
266
+ if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
267
+ if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
268
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
269
+ }
229
270
  function isSingleDollarMath(content, after) {
230
271
  if (!content || content.length > 400) return false;
231
272
  if (/^\s/.test(content) || /\s$/.test(content)) return false;
232
273
  if (after !== void 0 && /[0-9]/.test(after)) return false;
233
274
  if (LONE_VARIABLE.test(content)) return true;
234
275
  if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
235
- return TEX_SIGNAL.test(content);
276
+ return TEX_SIGNAL.test(content) || isMathShape(content);
236
277
  }
237
278
  function singleDollarMathEnd(text, open, limit = text.length) {
238
279
  const bound = Math.min(limit, open + 403);
@@ -468,6 +509,10 @@ var XmlContainerTracker = class {
468
509
 
469
510
  // source/tokenize.ts
470
511
  var NOT_WHITESPACE = /\S/;
512
+ var DIRECTIVE_OPEN = /^(:{2,})([A-Za-z][\w-]*)/;
513
+ var MKDOCS_OPEN = /^(!!!|\?\?\?\+?)[ \t]+([A-Za-z][\w-]*)/;
514
+ var CALLOUT_OPEN = /^>[ \t]*\[!([A-Za-z][\w-]*)\]([+-])?/;
515
+ var FOOTNOTE_DEF = /^\[\^([^\]\s]+)\]:/;
471
516
  function countStructuralBraces(source) {
472
517
  let opens = 0;
473
518
  let closes = 0;
@@ -595,8 +640,9 @@ function isSplitterTreeLine(line) {
595
640
  }
596
641
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
597
642
  var Tokenizer = class {
598
- constructor(text) {
643
+ constructor(text, streaming = false) {
599
644
  this.text = text;
645
+ this.streaming = streaming;
600
646
  this.n = text.length;
601
647
  this.mathPairs = pairDisplayMath(text);
602
648
  let start = 0;
@@ -615,6 +661,7 @@ var Tokenizer = class {
615
661
  }
616
662
  }
617
663
  text;
664
+ streaming;
618
665
  n;
619
666
  lineStarts = [];
620
667
  contentEnds = [];
@@ -652,6 +699,10 @@ var Tokenizer = class {
652
699
  }
653
700
  return lo < list.length ? list[lo] : -1;
654
701
  }
702
+ /** Streaming only: the final line, still waiting for its terminator. */
703
+ isOpenTail(k) {
704
+ return this.streaming && k === this.lineStarts.length - 1 && this.lineEnds[k] === this.contentEnds[k];
705
+ }
655
706
  get lineCount() {
656
707
  return this.lineStarts.length;
657
708
  }
@@ -733,6 +784,22 @@ var Tokenizer = class {
733
784
  const fm = this.frontMatter(li, t);
734
785
  if (fm) return fm;
735
786
  }
787
+ if (indent <= 3 && t.startsWith("::")) {
788
+ const directive = this.directive(p, li, t);
789
+ if (directive) return directive;
790
+ }
791
+ if (indent === 0 && (t.startsWith("!!!") || t.startsWith("???"))) {
792
+ const admonition = this.mkdocsAdmonition(p, li, t);
793
+ if (admonition) return admonition;
794
+ }
795
+ if (indent <= 3 && t.startsWith(">")) {
796
+ const callout = this.callout(p, li, t);
797
+ if (callout) return callout;
798
+ }
799
+ if (atParaStart && indent <= 3 && t.startsWith("[^")) {
800
+ const footnote = this.footnoteDefinition(p, li, t);
801
+ if (footnote) return footnote;
802
+ }
736
803
  if (t.startsWith("```")) return this.backtickFence(p, li, t);
737
804
  if (indent <= 3 && t.startsWith("~~~")) {
738
805
  const tilde = this.tildeFence(p, li, t);
@@ -770,19 +837,110 @@ var Tokenizer = class {
770
837
  return { type, start, end, complete, meta };
771
838
  }
772
839
  frontMatter(li, t) {
773
- if (this.lineText(li) !== "---" || t !== "---") return null;
840
+ const fence = this.lineText(li);
841
+ if (fence !== "---" && fence !== "+++" || t !== fence) return null;
842
+ const toml = fence === "+++";
774
843
  let sawContent = false;
775
844
  for (let k = li + 1; k < this.lineCount; k++) {
776
845
  const line = this.lineText(k);
777
- if (line === "---" || line === "...") {
778
- return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true) : null;
846
+ if (line === fence || !toml && line === "...") {
847
+ return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true, { format: toml ? "toml" : "yaml" }) : null;
779
848
  }
780
849
  if (!NOT_WHITESPACE.test(line)) continue;
781
- if (!/^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s)/.test(line)) return null;
850
+ const shaped = toml ? /^\s*(?:[A-Za-z0-9_."-]+\s*=|\[|#)/.test(line) : /^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s|#)/.test(line);
851
+ if (!shaped) return null;
782
852
  sawContent = true;
783
853
  }
784
854
  return null;
785
855
  }
856
+ /**
857
+ * `:::name[label]{attrs}` … closing `:::` (a colon run at least as long as
858
+ * the opener's, nesting counted), or a leaf `::name[…]{…}` line. The whole
859
+ * container is ONE island: its grammar (fence lengths, labels, attributes,
860
+ * nested tabs) is exactly what a visual editor would re-serialize wrongly.
861
+ */
862
+ directive(p, li, t) {
863
+ const open = DIRECTIVE_OPEN.exec(t);
864
+ if (!open) return null;
865
+ const colons = open[1].length;
866
+ const name = open[2];
867
+ if (colons === 2) {
868
+ return this.island("directive", p, this.contentEnds[li], true, { name, leaf: true });
869
+ }
870
+ const stack = [colons];
871
+ for (let k = li + 1; k < this.lineCount; k++) {
872
+ const line = this.lineText(k).trim();
873
+ const inner = DIRECTIVE_OPEN.exec(line);
874
+ if (inner && inner[1].length >= 3) {
875
+ stack.push(inner[1].length);
876
+ continue;
877
+ }
878
+ const close = /^(:{3,})\s*$/.exec(line);
879
+ if (close && close[1].length >= stack[stack.length - 1]) {
880
+ stack.pop();
881
+ if (stack.length === 0) return this.island("directive", p, this.contentEnds[k], true, { name });
882
+ }
883
+ }
884
+ return this.island("directive", p, this.n, false, { name });
885
+ }
886
+ /** MkDocs `!!! type "Title"` / `??? type` / `???+ type` and its indented (or blank-separated indented) body. */
887
+ mkdocsAdmonition(p, li, t) {
888
+ const open = MKDOCS_OPEN.exec(t);
889
+ if (!open) return null;
890
+ let last = li;
891
+ for (let k = li + 1; k < this.lineCount; k++) {
892
+ const line = this.lineText(k);
893
+ if (/^(?: {4}|\t)/.test(line) && NOT_WHITESPACE.test(line)) {
894
+ last = k;
895
+ continue;
896
+ }
897
+ if (!NOT_WHITESPACE.test(line)) continue;
898
+ break;
899
+ }
900
+ return this.island("directive", p, this.contentEnds[last], true, {
901
+ name: open[2].toLowerCase(),
902
+ syntax: "mkdocs"
903
+ });
904
+ }
905
+ /** A blockquote whose first line is `> [!TYPE]…` — every following `>` line belongs to it. */
906
+ callout(p, li, t) {
907
+ const open = CALLOUT_OPEN.exec(t);
908
+ if (!open) return null;
909
+ let last = li;
910
+ for (let k = li + 1; k < this.lineCount; k++) {
911
+ if (!/^ {0,3}>/.test(this.lineText(k))) break;
912
+ last = k;
913
+ }
914
+ return this.island("callout", p, this.contentEnds[last], true, {
915
+ name: open[1].toLowerCase(),
916
+ ...open[2] ? { fold: open[2] === "-" ? "closed" : "open" } : {}
917
+ });
918
+ }
919
+ /** `[^id]: text` plus its continuation lines (indented, or lazy text before a blank line). */
920
+ footnoteDefinition(p, li, t) {
921
+ const open = FOOTNOTE_DEF.exec(t);
922
+ if (!open) return null;
923
+ let last = li;
924
+ for (let k = li + 1; k < this.lineCount; k++) {
925
+ const line = this.lineText(k);
926
+ if (!NOT_WHITESPACE.test(line)) {
927
+ let j = k + 1;
928
+ while (j < this.lineCount && !NOT_WHITESPACE.test(this.lineText(j))) j++;
929
+ if (j < this.lineCount && /^(?: {4}|\t)/.test(this.lineText(j))) {
930
+ k = j - 1;
931
+ continue;
932
+ }
933
+ break;
934
+ }
935
+ if (/^(?: {4}|\t)/.test(line)) {
936
+ last = k;
937
+ continue;
938
+ }
939
+ if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
940
+ last = k;
941
+ }
942
+ return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
943
+ }
786
944
  /**
787
945
  * Backtick fence — the renderer splitter's close rules: JSON-string aware for
788
946
  * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
@@ -811,8 +969,9 @@ var Tokenizer = class {
811
969
  let nestedDepth = 0;
812
970
  let sawNested = false;
813
971
  for (let k = li + 1; k < this.lineCount; k++) {
972
+ if (this.isOpenTail(k)) continue;
814
973
  const line = this.lineText(k);
815
- const trimmedLine = line.trim();
974
+ const trimmedLine = trimFenceLine(line);
816
975
  if (trimmedLine.startsWith("```")) {
817
976
  const at2 = line.indexOf("```");
818
977
  if (isJson) {
@@ -842,7 +1001,7 @@ var Tokenizer = class {
842
1001
  }
843
1002
  }
844
1003
  const closeTicks = backtickRunLength(line, at);
845
- const after = line.slice(at + closeTicks).trim();
1004
+ const after = trimFenceLine(line.slice(at + closeTicks));
846
1005
  if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
847
1006
  if (isJson) state = jsonStringState(line, 0, line.length, state);
848
1007
  continue;
@@ -869,6 +1028,7 @@ var Tokenizer = class {
869
1028
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
870
1029
  const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
871
1030
  for (let k = li + 1; k < this.lineCount; k++) {
1031
+ if (this.isOpenTail(k)) continue;
872
1032
  if (close.test(this.lineText(k))) {
873
1033
  return this.island("fence", p, this.contentEnds[k], true, meta);
874
1034
  }
@@ -1258,6 +1418,21 @@ var Tokenizer = class {
1258
1418
  i++;
1259
1419
  continue;
1260
1420
  }
1421
+ if (ch === "[" && text[i + 1] === "[" || ch === "!" && text[i + 1] === "[" && text[i + 2] === "[") {
1422
+ const open = ch === "!" ? i + 3 : i + 2;
1423
+ const close = within("]]", open);
1424
+ const newline = this.next("\n", open);
1425
+ const inner = close === -1 ? "" : text.slice(open, close);
1426
+ if (close !== -1 && (newline === -1 || newline > close) && inner.trim() && !/[[\]]/.test(inner)) {
1427
+ const cut = inner.search(/\\?\|/);
1428
+ const target = (cut < 0 ? inner : inner.slice(0, cut)).trim();
1429
+ inlines.push(
1430
+ this.island("wikilink", i, close + 2, true, ch === "!" ? { target, embed: true } : { target })
1431
+ );
1432
+ i = close + 2;
1433
+ continue;
1434
+ }
1435
+ }
1261
1436
  if (ch === "[") {
1262
1437
  const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
1263
1438
  if (media) {
@@ -1339,8 +1514,8 @@ var Tokenizer = class {
1339
1514
  return { inlines, breakIsland: null, orphan: false };
1340
1515
  }
1341
1516
  };
1342
- function tokenizeSource(text) {
1343
- const raw = new Tokenizer(text).run();
1517
+ function tokenizeSource(text, options = {}) {
1518
+ const raw = new Tokenizer(text, options.streaming ?? false).run();
1344
1519
  const cp = buildCodePointIndex(text);
1345
1520
  const inline = (island) => ({
1346
1521
  islandType: island.type,
@@ -1389,7 +1564,7 @@ function listIslands(blocks) {
1389
1564
  raw: block.raw
1390
1565
  });
1391
1566
  } else {
1392
- out.push(...block.inlines);
1567
+ for (const inline of block.inlines) out.push(inline);
1393
1568
  }
1394
1569
  }
1395
1570
  return out;
@@ -1425,15 +1600,37 @@ function islandEdit(island, text) {
1425
1600
  function blockRangeEdit(first, last, text) {
1426
1601
  return { start: first.start, end: last.end, text };
1427
1602
  }
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;
1603
+ var CONTEXT_WINDOW = 64;
1604
+ function locateIsland(text, raw, from, expected, expectedFromEnd, oldText, oldStart, oldEnd) {
1605
+ const candidates = /* @__PURE__ */ new Set();
1606
+ if (expected >= from && text.startsWith(raw, expected)) candidates.add(expected);
1607
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) candidates.add(expectedFromEnd);
1431
1608
  const after = text.indexOf(raw, Math.max(from, expected));
1609
+ if (after !== -1) candidates.add(after);
1432
1610
  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;
1611
+ if (before >= from) candidates.add(before);
1612
+ if (candidates.size === 0) return text.indexOf(raw, from);
1613
+ if (candidates.size === 1) return candidates.values().next().value;
1614
+ let best = -1;
1615
+ let bestScore = -1;
1616
+ for (const at of candidates) {
1617
+ let score = 0;
1618
+ for (let k = 0; k < CONTEXT_WINDOW; k++) {
1619
+ const n = text[at + raw.length + k];
1620
+ if (n === void 0 || n !== oldText[oldEnd + k]) break;
1621
+ score++;
1622
+ }
1623
+ for (let k = 1; k <= CONTEXT_WINDOW; k++) {
1624
+ const n = text[at - k];
1625
+ if (n === void 0 || n !== oldText[oldStart - k]) break;
1626
+ score++;
1627
+ }
1628
+ if (score > bestScore || score === bestScore && at === expected) {
1629
+ best = at;
1630
+ bestScore = score;
1631
+ }
1632
+ }
1633
+ return best;
1437
1634
  }
1438
1635
  function commonPrefix(a, b) {
1439
1636
  const max = Math.min(a.length, b.length);
@@ -1510,7 +1707,16 @@ function spliceSave(original, edits, options = {}) {
1510
1707
  let oldCursor2 = edit.start;
1511
1708
  let newCursor2 = 0;
1512
1709
  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));
1710
+ const at = locateIsland(
1711
+ edit.text,
1712
+ island.raw,
1713
+ newCursor2,
1714
+ newCursor2 + (island.start - oldCursor2),
1715
+ edit.text.length - (edit.end - island.start),
1716
+ original.slice(edit.start, edit.end),
1717
+ island.start - edit.start,
1718
+ island.end - edit.start
1719
+ );
1514
1720
  if (at === -1) {
1515
1721
  const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
1516
1722
  throw new SourceSpliceError(
@@ -1648,6 +1854,6 @@ function mapRange(changes, start, end, unit = "utf16") {
1648
1854
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
1649
1855
  }
1650
1856
 
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 };
1857
+ export { FENCE_WHITESPACE, 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, trimFenceLine };
1652
1858
  //# sourceMappingURL=source.js.map
1653
1859
  //# sourceMappingURL=source.js.map