stream-markdown-parser 0.0.73 → 0.0.75

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
@@ -601,7 +601,7 @@ var require_markdown_it_task_checkbox = /* @__PURE__ */ __commonJS({ "../../node
601
601
  }) });
602
602
 
603
603
  //#endregion
604
- //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.7/node_modules/markdown-it-ts/dist/chunk-Bp6m_JJh.js
604
+ //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.9/node_modules/markdown-it-ts/dist/chunk-Bp6m_JJh.js
605
605
  var import_markdown_it_task_checkbox = /* @__PURE__ */ __toESM(require_markdown_it_task_checkbox(), 1);
606
606
  var __defProp = Object.defineProperty;
607
607
  var __export = (all) => {
@@ -2187,7 +2187,7 @@ var require_punycode = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/pu
2187
2187
  }) });
2188
2188
 
2189
2189
  //#endregion
2190
- //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.7/node_modules/markdown-it-ts/dist/index.js
2190
+ //#region ../../node_modules/.pnpm/markdown-it-ts@0.0.9/node_modules/markdown-it-ts/dist/index.js
2191
2191
  var import_punycode = /* @__PURE__ */ __toESM(require_punycode(), 1);
2192
2192
  var utils_exports = /* @__PURE__ */ __export({
2193
2193
  arrayReplaceAt: () => arrayReplaceAt,
@@ -2445,10 +2445,23 @@ function parseLinkDestination(str, start, max) {
2445
2445
  return result;
2446
2446
  }
2447
2447
  var parse_link_destination_default = parseLinkDestination;
2448
- /**
2449
- * Parse link label: returns the end position of label or -1 if not found
2450
- * Assumes first character ([) already matches
2451
- */
2448
+ const FALLBACK_TO_INLINE_SCAN = -2;
2449
+ function scanPlainLinkLabel(src, start, max) {
2450
+ let pos = start + 1;
2451
+ while (pos < max) {
2452
+ const marker = src.charCodeAt(pos);
2453
+ if (marker === 93) return pos;
2454
+ if (marker === 92) {
2455
+ pos += 2;
2456
+ continue;
2457
+ }
2458
+ if (marker === 96 || marker === 60) return FALLBACK_TO_INLINE_SCAN;
2459
+ if (marker === 33 && pos + 1 < max && src.charCodeAt(pos + 1) === 91) return FALLBACK_TO_INLINE_SCAN;
2460
+ if (marker === 91) return FALLBACK_TO_INLINE_SCAN;
2461
+ pos++;
2462
+ }
2463
+ return -1;
2464
+ }
2452
2465
  function parseLinkLabel(state, start, disableNested) {
2453
2466
  let level = 1;
2454
2467
  let found = false;
@@ -2457,7 +2470,15 @@ function parseLinkLabel(state, start, disableNested) {
2457
2470
  const src = state.src;
2458
2471
  const max = state.posMax;
2459
2472
  const oldPos = state.pos;
2460
- const inline$1 = state.md.inline;
2473
+ const noCloseFrom = state.__mdtsLinkLabelNoCloseFrom;
2474
+ if (typeof noCloseFrom === "number" && start + 1 >= noCloseFrom) return -1;
2475
+ const nextClose = src.indexOf("]", start + 1);
2476
+ if (nextClose < 0 || nextClose >= max) {
2477
+ state.__mdtsLinkLabelNoCloseFrom = start + 1;
2478
+ return -1;
2479
+ }
2480
+ const fastLabelEnd = scanPlainLinkLabel(src, start, max);
2481
+ if (fastLabelEnd !== FALLBACK_TO_INLINE_SCAN) return fastLabelEnd;
2461
2482
  state.pos = start + 1;
2462
2483
  while (state.pos < max) {
2463
2484
  marker = src.charCodeAt(state.pos);
@@ -2469,7 +2490,7 @@ function parseLinkLabel(state, start, disableNested) {
2469
2490
  }
2470
2491
  }
2471
2492
  prevPos = state.pos;
2472
- inline$1.skipToken(state);
2493
+ state.md.inline.skipToken(state);
2473
2494
  if (marker === 91) {
2474
2495
  if (prevPos === state.pos - 1) level++;
2475
2496
  else if (disableNested) {
@@ -2600,6 +2621,12 @@ function normalizeLinkText(url) {
2600
2621
  }
2601
2622
  return decode_default(format(parsed), `${decode_default.defaultChars}%`);
2602
2623
  }
2624
+ function setStrategyDiagnostics(env, info) {
2625
+ if (!env) return;
2626
+ try {
2627
+ env.__mdtsStrategyInfo = info;
2628
+ } catch {}
2629
+ }
2603
2630
  /**
2604
2631
  * class Token
2605
2632
  *
@@ -2787,42 +2814,22 @@ function block(state) {
2787
2814
  /**
2788
2815
  * Process autolinks '<protocol:...>'
2789
2816
  */
2790
- const EMAIL_RE = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
2791
- function isAsciiLetter$2(ch) {
2792
- const lc = ch | 32;
2793
- return lc >= 97 && lc <= 122;
2794
- }
2795
- function isAutolinkScheme(src, start, end) {
2796
- const len = end - start;
2797
- if (len < 2 || len > 32 || !isAsciiLetter$2(src.charCodeAt(start))) return false;
2798
- for (let pos = start + 1; pos < end; pos++) {
2799
- const ch = src.charCodeAt(pos);
2800
- if (ch >= 48 && ch <= 57 || ch >= 65 && ch <= 90 || ch >= 97 && ch <= 122 || ch === 43 || ch === 45 || ch === 46) continue;
2801
- return false;
2802
- }
2803
- return true;
2804
- }
2817
+ const EMAIL_RE = /^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/;
2818
+ const AUTOLINK_RE = /^([a-zA-Z][a-zA-Z0-9+.-]{1,31}):([^<>\x00-\x20]*)$/;
2805
2819
  function autolink(state, silent) {
2806
2820
  let pos = state.pos;
2807
2821
  const src = state.src;
2808
2822
  if (src.charCodeAt(pos) !== 60) return false;
2809
2823
  const start = pos;
2810
2824
  const max = state.posMax;
2811
- let colonPos = -1;
2812
- let hasAt = false;
2813
2825
  for (;;) {
2814
2826
  if (++pos >= max) return false;
2815
2827
  const ch = src.charCodeAt(pos);
2816
2828
  if (ch === 60) return false;
2817
2829
  if (ch === 62) break;
2818
- if (ch <= 32) return false;
2819
- if (ch === 58) {
2820
- if (colonPos < 0) colonPos = pos;
2821
- } else if (ch === 64) hasAt = true;
2822
- }
2823
- if (colonPos < 0 && !hasAt) return false;
2824
- if (colonPos > start + 2 && isAutolinkScheme(src, start + 1, colonPos)) {
2825
- const url = src.slice(start + 1, pos);
2830
+ }
2831
+ const url = src.slice(start + 1, pos);
2832
+ if (AUTOLINK_RE.test(url)) {
2826
2833
  const fullUrl = state.md.normalizeLink(url);
2827
2834
  if (!state.md.validateLink(fullUrl)) return false;
2828
2835
  if (!silent) {
@@ -2830,18 +2837,16 @@ function autolink(state, silent) {
2830
2837
  token_o.attrs = [["href", fullUrl]];
2831
2838
  token_o.markup = "autolink";
2832
2839
  token_o.info = "auto";
2833
- const token_t = state.pushSimple("text", "");
2840
+ const token_t = state.push("text", "", 0);
2834
2841
  token_t.content = state.md.normalizeLinkText(url);
2835
2842
  const token_c = state.push("link_close", "a", -1);
2836
2843
  token_c.markup = "autolink";
2837
2844
  token_c.info = "auto";
2838
2845
  }
2839
- state.pos = pos + 1;
2846
+ state.pos += url.length + 2;
2840
2847
  return true;
2841
2848
  }
2842
- if (hasAt) {
2843
- const url = src.slice(start + 1, pos);
2844
- if (!EMAIL_RE.test(url)) return false;
2849
+ if (EMAIL_RE.test(url)) {
2845
2850
  const fullUrl = state.md.normalizeLink(`mailto:${url}`);
2846
2851
  if (!state.md.validateLink(fullUrl)) return false;
2847
2852
  if (!silent) {
@@ -2849,13 +2854,13 @@ function autolink(state, silent) {
2849
2854
  token_o.attrs = [["href", fullUrl]];
2850
2855
  token_o.markup = "autolink";
2851
2856
  token_o.info = "auto";
2852
- const token_t = state.pushSimple("text", "");
2857
+ const token_t = state.push("text", "", 0);
2853
2858
  token_t.content = state.md.normalizeLinkText(url);
2854
2859
  const token_c = state.push("link_close", "a", -1);
2855
2860
  token_c.markup = "autolink";
2856
2861
  token_c.info = "auto";
2857
2862
  }
2858
- state.pos = pos + 1;
2863
+ state.pos += url.length + 2;
2859
2864
  return true;
2860
2865
  }
2861
2866
  return false;
@@ -3641,9 +3646,11 @@ var text_default = text;
3641
3646
  var InlineRuler = class {
3642
3647
  rules = [];
3643
3648
  cache = null;
3649
+ namedCache = null;
3644
3650
  version = 0;
3645
3651
  invalidateCache() {
3646
3652
  this.cache = null;
3653
+ this.namedCache = null;
3647
3654
  this.version++;
3648
3655
  }
3649
3656
  /**
@@ -3743,6 +3750,11 @@ var InlineRuler = class {
3743
3750
  if (!this.cache) this.compileCache();
3744
3751
  return this.cache.get(chain) ?? [];
3745
3752
  }
3753
+ getNamedRules(chainName) {
3754
+ const chain = chainName || "";
3755
+ if (!this.namedCache) this.compileCache();
3756
+ return this.namedCache.get(chain) ?? [];
3757
+ }
3746
3758
  compileCache() {
3747
3759
  const chains = new Set([""]);
3748
3760
  for (const rule of this.rules) {
@@ -3750,16 +3762,24 @@ var InlineRuler = class {
3750
3762
  if (rule.alt) for (const alt of rule.alt) chains.add(alt);
3751
3763
  }
3752
3764
  const cache = /* @__PURE__ */ new Map();
3765
+ const namedCache = /* @__PURE__ */ new Map();
3753
3766
  for (const chain of chains) {
3754
3767
  const bucket = [];
3768
+ const namedBucket = [];
3755
3769
  for (const rule of this.rules) {
3756
3770
  if (!rule.enabled) continue;
3757
3771
  if (chain !== "" && !rule.alt?.includes(chain)) continue;
3758
3772
  bucket.push(rule.fn);
3773
+ namedBucket.push({
3774
+ name: rule.name,
3775
+ fn: rule.fn
3776
+ });
3759
3777
  }
3760
3778
  cache.set(chain, bucket);
3779
+ namedCache.set(chain, namedBucket);
3761
3780
  }
3762
3781
  this.cache = cache;
3782
+ this.namedCache = namedCache;
3763
3783
  }
3764
3784
  };
3765
3785
  /**
@@ -3871,6 +3891,78 @@ var StateInline = class {
3871
3891
  }
3872
3892
  };
3873
3893
  StateInline.prototype.Token = Token;
3894
+ function now() {
3895
+ if (typeof performance !== "undefined" && typeof performance.now === "function") return performance.now();
3896
+ return Date.now();
3897
+ }
3898
+ function median(values) {
3899
+ if (values.length === 0) return 0;
3900
+ const sorted = values.slice().sort((a, b) => a - b);
3901
+ const mid = Math.floor(sorted.length / 2);
3902
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
3903
+ }
3904
+ function createRecord(chain, name) {
3905
+ return {
3906
+ chain,
3907
+ name,
3908
+ calls: 0,
3909
+ hits: 0,
3910
+ inclusiveMs: 0,
3911
+ medianMs: 0,
3912
+ maxMs: 0,
3913
+ normalCalls: 0,
3914
+ normalHits: 0,
3915
+ silentCalls: 0,
3916
+ silentHits: 0,
3917
+ samples: []
3918
+ };
3919
+ }
3920
+ function getRuleProfile(env) {
3921
+ const carrier = env;
3922
+ if (!carrier) return null;
3923
+ if (carrier.__mdtsRuleProfile) return carrier.__mdtsRuleProfile;
3924
+ if (!carrier.__mdtsProfileRules) return null;
3925
+ const meta = carrier.__mdtsProfileRules === true ? {} : carrier.__mdtsProfileRules;
3926
+ const session = {
3927
+ enabled: true,
3928
+ fixture: meta.fixture,
3929
+ mode: meta.mode,
3930
+ startedAt: now(),
3931
+ records: Object.create(null)
3932
+ };
3933
+ carrier.__mdtsRuleProfile = session;
3934
+ return session;
3935
+ }
3936
+ function recordRuleInvocation(env, chain, name, durationMs, hit, silent) {
3937
+ const session = getRuleProfile(env);
3938
+ if (!session) return;
3939
+ const key = `${chain}:${name}`;
3940
+ const record = session.records[key] ?? (session.records[key] = createRecord(chain, name));
3941
+ record.calls++;
3942
+ record.inclusiveMs += durationMs;
3943
+ if (durationMs > record.maxMs) record.maxMs = durationMs;
3944
+ record.samples.push(durationMs);
3945
+ if (silent) {
3946
+ record.silentCalls++;
3947
+ if (hit) record.silentHits++;
3948
+ } else {
3949
+ record.normalCalls++;
3950
+ if (hit) record.normalHits++;
3951
+ }
3952
+ if (hit) record.hits++;
3953
+ session.completedAt = now();
3954
+ }
3955
+ function finalizeRuleProfile(env) {
3956
+ const session = getRuleProfile(env);
3957
+ if (!session) return null;
3958
+ const records = Object.keys(session.records);
3959
+ for (let i = 0; i < records.length; i++) {
3960
+ const record = session.records[records[i]];
3961
+ record.medianMs = median(record.samples);
3962
+ }
3963
+ session.completedAt = now();
3964
+ return session;
3965
+ }
3874
3966
  /**
3875
3967
  * ParserInline - inline parser with Ruler-based rule management
3876
3968
  */
@@ -3939,9 +4031,11 @@ var ParserInline = class {
3939
4031
  skipToken(state) {
3940
4032
  const pos = state.pos;
3941
4033
  const rules = this.getRules();
4034
+ const namedRules = this.ruler.getNamedRules("");
3942
4035
  const len = rules.length;
3943
4036
  const cache = state.cache;
3944
4037
  const cached = cache[pos];
4038
+ const shouldProfile = !!state.env && (Object.prototype.hasOwnProperty.call(state.env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(state.env, "__mdtsProfileRules"));
3945
4039
  if (cached !== void 0) {
3946
4040
  state.pos = cached;
3947
4041
  return;
@@ -3949,7 +4043,13 @@ var ParserInline = class {
3949
4043
  let ok = false;
3950
4044
  if (state.level < state.maxNesting) for (let i = 0; i < len; i++) {
3951
4045
  state.level++;
3952
- ok = rules[i](state, true);
4046
+ if (!shouldProfile) ok = rules[i](state, true);
4047
+ else {
4048
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4049
+ ok = namedRules[i].fn(state, true);
4050
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4051
+ recordRuleInvocation(state.env, "inline", namedRules[i].name, endedAt - startedAt, !!ok, true);
4052
+ }
3953
4053
  state.level--;
3954
4054
  if (ok) {
3955
4055
  if (pos >= state.pos) throw new Error("inline rule didn't increment state.pos");
@@ -3965,13 +4065,21 @@ var ParserInline = class {
3965
4065
  */
3966
4066
  tokenize(state) {
3967
4067
  const rules = this.getRules();
4068
+ const namedRules = this.ruler.getNamedRules("");
3968
4069
  const len = rules.length;
3969
4070
  const end = state.posMax;
4071
+ const shouldProfile = !!state.env && (Object.prototype.hasOwnProperty.call(state.env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(state.env, "__mdtsProfileRules"));
3970
4072
  while (state.pos < end) {
3971
4073
  const prevPos = state.pos;
3972
4074
  let ok = false;
3973
4075
  if (state.level < state.maxNesting) for (let i = 0; i < len; i++) {
3974
- ok = rules[i](state, false);
4076
+ if (!shouldProfile) ok = rules[i](state, false);
4077
+ else {
4078
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4079
+ ok = namedRules[i].fn(state, false);
4080
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4081
+ recordRuleInvocation(state.env, "inline", namedRules[i].name, endedAt - startedAt, !!ok, false);
4082
+ }
3975
4083
  if (ok) {
3976
4084
  if (prevPos >= state.pos) throw new Error("inline rule didn't increment state.pos");
3977
4085
  break;
@@ -4001,8 +4109,16 @@ var ParserInline = class {
4001
4109
  const state = new StateInline(src, md, env, outTokens);
4002
4110
  this.tokenize(state);
4003
4111
  const rules2 = this.getRules2();
4112
+ const namedRules2 = this.ruler2.getNamedRules("");
4004
4113
  const len = rules2.length;
4005
- for (let i = 0; i < len; i++) rules2[i](state, false);
4114
+ const shouldProfile = !!state.env && (Object.prototype.hasOwnProperty.call(state.env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(state.env, "__mdtsProfileRules"));
4115
+ for (let i = 0; i < len; i++) if (!shouldProfile) rules2[i](state, false);
4116
+ else {
4117
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4118
+ namedRules2[i].fn(state, false);
4119
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4120
+ recordRuleInvocation(state.env, "inline2", namedRules2[i].name, endedAt - startedAt, true, false);
4121
+ }
4006
4122
  }
4007
4123
  parse(str, md, env, outTokens) {
4008
4124
  this.parseSource(str, md, env, outTokens);
@@ -4199,8 +4315,10 @@ function replacements(state) {
4199
4315
  var CoreRuler = class {
4200
4316
  rules = [];
4201
4317
  cache = null;
4318
+ namedCache = null;
4202
4319
  invalidateCache() {
4203
4320
  this.cache = null;
4321
+ this.namedCache = null;
4204
4322
  }
4205
4323
  push(name, fn) {
4206
4324
  const idx = this.rules.findIndex((r) => r.name === name);
@@ -4290,11 +4408,19 @@ var CoreRuler = class {
4290
4408
  }
4291
4409
  compileCache() {
4292
4410
  this.cache = this.rules.filter((r) => r.enabled).map((r) => r.fn);
4411
+ this.namedCache = this.rules.filter((r) => r.enabled).map((r) => ({
4412
+ name: r.name,
4413
+ fn: r.fn
4414
+ }));
4293
4415
  }
4294
4416
  getRules(_chainName = "") {
4295
4417
  if (!this.cache) this.compileCache();
4296
4418
  return this.cache;
4297
4419
  }
4420
+ getNamedRules(_chainName = "") {
4421
+ if (!this.namedCache) this.compileCache();
4422
+ return this.namedCache;
4423
+ }
4298
4424
  };
4299
4425
  const QUOTE_TEST_RE = /['"]/;
4300
4426
  const QUOTE_RE = /['"]/g;
@@ -4449,7 +4575,7 @@ function blockquote(state, startLine, endLine, silent) {
4449
4575
  const oldBSCount = [];
4450
4576
  const oldSCount = [];
4451
4577
  const oldTShift = [];
4452
- const terminatorRules = state.md.block.ruler.getRules("blockquote");
4578
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "blockquote");
4453
4579
  const oldParentType = state.parentType;
4454
4580
  state.parentType = "blockquote";
4455
4581
  let lastLineEmpty = false;
@@ -4839,19 +4965,46 @@ function html_block(state, startLine, endLine, silent) {
4839
4965
  token.content = state.getLines(startLine, nextLine, state.blkIndent, true);
4840
4966
  return true;
4841
4967
  }
4968
+ function hasPipeOnLine(src, start, max) {
4969
+ for (let pos = start; pos < max; pos++) if (src.charCodeAt(pos) === 124) return true;
4970
+ return false;
4971
+ }
4972
+ function canUseParagraphTerminatorFastPath(state) {
4973
+ const ruler = state?.md?.block?.ruler;
4974
+ if (!ruler) return false;
4975
+ return ruler.version === ruler.__mdtsDefaultVersion;
4976
+ }
4977
+ function couldTerminateParagraph(src, start, max) {
4978
+ if (start >= max) return false;
4979
+ const marker = src.charCodeAt(start);
4980
+ switch (marker) {
4981
+ case 35:
4982
+ case 42:
4983
+ case 43:
4984
+ case 45:
4985
+ case 60:
4986
+ case 62:
4987
+ case 95:
4988
+ case 96:
4989
+ case 126: return true;
4990
+ }
4991
+ if (marker >= 48 && marker <= 57) return true;
4992
+ return hasPipeOnLine(src, start, max);
4993
+ }
4842
4994
  const HEADING_TAGS = [
4843
4995
  "",
4844
4996
  "h1",
4845
4997
  "h2"
4846
4998
  ];
4847
4999
  function lheading(state, startLine, endLine) {
4848
- const terminatorRules = state.md.block.ruler.getRules("paragraph");
5000
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "paragraph");
4849
5001
  const src = state.src;
4850
5002
  const bMarks = state.bMarks;
4851
5003
  const tShift = state.tShift;
4852
5004
  const eMarks = state.eMarks;
4853
5005
  const sCount = state.sCount;
4854
5006
  const blkIndent = state.blkIndent;
5007
+ const canUseFastTerminatorHint = canUseParagraphTerminatorFastPath(state);
4855
5008
  if (sCount[startLine] - blkIndent >= 4) return false;
4856
5009
  const oldParentType = state.parentType;
4857
5010
  state.parentType = "paragraph";
@@ -4883,6 +5036,7 @@ function lheading(state, startLine, endLine) {
4883
5036
  }
4884
5037
  }
4885
5038
  if (sCount[nextLine] < 0) continue;
5039
+ if (canUseFastTerminatorHint && !couldTerminateParagraph(src, lineStart, max)) continue;
4886
5040
  let terminate = false;
4887
5041
  for (let i = 0, l = terminatorRules.length; i < l; i++) if (terminatorRules[i](state, nextLine, endLine, true)) {
4888
5042
  terminate = true;
@@ -5054,7 +5208,7 @@ function list(state, startLine, endLine, silent) {
5054
5208
  state.tokens[state.tokens.length - 1].markup = markerMarkup;
5055
5209
  let prevEmptyEnd = false;
5056
5210
  const listTokIdx = state.tokens.length - 1;
5057
- const terminatorRules = state.md.block.ruler.getRules("list");
5211
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "list");
5058
5212
  const oldParentType = state.parentType;
5059
5213
  state.parentType = "list";
5060
5214
  while (nextLine < endLine) {
@@ -5160,7 +5314,7 @@ function isSpace$3(code$1) {
5160
5314
  return code$1 === 9 || code$1 === 32;
5161
5315
  }
5162
5316
  function paragraph(state, startLine, endLine) {
5163
- const terminatorRules = state.md.block.ruler.getRules("paragraph");
5317
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "paragraph");
5164
5318
  const oldParentType = state.parentType;
5165
5319
  const src = state.src;
5166
5320
  const bMarks = state.bMarks;
@@ -5168,34 +5322,35 @@ function paragraph(state, startLine, endLine) {
5168
5322
  const eMarks = state.eMarks;
5169
5323
  const sCount = state.sCount;
5170
5324
  const blkIndent = state.blkIndent;
5325
+ const canUseFastTerminatorHint = canUseParagraphTerminatorFastPath(state);
5171
5326
  let nextLine = startLine + 1;
5172
5327
  state.parentType = "paragraph";
5173
5328
  for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
5174
5329
  if (sCount[nextLine] - blkIndent > 3) continue;
5175
5330
  if (sCount[nextLine] < 0) continue;
5176
5331
  if (oldParentType === "list" && sCount[nextLine] >= blkIndent) {
5177
- const start = bMarks[nextLine] + tShift[nextLine];
5178
- const max = eMarks[nextLine];
5179
- if (start < max) {
5180
- const marker = src.charCodeAt(start);
5332
+ const start$1 = bMarks[nextLine] + tShift[nextLine];
5333
+ const max$1 = eMarks[nextLine];
5334
+ if (start$1 < max$1) {
5335
+ const marker = src.charCodeAt(start$1);
5181
5336
  if (marker === 42 || marker === 45 || marker === 43) {
5182
- if (start + 1 >= max || isSpace$3(src.charCodeAt(start + 1))) break;
5183
- } else if (marker >= 48 && marker <= 57 && start + 1 < max) {
5184
- let pos = start + 1;
5337
+ if (start$1 + 1 >= max$1 || isSpace$3(src.charCodeAt(start$1 + 1))) break;
5338
+ } else if (marker >= 48 && marker <= 57 && start$1 + 1 < max$1) {
5339
+ let pos = start$1 + 1;
5185
5340
  for (;;) {
5186
- if (pos >= max) {
5341
+ if (pos >= max$1) {
5187
5342
  pos = -1;
5188
5343
  break;
5189
5344
  }
5190
5345
  const ch = src.charCodeAt(pos++);
5191
5346
  if (ch >= 48 && ch <= 57) {
5192
- if (pos - start >= 10) {
5347
+ if (pos - start$1 >= 10) {
5193
5348
  pos = -1;
5194
5349
  break;
5195
5350
  }
5196
5351
  continue;
5197
5352
  }
5198
- if ((ch === 41 || ch === 46) && (pos >= max || isSpace$3(src.charCodeAt(pos)))) break;
5353
+ if ((ch === 41 || ch === 46) && (pos >= max$1 || isSpace$3(src.charCodeAt(pos)))) break;
5199
5354
  pos = -1;
5200
5355
  break;
5201
5356
  }
@@ -5203,6 +5358,9 @@ function paragraph(state, startLine, endLine) {
5203
5358
  }
5204
5359
  }
5205
5360
  }
5361
+ const start = bMarks[nextLine] + tShift[nextLine];
5362
+ const max = eMarks[nextLine];
5363
+ if (canUseFastTerminatorHint && !couldTerminateParagraph(src, start, max)) continue;
5206
5364
  let terminate = false;
5207
5365
  for (let i = 0, l = terminatorRules.length; i < l; i++) if (terminatorRules[i](state, nextLine, endLine, true)) {
5208
5366
  terminate = true;
@@ -5233,7 +5391,7 @@ function reference(state, startLine, _endLine, silent) {
5233
5391
  let pos = state.bMarks[startLine] + state.tShift[startLine];
5234
5392
  let max = state.eMarks[startLine];
5235
5393
  let nextLine = startLine + 1;
5236
- const terminatorRules = state.md.block.ruler.getRules("reference");
5394
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "reference");
5237
5395
  if (state.sCount[startLine] - state.blkIndent >= 4) return false;
5238
5396
  if (state.src.charCodeAt(pos) !== 91) return false;
5239
5397
  function getNextLine(nextLine$1) {
@@ -5444,7 +5602,7 @@ function table(state, startLine, endLine, silent) {
5444
5602
  if (silent) return true;
5445
5603
  const oldParentType = state.parentType;
5446
5604
  state.parentType = "table";
5447
- const terminatorRules = state.md.block.ruler.getRules("blockquote");
5605
+ const terminatorRules = state.md.block.ruler.getRulesForState(state, "blockquote");
5448
5606
  const token_to = state.push("table_open", "table", 1);
5449
5607
  const tableLines = [startLine, 0];
5450
5608
  token_to.map = tableLines;
@@ -5506,15 +5664,14 @@ function table(state, startLine, endLine, silent) {
5506
5664
  state.line = nextLine;
5507
5665
  return true;
5508
5666
  }
5509
- /**
5510
- * Block-level rule management with Ruler pattern
5511
- */
5512
5667
  var BlockRuler = class {
5513
5668
  _rules = [];
5514
5669
  cache = null;
5670
+ namedCache = null;
5515
5671
  version = 0;
5516
5672
  invalidateCache() {
5517
5673
  this.cache = null;
5674
+ this.namedCache = null;
5518
5675
  this.version++;
5519
5676
  }
5520
5677
  push(name, fn, options) {
@@ -5557,6 +5714,24 @@ var BlockRuler = class {
5557
5714
  if (!this.cache) this.compileCache();
5558
5715
  return this.cache[chain] ?? [];
5559
5716
  }
5717
+ getNamedRules(chainName) {
5718
+ const chain = chainName || "";
5719
+ if (!this.namedCache) this.compileCache();
5720
+ return this.namedCache[chain] ?? [];
5721
+ }
5722
+ getRulesForState(state, chainName) {
5723
+ const env = state?.env;
5724
+ if (!(!!env && (Object.prototype.hasOwnProperty.call(env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(env, "__mdtsProfileRules")))) return this.getRules(chainName);
5725
+ return this.getNamedRules(chainName).map(({ name, fn }) => {
5726
+ return (currentState, startLine, endLine, silent) => {
5727
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5728
+ const ok = fn(currentState, startLine, endLine, silent);
5729
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5730
+ recordRuleInvocation(currentState?.env, "block", name, endedAt - startedAt, ok, !!silent);
5731
+ return ok;
5732
+ };
5733
+ });
5734
+ }
5560
5735
  at(name, fn, options) {
5561
5736
  const index = this._rules.findIndex((r) => r.name === name);
5562
5737
  if (index === -1) throw new Error(`Parser rule not found: ${name}`);
@@ -5617,16 +5792,24 @@ var BlockRuler = class {
5617
5792
  for (const alt of rule.alt) chains.add(alt);
5618
5793
  }
5619
5794
  const cache = Object.create(null);
5795
+ const namedCache = Object.create(null);
5620
5796
  for (const chain of chains) {
5621
5797
  const bucket = [];
5798
+ const namedBucket = [];
5622
5799
  for (const rule of this._rules) {
5623
5800
  if (!rule.enabled) continue;
5624
5801
  if (chain !== "" && !rule.alt.includes(chain)) continue;
5625
5802
  bucket.push(rule.fn);
5803
+ namedBucket.push({
5804
+ name: rule.name,
5805
+ fn: rule.fn
5806
+ });
5626
5807
  }
5627
5808
  cache[chain] = bucket;
5809
+ namedCache[chain] = namedBucket;
5628
5810
  }
5629
5811
  this.cache = cache;
5812
+ this.namedCache = namedCache;
5630
5813
  }
5631
5814
  };
5632
5815
  function isSpace(code$1) {
@@ -5871,12 +6054,14 @@ var ParserBlock = class {
5871
6054
  constructor() {
5872
6055
  this.ruler = new BlockRuler();
5873
6056
  for (let i = 0; i < _rules.length; i++) this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() });
6057
+ this.ruler.__mdtsDefaultVersion = this.ruler.version;
5874
6058
  }
5875
6059
  /**
5876
6060
  * Generate tokens for input range
5877
6061
  */
5878
6062
  tokenize(state, startLine, endLine) {
5879
6063
  const rules = this.getRules();
6064
+ const namedRules = this.ruler.getNamedRules("");
5880
6065
  const len = rules.length;
5881
6066
  const maxNesting = state.md.options.maxNesting;
5882
6067
  const bMarks = state.bMarks;
@@ -5885,6 +6070,7 @@ var ParserBlock = class {
5885
6070
  const sCount = state.sCount;
5886
6071
  let line = startLine;
5887
6072
  let hasEmptyLines = false;
6073
+ const shouldProfile = !!state.env && (Object.prototype.hasOwnProperty.call(state.env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(state.env, "__mdtsProfileRules"));
5888
6074
  while (line < endLine) {
5889
6075
  while (line < endLine && bMarks[line] + tShift[line] >= eMarks[line]) line++;
5890
6076
  state.line = line;
@@ -5897,7 +6083,13 @@ var ParserBlock = class {
5897
6083
  const prevLine = state.line;
5898
6084
  let ok = false;
5899
6085
  for (let i = 0; i < len; i++) {
5900
- ok = rules[i](state, line, endLine, false);
6086
+ if (!shouldProfile) ok = rules[i](state, line, endLine, false);
6087
+ else {
6088
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
6089
+ ok = namedRules[i].fn(state, line, endLine, false);
6090
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
6091
+ recordRuleInvocation(state.env, "block", namedRules[i].name, endedAt - startedAt, ok, false);
6092
+ }
5901
6093
  if (ok) {
5902
6094
  if (prevLine >= state.line) throw new Error("block rule didn't increment state.line");
5903
6095
  break;
@@ -6019,7 +6211,19 @@ var ParserCore = class {
6019
6211
  }
6020
6212
  process(state) {
6021
6213
  const rules = this.cachedCoreRules ?? (this.cachedCoreRules = this.ruler.getRules(""));
6022
- for (let i = 0; i < rules.length; i++) rules[i](state);
6214
+ const namedRules = this.ruler.getNamedRules("");
6215
+ const shouldProfile = !!state.env && (Object.prototype.hasOwnProperty.call(state.env, "__mdtsRuleProfile") || Object.prototype.hasOwnProperty.call(state.env, "__mdtsProfileRules"));
6216
+ for (let i = 0; i < rules.length; i++) {
6217
+ if (!shouldProfile) {
6218
+ rules[i](state);
6219
+ continue;
6220
+ }
6221
+ const startedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
6222
+ namedRules[i].fn(state);
6223
+ const endedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
6224
+ recordRuleInvocation(state.env, "core", namedRules[i].name, endedAt - startedAt, true, false);
6225
+ }
6226
+ finalizeRuleProfile(state.env);
6023
6227
  }
6024
6228
  parseSource(src, env = {}, md) {
6025
6229
  if (typeof src !== "string" && hasNormalizationChars(src)) return this.parse(sourceToString(src), env, md);
@@ -7065,8 +7269,8 @@ const FULL_DISCRETE_RECOMMENDATIONS = [
7065
7269
  {
7066
7270
  max: 5e5,
7067
7271
  strategy: "discrete",
7068
- maxChunkChars: 32e3,
7069
- maxChunkLines: 350,
7272
+ maxChunkChars: 64e3,
7273
+ maxChunkLines: 700,
7070
7274
  maxChunks: 16,
7071
7275
  notes: "<=500k"
7072
7276
  },
@@ -7091,26 +7295,34 @@ const STREAM_DISCRETE_RECOMMENDATIONS = [
7091
7295
  {
7092
7296
  max: 2e4,
7093
7297
  strategy: "discrete",
7094
- maxChunkChars: 16e3,
7298
+ maxChunkChars: 2e4,
7095
7299
  maxChunkLines: 200,
7096
- maxChunks: 12,
7300
+ maxChunks: 24,
7097
7301
  notes: "<=20k"
7098
7302
  },
7099
7303
  {
7100
- max: 5e4,
7304
+ max: 1e5,
7101
7305
  strategy: "discrete",
7102
- maxChunkChars: 16e3,
7103
- maxChunkLines: 250,
7104
- maxChunks: 12,
7105
- notes: "<=50k"
7306
+ maxChunkChars: 2e4,
7307
+ maxChunkLines: 200,
7308
+ maxChunks: 24,
7309
+ notes: "<=100k"
7106
7310
  },
7107
7311
  {
7108
7312
  max: 5e5,
7109
7313
  strategy: "discrete",
7110
- maxChunkChars: 32e3,
7111
- maxChunkLines: 350,
7112
- maxChunks: 16,
7314
+ maxChunkChars: 64e3,
7315
+ maxChunkLines: 700,
7316
+ maxChunks: 32,
7113
7317
  notes: "<=500k"
7318
+ },
7319
+ {
7320
+ max: 5e6,
7321
+ strategy: "discrete",
7322
+ maxChunkChars: 64e3,
7323
+ maxChunkLines: 700,
7324
+ maxChunks: 32,
7325
+ notes: "<=5M"
7114
7326
  }
7115
7327
  ];
7116
7328
  function toRecommendation(fenceAware, discrete) {
@@ -7166,10 +7378,10 @@ function recommendStreamChunkStrategy(sizeChars, sizeLines = Math.max(0, sizeCha
7166
7378
  break;
7167
7379
  }
7168
7380
  }
7169
- if (sizeChars > 5e5) return {
7381
+ if (sizeChars > 5e6) return {
7170
7382
  strategy: "plain",
7171
7383
  fenceAware,
7172
- notes: ">500k plain"
7384
+ notes: ">5M plain"
7173
7385
  };
7174
7386
  if (adaptive) return {
7175
7387
  strategy: "adaptive",
@@ -7584,6 +7796,7 @@ function makeEmptyStats() {
7584
7796
  total: 0,
7585
7797
  cacheHits: 0,
7586
7798
  appendHits: 0,
7799
+ unboundedAppendHits: 0,
7587
7800
  tailHits: 0,
7588
7801
  fullParses: 0,
7589
7802
  resets: 0,
@@ -7596,12 +7809,16 @@ var StreamParser = class {
7596
7809
  cache = null;
7597
7810
  stats = makeEmptyStats();
7598
7811
  MIN_SIZE_FOR_OPTIMIZATION = 1e3;
7599
- DEFAULT_SKIP_CACHE_CHARS = 6e5;
7600
- DEFAULT_SKIP_CACHE_LINES = 1e4;
7812
+ DEFAULT_SKIP_CACHE_CHARS = 1e6;
7813
+ DEFAULT_SKIP_CACHE_LINES = 1e5;
7814
+ IMPLICIT_STREAM_CHUNK_MIN_CHARS = 16e4;
7601
7815
  MIN_LIST_LINES_FOR_MERGE = 80;
7602
7816
  MIN_LIST_CHARS_FOR_MERGE = 800;
7603
7817
  MIN_TABLE_LINES_FOR_MERGE = 48;
7604
7818
  MIN_TABLE_CHARS_FOR_MERGE = 1200;
7819
+ MIN_UNBOUNDED_APPEND_TOTAL_CHARS = 5e5;
7820
+ MIN_UNBOUNDED_APPEND_CHARS = 64e3;
7821
+ MIN_UNBOUNDED_APPEND_LINES = 700;
7605
7822
  constructor(core) {
7606
7823
  this.core = core;
7607
7824
  }
@@ -7620,7 +7837,10 @@ var StreamParser = class {
7620
7837
  const cached = this.cache;
7621
7838
  if (!cached || envProvided && envProvided !== cached.env) {
7622
7839
  const workingEnv = envProvided ?? {};
7623
- const chunkedEnabled$1 = !!md.options?.streamChunkedFallback;
7840
+ const explicitChunkFallbackSetting$1 = !!md.__explicitStreamChunkFallbackSetting;
7841
+ const wantsChunking$1 = !!md.options?.streamChunkedFallback;
7842
+ const allowImplicitChunk$1 = !explicitChunkFallbackSetting$1;
7843
+ const chunkedEnabled$1 = wantsChunking$1 || allowImplicitChunk$1;
7624
7844
  const chunkAdaptive$1 = md.options?.streamChunkAdaptive !== false;
7625
7845
  const targetChunks$1 = md.options?.streamChunkTargetChunks ?? 8;
7626
7846
  const chunkSizeCharsCfg$1 = md.options?.streamChunkSizeChars;
@@ -7642,6 +7862,12 @@ var StreamParser = class {
7642
7862
  this.stats.total += 1;
7643
7863
  this.stats.fullParses += 1;
7644
7864
  this.stats.lastMode = "full";
7865
+ setStrategyDiagnostics(workingEnv, {
7866
+ area: "stream",
7867
+ path: "stream-full",
7868
+ reason: "skip-cache-large-one-shot",
7869
+ unbounded: !!workingEnv.__mdtsUnboundedInfo
7870
+ });
7645
7871
  return parsed$2.tokens;
7646
7872
  } else if (chunkedEnabled$1) {
7647
7873
  const clamp$1 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
@@ -7651,7 +7877,8 @@ var StreamParser = class {
7651
7877
  const useLines = recommendation?.maxChunkLines ?? (chunkAdaptive$1 ? clamp$1(Math.ceil(srcLineCount / targetChunks$1), 150, 700) : chunkSizeLinesCfg$1 ?? 200);
7652
7878
  const useMaxChunks = recommendation?.maxChunks ?? (chunkAdaptive$1 ? clamp$1(Math.ceil(src.length / 64e3), targetChunks$1, 32) : chunkMaxChunksCfg$1);
7653
7879
  const hasTrailingNewline = src.length > 0 && src.charCodeAt(src.length - 1) === 10;
7654
- if (recommendation?.strategy !== "plain" && (src.length >= useChars * 2 || srcLineCount >= useLines * 2) && hasTrailingNewline) {
7880
+ const shouldAutoChunk = allowImplicitChunk$1 && src.length >= this.IMPLICIT_STREAM_CHUNK_MIN_CHARS && recommendation?.strategy !== "plain";
7881
+ if ((wantsChunking$1 || shouldAutoChunk) && (src.length >= useChars * 2 || srcLineCount >= useLines * 2) && hasTrailingNewline) {
7655
7882
  const tokens = chunkedParse(md, src, workingEnv, {
7656
7883
  maxChunkChars: useChars,
7657
7884
  maxChunkLines: useLines,
@@ -7669,6 +7896,12 @@ var StreamParser = class {
7669
7896
  this.stats.total += 1;
7670
7897
  this.stats.chunkedParses = (this.stats.chunkedParses || 0) + 1;
7671
7898
  this.stats.lastMode = "chunked";
7899
+ setStrategyDiagnostics(workingEnv, {
7900
+ area: "stream",
7901
+ path: "stream-chunked",
7902
+ chunked: true,
7903
+ reason: wantsChunking$1 ? "explicit-initial-large-doc" : "default-initial-large-doc"
7904
+ });
7672
7905
  return tokens;
7673
7906
  }
7674
7907
  }
@@ -7685,12 +7918,23 @@ var StreamParser = class {
7685
7918
  this.stats.total += 1;
7686
7919
  this.stats.fullParses += 1;
7687
7920
  this.stats.lastMode = "full";
7921
+ setStrategyDiagnostics(workingEnv, {
7922
+ area: "stream",
7923
+ path: "stream-full",
7924
+ reason: "initial-parse",
7925
+ unbounded: !!workingEnv.__mdtsUnboundedInfo
7926
+ });
7688
7927
  return parsed$1.tokens;
7689
7928
  }
7690
7929
  if (src === cached.src) {
7691
7930
  this.stats.total += 1;
7692
7931
  this.stats.cacheHits += 1;
7693
7932
  this.stats.lastMode = "cache";
7933
+ setStrategyDiagnostics(cached.env, {
7934
+ area: "stream",
7935
+ path: "stream-cache",
7936
+ reason: "same-source"
7937
+ });
7694
7938
  return cached.tokens;
7695
7939
  }
7696
7940
  const threshold = md.options?.streamOptimizationMinSize ?? this.MIN_SIZE_FOR_OPTIMIZATION;
@@ -7710,6 +7954,12 @@ var StreamParser = class {
7710
7954
  this.stats.total += 1;
7711
7955
  this.stats.fullParses += 1;
7712
7956
  this.stats.lastMode = "full";
7957
+ setStrategyDiagnostics(fallbackEnv$1, {
7958
+ area: "stream",
7959
+ path: "stream-full",
7960
+ reason: "small-non-append",
7961
+ unbounded: !!fallbackEnv$1.__mdtsUnboundedInfo
7962
+ });
7713
7963
  return nextTokens$1;
7714
7964
  }
7715
7965
  const appended = this.getAppendedSegment(cached.src, src);
@@ -7819,6 +8069,7 @@ var StreamParser = class {
7819
8069
  return appendedLineCount;
7820
8070
  };
7821
8071
  const canDirectParseAppend = this.canDirectlyParseAppend(cached);
8072
+ const useUnboundedAppend = canDirectParseAppend && this.shouldUseUnboundedAppend(src, cached, appended);
7822
8073
  let shouldAttemptContext = false;
7823
8074
  if (!canDirectParseAppend) switch (ctxStrategy) {
7824
8075
  case "lines":
@@ -7854,10 +8105,15 @@ var StreamParser = class {
7854
8105
  }
7855
8106
  } else appendedState = null;
7856
8107
  if (!appendedState) {
7857
- const simpleState = this.core.parse(appended, cached.env, md);
7858
8108
  const lineOffset = cachedLineCount;
7859
- if (lineOffset > 0) this.shiftTokenLines(simpleState.tokens, lineOffset);
7860
- appendedState = simpleState;
8109
+ if (useUnboundedAppend) {
8110
+ appendedState = { tokens: parseStringUnbounded(md, appended, cached.env, { mode: "stream" }) };
8111
+ if (lineOffset > 0) this.shiftTokenLines(appendedState.tokens, lineOffset);
8112
+ } else {
8113
+ const simpleState = this.core.parse(appended, cached.env, md);
8114
+ if (lineOffset > 0) this.shiftTokenLines(simpleState.tokens, lineOffset);
8115
+ appendedState = simpleState;
8116
+ }
7861
8117
  }
7862
8118
  if (cached.tokens.length > 0 && appendedState.tokens.length > 0) {
7863
8119
  const lastCached = cached.tokens[cached.tokens.length - 1];
@@ -7866,7 +8122,7 @@ var StreamParser = class {
7866
8122
  if (lastCached.type === "inline" && firstApp.type === "inline") {
7867
8123
  if (firstApp.children && firstApp.children.length > 0) {
7868
8124
  if (!lastCached.children) lastCached.children = [];
7869
- lastCached.children.push(...firstApp.children);
8125
+ this.appendTokens(lastCached.children, firstApp.children);
7870
8126
  }
7871
8127
  lastCached.content = (lastCached.content || "") + (firstApp.content || "");
7872
8128
  appendedState.tokens.shift();
@@ -7901,7 +8157,7 @@ var StreamParser = class {
7901
8157
  }
7902
8158
  }
7903
8159
  if (dup > 0) a.splice(0, dup);
7904
- if (a.length > 0) cached.tokens.push(...a);
8160
+ if (a.length > 0) this.appendTokens(cached.tokens, a);
7905
8161
  }
7906
8162
  cached.src = src;
7907
8163
  cached.lineCount = cachedLineCount + (appendedLineCount ?? countLines(appended));
@@ -7918,7 +8174,14 @@ var StreamParser = class {
7918
8174
  } else cached.lastSegment = void 0;
7919
8175
  this.stats.total += 1;
7920
8176
  this.stats.appendHits += 1;
8177
+ if (useUnboundedAppend) this.stats.unboundedAppendHits = (this.stats.unboundedAppendHits || 0) + 1;
7921
8178
  this.stats.lastMode = "append";
8179
+ setStrategyDiagnostics(cached.env, {
8180
+ area: "stream",
8181
+ path: useUnboundedAppend ? "stream-unbounded-append" : "stream-append",
8182
+ reason: useUnboundedAppend ? "large-delta" : "safe-append",
8183
+ unbounded: useUnboundedAppend
8184
+ });
7922
8185
  return cached.tokens;
7923
8186
  }
7924
8187
  const fallbackEnv = envProvided ?? cached.env;
@@ -7927,9 +8190,17 @@ var StreamParser = class {
7927
8190
  this.stats.total += 1;
7928
8191
  this.stats.tailHits += 1;
7929
8192
  this.stats.lastMode = "tail";
8193
+ setStrategyDiagnostics(fallbackEnv, {
8194
+ area: "stream",
8195
+ path: "stream-tail",
8196
+ reason: "tail-reparse"
8197
+ });
7930
8198
  return tailReparsed;
7931
8199
  }
7932
- const chunkedEnabled = !!md.options?.streamChunkedFallback;
8200
+ const explicitChunkFallbackSetting = !!md.__explicitStreamChunkFallbackSetting;
8201
+ const wantsChunking = !!md.options?.streamChunkedFallback;
8202
+ const allowImplicitChunk = !explicitChunkFallbackSetting && !appended;
8203
+ const chunkedEnabled = wantsChunking || allowImplicitChunk;
7933
8204
  const chunkAdaptive = md.options?.streamChunkAdaptive !== false;
7934
8205
  const targetChunks = md.options?.streamChunkTargetChunks ?? 8;
7935
8206
  const chunkSizeCharsCfg = md.options?.streamChunkSizeChars;
@@ -7947,7 +8218,8 @@ var StreamParser = class {
7947
8218
  const useLines = recommendation?.maxChunkLines ?? (chunkAdaptive ? clamp$1(Math.ceil(srcLineCount2 / targetChunks), 150, 700) : chunkSizeLinesCfg ?? 200);
7948
8219
  const useMaxChunks = recommendation?.maxChunks ?? (chunkAdaptive ? clamp$1(Math.ceil(src.length / 64e3), targetChunks, 32) : chunkMaxChunksCfg);
7949
8220
  const hasTrailingNewline2 = src.length > 0 && src.charCodeAt(src.length - 1) === 10;
7950
- if (recommendation?.strategy !== "plain" && (src.length >= useChars * 2 || srcLineCount2 >= useLines * 2) && hasTrailingNewline2) {
8221
+ const shouldAutoChunk = allowImplicitChunk && src.length >= this.IMPLICIT_STREAM_CHUNK_MIN_CHARS && recommendation?.strategy !== "plain";
8222
+ if ((wantsChunking || shouldAutoChunk) && (src.length >= useChars * 2 || srcLineCount2 >= useLines * 2) && hasTrailingNewline2) {
7951
8223
  const tokens = chunkedParse(md, src, fallbackEnv, {
7952
8224
  maxChunkChars: useChars,
7953
8225
  maxChunkLines: useLines,
@@ -7965,6 +8237,12 @@ var StreamParser = class {
7965
8237
  this.stats.total += 1;
7966
8238
  this.stats.chunkedParses = (this.stats.chunkedParses || 0) + 1;
7967
8239
  this.stats.lastMode = "chunked";
8240
+ setStrategyDiagnostics(fallbackEnv, {
8241
+ area: "stream",
8242
+ path: "stream-chunked",
8243
+ chunked: true,
8244
+ reason: wantsChunking ? "explicit-fallback-large-doc" : "default-fallback-large-doc"
8245
+ });
7968
8246
  return tokens;
7969
8247
  }
7970
8248
  }
@@ -7982,21 +8260,43 @@ var StreamParser = class {
7982
8260
  this.stats.total += 1;
7983
8261
  this.stats.fullParses += 1;
7984
8262
  this.stats.lastMode = "full";
8263
+ setStrategyDiagnostics(fallbackEnv, {
8264
+ area: "stream",
8265
+ path: "stream-full",
8266
+ reason: "fallback-full",
8267
+ unbounded: !!fallbackEnv.__mdtsUnboundedInfo
8268
+ });
7985
8269
  return nextTokens;
7986
8270
  }
7987
8271
  parseFullDocument(src, env, md, knownLineCount, needLineCount = true) {
7988
8272
  const autoUnboundedDecision = getAutoUnboundedDecision(md, src.length, knownLineCount);
7989
- if (autoUnboundedDecision === "yes") return {
7990
- tokens: parseStringUnbounded(md, src, env),
7991
- lineCount: knownLineCount ?? (needLineCount ? countLines(src) : 0)
7992
- };
8273
+ if (autoUnboundedDecision === "yes") {
8274
+ setStrategyDiagnostics(env, {
8275
+ area: "stream",
8276
+ path: "stream-full",
8277
+ reason: "auto-unbounded-char-threshold",
8278
+ unbounded: true
8279
+ });
8280
+ return {
8281
+ tokens: parseStringUnbounded(md, src, env),
8282
+ lineCount: knownLineCount ?? (needLineCount ? countLines(src) : 0)
8283
+ };
8284
+ }
7993
8285
  let lineCount = knownLineCount;
7994
8286
  if (autoUnboundedDecision === "need-lines") {
7995
8287
  lineCount = countLines(src);
7996
- if (shouldAutoUseUnbounded(md, src.length, lineCount)) return {
7997
- tokens: parseStringUnbounded(md, src, env),
7998
- lineCount
7999
- };
8288
+ if (shouldAutoUseUnbounded(md, src.length, lineCount)) {
8289
+ setStrategyDiagnostics(env, {
8290
+ area: "stream",
8291
+ path: "stream-full",
8292
+ reason: "auto-unbounded-line-threshold",
8293
+ unbounded: true
8294
+ });
8295
+ return {
8296
+ tokens: parseStringUnbounded(md, src, env),
8297
+ lineCount
8298
+ };
8299
+ }
8000
8300
  }
8001
8301
  if (lineCount === void 0) lineCount = needLineCount ? countLines(src) : 0;
8002
8302
  return {
@@ -8004,6 +8304,12 @@ var StreamParser = class {
8004
8304
  lineCount
8005
8305
  };
8006
8306
  }
8307
+ shouldUseUnboundedAppend(src, _cached, appended) {
8308
+ if (!appended) return false;
8309
+ if (src.length < this.MIN_UNBOUNDED_APPEND_TOTAL_CHARS && appended.length < this.MIN_UNBOUNDED_APPEND_CHARS) return false;
8310
+ if (appended.length >= this.MIN_UNBOUNDED_APPEND_CHARS) return true;
8311
+ return countLines(appended) >= this.MIN_UNBOUNDED_APPEND_LINES;
8312
+ }
8007
8313
  getAppendedSegment(prev, next) {
8008
8314
  if (!next.startsWith(prev)) return null;
8009
8315
  if (!prev.endsWith("\n")) return null;
@@ -8052,7 +8358,7 @@ var StreamParser = class {
8052
8358
  cached.src = src;
8053
8359
  cached.env = env;
8054
8360
  cached.tokens.length = lastSegment.tokenStart;
8055
- cached.tokens.push(...tailState.tokens);
8361
+ this.appendTokens(cached.tokens, tailState.tokens);
8056
8362
  cached.lineCount = countLines(src);
8057
8363
  if (localLastSegment) cached.lastSegment = {
8058
8364
  tokenStart: lastSegment.tokenStart + localLastSegment.tokenStart,
@@ -8118,6 +8424,9 @@ var StreamParser = class {
8118
8424
  getStats() {
8119
8425
  return { ...this.stats };
8120
8426
  }
8427
+ appendTokens(target, source) {
8428
+ for (let i = 0; i < source.length; i++) target.push(source[i]);
8429
+ }
8121
8430
  updateCacheLineCount(cache, lineCount) {
8122
8431
  cache.lineCount = lineCount ?? countLines(cache.src);
8123
8432
  cache.lastSegment = void 0;
@@ -8431,6 +8740,9 @@ function hasExplicitChunkOverride(presetOptions, userOptions, keys) {
8431
8740
  }
8432
8741
  return false;
8433
8742
  }
8743
+ function hasExplicitOption(presetOptions, userOptions, key) {
8744
+ return !!userOptions && Object.prototype.hasOwnProperty.call(userOptions, key) || !!presetOptions && Object.prototype.hasOwnProperty.call(presetOptions, key);
8745
+ }
8434
8746
  function markdownIt(presetName, options) {
8435
8747
  let opts = {
8436
8748
  html: false,
@@ -8451,8 +8763,8 @@ function markdownIt(presetName, options) {
8451
8763
  streamChunkAdaptive: true,
8452
8764
  streamChunkTargetChunks: 8,
8453
8765
  streamChunkMaxChunks: void 0,
8454
- streamSkipCacheAboveChars: 6e5,
8455
- streamSkipCacheAboveLines: 1e4,
8766
+ streamSkipCacheAboveChars: 1e6,
8767
+ streamSkipCacheAboveLines: 1e5,
8456
8768
  fullChunkedFallback: false,
8457
8769
  fullChunkThresholdChars: 2e4,
8458
8770
  fullChunkThresholdLines: 400,
@@ -8511,6 +8823,8 @@ function markdownIt(presetName, options) {
8511
8823
  "streamChunkSizeLines",
8512
8824
  "streamChunkMaxChunks"
8513
8825
  ]);
8826
+ let explicitFullChunkFallbackSetting = hasExplicitOption(preset?.options, userOptions, "fullChunkedFallback");
8827
+ let explicitStreamChunkFallbackSetting = hasExplicitOption(preset?.options, userOptions, "streamChunkedFallback");
8514
8828
  const core = new ParserCore();
8515
8829
  let renderer = null;
8516
8830
  const getRenderer = () => {
@@ -8552,6 +8866,8 @@ function markdownIt(presetName, options) {
8552
8866
  options: opts,
8553
8867
  __explicitFullChunkConfig: explicitFullChunkConfig,
8554
8868
  __explicitStreamChunkConfig: explicitStreamChunkConfig,
8869
+ __explicitFullChunkFallbackSetting: explicitFullChunkFallbackSetting,
8870
+ __explicitStreamChunkFallbackSetting: explicitStreamChunkFallbackSetting,
8555
8871
  set(newOpts) {
8556
8872
  this.options = {
8557
8873
  ...this.options,
@@ -8565,6 +8881,14 @@ function markdownIt(presetName, options) {
8565
8881
  explicitStreamChunkConfig = true;
8566
8882
  this.__explicitStreamChunkConfig = true;
8567
8883
  }
8884
+ if (Object.prototype.hasOwnProperty.call(newOpts, "fullChunkedFallback")) {
8885
+ explicitFullChunkFallbackSetting = true;
8886
+ this.__explicitFullChunkFallbackSetting = true;
8887
+ }
8888
+ if (Object.prototype.hasOwnProperty.call(newOpts, "streamChunkedFallback")) {
8889
+ explicitStreamChunkFallbackSetting = true;
8890
+ this.__explicitStreamChunkFallbackSetting = true;
8891
+ }
8568
8892
  if (renderer) renderer.set(newOpts);
8569
8893
  if (typeof newOpts.stream === "boolean") {
8570
8894
  this.stream.enabled = newOpts.stream;
@@ -8657,20 +8981,38 @@ function markdownIt(presetName, options) {
8657
8981
  helpers: { ...helpers_exports },
8658
8982
  parse(src, env = {}) {
8659
8983
  if (typeof src !== "string") throw new TypeError("Input data should be a String");
8984
+ let countedLines;
8660
8985
  if (!this.stream.enabled && !this.options.fullChunkedFallback) {
8661
8986
  const autoUnboundedDecision = getAutoUnboundedDecision(this, src.length);
8662
- if (autoUnboundedDecision === "yes") return parseStringUnbounded(this, src, env);
8663
- if (autoUnboundedDecision === "need-lines" && shouldAutoUseUnbounded(this, src.length, countLines(src))) return parseStringUnbounded(this, src, env);
8664
- return core.parse(src, env, this).tokens;
8987
+ if (autoUnboundedDecision === "yes") {
8988
+ setStrategyDiagnostics(env, {
8989
+ area: "parse",
8990
+ path: "auto-unbounded",
8991
+ unbounded: true,
8992
+ reason: "char-threshold"
8993
+ });
8994
+ return parseStringUnbounded(this, src, env);
8995
+ }
8996
+ if (autoUnboundedDecision === "need-lines") countedLines = countLines(src);
8665
8997
  }
8666
8998
  if (!this.stream.enabled) {
8667
8999
  const chars = src.length;
8668
- if (this.options.fullChunkedFallback) {
8669
- const lines = countLines(src);
8670
- const autoRecommendation = this.options.autoTuneChunks !== false && !explicitFullChunkConfig ? recommendFullChunkStrategy(chars, lines, this.options) : null;
8671
- if (chars >= (this.options.fullChunkThresholdChars ?? 2e4) || lines >= (this.options.fullChunkThresholdLines ?? 400)) {
8672
- if (autoRecommendation) {
8673
- if (autoRecommendation.strategy === "plain") return core.parse(src, env, this).tokens;
9000
+ const lines = countedLines ?? countLines(src);
9001
+ const auto = this.options.autoTuneChunks !== false;
9002
+ const userForcedChunk = explicitFullChunkConfig;
9003
+ const allowImplicitChunk = !explicitFullChunkFallbackSetting;
9004
+ const wantsChunking = !!this.options.fullChunkedFallback;
9005
+ const shouldAutoChunk = allowImplicitChunk && chars >= 2e5;
9006
+ const autoRecommendation = auto && !userForcedChunk ? recommendFullChunkStrategy(chars, lines, this.options) : null;
9007
+ if (wantsChunking || shouldAutoChunk) {
9008
+ if (wantsChunking ? chars >= (this.options.fullChunkThresholdChars ?? 2e4) || lines >= (this.options.fullChunkThresholdLines ?? 400) : shouldAutoChunk) {
9009
+ if (autoRecommendation && autoRecommendation.strategy !== "plain") {
9010
+ setStrategyDiagnostics(env, {
9011
+ area: "parse",
9012
+ path: "full-chunk",
9013
+ chunked: true,
9014
+ reason: wantsChunking ? "explicit-full-chunk" : "default-large-string"
9015
+ });
8674
9016
  return chunkedParse(this, src, env, {
8675
9017
  maxChunkChars: autoRecommendation.maxChunkChars,
8676
9018
  maxChunkLines: autoRecommendation.maxChunkLines,
@@ -8678,23 +9020,45 @@ function markdownIt(presetName, options) {
8678
9020
  maxChunks: autoRecommendation.maxChunks
8679
9021
  });
8680
9022
  }
8681
- const clamp$1 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
8682
- const adaptive = this.options.fullChunkAdaptive !== false;
8683
- const target = this.options.fullChunkTargetChunks ?? 8;
8684
- const dynMaxChunkChars = clamp$1(Math.ceil(chars / target), 8e3, 64e3);
8685
- const dynMaxChunkLines = clamp$1(Math.ceil(lines / target), 150, 700);
8686
- const maxChunkChars = adaptive ? dynMaxChunkChars : this.options.fullChunkSizeChars ?? 1e4;
8687
- const maxChunkLines = adaptive ? dynMaxChunkLines : this.options.fullChunkSizeLines ?? 200;
8688
- const maxChunks = adaptive ? clamp$1(Math.ceil(chars / 64e3), target, 32) : this.options.fullChunkMaxChunks;
8689
- return chunkedParse(this, src, env, {
8690
- maxChunkChars,
8691
- maxChunkLines,
8692
- fenceAware: this.options.fullChunkFenceAware ?? true,
8693
- maxChunks
8694
- });
9023
+ if (wantsChunking) {
9024
+ const clamp$1 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
9025
+ const adaptive = this.options.fullChunkAdaptive !== false;
9026
+ const target = this.options.fullChunkTargetChunks ?? 8;
9027
+ const dynMaxChunkChars = clamp$1(Math.ceil(chars / target), 8e3, 64e3);
9028
+ const dynMaxChunkLines = clamp$1(Math.ceil(lines / target), 150, 700);
9029
+ const maxChunkChars = adaptive ? dynMaxChunkChars : this.options.fullChunkSizeChars ?? 1e4;
9030
+ const maxChunkLines = adaptive ? dynMaxChunkLines : this.options.fullChunkSizeLines ?? 200;
9031
+ const maxChunks = adaptive ? clamp$1(Math.ceil(chars / 64e3), target, 32) : this.options.fullChunkMaxChunks;
9032
+ setStrategyDiagnostics(env, {
9033
+ area: "parse",
9034
+ path: "full-chunk",
9035
+ chunked: true,
9036
+ reason: "explicit-full-chunk"
9037
+ });
9038
+ return chunkedParse(this, src, env, {
9039
+ maxChunkChars,
9040
+ maxChunkLines,
9041
+ fenceAware: this.options.fullChunkFenceAware ?? true,
9042
+ maxChunks
9043
+ });
9044
+ }
8695
9045
  }
8696
9046
  }
9047
+ if (countedLines !== void 0 && shouldAutoUseUnbounded(this, chars, lines)) {
9048
+ setStrategyDiagnostics(env, {
9049
+ area: "parse",
9050
+ path: "auto-unbounded",
9051
+ unbounded: true,
9052
+ reason: "line-threshold"
9053
+ });
9054
+ return parseStringUnbounded(this, src, env);
9055
+ }
8697
9056
  }
9057
+ setStrategyDiagnostics(env, {
9058
+ area: "parse",
9059
+ path: "plain",
9060
+ reason: "default-plain"
9061
+ });
8698
9062
  return core.parse(src, env, this).tokens;
8699
9063
  },
8700
9064
  parseIterable(chunks, env = {}) {
@@ -8734,6 +9098,7 @@ function markdownIt(presetName, options) {
8734
9098
  total: 0,
8735
9099
  cacheHits: 0,
8736
9100
  appendHits: 0,
9101
+ unboundedAppendHits: 0,
8737
9102
  tailHits: 0,
8738
9103
  fullParses: 0,
8739
9104
  resets: 0,
@@ -9132,7 +9497,93 @@ const OPEN_TAG_RE = /<([A-Z][\w-]*)(?=[\s/>]|$)/gi;
9132
9497
  const CLOSE_TAG_RE = /<\/\s*([A-Z][\w-]*)(?=[\s/>]|$)/gi;
9133
9498
  const TAG_NAME_AT_START_RE = /^<\s*(?:\/\s*)?([A-Z][\w-]*)/i;
9134
9499
  const STRICT_OPEN_TAG_NAME_AT_START_RE = /^<\s*([A-Z][\w:-]*)(?=[\s/>]|$)/i;
9135
- function findTagCloseIndexOutsideQuotes$1(html) {
9500
+ function getHtmlInlineTagName(content) {
9501
+ return (content.match(TAG_NAME_AT_START_RE)?.[1] ?? "").toLowerCase();
9502
+ }
9503
+ function isHtmlInlineClosingTag(content) {
9504
+ return /^\s*<\s*\//.test(content);
9505
+ }
9506
+ function isSelfClosingHtmlInline(content, tag) {
9507
+ return VOID_TAGS$2.has(tag) || /\/\s*>\s*$/.test(content);
9508
+ }
9509
+ function escapeRegex$1(value) {
9510
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9511
+ }
9512
+ function findMatchingCloseChildIndex(children, tag) {
9513
+ let depth = 0;
9514
+ for (let index = 0; index < children.length; index++) {
9515
+ const child = children[index];
9516
+ if (!child || child.type !== "html_inline") continue;
9517
+ const content = String(child.content ?? "");
9518
+ const childTag = getHtmlInlineTagName(content);
9519
+ if (childTag !== tag) continue;
9520
+ if (isHtmlInlineClosingTag(content)) {
9521
+ if (depth === 0) return index;
9522
+ depth--;
9523
+ continue;
9524
+ }
9525
+ if (!isSelfClosingHtmlInline(content, childTag)) depth++;
9526
+ }
9527
+ return -1;
9528
+ }
9529
+ function getTrailingOpenDepth(children, tag) {
9530
+ let depth = 0;
9531
+ for (const child of children) {
9532
+ if (!child || child.type !== "html_inline") continue;
9533
+ const content = String(child.content ?? "");
9534
+ const childTag = getHtmlInlineTagName(content);
9535
+ if (childTag !== tag) continue;
9536
+ if (isHtmlInlineClosingTag(content)) {
9537
+ if (depth > 0) depth--;
9538
+ continue;
9539
+ }
9540
+ if (!isSelfClosingHtmlInline(content, childTag)) depth++;
9541
+ }
9542
+ return depth;
9543
+ }
9544
+ function findMatchingCloseRangeInHtml(content, tag, startIndex = 0) {
9545
+ const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$1(tag)}(?=[\s>/])[^>]*>`, "gi");
9546
+ tokenRe.lastIndex = Math.max(0, startIndex);
9547
+ let depth = 0;
9548
+ let match;
9549
+ while ((match = tokenRe.exec(content)) !== null) {
9550
+ const raw = match[0] ?? "";
9551
+ const closing = !!match[1];
9552
+ const selfClosing = !closing && /\/\s*>$/.test(raw);
9553
+ if (closing) {
9554
+ if (depth === 0) return {
9555
+ start: match.index,
9556
+ end: match.index + raw.length
9557
+ };
9558
+ depth--;
9559
+ continue;
9560
+ }
9561
+ if (!selfClosing) depth++;
9562
+ }
9563
+ return null;
9564
+ }
9565
+ function findMatchingCloseOffsetInHtml(content, tag, startIndex = 0) {
9566
+ const range = findMatchingCloseRangeInHtml(content, tag, startIndex);
9567
+ if (!range) return -1;
9568
+ return range.end;
9569
+ }
9570
+ function getTrailingCustomTagDepthInHtml(content, tag) {
9571
+ const tokenRe = new RegExp(String.raw`<\s*(\/?)\s*${escapeRegex$1(tag)}(?=[\s>/])[^>]*>`, "gi");
9572
+ let depth = 0;
9573
+ let match;
9574
+ while ((match = tokenRe.exec(content)) !== null) {
9575
+ const raw = match[0] ?? "";
9576
+ const closing = !!match[1];
9577
+ const selfClosing = !closing && /\/\s*>$/.test(raw);
9578
+ if (closing) {
9579
+ if (depth > 0) depth--;
9580
+ continue;
9581
+ }
9582
+ if (!selfClosing) depth++;
9583
+ }
9584
+ return depth;
9585
+ }
9586
+ function findTagCloseIndexOutsideQuotes$2(html) {
9136
9587
  let inSingle = false;
9137
9588
  let inDouble = false;
9138
9589
  for (let i = 0; i < html.length; i++) {
@@ -9176,7 +9627,7 @@ function findFirstIncompleteTag(content, tagSet) {
9176
9627
  if (idx < 0) continue;
9177
9628
  const tag = (m[1] ?? "").toLowerCase();
9178
9629
  if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
9179
- if (findTagCloseIndexOutsideQuotes$1(content.slice(idx)) !== -1) continue;
9630
+ if (findTagCloseIndexOutsideQuotes$2(content.slice(idx)) !== -1) continue;
9180
9631
  if (!first || idx < first.index) first = {
9181
9632
  index: idx,
9182
9633
  tag,
@@ -9188,7 +9639,7 @@ function findFirstIncompleteTag(content, tagSet) {
9188
9639
  if (idx < 0) continue;
9189
9640
  const tag = (m[1] ?? "").toLowerCase();
9190
9641
  if (!isCommonHtmlTagOrPrefix(tag, tagSet)) continue;
9191
- if (findTagCloseIndexOutsideQuotes$1(content.slice(idx)) !== -1) continue;
9642
+ if (findTagCloseIndexOutsideQuotes$2(content.slice(idx)) !== -1) continue;
9192
9643
  if (!first || idx < first.index) first = {
9193
9644
  index: idx,
9194
9645
  tag,
@@ -9254,7 +9705,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9254
9705
  cursor = lt + 1;
9255
9706
  continue;
9256
9707
  }
9257
- const closeIdx = findTagCloseIndexOutsideQuotes$1(sub);
9708
+ const closeIdx = findTagCloseIndexOutsideQuotes$2(sub);
9258
9709
  if (closeIdx === -1) {
9259
9710
  pushTextPart("<", baseToken);
9260
9711
  cursor = lt + 1;
@@ -9292,7 +9743,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9292
9743
  if (pending) {
9293
9744
  pending.buffer += tokenToRaw$1(child);
9294
9745
  pendingAtEnd = pending.buffer;
9295
- const closeIdx = findTagCloseIndexOutsideQuotes$1(pending.buffer);
9746
+ const closeIdx = findTagCloseIndexOutsideQuotes$2(pending.buffer);
9296
9747
  if (closeIdx === -1) continue;
9297
9748
  const tagChunk = pending.buffer.slice(0, closeIdx + 1);
9298
9749
  const afterChunk = pending.buffer.slice(closeIdx + 1);
@@ -9310,7 +9761,7 @@ function fixStreamingHtmlInlineChildren(children, tagSet) {
9310
9761
  if (child.type === "html_inline") {
9311
9762
  const content = tokenToRaw$1(child);
9312
9763
  const tagName = (content.match(TAG_NAME_AT_START_RE)?.[1] ?? "").toLowerCase();
9313
- if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes$1(content) === -1) {
9764
+ if (tagName && tagSet.has(tagName) && findTagCloseIndexOutsideQuotes$2(content) === -1) {
9314
9765
  pending = {
9315
9766
  tag: tagName,
9316
9767
  buffer: content,
@@ -9357,6 +9808,7 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9357
9808
  customTagSet.add(name);
9358
9809
  autoCloseInlineTagSet.add(name);
9359
9810
  }
9811
+ const shouldMergeHtmlBlockTag = (tag) => customTagSet.has(tag) || !commonHtmlTags.has(tag);
9360
9812
  md.core.ruler.after("inline", "fix_html_inline_streaming", (state) => {
9361
9813
  const toks = state.tokens ?? [];
9362
9814
  for (const t of toks) {
@@ -9399,15 +9851,13 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9399
9851
  continue;
9400
9852
  }
9401
9853
  const chunk = String(t.content ?? t.raw ?? "");
9402
- const closeRe = new RegExp(`<\\s*\\/\\s*${openTag}\\s*>`, "i");
9403
- const closeMatch = chunk ? closeRe.exec(chunk) : null;
9404
- const isClosingTag$1 = !!closeMatch;
9854
+ const closeEnd = chunk ? findMatchingCloseOffsetInHtml(chunk, openTag) : -1;
9855
+ const isClosingTag$1 = closeEnd !== -1;
9405
9856
  if (chunk) {
9406
9857
  const openToken = toks[openIndex];
9407
- if (closeMatch && typeof closeMatch.index === "number") {
9408
- const end = closeMatch.index + String(closeMatch[0] ?? "").length;
9409
- const before = chunk.slice(0, end);
9410
- const after = chunk.slice(end);
9858
+ if (closeEnd !== -1) {
9859
+ const before = chunk.slice(0, closeEnd);
9860
+ const after = chunk.slice(closeEnd);
9411
9861
  openToken.content = `${String(openToken.content || "")}\n${before}`;
9412
9862
  openToken.loading = false;
9413
9863
  const afterTrimmed = after.replace(/^\s+/, "");
@@ -9441,11 +9891,10 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9441
9891
  const rawContent = String(t.content || "");
9442
9892
  const tag = (rawContent.match(/<\s*(?:\/\s*)?([^\s>/]+)/)?.[1] ?? "").toLowerCase();
9443
9893
  const isClosingTag$1 = /^\s*<\s*\//.test(rawContent);
9444
- if (!tag || !customTagSet.has(tag)) continue;
9894
+ if (!tag || !shouldMergeHtmlBlockTag(tag)) continue;
9445
9895
  if (!isClosingTag$1) {
9446
9896
  if (tag) {
9447
- const closeRe = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i");
9448
- if (!new RegExp(`^\\s*<\\s*${tag}\\b[^>]*\\/\\s*>`, "i").test(rawContent) && !closeRe.test(rawContent)) tagStack.push([tag, i]);
9897
+ if (!new RegExp(`^\\s*<\\s*${tag}\\b[^>]*\\/\\s*>`, "i").test(rawContent) && getTrailingCustomTagDepthInHtml(rawContent, tag) > 0) tagStack.push([tag, i]);
9449
9898
  }
9450
9899
  } else if (tagStack.length > 0 && tag && tagStack[tagStack.length - 1][0] === tag) {
9451
9900
  const [, openIndex] = tagStack[tagStack.length - 1];
@@ -9515,15 +9964,11 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9515
9964
  continue;
9516
9965
  }
9517
9966
  if (tok.type !== "inline") continue;
9518
- const children = Array.isArray(tok.children) ? tok.children : [];
9519
- const closeChildIndex = children.findIndex((c) => {
9520
- if (!c || c.type !== "html_inline") return false;
9521
- const cContent = String(c.content ?? "");
9522
- return /^\s*<\s*\//.test(cContent) && cContent.toLowerCase().includes(top.tag);
9523
- });
9967
+ const children$1 = Array.isArray(tok.children) ? tok.children : [];
9968
+ const closeChildIndex = findMatchingCloseChildIndex(children$1, top.tag);
9524
9969
  if (closeChildIndex !== -1) {
9525
- const beforeChildren = children.slice(0, closeChildIndex + 1);
9526
- const afterChildren = children.slice(closeChildIndex + 1);
9970
+ const beforeChildren = children$1.slice(0, closeChildIndex + 1);
9971
+ const afterChildren = children$1.slice(closeChildIndex + 1);
9527
9972
  const beforeText = beforeChildren.map((c) => String(c?.content ?? c?.raw ?? "")).join("");
9528
9973
  openTok.content = `${String(openTok.content ?? "")}\n${beforeText}`;
9529
9974
  if (Array.isArray(openTok.children)) openTok.children.push(...beforeChildren);
@@ -9566,13 +10011,14 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9566
10011
  continue;
9567
10012
  }
9568
10013
  openTok.content = `${String(openTok.content ?? "")}\n${content}`;
9569
- if (Array.isArray(openTok.children)) openTok.children.push(...children);
10014
+ if (Array.isArray(openTok.children)) openTok.children.push(...children$1);
9570
10015
  toks.splice(i, 1);
9571
10016
  i--;
9572
10017
  continue;
9573
10018
  }
9574
10019
  if (tok.type !== "inline") continue;
9575
- for (const tag of customTagSet) if (getOpenRe(tag).test(content) && !getCloseRe(tag).test(content)) {
10020
+ const children = Array.isArray(tok.children) ? tok.children : [];
10021
+ for (const tag of customTagSet) if ((children.length ? getTrailingOpenDepth(children, tag) : getOpenRe(tag).test(content) && !getCloseRe(tag).test(content) ? 1 : 0) > 0) {
9576
10022
  stack.push({
9577
10023
  tag,
9578
10024
  index: i
@@ -9606,15 +10052,14 @@ function applyFixHtmlInlineTokens(md, options = {}) {
9606
10052
  }
9607
10053
  if (customTagSet.has(tag)) {
9608
10054
  const raw$2 = String(t.content ?? "");
9609
- const closeRe = new RegExp(`<\\/\\s*${tag}\\s*>`, "i");
9610
- t.loading = closeRe.test(raw$2) ? false : t.loading !== void 0 ? t.loading : true;
9611
- const closeMatch$1 = closeRe.exec(raw$2);
9612
- const endTagIndex$1 = closeMatch$1 ? closeMatch$1.index : -1;
9613
- const closeLen$1 = closeMatch$1 ? closeMatch$1[0].length : 0;
10055
+ const openEnd = findTagCloseIndexOutsideQuotes$2(raw$2);
10056
+ const closeRange = openEnd === -1 ? null : findMatchingCloseRangeInHtml(raw$2, tag, openEnd + 1);
10057
+ t.loading = !!closeRange ? false : t.loading !== void 0 ? t.loading : true;
10058
+ const endTagIndex$1 = closeRange?.start ?? -1;
10059
+ const closeLen$1 = closeRange ? closeRange.end - closeRange.start : 0;
9614
10060
  if (endTagIndex$1 !== -1) {
9615
10061
  const rawForNode = raw$2.slice(0, endTagIndex$1 + closeLen$1);
9616
10062
  let inner = "";
9617
- const openEnd = findTagCloseIndexOutsideQuotes$1(raw$2);
9618
10063
  if (openEnd !== -1 && openEnd < endTagIndex$1) inner = raw$2.slice(openEnd + 1, endTagIndex$1);
9619
10064
  t.children = [{
9620
10065
  type: tag,
@@ -10128,7 +10573,7 @@ function fixLinkToken(tokens) {
10128
10573
  tokens[i - 1].content = "";
10129
10574
  }
10130
10575
  }
10131
- if (curToken.type === "link_close" && curToken.nesting === -1 && tokens[i + 1]?.type === "text" && tokens[i - 1]?.type === "text") {
10576
+ if (curToken.type === "link_close" && curToken.nesting === -1 && tokens[i - 2]?.type === "link_open" && tokens[i + 1]?.type === "text" && tokens[i - 1]?.type === "text") {
10132
10577
  const text$1 = tokens[i - 1].content || "";
10133
10578
  const attrs = tokens[i - 2].attrs || [];
10134
10579
  const href = attrs.find((a) => a[0] === "href")?.[1] || "";
@@ -13677,7 +14122,57 @@ const VOID_TAGS = new Set([
13677
14122
  "track",
13678
14123
  "wbr"
13679
14124
  ]);
13680
- const CLOSE_TAG_RE_CACHE = /* @__PURE__ */ new Map();
14125
+ function findTagCloseIndexOutsideQuotes$1(input) {
14126
+ let inSingle = false;
14127
+ let inDouble = false;
14128
+ for (let i = 0; i < input.length; i++) {
14129
+ const ch = input[i];
14130
+ if (ch === "\\") {
14131
+ i++;
14132
+ continue;
14133
+ }
14134
+ if (!inDouble && ch === "'") {
14135
+ inSingle = !inSingle;
14136
+ continue;
14137
+ }
14138
+ if (!inSingle && ch === "\"") {
14139
+ inDouble = !inDouble;
14140
+ continue;
14141
+ }
14142
+ if (!inSingle && !inDouble && ch === ">") return i;
14143
+ }
14144
+ return -1;
14145
+ }
14146
+ function findMatchingCloseTagEnd(rawHtml, tag, startIndex) {
14147
+ const lowerTag = tag.toLowerCase();
14148
+ const openTagRe = new RegExp(String.raw`^<\s*${lowerTag}(?=\s|>|/)`, "i");
14149
+ const closeTagRe = new RegExp(String.raw`^<\s*\/\s*${lowerTag}(?=\s|>)`, "i");
14150
+ let depth = 0;
14151
+ let index = Math.max(0, startIndex);
14152
+ while (index < rawHtml.length) {
14153
+ const lt = rawHtml.indexOf("<", index);
14154
+ if (lt === -1) return -1;
14155
+ const slice = rawHtml.slice(lt);
14156
+ if (closeTagRe.test(slice)) {
14157
+ const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14158
+ if (endRel === -1) return -1;
14159
+ if (depth === 0) return lt + endRel + 1;
14160
+ depth--;
14161
+ index = lt + endRel + 1;
14162
+ continue;
14163
+ }
14164
+ if (openTagRe.test(slice)) {
14165
+ const endRel = findTagCloseIndexOutsideQuotes$1(slice);
14166
+ if (endRel === -1) return -1;
14167
+ const rawTag = slice.slice(0, endRel + 1);
14168
+ if (!/\/\s*>$/.test(rawTag)) depth++;
14169
+ index = lt + endRel + 1;
14170
+ continue;
14171
+ }
14172
+ index = lt + 1;
14173
+ }
14174
+ return -1;
14175
+ }
13681
14176
  function parseHtmlBlock(token) {
13682
14177
  const raw = String(token.content ?? "");
13683
14178
  if (/^\s*<!--/.test(raw) || /^\s*<!/.test(raw) || /^\s*<\?/.test(raw)) return {
@@ -13695,14 +14190,11 @@ function parseHtmlBlock(token) {
13695
14190
  tag: "",
13696
14191
  loading: false
13697
14192
  };
13698
- const selfClosing = /^\s*<[^>]*\/\s*>/.test(raw);
14193
+ const openEnd = findTagCloseIndexOutsideQuotes$1(raw);
14194
+ const openTag = openEnd === -1 ? raw : raw.slice(0, openEnd + 1);
14195
+ const selfClosing = openEnd !== -1 && /\/\s*>$/.test(openTag);
13699
14196
  const isVoid = VOID_TAGS.has(tag);
13700
- let closeRe = CLOSE_TAG_RE_CACHE.get(tag);
13701
- if (!closeRe) {
13702
- closeRe = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i");
13703
- CLOSE_TAG_RE_CACHE.set(tag, closeRe);
13704
- }
13705
- const hasClosing = closeRe.test(raw);
14197
+ const hasClosing = (openEnd === -1 ? -1 : findMatchingCloseTagEnd(raw, tag, openEnd + 1)) !== -1;
13706
14198
  const loading = !(isVoid || selfClosing || hasClosing);
13707
14199
  return {
13708
14200
  type: "html_block",
@@ -13932,6 +14424,43 @@ function stripTrailingPartialClosingTag(inner, tag) {
13932
14424
  const re = new RegExp(String.raw`[\t ]*<\s*\/\s*${tag}[^>]*$`, "i");
13933
14425
  return inner.replace(re, "");
13934
14426
  }
14427
+ function escapeRegex(value) {
14428
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14429
+ }
14430
+ function findMatchingCloseTagRange(rawHtml, tag, startIndex) {
14431
+ if (!rawHtml || !tag) return null;
14432
+ const lowerTag = tag.toLowerCase();
14433
+ const openTagRe = new RegExp(String.raw`^<\s*${escapeRegex(lowerTag)}(?=\s|>|/)`, "i");
14434
+ const closeTagRe = new RegExp(String.raw`^<\s*\/\s*${escapeRegex(lowerTag)}(?=\s|>)`, "i");
14435
+ let depth = 0;
14436
+ let index = Math.max(0, startIndex);
14437
+ while (index < rawHtml.length) {
14438
+ const lt = rawHtml.indexOf("<", index);
14439
+ if (lt === -1) break;
14440
+ const slice = rawHtml.slice(lt);
14441
+ if (closeTagRe.test(slice)) {
14442
+ const endRel = findTagCloseIndexOutsideQuotes(slice);
14443
+ if (endRel === -1) return null;
14444
+ if (depth === 0) return {
14445
+ start: lt,
14446
+ end: lt + endRel + 1
14447
+ };
14448
+ depth--;
14449
+ index = lt + endRel + 1;
14450
+ continue;
14451
+ }
14452
+ if (openTagRe.test(slice)) {
14453
+ const endRel = findTagCloseIndexOutsideQuotes(slice);
14454
+ if (endRel === -1) return null;
14455
+ const raw = slice.slice(0, endRel + 1);
14456
+ if (!/\/\s*>$/.test(raw)) depth++;
14457
+ index = lt + endRel + 1;
14458
+ continue;
14459
+ }
14460
+ index = lt + 1;
14461
+ }
14462
+ return null;
14463
+ }
13935
14464
  function findNextCustomHtmlBlockFromSource(source, tag, startIndex) {
13936
14465
  if (!source || !tag) return null;
13937
14466
  const lowerTag = tag.toLowerCase();
@@ -14037,16 +14566,17 @@ function parseBasicBlockToken(tokens, index, options) {
14037
14566
  const mappedLineStart = Array.isArray(token.map) ? lineToIndex(source, Number(token.map?.[0] ?? 0)) : 0;
14038
14567
  const fromSource = findNextCustomHtmlBlockFromSource(source, tag, Math.max(clampNonNegative(cursor), clampNonNegative(mappedLineStart)));
14039
14568
  if (fromSource) options.__customHtmlBlockCursor = fromSource.end;
14040
- const rawHtml = String(fromSource?.raw ?? htmlBlockNode.content ?? "");
14569
+ const rawHtml = String(fromSource?.raw ?? htmlBlockNode.raw ?? "");
14041
14570
  const openEnd = findTagCloseIndexOutsideQuotes(rawHtml);
14042
- const closeMatch = new RegExp(`<\\s*\\/\\s*${tag}\\s*>`, "i").exec(rawHtml);
14043
- const closeIndex = closeMatch ? closeMatch.index : -1;
14571
+ const openTag = openEnd !== -1 ? rawHtml.slice(0, openEnd + 1) : rawHtml;
14572
+ const selfClosing = openEnd !== -1 && /\/\s*>\s*$/.test(openTag);
14573
+ const closeRange = openEnd === -1 ? null : findMatchingCloseTagRange(rawHtml, tag, openEnd + 1);
14574
+ const closeIndex = closeRange?.start ?? -1;
14044
14575
  let inner = "";
14045
14576
  if (openEnd !== -1) if (closeIndex !== -1 && openEnd < closeIndex) inner = rawHtml.slice(openEnd + 1, closeIndex);
14046
14577
  else inner = rawHtml.slice(openEnd + 1);
14047
14578
  if (closeIndex === -1) inner = stripTrailingPartialClosingTag(inner, tag);
14048
14579
  const attrs = [];
14049
- const openTag = openEnd !== -1 ? rawHtml.slice(0, openEnd + 1) : rawHtml;
14050
14580
  const attrRegex = /\s([\w:-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
14051
14581
  let m;
14052
14582
  while ((m = attrRegex.exec(openTag)) !== null) {
@@ -14055,12 +14585,13 @@ function parseBasicBlockToken(tokens, index, options) {
14055
14585
  const value = m[2] || m[3] || m[4] || "";
14056
14586
  attrs.push([name, value]);
14057
14587
  }
14588
+ const loading = !options?.final && !selfClosing && closeRange == null;
14058
14589
  return [{
14059
14590
  type: tag,
14060
14591
  tag,
14061
14592
  content: stripWrapperNewlines(inner),
14062
14593
  raw: String(fromSource?.raw ?? htmlBlockNode.raw ?? rawHtml),
14063
- loading: htmlBlockNode.loading,
14594
+ loading,
14064
14595
  attrs: attrs.length ? attrs : void 0
14065
14596
  }, index + 1];
14066
14597
  }
@@ -14455,7 +14986,7 @@ function ensureBlankLineBeforeInlineMultilineCustomHtmlBlocks(markdown, tags) {
14455
14986
  }
14456
14987
  return false;
14457
14988
  };
14458
- const findTagCloseIndexOutsideQuotes$2 = (input) => {
14989
+ const findTagCloseIndexOutsideQuotes$3 = (input) => {
14459
14990
  let inSingle = false;
14460
14991
  let inDouble = false;
14461
14992
  for (let i = 0; i < input.length; i++) {
@@ -14504,7 +15035,7 @@ function ensureBlankLineBeforeInlineMultilineCustomHtmlBlocks(markdown, tags) {
14504
15035
  i++;
14505
15036
  continue;
14506
15037
  }
14507
- const closeIdxRel = findTagCloseIndexOutsideQuotes$2(line.slice(i));
15038
+ const closeIdxRel = findTagCloseIndexOutsideQuotes$3(line.slice(i));
14508
15039
  if (closeIdxRel === -1) {
14509
15040
  hasRenderablePrefix = true;
14510
15041
  i++;
@@ -14608,6 +15139,27 @@ function normalizeCustomHtmlOpeningTagSameLine(markdown, tags) {
14608
15139
  while (i < s.length && isIndentWs(s[i])) i++;
14609
15140
  return s.slice(i);
14610
15141
  };
15142
+ const findTagCloseIndexOutsideQuotes$3 = (input) => {
15143
+ let inSingle = false;
15144
+ let inDouble = false;
15145
+ for (let i = 0; i < input.length; i++) {
15146
+ const ch = input[i];
15147
+ if (ch === "\\") {
15148
+ i++;
15149
+ continue;
15150
+ }
15151
+ if (!inDouble && ch === "'") {
15152
+ inSingle = !inSingle;
15153
+ continue;
15154
+ }
15155
+ if (!inSingle && ch === "\"") {
15156
+ inDouble = !inDouble;
15157
+ continue;
15158
+ }
15159
+ if (!inSingle && !inDouble && ch === ">") return i;
15160
+ }
15161
+ return -1;
15162
+ };
14611
15163
  const hasClosingTagOnLine = (line, from, tag) => {
14612
15164
  const lowerTag = tag.toLowerCase();
14613
15165
  let pos = line.indexOf("<", from);
@@ -14659,8 +15211,9 @@ function normalizeCustomHtmlOpeningTagSameLine(markdown, tags) {
14659
15211
  if (i === nameStart) return line;
14660
15212
  const tagName = line.slice(nameStart, i).toLowerCase();
14661
15213
  if (!tagSet.has(tagName)) return line;
14662
- const gt = line.indexOf(">", i);
14663
- if (gt === -1) return line;
15214
+ const gtRel = findTagCloseIndexOutsideQuotes$3(line.slice(i));
15215
+ if (gtRel === -1) return line;
15216
+ const gt = i + gtRel;
14664
15217
  if (hasClosingTagOnLine(line, gt + 1, tagName)) return line;
14665
15218
  const rest = trimStartIndentWs(line.slice(gt + 1));
14666
15219
  if (!rest) return line;
@@ -15102,6 +15655,22 @@ function parseMarkdownToStructure(markdown, md, options = {}) {
15102
15655
  else result = postResult;
15103
15656
  }
15104
15657
  }
15658
+ if (isFinal) {
15659
+ const seen = /* @__PURE__ */ new WeakSet();
15660
+ const finalizeHtmlBlockLoading = (value) => {
15661
+ if (!value || typeof value !== "object") return;
15662
+ if (seen.has(value)) return;
15663
+ seen.add(value);
15664
+ if (Array.isArray(value)) {
15665
+ for (const item of value) finalizeHtmlBlockLoading(item);
15666
+ return;
15667
+ }
15668
+ const node = value;
15669
+ if (node.type === "html_block" && node.loading === true) node.loading = false;
15670
+ for (const child of Object.values(node)) finalizeHtmlBlockLoading(child);
15671
+ };
15672
+ finalizeHtmlBlockLoading(result);
15673
+ }
15105
15674
  if (options.debug) console.log("Parsed Markdown Tree Structure:", result);
15106
15675
  return result;
15107
15676
  }