@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/index.cjs CHANGED
@@ -5227,75 +5227,231 @@ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
5227
5227
  return "content";
5228
5228
  }
5229
5229
 
5230
- // source/math.ts
5231
- var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
5232
- var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
5233
- var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
5234
- var FUNCTION_NAMES = /* @__PURE__ */ new Set([
5235
- "sin",
5236
- "cos",
5237
- "tan",
5238
- "sec",
5239
- "csc",
5240
- "cot",
5241
- "log",
5242
- "ln",
5243
- "exp",
5244
- "lim",
5245
- "max",
5246
- "min",
5247
- "det",
5248
- "gcd",
5249
- "mod",
5250
- "arg",
5251
- "sinh",
5252
- "cosh",
5253
- "tanh"
5254
- ]);
5255
- function isMathShape(content) {
5256
- if (!MATH_SHAPE_CHARS.test(content)) return false;
5257
- const words = content.match(/[A-Za-z]+/g);
5258
- if (!words) return false;
5259
- if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
5260
- if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
5261
- return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
5230
+ // source/code-ranges.ts
5231
+ function fenceOpenerOf(content) {
5232
+ const t = content.trimStart();
5233
+ if (t.startsWith("```")) {
5234
+ let ticks = 0;
5235
+ while (ticks < t.length && t[ticks] === "`") ticks++;
5236
+ return { char: "`", ticks, lang: parseFenceOpener(t)?.lang ?? "" };
5237
+ }
5238
+ if (content.length - t.length <= 3 && t.startsWith("~~~")) {
5239
+ const open = /^(~{3,})(.*)$/.exec(t);
5240
+ if (!open) return null;
5241
+ return { char: "~", ticks: open[1].length, lang: open[2].trim().split(/\s/)[0] ?? "" };
5242
+ }
5243
+ return null;
5262
5244
  }
5263
- function isSingleDollarMath(content, after) {
5264
- if (!content || content.length > 400) return false;
5265
- if (/^\s/.test(content) || /\s$/.test(content)) return false;
5266
- if (after !== void 0 && /[0-9]/.test(after)) return false;
5267
- if (LONE_VARIABLE.test(content)) return true;
5268
- if (/^[0-9]/.test(content)) return /\\[A-Za-z]+|[\\^_{}]/.test(content);
5269
- return TEX_SIGNAL.test(content) || isMathShape(content);
5245
+ var FenceReader = class {
5246
+ constructor(opener, options = {}) {
5247
+ this.opener = opener;
5248
+ this.isJson = opener.char === "`" && opener.lang === "json";
5249
+ this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
5250
+ this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
5251
+ }
5252
+ opener;
5253
+ state = { inString: false, escaped: false };
5254
+ depth = 0;
5255
+ /** A nested fence was opened at some point. */
5256
+ sawNested = false;
5257
+ isJson;
5258
+ nesting;
5259
+ tildeCloser;
5260
+ /** How many nested fences are open right now. */
5261
+ get nestedDepth() {
5262
+ return this.depth;
5263
+ }
5264
+ feed(line) {
5265
+ if (this.tildeCloser) return this.tildeCloser.test(line);
5266
+ const trimmedLine = trimFenceLine(line);
5267
+ const at = line.indexOf("```");
5268
+ if (at === -1) {
5269
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
5270
+ return false;
5271
+ }
5272
+ if (this.isJson) {
5273
+ const probe = jsonStringState(line, 0, at, this.state);
5274
+ if (probe.inString) {
5275
+ this.state = jsonStringState(line, at, line.length, probe);
5276
+ return false;
5277
+ }
5278
+ }
5279
+ if (trimmedLine.startsWith("```")) {
5280
+ const kind = classifyInnerFenceLine(trimmedLine, this.opener.ticks, this.nesting, this.depth);
5281
+ if (kind === "close-outer") return true;
5282
+ if (kind === "open-nested") {
5283
+ this.depth++;
5284
+ this.sawNested = true;
5285
+ }
5286
+ if (kind === "close-nested") this.depth--;
5287
+ } else {
5288
+ const closeTicks = backtickRunLength(line, at);
5289
+ const after = trimFenceLine(line.slice(at + closeTicks));
5290
+ if (closeTicks >= this.opener.ticks && after === "" && this.depth === 0) return true;
5291
+ }
5292
+ if (this.isJson) this.state = jsonStringState(line, 0, line.length, this.state);
5293
+ return false;
5294
+ }
5295
+ };
5296
+ function closeFence(src, open, opener) {
5297
+ const first = readFence(src, open, new FenceReader(opener));
5298
+ if (first.closed || !first.reader.sawNested || first.reader.nestedDepth <= 0) return { line: first.line, closed: first.closed };
5299
+ const strict = readFence(src, open, new FenceReader(opener, { nesting: false }));
5300
+ return { line: strict.line, closed: strict.closed };
5270
5301
  }
5271
- function singleDollarMathEnd(text, open, limit = text.length) {
5272
- const bound = Math.min(limit, open + 403);
5273
- let close = -1;
5274
- for (let k = open + 1; k < bound; k += 1) {
5275
- const c = text[k];
5276
- if (c === "\n") break;
5277
- if (c === "\\") {
5278
- k += 1;
5302
+ function readFence(src, open, reader) {
5303
+ for (let k = open + 1; k < src.count; k++) {
5304
+ if (src.openTail?.(k)) continue;
5305
+ const line = src.line(k);
5306
+ if (line === null) return { line: k - 1, closed: false, reader };
5307
+ if (reader.feed(line)) return { line: k, closed: true, reader };
5308
+ }
5309
+ return { line: src.count - 1, closed: false, reader };
5310
+ }
5311
+ function jsonStringState(line, from, upTo, state) {
5312
+ let { inString, escaped } = state;
5313
+ for (let idx = from; idx < upTo; idx++) {
5314
+ const ch = line[idx];
5315
+ if (escaped) {
5316
+ escaped = false;
5279
5317
  continue;
5280
5318
  }
5281
- if (c === "$") {
5282
- close = k;
5283
- break;
5319
+ if (ch === "\\" && inString) {
5320
+ escaped = true;
5321
+ continue;
5284
5322
  }
5323
+ if (ch === '"') inString = !inString;
5285
5324
  }
5286
- if (close === -1 || text[close + 1] === "$") return -1;
5287
- const content = text.slice(open + 1, close);
5288
- return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
5325
+ return { inString, escaped };
5289
5326
  }
5290
-
5291
- // source/code-ranges.ts
5292
- function findCodeRanges(text) {
5293
- const ranges = [];
5294
- const n = text.length;
5295
- let fence = null;
5327
+ function backtickRunLength(str2, pos) {
5328
+ let n = 0;
5329
+ while (pos + n < str2.length && str2[pos + n] === "`") n++;
5330
+ return n;
5331
+ }
5332
+ function quotePrefix(line) {
5333
+ let depth = 0;
5334
+ let offset = 0;
5335
+ for (; ; ) {
5336
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
5337
+ if (!m) return { depth, offset };
5338
+ depth++;
5339
+ offset += m[0].length;
5340
+ }
5341
+ }
5342
+ function stripQuotes(line, depth) {
5343
+ let offset = 0;
5344
+ for (let d = 0; d < depth; d++) {
5345
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
5346
+ if (!m) return null;
5347
+ offset += m[0].length;
5348
+ }
5349
+ return line.slice(offset);
5350
+ }
5351
+ var LIST_ITEM = /^( *)([-*+]|\d{1,9}[.)])( +|\t|$)/;
5352
+ function leadingSpaces(s) {
5353
+ let i = 0;
5354
+ while (i < s.length && s[i] === " ") i++;
5355
+ return i;
5356
+ }
5357
+ function scan(text) {
5358
+ const starts = [];
5359
+ const ends = [];
5360
+ for (let pos = 0; ; ) {
5361
+ const nl = text.indexOf("\n", pos);
5362
+ const lineEnd = nl === -1 ? text.length : nl;
5363
+ starts.push(pos);
5364
+ ends.push(lineEnd > pos && text.charCodeAt(lineEnd - 1) === 13 ? lineEnd - 1 : lineEnd);
5365
+ if (nl === -1) break;
5366
+ pos = nl + 1;
5367
+ }
5368
+ const count = starts.length;
5369
+ const raw = (k) => text.slice(starts[k], ends[k]);
5370
+ const out = { ranges: [], paragraphs: [], endsInFence: false, pendingPara: -1 };
5371
+ const rawLines = { count, line: raw };
5372
+ let segment = 0;
5373
+ for (let k = 0; k < count; k++) {
5374
+ const opener = fenceOpenerOf(raw(k));
5375
+ if (!opener || opener.char !== "`") continue;
5376
+ prose(text, starts, ends, raw, segment, k, out);
5377
+ const end = closeFence(rawLines, k, opener);
5378
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
5379
+ out.pendingPara = -1;
5380
+ if (!end.closed && end.line === count - 1) out.endsInFence = true;
5381
+ k = end.line;
5382
+ segment = k + 1;
5383
+ }
5384
+ prose(text, starts, ends, raw, segment, count, out);
5385
+ return out;
5386
+ }
5387
+ function prose(text, starts, ends, raw, from, to, out) {
5296
5388
  let para = -1;
5297
- const spans = (from, to) => {
5298
- const runs = [];
5389
+ let quoteDepth = 0;
5390
+ let lists = [];
5391
+ const closePara = (at) => {
5392
+ if (para !== -1) out.paragraphs.push([para, at]);
5393
+ para = -1;
5394
+ };
5395
+ for (let k = from; k < to; k++) {
5396
+ const line = raw(k);
5397
+ const { depth, offset } = quotePrefix(line);
5398
+ if (depth !== quoteDepth) {
5399
+ quoteDepth = depth;
5400
+ lists = [];
5401
+ }
5402
+ let content = line.slice(offset);
5403
+ if (content.trim() === "") {
5404
+ closePara(starts[k]);
5405
+ if (!(k === starts.length - 1 && line === "")) out.pendingPara = -1;
5406
+ continue;
5407
+ }
5408
+ const indent = leadingSpaces(content);
5409
+ while (lists.length && indent < lists[lists.length - 1]) lists.pop();
5410
+ const item = LIST_ITEM.exec(content);
5411
+ let column = lists.length ? lists[lists.length - 1] : 0;
5412
+ if (item) {
5413
+ const gap = item[3].length;
5414
+ column = item[1].length + item[2].length + (gap >= 1 && gap <= 4 ? gap : 1);
5415
+ lists.push(column);
5416
+ content = content.slice(Math.min(column, content.length));
5417
+ } else {
5418
+ content = content.slice(Math.min(indent, column));
5419
+ }
5420
+ const opener = fenceOpenerOf(content);
5421
+ if (!opener) {
5422
+ if (para === -1) para = starts[k];
5423
+ if (out.pendingPara === -1) out.pendingPara = starts[k];
5424
+ continue;
5425
+ }
5426
+ closePara(starts[k]);
5427
+ out.pendingPara = -1;
5428
+ const col = column;
5429
+ const src = {
5430
+ count: to,
5431
+ line: (j) => {
5432
+ const inner = stripQuotes(raw(j), depth);
5433
+ if (inner === null) return null;
5434
+ if (opener.char === "~" && col > 0) {
5435
+ const ind = leadingSpaces(inner);
5436
+ if (inner.trim() !== "" && ind < col) return null;
5437
+ return inner.slice(Math.min(ind, col));
5438
+ }
5439
+ return inner;
5440
+ }
5441
+ };
5442
+ const end = closeFence(src, k, opener);
5443
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
5444
+ if (!end.closed && end.line === starts.length - 1) out.endsInFence = true;
5445
+ k = end.line;
5446
+ }
5447
+ closePara(to < starts.length ? starts[to] : text.length);
5448
+ }
5449
+ var BacktickRuns = class {
5450
+ /** Every run, in order: [start, length]. */
5451
+ all = [];
5452
+ byLength = /* @__PURE__ */ new Map();
5453
+ indexOf = /* @__PURE__ */ new Map();
5454
+ constructor(text, from, to) {
5299
5455
  for (let k = from; k < to; ) {
5300
5456
  if (text.charCodeAt(k) !== 96) {
5301
5457
  k++;
@@ -5303,117 +5459,57 @@ function findCodeRanges(text) {
5303
5459
  }
5304
5460
  let len = 1;
5305
5461
  while (k + len < to && text.charCodeAt(k + len) === 96) len++;
5306
- runs.push([k, len]);
5462
+ this.indexOf.set(k, this.all.length);
5463
+ this.all.push([k, len]);
5464
+ const list = this.byLength.get(len);
5465
+ if (list) list.push(k);
5466
+ else this.byLength.set(len, [k]);
5307
5467
  k += len;
5308
5468
  }
5309
- const nextSame = new Array(runs.length).fill(-1);
5310
- const last = /* @__PURE__ */ new Map();
5311
- for (let r = runs.length - 1; r >= 0; r--) {
5312
- const len = runs[r][1];
5313
- nextSame[r] = last.get(len) ?? -1;
5314
- last.set(len, r);
5315
- }
5316
- for (let r = 0; r < runs.length; ) {
5317
- const j = nextSame[r];
5318
- if (j === -1) {
5319
- r++;
5320
- continue;
5321
- }
5322
- const [s0] = runs[r];
5323
- const [s1, l1] = runs[j];
5324
- ranges.push({ start: s0, end: s1 + l1, kind: "span" });
5325
- r = j + 1;
5469
+ }
5470
+ /** Start of the first run of exactly `n` backticks at or after `after`, or -1. */
5471
+ next(n, after) {
5472
+ const list = this.byLength.get(n);
5473
+ if (!list) return -1;
5474
+ let lo = 0;
5475
+ let hi = list.length;
5476
+ while (lo < hi) {
5477
+ const mid = lo + hi >>> 1;
5478
+ if (list[mid] < after) lo = mid + 1;
5479
+ else hi = mid;
5326
5480
  }
5327
- };
5328
- let pos = 0;
5329
- while (pos <= n) {
5330
- const nl = text.indexOf("\n", pos);
5331
- const lineEnd = nl === -1 ? n : nl;
5332
- const line = text.slice(pos, lineEnd);
5333
- let indent = 0;
5334
- while (indent < line.length && line[indent] === " ") indent++;
5335
- const body = line.slice(indent);
5336
- if (fence) {
5337
- const trimmed = trimFenceLine(line);
5338
- let closes = false;
5339
- if (fence.char === "`") {
5340
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
5341
- if (kind === "open-nested") fence.depth++;
5342
- else if (kind === "close-nested") fence.depth--;
5343
- else if (kind === "close-outer") closes = indent <= 3;
5344
- } else {
5345
- let t = 0;
5346
- while (t < trimmed.length && trimmed[t] === "~") t++;
5347
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
5348
- }
5349
- if (closes) {
5350
- ranges.push({ start: fence.start, end: lineEnd, kind: "fence" });
5351
- fence = null;
5352
- }
5353
- } else {
5354
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
5355
- const info = opener ? opener[2] : "";
5356
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
5357
- if (para !== -1) spans(para, pos);
5358
- para = -1;
5359
- const char = opener[1][0];
5360
- const parsed = char === "`" ? parseFenceOpener(line) : null;
5361
- fence = { char, len: opener[1].length, start: pos, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
5362
- } else if (body.trim() === "") {
5363
- if (para !== -1) spans(para, pos);
5364
- para = -1;
5365
- } else if (para === -1) para = pos;
5481
+ return list[lo] ?? -1;
5482
+ }
5483
+ /** Index of the run starting at `start` in `all`. */
5484
+ index(start) {
5485
+ return this.indexOf.get(start) ?? -1;
5486
+ }
5487
+ };
5488
+ function codeSpans(text, from, to, out) {
5489
+ const runs = new BacktickRuns(text, from, to);
5490
+ for (let r = 0; r < runs.all.length; ) {
5491
+ const [start, n] = runs.all[r];
5492
+ const close = runs.next(n, start + n);
5493
+ if (close === -1) {
5494
+ r++;
5495
+ continue;
5366
5496
  }
5367
- if (nl === -1) break;
5368
- pos = nl + 1;
5497
+ out.push({ start, end: close + n, kind: "span" });
5498
+ r = runs.index(close) + 1;
5369
5499
  }
5370
- if (fence) ranges.push({ start: fence.start, end: n, kind: "fence" });
5371
- else if (para !== -1) spans(para, n);
5500
+ }
5501
+ function findCodeRanges(text) {
5502
+ const s = scan(text);
5503
+ const ranges = [...s.ranges];
5504
+ for (const [from, to] of s.paragraphs) codeSpans(text, from, to, ranges);
5372
5505
  return ranges.sort((a, b) => a.start - b.start);
5373
5506
  }
5374
5507
  function pendingCodeSpanStart(text) {
5375
5508
  const n = text.length;
5376
5509
  if (n === 0 || /\n[ \t]*\n[ \t]*$/.test(text)) return -1;
5377
- let fence = null;
5378
- let para = -1;
5379
- let pos = 0;
5380
- while (pos <= n) {
5381
- const nl = text.indexOf("\n", pos);
5382
- const lineEnd = nl === -1 ? n : nl;
5383
- const line = text.slice(pos, lineEnd);
5384
- let indent = 0;
5385
- while (indent < line.length && line[indent] === " ") indent++;
5386
- const body = line.slice(indent);
5387
- if (fence) {
5388
- const trimmed = trimFenceLine(line);
5389
- let closes = false;
5390
- if (fence.char === "`") {
5391
- const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
5392
- if (kind === "open-nested") fence.depth++;
5393
- else if (kind === "close-nested") fence.depth--;
5394
- else if (kind === "close-outer") closes = indent <= 3;
5395
- } else {
5396
- let t = 0;
5397
- while (t < trimmed.length && trimmed[t] === "~") t++;
5398
- closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
5399
- }
5400
- if (closes) fence = null;
5401
- } else {
5402
- const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
5403
- const info = opener ? opener[2] : "";
5404
- if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
5405
- para = -1;
5406
- const char = opener[1][0];
5407
- const parsed = char === "`" ? parseFenceOpener(line) : null;
5408
- fence = { char, len: opener[1].length, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
5409
- } else if (body.trim() === "") {
5410
- if (!(nl === -1 && line === "")) para = -1;
5411
- } else if (para === -1) para = pos;
5412
- }
5413
- if (nl === -1) break;
5414
- pos = nl + 1;
5415
- }
5416
- if (fence || para === -1) return -1;
5510
+ const s = scan(text);
5511
+ const para = s.pendingPara;
5512
+ if (s.endsInFence || para === -1) return -1;
5417
5513
  const runs = [];
5418
5514
  for (let k = para; k < n; ) {
5419
5515
  if (text.charCodeAt(k) !== 96) {
@@ -5550,6 +5646,83 @@ function pairDisplayMath(text) {
5550
5646
  return pairs;
5551
5647
  }
5552
5648
 
5649
+ // source/math.ts
5650
+ var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
5651
+ var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
5652
+ var MATH_SHAPE_CHARS = /^[A-Za-z0-9'\s(),.*\/-]+$/;
5653
+ function isSingleDollarMath(content, after) {
5654
+ if (!content || content.length > 400) return false;
5655
+ if (/^\s/.test(content) || /\s$/.test(content)) return false;
5656
+ if (after !== void 0 && /[0-9]/.test(after)) return false;
5657
+ if (/(?:^|[^\\])%/.test(content)) return false;
5658
+ if (/^[)\]}"/]/.test(content) || /[(\[{":;,/]$/.test(content)) return false;
5659
+ if (/^[0-9]/.test(content) && after !== void 0 && /[A-Za-z]/.test(after)) return false;
5660
+ if (isMathPlaceholder(content)) return false;
5661
+ return TEX_SIGNAL.test(content) || !readsAsProse(content);
5662
+ }
5663
+ var FUNCTION_NAMES = /* @__PURE__ */ new Set([
5664
+ "sin",
5665
+ "cos",
5666
+ "tan",
5667
+ "sec",
5668
+ "csc",
5669
+ "cot",
5670
+ "log",
5671
+ "ln",
5672
+ "exp",
5673
+ "lim",
5674
+ "max",
5675
+ "min",
5676
+ "det",
5677
+ "gcd",
5678
+ "mod",
5679
+ "arg",
5680
+ "sinh",
5681
+ "cosh",
5682
+ "tanh"
5683
+ ]);
5684
+ function readsAsProse(content) {
5685
+ if (/[A-Za-z]'[a-z]/.test(content)) return true;
5686
+ if (/(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return true;
5687
+ if (!/\s/.test(content)) return false;
5688
+ const words = (content.match(/[A-Za-z]{3,}/g) ?? []).filter((w) => !FUNCTION_NAMES.has(w.toLowerCase()));
5689
+ return words.length >= 2;
5690
+ }
5691
+ function looksLikeOpenInlineMath(partial) {
5692
+ if (!partial || partial.length > 400) return false;
5693
+ if (/^\s/.test(partial) || /\s$/.test(partial)) return false;
5694
+ if (LONE_VARIABLE.test(partial)) return true;
5695
+ if (/^[0-9]/.test(partial)) return /\\[A-Za-z]+|[\\^_{}]/.test(partial);
5696
+ return TEX_SIGNAL.test(partial) || isMathShape(partial);
5697
+ }
5698
+ function isMathShape(content) {
5699
+ if (!MATH_SHAPE_CHARS.test(content)) return false;
5700
+ const words = content.match(/[A-Za-z]+/g);
5701
+ if (!words) return false;
5702
+ if (!words.every((w) => w.length === 1 || FUNCTION_NAMES.has(w))) return false;
5703
+ if (/[A-Za-z]'+[A-Za-z]/.test(content) || /(?:^|[\s(,])[A-Za-z]\.(?:[A-Za-z]\.|\s|$)/.test(content)) return false;
5704
+ return /[A-Za-z]'*\(|[A-Za-z]'|\b[A-Za-z]\s+[A-Za-z]\b|\([^)]*[A-Za-z][^)]*\)/.test(content);
5705
+ }
5706
+ function singleDollarMathEnd(text, open, limit = text.length) {
5707
+ const bound = Math.min(limit, open + 403);
5708
+ let close = -1;
5709
+ for (let k = open + 1; k < bound; k += 1) {
5710
+ const c = text[k];
5711
+ if (c === "\n") break;
5712
+ if (c === "\\") {
5713
+ k += 1;
5714
+ continue;
5715
+ }
5716
+ if (c === "$") {
5717
+ close = k;
5718
+ break;
5719
+ }
5720
+ }
5721
+ if (close === -1 || text[close + 1] === "$") return -1;
5722
+ const content = text.slice(open + 1, close);
5723
+ return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
5724
+ }
5725
+
5553
5726
  // source/xml-tag.ts
5554
5727
  var XML_NAME_START = /[A-Za-z_]/;
5555
5728
  var XML_NAME_CHARACTER = /[\w.:-]/;
@@ -5842,27 +6015,6 @@ function isUnclosedGenericXmlOpening(source) {
5842
6015
  }
5843
6016
  return true;
5844
6017
  }
5845
- function jsonStringState(line, from, upTo, state) {
5846
- let { inString, escaped } = state;
5847
- for (let idx = from; idx < upTo; idx++) {
5848
- const ch = line[idx];
5849
- if (escaped) {
5850
- escaped = false;
5851
- continue;
5852
- }
5853
- if (ch === "\\" && inString) {
5854
- escaped = true;
5855
- continue;
5856
- }
5857
- if (ch === '"') inString = !inString;
5858
- }
5859
- return { inString, escaped };
5860
- }
5861
- function backtickRunLength(str2, pos) {
5862
- let n = 0;
5863
- while (pos + n < str2.length && str2[pos + n] === "`") n++;
5864
- return n;
5865
- }
5866
6018
  function isSplitterTreeLine(line) {
5867
6019
  return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
5868
6020
  }
@@ -6094,11 +6246,9 @@ var Tokenizer = class {
6094
6246
  const footnote = this.footnoteDefinition(p, li, t);
6095
6247
  if (footnote) return footnote;
6096
6248
  }
6097
- if (t.startsWith("```")) return this.backtickFence(p, li, t);
6098
- if (indent <= 3 && t.startsWith("~~~")) {
6099
- const tilde = this.tildeFence(p, li, t);
6100
- if (tilde) return tilde;
6101
- }
6249
+ const fence = fenceOpenerOf(seg);
6250
+ if (fence?.char === "`") return this.backtickFence(p, li, fence);
6251
+ if (fence?.char === "~") return this.tildeFence(p, li, fence);
6102
6252
  if (t.startsWith("$$")) {
6103
6253
  const math = this.mathBlock(p, tp);
6104
6254
  if (math) return math;
@@ -6253,74 +6403,31 @@ var Tokenizer = class {
6253
6403
  heldFootnote(p, id) {
6254
6404
  return this.island("footnote_def", p, this.n, false, { id });
6255
6405
  }
6406
+ /** The text's lines as the one fence closer reads them (streaming: the open tail closes nothing). */
6407
+ get fenceLines() {
6408
+ return {
6409
+ count: this.lineCount,
6410
+ line: (k) => this.lineText(k),
6411
+ openTail: (k) => this.isOpenTail(k)
6412
+ };
6413
+ }
6256
6414
  /**
6257
- * Backtick fence — the renderer splitter's close rules: JSON-string aware for
6258
- * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
6259
- * fences, with the splitter's strict-CommonMark retry when a nested fence is
6260
- * still open at the end of the text.
6415
+ * Backtick fence — THE one closer (`closeFence`, source/code-ranges.ts): the
6416
+ * nested-fence rule for markdown fences with the strict-CommonMark retry, the
6417
+ * renderer's line-end closer, JSON-string awareness for ```json.
6261
6418
  */
6262
- backtickFence(p, li, t) {
6263
- const openTicks = backtickRunLength(t, 0);
6264
- const lang = parseFenceOpener(t)?.lang ?? "";
6419
+ backtickFence(p, li, opener) {
6420
+ const lang = opener.lang;
6265
6421
  const isJson = lang === "json";
6266
6422
  const normalized = normalizeFenceLanguage(lang);
6267
- const meta = { fence: "`", ticks: openTicks, lang };
6423
+ const meta = { fence: "`", ticks: opener.ticks, lang };
6268
6424
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
6269
- const nesting = fenceNestsInnerFences(lang);
6270
- let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
6271
- if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
6272
- if (typeof close === "number") {
6273
- return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
6425
+ const end = closeFence(this.fenceLines, li, opener);
6426
+ if (end.closed) {
6427
+ return this.fenceIsland(p, this.contentEnds[end.line], true, meta, li, end.line, isJson);
6274
6428
  }
6275
6429
  return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
6276
6430
  }
6277
- /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
6278
- backtickFenceClose(li, openTicks, isJson, nesting) {
6279
- let state = { inString: false, escaped: false };
6280
- let nestedDepth = 0;
6281
- let sawNested = false;
6282
- for (let k = li + 1; k < this.lineCount; k++) {
6283
- if (this.isOpenTail(k)) continue;
6284
- const line = this.lineText(k);
6285
- const trimmedLine = trimFenceLine(line);
6286
- if (trimmedLine.startsWith("```")) {
6287
- const at2 = line.indexOf("```");
6288
- if (isJson) {
6289
- const probe = jsonStringState(line, 0, at2, state);
6290
- if (probe.inString) {
6291
- state = jsonStringState(line, at2, line.length, probe);
6292
- continue;
6293
- }
6294
- }
6295
- const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
6296
- if (kind === "close-outer") return k;
6297
- if (kind === "open-nested") {
6298
- nestedDepth++;
6299
- sawNested = true;
6300
- }
6301
- if (kind === "close-nested") nestedDepth--;
6302
- if (isJson) state = jsonStringState(line, 0, line.length, state);
6303
- continue;
6304
- }
6305
- const at = line.indexOf("```");
6306
- if (at !== -1) {
6307
- if (isJson) {
6308
- const probe = jsonStringState(line, 0, at, state);
6309
- if (probe.inString) {
6310
- state = jsonStringState(line, at, line.length, probe);
6311
- continue;
6312
- }
6313
- }
6314
- const closeTicks = backtickRunLength(line, at);
6315
- const after = trimFenceLine(line.slice(at + closeTicks));
6316
- if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
6317
- if (isJson) state = jsonStringState(line, 0, line.length, state);
6318
- continue;
6319
- }
6320
- if (isJson) state = jsonStringState(line, 0, line.length, state);
6321
- }
6322
- return sawNested && nestedDepth > 0 ? "retry-strict" : null;
6323
- }
6324
6431
  fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
6325
6432
  if (isJson && openLine + 1 < this.lineCount) {
6326
6433
  const bodyEnd = closeLine < this.lineCount ? this.lineStarts[closeLine] : this.n;
@@ -6329,22 +6436,16 @@ var Tokenizer = class {
6329
6436
  }
6330
6437
  return this.island("fence", p, end, complete, meta);
6331
6438
  }
6332
- tildeFence(p, li, t) {
6333
- const open = /^(~{3,})(.*)$/.exec(t);
6334
- if (!open) return null;
6335
- const ticks = open[1].length;
6336
- const lang = open[2].trim().split(/\s/)[0] ?? "";
6337
- const meta = { fence: "~", ticks, lang };
6439
+ tildeFence(p, li, opener) {
6440
+ const lang = opener.lang;
6441
+ const meta = { fence: "~", ticks: opener.ticks, lang };
6338
6442
  const normalized = normalizeFenceLanguage(lang);
6339
6443
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
6340
- const close = new RegExp(`^ {0,3}~{${ticks},}[ \\t]*$`);
6341
- for (let k = li + 1; k < this.lineCount; k++) {
6342
- if (this.isOpenTail(k)) continue;
6343
- if (close.test(this.lineText(k))) {
6344
- const tildeEnd = this.contentEnds[k];
6345
- const end = this.rendererOverflow(li + 1, k, tildeEnd);
6346
- return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
6347
- }
6444
+ const close = closeFence(this.fenceLines, li, opener);
6445
+ if (close.closed) {
6446
+ const tildeEnd = this.contentEnds[close.line];
6447
+ const end = this.rendererOverflow(li + 1, close.line, tildeEnd);
6448
+ return this.island("fence", p, end, end === this.n ? this.overflowComplete : true, meta);
6348
6449
  }
6349
6450
  return this.island("fence", p, this.n, false, meta);
6350
6451
  }
@@ -6364,7 +6465,7 @@ var Tokenizer = class {
6364
6465
  for (let j = firstLine; j <= closeLine && j < this.lineCount; j++) {
6365
6466
  const start = this.lineStarts[j];
6366
6467
  if (this.lineIsBlank(j)) continue;
6367
- if (this.lineText(j).trimStart().startsWith("~~~")) continue;
6468
+ if (fenceOpenerOf(this.lineText(j))?.char === "~") continue;
6368
6469
  const inner = this.detectBlockUncached(start, j, false);
6369
6470
  if (!inner) continue;
6370
6471
  if (inner.end > end) {
@@ -6612,9 +6713,9 @@ var Tokenizer = class {
6612
6713
  const tree = { ...breakIsland, start: treeStart };
6613
6714
  if (treeStart > pos) {
6614
6715
  const proseEnd = this.contentEnds[root - 1];
6615
- const scan2 = this.scanInline(pos, proseEnd);
6616
- if (!scan2.breakIsland) {
6617
- this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
6716
+ const scan3 = this.scanInline(pos, proseEnd);
6717
+ if (!scan3.breakIsland) {
6718
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan3.inlines });
6618
6719
  this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
6619
6720
  this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
6620
6721
  return tree.end;
@@ -6626,13 +6727,13 @@ var Tokenizer = class {
6626
6727
  }
6627
6728
  }
6628
6729
  }
6629
- const scan = this.scanInline(pos, end);
6630
- if (!scan.breakIsland) {
6631
- this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
6730
+ const scan2 = this.scanInline(pos, end);
6731
+ if (!scan2.breakIsland) {
6732
+ this.push({ kind: "prose", start: pos, end, island: null, inlines: scan2.inlines });
6632
6733
  return end;
6633
6734
  }
6634
- const br = scan.breakIsland;
6635
- if (scan.orphan) {
6735
+ const br = scan2.breakIsland;
6736
+ if (scan2.orphan) {
6636
6737
  let start = pos;
6637
6738
  while (this.blocks.length) {
6638
6739
  const last = this.blocks[this.blocks.length - 1];
@@ -6649,7 +6750,7 @@ var Tokenizer = class {
6649
6750
  start: pos,
6650
6751
  end: br.start,
6651
6752
  island: null,
6652
- inlines: scan.inlines.filter((inline) => inline.end <= br.start)
6753
+ inlines: scan2.inlines.filter((inline) => inline.end <= br.start)
6653
6754
  });
6654
6755
  this.push({ kind: "island", start: br.start, end: br.end, island: br, inlines: [] });
6655
6756
  return br.end;
@@ -6669,30 +6770,10 @@ var Tokenizer = class {
6669
6770
  const codeSpanClose = (n, from) => {
6670
6771
  if (!runs) {
6671
6772
  this.tick(pe - ps);
6672
- runs = /* @__PURE__ */ new Map();
6673
- for (let k = ps; k < pe; ) {
6674
- if (text.charCodeAt(k) !== 96) {
6675
- k++;
6676
- continue;
6677
- }
6678
- const len = backtickRunLength(text, k);
6679
- const list2 = runs.get(len);
6680
- if (list2) list2.push(k);
6681
- else runs.set(len, [k]);
6682
- k += len;
6683
- }
6773
+ runs = new BacktickRuns(text, ps, pe);
6684
6774
  }
6685
- const list = runs.get(n);
6686
- if (!list) return -1;
6687
- let lo = 0;
6688
- let hi = list.length;
6689
- while (lo < hi) {
6690
- const mid = lo + hi >>> 1;
6691
- if (list[mid] < from) lo = mid + 1;
6692
- else hi = mid;
6693
- }
6694
- const at = list[lo];
6695
- return at !== void 0 && at + n <= pe ? at : -1;
6775
+ const at = runs.next(n, from);
6776
+ return at !== -1 && at + n <= pe ? at : -1;
6696
6777
  };
6697
6778
  let i = ps;
6698
6779
  const scanEnd = Math.min(pe, this.provisionalFrom);
@@ -7328,6 +7409,7 @@ exports.legacyShellUses = legacyShellUses;
7328
7409
  exports.listIslands = listIslands;
7329
7410
  exports.looksLikeDirectiveHead = looksLikeDirectiveHead;
7330
7411
  exports.looksLikeDisplayMath = looksLikeDisplayMath;
7412
+ exports.looksLikeOpenInlineMath = looksLikeOpenInlineMath;
7331
7413
  exports.makePartialKindStalenessGate = makePartialKindStalenessGate;
7332
7414
  exports.mapPosition = mapPosition;
7333
7415
  exports.mapRange = mapRange;