@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.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,42 @@ 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
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
268
+ }
229
269
  function isSingleDollarMath(content, after) {
230
270
  if (!content || content.length > 400) return false;
231
271
  if (/^\s/.test(content) || /\s$/.test(content)) return false;
232
272
  if (after !== void 0 && /[0-9]/.test(after)) return false;
233
273
  if (LONE_VARIABLE.test(content)) return true;
234
274
  if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
235
- return TEX_SIGNAL.test(content);
275
+ return TEX_SIGNAL.test(content) || isMathShape(content);
236
276
  }
237
277
  function singleDollarMathEnd(text, open, limit = text.length) {
238
278
  const bound = Math.min(limit, open + 403);
@@ -468,6 +508,10 @@ var XmlContainerTracker = class {
468
508
 
469
509
  // source/tokenize.ts
470
510
  var NOT_WHITESPACE = /\S/;
511
+ var DIRECTIVE_OPEN = /^(:{2,})([A-Za-z][\w-]*)/;
512
+ var MKDOCS_OPEN = /^(!!!|\?\?\?\+?)[ \t]+([A-Za-z][\w-]*)/;
513
+ var CALLOUT_OPEN = /^>[ \t]*\[!([A-Za-z][\w-]*)\]([+-])?/;
514
+ var FOOTNOTE_DEF = /^\[\^([^\]\s]+)\]:/;
471
515
  function countStructuralBraces(source) {
472
516
  let opens = 0;
473
517
  let closes = 0;
@@ -595,8 +639,9 @@ function isSplitterTreeLine(line) {
595
639
  }
596
640
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
597
641
  var Tokenizer = class {
598
- constructor(text) {
642
+ constructor(text, streaming = false) {
599
643
  this.text = text;
644
+ this.streaming = streaming;
600
645
  this.n = text.length;
601
646
  this.mathPairs = pairDisplayMath(text);
602
647
  let start = 0;
@@ -615,6 +660,7 @@ var Tokenizer = class {
615
660
  }
616
661
  }
617
662
  text;
663
+ streaming;
618
664
  n;
619
665
  lineStarts = [];
620
666
  contentEnds = [];
@@ -652,6 +698,10 @@ var Tokenizer = class {
652
698
  }
653
699
  return lo < list.length ? list[lo] : -1;
654
700
  }
701
+ /** Streaming only: the final line, still waiting for its terminator. */
702
+ isOpenTail(k) {
703
+ return this.streaming && k === this.lineStarts.length - 1 && this.lineEnds[k] === this.contentEnds[k];
704
+ }
655
705
  get lineCount() {
656
706
  return this.lineStarts.length;
657
707
  }
@@ -733,6 +783,22 @@ var Tokenizer = class {
733
783
  const fm = this.frontMatter(li, t);
734
784
  if (fm) return fm;
735
785
  }
786
+ if (indent <= 3 && t.startsWith("::")) {
787
+ const directive = this.directive(p, li, t);
788
+ if (directive) return directive;
789
+ }
790
+ if (indent === 0 && (t.startsWith("!!!") || t.startsWith("???"))) {
791
+ const admonition = this.mkdocsAdmonition(p, li, t);
792
+ if (admonition) return admonition;
793
+ }
794
+ if (indent <= 3 && t.startsWith(">")) {
795
+ const callout = this.callout(p, li, t);
796
+ if (callout) return callout;
797
+ }
798
+ if (atParaStart && indent <= 3 && t.startsWith("[^")) {
799
+ const footnote = this.footnoteDefinition(p, li, t);
800
+ if (footnote) return footnote;
801
+ }
736
802
  if (t.startsWith("```")) return this.backtickFence(p, li, t);
737
803
  if (indent <= 3 && t.startsWith("~~~")) {
738
804
  const tilde = this.tildeFence(p, li, t);
@@ -770,19 +836,110 @@ var Tokenizer = class {
770
836
  return { type, start, end, complete, meta };
771
837
  }
772
838
  frontMatter(li, t) {
773
- if (this.lineText(li) !== "---" || t !== "---") return null;
839
+ const fence = this.lineText(li);
840
+ if (fence !== "---" && fence !== "+++" || t !== fence) return null;
841
+ const toml = fence === "+++";
774
842
  let sawContent = false;
775
843
  for (let k = li + 1; k < this.lineCount; k++) {
776
844
  const line = this.lineText(k);
777
- if (line === "---" || line === "...") {
778
- return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true) : null;
845
+ if (line === fence || !toml && line === "...") {
846
+ return sawContent ? this.island("front_matter", 0, this.contentEnds[k], true, { format: toml ? "toml" : "yaml" }) : null;
779
847
  }
780
848
  if (!NOT_WHITESPACE.test(line)) continue;
781
- if (!/^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s)/.test(line)) return null;
849
+ const shaped = toml ? /^\s*(?:[A-Za-z0-9_."-]+\s*=|\[|#)/.test(line) : /^(?:[A-Za-z0-9_"'-][^:]*:(?:\s|$)|\s+\S|-\s|#)/.test(line);
850
+ if (!shaped) return null;
782
851
  sawContent = true;
783
852
  }
784
853
  return null;
785
854
  }
855
+ /**
856
+ * `:::name[label]{attrs}` … closing `:::` (a colon run at least as long as
857
+ * the opener's, nesting counted), or a leaf `::name[…]{…}` line. The whole
858
+ * container is ONE island: its grammar (fence lengths, labels, attributes,
859
+ * nested tabs) is exactly what a visual editor would re-serialize wrongly.
860
+ */
861
+ directive(p, li, t) {
862
+ const open = DIRECTIVE_OPEN.exec(t);
863
+ if (!open) return null;
864
+ const colons = open[1].length;
865
+ const name = open[2];
866
+ if (colons === 2) {
867
+ return this.island("directive", p, this.contentEnds[li], true, { name, leaf: true });
868
+ }
869
+ const stack = [colons];
870
+ for (let k = li + 1; k < this.lineCount; k++) {
871
+ const line = this.lineText(k).trim();
872
+ const inner = DIRECTIVE_OPEN.exec(line);
873
+ if (inner && inner[1].length >= 3) {
874
+ stack.push(inner[1].length);
875
+ continue;
876
+ }
877
+ const close = /^(:{3,})\s*$/.exec(line);
878
+ if (close && close[1].length >= stack[stack.length - 1]) {
879
+ stack.pop();
880
+ if (stack.length === 0) return this.island("directive", p, this.contentEnds[k], true, { name });
881
+ }
882
+ }
883
+ return this.island("directive", p, this.n, false, { name });
884
+ }
885
+ /** MkDocs `!!! type "Title"` / `??? type` / `???+ type` and its indented (or blank-separated indented) body. */
886
+ mkdocsAdmonition(p, li, t) {
887
+ const open = MKDOCS_OPEN.exec(t);
888
+ if (!open) return null;
889
+ let last = li;
890
+ for (let k = li + 1; k < this.lineCount; k++) {
891
+ const line = this.lineText(k);
892
+ if (/^(?: {4}|\t)/.test(line) && NOT_WHITESPACE.test(line)) {
893
+ last = k;
894
+ continue;
895
+ }
896
+ if (!NOT_WHITESPACE.test(line)) continue;
897
+ break;
898
+ }
899
+ return this.island("directive", p, this.contentEnds[last], true, {
900
+ name: open[2].toLowerCase(),
901
+ syntax: "mkdocs"
902
+ });
903
+ }
904
+ /** A blockquote whose first line is `> [!TYPE]…` — every following `>` line belongs to it. */
905
+ callout(p, li, t) {
906
+ const open = CALLOUT_OPEN.exec(t);
907
+ if (!open) return null;
908
+ let last = li;
909
+ for (let k = li + 1; k < this.lineCount; k++) {
910
+ if (!/^ {0,3}>/.test(this.lineText(k))) break;
911
+ last = k;
912
+ }
913
+ return this.island("callout", p, this.contentEnds[last], true, {
914
+ name: open[1].toLowerCase(),
915
+ ...open[2] ? { fold: open[2] === "-" ? "closed" : "open" } : {}
916
+ });
917
+ }
918
+ /** `[^id]: text` plus its continuation lines (indented, or lazy text before a blank line). */
919
+ footnoteDefinition(p, li, t) {
920
+ const open = FOOTNOTE_DEF.exec(t);
921
+ if (!open) return null;
922
+ let last = li;
923
+ for (let k = li + 1; k < this.lineCount; k++) {
924
+ const line = this.lineText(k);
925
+ if (!NOT_WHITESPACE.test(line)) {
926
+ let j = k + 1;
927
+ while (j < this.lineCount && !NOT_WHITESPACE.test(this.lineText(j))) j++;
928
+ if (j < this.lineCount && /^(?: {4}|\t)/.test(this.lineText(j))) {
929
+ k = j - 1;
930
+ continue;
931
+ }
932
+ break;
933
+ }
934
+ if (/^(?: {4}|\t)/.test(line)) {
935
+ last = k;
936
+ continue;
937
+ }
938
+ if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
939
+ last = k;
940
+ }
941
+ return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
942
+ }
786
943
  /**
787
944
  * Backtick fence — the renderer splitter's close rules: JSON-string aware for
788
945
  * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
@@ -811,8 +968,9 @@ var Tokenizer = class {
811
968
  let nestedDepth = 0;
812
969
  let sawNested = false;
813
970
  for (let k = li + 1; k < this.lineCount; k++) {
971
+ if (this.isOpenTail(k)) continue;
814
972
  const line = this.lineText(k);
815
- const trimmedLine = line.trim();
973
+ const trimmedLine = trimFenceLine(line);
816
974
  if (trimmedLine.startsWith("```")) {
817
975
  const at2 = line.indexOf("```");
818
976
  if (isJson) {
@@ -842,7 +1000,7 @@ var Tokenizer = class {
842
1000
  }
843
1001
  }
844
1002
  const closeTicks = backtickRunLength(line, at);
845
- const after = line.slice(at + closeTicks).trim();
1003
+ const after = trimFenceLine(line.slice(at + closeTicks));
846
1004
  if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
847
1005
  if (isJson) state = jsonStringState(line, 0, line.length, state);
848
1006
  continue;
@@ -869,6 +1027,7 @@ var Tokenizer = class {
869
1027
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
870
1028
  const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
871
1029
  for (let k = li + 1; k < this.lineCount; k++) {
1030
+ if (this.isOpenTail(k)) continue;
872
1031
  if (close.test(this.lineText(k))) {
873
1032
  return this.island("fence", p, this.contentEnds[k], true, meta);
874
1033
  }
@@ -1258,6 +1417,21 @@ var Tokenizer = class {
1258
1417
  i++;
1259
1418
  continue;
1260
1419
  }
1420
+ if (ch === "[" && text[i + 1] === "[" || ch === "!" && text[i + 1] === "[" && text[i + 2] === "[") {
1421
+ const open = ch === "!" ? i + 3 : i + 2;
1422
+ const close = within("]]", open);
1423
+ const newline = this.next("\n", open);
1424
+ const inner = close === -1 ? "" : text.slice(open, close);
1425
+ if (close !== -1 && (newline === -1 || newline > close) && inner.trim() && !/[[\]]/.test(inner)) {
1426
+ const cut = inner.search(/\\?\|/);
1427
+ const target = (cut < 0 ? inner : inner.slice(0, cut)).trim();
1428
+ inlines.push(
1429
+ this.island("wikilink", i, close + 2, true, ch === "!" ? { target, embed: true } : { target })
1430
+ );
1431
+ i = close + 2;
1432
+ continue;
1433
+ }
1434
+ }
1261
1435
  if (ch === "[") {
1262
1436
  const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
1263
1437
  if (media) {
@@ -1339,8 +1513,8 @@ var Tokenizer = class {
1339
1513
  return { inlines, breakIsland: null, orphan: false };
1340
1514
  }
1341
1515
  };
1342
- function tokenizeSource(text) {
1343
- const raw = new Tokenizer(text).run();
1516
+ function tokenizeSource(text, options = {}) {
1517
+ const raw = new Tokenizer(text, options.streaming ?? false).run();
1344
1518
  const cp = buildCodePointIndex(text);
1345
1519
  const inline = (island) => ({
1346
1520
  islandType: island.type,
@@ -1389,7 +1563,7 @@ function listIslands(blocks) {
1389
1563
  raw: block.raw
1390
1564
  });
1391
1565
  } else {
1392
- out.push(...block.inlines);
1566
+ for (const inline of block.inlines) out.push(inline);
1393
1567
  }
1394
1568
  }
1395
1569
  return out;
@@ -1425,15 +1599,37 @@ function islandEdit(island, text) {
1425
1599
  function blockRangeEdit(first, last, text) {
1426
1600
  return { start: first.start, end: last.end, text };
1427
1601
  }
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;
1602
+ var CONTEXT_WINDOW = 64;
1603
+ function locateIsland(text, raw, from, expected, expectedFromEnd, oldText, oldStart, oldEnd) {
1604
+ const candidates = /* @__PURE__ */ new Set();
1605
+ if (expected >= from && text.startsWith(raw, expected)) candidates.add(expected);
1606
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) candidates.add(expectedFromEnd);
1431
1607
  const after = text.indexOf(raw, Math.max(from, expected));
1608
+ if (after !== -1) candidates.add(after);
1432
1609
  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;
1610
+ if (before >= from) candidates.add(before);
1611
+ if (candidates.size === 0) return text.indexOf(raw, from);
1612
+ if (candidates.size === 1) return candidates.values().next().value;
1613
+ let best = -1;
1614
+ let bestScore = -1;
1615
+ for (const at of candidates) {
1616
+ let score = 0;
1617
+ for (let k = 0; k < CONTEXT_WINDOW; k++) {
1618
+ const n = text[at + raw.length + k];
1619
+ if (n === void 0 || n !== oldText[oldEnd + k]) break;
1620
+ score++;
1621
+ }
1622
+ for (let k = 1; k <= CONTEXT_WINDOW; k++) {
1623
+ const n = text[at - k];
1624
+ if (n === void 0 || n !== oldText[oldStart - k]) break;
1625
+ score++;
1626
+ }
1627
+ if (score > bestScore || score === bestScore && at === expected) {
1628
+ best = at;
1629
+ bestScore = score;
1630
+ }
1631
+ }
1632
+ return best;
1437
1633
  }
1438
1634
  function commonPrefix(a, b) {
1439
1635
  const max = Math.min(a.length, b.length);
@@ -1510,7 +1706,16 @@ function spliceSave(original, edits, options = {}) {
1510
1706
  let oldCursor2 = edit.start;
1511
1707
  let newCursor2 = 0;
1512
1708
  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));
1709
+ const at = locateIsland(
1710
+ edit.text,
1711
+ island.raw,
1712
+ newCursor2,
1713
+ newCursor2 + (island.start - oldCursor2),
1714
+ edit.text.length - (edit.end - island.start),
1715
+ original.slice(edit.start, edit.end),
1716
+ island.start - edit.start,
1717
+ island.end - edit.start
1718
+ );
1514
1719
  if (at === -1) {
1515
1720
  const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
1516
1721
  throw new SourceSpliceError(
@@ -1648,6 +1853,6 @@ function mapRange(changes, start, end, unit = "utf16") {
1648
1853
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
1649
1854
  }
1650
1855
 
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 };
1856
+ 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
1857
  //# sourceMappingURL=source.js.map
1653
1858
  //# sourceMappingURL=source.js.map