@ai-matrx/content-ir 0.17.0 → 0.18.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.cjs CHANGED
@@ -254,75 +254,233 @@ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
254
254
  return "content";
255
255
  }
256
256
 
257
- // source/math.ts
258
- var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
259
- var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
260
- var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
261
- var FUNCTION_NAMES = /* @__PURE__ */ new Set([
262
- "sin",
263
- "cos",
264
- "tan",
265
- "sec",
266
- "csc",
267
- "cot",
268
- "log",
269
- "ln",
270
- "exp",
271
- "lim",
272
- "max",
273
- "min",
274
- "det",
275
- "gcd",
276
- "mod",
277
- "arg",
278
- "sinh",
279
- "cosh",
280
- "tanh"
281
- ]);
282
- function isMathShape(content) {
283
- if (!MATH_SHAPE_CHARS.test(content)) return false;
284
- const words = content.match(/[A-Za-z]+/g);
285
- if (!words) return false;
286
- if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
287
- if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
288
- return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
257
+ // source/code-ranges.ts
258
+ function fenceOpenerOf(content) {
259
+ const t = content.trimStart();
260
+ if (t.startsWith("```")) {
261
+ let ticks = 0;
262
+ while (ticks < t.length && t[ticks] === "`") ticks++;
263
+ return { char: "`", ticks, lang: parseFenceOpener(t)?.lang ?? "" };
264
+ }
265
+ if (content.length - t.length <= 3 && t.startsWith("~~~")) {
266
+ const open = /^(~{3,})(.*)$/.exec(t);
267
+ if (!open) return null;
268
+ return { char: "~", ticks: open[1].length, lang: open[2].trim().split(/\s/)[0] ?? "" };
269
+ }
270
+ return null;
289
271
  }
290
- function isSingleDollarMath(content, after) {
291
- if (!content || content.length > 400) return false;
292
- if (/^\s/.test(content) || /\s$/.test(content)) return false;
293
- if (after !== void 0 && /[0-9]/.test(after)) return false;
294
- if (LONE_VARIABLE.test(content)) return true;
295
- if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
296
- return TEX_SIGNAL.test(content) || isMathShape(content);
272
+ var FenceReader = class {
273
+ constructor(opener, options = {}) {
274
+ this.opener = opener;
275
+ this.isJson = opener.char === "`" && opener.lang === "json";
276
+ this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
277
+ this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
278
+ }
279
+ opener;
280
+ state = { inString: false, escaped: false };
281
+ depth = 0;
282
+ /** A nested fence was opened at some point. */
283
+ sawNested = false;
284
+ isJson;
285
+ nesting;
286
+ tildeCloser;
287
+ /** How many nested fences are open right now. */
288
+ get nestedDepth() {
289
+ return this.depth;
290
+ }
291
+ feed(line) {
292
+ if (this.tildeCloser) return this.tildeCloser.test(line);
293
+ const trimmedLine = trimFenceLine(line);
294
+ const at = line.indexOf("```");
295
+ if (at === -1) {
296
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
297
+ return false;
298
+ }
299
+ if (this.isJson) {
300
+ const probe = jsonStringState(line, 0, at, this.state);
301
+ if (probe.inString) {
302
+ this.state = jsonStringState(line, at, line.length, probe);
303
+ return false;
304
+ }
305
+ }
306
+ if (trimmedLine.startsWith("```")) {
307
+ const kind = classifyInnerFenceLine(trimmedLine, this.opener.ticks, this.nesting, this.depth);
308
+ if (kind === "close-outer") return true;
309
+ if (kind === "open-nested") {
310
+ this.depth++;
311
+ this.sawNested = true;
312
+ }
313
+ if (kind === "close-nested") this.depth--;
314
+ } else {
315
+ const closeTicks = backtickRunLength(line, at);
316
+ const after = trimFenceLine(line.slice(at + closeTicks));
317
+ if (closeTicks >= this.opener.ticks && after === "" && this.depth === 0) return true;
318
+ }
319
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
320
+ return false;
321
+ }
322
+ };
323
+ function closeFence(src, open, opener) {
324
+ const first = readFence(src, open, new FenceReader(opener));
325
+ if (first.closed || !first.reader.sawNested || first.reader.nestedDepth <= 0) return { line: first.line, closed: first.closed };
326
+ const strict = readFence(src, open, new FenceReader(opener, { nesting: false }));
327
+ return { line: strict.line, closed: strict.closed };
297
328
  }
298
- function singleDollarMathEnd(text, open, limit = text.length) {
299
- const bound = Math.min(limit, open + 403);
300
- let close = -1;
301
- for (let k = open + 1; k < bound; k += 1) {
302
- const c = text[k];
303
- if (c === "\n") break;
304
- if (c === "\\") {
305
- k += 1;
329
+ function readFence(src, open, reader) {
330
+ for (let k = open + 1; k < src.count; k++) {
331
+ if (src.openTail?.(k)) continue;
332
+ const line = src.line(k);
333
+ if (line === null) return { line: k - 1, closed: false, reader };
334
+ if (reader.feed(line)) return { line: k, closed: true, reader };
335
+ }
336
+ return { line: src.count - 1, closed: false, reader };
337
+ }
338
+ function jsonStringState(line, from, upTo, state) {
339
+ let { inString, escaped } = state;
340
+ for (let idx = from; idx < upTo; idx++) {
341
+ const ch = line[idx];
342
+ if (escaped) {
343
+ escaped = false;
306
344
  continue;
307
345
  }
308
- if (c === "$") {
309
- close = k;
310
- break;
346
+ if (ch === "\\" && inString) {
347
+ escaped = true;
348
+ continue;
311
349
  }
350
+ if (ch === '"') inString = !inString;
312
351
  }
313
- if (close === -1 || text[close + 1] === "$") return -1;
314
- const content = text.slice(open + 1, close);
315
- return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
352
+ return { inString, escaped };
316
353
  }
317
-
318
- // source/code-ranges.ts
319
- function findCodeRanges(text) {
320
- const ranges = [];
321
- const n = text.length;
322
- let fence = null;
354
+ function backtickRunLength(str, pos) {
355
+ let n = 0;
356
+ while (pos + n < str.length && str[pos + n] === "`") n++;
357
+ return n;
358
+ }
359
+ function quotePrefix(line) {
360
+ let depth = 0;
361
+ let offset = 0;
362
+ for (; ; ) {
363
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
364
+ if (!m) return { depth, offset };
365
+ depth++;
366
+ offset += m[0].length;
367
+ }
368
+ }
369
+ function stripQuotes(line, depth) {
370
+ let offset = 0;
371
+ for (let d = 0; d < depth; d++) {
372
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
373
+ if (!m) return null;
374
+ offset += m[0].length;
375
+ }
376
+ return line.slice(offset);
377
+ }
378
+ var LIST_ITEM = /^( *)([-*+]|\d{1,9}[.)])( +|\t|$)/;
379
+ function leadingSpaces(s) {
380
+ let i = 0;
381
+ while (i < s.length && s[i] === " ") i++;
382
+ return i;
383
+ }
384
+ function scan(text) {
385
+ const starts = [];
386
+ const ends = [];
387
+ for (let pos = 0; ; ) {
388
+ const nl = text.indexOf("\n", pos);
389
+ const lineEnd = nl === -1 ? text.length : nl;
390
+ starts.push(pos);
391
+ ends.push(lineEnd > pos && text.charCodeAt(lineEnd - 1) === 13 ? lineEnd - 1 : lineEnd);
392
+ if (nl === -1) break;
393
+ pos = nl + 1;
394
+ }
395
+ const count = starts.length;
396
+ const raw = (k) => text.slice(starts[k], ends[k]);
397
+ const out = { ranges: [], fences: [], paragraphs: [], endsInFence: false, pendingPara: -1 };
398
+ const rawLines = { count, line: raw };
399
+ let segment = 0;
400
+ for (let k = 0; k < count; k++) {
401
+ const opener = fenceOpenerOf(raw(k));
402
+ if (!opener || opener.char !== "`") continue;
403
+ prose(text, starts, ends, raw, segment, k, out);
404
+ const end = closeFence(rawLines, k, opener);
405
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
406
+ out.fences.push([k, end.line, end.closed]);
407
+ out.pendingPara = -1;
408
+ if (!end.closed && end.line === count - 1) out.endsInFence = true;
409
+ k = end.line;
410
+ segment = k + 1;
411
+ }
412
+ prose(text, starts, ends, raw, segment, count, out);
413
+ return out;
414
+ }
415
+ function prose(text, starts, ends, raw, from, to, out) {
323
416
  let para = -1;
324
- const spans = (from, to) => {
325
- const runs = [];
417
+ let quoteDepth = 0;
418
+ let lists = [];
419
+ const closePara = (at) => {
420
+ if (para !== -1) out.paragraphs.push([para, at]);
421
+ para = -1;
422
+ };
423
+ for (let k = from; k < to; k++) {
424
+ const line = raw(k);
425
+ const { depth, offset } = quotePrefix(line);
426
+ if (depth !== quoteDepth) {
427
+ quoteDepth = depth;
428
+ lists = [];
429
+ }
430
+ let content = line.slice(offset);
431
+ if (content.trim() === "") {
432
+ closePara(starts[k]);
433
+ if (!(k === starts.length - 1 && line === "")) out.pendingPara = -1;
434
+ continue;
435
+ }
436
+ const indent = leadingSpaces(content);
437
+ while (lists.length && indent < lists[lists.length - 1]) lists.pop();
438
+ const item = LIST_ITEM.exec(content);
439
+ let column = lists.length ? lists[lists.length - 1] : 0;
440
+ if (item) {
441
+ const gap = item[3].length;
442
+ column = item[1].length + item[2].length + (gap >= 1 && gap <= 4 ? gap : 1);
443
+ lists.push(column);
444
+ content = content.slice(Math.min(column, content.length));
445
+ } else {
446
+ content = content.slice(Math.min(indent, column));
447
+ }
448
+ const opener = fenceOpenerOf(content);
449
+ if (!opener) {
450
+ if (para === -1) para = starts[k];
451
+ if (out.pendingPara === -1) out.pendingPara = starts[k];
452
+ continue;
453
+ }
454
+ closePara(starts[k]);
455
+ out.pendingPara = -1;
456
+ const col = column;
457
+ const src = {
458
+ count: to,
459
+ line: (j) => {
460
+ const inner = stripQuotes(raw(j), depth);
461
+ if (inner === null) return null;
462
+ if (opener.char === "~" && col > 0) {
463
+ const ind = leadingSpaces(inner);
464
+ if (inner.trim() !== "" && ind < col) return null;
465
+ return inner.slice(Math.min(ind, col));
466
+ }
467
+ return inner;
468
+ }
469
+ };
470
+ const end = closeFence(src, k, opener);
471
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
472
+ out.fences.push([k, end.line, end.closed]);
473
+ if (!end.closed && end.line === starts.length - 1) out.endsInFence = true;
474
+ k = end.line;
475
+ }
476
+ closePara(to < starts.length ? starts[to] : text.length);
477
+ }
478
+ var BacktickRuns = class {
479
+ /** Every run, in order: [start, length]. */
480
+ all = [];
481
+ byLength = /* @__PURE__ */ new Map();
482
+ indexOf = /* @__PURE__ */ new Map();
483
+ constructor(text, from, to) {
326
484
  for (let k = from; k < to; ) {
327
485
  if (text.charCodeAt(k) !== 96) {
328
486
  k++;
@@ -330,117 +488,69 @@ function findCodeRanges(text) {
330
488
  }
331
489
  let len = 1;
332
490
  while (k + len < to && text.charCodeAt(k + len) === 96) len++;
333
- runs.push([k, len]);
491
+ this.indexOf.set(k, this.all.length);
492
+ this.all.push([k, len]);
493
+ const list = this.byLength.get(len);
494
+ if (list) list.push(k);
495
+ else this.byLength.set(len, [k]);
334
496
  k += len;
335
497
  }
336
- const nextSame = new Array(runs.length).fill(-1);
337
- const last = /* @__PURE__ */ new Map();
338
- for (let r = runs.length - 1; r >= 0; r--) {
339
- const len = runs[r][1];
340
- nextSame[r] = last.get(len) ?? -1;
341
- last.set(len, r);
342
- }
343
- for (let r = 0; r < runs.length; ) {
344
- const j = nextSame[r];
345
- if (j === -1) {
346
- r++;
347
- continue;
348
- }
349
- const [s0] = runs[r];
350
- const [s1, l1] = runs[j];
351
- ranges.push({ start: s0, end: s1 + l1, kind: "span" });
352
- r = j + 1;
498
+ }
499
+ /** Start of the first run of exactly `n` backticks at or after `after`, or -1. */
500
+ next(n, after) {
501
+ const list = this.byLength.get(n);
502
+ if (!list) return -1;
503
+ let lo = 0;
504
+ let hi = list.length;
505
+ while (lo < hi) {
506
+ const mid = lo + hi >>> 1;
507
+ if (list[mid] < after) lo = mid + 1;
508
+ else hi = mid;
353
509
  }
354
- };
355
- let pos = 0;
356
- while (pos <= n) {
357
- const nl = text.indexOf("\n", pos);
358
- const lineEnd = nl === -1 ? n : nl;
359
- const line = text.slice(pos, lineEnd);
360
- let indent = 0;
361
- while (indent < line.length && line[indent] === " ") indent++;
362
- const body = line.slice(indent);
363
- if (fence) {
364
- const trimmed = trimFenceLine(line);
365
- let closes = false;
366
- if (fence.char === "`") {
367
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
368
- if (kind === "open-nested") fence.depth++;
369
- else if (kind === "close-nested") fence.depth--;
370
- else if (kind === "close-outer") closes = indent <= 3;
371
- } else {
372
- let t = 0;
373
- while (t < trimmed.length && trimmed[t] === "~") t++;
374
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
375
- }
376
- if (closes) {
377
- ranges.push({ start: fence.start, end: lineEnd, kind: "fence" });
378
- fence = null;
379
- }
380
- } else {
381
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
382
- const info = opener ? opener[2] : "";
383
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
384
- if (para !== -1) spans(para, pos);
385
- para = -1;
386
- const char = opener[1][0];
387
- const parsed = char === "`" ? parseFenceOpener(line) : null;
388
- fence = { char, len: opener[1].length, start: pos, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
389
- } else if (body.trim() === "") {
390
- if (para !== -1) spans(para, pos);
391
- para = -1;
392
- } else if (para === -1) para = pos;
510
+ return list[lo] ?? -1;
511
+ }
512
+ /** Index of the run starting at `start` in `all`. */
513
+ index(start) {
514
+ return this.indexOf.get(start) ?? -1;
515
+ }
516
+ };
517
+ function codeSpans(text, from, to, out) {
518
+ const runs = new BacktickRuns(text, from, to);
519
+ for (let r = 0; r < runs.all.length; ) {
520
+ const [start, n] = runs.all[r];
521
+ const close = runs.next(n, start + n);
522
+ if (close === -1) {
523
+ r++;
524
+ continue;
393
525
  }
394
- if (nl === -1) break;
395
- pos = nl + 1;
526
+ out.push({ start, end: close + n, kind: "span" });
527
+ r = runs.index(close) + 1;
396
528
  }
397
- if (fence) ranges.push({ start: fence.start, end: n, kind: "fence" });
398
- else if (para !== -1) spans(para, n);
529
+ }
530
+ function findCodeRanges(text) {
531
+ const s = scan(text);
532
+ const ranges = [...s.ranges];
533
+ for (const [from, to] of s.paragraphs) codeSpans(text, from, to, ranges);
399
534
  return ranges.sort((a, b) => a.start - b.start);
400
535
  }
536
+ function fenceLineKinds(text) {
537
+ const s = scan(text);
538
+ let count = 1;
539
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) count++;
540
+ const kinds = new Array(count).fill("prose");
541
+ for (const [open, last, closed] of s.fences) {
542
+ kinds[open] = "open";
543
+ for (let k = open + 1; k <= last; k++) kinds[k] = "body";
544
+ if (closed && last > open) kinds[last] = "close";
545
+ }
546
+ return kinds;
547
+ }
401
548
  function pendingCodeSpanStart(text) {
402
549
  const n = text.length;
403
550
  if (n === 0 || /\n[ \t]*\n[ \t]*$/.test(text)) return -1;
404
- let fence = null;
405
- let para = -1;
406
- let pos = 0;
407
- while (pos <= n) {
408
- const nl = text.indexOf("\n", pos);
409
- const lineEnd = nl === -1 ? n : nl;
410
- const line = text.slice(pos, lineEnd);
411
- let indent = 0;
412
- while (indent < line.length && line[indent] === " ") indent++;
413
- const body = line.slice(indent);
414
- if (fence) {
415
- const trimmed = trimFenceLine(line);
416
- let closes = false;
417
- if (fence.char === "`") {
418
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
419
- if (kind === "open-nested") fence.depth++;
420
- else if (kind === "close-nested") fence.depth--;
421
- else if (kind === "close-outer") closes = indent <= 3;
422
- } else {
423
- let t = 0;
424
- while (t < trimmed.length && trimmed[t] === "~") t++;
425
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
426
- }
427
- if (closes) fence = null;
428
- } else {
429
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
430
- const info = opener ? opener[2] : "";
431
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
432
- para = -1;
433
- const char = opener[1][0];
434
- const parsed = char === "`" ? parseFenceOpener(line) : null;
435
- fence = { char, len: opener[1].length, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
436
- } else if (body.trim() === "") {
437
- if (!(nl === -1 && line === "")) para = -1;
438
- } else if (para === -1) para = pos;
439
- }
440
- if (nl === -1) break;
441
- pos = nl + 1;
442
- }
443
- if (fence || para === -1) return -1;
551
+ const s = scan(text);
552
+ const para = s.pendingPara;
553
+ if (s.endsInFence || para === -1) return -1;
444
554
  const runs = [];
445
555
  for (let k = para; k < n; ) {
446
556
  if (text.charCodeAt(k) !== 96) {
@@ -577,6 +687,83 @@ function pairDisplayMath(text) {
577
687
  return pairs;
578
688
  }
579
689
 
690
+ // source/math.ts
691
+ var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
692
+ var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
693
+ var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
694
+ function isSingleDollarMath(content, after) {
695
+ if (!content || content.length > 400) return false;
696
+ if (/^\s/.test(content) || /\s$/.test(content)) return false;
697
+ if (after !== void 0 && /[0-9]/.test(after)) return false;
698
+ if (/(?:^|[^\\])%/.test(content)) return false;
699
+ if (/^[)\]}"/]/.test(content) || /[(\[{":;,/]$/.test(content)) return false;
700
+ if (/^[0-9]/.test(content) && after !== void 0 && /[A-Za-z]/.test(after)) return false;
701
+ if (isMathPlaceholder(content)) return false;
702
+ return TEX_SIGNAL.test(content) || !readsAsProse(content);
703
+ }
704
+ var FUNCTION_NAMES = /* @__PURE__ */ new Set([
705
+ "sin",
706
+ "cos",
707
+ "tan",
708
+ "sec",
709
+ "csc",
710
+ "cot",
711
+ "log",
712
+ "ln",
713
+ "exp",
714
+ "lim",
715
+ "max",
716
+ "min",
717
+ "det",
718
+ "gcd",
719
+ "mod",
720
+ "arg",
721
+ "sinh",
722
+ "cosh",
723
+ "tanh"
724
+ ]);
725
+ function readsAsProse(content) {
726
+ if (/[A-Za-z]'[a-z]/.test(content)) return true;
727
+ if (/(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return true;
728
+ if (!/\s/.test(content)) return false;
729
+ const words = (content.match(/[A-Za-z]{3,}/g) ?? []).filter((w) => !FUNCTION_NAMES.has(w.toLowerCase()));
730
+ return words.length >= 2;
731
+ }
732
+ function looksLikeOpenInlineMath(partial) {
733
+ if (!partial || partial.length > 400) return false;
734
+ if (/^\s/.test(partial) || /\s$/.test(partial)) return false;
735
+ if (LONE_VARIABLE.test(partial)) return true;
736
+ if (/^[0-9]/.test(partial)) return /\\[A-Za-z]+|[\\^_{}]/.test(partial);
737
+ return TEX_SIGNAL.test(partial) || isMathShape(partial);
738
+ }
739
+ function isMathShape(content) {
740
+ if (!MATH_SHAPE_CHARS.test(content)) return false;
741
+ const words = content.match(/[A-Za-z]+/g);
742
+ if (!words) return false;
743
+ if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
744
+ if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
745
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
746
+ }
747
+ function singleDollarMathEnd(text, open, limit = text.length) {
748
+ const bound = Math.min(limit, open + 403);
749
+ let close = -1;
750
+ for (let k = open + 1; k < bound; k += 1) {
751
+ const c = text[k];
752
+ if (c === "\n") break;
753
+ if (c === "\\") {
754
+ k += 1;
755
+ continue;
756
+ }
757
+ if (c === "$") {
758
+ close = k;
759
+ break;
760
+ }
761
+ }
762
+ if (close === -1 || text[close + 1] === "$") return -1;
763
+ const content = text.slice(open + 1, close);
764
+ return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
765
+ }
766
+
580
767
  // source/xml-tag.ts
581
768
  var XML_NAME_START = /[A-Za-z_]/;
582
769
  var XML_NAME_CHARACTER = /[\w.:-]/;
@@ -656,25 +843,20 @@ var XmlContainerTracker = class {
656
843
  inComment = false;
657
844
  inCdata = false;
658
845
  inlineTicks = 0;
659
- fenceMarker = null;
846
+ /** The open fence, read by THE one closer (source/code-ranges.ts). */
847
+ fence = null;
660
848
  consumeLine(line, startOffset = 0) {
661
- const fence = /^[ \t]*(`{3,}|~{3,})(.*)$/.exec(line);
662
- if (!this.inComment && !this.inCdata && this.inlineTicks === 0 && fence) {
663
- const marker = fence[1];
664
- const char = marker[0];
665
- const suffix = fence[2];
666
- if (marker.length >= 3) {
667
- if (!this.fenceMarker) {
668
- this.fenceMarker = { char, ticks: marker.length };
669
- return null;
670
- }
671
- if (char === this.fenceMarker.char && marker.length >= this.fenceMarker.ticks && suffix.trim() === "") {
672
- this.fenceMarker = null;
673
- }
849
+ if (this.fence) {
850
+ if (this.fence.feed(line)) this.fence = null;
851
+ return null;
852
+ }
853
+ if (!this.inComment && !this.inCdata && this.inlineTicks === 0) {
854
+ const opener = fenceOpenerOf(line);
855
+ if (opener) {
856
+ this.fence = new FenceReader(opener);
674
857
  return null;
675
858
  }
676
859
  }
677
- if (this.fenceMarker) return null;
678
860
  for (let i = startOffset; i < line.length; i++) {
679
861
  if (this.inlineTicks > 0) {
680
862
  if (line[i] === "`") {
@@ -869,27 +1051,6 @@ function isUnclosedGenericXmlOpening(source) {
869
1051
  }
870
1052
  return true;
871
1053
  }
872
- function jsonStringState(line, from, upTo, state) {
873
- let { inString, escaped } = state;
874
- for (let idx = from; idx < upTo; idx++) {
875
- const ch = line[idx];
876
- if (escaped) {
877
- escaped = false;
878
- continue;
879
- }
880
- if (ch === "\\" && inString) {
881
- escaped = true;
882
- continue;
883
- }
884
- if (ch === '"') inString = !inString;
885
- }
886
- return { inString, escaped };
887
- }
888
- function backtickRunLength(str, pos) {
889
- let n = 0;
890
- while (pos + n < str.length && str[pos + n] === "`") n++;
891
- return n;
892
- }
893
1054
  function isSplitterTreeLine(line) {
894
1055
  return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
895
1056
  }
@@ -1121,11 +1282,9 @@ var Tokenizer = class {
1121
1282
  const footnote = this.footnoteDefinition(p, li, t);
1122
1283
  if (footnote) return footnote;
1123
1284
  }
1124
- if (t.startsWith("```")) return this.backtickFence(p, li, t);
1125
- if (indent <= 3 && t.startsWith("~~~")) {
1126
- const tilde = this.tildeFence(p, li, t);
1127
- if (tilde) return tilde;
1128
- }
1285
+ const fence = fenceOpenerOf(seg);
1286
+ if (fence?.char === "`") return this.backtickFence(p, li, fence);
1287
+ if (fence?.char === "~") return this.tildeFence(p, li, fence);
1129
1288
  if (t.startsWith("$$")) {
1130
1289
  const math = this.mathBlock(p, tp);
1131
1290
  if (math) return math;
@@ -1280,74 +1439,31 @@ var Tokenizer = class {
1280
1439
  heldFootnote(p, id) {
1281
1440
  return this.island("footnote_def", p, this.n, false, { id });
1282
1441
  }
1442
+ /** The text's lines as the one fence closer reads them (streaming: the open tail closes nothing). */
1443
+ get fenceLines() {
1444
+ return {
1445
+ count: this.lineCount,
1446
+ line: (k) => this.lineText(k),
1447
+ openTail: (k) => this.isOpenTail(k)
1448
+ };
1449
+ }
1283
1450
  /**
1284
- * Backtick fence — the renderer splitter's close rules: JSON-string aware for
1285
- * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
1286
- * fences, with the splitter's strict-CommonMark retry when a nested fence is
1287
- * still open at the end of the text.
1451
+ * Backtick fence — THE one closer (`closeFence`, source/code-ranges.ts): the
1452
+ * nested-fence rule for markdown fences with the strict-CommonMark retry, the
1453
+ * renderer's line-end closer, JSON-string awareness for ```json.
1288
1454
  */
1289
- backtickFence(p, li, t) {
1290
- const openTicks = backtickRunLength(t, 0);
1291
- const lang = parseFenceOpener(t)?.lang ?? "";
1455
+ backtickFence(p, li, opener) {
1456
+ const lang = opener.lang;
1292
1457
  const isJson = lang === "json";
1293
1458
  const normalized = normalizeFenceLanguage(lang);
1294
- const meta = { fence: "`", ticks: openTicks, lang };
1459
+ const meta = { fence: "`", ticks: opener.ticks, lang };
1295
1460
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
1296
- const nesting = fenceNestsInnerFences(lang);
1297
- let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
1298
- if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
1299
- if (typeof close === "number") {
1300
- return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
1461
+ const end = closeFence(this.fenceLines, li, opener);
1462
+ if (end.closed) {
1463
+ return this.fenceIsland(p, this.contentEnds[end.line], true, meta, li, end.line, isJson);
1301
1464
  }
1302
1465
  return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
1303
1466
  }
1304
- /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
1305
- backtickFenceClose(li, openTicks, isJson, nesting) {
1306
- let state = { inString: false, escaped: false };
1307
- let nestedDepth = 0;
1308
- let sawNested = false;
1309
- for (let k = li + 1; k < this.lineCount; k++) {
1310
- if (this.isOpenTail(k)) continue;
1311
- const line = this.lineText(k);
1312
- const trimmedLine = trimFenceLine(line);
1313
- if (trimmedLine.startsWith("```")) {
1314
- const at2 = line.indexOf("```");
1315
- if (isJson) {
1316
- const probe = jsonStringState(line, 0, at2, state);
1317
- if (probe.inString) {
1318
- state = jsonStringState(line, at2, line.length, probe);
1319
- continue;
1320
- }
1321
- }
1322
- const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
1323
- if (kind === "close-outer") return k;
1324
- if (kind === "open-nested") {
1325
- nestedDepth++;
1326
- sawNested = true;
1327
- }
1328
- if (kind === "close-nested") nestedDepth--;
1329
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1330
- continue;
1331
- }
1332
- const at = line.indexOf("```");
1333
- if (at !== -1) {
1334
- if (isJson) {
1335
- const probe = jsonStringState(line, 0, at, state);
1336
- if (probe.inString) {
1337
- state = jsonStringState(line, at, line.length, probe);
1338
- continue;
1339
- }
1340
- }
1341
- const closeTicks = backtickRunLength(line, at);
1342
- const after = trimFenceLine(line.slice(at + closeTicks));
1343
- if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
1344
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1345
- continue;
1346
- }
1347
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1348
- }
1349
- return sawNested && nestedDepth > 0 ? "retry-strict" : null;
1350
- }
1351
1467
  fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
1352
1468
  if (isJson && openLine + 1 < this.lineCount) {
1353
1469
  const bodyEnd = closeLine < this.lineCount ? this.lineStarts[closeLine] : this.n;
@@ -1356,22 +1472,16 @@ var Tokenizer = class {
1356
1472
  }
1357
1473
  return this.island("fence", p, end, complete, meta);
1358
1474
  }
1359
- tildeFence(p, li, t) {
1360
- const open = /^(~{3,})(.*)$/.exec(t);
1361
- if (!open) return null;
1362
- const ticks = open[1].length;
1363
- const lang = open[2].trim().split(/\s/)[0] ?? "";
1364
- const meta = { fence: "~", ticks, lang };
1475
+ tildeFence(p, li, opener) {
1476
+ const lang = opener.lang;
1477
+ const meta = { fence: "~", ticks: opener.ticks, lang };
1365
1478
  const normalized = normalizeFenceLanguage(lang);
1366
1479
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
1367
- const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
1368
- for (let k = li + 1; k < this.lineCount; k++) {
1369
- if (this.isOpenTail(k)) continue;
1370
- if (close.test(this.lineText(k))) {
1371
- const tildeEnd = this.contentEnds[k];
1372
- const end = this.rendererOverflow(li + 1, k, tildeEnd);
1373
- return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
1374
- }
1480
+ const close = closeFence(this.fenceLines, li, opener);
1481
+ if (close.closed) {
1482
+ const tildeEnd = this.contentEnds[close.line];
1483
+ const end = this.rendererOverflow(li + 1, close.line, tildeEnd);
1484
+ return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
1375
1485
  }
1376
1486
  return this.island("fence", p, this.n, false, meta);
1377
1487
  }
@@ -1391,7 +1501,7 @@ var Tokenizer = class {
1391
1501
  for (let j = firstLine; j <= closeLine && j < this.lineCount; j++) {
1392
1502
  const start = this.lineStarts[j];
1393
1503
  if (this.lineIsBlank(j)) continue;
1394
- if (this.lineText(j).trimStart().startsWith("~~~")) continue;
1504
+ if (fenceOpenerOf(this.lineText(j))?.char === "~") continue;
1395
1505
  const inner = this.detectBlockUncached(start, j, false);
1396
1506
  if (!inner) continue;
1397
1507
  if (inner.end > end) {
@@ -1639,9 +1749,9 @@ var Tokenizer = class {
1639
1749
  const tree = { ...breakIsland, start: treeStart };
1640
1750
  if (treeStart > pos) {
1641
1751
  const proseEnd = this.contentEnds[root - 1];
1642
- const scan2 = this.scanInline(pos, proseEnd);
1643
- if (!scan2.breakIsland) {
1644
- this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
1752
+ const scan3 = this.scanInline(pos, proseEnd);
1753
+ if (!scan3.breakIsland) {
1754
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan3.inlines });
1645
1755
  this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
1646
1756
  this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
1647
1757
  return tree.end;
@@ -1653,13 +1763,13 @@ var Tokenizer = class {
1653
1763
  }
1654
1764
  }
1655
1765
  }
1656
- const scan = this.scanInline(pos, end);
1657
- if (!scan.breakIsland) {
1658
- this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
1766
+ const scan2 = this.scanInline(pos, end);
1767
+ if (!scan2.breakIsland) {
1768
+ this.push({ kind: "prose", start: pos, end, island: null, inlines: scan2.inlines });
1659
1769
  return end;
1660
1770
  }
1661
- const br = scan.breakIsland;
1662
- if (scan.orphan) {
1771
+ const br = scan2.breakIsland;
1772
+ if (scan2.orphan) {
1663
1773
  let start = pos;
1664
1774
  while (this.blocks.length) {
1665
1775
  const last = this.blocks[this.blocks.length - 1];
@@ -1676,7 +1786,7 @@ var Tokenizer = class {
1676
1786
  start: pos,
1677
1787
  end: br.start,
1678
1788
  island: null,
1679
- inlines: scan.inlines.filter((inline) => inline.end <= br.start)
1789
+ inlines: scan2.inlines.filter((inline) => inline.end <= br.start)
1680
1790
  });
1681
1791
  this.push({ kind: "island", start: br.start, end: br.end, island: br, inlines: [] });
1682
1792
  return br.end;
@@ -1696,30 +1806,10 @@ var Tokenizer = class {
1696
1806
  const codeSpanClose = (n, from) => {
1697
1807
  if (!runs) {
1698
1808
  this.tick(pe - ps);
1699
- runs = /* @__PURE__ */ new Map();
1700
- for (let k = ps; k < pe; ) {
1701
- if (text.charCodeAt(k) !== 96) {
1702
- k++;
1703
- continue;
1704
- }
1705
- const len = backtickRunLength(text, k);
1706
- const list2 = runs.get(len);
1707
- if (list2) list2.push(k);
1708
- else runs.set(len, [k]);
1709
- k += len;
1710
- }
1711
- }
1712
- const list = runs.get(n);
1713
- if (!list) return -1;
1714
- let lo = 0;
1715
- let hi = list.length;
1716
- while (lo < hi) {
1717
- const mid = lo + hi >>> 1;
1718
- if (list[mid] < from) lo = mid + 1;
1719
- else hi = mid;
1809
+ runs = new BacktickRuns(text, ps, pe);
1720
1810
  }
1721
- const at = list[lo];
1722
- return at !== void 0 && at + n <= pe ? at : -1;
1811
+ const at = runs.next(n, from);
1812
+ return at !== -1 && at + n <= pe ? at : -1;
1723
1813
  };
1724
1814
  let i = ps;
1725
1815
  const scanEnd = Math.min(pe, this.provisionalFrom);
@@ -2256,9 +2346,11 @@ function mapRange(changes, start, end, unit = "utf16") {
2256
2346
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
2257
2347
  }
2258
2348
 
2349
+ exports.BacktickRuns = BacktickRuns;
2259
2350
  exports.CALLOUT_NAMES = CALLOUT_NAMES;
2260
2351
  exports.FENCE_OPENER_WHITESPACE = FENCE_OPENER_WHITESPACE;
2261
2352
  exports.FENCE_WHITESPACE = FENCE_WHITESPACE;
2353
+ exports.FenceReader = FenceReader;
2262
2354
  exports.NESTING_FENCE_LANGUAGES = NESTING_FENCE_LANGUAGES;
2263
2355
  exports.SourceSpliceError = SourceSpliceError;
2264
2356
  exports.XmlContainerTracker = XmlContainerTracker;
@@ -2267,7 +2359,10 @@ exports.blockEdit = blockEdit;
2267
2359
  exports.blockRangeEdit = blockRangeEdit;
2268
2360
  exports.buildCodePointIndex = buildCodePointIndex;
2269
2361
  exports.classifyInnerFenceLine = classifyInnerFenceLine;
2362
+ exports.closeFence = closeFence;
2363
+ exports.fenceLineKinds = fenceLineKinds;
2270
2364
  exports.fenceNestsInnerFences = fenceNestsInnerFences;
2365
+ exports.fenceOpenerOf = fenceOpenerOf;
2271
2366
  exports.findCodeRanges = findCodeRanges;
2272
2367
  exports.isMathPlaceholder = isMathPlaceholder;
2273
2368
  exports.isSingleDollarMath = isSingleDollarMath;
@@ -2275,6 +2370,7 @@ exports.islandEdit = islandEdit;
2275
2370
  exports.joinSource = joinSource;
2276
2371
  exports.listIslands = listIslands;
2277
2372
  exports.looksLikeDisplayMath = looksLikeDisplayMath;
2373
+ exports.looksLikeOpenInlineMath = looksLikeOpenInlineMath;
2278
2374
  exports.mapPosition = mapPosition;
2279
2375
  exports.mapRange = mapRange;
2280
2376
  exports.pairDisplayMath = pairDisplayMath;