@ai-react-markdown/engine 2.4.3 → 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.d.cts CHANGED
@@ -425,6 +425,49 @@ interface FreezeScanCheckpoint {
425
425
  * tagBalance but not yet confirmed by a later `>` — reverted at the next
426
426
  * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
427
427
  pendingTruncatedTags: string[];
428
+ /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
429
+ * run, waiting for a `>` on a later line of the same run. Unlike opens
430
+ * they are NOT counted up front: a close tag cannot carry attributes, so
431
+ * `para </style` (prose to micromark, still-open element to parse5 —
432
+ * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
433
+ * review P1 — the boundary crossed an open `<style>`). Confirmed and
434
+ * applied only when a later line of the run brings the `>` (parse5
435
+ * completes the end tag; micromark's block ends at the blank anyway);
436
+ * dropped unapplied at the blank (element stays counted — over-block).
437
+ * Paragraph-context truncated closes are never pended: a `>` at block
438
+ * indent on the next line is a blockquote to micromark, and the one
439
+ * shape that would complete the inline close (`</b\n >`, a 4+-space
440
+ * lazy continuation) is not modelled — the element stays counted,
441
+ * over-block. */
442
+ pendingTruncatedCloses: string[];
443
+ /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
444
+ * close or void, with or without attributes): parse5's tokenizer is
445
+ * still in that tag, so on the following lines everything up to the
446
+ * FIRST `>` is attribute garbage — `</div>` there does NOT close
447
+ * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
448
+ * past the still-open outer div; pre-existing, 1-char slices). While
449
+ * set: a line without `>` gets no tag scan at all; the line with the
450
+ * `>` completes the pending close (if any), then only its text after
451
+ * that `>` is scanned. Cleared there and at the blank line. */
452
+ tagAcrossLines: boolean;
453
+ /** Indent of the line that set `tagAcrossLines`. A following line that
454
+ * DE-INDENTS below it may have left the container (a list item's html
455
+ * block ends there; hast-util-raw resets the tokenizer at the li/ul
456
+ * boundary, so a `<div>` on that line is a real start tag, not garbage)
457
+ * — or may still be the same html block (root-level ` </div\n</div>`,
458
+ * still garbage). Unknowable here → poison (oracle 3rd pass). */
459
+ tagAcrossLinesIndent: number;
460
+ /** The html-flow run since the last blank REALLY started as a micromark
461
+ * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
462
+ * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
463
+ * start sets (over-approximation, fine for its over-blocking uses). Only
464
+ * in a real run are the bytes raw to parse5 across line endings; the
465
+ * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
466
+ * gated on it — in a paragraph starting `</i` the next line's `<div>` /
467
+ * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
468
+ * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
469
+ * blank; a type 6/1 start on a later line of a non-real run promotes it. */
470
+ htmlFlowReal: boolean;
428
471
  }
429
472
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
430
473
 
@@ -1902,6 +1945,20 @@ declare function preprocessLaTeX(str: string): string;
1902
1945
  */
1903
1946
  declare function createIncrementalLatexPreprocessor(options?: {
1904
1947
  freezeThreshold?: number;
1948
+ /** Failure backoff (default on). Tests that rely on `freezeThreshold: 0`
1949
+ * to attempt a freeze on EVERY call turn it off, so 1-char chunkings keep
1950
+ * exercising the cut rules instead of skipping most attempts; backoff
1951
+ * can only freeze LESS, so passing without it implies passing with it.
1952
+ * @internal */
1953
+ backoff?: boolean;
1954
+ /** Test hook: called once per freeze ATTEMPT (a scan of the active
1955
+ * region) with the region's length and how many source bytes the
1956
+ * attempt froze (0 = failed), so the backoff bound and freeze progress
1957
+ * can be pinned. @internal */
1958
+ onAttempt?: (info: {
1959
+ activeLength: number;
1960
+ frozenBytes: number;
1961
+ }) => void;
1905
1962
  }): (content: string) => string;
1906
1963
 
1907
1964
  /**
package/dist/index.d.ts CHANGED
@@ -425,6 +425,49 @@ interface FreezeScanCheckpoint {
425
425
  * tagBalance but not yet confirmed by a later `>` — reverted at the next
426
426
  * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
427
427
  pendingTruncatedTags: string[];
428
+ /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
429
+ * run, waiting for a `>` on a later line of the same run. Unlike opens
430
+ * they are NOT counted up front: a close tag cannot carry attributes, so
431
+ * `para </style` (prose to micromark, still-open element to parse5 —
432
+ * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
433
+ * review P1 — the boundary crossed an open `<style>`). Confirmed and
434
+ * applied only when a later line of the run brings the `>` (parse5
435
+ * completes the end tag; micromark's block ends at the blank anyway);
436
+ * dropped unapplied at the blank (element stays counted — over-block).
437
+ * Paragraph-context truncated closes are never pended: a `>` at block
438
+ * indent on the next line is a blockquote to micromark, and the one
439
+ * shape that would complete the inline close (`</b\n >`, a 4+-space
440
+ * lazy continuation) is not modelled — the element stays counted,
441
+ * over-block. */
442
+ pendingTruncatedCloses: string[];
443
+ /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
444
+ * close or void, with or without attributes): parse5's tokenizer is
445
+ * still in that tag, so on the following lines everything up to the
446
+ * FIRST `>` is attribute garbage — `</div>` there does NOT close
447
+ * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
448
+ * past the still-open outer div; pre-existing, 1-char slices). While
449
+ * set: a line without `>` gets no tag scan at all; the line with the
450
+ * `>` completes the pending close (if any), then only its text after
451
+ * that `>` is scanned. Cleared there and at the blank line. */
452
+ tagAcrossLines: boolean;
453
+ /** Indent of the line that set `tagAcrossLines`. A following line that
454
+ * DE-INDENTS below it may have left the container (a list item's html
455
+ * block ends there; hast-util-raw resets the tokenizer at the li/ul
456
+ * boundary, so a `<div>` on that line is a real start tag, not garbage)
457
+ * — or may still be the same html block (root-level ` </div\n</div>`,
458
+ * still garbage). Unknowable here → poison (oracle 3rd pass). */
459
+ tagAcrossLinesIndent: number;
460
+ /** The html-flow run since the last blank REALLY started as a micromark
461
+ * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
462
+ * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
463
+ * start sets (over-approximation, fine for its over-blocking uses). Only
464
+ * in a real run are the bytes raw to parse5 across line endings; the
465
+ * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
466
+ * gated on it — in a paragraph starting `</i` the next line's `<div>` /
467
+ * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
468
+ * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
469
+ * blank; a type 6/1 start on a later line of a non-real run promotes it. */
470
+ htmlFlowReal: boolean;
428
471
  }
429
472
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
430
473
 
@@ -1902,6 +1945,20 @@ declare function preprocessLaTeX(str: string): string;
1902
1945
  */
1903
1946
  declare function createIncrementalLatexPreprocessor(options?: {
1904
1947
  freezeThreshold?: number;
1948
+ /** Failure backoff (default on). Tests that rely on `freezeThreshold: 0`
1949
+ * to attempt a freeze on EVERY call turn it off, so 1-char chunkings keep
1950
+ * exercising the cut rules instead of skipping most attempts; backoff
1951
+ * can only freeze LESS, so passing without it implies passing with it.
1952
+ * @internal */
1953
+ backoff?: boolean;
1954
+ /** Test hook: called once per freeze ATTEMPT (a scan of the active
1955
+ * region) with the region's length and how many source bytes the
1956
+ * attempt froze (0 = failed), so the backoff bound and freeze progress
1957
+ * can be pinned. @internal */
1958
+ onAttempt?: (info: {
1959
+ activeLength: number;
1960
+ frozenBytes: number;
1961
+ }) => void;
1905
1962
  }): (content: string) => string;
1906
1963
 
1907
1964
  /**
@@ -269,6 +269,11 @@ var defaultUrlTransform = (value) => {
269
269
  var import_micromark_util_html_tag_name = require("micromark-util-html-tag-name");
270
270
  var import_micromark_util_normalize_identifier = require("micromark-util-normalize-identifier");
271
271
  var TYPE6_NAMES = new Set(import_micromark_util_html_tag_name.htmlBlockNames);
272
+ var TABLE_PART_NAMES = /* @__PURE__ */ new Set(["td", "th", "tr", "tbody", "thead", "tfoot", "caption", "col", "colgroup"]);
273
+ var TYPE1_NAMES = /* @__PURE__ */ new Set(["script", "pre", "style", "textarea"]);
274
+ var TYPE6_START_RE = /^<\/?([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r]|\/?>|$)/;
275
+ var TYPE1_START_RE = /^<(script|pre|style|textarea)(?:[ \t\r]|>|$)/i;
276
+ var TYPE7_LINE_RE = /^<(\/?)([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r][^>]*|\/)?>[ \t\r]*$/;
272
277
  var VOID_TAGS = /* @__PURE__ */ new Set([
273
278
  "area",
274
279
  "base",
@@ -407,7 +412,11 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
407
412
  htmlFlowSinceBlank: false,
408
413
  htmlSeamPending: false,
409
414
  phasePoisonedAt: Infinity,
410
- pendingTruncatedTags: []
415
+ pendingTruncatedTags: [],
416
+ pendingTruncatedCloses: [],
417
+ tagAcrossLines: false,
418
+ tagAcrossLinesIndent: 0,
419
+ htmlFlowReal: false
411
420
  };
412
421
  }
413
422
  function isPlausibleLinkDefRest(rest) {
@@ -704,6 +713,8 @@ function processConfirmedLine(cp, ln, text) {
704
713
  for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
705
714
  cp.pendingTruncatedTags = [];
706
715
  }
716
+ cp.pendingTruncatedCloses = [];
717
+ cp.tagAcrossLines = false;
707
718
  cp.blankRun += 1;
708
719
  cp.lastBlankStart = ln.start;
709
720
  cp.candidates.push({
@@ -717,6 +728,7 @@ function processConfirmedLine(cp, ln, text) {
717
728
  cp.paragraphHasUnpairedRun = false;
718
729
  cp.openBracket = null;
719
730
  cp.htmlFlowSinceBlank = false;
731
+ cp.htmlFlowReal = false;
720
732
  cp.prevLineBlank = true;
721
733
  cp.prevLineWasText = false;
722
734
  cp.prevLineWasValidDef = false;
@@ -732,6 +744,16 @@ function processConfirmedLine(cp, ln, text) {
732
744
  if (tagStart) {
733
745
  cp.htmlFlowSinceBlank = true;
734
746
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
747
+ if (!cp.htmlFlowReal) {
748
+ const t = mdTrimStart(ln.text);
749
+ const t6 = TYPE6_START_RE.exec(t);
750
+ const t7 = TYPE7_LINE_RE.exec(t);
751
+ 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
752
+ // names (those are type 1 as start tags, paragraph as end tags).
753
+ t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7[2].toLowerCase())) {
754
+ cp.htmlFlowReal = true;
755
+ }
756
+ }
735
757
  }
736
758
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
737
759
  const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(mdTrimStart(ln.text));
@@ -872,7 +894,21 @@ ${cont(scanText)}` };
872
894
  for (const [from, to] of rawSpans) {
873
895
  tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
874
896
  }
875
- {
897
+ let skipTagScan = false;
898
+ if (cp.tagAcrossLines) {
899
+ if (ln.indent < cp.tagAcrossLinesIndent) poisonRawDivergence();
900
+ const gt = ln.text.indexOf(">");
901
+ if (gt === -1) {
902
+ skipTagScan = true;
903
+ } else {
904
+ if (/["']/.test(ln.text.slice(0, gt))) poisonRawDivergence();
905
+ for (const tag of cp.pendingTruncatedCloses) applyTag(tag, true);
906
+ cp.pendingTruncatedCloses = [];
907
+ cp.tagAcrossLines = false;
908
+ tagText = " ".repeat(gt + 1) + tagText.slice(gt + 1);
909
+ }
910
+ }
911
+ if (!skipTagScan) {
876
912
  TAG_OR_COMMENT_RE.lastIndex = 0;
877
913
  let m;
878
914
  let lastCommentOpenerIdx = -1;
@@ -902,6 +938,7 @@ ${cont(scanText)}` };
902
938
  if (cp.commentOpen) continue;
903
939
  const closing = m[1] === "/";
904
940
  const tag = m[2].toLowerCase();
941
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + m.index);
905
942
  const selfClosing = m[3] !== void 0 && /\/\s*$/.test(m[3]);
906
943
  if (VOID_TAGS.has(tag) || selfClosing) continue;
907
944
  applyTag(tag, closing);
@@ -922,6 +959,7 @@ ${cont(scanText)}` };
922
959
  if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
923
960
  const closing = mr[1] === "/";
924
961
  const tag = mr[2].toLowerCase();
962
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
925
963
  const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
926
964
  if (VOID_TAGS.has(tag) || selfClosing) continue;
927
965
  applyTag(tag, closing);
@@ -937,7 +975,14 @@ ${cont(scanText)}` };
937
975
  if (m2) {
938
976
  const closing = m2[1] === "/";
939
977
  const tag = m2[2].toLowerCase();
940
- if (!VOID_TAGS.has(tag)) {
978
+ if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
979
+ if (cp.htmlFlowReal) {
980
+ cp.tagAcrossLines = true;
981
+ cp.tagAcrossLinesIndent = ln.indent;
982
+ }
983
+ if (closing) {
984
+ if (!VOID_TAGS.has(tag) && cp.htmlFlowReal) cp.pendingTruncatedCloses.push(tag);
985
+ } else if (!VOID_TAGS.has(tag)) {
941
986
  applyTag(tag, closing);
942
987
  const rawLastLt = ln.text.lastIndexOf("<");
943
988
  const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
@@ -1480,6 +1525,9 @@ function countTrailingNewlines(value) {
1480
1525
  function countNewlines(text, end = text.length) {
1481
1526
  let count = 0;
1482
1527
  for (let i = text.indexOf("\n"); i !== -1 && i < end; i = text.indexOf("\n", i + 1)) count += 1;
1528
+ for (let i = text.indexOf("\r"); i !== -1 && i < end; i = text.indexOf("\r", i + 1)) {
1529
+ if (text.charCodeAt(i + 1) !== 10) count += 1;
1530
+ }
1483
1531
  return count;
1484
1532
  }
1485
1533
 
@@ -1599,7 +1647,7 @@ function normalizeId(s) {
1599
1647
  return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
1600
1648
  }
1601
1649
  function normalizeForMatch(s) {
1602
- return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\(.)/g, "$1"));
1650
+ return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\([!-/:-@[-`{-~])/g, "$1"));
1603
1651
  }
1604
1652
 
1605
1653
  // src/components/collectDefLabels.ts
@@ -4037,10 +4085,16 @@ function lineIndentBefore(content, pos) {
4037
4085
  function findClosingBacktickRun(content, start, n) {
4038
4086
  let i = start;
4039
4087
  while (i < content.length) {
4040
- if (content[i] === "`") {
4088
+ const ch = content[i];
4089
+ if (ch === "`") {
4041
4090
  const runLen = getRepeatedMarkerLength(content, i, "`");
4042
4091
  if (runLen === n) return i;
4043
4092
  i += runLen;
4093
+ } else if (ch === "\n") {
4094
+ let j = i + 1;
4095
+ while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
4096
+ if (j >= content.length || content[j] === "\n") return -1;
4097
+ i += 1;
4044
4098
  } else {
4045
4099
  i += 1;
4046
4100
  }
@@ -4162,16 +4216,20 @@ function escapeCurrencyDollarSigns(text) {
4162
4216
  currentLineProcessed += segment;
4163
4217
  }
4164
4218
  let needEscape = true;
4165
- let restBeforeNextMatchOrEnd = "";
4166
- if (i < currencyMatches.length - 1) {
4167
- const nextMatch = currencyMatches[i + 1];
4168
- if (nextMatch.index - match.index > 1) {
4169
- restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);
4219
+ const restStart = match.index + 1;
4220
+ const restEnd = i < currencyMatches.length - 1 ? currencyMatches[i + 1].index : text.length;
4221
+ let firstLineBeforeNextMatch = "";
4222
+ if (restEnd - restStart > 0) {
4223
+ let eol = restEnd;
4224
+ for (let k = restStart; k < restEnd; k++) {
4225
+ const c = text.charCodeAt(k);
4226
+ if (c === 10 || c === 13) {
4227
+ eol = k;
4228
+ break;
4229
+ }
4170
4230
  }
4171
- } else {
4172
- restBeforeNextMatchOrEnd = text.substring(match.index + 1);
4231
+ firstLineBeforeNextMatch = text.substring(restStart, eol);
4173
4232
  }
4174
- const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
4175
4233
  if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4176
4234
  const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
4177
4235
  if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
@@ -4337,39 +4395,49 @@ function hasUnclosedTextCommand(text) {
4337
4395
  return false;
4338
4396
  }
4339
4397
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
4340
- function processSliceInstrumented(slice) {
4398
+ var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
4399
+ function processSliceInstrumented(slice, probe = true) {
4341
4400
  const segments = splitByProtectedRegions(slice);
4342
- let out = "";
4401
+ const parts = [];
4343
4402
  let quiescent = true;
4344
4403
  let truncatedAtSeamStart = false;
4345
4404
  for (let index = 0; index < segments.length; index++) {
4346
4405
  const segment = segments[index];
4347
4406
  if (segment.isCode) {
4348
- out += segment.text;
4407
+ parts.push(segment.text);
4349
4408
  continue;
4350
4409
  }
4351
4410
  let text = segment.text;
4352
4411
  text = escapeMhchemCommands(text);
4353
4412
  text = escapeCurrencyDollarSigns(text);
4354
4413
  text = convertLatexDelimiters(text);
4355
- if (RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4414
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
4356
4415
  text = escapeLatexPipes(text);
4357
- if (findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4416
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
4358
4417
  text = escapeLatexPipesInUnclosed(text);
4359
- if (hasUnclosedTextCommand(text)) quiescent = false;
4418
+ if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4360
4419
  text = escapeTextUnderscores(text);
4361
4420
  text = convertSingleToDoubleDollar(text);
4362
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4363
- if (unclosedDouble !== -1) {
4364
- quiescent = false;
4365
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4366
- truncatedAtSeamStart = true;
4421
+ if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4422
+ const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4423
+ if (unclosedDouble !== -1) {
4424
+ quiescent = false;
4425
+ if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
4426
+ truncatedAtSeamStart = true;
4427
+ }
4367
4428
  }
4368
4429
  }
4369
4430
  text = truncateUnclosedLatexBlock(text);
4370
- out += text;
4431
+ parts.push(text);
4371
4432
  }
4372
- return { out, quiescent, truncatedAtSeamStart };
4433
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart };
4434
+ }
4435
+ function isBlankRawLine(text, from, to) {
4436
+ for (let i = from; i < to; i++) {
4437
+ const c = text.charCodeAt(i);
4438
+ if (c !== 32 && c !== 9 && c !== 13) return false;
4439
+ }
4440
+ return true;
4373
4441
  }
4374
4442
  function findRawSafeCut(active) {
4375
4443
  const segments = splitByProtectedRegions(active);
@@ -4384,10 +4452,12 @@ function findRawSafeCut(active) {
4384
4452
  continue;
4385
4453
  }
4386
4454
  const text = segment.text;
4455
+ let atLineStart = offset === 0 || active.charCodeAt(offset - 1) === 10;
4387
4456
  let lineStart = 0;
4388
4457
  while (lineStart <= text.length) {
4389
4458
  const nl = text.indexOf("\n", lineStart);
4390
4459
  const lineEnd = nl === -1 ? text.length : nl;
4460
+ if (atLineStart && nl !== -1 && isBlankRawLine(text, lineStart, lineEnd)) backtickHazard = false;
4391
4461
  for (let i = lineStart; i < lineEnd; i++) {
4392
4462
  const ch = text[i];
4393
4463
  if (ch === "`") backtickHazard = true;
@@ -4400,6 +4470,7 @@ function findRawSafeCut(active) {
4400
4470
  if (nl === -1) break;
4401
4471
  if (!backtickHazard && !latentLt) lastCut = offset + nl + 1;
4402
4472
  lineStart = nl + 1;
4473
+ atLineStart = true;
4403
4474
  }
4404
4475
  offset += text.length;
4405
4476
  }
@@ -4408,11 +4479,14 @@ function findRawSafeCut(active) {
4408
4479
  var DEFAULT_FREEZE_ATTEMPT_THRESHOLD = 512;
4409
4480
  function createIncrementalLatexPreprocessor(options) {
4410
4481
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
4482
+ const onAttempt = options?.onAttempt;
4483
+ const backoff = options?.backoff ?? true;
4411
4484
  let prevSource = "";
4412
4485
  let prevOutput = "";
4413
4486
  let frozenSrcEnd = 0;
4414
4487
  let frozenOut = "";
4415
4488
  let triggered = false;
4489
+ let nextAttemptLen = 0;
4416
4490
  return function incrementalPreprocessLaTeX(source) {
4417
4491
  if (source === prevSource) return prevOutput;
4418
4492
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -4420,6 +4494,7 @@ function createIncrementalLatexPreprocessor(options) {
4420
4494
  frozenSrcEnd = 0;
4421
4495
  frozenOut = "";
4422
4496
  triggered = false;
4497
+ nextAttemptLen = 0;
4423
4498
  }
4424
4499
  if (!triggered) {
4425
4500
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
@@ -4431,18 +4506,26 @@ function createIncrementalLatexPreprocessor(options) {
4431
4506
  triggered = true;
4432
4507
  }
4433
4508
  let active = source.slice(frozenSrcEnd);
4434
- if (active.length > freezeThreshold) {
4509
+ if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
4510
+ const activeLength = active.length;
4511
+ let advanced = false;
4512
+ let frozenBytes = 0;
4513
+ const freeze = (cut2, slice) => {
4514
+ frozenOut += slice.out;
4515
+ frozenSrcEnd += cut2;
4516
+ active = source.slice(frozenSrcEnd);
4517
+ advanced = true;
4518
+ frozenBytes = cut2;
4519
+ };
4435
4520
  const cut = findRawSafeCut(active);
4436
4521
  if (cut > 0) {
4437
4522
  const candidate = processSliceInstrumented(active.slice(0, cut));
4438
- if (candidate.quiescent) {
4439
- frozenOut += candidate.out;
4440
- frozenSrcEnd += cut;
4441
- active = source.slice(frozenSrcEnd);
4442
- }
4523
+ if (candidate.quiescent) freeze(cut, candidate);
4443
4524
  }
4525
+ nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
4526
+ onAttempt?.({ activeLength, frozenBytes });
4444
4527
  }
4445
- const tail = processSliceInstrumented(active);
4528
+ const tail = processSliceInstrumented(active, false);
4446
4529
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
4447
4530
  const out = head + tail.out;
4448
4531
  prevSource = source;