@ai-react-markdown/engine 2.4.2 → 2.4.5

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.dev.js CHANGED
@@ -176,6 +176,11 @@ var defaultUrlTransform = (value) => {
176
176
  import { htmlBlockNames } from "micromark-util-html-tag-name";
177
177
  import { normalizeIdentifier } from "micromark-util-normalize-identifier";
178
178
  var TYPE6_NAMES = new Set(htmlBlockNames);
179
+ var TABLE_PART_NAMES = /* @__PURE__ */ new Set(["td", "th", "tr", "tbody", "thead", "tfoot", "caption", "col", "colgroup"]);
180
+ var TYPE1_NAMES = /* @__PURE__ */ new Set(["script", "pre", "style", "textarea"]);
181
+ var TYPE6_START_RE = /^<\/?([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r]|\/?>|$)/;
182
+ var TYPE1_START_RE = /^<(script|pre|style|textarea)(?:[ \t\r]|>|$)/i;
183
+ var TYPE7_LINE_RE = /^<(\/?)([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r][^>]*|\/)?>[ \t\r]*$/;
179
184
  var VOID_TAGS = /* @__PURE__ */ new Set([
180
185
  "area",
181
186
  "base",
@@ -314,7 +319,11 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
314
319
  htmlFlowSinceBlank: false,
315
320
  htmlSeamPending: false,
316
321
  phasePoisonedAt: Infinity,
317
- pendingTruncatedTags: []
322
+ pendingTruncatedTags: [],
323
+ pendingTruncatedCloses: [],
324
+ tagAcrossLines: false,
325
+ tagAcrossLinesIndent: 0,
326
+ htmlFlowReal: false
318
327
  };
319
328
  }
320
329
  function isPlausibleLinkDefRest(rest) {
@@ -611,6 +620,8 @@ function processConfirmedLine(cp, ln, text) {
611
620
  for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
612
621
  cp.pendingTruncatedTags = [];
613
622
  }
623
+ cp.pendingTruncatedCloses = [];
624
+ cp.tagAcrossLines = false;
614
625
  cp.blankRun += 1;
615
626
  cp.lastBlankStart = ln.start;
616
627
  cp.candidates.push({
@@ -624,6 +635,7 @@ function processConfirmedLine(cp, ln, text) {
624
635
  cp.paragraphHasUnpairedRun = false;
625
636
  cp.openBracket = null;
626
637
  cp.htmlFlowSinceBlank = false;
638
+ cp.htmlFlowReal = false;
627
639
  cp.prevLineBlank = true;
628
640
  cp.prevLineWasText = false;
629
641
  cp.prevLineWasValidDef = false;
@@ -639,6 +651,16 @@ function processConfirmedLine(cp, ln, text) {
639
651
  if (tagStart) {
640
652
  cp.htmlFlowSinceBlank = true;
641
653
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
654
+ if (!cp.htmlFlowReal) {
655
+ const t = mdTrimStart(ln.text);
656
+ const t6 = TYPE6_START_RE.exec(t);
657
+ const t7 = TYPE7_LINE_RE.exec(t);
658
+ if (t6 !== null && TYPE6_NAMES.has(t6[1].toLowerCase()) || TYPE1_START_RE.test(t) || // Type 7 cannot interrupt a paragraph, and excludes the raw-text
659
+ // names (those are type 1 as start tags, paragraph as end tags).
660
+ t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7[2].toLowerCase())) {
661
+ cp.htmlFlowReal = true;
662
+ }
663
+ }
642
664
  }
643
665
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
644
666
  const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(mdTrimStart(ln.text));
@@ -779,7 +801,21 @@ ${cont(scanText)}` };
779
801
  for (const [from, to] of rawSpans) {
780
802
  tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
781
803
  }
782
- {
804
+ let skipTagScan = false;
805
+ if (cp.tagAcrossLines) {
806
+ if (ln.indent < cp.tagAcrossLinesIndent) poisonRawDivergence();
807
+ const gt = ln.text.indexOf(">");
808
+ if (gt === -1) {
809
+ skipTagScan = true;
810
+ } else {
811
+ if (/["']/.test(ln.text.slice(0, gt))) poisonRawDivergence();
812
+ for (const tag of cp.pendingTruncatedCloses) applyTag(tag, true);
813
+ cp.pendingTruncatedCloses = [];
814
+ cp.tagAcrossLines = false;
815
+ tagText = " ".repeat(gt + 1) + tagText.slice(gt + 1);
816
+ }
817
+ }
818
+ if (!skipTagScan) {
783
819
  TAG_OR_COMMENT_RE.lastIndex = 0;
784
820
  let m;
785
821
  let lastCommentOpenerIdx = -1;
@@ -809,6 +845,7 @@ ${cont(scanText)}` };
809
845
  if (cp.commentOpen) continue;
810
846
  const closing = m[1] === "/";
811
847
  const tag = m[2].toLowerCase();
848
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + m.index);
812
849
  const selfClosing = m[3] !== void 0 && /\/\s*$/.test(m[3]);
813
850
  if (VOID_TAGS.has(tag) || selfClosing) continue;
814
851
  applyTag(tag, closing);
@@ -829,6 +866,7 @@ ${cont(scanText)}` };
829
866
  if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
830
867
  const closing = mr[1] === "/";
831
868
  const tag = mr[2].toLowerCase();
869
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
832
870
  const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
833
871
  if (VOID_TAGS.has(tag) || selfClosing) continue;
834
872
  applyTag(tag, closing);
@@ -844,7 +882,14 @@ ${cont(scanText)}` };
844
882
  if (m2) {
845
883
  const closing = m2[1] === "/";
846
884
  const tag = m2[2].toLowerCase();
847
- if (!VOID_TAGS.has(tag)) {
885
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
886
+ if (cp.htmlFlowReal) {
887
+ cp.tagAcrossLines = true;
888
+ cp.tagAcrossLinesIndent = ln.indent;
889
+ }
890
+ if (closing) {
891
+ if (!VOID_TAGS.has(tag) && cp.htmlFlowReal) cp.pendingTruncatedCloses.push(tag);
892
+ } else if (!VOID_TAGS.has(tag)) {
848
893
  applyTag(tag, closing);
849
894
  const rawLastLt = ln.text.lastIndexOf("<");
850
895
  const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
@@ -1056,6 +1101,8 @@ function rebaseDualWalk(node, segments, maxEnd, offsetDelta, lineDelta) {
1056
1101
  for (const child of children) rebaseDualWalk(child, segments, maxEnd, offsetDelta, lineDelta);
1057
1102
  }
1058
1103
  }
1104
+ var TABLE_PART_TAG_RE = /<(?:td|th|tr|tbody|thead|tfoot|caption|col|colgroup)\b/i;
1105
+ var STRAY_SYNTHESIZED_END_TAG_RE = /<\/(?:br|p)\b/i;
1059
1106
  function spliceTrees(input) {
1060
1107
  const { prevMdast, prevHast, tailMdast, tailHast, content, boundary, injectionPrefix, injectedSegments } = input;
1061
1108
  const injectedLen = injectionPrefix.length;
@@ -1112,6 +1159,12 @@ function spliceTrees(input) {
1112
1159
  return !(start !== void 0 && start < injectedLen);
1113
1160
  });
1114
1161
  const tailWrapVisible = tailMdastChildren.some((child) => !isWrapInvisible(child));
1162
+ if (prefixMdast.some((c) => c.type === "html" && TABLE_PART_TAG_RE.test(c.value))) return null;
1163
+ for (const child of tailMdastChildren) {
1164
+ if (isWrapInvisible(child)) continue;
1165
+ if (child.type !== "html") break;
1166
+ if (STRAY_SYNTHESIZED_END_TAG_RE.test(child.value) || TABLE_PART_TAG_RE.test(child.value)) return null;
1167
+ }
1115
1168
  const aligned = alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible);
1116
1169
  if (aligned === null) return null;
1117
1170
  const hastChildren = aligned.children;
@@ -1237,6 +1290,9 @@ function alignPrefixCut(prefixMdast, cutRegion, tailWrapVisible) {
1237
1290
  return null;
1238
1291
  }
1239
1292
  const lastIsLiteral = last !== void 0 && last.type === "text" && last.value.trim() !== "";
1293
+ if (lastIsLiteral && pairIdx >= 0 && visibles[pairIdx].type !== "html") {
1294
+ return null;
1295
+ }
1240
1296
  const litOwnerEnd = pairIdx >= 0 ? visibles[pairIdx].position?.end?.offset : void 0;
1241
1297
  const litEnd = lastIsLiteral ? last.position?.end?.offset : void 0;
1242
1298
  if (lastIsLiteral && last.position !== void 0 && (litEnd === void 0 || litOwnerEnd === void 0)) {
@@ -1325,8 +1381,9 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1325
1381
  }
1326
1382
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1327
1383
  const firstText = tailHastChildren[0];
1328
- if (!firstText) return false;
1329
1384
  const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
1385
+ if (firstVisible?.type === "html" && STRAY_SYNTHESIZED_END_TAG_RE.test(firstVisible.value)) return null;
1386
+ if (!firstText) return false;
1330
1387
  if (!isSeparatorText(firstText)) {
1331
1388
  if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
1332
1389
  if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
@@ -1375,6 +1432,9 @@ function countTrailingNewlines(value) {
1375
1432
  function countNewlines(text, end = text.length) {
1376
1433
  let count = 0;
1377
1434
  for (let i = text.indexOf("\n"); i !== -1 && i < end; i = text.indexOf("\n", i + 1)) count += 1;
1435
+ for (let i = text.indexOf("\r"); i !== -1 && i < end; i = text.indexOf("\r", i + 1)) {
1436
+ if (text.charCodeAt(i + 1) !== 10) count += 1;
1437
+ }
1378
1438
  return count;
1379
1439
  }
1380
1440
 
@@ -1494,7 +1554,7 @@ function normalizeId(s) {
1494
1554
  return normalizeIdentifier3(s);
1495
1555
  }
1496
1556
  function normalizeForMatch(s) {
1497
- return normalizeIdentifier3(s.replace(/\\(.)/g, "$1"));
1557
+ return normalizeIdentifier3(s.replace(/\\([!-/:-@[-`{-~])/g, "$1"));
1498
1558
  }
1499
1559
 
1500
1560
  // src/components/collectDefLabels.ts
@@ -1767,17 +1827,8 @@ function phantomSuffixCloser(content) {
1767
1827
  }
1768
1828
 
1769
1829
  // src/components/extractContributions.ts
1770
- function fakeAnchorElement(url) {
1771
- return { type: "element", tagName: "a", properties: { href: url }, children: [] };
1772
- }
1773
- function sanitizeDefUrl(url, urlTransform) {
1774
- if (!urlTransform) return url;
1775
- const result = urlTransform(url, "href", fakeAnchorElement(url));
1776
- return result == null ? "" : String(result);
1777
- }
1778
1830
  function* extractContributions(mdast, options = {}) {
1779
1831
  const phantomFn = options.phantomFootnoteLabels;
1780
- const urlTransform = options.urlTransform;
1781
1832
  const out = [];
1782
1833
  visit3(mdast, (n) => {
1783
1834
  if (n.type === "footnoteReference") {
@@ -1805,7 +1856,7 @@ function* extractContributions(mdast, options = {}) {
1805
1856
  out.push({
1806
1857
  kind: "linkDef",
1807
1858
  label: normalizeId(d.identifier),
1808
- url: sanitizeDefUrl(d.url, urlTransform),
1859
+ url: d.url,
1809
1860
  title: d.title
1810
1861
  });
1811
1862
  }
@@ -3519,8 +3570,7 @@ function isProtocolAllowed(url, allowed) {
3519
3570
  }
3520
3571
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3521
3572
  rawUrl = normalizeUri2(rawUrl);
3522
- const callerProtocols = schema.protocols;
3523
- const allowed = callerProtocols === void 0 || callerProtocols === null ? sanitizeSchema.protocols?.[key] : callerProtocols[key];
3573
+ const allowed = Object.hasOwn(schema, "protocols") ? schema.protocols?.[key] : sanitizeSchema.protocols?.[key];
3524
3574
  if (allowed && allowed.length > 0 && !isProtocolAllowed(rawUrl, allowed)) return null;
3525
3575
  const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3526
3576
  if (transformed == null) return null;
@@ -3930,23 +3980,28 @@ function lineHasBacktick(content, pos) {
3930
3980
  }
3931
3981
  return false;
3932
3982
  }
3933
- function isAtLineStart(content, pos) {
3983
+ function lineIndentBefore(content, pos) {
3934
3984
  let i = pos - 1;
3935
- let spaces = 0;
3936
- while (i >= 0 && content[i] === " ") {
3937
- spaces++;
3938
- if (spaces > 3) return false;
3985
+ let indent = 0;
3986
+ while (i >= 0 && (content[i] === " " || content[i] === " ")) {
3987
+ indent += content[i] === " " ? 4 : 1;
3939
3988
  i--;
3940
3989
  }
3941
- return i < 0 || content[i] === "\n" || content[i] === "\r";
3990
+ return i < 0 || content[i] === "\n" || content[i] === "\r" ? indent : -1;
3942
3991
  }
3943
3992
  function findClosingBacktickRun(content, start, n) {
3944
3993
  let i = start;
3945
3994
  while (i < content.length) {
3946
- if (content[i] === "`") {
3995
+ const ch = content[i];
3996
+ if (ch === "`") {
3947
3997
  const runLen = getRepeatedMarkerLength(content, i, "`");
3948
3998
  if (runLen === n) return i;
3949
3999
  i += runLen;
4000
+ } else if (ch === "\n") {
4001
+ let j = i + 1;
4002
+ while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
4003
+ if (j >= content.length || content[j] === "\n") return -1;
4004
+ i += 1;
3950
4005
  } else {
3951
4006
  i += 1;
3952
4007
  }
@@ -3959,6 +4014,7 @@ function splitByProtectedRegions(content) {
3959
4014
  let multilineStart = -1;
3960
4015
  let multilineFenceMarker = null;
3961
4016
  let multilineFenceLength = 0;
4017
+ let multilineFenceIndent = 0;
3962
4018
  function pushProtected(start, end) {
3963
4019
  if (start > lastIndex) {
3964
4020
  segments.push({ text: content.substring(lastIndex, start), isCode: false });
@@ -3972,7 +4028,8 @@ function splitByProtectedRegions(content) {
3972
4028
  if (multilineStart !== -1) {
3973
4029
  if (char === multilineFenceMarker) {
3974
4030
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
3975
- if (runLen >= multilineFenceLength && isAtLineStart(content, i) && restOfLineIsBlank(content, i + runLen)) {
4031
+ const closerIndent = lineIndentBefore(content, i);
4032
+ if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
3976
4033
  pushProtected(multilineStart, i + runLen);
3977
4034
  multilineStart = -1;
3978
4035
  multilineFenceMarker = null;
@@ -3988,10 +4045,12 @@ function splitByProtectedRegions(content) {
3988
4045
  }
3989
4046
  if (char === "`" || char === "~") {
3990
4047
  const runLen = getRepeatedMarkerLength(content, i, char);
3991
- if (runLen >= 3 && isAtLineStart(content, i) && !(char === "`" && lineHasBacktick(content, i + runLen))) {
4048
+ const openerIndent = lineIndentBefore(content, i);
4049
+ if (runLen >= 3 && openerIndent !== -1 && !(char === "`" && lineHasBacktick(content, i + runLen))) {
3992
4050
  multilineStart = i;
3993
4051
  multilineFenceMarker = char;
3994
4052
  multilineFenceLength = runLen;
4053
+ multilineFenceIndent = openerIndent;
3995
4054
  i += runLen;
3996
4055
  continue;
3997
4056
  }
@@ -4064,16 +4123,20 @@ function escapeCurrencyDollarSigns(text) {
4064
4123
  currentLineProcessed += segment;
4065
4124
  }
4066
4125
  let needEscape = true;
4067
- let restBeforeNextMatchOrEnd = "";
4068
- if (i < currencyMatches.length - 1) {
4069
- const nextMatch = currencyMatches[i + 1];
4070
- if (nextMatch.index - match.index > 1) {
4071
- restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);
4126
+ const restStart = match.index + 1;
4127
+ const restEnd = i < currencyMatches.length - 1 ? currencyMatches[i + 1].index : text.length;
4128
+ let firstLineBeforeNextMatch = "";
4129
+ if (restEnd - restStart > 0) {
4130
+ let eol = restEnd;
4131
+ for (let k = restStart; k < restEnd; k++) {
4132
+ const c = text.charCodeAt(k);
4133
+ if (c === 10 || c === 13) {
4134
+ eol = k;
4135
+ break;
4136
+ }
4072
4137
  }
4073
- } else {
4074
- restBeforeNextMatchOrEnd = text.substring(match.index + 1);
4138
+ firstLineBeforeNextMatch = text.substring(restStart, eol);
4075
4139
  }
4076
- const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
4077
4140
  if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4078
4141
  const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
4079
4142
  if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
@@ -4239,39 +4302,49 @@ function hasUnclosedTextCommand(text) {
4239
4302
  return false;
4240
4303
  }
4241
4304
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4242
- function processSliceInstrumented(slice) {
4305
+ var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4306
+ function processSliceInstrumented(slice, probe = true) {
4243
4307
  const segments = splitByProtectedRegions(slice);
4244
- let out = "";
4308
+ const parts = [];
4245
4309
  let quiescent = true;
4246
4310
  let truncatedAtSeamStart = false;
4247
4311
  for (let index = 0; index < segments.length; index++) {
4248
4312
  const segment = segments[index];
4249
4313
  if (segment.isCode) {
4250
- out += segment.text;
4314
+ parts.push(segment.text);
4251
4315
  continue;
4252
4316
  }
4253
4317
  let text = segment.text;
4254
4318
  text = escapeMhchemCommands(text);
4255
4319
  text = escapeCurrencyDollarSigns(text);
4256
4320
  text = convertLatexDelimiters(text);
4257
- if (RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4321
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4258
4322
  text = escapeLatexPipes(text);
4259
- if (findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4323
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4260
4324
  text = escapeLatexPipesInUnclosed(text);
4261
- if (hasUnclosedTextCommand(text)) quiescent = false;
4325
+ if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4262
4326
  text = escapeTextUnderscores(text);
4263
4327
  text = convertSingleToDoubleDollar(text);
4264
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4265
- if (unclosedDouble !== -1) {
4266
- quiescent = false;
4267
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4268
- truncatedAtSeamStart = true;
4328
+ if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4329
+ const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4330
+ if (unclosedDouble !== -1) {
4331
+ quiescent = false;
4332
+ if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4333
+ truncatedAtSeamStart = true;
4334
+ }
4269
4335
  }
4270
4336
  }
4271
4337
  text = truncateUnclosedLatexBlock(text);
4272
- out += text;
4338
+ parts.push(text);
4273
4339
  }
4274
- return { out, quiescent, truncatedAtSeamStart };
4340
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart };
4341
+ }
4342
+ function isBlankRawLine(text, from, to) {
4343
+ for (let i = from; i < to; i++) {
4344
+ const c = text.charCodeAt(i);
4345
+ if (c !== 32 && c !== 9 && c !== 13) return false;
4346
+ }
4347
+ return true;
4275
4348
  }
4276
4349
  function findRawSafeCut(active) {
4277
4350
  const segments = splitByProtectedRegions(active);
@@ -4286,10 +4359,12 @@ function findRawSafeCut(active) {
4286
4359
  continue;
4287
4360
  }
4288
4361
  const text = segment.text;
4362
+ let atLineStart = offset === 0 || active.charCodeAt(offset - 1) === 10;
4289
4363
  let lineStart = 0;
4290
4364
  while (lineStart <= text.length) {
4291
4365
  const nl = text.indexOf("\n", lineStart);
4292
4366
  const lineEnd = nl === -1 ? text.length : nl;
4367
+ if (atLineStart && nl !== -1 && isBlankRawLine(text, lineStart, lineEnd)) backtickHazard = false;
4293
4368
  for (let i = lineStart; i < lineEnd; i++) {
4294
4369
  const ch = text[i];
4295
4370
  if (ch === "`") backtickHazard = true;
@@ -4302,6 +4377,7 @@ function findRawSafeCut(active) {
4302
4377
  if (nl === -1) break;
4303
4378
  if (!backtickHazard && !latentLt) lastCut = offset + nl + 1;
4304
4379
  lineStart = nl + 1;
4380
+ atLineStart = true;
4305
4381
  }
4306
4382
  offset += text.length;
4307
4383
  }
@@ -4310,11 +4386,14 @@ function findRawSafeCut(active) {
4310
4386
  var DEFAULT_FREEZE_ATTEMPT_THRESHOLD = 512;
4311
4387
  function createIncrementalLatexPreprocessor(options) {
4312
4388
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
4389
+ const onAttempt = options?.onAttempt;
4390
+ const backoff = options?.backoff ?? true;
4313
4391
  let prevSource = "";
4314
4392
  let prevOutput = "";
4315
4393
  let frozenSrcEnd = 0;
4316
4394
  let frozenOut = "";
4317
4395
  let triggered = false;
4396
+ let nextAttemptLen = 0;
4318
4397
  return function incrementalPreprocessLaTeX(source) {
4319
4398
  if (source === prevSource) return prevOutput;
4320
4399
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -4322,6 +4401,7 @@ function createIncrementalLatexPreprocessor(options) {
4322
4401
  frozenSrcEnd = 0;
4323
4402
  frozenOut = "";
4324
4403
  triggered = false;
4404
+ nextAttemptLen = 0;
4325
4405
  }
4326
4406
  if (!triggered) {
4327
4407
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
@@ -4333,18 +4413,26 @@ function createIncrementalLatexPreprocessor(options) {
4333
4413
  triggered = true;
4334
4414
  }
4335
4415
  let active = source.slice(frozenSrcEnd);
4336
- if (active.length > freezeThreshold) {
4416
+ if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
4417
+ const activeLength = active.length;
4418
+ let advanced = false;
4419
+ let frozenBytes = 0;
4420
+ const freeze = (cut2, slice) => {
4421
+ frozenOut += slice.out;
4422
+ frozenSrcEnd += cut2;
4423
+ active = source.slice(frozenSrcEnd);
4424
+ advanced = true;
4425
+ frozenBytes = cut2;
4426
+ };
4337
4427
  const cut = findRawSafeCut(active);
4338
4428
  if (cut > 0) {
4339
4429
  const candidate = processSliceInstrumented(active.slice(0, cut));
4340
- if (candidate.quiescent) {
4341
- frozenOut += candidate.out;
4342
- frozenSrcEnd += cut;
4343
- active = source.slice(frozenSrcEnd);
4344
- }
4430
+ if (candidate.quiescent) freeze(cut, candidate);
4345
4431
  }
4432
+ nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
4433
+ onAttempt?.({ activeLength, frozenBytes });
4346
4434
  }
4347
- const tail = processSliceInstrumented(active);
4435
+ const tail = processSliceInstrumented(active, false);
4348
4436
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
4349
4437
  const out = head + tail.out;
4350
4438
  prevSource = source;