@ai-matrx/content-ir 0.17.0 → 0.18.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.js CHANGED
@@ -252,75 +252,231 @@ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
252
252
  return "content";
253
253
  }
254
254
 
255
- // source/math.ts
256
- var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
257
- var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
258
- var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
259
- var FUNCTION_NAMES = /* @__PURE__ */ new Set([
260
- "sin",
261
- "cos",
262
- "tan",
263
- "sec",
264
- "csc",
265
- "cot",
266
- "log",
267
- "ln",
268
- "exp",
269
- "lim",
270
- "max",
271
- "min",
272
- "det",
273
- "gcd",
274
- "mod",
275
- "arg",
276
- "sinh",
277
- "cosh",
278
- "tanh"
279
- ]);
280
- function isMathShape(content) {
281
- if (!MATH_SHAPE_CHARS.test(content)) return false;
282
- const words = content.match(/[A-Za-z]+/g);
283
- if (!words) return false;
284
- if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
285
- if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
286
- return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
255
+ // source/code-ranges.ts
256
+ function fenceOpenerOf(content) {
257
+ const t = content.trimStart();
258
+ if (t.startsWith("```")) {
259
+ let ticks = 0;
260
+ while (ticks < t.length && t[ticks] === "`") ticks++;
261
+ return { char: "`", ticks, lang: parseFenceOpener(t)?.lang ?? "" };
262
+ }
263
+ if (content.length - t.length <= 3 && t.startsWith("~~~")) {
264
+ const open = /^(~{3,})(.*)$/.exec(t);
265
+ if (!open) return null;
266
+ return { char: "~", ticks: open[1].length, lang: open[2].trim().split(/\s/)[0] ?? "" };
267
+ }
268
+ return null;
287
269
  }
288
- function isSingleDollarMath(content, after) {
289
- if (!content || content.length > 400) return false;
290
- if (/^\s/.test(content) || /\s$/.test(content)) return false;
291
- if (after !== void 0 && /[0-9]/.test(after)) return false;
292
- if (LONE_VARIABLE.test(content)) return true;
293
- if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
294
- return TEX_SIGNAL.test(content) || isMathShape(content);
270
+ var FenceReader = class {
271
+ constructor(opener, options = {}) {
272
+ this.opener = opener;
273
+ this.isJson = opener.char === "`" && opener.lang === "json";
274
+ this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
275
+ this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
276
+ }
277
+ opener;
278
+ state = { inString: false, escaped: false };
279
+ depth = 0;
280
+ /** A nested fence was opened at some point. */
281
+ sawNested = false;
282
+ isJson;
283
+ nesting;
284
+ tildeCloser;
285
+ /** How many nested fences are open right now. */
286
+ get nestedDepth() {
287
+ return this.depth;
288
+ }
289
+ feed(line) {
290
+ if (this.tildeCloser) return this.tildeCloser.test(line);
291
+ const trimmedLine = trimFenceLine(line);
292
+ const at = line.indexOf("```");
293
+ if (at === -1) {
294
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
295
+ return false;
296
+ }
297
+ if (this.isJson) {
298
+ const probe = jsonStringState(line, 0, at, this.state);
299
+ if (probe.inString) {
300
+ this.state = jsonStringState(line, at, line.length, probe);
301
+ return false;
302
+ }
303
+ }
304
+ if (trimmedLine.startsWith("```")) {
305
+ const kind = classifyInnerFenceLine(trimmedLine, this.opener.ticks, this.nesting, this.depth);
306
+ if (kind === "close-outer") return true;
307
+ if (kind === "open-nested") {
308
+ this.depth++;
309
+ this.sawNested = true;
310
+ }
311
+ if (kind === "close-nested") this.depth--;
312
+ } else {
313
+ const closeTicks = backtickRunLength(line, at);
314
+ const after = trimFenceLine(line.slice(at + closeTicks));
315
+ if (closeTicks >= this.opener.ticks && after === "" && this.depth === 0) return true;
316
+ }
317
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
318
+ return false;
319
+ }
320
+ };
321
+ function closeFence(src, open, opener) {
322
+ const first = readFence(src, open, new FenceReader(opener));
323
+ if (first.closed || !first.reader.sawNested || first.reader.nestedDepth <= 0) return { line: first.line, closed: first.closed };
324
+ const strict = readFence(src, open, new FenceReader(opener, { nesting: false }));
325
+ return { line: strict.line, closed: strict.closed };
295
326
  }
296
- function singleDollarMathEnd(text, open, limit = text.length) {
297
- const bound = Math.min(limit, open + 403);
298
- let close = -1;
299
- for (let k = open + 1; k < bound; k += 1) {
300
- const c = text[k];
301
- if (c === "\n") break;
302
- if (c === "\\") {
303
- k += 1;
327
+ function readFence(src, open, reader) {
328
+ for (let k = open + 1; k < src.count; k++) {
329
+ if (src.openTail?.(k)) continue;
330
+ const line = src.line(k);
331
+ if (line === null) return { line: k - 1, closed: false, reader };
332
+ if (reader.feed(line)) return { line: k, closed: true, reader };
333
+ }
334
+ return { line: src.count - 1, closed: false, reader };
335
+ }
336
+ function jsonStringState(line, from, upTo, state) {
337
+ let { inString, escaped } = state;
338
+ for (let idx = from; idx < upTo; idx++) {
339
+ const ch = line[idx];
340
+ if (escaped) {
341
+ escaped = false;
304
342
  continue;
305
343
  }
306
- if (c === "$") {
307
- close = k;
308
- break;
344
+ if (ch === "\\" && inString) {
345
+ escaped = true;
346
+ continue;
309
347
  }
348
+ if (ch === '"') inString = !inString;
310
349
  }
311
- if (close === -1 || text[close + 1] === "$") return -1;
312
- const content = text.slice(open + 1, close);
313
- return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
350
+ return { inString, escaped };
314
351
  }
315
-
316
- // source/code-ranges.ts
317
- function findCodeRanges(text) {
318
- const ranges = [];
319
- const n = text.length;
320
- let fence = null;
352
+ function backtickRunLength(str, pos) {
353
+ let n = 0;
354
+ while (pos + n < str.length && str[pos + n] === "`") n++;
355
+ return n;
356
+ }
357
+ function quotePrefix(line) {
358
+ let depth = 0;
359
+ let offset = 0;
360
+ for (; ; ) {
361
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
362
+ if (!m) return { depth, offset };
363
+ depth++;
364
+ offset += m[0].length;
365
+ }
366
+ }
367
+ function stripQuotes(line, depth) {
368
+ let offset = 0;
369
+ for (let d = 0; d < depth; d++) {
370
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
371
+ if (!m) return null;
372
+ offset += m[0].length;
373
+ }
374
+ return line.slice(offset);
375
+ }
376
+ var LIST_ITEM = /^( *)([-*+]|\d{1,9}[.)])( +|\t|$)/;
377
+ function leadingSpaces(s) {
378
+ let i = 0;
379
+ while (i < s.length && s[i] === " ") i++;
380
+ return i;
381
+ }
382
+ function scan(text) {
383
+ const starts = [];
384
+ const ends = [];
385
+ for (let pos = 0; ; ) {
386
+ const nl = text.indexOf("\n", pos);
387
+ const lineEnd = nl === -1 ? text.length : nl;
388
+ starts.push(pos);
389
+ ends.push(lineEnd > pos && text.charCodeAt(lineEnd - 1) === 13 ? lineEnd - 1 : lineEnd);
390
+ if (nl === -1) break;
391
+ pos = nl + 1;
392
+ }
393
+ const count = starts.length;
394
+ const raw = (k) => text.slice(starts[k], ends[k]);
395
+ const out = { ranges: [], paragraphs: [], endsInFence: false, pendingPara: -1 };
396
+ const rawLines = { count, line: raw };
397
+ let segment = 0;
398
+ for (let k = 0; k < count; k++) {
399
+ const opener = fenceOpenerOf(raw(k));
400
+ if (!opener || opener.char !== "`") continue;
401
+ prose(text, starts, ends, raw, segment, k, out);
402
+ const end = closeFence(rawLines, k, opener);
403
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
404
+ out.pendingPara = -1;
405
+ if (!end.closed && end.line === count - 1) out.endsInFence = true;
406
+ k = end.line;
407
+ segment = k + 1;
408
+ }
409
+ prose(text, starts, ends, raw, segment, count, out);
410
+ return out;
411
+ }
412
+ function prose(text, starts, ends, raw, from, to, out) {
321
413
  let para = -1;
322
- const spans = (from, to) => {
323
- const runs = [];
414
+ let quoteDepth = 0;
415
+ let lists = [];
416
+ const closePara = (at) => {
417
+ if (para !== -1) out.paragraphs.push([para, at]);
418
+ para = -1;
419
+ };
420
+ for (let k = from; k < to; k++) {
421
+ const line = raw(k);
422
+ const { depth, offset } = quotePrefix(line);
423
+ if (depth !== quoteDepth) {
424
+ quoteDepth = depth;
425
+ lists = [];
426
+ }
427
+ let content = line.slice(offset);
428
+ if (content.trim() === "") {
429
+ closePara(starts[k]);
430
+ if (!(k === starts.length - 1 && line === "")) out.pendingPara = -1;
431
+ continue;
432
+ }
433
+ const indent = leadingSpaces(content);
434
+ while (lists.length && indent < lists[lists.length - 1]) lists.pop();
435
+ const item = LIST_ITEM.exec(content);
436
+ let column = lists.length ? lists[lists.length - 1] : 0;
437
+ if (item) {
438
+ const gap = item[3].length;
439
+ column = item[1].length + item[2].length + (gap >= 1 && gap <= 4 ? gap : 1);
440
+ lists.push(column);
441
+ content = content.slice(Math.min(column, content.length));
442
+ } else {
443
+ content = content.slice(Math.min(indent, column));
444
+ }
445
+ const opener = fenceOpenerOf(content);
446
+ if (!opener) {
447
+ if (para === -1) para = starts[k];
448
+ if (out.pendingPara === -1) out.pendingPara = starts[k];
449
+ continue;
450
+ }
451
+ closePara(starts[k]);
452
+ out.pendingPara = -1;
453
+ const col = column;
454
+ const src = {
455
+ count: to,
456
+ line: (j) => {
457
+ const inner = stripQuotes(raw(j), depth);
458
+ if (inner === null) return null;
459
+ if (opener.char === "~" && col > 0) {
460
+ const ind = leadingSpaces(inner);
461
+ if (inner.trim() !== "" && ind < col) return null;
462
+ return inner.slice(Math.min(ind, col));
463
+ }
464
+ return inner;
465
+ }
466
+ };
467
+ const end = closeFence(src, k, opener);
468
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
469
+ if (!end.closed && end.line === starts.length - 1) out.endsInFence = true;
470
+ k = end.line;
471
+ }
472
+ closePara(to < starts.length ? starts[to] : text.length);
473
+ }
474
+ var BacktickRuns = class {
475
+ /** Every run, in order: [start, length]. */
476
+ all = [];
477
+ byLength = /* @__PURE__ */ new Map();
478
+ indexOf = /* @__PURE__ */ new Map();
479
+ constructor(text, from, to) {
324
480
  for (let k = from; k < to; ) {
325
481
  if (text.charCodeAt(k) !== 96) {
326
482
  k++;
@@ -328,117 +484,57 @@ function findCodeRanges(text) {
328
484
  }
329
485
  let len = 1;
330
486
  while (k + len < to && text.charCodeAt(k + len) === 96) len++;
331
- runs.push([k, len]);
487
+ this.indexOf.set(k, this.all.length);
488
+ this.all.push([k, len]);
489
+ const list = this.byLength.get(len);
490
+ if (list) list.push(k);
491
+ else this.byLength.set(len, [k]);
332
492
  k += len;
333
493
  }
334
- const nextSame = new Array(runs.length).fill(-1);
335
- const last = /* @__PURE__ */ new Map();
336
- for (let r = runs.length - 1; r >= 0; r--) {
337
- const len = runs[r][1];
338
- nextSame[r] = last.get(len) ?? -1;
339
- last.set(len, r);
340
- }
341
- for (let r = 0; r < runs.length; ) {
342
- const j = nextSame[r];
343
- if (j === -1) {
344
- r++;
345
- continue;
346
- }
347
- const [s0] = runs[r];
348
- const [s1, l1] = runs[j];
349
- ranges.push({ start: s0, end: s1 + l1, kind: "span" });
350
- r = j + 1;
494
+ }
495
+ /** Start of the first run of exactly `n` backticks at or after `after`, or -1. */
496
+ next(n, after) {
497
+ const list = this.byLength.get(n);
498
+ if (!list) return -1;
499
+ let lo = 0;
500
+ let hi = list.length;
501
+ while (lo < hi) {
502
+ const mid = lo + hi >>> 1;
503
+ if (list[mid] < after) lo = mid + 1;
504
+ else hi = mid;
351
505
  }
352
- };
353
- let pos = 0;
354
- while (pos <= n) {
355
- const nl = text.indexOf("\n", pos);
356
- const lineEnd = nl === -1 ? n : nl;
357
- const line = text.slice(pos, lineEnd);
358
- let indent = 0;
359
- while (indent < line.length && line[indent] === " ") indent++;
360
- const body = line.slice(indent);
361
- if (fence) {
362
- const trimmed = trimFenceLine(line);
363
- let closes = false;
364
- if (fence.char === "`") {
365
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
366
- if (kind === "open-nested") fence.depth++;
367
- else if (kind === "close-nested") fence.depth--;
368
- else if (kind === "close-outer") closes = indent <= 3;
369
- } else {
370
- let t = 0;
371
- while (t < trimmed.length && trimmed[t] === "~") t++;
372
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
373
- }
374
- if (closes) {
375
- ranges.push({ start: fence.start, end: lineEnd, kind: "fence" });
376
- fence = null;
377
- }
378
- } else {
379
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
380
- const info = opener ? opener[2] : "";
381
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
382
- if (para !== -1) spans(para, pos);
383
- para = -1;
384
- const char = opener[1][0];
385
- const parsed = char === "`" ? parseFenceOpener(line) : null;
386
- fence = { char, len: opener[1].length, start: pos, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
387
- } else if (body.trim() === "") {
388
- if (para !== -1) spans(para, pos);
389
- para = -1;
390
- } else if (para === -1) para = pos;
506
+ return list[lo] ?? -1;
507
+ }
508
+ /** Index of the run starting at `start` in `all`. */
509
+ index(start) {
510
+ return this.indexOf.get(start) ?? -1;
511
+ }
512
+ };
513
+ function codeSpans(text, from, to, out) {
514
+ const runs = new BacktickRuns(text, from, to);
515
+ for (let r = 0; r < runs.all.length; ) {
516
+ const [start, n] = runs.all[r];
517
+ const close = runs.next(n, start + n);
518
+ if (close === -1) {
519
+ r++;
520
+ continue;
391
521
  }
392
- if (nl === -1) break;
393
- pos = nl + 1;
522
+ out.push({ start, end: close + n, kind: "span" });
523
+ r = runs.index(close) + 1;
394
524
  }
395
- if (fence) ranges.push({ start: fence.start, end: n, kind: "fence" });
396
- else if (para !== -1) spans(para, n);
525
+ }
526
+ function findCodeRanges(text) {
527
+ const s = scan(text);
528
+ const ranges = [...s.ranges];
529
+ for (const [from, to] of s.paragraphs) codeSpans(text, from, to, ranges);
397
530
  return ranges.sort((a, b) => a.start - b.start);
398
531
  }
399
532
  function pendingCodeSpanStart(text) {
400
533
  const n = text.length;
401
534
  if (n === 0 || /\n[ \t]*\n[ \t]*$/.test(text)) return -1;
402
- let fence = null;
403
- let para = -1;
404
- let pos = 0;
405
- while (pos <= n) {
406
- const nl = text.indexOf("\n", pos);
407
- const lineEnd = nl === -1 ? n : nl;
408
- const line = text.slice(pos, lineEnd);
409
- let indent = 0;
410
- while (indent < line.length && line[indent] === " ") indent++;
411
- const body = line.slice(indent);
412
- if (fence) {
413
- const trimmed = trimFenceLine(line);
414
- let closes = false;
415
- if (fence.char === "`") {
416
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
417
- if (kind === "open-nested") fence.depth++;
418
- else if (kind === "close-nested") fence.depth--;
419
- else if (kind === "close-outer") closes = indent <= 3;
420
- } else {
421
- let t = 0;
422
- while (t < trimmed.length && trimmed[t] === "~") t++;
423
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
424
- }
425
- if (closes) fence = null;
426
- } else {
427
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
428
- const info = opener ? opener[2] : "";
429
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
430
- para = -1;
431
- const char = opener[1][0];
432
- const parsed = char === "`" ? parseFenceOpener(line) : null;
433
- fence = { char, len: opener[1].length, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
434
- } else if (body.trim() === "") {
435
- if (!(nl === -1 && line === "")) para = -1;
436
- } else if (para === -1) para = pos;
437
- }
438
- if (nl === -1) break;
439
- pos = nl + 1;
440
- }
441
- if (fence || para === -1) return -1;
535
+ const s = scan(text);
536
+ const para = s.pendingPara;
537
+ if (s.endsInFence || para === -1) return -1;
442
538
  const runs = [];
443
539
  for (let k = para; k < n; ) {
444
540
  if (text.charCodeAt(k) !== 96) {
@@ -575,6 +671,83 @@ function pairDisplayMath(text) {
575
671
  return pairs;
576
672
  }
577
673
 
674
+ // source/math.ts
675
+ var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
676
+ var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
677
+ var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
678
+ function isSingleDollarMath(content, after) {
679
+ if (!content || content.length > 400) return false;
680
+ if (/^\s/.test(content) || /\s$/.test(content)) return false;
681
+ if (after !== void 0 && /[0-9]/.test(after)) return false;
682
+ if (/(?:^|[^\\])%/.test(content)) return false;
683
+ if (/^[)\]}"/]/.test(content) || /[(\[{":;,/]$/.test(content)) return false;
684
+ if (/^[0-9]/.test(content) && after !== void 0 && /[A-Za-z]/.test(after)) return false;
685
+ if (isMathPlaceholder(content)) return false;
686
+ return TEX_SIGNAL.test(content) || !readsAsProse(content);
687
+ }
688
+ var FUNCTION_NAMES = /* @__PURE__ */ new Set([
689
+ "sin",
690
+ "cos",
691
+ "tan",
692
+ "sec",
693
+ "csc",
694
+ "cot",
695
+ "log",
696
+ "ln",
697
+ "exp",
698
+ "lim",
699
+ "max",
700
+ "min",
701
+ "det",
702
+ "gcd",
703
+ "mod",
704
+ "arg",
705
+ "sinh",
706
+ "cosh",
707
+ "tanh"
708
+ ]);
709
+ function readsAsProse(content) {
710
+ if (/[A-Za-z]'[a-z]/.test(content)) return true;
711
+ if (/(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return true;
712
+ if (!/\s/.test(content)) return false;
713
+ const words = (content.match(/[A-Za-z]{3,}/g) ?? []).filter((w) => !FUNCTION_NAMES.has(w.toLowerCase()));
714
+ return words.length >= 2;
715
+ }
716
+ function looksLikeOpenInlineMath(partial) {
717
+ if (!partial || partial.length > 400) return false;
718
+ if (/^\s/.test(partial) || /\s$/.test(partial)) return false;
719
+ if (LONE_VARIABLE.test(partial)) return true;
720
+ if (/^[0-9]/.test(partial)) return /\\[A-Za-z]+|[\\^_{}]/.test(partial);
721
+ return TEX_SIGNAL.test(partial) || isMathShape(partial);
722
+ }
723
+ function isMathShape(content) {
724
+ if (!MATH_SHAPE_CHARS.test(content)) return false;
725
+ const words = content.match(/[A-Za-z]+/g);
726
+ if (!words) return false;
727
+ if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
728
+ if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
729
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
730
+ }
731
+ function singleDollarMathEnd(text, open, limit = text.length) {
732
+ const bound = Math.min(limit, open + 403);
733
+ let close = -1;
734
+ for (let k = open + 1; k < bound; k += 1) {
735
+ const c = text[k];
736
+ if (c === "\n") break;
737
+ if (c === "\\") {
738
+ k += 1;
739
+ continue;
740
+ }
741
+ if (c === "$") {
742
+ close = k;
743
+ break;
744
+ }
745
+ }
746
+ if (close === -1 || text[close + 1] === "$") return -1;
747
+ const content = text.slice(open + 1, close);
748
+ return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
749
+ }
750
+
578
751
  // source/xml-tag.ts
579
752
  var XML_NAME_START = /[A-Za-z_]/;
580
753
  var XML_NAME_CHARACTER = /[\w.:-]/;
@@ -867,27 +1040,6 @@ function isUnclosedGenericXmlOpening(source) {
867
1040
  }
868
1041
  return true;
869
1042
  }
870
- function jsonStringState(line, from, upTo, state) {
871
- let { inString, escaped } = state;
872
- for (let idx = from; idx < upTo; idx++) {
873
- const ch = line[idx];
874
- if (escaped) {
875
- escaped = false;
876
- continue;
877
- }
878
- if (ch === "\\" && inString) {
879
- escaped = true;
880
- continue;
881
- }
882
- if (ch === '"') inString = !inString;
883
- }
884
- return { inString, escaped };
885
- }
886
- function backtickRunLength(str, pos) {
887
- let n = 0;
888
- while (pos + n < str.length && str[pos + n] === "`") n++;
889
- return n;
890
- }
891
1043
  function isSplitterTreeLine(line) {
892
1044
  return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
893
1045
  }
@@ -1119,11 +1271,9 @@ var Tokenizer = class {
1119
1271
  const footnote = this.footnoteDefinition(p, li, t);
1120
1272
  if (footnote) return footnote;
1121
1273
  }
1122
- if (t.startsWith("```")) return this.backtickFence(p, li, t);
1123
- if (indent <= 3 && t.startsWith("~~~")) {
1124
- const tilde = this.tildeFence(p, li, t);
1125
- if (tilde) return tilde;
1126
- }
1274
+ const fence = fenceOpenerOf(seg);
1275
+ if (fence?.char === "`") return this.backtickFence(p, li, fence);
1276
+ if (fence?.char === "~") return this.tildeFence(p, li, fence);
1127
1277
  if (t.startsWith("$$")) {
1128
1278
  const math = this.mathBlock(p, tp);
1129
1279
  if (math) return math;
@@ -1278,74 +1428,31 @@ var Tokenizer = class {
1278
1428
  heldFootnote(p, id) {
1279
1429
  return this.island("footnote_def", p, this.n, false, { id });
1280
1430
  }
1431
+ /** The text's lines as the one fence closer reads them (streaming: the open tail closes nothing). */
1432
+ get fenceLines() {
1433
+ return {
1434
+ count: this.lineCount,
1435
+ line: (k) => this.lineText(k),
1436
+ openTail: (k) => this.isOpenTail(k)
1437
+ };
1438
+ }
1281
1439
  /**
1282
- * Backtick fence — the renderer splitter's close rules: JSON-string aware for
1283
- * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
1284
- * fences, with the splitter's strict-CommonMark retry when a nested fence is
1285
- * still open at the end of the text.
1440
+ * Backtick fence — THE one closer (`closeFence`, source/code-ranges.ts): the
1441
+ * nested-fence rule for markdown fences with the strict-CommonMark retry, the
1442
+ * renderer's line-end closer, JSON-string awareness for ```json.
1286
1443
  */
1287
- backtickFence(p, li, t) {
1288
- const openTicks = backtickRunLength(t, 0);
1289
- const lang = parseFenceOpener(t)?.lang ?? "";
1444
+ backtickFence(p, li, opener) {
1445
+ const lang = opener.lang;
1290
1446
  const isJson = lang === "json";
1291
1447
  const normalized = normalizeFenceLanguage(lang);
1292
- const meta = { fence: "`", ticks: openTicks, lang };
1448
+ const meta = { fence: "`", ticks: opener.ticks, lang };
1293
1449
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
1294
- const nesting = fenceNestsInnerFences(lang);
1295
- let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
1296
- if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
1297
- if (typeof close === "number") {
1298
- return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
1450
+ const end = closeFence(this.fenceLines, li, opener);
1451
+ if (end.closed) {
1452
+ return this.fenceIsland(p, this.contentEnds[end.line], true, meta, li, end.line, isJson);
1299
1453
  }
1300
1454
  return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
1301
1455
  }
1302
- /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
1303
- backtickFenceClose(li, openTicks, isJson, nesting) {
1304
- let state = { inString: false, escaped: false };
1305
- let nestedDepth = 0;
1306
- let sawNested = false;
1307
- for (let k = li + 1; k < this.lineCount; k++) {
1308
- if (this.isOpenTail(k)) continue;
1309
- const line = this.lineText(k);
1310
- const trimmedLine = trimFenceLine(line);
1311
- if (trimmedLine.startsWith("```")) {
1312
- const at2 = line.indexOf("```");
1313
- if (isJson) {
1314
- const probe = jsonStringState(line, 0, at2, state);
1315
- if (probe.inString) {
1316
- state = jsonStringState(line, at2, line.length, probe);
1317
- continue;
1318
- }
1319
- }
1320
- const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
1321
- if (kind === "close-outer") return k;
1322
- if (kind === "open-nested") {
1323
- nestedDepth++;
1324
- sawNested = true;
1325
- }
1326
- if (kind === "close-nested") nestedDepth--;
1327
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1328
- continue;
1329
- }
1330
- const at = line.indexOf("```");
1331
- if (at !== -1) {
1332
- if (isJson) {
1333
- const probe = jsonStringState(line, 0, at, state);
1334
- if (probe.inString) {
1335
- state = jsonStringState(line, at, line.length, probe);
1336
- continue;
1337
- }
1338
- }
1339
- const closeTicks = backtickRunLength(line, at);
1340
- const after = trimFenceLine(line.slice(at + closeTicks));
1341
- if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
1342
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1343
- continue;
1344
- }
1345
- if (isJson) state = jsonStringState(line, 0, line.length, state);
1346
- }
1347
- return sawNested && nestedDepth > 0 ? "retry-strict" : null;
1348
- }
1349
1456
  fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
1350
1457
  if (isJson && openLine + 1 < this.lineCount) {
1351
1458
  const bodyEnd = closeLine < this.lineCount ? this.lineStarts[closeLine] : this.n;
@@ -1354,22 +1461,16 @@ var Tokenizer = class {
1354
1461
  }
1355
1462
  return this.island("fence", p, end, complete, meta);
1356
1463
  }
1357
- tildeFence(p, li, t) {
1358
- const open = /^(~{3,})(.*)$/.exec(t);
1359
- if (!open) return null;
1360
- const ticks = open[1].length;
1361
- const lang = open[2].trim().split(/\s/)[0] ?? "";
1362
- const meta = { fence: "~", ticks, lang };
1464
+ tildeFence(p, li, opener) {
1465
+ const lang = opener.lang;
1466
+ const meta = { fence: "~", ticks: opener.ticks, lang };
1363
1467
  const normalized = normalizeFenceLanguage(lang);
1364
1468
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
1365
- const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
1366
- for (let k = li + 1; k < this.lineCount; k++) {
1367
- if (this.isOpenTail(k)) continue;
1368
- if (close.test(this.lineText(k))) {
1369
- const tildeEnd = this.contentEnds[k];
1370
- const end = this.rendererOverflow(li + 1, k, tildeEnd);
1371
- return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
1372
- }
1469
+ const close = closeFence(this.fenceLines, li, opener);
1470
+ if (close.closed) {
1471
+ const tildeEnd = this.contentEnds[close.line];
1472
+ const end = this.rendererOverflow(li + 1, close.line, tildeEnd);
1473
+ return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
1373
1474
  }
1374
1475
  return this.island("fence", p, this.n, false, meta);
1375
1476
  }
@@ -1389,7 +1490,7 @@ var Tokenizer = class {
1389
1490
  for (let j = firstLine; j <= closeLine && j < this.lineCount; j++) {
1390
1491
  const start = this.lineStarts[j];
1391
1492
  if (this.lineIsBlank(j)) continue;
1392
- if (this.lineText(j).trimStart().startsWith("~~~")) continue;
1493
+ if (fenceOpenerOf(this.lineText(j))?.char === "~") continue;
1393
1494
  const inner = this.detectBlockUncached(start, j, false);
1394
1495
  if (!inner) continue;
1395
1496
  if (inner.end > end) {
@@ -1637,9 +1738,9 @@ var Tokenizer = class {
1637
1738
  const tree = { ...breakIsland, start: treeStart };
1638
1739
  if (treeStart > pos) {
1639
1740
  const proseEnd = this.contentEnds[root - 1];
1640
- const scan2 = this.scanInline(pos, proseEnd);
1641
- if (!scan2.breakIsland) {
1642
- this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
1741
+ const scan3 = this.scanInline(pos, proseEnd);
1742
+ if (!scan3.breakIsland) {
1743
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan3.inlines });
1643
1744
  this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
1644
1745
  this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
1645
1746
  return tree.end;
@@ -1651,13 +1752,13 @@ var Tokenizer = class {
1651
1752
  }
1652
1753
  }
1653
1754
  }
1654
- const scan = this.scanInline(pos, end);
1655
- if (!scan.breakIsland) {
1656
- this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
1755
+ const scan2 = this.scanInline(pos, end);
1756
+ if (!scan2.breakIsland) {
1757
+ this.push({ kind: "prose", start: pos, end, island: null, inlines: scan2.inlines });
1657
1758
  return end;
1658
1759
  }
1659
- const br = scan.breakIsland;
1660
- if (scan.orphan) {
1760
+ const br = scan2.breakIsland;
1761
+ if (scan2.orphan) {
1661
1762
  let start = pos;
1662
1763
  while (this.blocks.length) {
1663
1764
  const last = this.blocks[this.blocks.length - 1];
@@ -1674,7 +1775,7 @@ var Tokenizer = class {
1674
1775
  start: pos,
1675
1776
  end: br.start,
1676
1777
  island: null,
1677
- inlines: scan.inlines.filter((inline) => inline.end <= br.start)
1778
+ inlines: scan2.inlines.filter((inline) => inline.end <= br.start)
1678
1779
  });
1679
1780
  this.push({ kind: "island", start: br.start, end: br.end, island: br, inlines: [] });
1680
1781
  return br.end;
@@ -1694,30 +1795,10 @@ var Tokenizer = class {
1694
1795
  const codeSpanClose = (n, from) => {
1695
1796
  if (!runs) {
1696
1797
  this.tick(pe - ps);
1697
- runs = /* @__PURE__ */ new Map();
1698
- for (let k = ps; k < pe; ) {
1699
- if (text.charCodeAt(k) !== 96) {
1700
- k++;
1701
- continue;
1702
- }
1703
- const len = backtickRunLength(text, k);
1704
- const list2 = runs.get(len);
1705
- if (list2) list2.push(k);
1706
- else runs.set(len, [k]);
1707
- k += len;
1708
- }
1709
- }
1710
- const list = runs.get(n);
1711
- if (!list) return -1;
1712
- let lo = 0;
1713
- let hi = list.length;
1714
- while (lo < hi) {
1715
- const mid = lo + hi >>> 1;
1716
- if (list[mid] < from) lo = mid + 1;
1717
- else hi = mid;
1798
+ runs = new BacktickRuns(text, ps, pe);
1718
1799
  }
1719
- const at = list[lo];
1720
- return at !== void 0 && at + n <= pe ? at : -1;
1800
+ const at = runs.next(n, from);
1801
+ return at !== -1 && at + n <= pe ? at : -1;
1721
1802
  };
1722
1803
  let i = ps;
1723
1804
  const scanEnd = Math.min(pe, this.provisionalFrom);
@@ -2254,6 +2335,6 @@ function mapRange(changes, start, end, unit = "utf16") {
2254
2335
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
2255
2336
  }
2256
2337
 
2257
- export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, findCodeRanges, isMathPlaceholder, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
2338
+ export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, findCodeRanges, isMathPlaceholder, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, looksLikeOpenInlineMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
2258
2339
  //# sourceMappingURL=source.js.map
2259
2340
  //# sourceMappingURL=source.js.map