@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.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;
@@ -3918,23 +3968,28 @@ function lineHasBacktick(content, pos) {
3918
3968
  }
3919
3969
  return false;
3920
3970
  }
3921
- function isAtLineStart(content, pos) {
3971
+ function lineIndentBefore(content, pos) {
3922
3972
  let i = pos - 1;
3923
- let spaces = 0;
3924
- while (i >= 0 && content[i] === " ") {
3925
- spaces++;
3926
- if (spaces > 3) return false;
3973
+ let indent = 0;
3974
+ while (i >= 0 && (content[i] === " " || content[i] === " ")) {
3975
+ indent += content[i] === " " ? 4 : 1;
3927
3976
  i--;
3928
3977
  }
3929
- return i < 0 || content[i] === "\n" || content[i] === "\r";
3978
+ return i < 0 || content[i] === "\n" || content[i] === "\r" ? indent : -1;
3930
3979
  }
3931
3980
  function findClosingBacktickRun(content, start, n) {
3932
3981
  let i = start;
3933
3982
  while (i < content.length) {
3934
- if (content[i] === "`") {
3983
+ const ch = content[i];
3984
+ if (ch === "`") {
3935
3985
  const runLen = getRepeatedMarkerLength(content, i, "`");
3936
3986
  if (runLen === n) return i;
3937
3987
  i += runLen;
3988
+ } else if (ch === "\n") {
3989
+ let j = i + 1;
3990
+ while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
3991
+ if (j >= content.length || content[j] === "\n") return -1;
3992
+ i += 1;
3938
3993
  } else {
3939
3994
  i += 1;
3940
3995
  }
@@ -3947,6 +4002,7 @@ function splitByProtectedRegions(content) {
3947
4002
  let multilineStart = -1;
3948
4003
  let multilineFenceMarker = null;
3949
4004
  let multilineFenceLength = 0;
4005
+ let multilineFenceIndent = 0;
3950
4006
  function pushProtected(start, end) {
3951
4007
  if (start > lastIndex) {
3952
4008
  segments.push({ text: content.substring(lastIndex, start), isCode: false });
@@ -3960,7 +4016,8 @@ function splitByProtectedRegions(content) {
3960
4016
  if (multilineStart !== -1) {
3961
4017
  if (char === multilineFenceMarker) {
3962
4018
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
3963
- if (runLen >= multilineFenceLength && isAtLineStart(content, i) && restOfLineIsBlank(content, i + runLen)) {
4019
+ const closerIndent = lineIndentBefore(content, i);
4020
+ if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
3964
4021
  pushProtected(multilineStart, i + runLen);
3965
4022
  multilineStart = -1;
3966
4023
  multilineFenceMarker = null;
@@ -3976,10 +4033,12 @@ function splitByProtectedRegions(content) {
3976
4033
  }
3977
4034
  if (char === "`" || char === "~") {
3978
4035
  const runLen = getRepeatedMarkerLength(content, i, char);
3979
- if (runLen >= 3 && isAtLineStart(content, i) && !(char === "`" && lineHasBacktick(content, i + runLen))) {
4036
+ const openerIndent = lineIndentBefore(content, i);
4037
+ if (runLen >= 3 && openerIndent !== -1 && !(char === "`" && lineHasBacktick(content, i + runLen))) {
3980
4038
  multilineStart = i;
3981
4039
  multilineFenceMarker = char;
3982
4040
  multilineFenceLength = runLen;
4041
+ multilineFenceIndent = openerIndent;
3983
4042
  i += runLen;
3984
4043
  continue;
3985
4044
  }
@@ -4052,16 +4111,20 @@ function escapeCurrencyDollarSigns(text) {
4052
4111
  currentLineProcessed += segment;
4053
4112
  }
4054
4113
  let needEscape = true;
4055
- let restBeforeNextMatchOrEnd = "";
4056
- if (i < currencyMatches.length - 1) {
4057
- const nextMatch = currencyMatches[i + 1];
4058
- if (nextMatch.index - match.index > 1) {
4059
- restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);
4114
+ const restStart = match.index + 1;
4115
+ const restEnd = i < currencyMatches.length - 1 ? currencyMatches[i + 1].index : text.length;
4116
+ let firstLineBeforeNextMatch = "";
4117
+ if (restEnd - restStart > 0) {
4118
+ let eol = restEnd;
4119
+ for (let k = restStart; k < restEnd; k++) {
4120
+ const c = text.charCodeAt(k);
4121
+ if (c === 10 || c === 13) {
4122
+ eol = k;
4123
+ break;
4124
+ }
4060
4125
  }
4061
- } else {
4062
- restBeforeNextMatchOrEnd = text.substring(match.index + 1);
4126
+ firstLineBeforeNextMatch = text.substring(restStart, eol);
4063
4127
  }
4064
- const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
4065
4128
  if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4066
4129
  const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
4067
4130
  if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
@@ -4227,39 +4290,49 @@ function hasUnclosedTextCommand(text) {
4227
4290
  return false;
4228
4291
  }
4229
4292
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4230
- function processSliceInstrumented(slice) {
4293
+ var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4294
+ function processSliceInstrumented(slice, probe = true) {
4231
4295
  const segments = splitByProtectedRegions(slice);
4232
- let out = "";
4296
+ const parts = [];
4233
4297
  let quiescent = true;
4234
4298
  let truncatedAtSeamStart = false;
4235
4299
  for (let index = 0; index < segments.length; index++) {
4236
4300
  const segment = segments[index];
4237
4301
  if (segment.isCode) {
4238
- out += segment.text;
4302
+ parts.push(segment.text);
4239
4303
  continue;
4240
4304
  }
4241
4305
  let text = segment.text;
4242
4306
  text = escapeMhchemCommands(text);
4243
4307
  text = escapeCurrencyDollarSigns(text);
4244
4308
  text = convertLatexDelimiters(text);
4245
- if (RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4309
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4246
4310
  text = escapeLatexPipes(text);
4247
- if (findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4311
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4248
4312
  text = escapeLatexPipesInUnclosed(text);
4249
- if (hasUnclosedTextCommand(text)) quiescent = false;
4313
+ if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4250
4314
  text = escapeTextUnderscores(text);
4251
4315
  text = convertSingleToDoubleDollar(text);
4252
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4253
- if (unclosedDouble !== -1) {
4254
- quiescent = false;
4255
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4256
- truncatedAtSeamStart = true;
4316
+ if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4317
+ const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4318
+ if (unclosedDouble !== -1) {
4319
+ quiescent = false;
4320
+ if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4321
+ truncatedAtSeamStart = true;
4322
+ }
4257
4323
  }
4258
4324
  }
4259
4325
  text = truncateUnclosedLatexBlock(text);
4260
- out += text;
4326
+ parts.push(text);
4261
4327
  }
4262
- return { out, quiescent, truncatedAtSeamStart };
4328
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart };
4329
+ }
4330
+ function isBlankRawLine(text, from, to) {
4331
+ for (let i = from; i < to; i++) {
4332
+ const c = text.charCodeAt(i);
4333
+ if (c !== 32 && c !== 9 && c !== 13) return false;
4334
+ }
4335
+ return true;
4263
4336
  }
4264
4337
  function findRawSafeCut(active) {
4265
4338
  const segments = splitByProtectedRegions(active);
@@ -4274,10 +4347,12 @@ function findRawSafeCut(active) {
4274
4347
  continue;
4275
4348
  }
4276
4349
  const text = segment.text;
4350
+ let atLineStart = offset === 0 || active.charCodeAt(offset - 1) === 10;
4277
4351
  let lineStart = 0;
4278
4352
  while (lineStart <= text.length) {
4279
4353
  const nl = text.indexOf("\n", lineStart);
4280
4354
  const lineEnd = nl === -1 ? text.length : nl;
4355
+ if (atLineStart && nl !== -1 && isBlankRawLine(text, lineStart, lineEnd)) backtickHazard = false;
4281
4356
  for (let i = lineStart; i < lineEnd; i++) {
4282
4357
  const ch = text[i];
4283
4358
  if (ch === "`") backtickHazard = true;
@@ -4290,6 +4365,7 @@ function findRawSafeCut(active) {
4290
4365
  if (nl === -1) break;
4291
4366
  if (!backtickHazard && !latentLt) lastCut = offset + nl + 1;
4292
4367
  lineStart = nl + 1;
4368
+ atLineStart = true;
4293
4369
  }
4294
4370
  offset += text.length;
4295
4371
  }
@@ -4298,11 +4374,14 @@ function findRawSafeCut(active) {
4298
4374
  var DEFAULT_FREEZE_ATTEMPT_THRESHOLD = 512;
4299
4375
  function createIncrementalLatexPreprocessor(options) {
4300
4376
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
4377
+ const onAttempt = options?.onAttempt;
4378
+ const backoff = options?.backoff ?? true;
4301
4379
  let prevSource = "";
4302
4380
  let prevOutput = "";
4303
4381
  let frozenSrcEnd = 0;
4304
4382
  let frozenOut = "";
4305
4383
  let triggered = false;
4384
+ let nextAttemptLen = 0;
4306
4385
  return function incrementalPreprocessLaTeX(source) {
4307
4386
  if (source === prevSource) return prevOutput;
4308
4387
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -4310,6 +4389,7 @@ function createIncrementalLatexPreprocessor(options) {
4310
4389
  frozenSrcEnd = 0;
4311
4390
  frozenOut = "";
4312
4391
  triggered = false;
4392
+ nextAttemptLen = 0;
4313
4393
  }
4314
4394
  if (!triggered) {
4315
4395
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
@@ -4321,18 +4401,26 @@ function createIncrementalLatexPreprocessor(options) {
4321
4401
  triggered = true;
4322
4402
  }
4323
4403
  let active = source.slice(frozenSrcEnd);
4324
- if (active.length > freezeThreshold) {
4404
+ if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
4405
+ const activeLength = active.length;
4406
+ let advanced = false;
4407
+ let frozenBytes = 0;
4408
+ const freeze = (cut2, slice) => {
4409
+ frozenOut += slice.out;
4410
+ frozenSrcEnd += cut2;
4411
+ active = source.slice(frozenSrcEnd);
4412
+ advanced = true;
4413
+ frozenBytes = cut2;
4414
+ };
4325
4415
  const cut = findRawSafeCut(active);
4326
4416
  if (cut > 0) {
4327
4417
  const candidate = processSliceInstrumented(active.slice(0, cut));
4328
- if (candidate.quiescent) {
4329
- frozenOut += candidate.out;
4330
- frozenSrcEnd += cut;
4331
- active = source.slice(frozenSrcEnd);
4332
- }
4418
+ if (candidate.quiescent) freeze(cut, candidate);
4333
4419
  }
4420
+ nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
4421
+ onAttempt?.({ activeLength, frozenBytes });
4334
4422
  }
4335
- const tail = processSliceInstrumented(active);
4423
+ const tail = processSliceInstrumented(active, false);
4336
4424
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
4337
4425
  const out = head + tail.out;
4338
4426
  prevSource = source;