stream-markdown-parser 1.0.3 → 1.0.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
@@ -10193,7 +10193,14 @@ const NON_STRUCTURING_HTML_TAG_NAMES = [
10193
10193
  "pre",
10194
10194
  "script",
10195
10195
  "style",
10196
+ "table",
10197
+ "tbody",
10198
+ "td",
10199
+ "tfoot",
10200
+ "th",
10201
+ "thead",
10196
10202
  "textarea",
10203
+ "tr",
10197
10204
  "title"
10198
10205
  ];
10199
10206
  const VOID_HTML_TAGS = new Set(VOID_HTML_TAG_NAMES);
@@ -12712,6 +12719,16 @@ function isMathLike(s) {
12712
12719
 
12713
12720
  //#endregion
12714
12721
  //#region src/plugins/math.ts
12722
+ const MARKSTREAM_MATH_PLUGIN_APPLIED = "__markstreamMathPluginApplied";
12723
+ const TOLERANT_BOUNDARY_SCAN_MAX_LINES = 80;
12724
+ const TOLERANT_BOUNDARY_SCAN_MAX_CHARS = 2e4;
12725
+ const TOLERANT_BOUNDARY_SCAN_TAIL_CHARS = TOLERANT_BOUNDARY_SCAN_MAX_CHARS + 4096;
12726
+ function hasMarkstreamMathPlugin(md) {
12727
+ return !!md[MARKSTREAM_MATH_PLUGIN_APPLIED];
12728
+ }
12729
+ function markMarkstreamMathPluginApplied(md) {
12730
+ md[MARKSTREAM_MATH_PLUGIN_APPLIED] = true;
12731
+ }
12715
12732
  const KATEX_COMMANDS = [
12716
12733
  "ldots",
12717
12734
  "cdots",
@@ -12985,6 +13002,233 @@ function findSingleDollarClose(src, startIdx) {
12985
13002
  }
12986
13003
  return -1;
12987
13004
  }
13005
+ function findUnescapedDelimiter(src, delimiter$1, startIdx = 0) {
13006
+ let searchPos = Math.max(0, startIdx);
13007
+ while (searchPos < src.length) {
13008
+ const index = src.indexOf(delimiter$1, searchPos);
13009
+ if (index === -1) return -1;
13010
+ if (!isEscapedAt(src, index)) return index;
13011
+ searchPos = index + Math.max(1, delimiter$1.length);
13012
+ }
13013
+ return -1;
13014
+ }
13015
+ function countUnescapedDelimiter(src, delimiter$1, startIdx = 0, endIdx = src.length, excludedRanges = []) {
13016
+ let count = 0;
13017
+ let searchPos = Math.max(0, startIdx);
13018
+ const end = Math.min(src.length, Math.max(0, endIdx));
13019
+ while (searchPos < end) {
13020
+ const index = src.indexOf(delimiter$1, searchPos);
13021
+ if (index === -1 || index >= end) break;
13022
+ const excluded = findRangeAt(excludedRanges, index);
13023
+ if (excluded) {
13024
+ searchPos = Math.max(index + Math.max(1, delimiter$1.length), excluded[1]);
13025
+ continue;
13026
+ }
13027
+ if (!isEscapedAt(src, index)) count++;
13028
+ searchPos = index + Math.max(1, delimiter$1.length);
13029
+ }
13030
+ return count;
13031
+ }
13032
+ function getTolerantBoundaryLineEndOpenIndex(line, openDelim, closeDelim) {
13033
+ const source = trimRightSpaceOrTab(String(line ?? ""));
13034
+ if (!source.endsWith(openDelim)) return -1;
13035
+ const openIndex = source.length - openDelim.length;
13036
+ if (openIndex <= 0) return -1;
13037
+ if (!trimRightSpaceOrTab(source.slice(0, openIndex)).trim()) return -1;
13038
+ if (isEscapedAt(source, openIndex)) return -1;
13039
+ const codeSpanRanges = buildCodeSpanRanges(source);
13040
+ if (findRangeAt(codeSpanRanges, openIndex)) return -1;
13041
+ const previousOpenCount = countUnescapedDelimiter(source, openDelim, 0, openIndex, codeSpanRanges);
13042
+ if (openDelim === "$$") {
13043
+ if (previousOpenCount % 2 === 1) return -1;
13044
+ } else if (previousOpenCount > countUnescapedDelimiter(source, closeDelim, 0, openIndex, codeSpanRanges)) return -1;
13045
+ return openIndex;
13046
+ }
13047
+ function isSpaceOrTab(ch) {
13048
+ return ch === " " || ch === " ";
13049
+ }
13050
+ function trimRightSpaceOrTab(value) {
13051
+ let end = value.length;
13052
+ while (end > 0 && isSpaceOrTab(value[end - 1])) end--;
13053
+ return value.slice(0, end);
13054
+ }
13055
+ function countLineBreaks(value) {
13056
+ let count = 0;
13057
+ for (let index = 0; index < value.length; index++) if (value[index] === "\n") count++;
13058
+ return count;
13059
+ }
13060
+ function isAsciiDigit(ch) {
13061
+ if (!ch) return false;
13062
+ const code$1 = ch.charCodeAt(0);
13063
+ return code$1 >= 48 && code$1 <= 57;
13064
+ }
13065
+ function isThematicLikeLine(trimmed) {
13066
+ if (trimmed.length < 3) return false;
13067
+ const marker = trimmed[0];
13068
+ if (marker !== "-" && marker !== "*" && marker !== "_" && marker !== "=") return false;
13069
+ let markerCount = 0;
13070
+ for (let index = 0; index < trimmed.length; index++) {
13071
+ const ch = trimmed[index];
13072
+ if (ch === marker) {
13073
+ markerCount++;
13074
+ continue;
13075
+ }
13076
+ if (isSpaceOrTab(ch)) continue;
13077
+ return false;
13078
+ }
13079
+ return markerCount >= 3;
13080
+ }
13081
+ function isMarkdownTableDelimiterCell(cell) {
13082
+ const value = cell.trim();
13083
+ if (!value) return false;
13084
+ let index = 0;
13085
+ if (value[index] === ":") index++;
13086
+ let dashCount = 0;
13087
+ while (value[index] === "-") {
13088
+ dashCount++;
13089
+ index++;
13090
+ }
13091
+ if (dashCount < 3) return false;
13092
+ if (value[index] === ":") index++;
13093
+ return index === value.length;
13094
+ }
13095
+ function isMarkdownTableDelimiterLine(trimmed) {
13096
+ if (!trimmed.includes("|")) return false;
13097
+ const withoutLeadingPipe = trimmed[0] === "|" ? trimmed.slice(1) : trimmed;
13098
+ return (withoutLeadingPipe.endsWith("|") ? withoutLeadingPipe.slice(0, -1) : withoutLeadingPipe).split("|").every(isMarkdownTableDelimiterCell);
13099
+ }
13100
+ function isOrderedListBoundaryLine(trimmed) {
13101
+ let index = 0;
13102
+ if (!isAsciiDigit(trimmed[index])) return false;
13103
+ while (isAsciiDigit(trimmed[index])) index++;
13104
+ if (trimmed[index] !== "." && trimmed[index] !== ")") return false;
13105
+ return isSpaceOrTab(trimmed[index + 1]);
13106
+ }
13107
+ function isTolerantBoundaryStopLine(line) {
13108
+ const trimmed = line.trimStart();
13109
+ if (!trimmed) return true;
13110
+ if (trimmed.startsWith("```") || trimmed.startsWith("~~~") || trimmed.startsWith(":::")) return true;
13111
+ if (trimmed[0] === ">" || trimmed[0] === "<") return true;
13112
+ if (trimmed[0] === "#") {
13113
+ let level = 0;
13114
+ while (trimmed[level] === "#") level++;
13115
+ if (level >= 1 && level <= 6 && isSpaceOrTab(trimmed[level])) return true;
13116
+ }
13117
+ if ((trimmed[0] === "-" || trimmed[0] === "+" || trimmed[0] === "*") && isSpaceOrTab(trimmed[1])) return true;
13118
+ if (isOrderedListBoundaryLine(trimmed)) return true;
13119
+ if (isThematicLikeLine(trimmed)) return true;
13120
+ if (isMarkdownTableDelimiterLine(trimmed)) return true;
13121
+ return false;
13122
+ }
13123
+ function appendTolerantBoundaryContent(content, line) {
13124
+ if (!content) return line;
13125
+ if (!line) return content;
13126
+ return `${content}\n${line}`;
13127
+ }
13128
+ function isTolerantMathBlockContent(content) {
13129
+ const stripped = String(content ?? "").trim();
13130
+ if (!stripped) return false;
13131
+ return isMathLike(stripped);
13132
+ }
13133
+ function hashTolerantBoundaryContent(content) {
13134
+ let hash = 0;
13135
+ for (let index = 0; index < content.length; index++) hash = hash * 31 + content.charCodeAt(index) | 0;
13136
+ return hash.toString(36);
13137
+ }
13138
+ function getTolerantBoundaryScanWindow(source) {
13139
+ if (source.length <= TOLERANT_BOUNDARY_SCAN_TAIL_CHARS) return {
13140
+ source,
13141
+ lineOffset: 0
13142
+ };
13143
+ let start = source.length - TOLERANT_BOUNDARY_SCAN_TAIL_CHARS;
13144
+ const nextLineBreak = source.indexOf("\n", start);
13145
+ if (nextLineBreak === -1) return {
13146
+ source: "",
13147
+ lineOffset: countLineBreaks(source)
13148
+ };
13149
+ start = nextLineBreak + 1;
13150
+ return {
13151
+ source: source.slice(start),
13152
+ lineOffset: countLineBreaks(source.slice(0, start))
13153
+ };
13154
+ }
13155
+ function mayContainTolerantMathBlockBoundaryOpener(markdown) {
13156
+ const fullSource = String(markdown ?? "");
13157
+ if (!fullSource || !fullSource.includes("$$") && !fullSource.includes("\\[")) return false;
13158
+ const { source } = getTolerantBoundaryScanWindow(fullSource);
13159
+ if (!source) return false;
13160
+ const lines = source.split(/\r?\n/);
13161
+ const startLine = Math.max(0, lines.length - TOLERANT_BOUNDARY_SCAN_MAX_LINES - 2);
13162
+ const delimiters = [["$$", "$$"], ["\\[", "\\]"]];
13163
+ for (let line = startLine; line < lines.length; line++) {
13164
+ const openingLine = trimRightSpaceOrTab(lines[line]);
13165
+ if (!openingLine) continue;
13166
+ if (isTolerantBoundaryStopLine(openingLine)) continue;
13167
+ for (const [openDelim, closeDelim] of delimiters) if (getTolerantBoundaryLineEndOpenIndex(openingLine, openDelim, closeDelim) !== -1) return true;
13168
+ }
13169
+ return false;
13170
+ }
13171
+ function getTolerantMathBlockBoundaryStreamKey(markdown) {
13172
+ const fullSource = String(markdown ?? "");
13173
+ if (!fullSource || !fullSource.includes("$$") && !fullSource.includes("\\[")) return null;
13174
+ const { source, lineOffset } = getTolerantBoundaryScanWindow(fullSource);
13175
+ if (!source) return null;
13176
+ const lines = source.split(/\r?\n/);
13177
+ const startLine = Math.max(0, lines.length - TOLERANT_BOUNDARY_SCAN_MAX_LINES - 2);
13178
+ const delimiters = [["$$", "$$"], ["\\[", "\\]"]];
13179
+ for (let line = startLine; line < lines.length - 1; line++) {
13180
+ const openingLine = trimRightSpaceOrTab(lines[line]);
13181
+ for (const [openDelim, closeDelim] of delimiters) {
13182
+ const openIndex = getTolerantBoundaryLineEndOpenIndex(openingLine, openDelim, closeDelim);
13183
+ if (openIndex === -1) continue;
13184
+ let content = "";
13185
+ let stopped = false;
13186
+ for (let currentLine = line + 1; currentLine < lines.length; currentLine++) {
13187
+ if (currentLine - line > TOLERANT_BOUNDARY_SCAN_MAX_LINES) {
13188
+ stopped = true;
13189
+ break;
13190
+ }
13191
+ const current = lines[currentLine];
13192
+ const closeIndex = findUnescapedDelimiter(current, closeDelim);
13193
+ if (closeIndex !== -1) {
13194
+ const nextContent = appendTolerantBoundaryContent(content, current.slice(0, closeIndex));
13195
+ if (!isTolerantMathBlockContent(nextContent)) {
13196
+ stopped = true;
13197
+ break;
13198
+ }
13199
+ const suffix = current.slice(closeIndex + closeDelim.length);
13200
+ const suffixKey = suffix.trim() ? `suffix:${hashTolerantBoundaryContent(suffix)}` : "nosuffix";
13201
+ return [
13202
+ "closed",
13203
+ openDelim,
13204
+ lineOffset + line,
13205
+ openIndex,
13206
+ lineOffset + currentLine,
13207
+ closeIndex,
13208
+ hashTolerantBoundaryContent(nextContent),
13209
+ suffixKey
13210
+ ].join(":");
13211
+ }
13212
+ if (isTolerantBoundaryStopLine(current)) {
13213
+ stopped = true;
13214
+ break;
13215
+ }
13216
+ content = appendTolerantBoundaryContent(content, current);
13217
+ if (content.length > TOLERANT_BOUNDARY_SCAN_MAX_CHARS) {
13218
+ stopped = true;
13219
+ break;
13220
+ }
13221
+ }
13222
+ if (!stopped && isTolerantMathBlockContent(content)) return [
13223
+ "pending",
13224
+ openDelim,
13225
+ lineOffset + line,
13226
+ openIndex
13227
+ ].join(":");
13228
+ }
13229
+ }
13230
+ return null;
13231
+ }
12988
13232
  function isLikelyCurrencyRangeDollar(content, nextChar) {
12989
13233
  const stripped = String(content ?? "").trim();
12990
13234
  if (!stripped) return false;
@@ -13005,6 +13249,18 @@ function isLikelyPlaceholderDollar(content) {
13005
13249
  return /^(?:\.{3,}|…+)$/.test(stripped);
13006
13250
  }
13007
13251
  function applyMath(md, mathOpts) {
13252
+ markMarkstreamMathPluginApplied(md);
13253
+ const pushInlineParagraph = (s, content, line) => {
13254
+ const paragraphContent = String(content ?? "").replace(/^[\t ]+/, "").replace(/[\t ]+$/, "");
13255
+ if (!paragraphContent) return;
13256
+ const paragraphOpen = s.push("paragraph_open", "p", 1);
13257
+ paragraphOpen.map = [line, line + 1];
13258
+ const inlineToken = s.push("inline", "", 0);
13259
+ inlineToken.content = paragraphContent;
13260
+ inlineToken.map = [line, line + 1];
13261
+ inlineToken.children = [];
13262
+ s.push("paragraph_close", "p", -1);
13263
+ };
13008
13264
  const mathInline = (state, silent) => {
13009
13265
  const s = state;
13010
13266
  const strict = !!mathOpts?.strictDelimiters;
@@ -13020,6 +13276,18 @@ function applyMath(md, mathOpts) {
13020
13276
  return end;
13021
13277
  };
13022
13278
  if (/^\*[^*]+/.test(s.src)) return false;
13279
+ if (s.src[s.pos] === "$") {
13280
+ let dollarRunEnd = s.pos + 1;
13281
+ while (s.src[dollarRunEnd] === "$") dollarRunEnd++;
13282
+ const dollarRunLength = dollarRunEnd - s.pos;
13283
+ const nextChar = s.src[dollarRunEnd];
13284
+ if (dollarRunLength >= 3 && (!nextChar || /\s/.test(nextChar))) {
13285
+ const token = s.push("text", "", 0);
13286
+ token.content = s.src.slice(s.pos, dollarRunEnd);
13287
+ s.pos = dollarRunEnd;
13288
+ return true;
13289
+ }
13290
+ }
13023
13291
  const delimiters = [
13024
13292
  ["$$", "$$"],
13025
13293
  ["$", "$"],
@@ -13383,6 +13651,8 @@ function applyMath(md, mathOpts) {
13383
13651
  let openDelim = "";
13384
13652
  let closeDelim = "";
13385
13653
  let skipFirstLine = false;
13654
+ let prefixBeforeOpen = "";
13655
+ let tolerantBoundary = false;
13386
13656
  for (const [open, close] of delimiters) if (lineText.startsWith(open)) if (open.includes("[")) if (mathOpts?.strictDelimiters) {
13387
13657
  if (lineText.replace("\\", "") === "[") {
13388
13658
  if (startLine + 1 < endLine) {
@@ -13423,8 +13693,11 @@ function applyMath(md, mathOpts) {
13423
13693
  closeDelim = close;
13424
13694
  break;
13425
13695
  }
13426
- else if (open === "$$" && lineText.endsWith(open) && !lineText.slice(0, lineText.length - open.length).trim().includes(open) && startLine + 1 < endLine) {
13427
- s.push("text", "", 0).content = lineText.slice(0, lineText.length - open.length);
13696
+ else if ((open === "$$" || open === "\\[") && lineText.endsWith(open) && startLine + 1 < endLine) {
13697
+ const openIndex = getTolerantBoundaryLineEndOpenIndex(lineText, open, close);
13698
+ if (openIndex === -1) continue;
13699
+ prefixBeforeOpen = trimRightSpaceOrTab(lineText.slice(0, openIndex));
13700
+ tolerantBoundary = true;
13428
13701
  const nextLineStartPos = s.bMarks[startLine + 1] + s.tShift[startLine + 1];
13429
13702
  lineText = s.src.slice(nextLineStartPos, s.eMarks[startLine + 1]).trim();
13430
13703
  skipFirstLine = true;
@@ -13434,13 +13707,13 @@ function applyMath(md, mathOpts) {
13434
13707
  break;
13435
13708
  }
13436
13709
  if (!matched) return false;
13437
- if (silent) return true;
13710
+ if (silent && !tolerantBoundary) return true;
13438
13711
  const startDelimIndex = lineText.indexOf(openDelim);
13439
13712
  const closeSearchStart = startDelimIndex + openDelim.length;
13440
13713
  const escapedPlainBracketCloseIndex = !strict && openDelim === "[" ? lineText.indexOf("\\]", closeSearchStart) : -1;
13441
13714
  const sameLineCloseDelim = escapedPlainBracketCloseIndex >= 0 ? "\\]" : closeDelim;
13442
- const sameLineCloseIndex = escapedPlainBracketCloseIndex >= 0 ? escapedPlainBracketCloseIndex : lineText.indexOf(closeDelim, closeSearchStart);
13443
- if (sameLineCloseIndex > openDelim.length) {
13715
+ const sameLineCloseIndex = escapedPlainBracketCloseIndex >= 0 ? escapedPlainBracketCloseIndex : findUnescapedDelimiter(lineText, closeDelim, closeSearchStart);
13716
+ if (!skipFirstLine && sameLineCloseIndex > openDelim.length) {
13444
13717
  const content$1 = lineText.slice(startDelimIndex + openDelim.length, sameLineCloseIndex);
13445
13718
  const token$1 = s.push("math_block", "math", 0);
13446
13719
  token$1.content = normalizeStandaloneBackslashT(content$1);
@@ -13450,18 +13723,25 @@ function applyMath(md, mathOpts) {
13450
13723
  token$1.block = true;
13451
13724
  token$1.loading = false;
13452
13725
  s.line = startLine + 1;
13726
+ const trailingAfterClose$1 = lineText.slice(sameLineCloseIndex + sameLineCloseDelim.length);
13727
+ if (trailingAfterClose$1.trim()) pushInlineParagraph(s, trailingAfterClose$1, startLine);
13453
13728
  return true;
13454
13729
  }
13455
13730
  let nextLine = startLine;
13456
13731
  let content = "";
13457
13732
  let found = false;
13458
- const firstLineContent = lineText === openDelim ? "" : lineText.slice(openDelim.length);
13733
+ let trailingAfterClose = "";
13734
+ let trailingAfterCloseLine = startLine;
13735
+ const firstLineContent = skipFirstLine ? lineText : lineText === openDelim ? "" : lineText.slice(openDelim.length);
13459
13736
  const fallbackPlainBracketClose = !strict && openDelim === "\\[" ? "]" : "";
13460
- if (firstLineContent.includes(closeDelim)) {
13461
- const endIndex = firstLineContent.indexOf(closeDelim);
13737
+ const firstLineCloseIndex = findUnescapedDelimiter(firstLineContent, closeDelim);
13738
+ if (firstLineCloseIndex !== -1) {
13739
+ const endIndex = firstLineCloseIndex;
13462
13740
  content = firstLineContent.slice(0, endIndex);
13741
+ trailingAfterClose = firstLineContent.slice(endIndex + closeDelim.length);
13742
+ trailingAfterCloseLine = skipFirstLine ? startLine + 1 : startLine;
13463
13743
  found = true;
13464
- nextLine = startLine;
13744
+ nextLine = trailingAfterCloseLine;
13465
13745
  } else {
13466
13746
  if (firstLineContent && !skipFirstLine) content = firstLineContent;
13467
13747
  for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
@@ -13486,12 +13766,18 @@ function applyMath(md, mathOpts) {
13486
13766
  found = true;
13487
13767
  const endIndex = currentLine.indexOf("\\]");
13488
13768
  closeDelim = "\\]";
13489
- content += (content ? "\n" : "") + currentLine.slice(0, endIndex);
13769
+ const beforeClose = currentLine.slice(0, endIndex);
13770
+ if (beforeClose) content += (content ? "\n" : "") + beforeClose;
13771
+ trailingAfterClose = currentLine.slice(endIndex + closeDelim.length);
13772
+ trailingAfterCloseLine = nextLine;
13490
13773
  break;
13491
- } else if (currentLine.includes(closeDelim)) {
13774
+ } else if (findUnescapedDelimiter(currentLine, closeDelim) !== -1) {
13492
13775
  found = true;
13493
- const endIndex = currentLine.indexOf(closeDelim);
13494
- content += (content ? "\n" : "") + currentLine.slice(0, endIndex);
13776
+ const endIndex = findUnescapedDelimiter(currentLine, closeDelim);
13777
+ const beforeClose = currentLine.slice(0, endIndex);
13778
+ if (beforeClose) content += (content ? "\n" : "") + beforeClose;
13779
+ trailingAfterClose = currentLine.slice(endIndex + closeDelim.length);
13780
+ trailingAfterCloseLine = nextLine;
13495
13781
  break;
13496
13782
  }
13497
13783
  content += (content ? "\n" : "") + currentLine;
@@ -13499,7 +13785,9 @@ function applyMath(md, mathOpts) {
13499
13785
  }
13500
13786
  if ((!allowLoading || strict) && !found) return false;
13501
13787
  const hasMarkdownPrefix = /^\s*!\[/.test(content);
13502
- if (!(openDelim === "$$" ? !hasMarkdownPrefix : openDelim === "[" ? isPlainBracketMathLike(content) : isMathLike(content))) return false;
13788
+ if (!(tolerantBoundary ? !hasMarkdownPrefix && isTolerantMathBlockContent(content) : openDelim === "$$" ? !hasMarkdownPrefix : openDelim === "[" ? isPlainBracketMathLike(content) : isMathLike(content))) return false;
13789
+ if (silent) return true;
13790
+ if (prefixBeforeOpen) pushInlineParagraph(s, prefixBeforeOpen, startLine);
13503
13791
  const token = s.push("math_block", "math", 0);
13504
13792
  token.content = normalizeStandaloneBackslashT(content);
13505
13793
  token.markup = openDelim === "$$" ? "$$" : openDelim === "[" ? "[]" : "\\[\\]";
@@ -13508,6 +13796,7 @@ function applyMath(md, mathOpts) {
13508
13796
  token.block = true;
13509
13797
  token.loading = !found;
13510
13798
  s.line = nextLine + 1;
13799
+ if (trailingAfterClose.trim()) pushInlineParagraph(s, trailingAfterClose, trailingAfterCloseLine);
13511
13800
  return true;
13512
13801
  };
13513
13802
  const explicitMathBlockBeforeSetext = (state, startLine, endLine, silent) => {
@@ -16848,6 +17137,8 @@ function parseParagraph(tokens, index, options) {
16848
17137
  //#endregion
16849
17138
  //#region src/parser/index.ts
16850
17139
  const streamParseEnvCache = /* @__PURE__ */ new WeakMap();
17140
+ const tolerantMathBoundaryStreamCache = /* @__PURE__ */ new WeakMap();
17141
+ const TOLERANT_BOUNDARY_SPLIT_OPENERS = ["$$", "\\["];
16851
17142
  function getNodeFields(node) {
16852
17143
  return node;
16853
17144
  }
@@ -17027,13 +17318,89 @@ function shouldResetTopLevelStreamCacheForFinalAutoParse(md, options) {
17027
17318
  const stream = md.stream;
17028
17319
  return options.final === true && streamParse === "auto" && internalOptions.__disableStreamParse !== true && stream?.enabled === true && typeof stream.reset === "function";
17029
17320
  }
17321
+ function clearTolerantMathBoundaryStreamCache(md) {
17322
+ tolerantMathBoundaryStreamCache.delete(md);
17323
+ }
17324
+ function setTolerantMathBoundaryStreamCache(md, source, key) {
17325
+ tolerantMathBoundaryStreamCache.set(md, {
17326
+ source,
17327
+ key,
17328
+ pendingCandidate: key === null && mayContainTolerantMathBlockBoundaryOpener(source)
17329
+ });
17330
+ }
17331
+ function sourceEndsWithSplitTolerantBoundaryPrefix(source) {
17332
+ return source.endsWith("$") || source.endsWith("\\");
17333
+ }
17334
+ function sourceEndsWithCompleteTolerantBoundaryOpener(source) {
17335
+ const lastLineStart = Math.max(source.lastIndexOf("\n") + 1, 0);
17336
+ const lastLine = source.slice(lastLineStart).replace(/[\t ]+$/, "");
17337
+ return TOLERANT_BOUNDARY_SPLIT_OPENERS.some((open) => lastLine.endsWith(open));
17338
+ }
17339
+ function appendedChunkMayAffectTolerantMathBoundary(previousSource, appended) {
17340
+ if (!appended) return false;
17341
+ if (appended.includes("$$") || appended.includes("\\[") || appended.includes("\\]")) return true;
17342
+ if (previousSource.endsWith("$") && appended[0] === "$") return true;
17343
+ if (previousSource.endsWith("\\") && (appended[0] === "[" || appended[0] === "]")) return true;
17344
+ if (sourceEndsWithCompleteTolerantBoundaryOpener(previousSource) && /[\r\n]/.test(appended)) return true;
17345
+ return false;
17346
+ }
17347
+ function syncTolerantMathBoundaryStreamCache(md, source) {
17348
+ if (!hasMarkstreamMathPlugin(md)) return;
17349
+ const stream = md.stream;
17350
+ if (typeof stream?.reset !== "function") return;
17351
+ const owner = md;
17352
+ const previous = tolerantMathBoundaryStreamCache.get(owner);
17353
+ if (previous?.source === source) return;
17354
+ if (previous && source.startsWith(previous.source)) {
17355
+ const appended = source.slice(previous.source.length);
17356
+ if (previous.key === null && previous.pendingCandidate === false && !appendedChunkMayAffectTolerantMathBoundary(previous.source, appended) && !sourceEndsWithSplitTolerantBoundaryPrefix(source)) {
17357
+ previous.source = source;
17358
+ return;
17359
+ }
17360
+ }
17361
+ const nextKey = getTolerantMathBlockBoundaryStreamKey(source);
17362
+ const sourceWasReplaced = previous ? !source.startsWith(previous.source) : false;
17363
+ if (previous && (sourceWasReplaced || previous.key !== nextKey)) stream.reset();
17364
+ else if (!previous && nextKey) stream.reset();
17365
+ setTolerantMathBoundaryStreamCache(md, source, nextKey);
17366
+ }
17030
17367
  function shouldCloneTopLevelStreamTokens(options) {
17031
17368
  return typeof options.preTransformTokens === "function" || typeof options.postTransformTokens === "function";
17032
17369
  }
17370
+ function sameTokenMap(left, right) {
17371
+ const leftMap = left?.map;
17372
+ const rightMap = right?.map;
17373
+ if (leftMap === rightMap) return true;
17374
+ if (!Array.isArray(leftMap) || !Array.isArray(rightMap)) return false;
17375
+ return leftMap.length === rightMap.length && leftMap.every((value, index) => value === rightMap[index]);
17376
+ }
17377
+ function isSameTokenShape(left, right) {
17378
+ return !!left && !!right && left.type === right.type && left.tag === right.tag && left.nesting === right.nesting && left.markup === right.markup && left.content === right.content && sameTokenMap(left, right);
17379
+ }
17380
+ function isParagraphTokenTriplet(tokens, index) {
17381
+ return tokens[index]?.type === "paragraph_open" && tokens[index + 1]?.type === "inline" && tokens[index + 2]?.type === "paragraph_close";
17382
+ }
17383
+ function hasAdjacentDuplicateParagraphTokenTriplet(tokens) {
17384
+ for (let index = 0; index + 5 < tokens.length; index++) if (isParagraphTokenTriplet(tokens, index) && isParagraphTokenTriplet(tokens, index + 3) && isSameTokenShape(tokens[index], tokens[index + 3]) && isSameTokenShape(tokens[index + 1], tokens[index + 4]) && isSameTokenShape(tokens[index + 2], tokens[index + 5])) return true;
17385
+ return false;
17386
+ }
17387
+ function shouldFallbackDuplicateTolerantMathStreamTokens(md, source, tokens) {
17388
+ return hasMarkstreamMathPlugin(md) && mayContainTolerantMathBlockBoundaryOpener(source) && hasAdjacentDuplicateParagraphTokenTriplet(tokens);
17389
+ }
17390
+ function shouldUseSyncParseForPendingTolerantMathBoundary(md) {
17391
+ const cache = tolerantMathBoundaryStreamCache.get(md);
17392
+ return typeof cache?.key === "string" && cache.key.startsWith("pending:");
17393
+ }
17033
17394
  function parseTopLevelTokens(md, source, env, options) {
17034
17395
  if (options.customHtmlTags?.length) env.__markstreamCustomHtmlTags = options.customHtmlTags;
17035
17396
  if (!shouldUseTopLevelStreamParse(md, options)) return md.parse(source, env);
17397
+ syncTolerantMathBoundaryStreamCache(md, source);
17398
+ if (shouldUseSyncParseForPendingTolerantMathBoundary(md)) return md.parse(source, env);
17036
17399
  const tokens = md.stream.parse(source, getStableStreamEnv(md, env));
17400
+ if (shouldFallbackDuplicateTolerantMathStreamTokens(md, source, tokens)) {
17401
+ md.stream?.reset?.();
17402
+ return md.parse(source, env);
17403
+ }
17037
17404
  if (!shouldCloneTopLevelStreamTokens(options)) return tokens;
17038
17405
  const timing = getParseTiming(options);
17039
17406
  if (!timing) return cloneMarkdownTokens(tokens, true);
@@ -18239,7 +18606,10 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
18239
18606
  const parseStartedAt = timing ? getParserNow() : 0;
18240
18607
  const isFinal = !!options.final;
18241
18608
  let safeMarkdown = (markdown ?? "").toString().replace(/([^\\])\r(ight|ho)/g, "$1\\r$2").replace(/([^\\])\n(abla|eq|ot|exists)/g, "$1\\n$2");
18242
- if (shouldResetTopLevelStreamCacheForFinalAutoParse(md, options)) md.stream.reset();
18609
+ if (shouldResetTopLevelStreamCacheForFinalAutoParse(md, options)) {
18610
+ md.stream.reset();
18611
+ clearTolerantMathBoundaryStreamCache(md);
18612
+ }
18243
18613
  if (!isFinal) {
18244
18614
  if (safeMarkdown.endsWith("- *")) safeMarkdown = safeMarkdown.replace(/- \*$/, "- \\*");
18245
18615
  if (/(?:^|\n)\s*-\s*$/.test(safeMarkdown)) safeMarkdown = safeMarkdown.replace(/(?:^|\n)\s*-\s*$/, (m) => {