@stream-mdx/worker 0.2.0 → 0.4.0

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.
@@ -0,0 +1,3978 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/worker-dom-stub.ts
34
+ var characterEntities;
35
+ var init_worker_dom_stub = __esm({
36
+ "src/worker-dom-stub.ts"() {
37
+ "use strict";
38
+ characterEntities = __toESM(require("character-entities"), 1);
39
+ if (typeof document === "undefined") {
40
+ const entityMap = characterEntities;
41
+ globalThis.document = {
42
+ compatMode: "CSS1Compat",
43
+ createElement() {
44
+ return {
45
+ _inner: "",
46
+ set innerHTML(value) {
47
+ this._inner = value;
48
+ },
49
+ get innerHTML() {
50
+ return this._inner;
51
+ },
52
+ get textContent() {
53
+ const match = /^&([^;]+);$/.exec(this._inner);
54
+ if (match) {
55
+ const entity = entityMap[match[1]];
56
+ if (typeof entity === "string") {
57
+ return entity;
58
+ }
59
+ }
60
+ return this._inner;
61
+ }
62
+ };
63
+ }
64
+ };
65
+ }
66
+ if (typeof globalThis.DOMParser === "undefined") {
67
+ class DOMParserStub {
68
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
69
+ parseFromString(content, _type) {
70
+ const text = content ?? "";
71
+ const noop = () => null;
72
+ const emptyList = () => [];
73
+ return {
74
+ textContent: text,
75
+ innerHTML: text,
76
+ documentElement: { textContent: text, innerHTML: text },
77
+ body: {
78
+ innerHTML: text,
79
+ textContent: text,
80
+ firstChild: null,
81
+ querySelector: noop,
82
+ querySelectorAll: emptyList,
83
+ appendChild: noop,
84
+ removeChild: noop
85
+ },
86
+ createElement() {
87
+ return {
88
+ innerHTML: "",
89
+ textContent: "",
90
+ querySelector: noop,
91
+ querySelectorAll: emptyList,
92
+ appendChild: noop,
93
+ removeChild: noop
94
+ };
95
+ },
96
+ querySelector: noop,
97
+ querySelectorAll: emptyList
98
+ };
99
+ }
100
+ }
101
+ globalThis.DOMParser = DOMParserStub;
102
+ }
103
+ }
104
+ });
105
+
106
+ // src/parser/block-types.ts
107
+ function normalizeNodeName(nodeType) {
108
+ if (!nodeType) return "";
109
+ if (nodeType.startsWith("ATXHeading")) {
110
+ return "ATXHeading";
111
+ }
112
+ if (nodeType.startsWith("SetextHeading")) {
113
+ return "SetextHeading";
114
+ }
115
+ return nodeType;
116
+ }
117
+ function isBlockLevelNode(nodeType) {
118
+ const normalized = normalizeNodeName(nodeType);
119
+ if (normalized === "BulletList" || normalized === "OrderedList") {
120
+ return true;
121
+ }
122
+ return BLOCK_NODE_TYPES.has(normalized);
123
+ }
124
+ function mapLezerNodeToBlockType(nodeType) {
125
+ const normalized = normalizeNodeName(nodeType);
126
+ switch (normalized) {
127
+ case "ATXHeading":
128
+ case "SetextHeading":
129
+ return "heading";
130
+ case "Paragraph":
131
+ return "paragraph";
132
+ case "FencedCode":
133
+ case "IndentedCode":
134
+ return "code";
135
+ case "Blockquote":
136
+ return "blockquote";
137
+ case "BulletList":
138
+ case "OrderedList":
139
+ return "list";
140
+ case "HTMLBlock":
141
+ return "html";
142
+ case "ThematicBreak":
143
+ case "HorizontalRule":
144
+ return "hr";
145
+ default:
146
+ return "paragraph";
147
+ }
148
+ }
149
+ var BLOCK_NODE_TYPES;
150
+ var init_block_types = __esm({
151
+ "src/parser/block-types.ts"() {
152
+ "use strict";
153
+ BLOCK_NODE_TYPES = /* @__PURE__ */ new Set([
154
+ "Paragraph",
155
+ "Blockquote",
156
+ "FencedCode",
157
+ "IndentedCode",
158
+ "BulletList",
159
+ "OrderedList",
160
+ "HTMLBlock",
161
+ "ThematicBreak",
162
+ "HorizontalRule",
163
+ "ATXHeading",
164
+ "SetextHeading"
165
+ ]);
166
+ }
167
+ });
168
+
169
+ // src/perf/patch-heuristics.ts
170
+ function isParagraphBoundaryPatch(patch) {
171
+ if (patch.op === "finalize") {
172
+ return true;
173
+ }
174
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && patch.node && typeof patch.node === "object" && patch.node.type === "paragraph") {
175
+ return true;
176
+ }
177
+ if (patch.op === "setProps" && typeof patch.at?.nodeId === "string" && patch.at.nodeId.includes("paragraph")) {
178
+ return true;
179
+ }
180
+ return false;
181
+ }
182
+ function countParagraphBoundaries(patches) {
183
+ let count = 0;
184
+ for (const patch of patches) {
185
+ if (isParagraphBoundaryPatch(patch)) {
186
+ count += 1;
187
+ }
188
+ }
189
+ return count;
190
+ }
191
+ function computeParagraphPatchLimit(patches, {
192
+ largePatchThreshold = 80,
193
+ baseLimit = 64,
194
+ finalizeLimit = 48
195
+ } = {}) {
196
+ if (!Array.isArray(patches) || patches.length < largePatchThreshold) {
197
+ return null;
198
+ }
199
+ const paragraphBoundaries = countParagraphBoundaries(patches);
200
+ if (paragraphBoundaries === 0) {
201
+ return null;
202
+ }
203
+ const hasFinalize = patches.some((patch) => patch.op === "finalize");
204
+ if (hasFinalize) {
205
+ return Math.min(Math.max(16, finalizeLimit), patches.length);
206
+ }
207
+ const scaledLimit = Math.max(32, Math.min(baseLimit, baseLimit - (paragraphBoundaries - 1) * 8));
208
+ return Math.min(scaledLimit, patches.length);
209
+ }
210
+ var init_patch_heuristics = __esm({
211
+ "src/perf/patch-heuristics.ts"() {
212
+ "use strict";
213
+ }
214
+ });
215
+
216
+ // src/lazy-tokenization.ts
217
+ function clampLazyRange(startLine, endLine, totalLines) {
218
+ const clampedStart = Math.max(0, Math.min(Math.floor(startLine), totalLines));
219
+ const clampedEnd = Math.max(clampedStart, Math.min(Math.floor(endLine), totalLines));
220
+ return { startLine: clampedStart, endLine: clampedEnd };
221
+ }
222
+ function compareLazyPriority(a, b) {
223
+ return (PRIORITY_ORDER[a] ?? 0) - (PRIORITY_ORDER[b] ?? 0);
224
+ }
225
+ function mergeLazyRequests(existing, next) {
226
+ const priority = compareLazyPriority(existing.priority, next.priority) >= 0 ? existing.priority : next.priority;
227
+ const startLine = Math.min(existing.startLine, next.startLine);
228
+ const endLine = Math.max(existing.endLine, next.endLine);
229
+ const useNextTimestamp = compareLazyPriority(next.priority, existing.priority) >= 0;
230
+ return {
231
+ blockId: existing.blockId,
232
+ startLine,
233
+ endLine,
234
+ priority,
235
+ requestedAt: useNextTimestamp ? next.requestedAt : existing.requestedAt
236
+ };
237
+ }
238
+ function lazyRequestRangeSize(request) {
239
+ return Math.max(0, request.endLine - request.startLine);
240
+ }
241
+ var PRIORITY_ORDER;
242
+ var init_lazy_tokenization = __esm({
243
+ "src/lazy-tokenization.ts"() {
244
+ "use strict";
245
+ PRIORITY_ORDER = {
246
+ visible: 2,
247
+ prefetch: 1
248
+ };
249
+ }
250
+ });
251
+
252
+ // src/mdx-compile.ts
253
+ function collectMdxDependencies(source) {
254
+ const dependencies = /* @__PURE__ */ new Set();
255
+ const importRegex = /(?:import|export)\s+[^'"]*?\sfrom\s+['"]([^'"]+)['"]/g;
256
+ let match;
257
+ while (true) {
258
+ match = importRegex.exec(source);
259
+ if (match === null) {
260
+ break;
261
+ }
262
+ dependencies.add(match[1]);
263
+ }
264
+ return Array.from(dependencies);
265
+ }
266
+ async function compileMdxContent(source) {
267
+ const isDev = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env?.NODE_ENV === "development";
268
+ const compiled = await (0, import_mdx.compile)(source, {
269
+ outputFormat: "function-body",
270
+ development: isDev,
271
+ remarkPlugins: [import_remark_gfm.default, import_remark_math.default],
272
+ rehypePlugins: [
273
+ import_rehype_slug.default,
274
+ [
275
+ import_rehype_katex.default,
276
+ {
277
+ throwOnError: false,
278
+ errorColor: "#cc0000",
279
+ strict: false
280
+ }
281
+ ]
282
+ ],
283
+ jsxImportSource: "react"
284
+ });
285
+ return {
286
+ code: String(compiled),
287
+ dependencies: collectMdxDependencies(source)
288
+ };
289
+ }
290
+ var import_mdx, import_rehype_katex, import_rehype_slug, import_remark_gfm, import_remark_math;
291
+ var init_mdx_compile = __esm({
292
+ "src/mdx-compile.ts"() {
293
+ "use strict";
294
+ import_mdx = require("@mdx-js/mdx");
295
+ import_rehype_katex = __toESM(require("rehype-katex"), 1);
296
+ import_rehype_slug = __toESM(require("rehype-slug"), 1);
297
+ import_remark_gfm = __toESM(require("remark-gfm"), 1);
298
+ import_remark_math = __toESM(require("remark-math"), 1);
299
+ }
300
+ });
301
+
302
+ // src/worker.ts
303
+ var worker_exports = {};
304
+ function clampInt(value, min, max) {
305
+ if (!Number.isFinite(value)) return min;
306
+ return Math.min(max, Math.max(min, Math.floor(value)));
307
+ }
308
+ function readNumericEnv(key) {
309
+ try {
310
+ if (typeof process !== "undefined" && process.env && process.env[key]) {
311
+ const parsed = Number(process.env[key]);
312
+ if (Number.isFinite(parsed)) {
313
+ return parsed;
314
+ }
315
+ }
316
+ } catch {
317
+ }
318
+ return null;
319
+ }
320
+ function sliceAppendChunk(text, maxChars) {
321
+ if (text.length <= maxChars) {
322
+ return { chunk: text, rest: "" };
323
+ }
324
+ const newlineIndex = text.lastIndexOf("\n", maxChars);
325
+ const cut = newlineIndex > 0 && maxChars - newlineIndex <= APPEND_NEWLINE_GRACE ? newlineIndex + 1 : maxChars;
326
+ return { chunk: text.slice(0, cut), rest: text.slice(cut) };
327
+ }
328
+ function waitForNextTick() {
329
+ return new Promise((resolve) => setTimeout(resolve, 0));
330
+ }
331
+ function isDebugEnabled(flag) {
332
+ try {
333
+ if (typeof process !== "undefined" && process.env) {
334
+ if (flag === "mdx" && process.env.NEXT_PUBLIC_STREAMING_DEBUG_MDX === "1") return true;
335
+ if (flag === "worker" && process.env.NEXT_PUBLIC_STREAMING_DEBUG_WORKER === "1") return true;
336
+ }
337
+ } catch {
338
+ }
339
+ try {
340
+ const g = globalThis;
341
+ if (flag === "mdx" && g.__STREAMING_DEBUG__?.mdx) return true;
342
+ if (flag === "worker" && g.__STREAMING_DEBUG__?.worker) return true;
343
+ } catch {
344
+ }
345
+ return false;
346
+ }
347
+ function now() {
348
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
349
+ }
350
+ function roundMetric(value) {
351
+ if (!Number.isFinite(value)) return 0;
352
+ return Math.round(value * 1e3) / 1e3;
353
+ }
354
+ function setActiveMetricsCollector(collector) {
355
+ activeMetricsCollector = collector;
356
+ }
357
+ function getActiveMetricsCollector() {
358
+ return activeMetricsCollector;
359
+ }
360
+ function estimatePatchSize(patches) {
361
+ if (!sharedTextEncoder) return 0;
362
+ try {
363
+ return sharedTextEncoder.encode(JSON.stringify(patches)).length;
364
+ } catch {
365
+ return 0;
366
+ }
367
+ }
368
+ function countChangedBlocksFromPatches(patches) {
369
+ if (!patches || patches.length === 0) return 0;
370
+ const ids = /* @__PURE__ */ new Set();
371
+ for (const patch of patches) {
372
+ const blockId = patch.at?.blockId;
373
+ if (blockId) {
374
+ ids.add(blockId);
375
+ }
376
+ }
377
+ return ids.size;
378
+ }
379
+ function partitionPatchesForCredits(patches, maxImmediate) {
380
+ let combined = patches;
381
+ if (deferredPatchQueue.length > 0) {
382
+ combined = deferredPatchQueue.concat(patches);
383
+ deferredPatchQueue = [];
384
+ }
385
+ if (combined.length === 0) {
386
+ return [];
387
+ }
388
+ const immediate = [];
389
+ const deferred = [];
390
+ let heavyBudget = (0, import_core3.computeHeavyPatchBudget)(workerCredits);
391
+ let nonStructuralImmediate = 0;
392
+ const isStructuralPatch = (patch) => {
393
+ switch (patch.op) {
394
+ case "insertChild":
395
+ case "deleteChild":
396
+ case "replaceChild":
397
+ case "reorder":
398
+ return true;
399
+ default:
400
+ return false;
401
+ }
402
+ };
403
+ for (const patch of combined) {
404
+ if (isStructuralPatch(patch)) {
405
+ immediate.push(patch);
406
+ continue;
407
+ }
408
+ const heavy = (0, import_patch_batching.isHeavyPatch)(patch);
409
+ if (heavy) {
410
+ if (heavyBudget <= 0) {
411
+ if (deferred.length < MAX_DEFERRED_PATCHES) {
412
+ deferred.push(patch);
413
+ continue;
414
+ }
415
+ } else {
416
+ heavyBudget -= 1;
417
+ }
418
+ }
419
+ if (typeof maxImmediate === "number" && nonStructuralImmediate >= maxImmediate) {
420
+ deferred.push(patch);
421
+ continue;
422
+ }
423
+ immediate.push(patch);
424
+ nonStructuralImmediate += 1;
425
+ }
426
+ deferredPatchQueue = deferred;
427
+ return immediate;
428
+ }
429
+ function flushDeferredPatches() {
430
+ if (workerCredits <= 0 || deferredPatchQueue.length === 0) {
431
+ return;
432
+ }
433
+ const immediate = partitionPatchesForCredits([], MAX_DEFERRED_FLUSH_PATCHES);
434
+ if (immediate.length === 0) {
435
+ return;
436
+ }
437
+ const metricsCollector = new WorkerMetricsCollector(workerGrammarEngine);
438
+ metricsCollector.setBlocksProduced(blocks.length);
439
+ const tx = ++txCounter;
440
+ const patchBytes = estimatePatchSize(immediate);
441
+ metricsCollector.finalizePatch(tx, immediate.length, deferredPatchQueue.length, patchBytes);
442
+ const changedBlocks = countChangedBlocksFromPatches(immediate);
443
+ const patchMetrics = metricsCollector.toPatchMetrics(changedBlocks);
444
+ postMessage({
445
+ type: "PATCH",
446
+ tx,
447
+ patches: immediate,
448
+ metrics: patchMetrics
449
+ });
450
+ emitMetricsSample(metricsCollector);
451
+ if (getActiveMetricsCollector() === metricsCollector) {
452
+ setActiveMetricsCollector(null);
453
+ }
454
+ }
455
+ function emitMetricsSample(collector) {
456
+ if (!collector) return;
457
+ const metrics = collector.toPerformanceMetrics();
458
+ if (!metrics) return;
459
+ postMessage({
460
+ type: "METRICS",
461
+ metrics
462
+ });
463
+ }
464
+ function mapToNumberRecord(map, roundValues = false) {
465
+ const result = {};
466
+ for (const [key, value] of map.entries()) {
467
+ if (!Number.isFinite(value)) continue;
468
+ if (value === 0) continue;
469
+ result[key] = roundValues ? roundMetric(value) : value;
470
+ }
471
+ return result;
472
+ }
473
+ function mapToHighlightRecord(map) {
474
+ const result = {};
475
+ for (const [key, value] of map.entries()) {
476
+ if (!value || value.count <= 0 || !Number.isFinite(value.time)) continue;
477
+ const total = roundMetric(value.time);
478
+ result[key] = {
479
+ count: value.count,
480
+ totalMs: total,
481
+ avgMs: roundMetric(total / value.count)
482
+ };
483
+ }
484
+ return result;
485
+ }
486
+ function parseInlineStreaming(content) {
487
+ const prepared = (0, import_core.prepareInlineStreamingContent)(content, { formatAnticipation: formatAnticipationConfig, math: enableMath });
488
+ if (prepared.kind === "raw") {
489
+ return { inline: [{ kind: "text", text: content }], status: prepared.status };
490
+ }
491
+ let preparedContent = prepared.content;
492
+ let appended = prepared.appended;
493
+ if (formatAnticipationConfig.regex) {
494
+ const regexAppend = inlineParser.getRegexAnticipationAppend(content);
495
+ if (regexAppend) {
496
+ preparedContent += regexAppend;
497
+ appended += regexAppend;
498
+ }
499
+ }
500
+ const status = appended.length > 0 ? "anticipated" : prepared.status;
501
+ return {
502
+ inline: inlineParser.parse(preparedContent, { cache: false }),
503
+ status
504
+ };
505
+ }
506
+ function slugifyHeading(text) {
507
+ const normalized = text.trim().toLowerCase().replace(/[\u0000-\u001f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
508
+ return normalized;
509
+ }
510
+ function buildTocHeadings(blocksInput) {
511
+ const headings = [];
512
+ for (const block of blocksInput) {
513
+ if (block.type !== "heading") continue;
514
+ const meta = block.payload.meta ?? {};
515
+ const rawHeading = typeof block.payload.raw === "string" ? block.payload.raw : "";
516
+ const headingText = typeof meta.headingText === "string" && meta.headingText.trim().length > 0 ? meta.headingText : (0, import_core.removeHeadingMarkers)(rawHeading).trim();
517
+ const levelFromMeta = typeof meta.headingLevel === "number" ? meta.headingLevel : 1;
518
+ const level = Math.min(Math.max(levelFromMeta, 1), 6);
519
+ const id = typeof meta.headingId === "string" && meta.headingId.trim().length > 0 ? meta.headingId : slugifyHeading(headingText) || "heading";
520
+ headings.push({
521
+ id,
522
+ text: headingText,
523
+ level,
524
+ blockId: block.id
525
+ });
526
+ }
527
+ return headings;
528
+ }
529
+ function tocSignatureFor(headings) {
530
+ if (headings.length === 0) return "";
531
+ return headings.map((heading) => `${heading.id}:${heading.level}:${heading.text}`).join("|");
532
+ }
533
+ function maybeBuildTocPatch(blocksInput) {
534
+ const nextHeadings = buildTocHeadings(blocksInput);
535
+ const nextSignature = tocSignatureFor(nextHeadings);
536
+ tocHeadings = nextHeadings;
537
+ if (nextSignature === tocSignature) {
538
+ return null;
539
+ }
540
+ tocSignature = nextSignature;
541
+ return {
542
+ op: "setProps",
543
+ at: { blockId: import_core.PATCH_ROOT_ID },
544
+ props: {
545
+ tocHeadings: nextHeadings
546
+ }
547
+ };
548
+ }
549
+ async function initialize(initialContent = "", prewarmLangs = [], docPlugins, mdxOptions) {
550
+ performanceTimer = new import_core.PerformanceTimer();
551
+ blocks = [];
552
+ lastTree = null;
553
+ currentContent = "";
554
+ deferredPatchQueue = [];
555
+ tocHeadings = [];
556
+ tocSignature = "";
557
+ resetIncrementalHighlightState();
558
+ lazyTokenizationStates.clear();
559
+ lazyTokenizationQueue.clear();
560
+ lazyTokenizationScheduled = false;
561
+ lazyTokenizationProcessing = false;
562
+ mdxCompileMode = mdxOptions?.compileMode ?? "server";
563
+ try {
564
+ if (DEBUG_MDX) {
565
+ console.debug("[markdown-worker] init", { mdxCompileMode });
566
+ }
567
+ } catch {
568
+ }
569
+ clearWorkerMdxCaches();
570
+ const enable = {
571
+ footnotes: docPlugins?.footnotes ?? true,
572
+ html: docPlugins?.html ?? true,
573
+ mdx: docPlugins?.mdx ?? true,
574
+ tables: docPlugins?.tables ?? true,
575
+ callouts: docPlugins?.callouts ?? false,
576
+ math: docPlugins?.math ?? true,
577
+ formatAnticipation: docPlugins?.formatAnticipation ?? false,
578
+ codeHighlighting: docPlugins?.codeHighlighting,
579
+ liveCodeHighlighting: docPlugins?.liveCodeHighlighting ?? false,
580
+ liveTokenization: docPlugins?.liveTokenization ?? true,
581
+ emitHighlightTokens: docPlugins?.emitHighlightTokens ?? true,
582
+ emitDiffBlocks: docPlugins?.emitDiffBlocks ?? false
583
+ };
584
+ enableMath = enable.math;
585
+ formatAnticipationConfig = (0, import_core.normalizeFormatAnticipation)(enable.formatAnticipation);
586
+ if (enable.codeHighlighting === "incremental" || enable.codeHighlighting === "live" || enable.codeHighlighting === "final") {
587
+ codeHighlightingMode = enable.codeHighlighting;
588
+ } else {
589
+ codeHighlightingMode = enable.liveCodeHighlighting ? "live" : "final";
590
+ }
591
+ if (docPlugins?.outputMode === "html" || docPlugins?.outputMode === "tokens" || docPlugins?.outputMode === "both") {
592
+ highlightOutputMode = docPlugins.outputMode;
593
+ } else {
594
+ highlightOutputMode = "html";
595
+ }
596
+ emitHighlightTokens = enable.emitHighlightTokens;
597
+ emitDiffBlocks = enable.emitDiffBlocks;
598
+ liveTokenizationEnabled = enable.liveTokenization;
599
+ if (docPlugins?.lazyTokenization) {
600
+ lazyTokenizationEnabled = docPlugins.lazyTokenization.enabled ?? true;
601
+ const desiredThreshold = docPlugins.lazyTokenization.thresholdLines ?? DEFAULT_LAZY_TOKENIZATION_THRESHOLD;
602
+ lazyTokenizationThresholdLines = clampInt(desiredThreshold, MIN_LAZY_TOKENIZATION_THRESHOLD, MAX_LAZY_TOKENIZATION_THRESHOLD);
603
+ } else {
604
+ lazyTokenizationEnabled = true;
605
+ lazyTokenizationThresholdLines = DEFAULT_LAZY_TOKENIZATION_THRESHOLD;
606
+ }
607
+ if (Array.isArray(docPlugins?.mdxComponentNames) && docPlugins?.mdxComponentNames.length > 0) {
608
+ mdxComponentAllowlist = new Set(docPlugins.mdxComponentNames);
609
+ } else {
610
+ mdxComponentAllowlist = null;
611
+ }
612
+ inlineParser = new import_core2.InlineParser({ enableMath });
613
+ documentPluginState.inlineParser = inlineParser;
614
+ if (enable.footnotes) (0, import_plugins.registerFootnotesPlugin)();
615
+ if (enable.tables) import_plugins.globalDocumentPluginRegistry.register(import_plugins.TablesPlugin);
616
+ if (enable.callouts) import_plugins.globalDocumentPluginRegistry.register(import_plugins.CalloutsPlugin);
617
+ if (enable.html) import_plugins.globalDocumentPluginRegistry.register(import_plugins.HTMLBlockPlugin);
618
+ if (enable.mdx) import_plugins.globalDocumentPluginRegistry.register(import_plugins.MDXDetectionPlugin);
619
+ performanceTimer.mark("highlighter-init");
620
+ const coreLangs = ["javascript", "typescript", "json", "text", "markdown", "diff"];
621
+ const initialLangs = [...coreLangs, ...prewarmLangs];
622
+ highlighter = await (0, import_shiki.createHighlighter)({
623
+ engine: (0, import_engine_javascript.createJavaScriptRegexEngine)(),
624
+ langs: initialLangs,
625
+ themes: ["github-dark", "github-light"]
626
+ });
627
+ const highlighterTime = performanceTimer.measure("highlighter-init");
628
+ if (initialContent) {
629
+ currentContent = initialContent;
630
+ const result = await parseAll(initialContent);
631
+ blocks = result.blocks;
632
+ lastTree = result.lastTree;
633
+ }
634
+ txCounter = 0;
635
+ postMessage({
636
+ type: "INITIALIZED",
637
+ blocks
638
+ });
639
+ if (blocks.length > 0) {
640
+ await emitDocumentPatch(blocks);
641
+ }
642
+ }
643
+ async function handleAppend(text) {
644
+ let remaining = text;
645
+ let batchStartedAt = now();
646
+ while (remaining.length > 0) {
647
+ const { chunk, rest } = sliceAppendChunk(remaining, APPEND_CHUNK_SIZE);
648
+ remaining = rest;
649
+ performanceTimer.mark("append-operation");
650
+ const metricsCollector = new WorkerMetricsCollector(workerGrammarEngine);
651
+ setActiveMetricsCollector(metricsCollector);
652
+ await appendAndReparse(chunk, metricsCollector);
653
+ const hadPatchMetrics = metricsCollector.patchCount > 0;
654
+ const totalTime = performanceTimer.measure("append-operation");
655
+ if (getActiveMetricsCollector() === metricsCollector) {
656
+ setActiveMetricsCollector(null);
657
+ }
658
+ if (!hadPatchMetrics && totalTime !== null && Number.isFinite(totalTime)) {
659
+ postMessage({
660
+ type: "METRICS",
661
+ metrics: {
662
+ parseMs: roundMetric(totalTime),
663
+ parseTime: roundMetric(totalTime),
664
+ blocksProduced: blocks.length,
665
+ grammarEngine: workerGrammarEngine
666
+ }
667
+ });
668
+ }
669
+ if (remaining.length > 0 && now() - batchStartedAt >= APPEND_BATCH_MS) {
670
+ await waitForNextTick();
671
+ batchStartedAt = now();
672
+ }
673
+ }
674
+ }
675
+ async function parseAll(content, options = {}) {
676
+ performanceTimer.mark("parse-all");
677
+ const metrics = getActiveMetricsCollector();
678
+ metrics?.markParseStart();
679
+ const tree = import_markdown.parser.parse(content);
680
+ let extractedBlocks = await extractBlocks(tree, content, options);
681
+ extractedBlocks = runDocumentPlugins(extractedBlocks, content);
682
+ performanceTimer.measure("parse-all");
683
+ metrics?.markParseEnd();
684
+ metrics?.setBlocksProduced(extractedBlocks.length);
685
+ return {
686
+ blocks: extractedBlocks,
687
+ lastTree: tree
688
+ };
689
+ }
690
+ async function appendAndReparse(appendedText, metrics) {
691
+ performanceTimer.mark("incremental-parse");
692
+ metrics?.markParseStart();
693
+ const newContent = currentContent + appendedText;
694
+ const changeRanges = computeChangedRanges(currentContent, newContent);
695
+ let newTree = lastTree ? import_markdown.parser.parse(newContent, lastTree.fragments) : import_markdown.parser.parse(newContent);
696
+ let changedBlocks = await extractBlocks(newTree, newContent);
697
+ changedBlocks = runDocumentPlugins(changedBlocks, newContent);
698
+ const lastRange = changedBlocks.length > 0 ? changedBlocks[changedBlocks.length - 1]?.payload?.range : void 0;
699
+ const lastTo = typeof lastRange?.to === "number" ? lastRange.to : 0;
700
+ if (lastTo < newContent.length - 1) {
701
+ const tail = newContent.slice(lastTo).trim();
702
+ if (tail.length > 0) {
703
+ const fullTree = import_markdown.parser.parse(newContent);
704
+ let fullBlocks = await extractBlocks(fullTree, newContent);
705
+ fullBlocks = runDocumentPlugins(fullBlocks, newContent);
706
+ const fullLast = fullBlocks.length > 0 ? fullBlocks[fullBlocks.length - 1]?.payload?.range?.to ?? 0 : 0;
707
+ if (fullLast >= newContent.length - 1 || fullBlocks.length >= changedBlocks.length) {
708
+ newTree = fullTree;
709
+ changedBlocks = fullBlocks;
710
+ }
711
+ }
712
+ }
713
+ const prevBlocks = blocks;
714
+ blocks = changedBlocks;
715
+ lastTree = newTree;
716
+ currentContent = newContent;
717
+ pruneLazyTokenizationStates(blocks);
718
+ performanceTimer.measure("incremental-parse");
719
+ metrics?.markParseEnd();
720
+ metrics?.setBlocksProduced(blocks.length);
721
+ await emitBlockDiffPatches(prevBlocks, changedBlocks, changeRanges, metrics);
722
+ }
723
+ async function extractBlocks(tree, content, options = {}) {
724
+ try {
725
+ headingIdCounts = /* @__PURE__ */ new Map();
726
+ const blocks2 = [];
727
+ const cursor = tree.cursor();
728
+ if (cursor.firstChild()) {
729
+ do {
730
+ const nodeType = cursor.type.name;
731
+ const from = cursor.from;
732
+ const to = cursor.to;
733
+ const raw = content.slice(from, to);
734
+ if (isBlockLevelNode(nodeType)) {
735
+ const blockType = mapLezerNodeToBlockType(nodeType);
736
+ const block = await createBlock(blockType, raw, from, to, content, options);
737
+ if (block && raw.trim()) {
738
+ blocks2.push(block);
739
+ }
740
+ }
741
+ } while (cursor.nextSibling());
742
+ }
743
+ if (blocks2.length === 0) {
744
+ return await extractBlocksSimple(content, options);
745
+ }
746
+ return blocks2;
747
+ } catch (error) {
748
+ const details = error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : error;
749
+ console.warn("Lezer parsing failed, falling back to simple parsing:", details);
750
+ return await extractBlocksSimple(content, options);
751
+ }
752
+ }
753
+ async function extractBlocksSimple(content, options = {}) {
754
+ const blocks2 = [];
755
+ const lines = content.split("\n");
756
+ let currentParagraph = [];
757
+ let currentPos = 0;
758
+ const finalizeParagraph = async () => {
759
+ if (currentParagraph.length > 0) {
760
+ const raw = currentParagraph.join("\n");
761
+ const from = currentPos - raw.length;
762
+ const to = currentPos;
763
+ let blockType = "paragraph";
764
+ if (raw.match(/^#{1,6}\s+/)) {
765
+ blockType = "heading";
766
+ } else if (raw.match(/^```/)) {
767
+ blockType = "code";
768
+ } else if (raw.match(/^>/)) {
769
+ blockType = "blockquote";
770
+ } else if (raw.match(/^[-*+]\s+|^\d+\.\s+/)) {
771
+ blockType = "list";
772
+ }
773
+ const block = await createBlock(blockType, raw, from, to, content, options);
774
+ if (block) {
775
+ blocks2.push(block);
776
+ }
777
+ currentParagraph = [];
778
+ }
779
+ };
780
+ for (const line of lines) {
781
+ currentPos += line.length + 1;
782
+ if (line.trim() === "") {
783
+ await finalizeParagraph();
784
+ } else {
785
+ currentParagraph.push(line);
786
+ }
787
+ }
788
+ await finalizeParagraph();
789
+ return blocks2;
790
+ }
791
+ async function createBlock(type, raw, from, to, fullContent, options = {}) {
792
+ if (!raw.trim()) return null;
793
+ const id = (0, import_core.generateBlockId)(`${from}:${type}`, type);
794
+ const isFinalized = options.forceFinalize ? true : to < fullContent.length - 1;
795
+ const block = {
796
+ id,
797
+ type,
798
+ isFinalized,
799
+ payload: {
800
+ raw,
801
+ range: { from, to }
802
+ }
803
+ };
804
+ await enrichBlock(block);
805
+ return block;
806
+ }
807
+ async function enrichBlock(block) {
808
+ performanceTimer.mark("enrich-block");
809
+ const metrics = getActiveMetricsCollector();
810
+ metrics?.countBlockType(block.type);
811
+ const rawLength = typeof block.payload.raw === "string" ? block.payload.raw.length : 0;
812
+ metrics?.recordBlockSize(block.type, rawLength);
813
+ switch (block.type) {
814
+ case "heading": {
815
+ const rawHeading = typeof block.payload.raw === "string" ? block.payload.raw : "";
816
+ const normalizedHeading = (0, import_core.removeHeadingMarkers)(rawHeading);
817
+ const headingLevel = Math.min(Math.max(rawHeading.match(/^#{1,6}/)?.[0].length ?? 1, 1), 6);
818
+ const baseSlug = slugifyHeading(normalizedHeading) || "heading";
819
+ const nextCount = (headingIdCounts.get(baseSlug) ?? 0) + 1;
820
+ headingIdCounts.set(baseSlug, nextCount);
821
+ const headingId = nextCount > 1 ? `${baseSlug}-${nextCount}` : baseSlug;
822
+ block.payload.meta = {
823
+ ...block.payload.meta ?? {},
824
+ headingLevel,
825
+ headingText: normalizedHeading,
826
+ headingId
827
+ };
828
+ if (block.isFinalized) {
829
+ block.payload.raw = normalizedHeading;
830
+ block.payload.inline = inlineParser.parse(normalizedHeading);
831
+ if (shouldTrackInlineStatus() && block.payload.meta) {
832
+ block.payload.meta.inlineStatus = void 0;
833
+ }
834
+ } else {
835
+ const parsed = parseInlineStreaming(normalizedHeading);
836
+ block.payload.inline = parsed.inline;
837
+ if (shouldTrackInlineStatus() && block.payload.meta) {
838
+ block.payload.meta.inlineStatus = parsed.status;
839
+ }
840
+ }
841
+ break;
842
+ }
843
+ case "blockquote": {
844
+ const normalized = (0, import_core.normalizeBlockquoteText)(block.payload.raw ?? "");
845
+ block.payload.raw = normalized;
846
+ const streamingParsed = !block.isFinalized ? parseInlineStreaming(normalized) : null;
847
+ const inlineParse = block.isFinalized ? (value) => inlineParser.parse(value) : (value) => parseInlineStreaming(value).inline;
848
+ const mixedOptions = !block.isFinalized && shouldAllowMixedStreaming() ? {
849
+ html: formatAnticipationConfig.html ? { autoClose: true, maxNewlines: MIXED_CONTENT_AUTOCLOSE_NEWLINES } : void 0,
850
+ mdx: formatAnticipationConfig.mdx ? { autoClose: true, maxNewlines: MIXED_CONTENT_AUTOCLOSE_NEWLINES, componentAllowlist: mdxComponentAllowlist ?? void 0 } : void 0
851
+ } : void 0;
852
+ const segments = (0, import_core.extractMixedContentSegments)(normalized, void 0, inlineParse, mixedOptions);
853
+ const currentMeta = block.payload.meta ?? {};
854
+ const nextMeta = {
855
+ ...currentMeta,
856
+ normalizedText: normalized
857
+ };
858
+ if (segments.length > 0) {
859
+ nextMeta.mixedSegments = segments;
860
+ } else {
861
+ nextMeta.mixedSegments = void 0;
862
+ }
863
+ if (shouldTrackInlineStatus()) {
864
+ nextMeta.inlineStatus = block.isFinalized ? void 0 : streamingParsed?.status;
865
+ }
866
+ if (Object.keys(nextMeta).length > 0) {
867
+ block.payload.meta = nextMeta;
868
+ } else {
869
+ block.payload.meta = void 0;
870
+ }
871
+ if (block.isFinalized) {
872
+ block.payload.inline = inlineParser.parse(normalized);
873
+ } else {
874
+ block.payload.inline = streamingParsed?.inline ?? [{ kind: "text", text: normalized }];
875
+ }
876
+ break;
877
+ }
878
+ case "paragraph": {
879
+ const rawParagraph = typeof block.payload.raw === "string" ? block.payload.raw : "";
880
+ const streamingParsed = !block.isFinalized ? parseInlineStreaming(rawParagraph) : null;
881
+ const inlineParse = block.isFinalized ? (value) => inlineParser.parse(value) : (value) => parseInlineStreaming(value).inline;
882
+ block.payload.inline = block.isFinalized ? inlineParse(rawParagraph) : streamingParsed?.inline ?? [{ kind: "text", text: rawParagraph }];
883
+ const currentMeta = block.payload.meta ?? {};
884
+ const nextMeta = { ...currentMeta };
885
+ let metaChanged = false;
886
+ const allowMixedStreaming = shouldAllowMixedStreaming();
887
+ if (shouldTrackInlineStatus()) {
888
+ const desired = block.isFinalized ? void 0 : streamingParsed?.status;
889
+ if (desired !== void 0) {
890
+ if (!Object.prototype.hasOwnProperty.call(nextMeta, "inlineStatus") || nextMeta.inlineStatus !== desired) {
891
+ nextMeta.inlineStatus = desired;
892
+ metaChanged = true;
893
+ }
894
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "inlineStatus")) {
895
+ nextMeta.inlineStatus = void 0;
896
+ metaChanged = true;
897
+ }
898
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "inlineStatus")) {
899
+ nextMeta.inlineStatus = void 0;
900
+ metaChanged = true;
901
+ }
902
+ const baseOffset = typeof block.payload.range?.from === "number" ? block.payload.range.from : void 0;
903
+ const protectedRanges = [];
904
+ if (enableMath) {
905
+ protectedRanges.push(...collectMathProtectedRanges(rawParagraph));
906
+ }
907
+ const autolinkRanges = collectAutolinkProtectedRanges(rawParagraph);
908
+ if (autolinkRanges.length > 0) {
909
+ protectedRanges.push(...autolinkRanges);
910
+ }
911
+ const shouldExtractSegments = typeof rawParagraph === "string" && (rawParagraph.includes("<") || rawParagraph.includes("{"));
912
+ if (shouldExtractSegments) {
913
+ const mixedOptions = !block.isFinalized && shouldAllowMixedStreaming() ? {
914
+ html: formatAnticipationConfig.html ? { autoClose: true, maxNewlines: MIXED_CONTENT_AUTOCLOSE_NEWLINES } : void 0,
915
+ mdx: formatAnticipationConfig.mdx ? { autoClose: true, maxNewlines: MIXED_CONTENT_AUTOCLOSE_NEWLINES, componentAllowlist: mdxComponentAllowlist ?? void 0 } : void 0
916
+ } : void 0;
917
+ const protectedRangesAbsolute = typeof baseOffset === "number" && protectedRanges.length > 0 ? protectedRanges.map((range) => ({
918
+ ...range,
919
+ from: baseOffset + range.from,
920
+ to: baseOffset + range.to
921
+ })) : void 0;
922
+ const mixedSegmentOptions = mixedOptions || protectedRangesAbsolute ? {
923
+ ...mixedOptions,
924
+ protectedRanges: protectedRangesAbsolute
925
+ } : void 0;
926
+ const segments = (0, import_core.extractMixedContentSegments)(rawParagraph, baseOffset, (value) => inlineParse(value), mixedSegmentOptions);
927
+ const htmlRanges = collectHtmlProtectedRangesFromSegments(segments, baseOffset);
928
+ if (htmlRanges.length > 0) {
929
+ protectedRanges.push(...htmlRanges);
930
+ }
931
+ if (segments.length > 0) {
932
+ nextMeta.mixedSegments = segments;
933
+ metaChanged = true;
934
+ if (allowMixedStreaming) {
935
+ nextMeta.allowMixedStreaming = true;
936
+ metaChanged = true;
937
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "allowMixedStreaming")) {
938
+ nextMeta.allowMixedStreaming = void 0;
939
+ metaChanged = true;
940
+ }
941
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "mixedSegments")) {
942
+ nextMeta.mixedSegments = void 0;
943
+ metaChanged = true;
944
+ if (Object.prototype.hasOwnProperty.call(nextMeta, "allowMixedStreaming")) {
945
+ nextMeta.allowMixedStreaming = void 0;
946
+ metaChanged = true;
947
+ }
948
+ }
949
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "mixedSegments")) {
950
+ nextMeta.mixedSegments = void 0;
951
+ metaChanged = true;
952
+ if (Object.prototype.hasOwnProperty.call(nextMeta, "allowMixedStreaming")) {
953
+ nextMeta.allowMixedStreaming = void 0;
954
+ metaChanged = true;
955
+ }
956
+ }
957
+ if (protectedRanges.length > 0) {
958
+ nextMeta.protectedRanges = protectedRanges;
959
+ metaChanged = true;
960
+ } else if (Object.prototype.hasOwnProperty.call(nextMeta, "protectedRanges")) {
961
+ nextMeta.protectedRanges = void 0;
962
+ metaChanged = true;
963
+ }
964
+ if (metaChanged) {
965
+ if (Object.keys(nextMeta).length > 0) {
966
+ block.payload.meta = nextMeta;
967
+ } else {
968
+ block.payload.meta = void 0;
969
+ }
970
+ } else if (!block.payload.meta || Object.keys(block.payload.meta).length === 0) {
971
+ block.payload.meta = void 0;
972
+ }
973
+ break;
974
+ }
975
+ case "code":
976
+ await enrichCodeBlock(block);
977
+ break;
978
+ case "html": {
979
+ const rawHtml = typeof block.payload.raw === "string" ? block.payload.raw : "";
980
+ const sanitized = (0, import_core2.sanitizeHtmlInWorker)(rawHtml);
981
+ block.payload.sanitizedHtml = sanitized;
982
+ const nextMeta = { ...block.payload.meta || {}, sanitized: true };
983
+ if (rawHtml) {
984
+ nextMeta.protectedRanges = [{ from: 0, to: rawHtml.length, kind: "html-block" }];
985
+ }
986
+ block.payload.meta = nextMeta;
987
+ break;
988
+ }
989
+ case "list":
990
+ enrichListBlock(block);
991
+ break;
992
+ }
993
+ if (block.type === "paragraph" || block.type === "html") {
994
+ const maybeMeta = block.payload.meta;
995
+ const protectedRanges = Array.isArray(maybeMeta?.protectedRanges) ? maybeMeta?.protectedRanges : void 0;
996
+ const blockRange = block.payload.range;
997
+ const baseOffset = typeof blockRange?.from === "number" ? blockRange.from : 0;
998
+ const normalizedRanges = protectedRanges && protectedRanges.length > 0 ? protectedRanges.map((range) => ({
999
+ ...range,
1000
+ from: baseOffset + range.from,
1001
+ to: baseOffset + range.to
1002
+ })) : void 0;
1003
+ const mdxOptions = normalizedRanges && normalizedRanges.length > 0 ? { protectedRanges: normalizedRanges, baseOffset } : baseOffset ? { baseOffset } : void 0;
1004
+ const mdxDetectStart = now();
1005
+ let shouldConvertToMDX = (0, import_core.detectMDX)(block.payload.raw, mdxOptions);
1006
+ if (!shouldConvertToMDX && block.type === "html") {
1007
+ const fallbackOptions = baseOffset ? { baseOffset } : void 0;
1008
+ shouldConvertToMDX = (0, import_core.detectMDX)(block.payload.raw, fallbackOptions);
1009
+ }
1010
+ metrics?.recordMdxDetect(now() - mdxDetectStart);
1011
+ if (shouldConvertToMDX && protectedRanges && protectedRanges.length > 0 && block.type !== "html") {
1012
+ const exprPattern = /\{[^{}]+\}/g;
1013
+ let match;
1014
+ while (true) {
1015
+ match = exprPattern.exec(block.payload.raw);
1016
+ if (match === null) {
1017
+ break;
1018
+ }
1019
+ const start = match.index;
1020
+ const end = start + match[0].length;
1021
+ const covered = protectedRanges.some((range) => range.from <= start && range.to >= end);
1022
+ if (!covered) {
1023
+ shouldConvertToMDX = true;
1024
+ break;
1025
+ }
1026
+ shouldConvertToMDX = false;
1027
+ }
1028
+ }
1029
+ if (shouldConvertToMDX) {
1030
+ if (DEBUG_MDX) {
1031
+ try {
1032
+ console.debug("[markdown-worker] mdx detected", { blockId: block.id, originalType: block.type });
1033
+ } catch {
1034
+ }
1035
+ }
1036
+ const originalType = block.type;
1037
+ block.payload.meta = { ...block.payload.meta, originalType };
1038
+ block.type = "mdx";
1039
+ } else if (DEBUG_MDX && loggedMdxSkipCount < MAX_MDX_SKIP_LOGS) {
1040
+ if (block.payload.raw && block.payload.raw.includes("<Preview")) {
1041
+ loggedMdxSkipCount += 1;
1042
+ try {
1043
+ console.debug("[markdown-worker] mdx detection skipped", {
1044
+ blockId: block.id,
1045
+ originalType: block.type,
1046
+ len: block.payload.raw.length,
1047
+ protected: Array.isArray(block.payload.meta?.protectedRanges) ? block.payload.meta.protectedRanges.length : 0,
1048
+ sample: block.payload.raw.slice(0, 120)
1049
+ });
1050
+ } catch {
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+ await updateMdxCompilationState(block);
1056
+ const enrichDuration = performanceTimer.measure("enrich-block");
1057
+ metrics?.recordEnrich(enrichDuration);
1058
+ metrics?.recordBlockEnrich(block.type, enrichDuration);
1059
+ }
1060
+ function collectMathProtectedRanges(content) {
1061
+ if (!content) return [];
1062
+ const ranges = [];
1063
+ const inlineStack = [];
1064
+ const displayStack = [];
1065
+ const length = content.length;
1066
+ let i = 0;
1067
+ while (i < length) {
1068
+ const char = content[i];
1069
+ if (char === "\\") {
1070
+ i += 2;
1071
+ continue;
1072
+ }
1073
+ if (char !== "$") {
1074
+ i++;
1075
+ continue;
1076
+ }
1077
+ const isDouble = i + 1 < length && content[i + 1] === "$";
1078
+ if (isDouble) {
1079
+ if (displayStack.length > 0) {
1080
+ const start = displayStack.pop();
1081
+ ranges.push({ from: start, to: i + 2, kind: "math-display" });
1082
+ } else {
1083
+ displayStack.push(i);
1084
+ }
1085
+ i += 2;
1086
+ continue;
1087
+ }
1088
+ if (inlineStack.length > 0) {
1089
+ const start = inlineStack.pop();
1090
+ ranges.push({ from: start, to: i + 1, kind: "math-inline" });
1091
+ } else {
1092
+ inlineStack.push(i);
1093
+ }
1094
+ i++;
1095
+ }
1096
+ return ranges;
1097
+ }
1098
+ function collectAutolinkProtectedRanges(content) {
1099
+ if (!content) return [];
1100
+ const ranges = [];
1101
+ const autolinkPattern = /<((?:https?:\/\/|mailto:)[^>\s]+)>/gi;
1102
+ let match = autolinkPattern.exec(content);
1103
+ while (match !== null) {
1104
+ ranges.push({ from: match.index, to: match.index + match[0].length, kind: "autolink" });
1105
+ match = autolinkPattern.exec(content);
1106
+ }
1107
+ return ranges;
1108
+ }
1109
+ function collectHtmlProtectedRangesFromSegments(segments, baseOffset) {
1110
+ if (!segments || segments.length === 0) return [];
1111
+ if (typeof baseOffset !== "number" || !Number.isFinite(baseOffset)) {
1112
+ return [];
1113
+ }
1114
+ const ranges = [];
1115
+ for (const segment of segments) {
1116
+ if (segment.kind !== "html") continue;
1117
+ const range = segment.range;
1118
+ if (!range) continue;
1119
+ const from = range.from - baseOffset;
1120
+ const to = range.to - baseOffset;
1121
+ if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) continue;
1122
+ ranges.push({ from, to, kind: "html-inline" });
1123
+ }
1124
+ return ranges;
1125
+ }
1126
+ function escapeHtmlText(text) {
1127
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1128
+ }
1129
+ function renderShikiToken(token) {
1130
+ const dark = token.variants.dark;
1131
+ const light = token.variants.light;
1132
+ const styles = [];
1133
+ if (dark?.color) {
1134
+ styles.push(`--shiki-dark:${dark.color}`);
1135
+ }
1136
+ if (light?.color) {
1137
+ styles.push(`--shiki-light:${light.color}`);
1138
+ }
1139
+ const fontStyle = light?.fontStyle ?? dark?.fontStyle;
1140
+ if (typeof fontStyle === "number" && fontStyle > 0) {
1141
+ if (fontStyle & FONT_STYLE_ITALIC) {
1142
+ styles.push("font-style: italic");
1143
+ }
1144
+ if (fontStyle & FONT_STYLE_BOLD) {
1145
+ styles.push("font-weight: bold");
1146
+ }
1147
+ const decorations = [];
1148
+ if (fontStyle & FONT_STYLE_UNDERLINE) {
1149
+ decorations.push("underline");
1150
+ }
1151
+ if (fontStyle & FONT_STYLE_STRIKETHROUGH) {
1152
+ decorations.push("line-through");
1153
+ }
1154
+ if (decorations.length > 0) {
1155
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
1156
+ }
1157
+ }
1158
+ const styleAttr = styles.length > 0 ? ` style="${styles.join(";")}"` : "";
1159
+ return `<span${styleAttr}>${escapeHtmlText(token.content)}</span>`;
1160
+ }
1161
+ function getTokenFontStyle(token) {
1162
+ const dark = token.variants?.dark?.fontStyle ?? 0;
1163
+ const light = token.variants?.light?.fontStyle ?? 0;
1164
+ return (typeof dark === "number" ? dark : 0) | (typeof light === "number" ? light : 0);
1165
+ }
1166
+ function mergeWhitespaceTokens(tokens) {
1167
+ return tokens.map((line) => {
1168
+ const merged = [];
1169
+ let carryContent = "";
1170
+ let carryOffset = 0;
1171
+ let carryVariants = null;
1172
+ line.forEach((token, idx) => {
1173
+ const fontStyle = getTokenFontStyle(token);
1174
+ const couldMerge = (fontStyle & FONT_STYLE_UNDERLINE) === 0;
1175
+ if (couldMerge && /^\s+$/.test(token.content) && line[idx + 1]) {
1176
+ if (!carryContent) {
1177
+ carryOffset = token.offset ?? 0;
1178
+ carryVariants = token.variants;
1179
+ }
1180
+ carryContent += token.content;
1181
+ return;
1182
+ }
1183
+ if (carryContent) {
1184
+ if (couldMerge) {
1185
+ merged.push({ ...token, offset: carryOffset, content: carryContent + token.content });
1186
+ } else {
1187
+ merged.push({ content: carryContent, offset: carryOffset, variants: carryVariants ?? token.variants });
1188
+ merged.push(token);
1189
+ }
1190
+ carryContent = "";
1191
+ carryOffset = 0;
1192
+ carryVariants = null;
1193
+ return;
1194
+ }
1195
+ merged.push(token);
1196
+ });
1197
+ return merged;
1198
+ });
1199
+ }
1200
+ function renderShikiLines(tokens) {
1201
+ const merged = mergeWhitespaceTokens(tokens);
1202
+ return merged.map((lineTokens) => lineTokens.map(renderShikiToken).join(""));
1203
+ }
1204
+ function detectDiffLanguage(lang, meta) {
1205
+ const normalized = (lang || "").trim().toLowerCase();
1206
+ if (!normalized) {
1207
+ return { isDiff: false, diffLang: "diff", baseLang: null };
1208
+ }
1209
+ let baseLang = null;
1210
+ if (normalized === "diff" || normalized === "udiff" || normalized === "diff-unified") {
1211
+ baseLang = null;
1212
+ } else if (normalized.startsWith("diff-")) {
1213
+ baseLang = normalized.slice("diff-".length) || null;
1214
+ } else if (normalized.endsWith("-diff")) {
1215
+ baseLang = normalized.slice(0, Math.max(0, normalized.length - "-diff".length)) || null;
1216
+ } else {
1217
+ return { isDiff: false, diffLang: "diff", baseLang: null };
1218
+ }
1219
+ if (!baseLang) {
1220
+ const metaLang = typeof meta.lang === "string" ? meta.lang : typeof meta.language === "string" ? meta.language : typeof meta.base === "string" ? meta.base : null;
1221
+ if (metaLang) {
1222
+ baseLang = metaLang;
1223
+ } else {
1224
+ const metaKeys = Object.keys(meta);
1225
+ if (metaKeys.length > 0) {
1226
+ baseLang = metaKeys[0] ?? null;
1227
+ }
1228
+ }
1229
+ }
1230
+ return {
1231
+ isDiff: true,
1232
+ diffLang: "diff",
1233
+ baseLang: baseLang ? (0, import_core.normalizeLang)(String(baseLang)) : null
1234
+ };
1235
+ }
1236
+ function parseDiffHunkHeader(line) {
1237
+ const match = line.match(/^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/);
1238
+ if (!match) return null;
1239
+ return {
1240
+ oldStart: Number.parseInt(match[1] ?? "0", 10),
1241
+ newStart: Number.parseInt(match[3] ?? "0", 10)
1242
+ };
1243
+ }
1244
+ function isDiffMetaLine(line) {
1245
+ if (!line) return false;
1246
+ return line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("new file") || line.startsWith("deleted file") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to") || line.startsWith("mode ") || line.startsWith("\\\\ No newline");
1247
+ }
1248
+ function parseUnifiedDiffLine(line, cursor) {
1249
+ if (line.startsWith("@@")) {
1250
+ const header = parseDiffHunkHeader(line);
1251
+ if (header) {
1252
+ cursor.oldLine = header.oldStart;
1253
+ cursor.newLine = header.newStart;
1254
+ }
1255
+ return { text: line, kind: "hunk", prefix: "", content: line, oldNo: null, newNo: null };
1256
+ }
1257
+ if (line.startsWith("--- ") || line.startsWith("+++ ") || isDiffMetaLine(line)) {
1258
+ return { text: line, kind: "meta", prefix: "", content: line, oldNo: null, newNo: null };
1259
+ }
1260
+ if (line.startsWith("+")) {
1261
+ if (line.startsWith("+++")) {
1262
+ return { text: line, kind: "meta", prefix: "", content: line, oldNo: null, newNo: null };
1263
+ }
1264
+ const newNo = cursor.newLine;
1265
+ if (cursor.newLine !== null) cursor.newLine += 1;
1266
+ return { text: line, kind: "add", prefix: "+", content: line.slice(1), oldNo: null, newNo };
1267
+ }
1268
+ if (line.startsWith("-")) {
1269
+ if (line.startsWith("---")) {
1270
+ return { text: line, kind: "meta", prefix: "", content: line, oldNo: null, newNo: null };
1271
+ }
1272
+ const oldNo = cursor.oldLine;
1273
+ if (cursor.oldLine !== null) cursor.oldLine += 1;
1274
+ return { text: line, kind: "remove", prefix: "-", content: line.slice(1), oldNo, newNo: null };
1275
+ }
1276
+ if (line.startsWith(" ")) {
1277
+ const oldNo = cursor.oldLine;
1278
+ const newNo = cursor.newLine;
1279
+ if (cursor.oldLine !== null) cursor.oldLine += 1;
1280
+ if (cursor.newLine !== null) cursor.newLine += 1;
1281
+ return { text: line, kind: "context", prefix: " ", content: line.slice(1), oldNo, newNo };
1282
+ }
1283
+ return { text: line, kind: "meta", prefix: "", content: line, oldNo: null, newNo: null };
1284
+ }
1285
+ function normalizeDiffPath(value) {
1286
+ if (!value) return null;
1287
+ let path = value.trim();
1288
+ if (!path || path === "/dev/null") return null;
1289
+ if (path.startsWith('"') && path.endsWith('"') || path.startsWith("'") && path.endsWith("'")) {
1290
+ path = path.slice(1, -1);
1291
+ }
1292
+ if (path.startsWith("a/") || path.startsWith("b/")) {
1293
+ path = path.slice(2);
1294
+ }
1295
+ return path || null;
1296
+ }
1297
+ function extractDiffFilePath(line) {
1298
+ if (line.startsWith("diff --git")) {
1299
+ const match = line.match(/^diff --git\\s+(\\S+)\\s+(\\S+)/);
1300
+ if (match) {
1301
+ return normalizeDiffPath(match[2] ?? match[1] ?? null);
1302
+ }
1303
+ }
1304
+ if (line.startsWith("+++ ") || line.startsWith("--- ")) {
1305
+ const path = normalizeDiffPath(line.slice(4));
1306
+ return path;
1307
+ }
1308
+ if (line.startsWith("rename to ")) {
1309
+ return normalizeDiffPath(line.slice("rename to ".length));
1310
+ }
1311
+ return null;
1312
+ }
1313
+ function looksLikeUnifiedDiff(lines) {
1314
+ if (lines.length === 0) return false;
1315
+ for (const line of lines) {
1316
+ if (line.startsWith("diff --git") || line.startsWith("@@") || line.startsWith("+++ ") || line.startsWith("--- ")) {
1317
+ return true;
1318
+ }
1319
+ }
1320
+ return false;
1321
+ }
1322
+ function guessLanguageFromPath(filePath) {
1323
+ if (!filePath) return null;
1324
+ const parts = filePath.split(/[\\\\/]/);
1325
+ const file = parts[parts.length - 1] ?? "";
1326
+ const idx = file.lastIndexOf(".");
1327
+ if (idx <= 0 || idx >= file.length - 1) return null;
1328
+ const ext = file.slice(idx + 1);
1329
+ return (0, import_core.normalizeLang)(ext);
1330
+ }
1331
+ function guessLanguageFromDiffLines(lines) {
1332
+ for (const line of lines) {
1333
+ const filePath = extractDiffFilePath(line);
1334
+ if (filePath) {
1335
+ const guessed = guessLanguageFromPath(filePath);
1336
+ if (guessed) return guessed;
1337
+ }
1338
+ }
1339
+ return null;
1340
+ }
1341
+ function toDiffLineKind(kind) {
1342
+ if (kind === "remove") return "del";
1343
+ return kind;
1344
+ }
1345
+ function toThemedLine(tokenLine, theme) {
1346
+ if (!tokenLine) return null;
1347
+ return tokenLine.spans.map((span) => {
1348
+ const style = span.s ?? span.v?.[theme];
1349
+ const color = typeof style?.fg === "string" ? style.fg : null;
1350
+ const fontStyle = typeof style?.fs === "number" ? style.fs : null;
1351
+ return {
1352
+ content: span.t,
1353
+ color,
1354
+ fontStyle
1355
+ };
1356
+ });
1357
+ }
1358
+ function stripPrefixFromThemedLine(tokens, prefix) {
1359
+ if (!tokens || !prefix) return tokens;
1360
+ if (tokens.length === 0) return tokens;
1361
+ const first = tokens[0];
1362
+ if (first.content === prefix) {
1363
+ return tokens.slice(1);
1364
+ }
1365
+ if (first.content.startsWith(prefix)) {
1366
+ const trimmed = first.content.slice(prefix.length);
1367
+ const next = trimmed ? [{ ...first, content: trimmed }, ...tokens.slice(1)] : tokens.slice(1);
1368
+ return next;
1369
+ }
1370
+ return tokens;
1371
+ }
1372
+ function buildDiffBlocksFromLines(lines, rawLines, tokenLines, defaultLanguage) {
1373
+ const blocks2 = [];
1374
+ let current = null;
1375
+ let currentRaw = [];
1376
+ let additions = 0;
1377
+ let deletions = 0;
1378
+ const finalize = () => {
1379
+ if (!current) return;
1380
+ current.additions = additions;
1381
+ current.deletions = deletions;
1382
+ if (currentRaw.length > 0) {
1383
+ current.unified = currentRaw.join("\n");
1384
+ }
1385
+ blocks2.push(current);
1386
+ current = null;
1387
+ currentRaw = [];
1388
+ additions = 0;
1389
+ deletions = 0;
1390
+ };
1391
+ for (let i = 0; i < lines.length; i++) {
1392
+ const line = lines[i];
1393
+ const raw = rawLines[i] ?? line.text;
1394
+ const filePath = extractDiffFilePath(raw);
1395
+ if (raw.startsWith("diff --git")) {
1396
+ finalize();
1397
+ }
1398
+ if (!current) {
1399
+ current = {
1400
+ kind: "diff",
1401
+ filePath: filePath ?? null,
1402
+ language: filePath ? guessLanguageFromPath(filePath) ?? defaultLanguage : defaultLanguage,
1403
+ lines: [],
1404
+ additions: null,
1405
+ deletions: null,
1406
+ unified: null
1407
+ };
1408
+ } else if (filePath && !current.filePath) {
1409
+ current.filePath = filePath;
1410
+ if (!current.language) {
1411
+ current.language = guessLanguageFromPath(filePath) ?? defaultLanguage;
1412
+ }
1413
+ }
1414
+ if (line.kind === "add") additions += 1;
1415
+ if (line.kind === "remove") deletions += 1;
1416
+ let themed = tokenLines ? toThemedLine(tokenLines[i] ?? null, "dark") : null;
1417
+ if (line.kind === "add" || line.kind === "remove" || line.kind === "context") {
1418
+ themed = stripPrefixFromThemedLine(themed, line.prefix);
1419
+ }
1420
+ const entry = {
1421
+ kind: toDiffLineKind(line.kind),
1422
+ oldNo: line.oldNo ?? null,
1423
+ newNo: line.newNo ?? null,
1424
+ raw,
1425
+ tokens: themed ?? null
1426
+ };
1427
+ current.lines.push(entry);
1428
+ currentRaw.push(raw);
1429
+ }
1430
+ finalize();
1431
+ return blocks2;
1432
+ }
1433
+ function tokenizeDiffRun(lines, language, grammarState) {
1434
+ if (!highlighter) {
1435
+ return { tokens: [], nextGrammarState: grammarState };
1436
+ }
1437
+ const content = lines.map((line) => line.content).join("\\n");
1438
+ if (!content && lines.length > 0) {
1439
+ return { tokens: lines.map(() => []), nextGrammarState: grammarState };
1440
+ }
1441
+ const tokens = highlighter.codeToTokensWithThemes(content, {
1442
+ lang: language,
1443
+ themes: CODE_HIGHLIGHT_THEMES,
1444
+ grammarState
1445
+ });
1446
+ const nextGrammarState = highlighter.getLastGrammarState(tokens);
1447
+ return { tokens, nextGrammarState };
1448
+ }
1449
+ function buildDiffTokenLines(lines, language, grammarState) {
1450
+ const output = [];
1451
+ let idx = 0;
1452
+ let nextOldState = grammarState.old;
1453
+ let nextNewState = grammarState.new;
1454
+ while (idx < lines.length) {
1455
+ const line = lines[idx];
1456
+ if (line.kind === "meta" || line.kind === "hunk") {
1457
+ output.push({ spans: [{ t: line.text }] });
1458
+ idx += 1;
1459
+ continue;
1460
+ }
1461
+ const runKind = line.kind;
1462
+ const run = [];
1463
+ while (idx < lines.length && lines[idx].kind === runKind) {
1464
+ run.push(lines[idx]);
1465
+ idx += 1;
1466
+ }
1467
+ try {
1468
+ if (runKind === "add") {
1469
+ const { tokens, nextGrammarState } = tokenizeDiffRun(run, language, nextNewState);
1470
+ nextNewState = nextGrammarState;
1471
+ const tokenLines = renderTokenLines(tokens);
1472
+ for (let i = 0; i < run.length; i++) {
1473
+ const prefixSpan = { t: run[i].prefix };
1474
+ const lineTokens = tokenLines[i] ?? null;
1475
+ if (!lineTokens || lineTokens.spans.length === 0) {
1476
+ const fullText = `${run[i].prefix}${run[i].content}`;
1477
+ output.push(fullText.length > 0 ? { spans: [{ t: fullText }] } : { spans: [] });
1478
+ } else {
1479
+ output.push({ spans: [prefixSpan, ...lineTokens.spans] });
1480
+ }
1481
+ }
1482
+ continue;
1483
+ }
1484
+ if (runKind === "remove") {
1485
+ const { tokens, nextGrammarState } = tokenizeDiffRun(run, language, nextOldState);
1486
+ nextOldState = nextGrammarState;
1487
+ const tokenLines = renderTokenLines(tokens);
1488
+ for (let i = 0; i < run.length; i++) {
1489
+ const prefixSpan = { t: run[i].prefix };
1490
+ const lineTokens = tokenLines[i] ?? null;
1491
+ if (!lineTokens || lineTokens.spans.length === 0) {
1492
+ const fullText = `${run[i].prefix}${run[i].content}`;
1493
+ output.push(fullText.length > 0 ? { spans: [{ t: fullText }] } : { spans: [] });
1494
+ } else {
1495
+ output.push({ spans: [prefixSpan, ...lineTokens.spans] });
1496
+ }
1497
+ }
1498
+ continue;
1499
+ }
1500
+ if (runKind === "context") {
1501
+ const { tokens, nextGrammarState } = tokenizeDiffRun(run, language, nextNewState);
1502
+ nextNewState = nextGrammarState;
1503
+ if (run.length > 0) {
1504
+ const oldResult = tokenizeDiffRun(run, language, nextOldState);
1505
+ nextOldState = oldResult.nextGrammarState;
1506
+ }
1507
+ const tokenLines = renderTokenLines(tokens);
1508
+ for (let i = 0; i < run.length; i++) {
1509
+ const prefixSpan = { t: run[i].prefix };
1510
+ const lineTokens = tokenLines[i] ?? null;
1511
+ if (!lineTokens || lineTokens.spans.length === 0) {
1512
+ const fullText = `${run[i].prefix}${run[i].content}`;
1513
+ output.push(fullText.length > 0 ? { spans: [{ t: fullText }] } : { spans: [] });
1514
+ } else {
1515
+ output.push({ spans: [prefixSpan, ...lineTokens.spans] });
1516
+ }
1517
+ }
1518
+ continue;
1519
+ }
1520
+ } catch (error) {
1521
+ console.warn("Diff tokenization failed for", language, error);
1522
+ }
1523
+ for (let i = 0; i < run.length; i++) {
1524
+ const fullText = run[i].text;
1525
+ output.push(fullText.length > 0 ? { spans: [{ t: fullText }] } : { spans: [] });
1526
+ }
1527
+ }
1528
+ return { tokenLines: output, grammarState: { old: nextOldState, new: nextNewState } };
1529
+ }
1530
+ function toTokenStyle(style) {
1531
+ if (!style) return void 0;
1532
+ const fg = typeof style.color === "string" ? style.color : void 0;
1533
+ const fs = typeof style.fontStyle === "number" && style.fontStyle > 0 ? style.fontStyle : void 0;
1534
+ if (!fg && fs === void 0) return void 0;
1535
+ return { fg, fs };
1536
+ }
1537
+ function toTokenSpan(token) {
1538
+ const dark = toTokenStyle(token.variants.dark);
1539
+ const light = toTokenStyle(token.variants.light);
1540
+ const span = { t: token.content };
1541
+ if (dark || light) {
1542
+ span.v = {};
1543
+ if (dark) span.v.dark = dark;
1544
+ if (light) span.v.light = light;
1545
+ }
1546
+ return span;
1547
+ }
1548
+ function renderTokenLines(tokens) {
1549
+ return tokens.map((lineTokens) => ({
1550
+ spans: lineTokens.map((token) => toTokenSpan(token))
1551
+ }));
1552
+ }
1553
+ function resetIncrementalHighlightState(blockId) {
1554
+ if (blockId) {
1555
+ incrementalHighlightStates.delete(blockId);
1556
+ return;
1557
+ }
1558
+ incrementalHighlightStates.clear();
1559
+ }
1560
+ function getIncrementalHighlightState(blockId, lang) {
1561
+ const existing = incrementalHighlightStates.get(blockId);
1562
+ if (!existing || existing.lang !== lang) {
1563
+ const fresh = {
1564
+ lang,
1565
+ tokenLang: void 0,
1566
+ processedLength: 0,
1567
+ pendingLine: "",
1568
+ highlightedLines: [],
1569
+ tokenLines: [],
1570
+ diffKind: [],
1571
+ oldNo: [],
1572
+ newNo: [],
1573
+ diffCursor: void 0,
1574
+ diffGrammar: void 0,
1575
+ grammarState: void 0
1576
+ };
1577
+ incrementalHighlightStates.set(blockId, fresh);
1578
+ return fresh;
1579
+ }
1580
+ return existing;
1581
+ }
1582
+ async function resolveHighlightLanguage(requestedLanguage) {
1583
+ if (!highlighter) return "text";
1584
+ const loadedLangs = highlighter.getLoadedLanguages();
1585
+ if (!loadedLangs.includes(requestedLanguage)) {
1586
+ try {
1587
+ await highlighter.loadLanguage(requestedLanguage);
1588
+ } catch (loadError) {
1589
+ console.warn(`Failed to load language ${requestedLanguage}, falling back to text:`, loadError);
1590
+ }
1591
+ }
1592
+ const nextLangs = highlighter.getLoadedLanguages();
1593
+ return nextLangs.includes(requestedLanguage) ? requestedLanguage : "text";
1594
+ }
1595
+ function makeLazySignature(codeBody, lang, tokenLang, diffEnabled) {
1596
+ const head = codeBody.slice(0, 32);
1597
+ const tail = codeBody.slice(Math.max(0, codeBody.length - 32));
1598
+ return `${lang}|${tokenLang}|${diffEnabled ? "diff" : "plain"}|${codeBody.length}|${head}|${tail}`;
1599
+ }
1600
+ function getOrInitLazyState(blockId, signature, lineCount, lang, tokenLang, diffEnabled) {
1601
+ const existing = lazyTokenizationStates.get(blockId);
1602
+ if (existing && existing.signature === signature && existing.highlightedLines.length === lineCount) {
1603
+ return existing;
1604
+ }
1605
+ const fresh = {
1606
+ signature,
1607
+ lang,
1608
+ tokenLang,
1609
+ processedLines: 0,
1610
+ highlightedLines: new Array(lineCount).fill(null),
1611
+ tokenLines: new Array(lineCount).fill(null),
1612
+ diffKind: new Array(lineCount).fill(null),
1613
+ oldNo: new Array(lineCount).fill(null),
1614
+ newNo: new Array(lineCount).fill(null),
1615
+ diffCursor: diffEnabled ? { oldLine: null, newLine: null } : void 0,
1616
+ diffGrammar: diffEnabled ? {} : void 0,
1617
+ grammarState: void 0
1618
+ };
1619
+ lazyTokenizationStates.set(blockId, fresh);
1620
+ return fresh;
1621
+ }
1622
+ function shouldLazyTokenizeBlock(lineCount, hasHighlighter, hasHighlightableContent) {
1623
+ if (!lazyTokenizationEnabled) return false;
1624
+ if (!hasHighlighter || !hasHighlightableContent) return false;
1625
+ return lineCount >= lazyTokenizationThresholdLines;
1626
+ }
1627
+ function pruneLazyTokenizationStates(nextBlocks) {
1628
+ if (lazyTokenizationStates.size === 0) return;
1629
+ const ids = new Set(nextBlocks.map((block) => block.id));
1630
+ for (const key of lazyTokenizationStates.keys()) {
1631
+ if (!ids.has(key)) {
1632
+ lazyTokenizationStates.delete(key);
1633
+ }
1634
+ }
1635
+ }
1636
+ function enqueueLazyTokenization(request) {
1637
+ if (!lazyTokenizationEnabled) return;
1638
+ const existing = lazyTokenizationQueue.get(request.blockId);
1639
+ const next = existing ? mergeLazyRequests(existing, request) : request;
1640
+ lazyTokenizationQueue.set(request.blockId, next);
1641
+ if (!lazyTokenizationScheduled) {
1642
+ lazyTokenizationScheduled = true;
1643
+ setTimeout(() => {
1644
+ lazyTokenizationScheduled = false;
1645
+ void processLazyTokenizationQueue();
1646
+ }, 0);
1647
+ }
1648
+ }
1649
+ function selectNextLazyRequest() {
1650
+ let selected = null;
1651
+ for (const request of lazyTokenizationQueue.values()) {
1652
+ if (!selected) {
1653
+ selected = request;
1654
+ continue;
1655
+ }
1656
+ const priorityDiff = compareLazyPriority(request.priority, selected.priority);
1657
+ if (priorityDiff > 0) {
1658
+ selected = request;
1659
+ continue;
1660
+ }
1661
+ if (priorityDiff === 0 && request.requestedAt < selected.requestedAt) {
1662
+ selected = request;
1663
+ }
1664
+ }
1665
+ return selected;
1666
+ }
1667
+ async function processLazyTokenizationQueue() {
1668
+ if (lazyTokenizationProcessing) return;
1669
+ lazyTokenizationProcessing = true;
1670
+ try {
1671
+ while (lazyTokenizationQueue.size > 0) {
1672
+ const next = selectNextLazyRequest();
1673
+ if (!next) break;
1674
+ lazyTokenizationQueue.delete(next.blockId);
1675
+ await handleLazyTokenizationRequest(next);
1676
+ }
1677
+ } finally {
1678
+ lazyTokenizationProcessing = false;
1679
+ }
1680
+ }
1681
+ async function handleLazyTokenizationRequest(request) {
1682
+ if (!lazyTokenizationEnabled || !highlighter) return;
1683
+ const index = blocks.findIndex((block2) => block2.id === request.blockId);
1684
+ if (index === -1) return;
1685
+ const block = blocks[index];
1686
+ if (block.type !== "code" || !block.isFinalized) return;
1687
+ const raw = block.payload.raw ?? "";
1688
+ const { code, info, hadFence } = (0, import_core2.stripCodeFence)(raw);
1689
+ const { lang, meta } = (0, import_core.parseCodeFenceInfo)(info);
1690
+ const codeBody = hadFence ? code : (0, import_core2.dedentIndentedCode)(raw);
1691
+ const codeLines = (0, import_core.extractCodeLines)(raw);
1692
+ let diffInfo = detectDiffLanguage(lang, meta);
1693
+ if (!diffInfo.isDiff && emitDiffBlocks && looksLikeUnifiedDiff(codeLines)) {
1694
+ diffInfo = {
1695
+ isDiff: true,
1696
+ diffLang: "diff",
1697
+ baseLang: guessLanguageFromDiffLines(codeLines)
1698
+ };
1699
+ }
1700
+ const requestedLanguage = diffInfo.isDiff ? diffInfo.diffLang : lang || "text";
1701
+ const tokenLanguage = diffInfo.isDiff ? diffInfo.baseLang ?? "text" : requestedLanguage;
1702
+ const hasHighlightableContent = codeBody.trim().length > 0;
1703
+ if (!shouldLazyTokenizeBlock(codeLines.length, Boolean(highlighter), hasHighlightableContent)) {
1704
+ return;
1705
+ }
1706
+ const wantsHtml = highlightOutputMode === "html" || highlightOutputMode === "both";
1707
+ const wantsTokens = (highlightOutputMode === "tokens" || highlightOutputMode === "both") && emitHighlightTokens;
1708
+ const diffEnabled = diffInfo.isDiff && wantsTokens;
1709
+ if (!wantsHtml && !wantsTokens) return;
1710
+ const { startLine, endLine } = clampLazyRange(request.startLine, request.endLine, codeLines.length);
1711
+ if (endLine <= startLine) return;
1712
+ let resolvedLanguage = requestedLanguage;
1713
+ let resolvedTokenLanguage = tokenLanguage;
1714
+ if (wantsHtml) {
1715
+ resolvedLanguage = await resolveHighlightLanguage(requestedLanguage);
1716
+ }
1717
+ if (wantsTokens) {
1718
+ resolvedTokenLanguage = await resolveHighlightLanguage(tokenLanguage);
1719
+ }
1720
+ const signature = makeLazySignature(codeBody, resolvedLanguage, resolvedTokenLanguage, diffEnabled);
1721
+ const state = getOrInitLazyState(block.id, signature, codeLines.length, resolvedLanguage, resolvedTokenLanguage, diffEnabled);
1722
+ if (state.processedLines < 0 || state.processedLines > codeLines.length) {
1723
+ state.processedLines = 0;
1724
+ }
1725
+ const targetEnd = Math.min(endLine, codeLines.length);
1726
+ if (targetEnd <= state.processedLines) {
1727
+ return;
1728
+ }
1729
+ const metricsCollector = new WorkerMetricsCollector(workerGrammarEngine);
1730
+ setActiveMetricsCollector(metricsCollector);
1731
+ metricsCollector.setBlocksProduced(blocks.length);
1732
+ const prevSnapshot = await blockToNodeSnapshot(block);
1733
+ const tokenizationStart = now();
1734
+ const segmentLines = codeLines.slice(state.processedLines, targetEnd);
1735
+ if (segmentLines.length > 0) {
1736
+ if (wantsHtml || wantsTokens && !diffEnabled) {
1737
+ try {
1738
+ const sharedLanguage = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
1739
+ const tokens = highlighter.codeToTokensWithThemes(segmentLines.join("\n"), {
1740
+ lang: sharedLanguage,
1741
+ themes: CODE_HIGHLIGHT_THEMES,
1742
+ grammarState: state.grammarState
1743
+ });
1744
+ if (wantsHtml) {
1745
+ const htmlLines = renderShikiLines(tokens);
1746
+ for (let i = 0; i < htmlLines.length; i += 1) {
1747
+ state.highlightedLines[state.processedLines + i] = htmlLines[i] ?? null;
1748
+ }
1749
+ }
1750
+ if (wantsTokens && !diffEnabled) {
1751
+ const tokenLines = renderTokenLines(tokens);
1752
+ for (let i = 0; i < tokenLines.length; i += 1) {
1753
+ state.tokenLines[state.processedLines + i] = tokenLines[i] ?? null;
1754
+ }
1755
+ }
1756
+ state.grammarState = highlighter.getLastGrammarState(tokens);
1757
+ } catch (error) {
1758
+ console.warn("Lazy tokenization failed for", requestedLanguage, error);
1759
+ if (wantsHtml) {
1760
+ for (let i = 0; i < segmentLines.length; i += 1) {
1761
+ state.highlightedLines[state.processedLines + i] = null;
1762
+ }
1763
+ }
1764
+ if (wantsTokens && !diffEnabled) {
1765
+ for (let i = 0; i < segmentLines.length; i += 1) {
1766
+ state.tokenLines[state.processedLines + i] = null;
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ if (wantsTokens && diffEnabled) {
1772
+ const cursor = state.diffCursor ?? { oldLine: null, newLine: null };
1773
+ const diffLines = segmentLines.map((line) => parseUnifiedDiffLine(line, cursor));
1774
+ for (let i = 0; i < diffLines.length; i += 1) {
1775
+ const line = diffLines[i];
1776
+ const idx = state.processedLines + i;
1777
+ state.diffKind[idx] = line.kind;
1778
+ state.oldNo[idx] = line.oldNo ?? null;
1779
+ state.newNo[idx] = line.newNo ?? null;
1780
+ }
1781
+ try {
1782
+ const { tokenLines, grammarState } = buildDiffTokenLines(diffLines, resolvedTokenLanguage, state.diffGrammar ?? {});
1783
+ for (let i = 0; i < tokenLines.length; i += 1) {
1784
+ state.tokenLines[state.processedLines + i] = tokenLines[i] ?? null;
1785
+ }
1786
+ state.diffGrammar = grammarState;
1787
+ } catch (error) {
1788
+ console.warn("Lazy diff tokenization failed for", tokenLanguage, error);
1789
+ for (let i = 0; i < diffLines.length; i += 1) {
1790
+ const idx = state.processedLines + i;
1791
+ const line = diffLines[i];
1792
+ state.tokenLines[idx] = line.text.length > 0 ? { spans: [{ t: line.text }] } : { spans: [] };
1793
+ }
1794
+ }
1795
+ state.diffCursor = cursor;
1796
+ }
1797
+ }
1798
+ state.processedLines = targetEnd;
1799
+ const tokenizationDuration = now() - tokenizationStart;
1800
+ metricsCollector.recordShiki(tokenizationDuration);
1801
+ metricsCollector.recordHighlightForLanguage(resolvedLanguage, tokenizationDuration);
1802
+ metricsCollector.recordLazyTokenization(
1803
+ lazyRequestRangeSize({ ...request, startLine, endLine }),
1804
+ now() - request.requestedAt,
1805
+ lazyTokenizationQueue.size
1806
+ );
1807
+ const updated = (0, import_core.cloneBlock)(block);
1808
+ updated.payload.highlightedHtml = void 0;
1809
+ const effectiveLang = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
1810
+ const nextMeta = { ...updated.payload.meta ?? {}, ...meta, lang: effectiveLang };
1811
+ nextMeta.lazyTokenization = true;
1812
+ nextMeta.lazyTokenizedUntil = state.processedLines;
1813
+ if (wantsHtml) {
1814
+ nextMeta.highlightedLines = state.highlightedLines;
1815
+ } else if ("highlightedLines" in nextMeta) {
1816
+ delete nextMeta.highlightedLines;
1817
+ }
1818
+ if (wantsTokens) {
1819
+ nextMeta.tokenLines = state.tokenLines;
1820
+ if (diffEnabled) {
1821
+ nextMeta.diffKind = state.diffKind;
1822
+ nextMeta.oldNo = state.oldNo;
1823
+ nextMeta.newNo = state.newNo;
1824
+ } else {
1825
+ if ("diffKind" in nextMeta) delete nextMeta.diffKind;
1826
+ if ("oldNo" in nextMeta) delete nextMeta.oldNo;
1827
+ if ("newNo" in nextMeta) delete nextMeta.newNo;
1828
+ }
1829
+ } else if ("tokenLines" in nextMeta) {
1830
+ delete nextMeta.tokenLines;
1831
+ if ("diffKind" in nextMeta) delete nextMeta.diffKind;
1832
+ if ("oldNo" in nextMeta) delete nextMeta.oldNo;
1833
+ if ("newNo" in nextMeta) delete nextMeta.newNo;
1834
+ }
1835
+ if (emitDiffBlocks && diffInfo.isDiff) {
1836
+ const cursor = { oldLine: null, newLine: null };
1837
+ const diffLines = codeLines.map((line) => parseUnifiedDiffLine(line, cursor));
1838
+ nextMeta.diffBlocks = buildDiffBlocksFromLines(diffLines, codeLines, wantsTokens ? state.tokenLines : null, diffInfo.baseLang ?? null);
1839
+ } else if ("diffBlocks" in nextMeta) {
1840
+ delete nextMeta.diffBlocks;
1841
+ }
1842
+ updated.payload.meta = nextMeta;
1843
+ blocks[index] = updated;
1844
+ const nextSnapshot = await blockToNodeSnapshot(updated);
1845
+ const patches = [];
1846
+ diffNodeSnapshot(updated.id, prevSnapshot, nextSnapshot, patches, metricsCollector);
1847
+ if (patches.length > 0) {
1848
+ dispatchPatchBatch(patches, metricsCollector);
1849
+ } else {
1850
+ emitMetricsSample(metricsCollector);
1851
+ if (getActiveMetricsCollector() === metricsCollector) {
1852
+ setActiveMetricsCollector(null);
1853
+ }
1854
+ }
1855
+ }
1856
+ async function enrichCodeBlock(block) {
1857
+ performanceTimer.mark("highlight-code");
1858
+ const metrics = getActiveMetricsCollector();
1859
+ const raw = block.payload.raw ?? "";
1860
+ const { code, info, hadFence } = (0, import_core2.stripCodeFence)(raw);
1861
+ const { lang, meta } = (0, import_core.parseCodeFenceInfo)(info);
1862
+ let diffInfo = detectDiffLanguage(lang, meta);
1863
+ const codeBody = hadFence ? code : (0, import_core2.dedentIndentedCode)(raw);
1864
+ const codeLines = (0, import_core.extractCodeLines)(raw);
1865
+ if (!diffInfo.isDiff && emitDiffBlocks && looksLikeUnifiedDiff(codeLines)) {
1866
+ diffInfo = {
1867
+ isDiff: true,
1868
+ diffLang: "diff",
1869
+ baseLang: guessLanguageFromDiffLines(codeLines)
1870
+ };
1871
+ }
1872
+ const requestedLanguage = diffInfo.isDiff ? diffInfo.diffLang : lang || "text";
1873
+ const tokenLanguage = diffInfo.isDiff ? diffInfo.baseLang ?? "text" : requestedLanguage;
1874
+ const baseMeta = block.payload.meta ?? {};
1875
+ let resolvedLanguage = requestedLanguage;
1876
+ let resolvedTokenLanguage = tokenLanguage;
1877
+ const hasHighlighter = Boolean(highlighter);
1878
+ const hasHighlightableContent = codeBody.trim().length > 0;
1879
+ const wantsHtml = highlightOutputMode === "html" || highlightOutputMode === "both";
1880
+ const wantsTokens = (highlightOutputMode === "tokens" || highlightOutputMode === "both") && emitHighlightTokens;
1881
+ const wantsTokensNow = wantsTokens && (block.isFinalized || liveTokenizationEnabled);
1882
+ if (!block.isFinalized) {
1883
+ if (codeHighlightingMode === "final" || !hasHighlighter || !hasHighlightableContent) {
1884
+ resetIncrementalHighlightState(block.id);
1885
+ block.payload.highlightedHtml = void 0;
1886
+ const nextMeta2 = { ...baseMeta, ...meta, lang: resolvedLanguage };
1887
+ nextMeta2.code = codeBody;
1888
+ if ("highlightedLines" in nextMeta2) {
1889
+ delete nextMeta2.highlightedLines;
1890
+ }
1891
+ if ("tokenLines" in nextMeta2) {
1892
+ delete nextMeta2.tokenLines;
1893
+ }
1894
+ if ("diffKind" in nextMeta2) {
1895
+ delete nextMeta2.diffKind;
1896
+ }
1897
+ if ("oldNo" in nextMeta2) {
1898
+ delete nextMeta2.oldNo;
1899
+ }
1900
+ if ("newNo" in nextMeta2) {
1901
+ delete nextMeta2.newNo;
1902
+ }
1903
+ if ("diffBlocks" in nextMeta2) {
1904
+ delete nextMeta2.diffBlocks;
1905
+ }
1906
+ block.payload.meta = nextMeta2;
1907
+ return;
1908
+ }
1909
+ if (codeHighlightingMode === "incremental") {
1910
+ if (hasHighlighter) {
1911
+ if (wantsHtml) {
1912
+ resolvedLanguage = await resolveHighlightLanguage(requestedLanguage);
1913
+ }
1914
+ if (wantsTokens) {
1915
+ resolvedTokenLanguage = await resolveHighlightLanguage(tokenLanguage);
1916
+ }
1917
+ }
1918
+ let state = getIncrementalHighlightState(block.id, resolvedLanguage);
1919
+ if (wantsTokens && state.tokenLang && state.tokenLang !== resolvedTokenLanguage) {
1920
+ resetIncrementalHighlightState(block.id);
1921
+ state = getIncrementalHighlightState(block.id, resolvedLanguage);
1922
+ }
1923
+ if (codeBody.length < state.processedLength) {
1924
+ resetIncrementalHighlightState(block.id);
1925
+ state = getIncrementalHighlightState(block.id, resolvedLanguage);
1926
+ }
1927
+ state.tokenLang = wantsTokens ? resolvedTokenLanguage : void 0;
1928
+ const appended = codeBody.slice(state.processedLength);
1929
+ const combined = state.pendingLine + appended;
1930
+ const diffEnabled = diffInfo.isDiff && wantsTokensNow;
1931
+ if (combined.length > 0) {
1932
+ const parts = combined.split("\n");
1933
+ const completeLines = parts.slice(0, -1);
1934
+ const tail = parts.length > 0 ? parts[parts.length - 1] ?? "" : "";
1935
+ if (completeLines.length > 0) {
1936
+ if (wantsHtml || wantsTokensNow && !diffEnabled) {
1937
+ try {
1938
+ const sharedLanguage = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
1939
+ const tokens = highlighter?.codeToTokensWithThemes(completeLines.join("\n"), {
1940
+ lang: sharedLanguage,
1941
+ themes: CODE_HIGHLIGHT_THEMES,
1942
+ grammarState: state.grammarState
1943
+ });
1944
+ if (wantsHtml) {
1945
+ const htmlLines = renderShikiLines(tokens);
1946
+ state.highlightedLines.push(...htmlLines);
1947
+ }
1948
+ if (wantsTokensNow && !diffEnabled) {
1949
+ const tokenLines2 = renderTokenLines(tokens);
1950
+ state.tokenLines.push(...tokenLines2);
1951
+ }
1952
+ state.grammarState = highlighter?.getLastGrammarState(tokens);
1953
+ } catch (error) {
1954
+ console.warn("Incremental highlighting failed for", requestedLanguage, error);
1955
+ if (wantsHtml) {
1956
+ state.highlightedLines.push(...completeLines.map(() => null));
1957
+ }
1958
+ if (wantsTokensNow && !diffEnabled) {
1959
+ state.tokenLines.push(...completeLines.map(() => null));
1960
+ }
1961
+ }
1962
+ }
1963
+ if (wantsTokensNow && diffEnabled) {
1964
+ const cursor = state.diffCursor ?? { oldLine: null, newLine: null };
1965
+ const diffLines = completeLines.map((line) => parseUnifiedDiffLine(line, cursor));
1966
+ state.diffCursor = cursor;
1967
+ state.diffKind.push(...diffLines.map((line) => line.kind));
1968
+ state.oldNo.push(...diffLines.map((line) => line.oldNo ?? null));
1969
+ state.newNo.push(...diffLines.map((line) => line.newNo ?? null));
1970
+ try {
1971
+ const { tokenLines: tokenLines2, grammarState } = buildDiffTokenLines(diffLines, resolvedTokenLanguage, state.diffGrammar ?? {});
1972
+ state.tokenLines.push(...tokenLines2);
1973
+ state.diffGrammar = grammarState;
1974
+ } catch (error) {
1975
+ console.warn("Incremental diff tokenization failed for", tokenLanguage, error);
1976
+ state.tokenLines.push(
1977
+ ...diffLines.map((line) => line.text.length > 0 ? { spans: [{ t: line.text }] } : { spans: [] })
1978
+ );
1979
+ }
1980
+ }
1981
+ }
1982
+ state.pendingLine = tail ?? "";
1983
+ }
1984
+ state.processedLength = codeBody.length;
1985
+ state.lang = resolvedLanguage;
1986
+ block.payload.highlightedHtml = void 0;
1987
+ const effectiveLang2 = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
1988
+ const nextMeta2 = { ...baseMeta, ...meta, lang: effectiveLang2 };
1989
+ nextMeta2.code = codeBody;
1990
+ if (wantsHtml) {
1991
+ nextMeta2.highlightedLines = state.highlightedLines;
1992
+ } else if ("highlightedLines" in nextMeta2) {
1993
+ delete nextMeta2.highlightedLines;
1994
+ }
1995
+ if (wantsTokensNow) {
1996
+ nextMeta2.tokenLines = state.tokenLines;
1997
+ if (diffEnabled) {
1998
+ nextMeta2.diffKind = state.diffKind;
1999
+ nextMeta2.oldNo = state.oldNo;
2000
+ nextMeta2.newNo = state.newNo;
2001
+ } else {
2002
+ if ("diffKind" in nextMeta2) delete nextMeta2.diffKind;
2003
+ if ("oldNo" in nextMeta2) delete nextMeta2.oldNo;
2004
+ if ("newNo" in nextMeta2) delete nextMeta2.newNo;
2005
+ }
2006
+ } else if ("tokenLines" in nextMeta2) {
2007
+ delete nextMeta2.tokenLines;
2008
+ if ("diffKind" in nextMeta2) delete nextMeta2.diffKind;
2009
+ if ("oldNo" in nextMeta2) delete nextMeta2.oldNo;
2010
+ if ("newNo" in nextMeta2) delete nextMeta2.newNo;
2011
+ }
2012
+ if (emitDiffBlocks && diffInfo.isDiff && liveTokenizationEnabled) {
2013
+ const cursor = { oldLine: null, newLine: null };
2014
+ const diffLines = codeLines.map((line) => parseUnifiedDiffLine(line, cursor));
2015
+ nextMeta2.diffBlocks = buildDiffBlocksFromLines(diffLines, codeLines, wantsTokensNow ? state.tokenLines : null, diffInfo.baseLang ?? null);
2016
+ } else if ("diffBlocks" in nextMeta2) {
2017
+ delete nextMeta2.diffBlocks;
2018
+ }
2019
+ block.payload.meta = nextMeta2;
2020
+ const highlightDuration2 = performanceTimer.measure("highlight-code");
2021
+ metrics?.recordShiki(highlightDuration2);
2022
+ metrics?.recordHighlightForLanguage(resolvedLanguage, highlightDuration2);
2023
+ return;
2024
+ }
2025
+ }
2026
+ resetIncrementalHighlightState(block.id);
2027
+ if (block.isFinalized && shouldLazyTokenizeBlock(codeLines.length, hasHighlighter, hasHighlightableContent) && (wantsHtml || wantsTokens)) {
2028
+ if (hasHighlighter) {
2029
+ if (wantsHtml) {
2030
+ resolvedLanguage = await resolveHighlightLanguage(requestedLanguage);
2031
+ }
2032
+ if (wantsTokens) {
2033
+ resolvedTokenLanguage = await resolveHighlightLanguage(tokenLanguage);
2034
+ }
2035
+ }
2036
+ const diffEnabled = diffInfo.isDiff && wantsTokens;
2037
+ const signature = makeLazySignature(codeBody, resolvedLanguage, resolvedTokenLanguage, diffEnabled);
2038
+ const state = getOrInitLazyState(block.id, signature, codeLines.length, resolvedLanguage, resolvedTokenLanguage, diffEnabled);
2039
+ const effectiveLang2 = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
2040
+ const nextMeta2 = { ...baseMeta, ...meta, lang: effectiveLang2 };
2041
+ nextMeta2.code = codeBody;
2042
+ nextMeta2.lazyTokenization = true;
2043
+ nextMeta2.lazyTokenizedUntil = state.processedLines;
2044
+ if (wantsHtml) {
2045
+ nextMeta2.highlightedLines = state.highlightedLines;
2046
+ } else if ("highlightedLines" in nextMeta2) {
2047
+ delete nextMeta2.highlightedLines;
2048
+ }
2049
+ if (wantsTokens) {
2050
+ nextMeta2.tokenLines = state.tokenLines;
2051
+ if (diffEnabled) {
2052
+ nextMeta2.diffKind = state.diffKind;
2053
+ nextMeta2.oldNo = state.oldNo;
2054
+ nextMeta2.newNo = state.newNo;
2055
+ } else {
2056
+ if ("diffKind" in nextMeta2) delete nextMeta2.diffKind;
2057
+ if ("oldNo" in nextMeta2) delete nextMeta2.oldNo;
2058
+ if ("newNo" in nextMeta2) delete nextMeta2.newNo;
2059
+ }
2060
+ } else if ("tokenLines" in nextMeta2) {
2061
+ delete nextMeta2.tokenLines;
2062
+ if ("diffKind" in nextMeta2) delete nextMeta2.diffKind;
2063
+ if ("oldNo" in nextMeta2) delete nextMeta2.oldNo;
2064
+ if ("newNo" in nextMeta2) delete nextMeta2.newNo;
2065
+ }
2066
+ if (emitDiffBlocks && diffInfo.isDiff) {
2067
+ const cursor = { oldLine: null, newLine: null };
2068
+ const diffLines = codeLines.map((line) => parseUnifiedDiffLine(line, cursor));
2069
+ nextMeta2.diffBlocks = buildDiffBlocksFromLines(diffLines, codeLines, wantsTokens ? state.tokenLines : null, diffInfo.baseLang ?? null);
2070
+ } else if ("diffBlocks" in nextMeta2) {
2071
+ delete nextMeta2.diffBlocks;
2072
+ }
2073
+ block.payload.highlightedHtml = void 0;
2074
+ block.payload.meta = nextMeta2;
2075
+ return;
2076
+ }
2077
+ let tokenLines;
2078
+ let diffKind;
2079
+ let diffOldNo;
2080
+ let diffNewNo;
2081
+ if (wantsHtml) {
2082
+ let cachedHighlight = block.isFinalized ? getHighlightCacheEntry(requestedLanguage, codeBody) : null;
2083
+ if (!cachedHighlight && highlighter && hasHighlightableContent) {
2084
+ try {
2085
+ resolvedLanguage = await resolveHighlightLanguage(requestedLanguage);
2086
+ const highlighted = highlighter.codeToHtml(codeBody, {
2087
+ lang: resolvedLanguage,
2088
+ themes: CODE_HIGHLIGHT_THEMES,
2089
+ defaultColor: false
2090
+ });
2091
+ const enhanced = enhanceHighlightedHtml(highlighted, resolvedLanguage);
2092
+ block.payload.highlightedHtml = enhanced;
2093
+ if (block.isFinalized) {
2094
+ setHighlightCacheEntry(resolvedLanguage, codeBody, enhanced);
2095
+ if (resolvedLanguage !== requestedLanguage) {
2096
+ setHighlightCacheEntry(requestedLanguage, codeBody, enhanced);
2097
+ }
2098
+ }
2099
+ cachedHighlight = getHighlightCacheEntry(resolvedLanguage, codeBody);
2100
+ } catch (error) {
2101
+ console.warn("Highlighting failed for", requestedLanguage, error);
2102
+ resolvedLanguage = "text";
2103
+ }
2104
+ }
2105
+ if (cachedHighlight) {
2106
+ resolvedLanguage = cachedHighlight.lang;
2107
+ block.payload.highlightedHtml = cachedHighlight.html;
2108
+ }
2109
+ } else {
2110
+ block.payload.highlightedHtml = void 0;
2111
+ }
2112
+ if (wantsTokens) {
2113
+ if (diffInfo.isDiff) {
2114
+ if (hasHighlighter) {
2115
+ resolvedTokenLanguage = await resolveHighlightLanguage(tokenLanguage);
2116
+ }
2117
+ const diffLines = codeBody.length > 0 ? codeBody.split("\n") : [];
2118
+ const cursor = { oldLine: null, newLine: null };
2119
+ const diffInfoLines = diffLines.map((line) => parseUnifiedDiffLine(line, cursor));
2120
+ diffKind = diffInfoLines.map((line) => line.kind);
2121
+ diffOldNo = diffInfoLines.map((line) => line.oldNo ?? null);
2122
+ diffNewNo = diffInfoLines.map((line) => line.newNo ?? null);
2123
+ if (!hasHighlighter || !hasHighlightableContent) {
2124
+ tokenLines = diffInfoLines.map((line) => line.text.length > 0 ? { spans: [{ t: line.text }] } : { spans: [] });
2125
+ } else {
2126
+ try {
2127
+ const result = buildDiffTokenLines(diffInfoLines, resolvedTokenLanguage, {});
2128
+ tokenLines = result.tokenLines;
2129
+ } catch (error) {
2130
+ console.warn("Diff tokenization failed for", tokenLanguage, error);
2131
+ tokenLines = diffInfoLines.map((line) => line.text.length > 0 ? { spans: [{ t: line.text }] } : { spans: [] });
2132
+ }
2133
+ }
2134
+ } else {
2135
+ const lineCount = codeBody.length > 0 ? codeBody.split("\n").length : 0;
2136
+ if (!hasHighlighter || !hasHighlightableContent) {
2137
+ tokenLines = new Array(lineCount).fill(null);
2138
+ } else {
2139
+ try {
2140
+ resolvedTokenLanguage = await resolveHighlightLanguage(tokenLanguage);
2141
+ const tokens = highlighter?.codeToTokensWithThemes(codeBody, {
2142
+ lang: resolvedTokenLanguage,
2143
+ themes: CODE_HIGHLIGHT_THEMES
2144
+ });
2145
+ tokenLines = renderTokenLines(tokens);
2146
+ } catch (error) {
2147
+ console.warn("Tokenization failed for", requestedLanguage, error);
2148
+ tokenLines = new Array(lineCount).fill(null);
2149
+ }
2150
+ }
2151
+ }
2152
+ }
2153
+ const effectiveLang = wantsHtml ? resolvedLanguage : resolvedTokenLanguage;
2154
+ const nextMeta = { ...baseMeta, ...meta, lang: effectiveLang };
2155
+ nextMeta.code = codeBody;
2156
+ if ("highlightedLines" in nextMeta) {
2157
+ delete nextMeta.highlightedLines;
2158
+ }
2159
+ if (wantsTokens) {
2160
+ nextMeta.tokenLines = tokenLines ?? [];
2161
+ if (diffKind) {
2162
+ nextMeta.diffKind = diffKind;
2163
+ nextMeta.oldNo = diffOldNo ?? [];
2164
+ nextMeta.newNo = diffNewNo ?? [];
2165
+ } else {
2166
+ if ("diffKind" in nextMeta) delete nextMeta.diffKind;
2167
+ if ("oldNo" in nextMeta) delete nextMeta.oldNo;
2168
+ if ("newNo" in nextMeta) delete nextMeta.newNo;
2169
+ }
2170
+ } else if ("tokenLines" in nextMeta) {
2171
+ delete nextMeta.tokenLines;
2172
+ if ("diffKind" in nextMeta) delete nextMeta.diffKind;
2173
+ if ("oldNo" in nextMeta) delete nextMeta.oldNo;
2174
+ if ("newNo" in nextMeta) delete nextMeta.newNo;
2175
+ }
2176
+ if (emitDiffBlocks && diffInfo.isDiff) {
2177
+ const cursor = { oldLine: null, newLine: null };
2178
+ const diffLines = codeLines.map((line) => parseUnifiedDiffLine(line, cursor));
2179
+ nextMeta.diffBlocks = buildDiffBlocksFromLines(diffLines, codeLines, wantsTokens ? tokenLines ?? null : null, diffInfo.baseLang ?? null);
2180
+ } else if ("diffBlocks" in nextMeta) {
2181
+ delete nextMeta.diffBlocks;
2182
+ }
2183
+ block.payload.meta = nextMeta;
2184
+ const highlightDuration = performanceTimer.measure("highlight-code");
2185
+ metrics?.recordShiki(highlightDuration);
2186
+ metrics?.recordHighlightForLanguage(resolvedLanguage, highlightDuration);
2187
+ }
2188
+ function makeHighlightCacheKey(language, code) {
2189
+ return `${language}::${code}`;
2190
+ }
2191
+ function getHighlightCacheEntry(language, code) {
2192
+ if (!language || !code) return null;
2193
+ return CODE_HIGHLIGHT_CACHE.get(makeHighlightCacheKey(language, code)) ?? null;
2194
+ }
2195
+ function setHighlightCacheEntry(language, code, html) {
2196
+ if (!language || !code || typeof html !== "string") return;
2197
+ const key = makeHighlightCacheKey(language, code);
2198
+ CODE_HIGHLIGHT_CACHE.set(key, { html, lang: language });
2199
+ if (CODE_HIGHLIGHT_CACHE.size > MAX_CODE_HIGHLIGHT_CACHE_ENTRIES) {
2200
+ const oldest = CODE_HIGHLIGHT_CACHE.keys().next().value;
2201
+ if (oldest) {
2202
+ CODE_HIGHLIGHT_CACHE.delete(oldest);
2203
+ }
2204
+ }
2205
+ }
2206
+ function enhanceHighlightedHtml(html, language) {
2207
+ if (!html) return html;
2208
+ let lineIndex = 0;
2209
+ const withLineNumbers = html.replace(/<span class="line"/g, (match) => {
2210
+ if (/data-line="\d+"/.test(match)) {
2211
+ return match;
2212
+ }
2213
+ lineIndex += 1;
2214
+ return `${match} data-line="${lineIndex}"`;
2215
+ });
2216
+ const enhancedCode = withLineNumbers.replace(/<code([^>]*)>/, (match, attrs) => {
2217
+ const attrMap = parseAttributeString(attrs);
2218
+ attrMap["data-language"] = language || "text";
2219
+ if (!attrMap["data-theme"]) {
2220
+ attrMap["data-theme"] = "github-dark github-light";
2221
+ }
2222
+ if (!attrMap.style) {
2223
+ attrMap.style = "display: grid;";
2224
+ } else if (!/display\s*:/.test(attrMap.style)) {
2225
+ attrMap.style = `${attrMap.style.trim().replace(/;$/, "")};display: grid;`;
2226
+ }
2227
+ return `<code${serializeAttributes(attrMap)}>`;
2228
+ });
2229
+ return enhancedCode.replace(/<pre([^>]*)>/, (match, attrs) => {
2230
+ const attrMap = parseAttributeString(attrs);
2231
+ attrMap["data-language"] = language || "text";
2232
+ if (attrMap.tabindex !== void 0) {
2233
+ attrMap.tabindex = void 0;
2234
+ }
2235
+ attrMap.style = sanitizeShikiStyle(attrMap.style);
2236
+ if (attrMap.style === void 0 || attrMap.style.length === 0) {
2237
+ attrMap.style = void 0;
2238
+ }
2239
+ return `<pre${serializeAttributes(attrMap)}>`;
2240
+ });
2241
+ }
2242
+ function parseAttributeString(fragment) {
2243
+ const attrs = {};
2244
+ if (!fragment) return attrs;
2245
+ const regex = /([a-zA-Z_:][\w:.-]*)\s*=\s*"([^"]*)"/g;
2246
+ let match;
2247
+ while (true) {
2248
+ match = regex.exec(fragment);
2249
+ if (match === null) {
2250
+ break;
2251
+ }
2252
+ const [, name, value] = match;
2253
+ attrs[name] = value;
2254
+ }
2255
+ return attrs;
2256
+ }
2257
+ function serializeAttributes(attrs) {
2258
+ return Object.entries(attrs).map(([key, value]) => ` ${key}="${value}"`).join("");
2259
+ }
2260
+ function sanitizeShikiStyle(style) {
2261
+ const sanitized = [];
2262
+ if (style) {
2263
+ for (const rawEntry of style.split(";")) {
2264
+ const entry = rawEntry.trim();
2265
+ if (!entry) continue;
2266
+ const separatorIndex = entry.indexOf(":");
2267
+ if (separatorIndex === -1) continue;
2268
+ const name = entry.slice(0, separatorIndex).trim();
2269
+ const value = entry.slice(separatorIndex + 1).trim();
2270
+ const lowerName = name.toLowerCase();
2271
+ if (lowerName.startsWith("background")) {
2272
+ continue;
2273
+ }
2274
+ if (lowerName === "--shiki-dark-bg" || lowerName === "--shiki-light-bg") {
2275
+ continue;
2276
+ }
2277
+ sanitized.push(`${name}:${value}`);
2278
+ }
2279
+ }
2280
+ sanitized.push("--shiki-dark-bg: transparent");
2281
+ sanitized.push("--shiki-light-bg: transparent");
2282
+ return sanitized.length > 0 ? sanitized.join(";") : void 0;
2283
+ }
2284
+ function clearWorkerMdxCaches() {
2285
+ WORKER_MDX_CACHE.clear();
2286
+ WORKER_MDX_INFLIGHT.clear();
2287
+ }
2288
+ function cacheWorkerMdxModule(key, module2) {
2289
+ const stored = {
2290
+ ...module2,
2291
+ dependencies: module2.dependencies ? [...module2.dependencies] : void 0
2292
+ };
2293
+ if (!WORKER_MDX_CACHE.has(key) && WORKER_MDX_CACHE.size >= MAX_WORKER_MDX_CACHE_ENTRIES) {
2294
+ const oldest = WORKER_MDX_CACHE.keys().next().value;
2295
+ if (typeof oldest === "string") {
2296
+ WORKER_MDX_CACHE.delete(oldest);
2297
+ }
2298
+ }
2299
+ WORKER_MDX_CACHE.set(key, stored);
2300
+ }
2301
+ async function getOrCompileMdxModuleForBlock(block) {
2302
+ const cacheKey = block.id;
2303
+ const cached = WORKER_MDX_CACHE.get(cacheKey);
2304
+ if (cached) {
2305
+ return {
2306
+ ...cached,
2307
+ dependencies: cached.dependencies ? [...cached.dependencies] : void 0
2308
+ };
2309
+ }
2310
+ const inflight = WORKER_MDX_INFLIGHT.get(cacheKey);
2311
+ if (inflight) {
2312
+ const result = await inflight;
2313
+ return {
2314
+ ...result,
2315
+ dependencies: result.dependencies ? [...result.dependencies] : void 0
2316
+ };
2317
+ }
2318
+ const source = block.payload.raw ?? "";
2319
+ const compilePromise = (async () => {
2320
+ const { code, dependencies } = await compileMdxContent(source);
2321
+ const module2 = {
2322
+ id: `worker:${cacheKey}`,
2323
+ code,
2324
+ dependencies,
2325
+ source: "worker"
2326
+ };
2327
+ cacheWorkerMdxModule(cacheKey, module2);
2328
+ return module2;
2329
+ })();
2330
+ WORKER_MDX_INFLIGHT.set(cacheKey, compilePromise);
2331
+ try {
2332
+ const compiled = await compilePromise;
2333
+ return {
2334
+ ...compiled,
2335
+ dependencies: compiled.dependencies ? [...compiled.dependencies] : void 0
2336
+ };
2337
+ } finally {
2338
+ WORKER_MDX_INFLIGHT.delete(cacheKey);
2339
+ }
2340
+ }
2341
+ async function updateMdxCompilationState(block) {
2342
+ try {
2343
+ if (DEBUG_MDX) {
2344
+ console.debug("[markdown-worker] mdx update", { blockId: block.id, type: block.type, finalized: block.isFinalized, compileMode: mdxCompileMode });
2345
+ }
2346
+ } catch {
2347
+ }
2348
+ if (block.type !== "mdx") {
2349
+ if ("compiledMdxModule" in block.payload) {
2350
+ block.payload.compiledMdxModule = void 0;
2351
+ }
2352
+ return;
2353
+ }
2354
+ if (mdxCompileMode !== "worker") {
2355
+ if ("compiledMdxModule" in block.payload) {
2356
+ block.payload.compiledMdxModule = void 0;
2357
+ }
2358
+ return;
2359
+ }
2360
+ const baseMeta = block.payload.meta ?? {};
2361
+ const pendingMeta = { ...baseMeta, mdxStatus: "pending" };
2362
+ if ("mdxError" in pendingMeta) {
2363
+ pendingMeta.mdxError = void 0;
2364
+ }
2365
+ block.payload.meta = pendingMeta;
2366
+ if (!block.isFinalized) {
2367
+ block.payload.compiledMdxModule = null;
2368
+ block.payload.compiledMdxRef = { id: "pending" };
2369
+ return;
2370
+ }
2371
+ try {
2372
+ const module2 = await getOrCompileMdxModuleForBlock(block);
2373
+ block.payload.compiledMdxModule = {
2374
+ ...module2,
2375
+ dependencies: module2.dependencies ? [...module2.dependencies] : void 0
2376
+ };
2377
+ block.payload.compiledMdxRef = { id: module2.id };
2378
+ const nextMeta = {
2379
+ ...block.payload.meta ?? {},
2380
+ mdxStatus: "compiled"
2381
+ };
2382
+ if ("mdxError" in nextMeta) {
2383
+ nextMeta.mdxError = void 0;
2384
+ }
2385
+ block.payload.meta = nextMeta;
2386
+ if (DEBUG_MDX) {
2387
+ try {
2388
+ console.debug("[markdown-worker] mdx compiled", { blockId: block.id, id: module2.id, deps: module2.dependencies?.length ?? 0 });
2389
+ } catch {
2390
+ }
2391
+ }
2392
+ } catch (error) {
2393
+ const message = error instanceof Error ? error.message : "MDX compilation failed";
2394
+ block.payload.compiledMdxModule = null;
2395
+ block.payload.compiledMdxRef = void 0;
2396
+ const errorMeta = {
2397
+ ...block.payload.meta ?? {},
2398
+ mdxStatus: "error",
2399
+ mdxError: message
2400
+ };
2401
+ block.payload.meta = errorMeta;
2402
+ if (DEBUG_MDX) {
2403
+ try {
2404
+ console.error("[markdown-worker] mdx compile failed", { blockId: block.id, error: message });
2405
+ } catch {
2406
+ }
2407
+ }
2408
+ }
2409
+ }
2410
+ function enrichListBlock(block) {
2411
+ const lines = block.payload.raw.split("\n");
2412
+ const itemsRaw = [];
2413
+ let current = null;
2414
+ const bulletRe = /^[-*+]\s+(.*)$/;
2415
+ const orderedRe = /^\d+\.\s+(.*)$/;
2416
+ for (const line of lines) {
2417
+ const trimmed = line.trimEnd();
2418
+ const bare = trimmed.trimStart();
2419
+ const bulletMatch = bare.match(bulletRe);
2420
+ const orderedMatch = bare.match(orderedRe);
2421
+ if (bulletMatch || orderedMatch) {
2422
+ if (current) {
2423
+ itemsRaw.push(current.join("\n"));
2424
+ }
2425
+ const content = (bulletMatch ? bulletMatch[1] : orderedMatch?.[1]) ?? "";
2426
+ current = [content];
2427
+ } else if (current) {
2428
+ current.push(bare);
2429
+ }
2430
+ }
2431
+ if (current) {
2432
+ itemsRaw.push(current.join("\n"));
2433
+ }
2434
+ const items = itemsRaw.map((raw) => inlineParser.parse(raw));
2435
+ const isOrdered = /^\d+\./.test(lines[0]?.trimStart() || "");
2436
+ block.payload.meta = {
2437
+ ordered: isOrdered,
2438
+ items,
2439
+ formatAnticipation: block.isFinalized ? void 0 : formatAnticipationConfig,
2440
+ mathEnabled: enableMath
2441
+ };
2442
+ }
2443
+ function runDocumentPlugins(inputBlocks, content) {
2444
+ const blocks2 = inputBlocks.slice();
2445
+ const aggregatedProtected = [];
2446
+ for (const block of blocks2) {
2447
+ const meta = block.payload.meta;
2448
+ const rawRanges = Array.isArray(meta?.protectedRanges) ? meta.protectedRanges : void 0;
2449
+ if (!rawRanges || rawRanges.length === 0) continue;
2450
+ const base = typeof block.payload.range?.from === "number" ? block.payload.range.from : null;
2451
+ if (base === null) continue;
2452
+ for (const range of rawRanges) {
2453
+ if (typeof range.from !== "number" || typeof range.to !== "number") continue;
2454
+ aggregatedProtected.push({
2455
+ ...range,
2456
+ from: base + range.from,
2457
+ to: base + range.to
2458
+ });
2459
+ }
2460
+ }
2461
+ const { syntheticBlocks } = import_plugins.globalDocumentPluginRegistry.run({
2462
+ content,
2463
+ blocks: blocks2,
2464
+ state: documentPluginState,
2465
+ protectedRanges: aggregatedProtected
2466
+ });
2467
+ const filtered = blocks2.filter((b) => b.type !== "footnotes");
2468
+ if (syntheticBlocks && syntheticBlocks.length > 0) {
2469
+ const tail = filtered[filtered.length - 1];
2470
+ const hasDirtyTail = tail ? !tail.isFinalized : false;
2471
+ if (hasDirtyTail) {
2472
+ return filtered;
2473
+ }
2474
+ filtered.push(...syntheticBlocks);
2475
+ }
2476
+ return filtered;
2477
+ }
2478
+ function isBlockLike(value) {
2479
+ if (!value || typeof value !== "object") return false;
2480
+ const candidate = value;
2481
+ if (typeof candidate.id !== "string" || typeof candidate.type !== "string") return false;
2482
+ return typeof candidate.payload === "object" && candidate.payload !== null;
2483
+ }
2484
+ async function enrichNestedCodeBlocks(snapshot, allowHighlight) {
2485
+ if (!allowHighlight || !highlighter) {
2486
+ return;
2487
+ }
2488
+ const stack = [snapshot];
2489
+ while (stack.length > 0) {
2490
+ const current = stack.pop();
2491
+ if (!current) continue;
2492
+ const props = current.props ?? {};
2493
+ const maybeBlock = props.block;
2494
+ if (isBlockLike(maybeBlock) && maybeBlock.type === "code") {
2495
+ const block = (0, import_core.cloneBlock)(maybeBlock);
2496
+ const hasHighlight = typeof block.payload.highlightedHtml === "string" && block.payload.highlightedHtml.trim().length > 0;
2497
+ if (!hasHighlight) {
2498
+ await enrichCodeBlock(block);
2499
+ const updated = (0, import_core.createBlockSnapshot)(block);
2500
+ current.type = updated.type;
2501
+ current.props = updated.props;
2502
+ current.meta = updated.meta;
2503
+ current.range = updated.range;
2504
+ current.children = updated.children;
2505
+ continue;
2506
+ }
2507
+ }
2508
+ const children = current.children ?? [];
2509
+ for (let idx = children.length - 1; idx >= 0; idx--) {
2510
+ const child = children[idx];
2511
+ if (child) {
2512
+ stack.push(child);
2513
+ }
2514
+ }
2515
+ }
2516
+ }
2517
+ async function blockToNodeSnapshot(block) {
2518
+ const snapshot = (0, import_core.createBlockSnapshot)((0, import_core.cloneBlock)(block));
2519
+ await enrichNestedCodeBlocks(snapshot, Boolean(block.isFinalized));
2520
+ return snapshot;
2521
+ }
2522
+ async function emitDocumentPatch(currentBlocks) {
2523
+ const patches = [];
2524
+ const tocPatch = maybeBuildTocPatch(currentBlocks);
2525
+ for (let index = 0; index < currentBlocks.length; index++) {
2526
+ const block = currentBlocks[index];
2527
+ if (!block) continue;
2528
+ patches.push({
2529
+ op: "insertChild",
2530
+ at: { blockId: import_core.PATCH_ROOT_ID },
2531
+ index,
2532
+ node: await blockToNodeSnapshot(block)
2533
+ });
2534
+ }
2535
+ if (tocPatch) {
2536
+ patches.push(tocPatch);
2537
+ }
2538
+ if (patches.length > 0) {
2539
+ postMessage({
2540
+ type: "PATCH",
2541
+ tx: ++txCounter,
2542
+ patches
2543
+ });
2544
+ }
2545
+ }
2546
+ async function emitBlockDiffPatches(previousBlocks, nextBlocks, changedRanges, metrics) {
2547
+ const patches = [];
2548
+ const tocPatch = maybeBuildTocPatch(nextBlocks);
2549
+ if (previousBlocks === nextBlocks) return;
2550
+ let prefix = 0;
2551
+ const maxPrefix = Math.min(previousBlocks.length, nextBlocks.length);
2552
+ while (prefix < maxPrefix && previousBlocks[prefix].id === nextBlocks[prefix].id) {
2553
+ prefix++;
2554
+ }
2555
+ let prevTail = previousBlocks.length - 1;
2556
+ let nextTail = nextBlocks.length - 1;
2557
+ while (prevTail >= prefix && nextTail >= prefix && previousBlocks[prevTail].id === nextBlocks[nextTail].id) {
2558
+ prevTail--;
2559
+ nextTail--;
2560
+ }
2561
+ const removeCount = prevTail >= prefix ? prevTail - prefix + 1 : 0;
2562
+ const addCount = nextTail >= prefix ? nextTail - prefix + 1 : 0;
2563
+ if (removeCount === addCount) {
2564
+ for (let offset = 0; offset < addCount; offset++) {
2565
+ const targetIndex = prefix + offset;
2566
+ patches.push({
2567
+ op: "replaceChild",
2568
+ at: { blockId: import_core.PATCH_ROOT_ID },
2569
+ index: targetIndex,
2570
+ node: await blockToNodeSnapshot(nextBlocks[targetIndex])
2571
+ });
2572
+ }
2573
+ } else {
2574
+ for (let i = removeCount - 1; i >= 0; i--) {
2575
+ patches.push({
2576
+ op: "deleteChild",
2577
+ at: { blockId: import_core.PATCH_ROOT_ID },
2578
+ index: prefix + i
2579
+ });
2580
+ }
2581
+ for (let offset = 0; offset < addCount; offset++) {
2582
+ const insertIndex = prefix + offset;
2583
+ const block = (0, import_core.cloneBlock)(nextBlocks[insertIndex]);
2584
+ patches.push({
2585
+ op: "insertChild",
2586
+ at: { blockId: import_core.PATCH_ROOT_ID },
2587
+ index: insertIndex,
2588
+ node: await blockToNodeSnapshot(block)
2589
+ });
2590
+ }
2591
+ }
2592
+ metrics?.markDiffStart();
2593
+ const { patches: contentPatches, changedBlockCount } = await diffBlockContent(previousBlocks, nextBlocks, changedRanges, metrics);
2594
+ metrics?.markDiffEnd();
2595
+ const combined = patches.concat(contentPatches);
2596
+ if (tocPatch) {
2597
+ combined.push(tocPatch);
2598
+ }
2599
+ let paragraphLimit = null;
2600
+ if (workerCredits < 0.9) {
2601
+ const dynamicBase = workerCredits < 0.5 ? 48 : 96;
2602
+ const dynamicFinalize = workerCredits < 0.5 ? 40 : 64;
2603
+ paragraphLimit = computeParagraphPatchLimit(combined, {
2604
+ baseLimit: dynamicBase,
2605
+ finalizeLimit: dynamicFinalize
2606
+ });
2607
+ }
2608
+ const immediatePatches = partitionPatchesForCredits(combined, paragraphLimit === null ? void 0 : paragraphLimit);
2609
+ if (deferredPatchQueue.length > maxDeferredQueueSize) {
2610
+ maxDeferredQueueSize = deferredPatchQueue.length;
2611
+ }
2612
+ let structuralCount = 0;
2613
+ for (const patch of patches) {
2614
+ if (patch.op === "insertChild" || patch.op === "deleteChild" || patch.op === "replaceChild" || patch.op === "reorder") {
2615
+ structuralCount += 1;
2616
+ }
2617
+ }
2618
+ lastDiffSummary = {
2619
+ prevCount: previousBlocks.length,
2620
+ nextCount: nextBlocks.length,
2621
+ prefix,
2622
+ removeCount,
2623
+ addCount,
2624
+ structuralPatches: structuralCount,
2625
+ contentPatches: contentPatches.length,
2626
+ immediatePatches: immediatePatches.length,
2627
+ deferredQueue: deferredPatchQueue.length
2628
+ };
2629
+ if (addCount > 0 || removeCount > 0 || structuralCount > 0 || deferredPatchQueue.length > 0) {
2630
+ lastStructuralDiffSummary = { ...lastDiffSummary };
2631
+ }
2632
+ if (immediatePatches.length > 0) {
2633
+ maxEmittedBlockCount = Math.max(maxEmittedBlockCount, nextBlocks.length);
2634
+ }
2635
+ if (structuralCount > 0) {
2636
+ maxStructuralBlockCount = Math.max(maxStructuralBlockCount, nextBlocks.length);
2637
+ }
2638
+ if (nextBlocks.length >= 60 && removeCount === 0 && addCount === 0) {
2639
+ const sliceStart = Math.max(0, Math.min(55, nextBlocks.length - 1));
2640
+ const sliceEnd = Math.min(nextBlocks.length, sliceStart + 8);
2641
+ const toPreview = (block) => ({
2642
+ id: block.id,
2643
+ type: block.type,
2644
+ raw: typeof block.payload.raw === "string" ? block.payload.raw.slice(0, 80) : ""
2645
+ });
2646
+ lastHighCountNoStructural = {
2647
+ nextCount: nextBlocks.length,
2648
+ sliceStart,
2649
+ prevSlice: previousBlocks.slice(sliceStart, sliceEnd).map(toPreview),
2650
+ nextSlice: nextBlocks.slice(sliceStart, sliceEnd).map(toPreview)
2651
+ };
2652
+ }
2653
+ if (immediatePatches.length === 0) {
2654
+ if (metrics) {
2655
+ metrics.finalizePatch(txCounter, 0, deferredPatchQueue.length, 0);
2656
+ emitMetricsSample(metrics);
2657
+ if (getActiveMetricsCollector() === metrics) {
2658
+ setActiveMetricsCollector(null);
2659
+ }
2660
+ }
2661
+ return;
2662
+ }
2663
+ dispatchPatchBatch(immediatePatches, metrics);
2664
+ }
2665
+ function dispatchPatchBatch(patches, metrics) {
2666
+ if (!patches || patches.length === 0) {
2667
+ return;
2668
+ }
2669
+ const tx = ++txCounter;
2670
+ const patchBytes = metrics ? estimatePatchSize(patches) : 0;
2671
+ metrics?.finalizePatch(tx, patches.length, deferredPatchQueue.length, patchBytes);
2672
+ const changedBlockReport = countChangedBlocksFromPatches(patches);
2673
+ const patchMetrics = metrics ? metrics.toPatchMetrics(changedBlockReport) : {
2674
+ patchCount: patches.length,
2675
+ changedBlocks: changedBlockReport
2676
+ };
2677
+ const message = {
2678
+ type: "PATCH",
2679
+ tx,
2680
+ patches,
2681
+ metrics: patchMetrics
2682
+ };
2683
+ metrics?.beginSerialize();
2684
+ postMessage(message);
2685
+ metrics?.endSerialize();
2686
+ emitMetricsSample(metrics ?? null);
2687
+ if (metrics && getActiveMetricsCollector() === metrics) {
2688
+ setActiveMetricsCollector(null);
2689
+ }
2690
+ }
2691
+ async function diffBlockContent(previousBlocks, nextBlocks, changedRanges, metrics) {
2692
+ const patches = [];
2693
+ const prevMap = new Map(previousBlocks.map((block) => [block.id, block]));
2694
+ const changedBlockIds = collectChangedBlockIds(previousBlocks, nextBlocks, changedRanges);
2695
+ for (const nextBlock of nextBlocks) {
2696
+ const prevBlock = prevMap.get(nextBlock.id);
2697
+ if (!prevBlock) continue;
2698
+ if (prevBlock.type === "mdx" && nextBlock.type === "mdx") {
2699
+ preserveMdxMetadata(prevBlock, nextBlock);
2700
+ }
2701
+ if (changedBlockIds.size > 0 && !changedBlockIds.has(nextBlock.id) && (0, import_core.blocksStructurallyEqual)(prevBlock, nextBlock)) {
2702
+ continue;
2703
+ }
2704
+ if (changedBlockIds.size === 0 && (0, import_core.blocksStructurallyEqual)(prevBlock, nextBlock)) continue;
2705
+ if (nextBlock.type === "html") {
2706
+ const nextSanitized = nextBlock.payload.sanitizedHtml ?? (0, import_core2.sanitizeHtmlInWorker)(nextBlock.payload.raw ?? "");
2707
+ const prevSanitized = prevBlock.payload.sanitizedHtml ?? "";
2708
+ if (prevSanitized !== nextSanitized || prevBlock.payload.raw !== nextBlock.payload.raw) {
2709
+ const cloned = (0, import_core.cloneBlock)(nextBlock);
2710
+ cloned.payload.sanitizedHtml = nextSanitized;
2711
+ patches.push({
2712
+ op: "setHTML",
2713
+ at: { blockId: nextBlock.id },
2714
+ html: nextSanitized,
2715
+ policy: "markdown-renderer-v2",
2716
+ block: cloned,
2717
+ meta: nextBlock.payload.meta ? { ...nextBlock.payload.meta } : void 0,
2718
+ sanitized: true
2719
+ });
2720
+ }
2721
+ continue;
2722
+ }
2723
+ const prevSnapshot = await blockToNodeSnapshot(prevBlock);
2724
+ const nextSnapshot = await blockToNodeSnapshot(nextBlock);
2725
+ diffNodeSnapshot(nextBlock.id, prevSnapshot, nextSnapshot, patches, metrics);
2726
+ }
2727
+ return { patches, changedBlockCount: changedBlockIds.size };
2728
+ }
2729
+ function preserveMdxMetadata(previous, next) {
2730
+ const prevRaw = previous.payload.raw ?? "";
2731
+ const nextRaw = next.payload.raw ?? "";
2732
+ if (prevRaw !== nextRaw) {
2733
+ return;
2734
+ }
2735
+ if (previous.payload.compiledMdxRef && !next.payload.compiledMdxRef) {
2736
+ next.payload.compiledMdxRef = { ...previous.payload.compiledMdxRef };
2737
+ }
2738
+ if (previous.payload.compiledMdxModule && !next.payload.compiledMdxModule) {
2739
+ next.payload.compiledMdxModule = {
2740
+ ...previous.payload.compiledMdxModule,
2741
+ dependencies: previous.payload.compiledMdxModule.dependencies ? [...previous.payload.compiledMdxModule.dependencies] : void 0
2742
+ };
2743
+ } else if (previous.payload.compiledMdxModule === null && typeof next.payload.compiledMdxModule === "undefined") {
2744
+ next.payload.compiledMdxModule = null;
2745
+ }
2746
+ const prevMeta = previous.payload.meta ?? null;
2747
+ if (!prevMeta) {
2748
+ return;
2749
+ }
2750
+ const currentMeta = next.payload.meta ?? void 0;
2751
+ const mergedMeta = currentMeta ? { ...currentMeta } : {};
2752
+ let metaChanged = false;
2753
+ if (typeof prevMeta.mdxStatus === "string" && mergedMeta.mdxStatus === void 0) {
2754
+ mergedMeta.mdxStatus = prevMeta.mdxStatus;
2755
+ metaChanged = true;
2756
+ }
2757
+ if (typeof prevMeta.mdxError === "string" && mergedMeta.mdxError === void 0) {
2758
+ mergedMeta.mdxError = prevMeta.mdxError;
2759
+ metaChanged = true;
2760
+ }
2761
+ if (metaChanged || !currentMeta) {
2762
+ next.payload.meta = mergedMeta;
2763
+ }
2764
+ }
2765
+ function diffNodeSnapshot(blockId, prevNode, nextNode, patches, metrics) {
2766
+ const prevProps = prevNode.props ?? {};
2767
+ const nextProps = nextNode.props ?? {};
2768
+ if (!shallowEqual(prevProps, nextProps)) {
2769
+ patches.push({
2770
+ op: "setProps",
2771
+ at: { blockId, nodeId: prevNode.id },
2772
+ props: nextProps ?? {}
2773
+ });
2774
+ }
2775
+ const prevChildren = prevNode.children ?? [];
2776
+ const nextChildren = nextNode.children ?? [];
2777
+ if (prevNode.type === "list" && nextNode.type === "list") {
2778
+ diffListChildren(blockId, prevNode, prevChildren, nextChildren, patches, metrics);
2779
+ return;
2780
+ }
2781
+ if (prevNode.type === "code" && nextNode.type === "code") {
2782
+ const commonLength = Math.min(prevChildren.length, nextChildren.length);
2783
+ let divergeIndex = 0;
2784
+ while (divergeIndex < commonLength && prevChildren[divergeIndex].id === nextChildren[divergeIndex].id) {
2785
+ diffNodeSnapshot(blockId, prevChildren[divergeIndex], nextChildren[divergeIndex], patches, metrics);
2786
+ divergeIndex++;
2787
+ }
2788
+ const onlyAppend = divergeIndex === prevChildren.length && nextChildren.length >= prevChildren.length && prevChildren.every((child, idx) => child.id === nextChildren[idx].id);
2789
+ if (onlyAppend && nextChildren.length > prevChildren.length) {
2790
+ const startIndex = prevChildren.length;
2791
+ const appended = nextChildren.slice(startIndex);
2792
+ const hasTokenLines = appended.some((child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens"));
2793
+ const hasDiffKind = appended.some((child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "diffKind"));
2794
+ const hasOldNo = appended.some((child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "oldNo"));
2795
+ const hasNewNo = appended.some((child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "newNo"));
2796
+ patches.push({
2797
+ op: "appendLines",
2798
+ at: { blockId, nodeId: prevNode.id },
2799
+ startIndex,
2800
+ lines: appended.map((child) => {
2801
+ const text = typeof child.props?.text === "string" ? child.props?.text : "";
2802
+ return text;
2803
+ }),
2804
+ highlight: appended.map((child) => typeof child.props?.html === "string" ? child.props?.html : null),
2805
+ ...hasTokenLines ? {
2806
+ tokens: appended.map(
2807
+ (child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens") ? child.props?.tokens : null
2808
+ )
2809
+ } : {},
2810
+ ...hasDiffKind ? {
2811
+ diffKind: appended.map(
2812
+ (child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "diffKind") ? child.props?.diffKind : null
2813
+ )
2814
+ } : {},
2815
+ ...hasOldNo ? {
2816
+ oldNo: appended.map(
2817
+ (child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "oldNo") ? child.props?.oldNo : null
2818
+ )
2819
+ } : {},
2820
+ ...hasNewNo ? {
2821
+ newNo: appended.map(
2822
+ (child) => Object.prototype.hasOwnProperty.call(child.props ?? {}, "newNo") ? child.props?.newNo : null
2823
+ )
2824
+ } : {}
2825
+ });
2826
+ metrics?.recordAppendLines(appended.length);
2827
+ return;
2828
+ }
2829
+ }
2830
+ const minLen = Math.min(prevChildren.length, nextChildren.length);
2831
+ let prefix = 0;
2832
+ while (prefix < minLen && prevChildren[prefix].id === nextChildren[prefix].id) {
2833
+ diffNodeSnapshot(blockId, prevChildren[prefix], nextChildren[prefix], patches, metrics);
2834
+ prefix++;
2835
+ }
2836
+ let suffix = 0;
2837
+ while (suffix < minLen - prefix && prevChildren[prevChildren.length - 1 - suffix].id === nextChildren[nextChildren.length - 1 - suffix].id) {
2838
+ const prevIdx = prevChildren.length - 1 - suffix;
2839
+ const nextIdx = nextChildren.length - 1 - suffix;
2840
+ diffNodeSnapshot(blockId, prevChildren[prevIdx], nextChildren[nextIdx], patches, metrics);
2841
+ suffix++;
2842
+ }
2843
+ const prevMid = prevChildren.slice(prefix, prevChildren.length - suffix);
2844
+ const nextMid = nextChildren.slice(prefix, nextChildren.length - suffix);
2845
+ if (prevMid.length === 0 && nextMid.length === 0) {
2846
+ return;
2847
+ }
2848
+ if (prevMid.length === nextMid.length && prevMid.length > 0) {
2849
+ const prevIds = prevMid.map((child) => child.id);
2850
+ const nextIds = nextMid.map((child) => child.id);
2851
+ if (haveSameMultiset(prevIds, nextIds)) {
2852
+ const currentOrder = prevIds.slice();
2853
+ const reorderOps = [];
2854
+ for (let targetIdx = 0; targetIdx < nextIds.length; targetIdx++) {
2855
+ const desiredId = nextIds[targetIdx];
2856
+ if (currentOrder[targetIdx] === desiredId) {
2857
+ continue;
2858
+ }
2859
+ const sourceIdx = currentOrder.indexOf(desiredId);
2860
+ if (sourceIdx === -1) continue;
2861
+ reorderOps.push({
2862
+ from: prefix + sourceIdx,
2863
+ to: prefix + targetIdx
2864
+ });
2865
+ const [moved] = currentOrder.splice(sourceIdx, 1);
2866
+ currentOrder.splice(targetIdx, 0, moved);
2867
+ }
2868
+ for (const op of reorderOps) {
2869
+ patches.push({
2870
+ op: "reorder",
2871
+ at: { blockId, nodeId: prevNode.id },
2872
+ from: op.from,
2873
+ to: op.to,
2874
+ count: 1
2875
+ });
2876
+ }
2877
+ const prevMap = new Map(prevMid.map((child) => [child.id, child]));
2878
+ for (const child of nextMid) {
2879
+ const previous = prevMap.get(child.id);
2880
+ if (previous) {
2881
+ diffNodeSnapshot(blockId, previous, child, patches, metrics);
2882
+ }
2883
+ }
2884
+ return;
2885
+ }
2886
+ }
2887
+ for (let i = prevChildren.length - 1 - suffix; i >= prefix; i--) {
2888
+ patches.push({
2889
+ op: "deleteChild",
2890
+ at: { blockId, nodeId: prevNode.id },
2891
+ index: i
2892
+ });
2893
+ }
2894
+ for (let i = prefix; i < nextChildren.length - suffix; i++) {
2895
+ patches.push({
2896
+ op: "insertChild",
2897
+ at: { blockId, nodeId: prevNode.id },
2898
+ index: i,
2899
+ node: nextChildren[i]
2900
+ });
2901
+ }
2902
+ }
2903
+ function shallowEqual(a, b) {
2904
+ const keysA = Object.keys(a);
2905
+ const keysB = Object.keys(b);
2906
+ if (keysA.length !== keysB.length) return false;
2907
+ for (const key of keysA) {
2908
+ const valA = a[key];
2909
+ const valB = b[key];
2910
+ if (typeof valA === "object" || typeof valB === "object") {
2911
+ if (JSON.stringify(valA) !== JSON.stringify(valB)) return false;
2912
+ } else if (valA !== valB) {
2913
+ return false;
2914
+ }
2915
+ }
2916
+ return true;
2917
+ }
2918
+ function haveSameMultiset(a, b) {
2919
+ if (a.length !== b.length) return false;
2920
+ const counts = /* @__PURE__ */ new Map();
2921
+ for (const id of a) {
2922
+ counts.set(id, (counts.get(id) ?? 0) + 1);
2923
+ }
2924
+ for (const id of b) {
2925
+ const next = (counts.get(id) ?? 0) - 1;
2926
+ if (next < 0) return false;
2927
+ counts.set(id, next);
2928
+ }
2929
+ return Array.from(counts.values()).every((val) => val === 0);
2930
+ }
2931
+ function diffListChildren(blockId, listNode, prevChildren, nextChildren, patches, metrics) {
2932
+ const prevLength = prevChildren.length;
2933
+ const nextLength = nextChildren.length;
2934
+ if (prevLength === 0 && nextLength === 0) {
2935
+ return;
2936
+ }
2937
+ const commonLength = Math.min(prevLength, nextLength);
2938
+ let prefix = 0;
2939
+ while (prefix < commonLength && prevChildren[prefix].id === nextChildren[prefix].id) {
2940
+ diffNodeSnapshot(blockId, prevChildren[prefix], nextChildren[prefix], patches, metrics);
2941
+ prefix++;
2942
+ }
2943
+ if (prefix === prevLength && prefix === nextLength) {
2944
+ return;
2945
+ }
2946
+ if (prefix === prevLength && nextLength > prevLength) {
2947
+ for (let i = prefix; i < nextLength; i++) {
2948
+ patches.push({
2949
+ op: "insertChild",
2950
+ at: { blockId, nodeId: listNode.id },
2951
+ index: i,
2952
+ node: nextChildren[i]
2953
+ });
2954
+ }
2955
+ return;
2956
+ }
2957
+ if (prefix === nextLength && prevLength > nextLength) {
2958
+ for (let i = prevLength - 1; i >= nextLength; i--) {
2959
+ patches.push({
2960
+ op: "deleteChild",
2961
+ at: { blockId, nodeId: listNode.id },
2962
+ index: i
2963
+ });
2964
+ }
2965
+ return;
2966
+ }
2967
+ let suffix = 0;
2968
+ while (suffix < commonLength - prefix && prevChildren[prevLength - 1 - suffix].id === nextChildren[nextLength - 1 - suffix].id) {
2969
+ const prevIdx = prevLength - 1 - suffix;
2970
+ const nextIdx = nextLength - 1 - suffix;
2971
+ diffNodeSnapshot(blockId, prevChildren[prevIdx], nextChildren[nextIdx], patches, metrics);
2972
+ suffix++;
2973
+ }
2974
+ const prevMidStart = prefix;
2975
+ const prevMidEnd = prevLength - suffix;
2976
+ const nextMidStart = prefix;
2977
+ const nextMidEnd = nextLength - suffix;
2978
+ const prevMidLen = Math.max(0, prevMidEnd - prevMidStart);
2979
+ const nextMidLen = Math.max(0, nextMidEnd - nextMidStart);
2980
+ if (prevMidLen === 0 && nextMidLen === 0) {
2981
+ return;
2982
+ }
2983
+ if (prevMidLen === 0 && nextMidLen > 0) {
2984
+ for (let i = nextMidStart; i < nextMidEnd; i++) {
2985
+ patches.push({
2986
+ op: "insertChild",
2987
+ at: { blockId, nodeId: listNode.id },
2988
+ index: i,
2989
+ node: nextChildren[i]
2990
+ });
2991
+ }
2992
+ return;
2993
+ }
2994
+ if (prevMidLen > 0 && nextMidLen === 0) {
2995
+ for (let i = prevMidEnd - 1; i >= prevMidStart; i--) {
2996
+ patches.push({
2997
+ op: "deleteChild",
2998
+ at: { blockId, nodeId: listNode.id },
2999
+ index: i
3000
+ });
3001
+ }
3002
+ return;
3003
+ }
3004
+ const prevMid = prevChildren.slice(prevMidStart, prevMidEnd);
3005
+ const nextMid = nextChildren.slice(nextMidStart, nextMidEnd);
3006
+ const sharedMid = Math.min(prevMid.length, nextMid.length);
3007
+ let midPrefix = 0;
3008
+ while (midPrefix < sharedMid && prevMid[midPrefix].id === nextMid[midPrefix].id) {
3009
+ diffNodeSnapshot(blockId, prevMid[midPrefix], nextMid[midPrefix], patches, metrics);
3010
+ midPrefix++;
3011
+ }
3012
+ if (midPrefix === prevMid.length && midPrefix === nextMid.length) {
3013
+ return;
3014
+ }
3015
+ if (midPrefix === prevMid.length && nextMid.length > prevMid.length) {
3016
+ for (let i = nextMidStart + midPrefix; i < nextMidEnd; i++) {
3017
+ patches.push({
3018
+ op: "insertChild",
3019
+ at: { blockId, nodeId: listNode.id },
3020
+ index: i,
3021
+ node: nextChildren[i]
3022
+ });
3023
+ }
3024
+ return;
3025
+ }
3026
+ if (midPrefix === nextMid.length && prevMid.length > nextMid.length) {
3027
+ for (let i = prevMidEnd - 1; i >= prevMidStart + midPrefix; i--) {
3028
+ patches.push({
3029
+ op: "deleteChild",
3030
+ at: { blockId, nodeId: listNode.id },
3031
+ index: i
3032
+ });
3033
+ }
3034
+ return;
3035
+ }
3036
+ const remainingPrevMid = prevMid.slice(midPrefix);
3037
+ const remainingNextMid = nextMid.slice(midPrefix);
3038
+ if (remainingPrevMid.length === remainingNextMid.length && remainingPrevMid.every((child, idx) => child.id === remainingNextMid[idx].id)) {
3039
+ for (let idx = 0; idx < remainingPrevMid.length; idx++) {
3040
+ diffNodeSnapshot(blockId, remainingPrevMid[idx], remainingNextMid[idx], patches, metrics);
3041
+ }
3042
+ return;
3043
+ }
3044
+ for (let i = prevMidEnd - 1; i >= prevMidStart + midPrefix; i--) {
3045
+ patches.push({
3046
+ op: "deleteChild",
3047
+ at: { blockId, nodeId: listNode.id },
3048
+ index: i
3049
+ });
3050
+ }
3051
+ for (let i = nextMidStart + midPrefix; i < nextMidEnd; i++) {
3052
+ patches.push({
3053
+ op: "insertChild",
3054
+ at: { blockId, nodeId: listNode.id },
3055
+ index: i,
3056
+ node: nextChildren[i]
3057
+ });
3058
+ }
3059
+ }
3060
+ function computeChangedRanges(previous, next) {
3061
+ if (previous === next) {
3062
+ return [];
3063
+ }
3064
+ let from = 0;
3065
+ const minLen = Math.min(previous.length, next.length);
3066
+ while (from < minLen && previous.charCodeAt(from) === next.charCodeAt(from)) {
3067
+ from++;
3068
+ }
3069
+ let toA = previous.length;
3070
+ let toB = next.length;
3071
+ while (toA > from && toB > from && previous.charCodeAt(toA - 1) === next.charCodeAt(toB - 1)) {
3072
+ toA--;
3073
+ toB--;
3074
+ }
3075
+ return [
3076
+ {
3077
+ fromA: from,
3078
+ toA,
3079
+ fromB: from,
3080
+ toB
3081
+ }
3082
+ ];
3083
+ }
3084
+ function collectChangedBlockIds(previousBlocks, nextBlocks, ranges) {
3085
+ const ids = /* @__PURE__ */ new Set();
3086
+ if (!ranges || ranges.length === 0) {
3087
+ return ids;
3088
+ }
3089
+ for (const range of ranges) {
3090
+ addIntersectingBlocks(ids, previousBlocks, range.fromA, range.toA);
3091
+ addIntersectingBlocks(ids, nextBlocks, range.fromB, range.toB);
3092
+ }
3093
+ return ids;
3094
+ }
3095
+ function addIntersectingBlocks(target, blocks2, from, to) {
3096
+ if (from === to) {
3097
+ for (const block of blocks2) {
3098
+ const range = block.payload.range;
3099
+ if (!range) continue;
3100
+ if (from >= range.from && from <= range.to) {
3101
+ target.add(block.id);
3102
+ break;
3103
+ }
3104
+ }
3105
+ return;
3106
+ }
3107
+ for (const block of blocks2) {
3108
+ const range = block.payload.range;
3109
+ if (!range) continue;
3110
+ if (rangesIntersect(range.from, range.to, from, to)) {
3111
+ target.add(block.id);
3112
+ }
3113
+ }
3114
+ }
3115
+ function rangesIntersect(aFrom, aTo, bFrom, bTo) {
3116
+ return Math.max(aFrom, bFrom) <= Math.min(aTo, bTo);
3117
+ }
3118
+ function reportWorkerError(error, phase, meta) {
3119
+ const payload = error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : { message: typeof error === "string" ? error : JSON.stringify(error) };
3120
+ postMessage({
3121
+ type: "ERROR",
3122
+ phase,
3123
+ error: payload,
3124
+ meta,
3125
+ timestamp: Date.now()
3126
+ });
3127
+ }
3128
+ async function finalizeAllBlocks() {
3129
+ const metricsCollector = new WorkerMetricsCollector(workerGrammarEngine);
3130
+ setActiveMetricsCollector(metricsCollector);
3131
+ const prevBlocks = blocks.map((block) => (0, import_core.cloneBlock)(block));
3132
+ const finalizePatches = [];
3133
+ const finalizeTargets = /* @__PURE__ */ new Set();
3134
+ for (const block of blocks) {
3135
+ if (!block.isFinalized) {
3136
+ block.isFinalized = true;
3137
+ finalizeTargets.add(block.id);
3138
+ finalizePatches.push({
3139
+ op: "finalize",
3140
+ at: { blockId: block.id }
3141
+ });
3142
+ }
3143
+ }
3144
+ if (finalizePatches.length > 0) {
3145
+ postMessage({
3146
+ type: "PATCH",
3147
+ tx: ++txCounter,
3148
+ patches: finalizePatches
3149
+ });
3150
+ }
3151
+ const { blocks: reparsedBlocks, lastTree: reparsedTree } = await parseAll(currentContent, { forceFinalize: true });
3152
+ for (const block of reparsedBlocks) {
3153
+ block.isFinalized = true;
3154
+ await updateMdxCompilationState(block);
3155
+ }
3156
+ blocks = reparsedBlocks;
3157
+ lastTree = reparsedTree;
3158
+ metricsCollector.setBlocksProduced(blocks.length);
3159
+ const fullRange = [
3160
+ {
3161
+ fromA: 0,
3162
+ toA: currentContent.length,
3163
+ fromB: 0,
3164
+ toB: currentContent.length
3165
+ }
3166
+ ];
3167
+ await emitBlockDiffPatches(prevBlocks, blocks, fullRange, metricsCollector);
3168
+ if (prevBlocks && prevBlocks.length > 0) {
3169
+ const finalizeSetProps = [];
3170
+ for (const block of blocks) {
3171
+ if (!block.isFinalized || !finalizeTargets.has(block.id)) continue;
3172
+ finalizeSetProps.push({
3173
+ op: "setProps",
3174
+ at: { blockId: block.id, nodeId: block.id },
3175
+ props: {
3176
+ block: (0, import_core.cloneBlock)(block)
3177
+ }
3178
+ });
3179
+ }
3180
+ if (finalizeSetProps.length > 0) {
3181
+ dispatchPatchBatch(finalizeSetProps, metricsCollector);
3182
+ }
3183
+ }
3184
+ if (deferredPatchQueue.length > 0) {
3185
+ const previousCredits = workerCredits;
3186
+ workerCredits = 1;
3187
+ const maxIterations = Math.ceil(deferredPatchQueue.length / MAX_DEFERRED_FLUSH_PATCHES) + 8;
3188
+ for (let i = 0; i < maxIterations && deferredPatchQueue.length > 0; i++) {
3189
+ const before = deferredPatchQueue.length;
3190
+ flushDeferredPatches();
3191
+ if (deferredPatchQueue.length >= before) {
3192
+ break;
3193
+ }
3194
+ }
3195
+ workerCredits = previousCredits;
3196
+ }
3197
+ postMessage({
3198
+ type: "FINALIZED"
3199
+ });
3200
+ if (getActiveMetricsCollector() === metricsCollector) {
3201
+ setActiveMetricsCollector(null);
3202
+ }
3203
+ }
3204
+ function handleMdxStatus(blockId, update) {
3205
+ if (mdxCompileMode === "worker") {
3206
+ return;
3207
+ }
3208
+ const index = blocks.findIndex((b) => b.id === blockId);
3209
+ if (index === -1) return;
3210
+ const previous = blocks[index];
3211
+ const updated = (0, import_core.cloneBlock)(previous);
3212
+ updated.payload = {
3213
+ ...updated.payload,
3214
+ compiledMdxRef: update.compiledRef ?? updated.payload.compiledMdxRef,
3215
+ meta: {
3216
+ ...updated.payload.meta ?? {},
3217
+ mdxStatus: update.status,
3218
+ ...update.error ? { mdxError: update.error } : {}
3219
+ }
3220
+ };
3221
+ blocks[index] = updated;
3222
+ postMessage({
3223
+ type: "PATCH",
3224
+ tx: ++txCounter,
3225
+ patches: [
3226
+ {
3227
+ op: "setProps",
3228
+ at: { blockId },
3229
+ props: {
3230
+ block: updated
3231
+ }
3232
+ }
3233
+ ]
3234
+ });
3235
+ }
3236
+ async function processWorkerMessage(msg) {
3237
+ switch (msg.type) {
3238
+ case "INIT":
3239
+ await initialize(msg.initialContent, msg.prewarmLangs ?? [], msg.docPlugins, msg.mdx);
3240
+ return;
3241
+ case "APPEND":
3242
+ await handleAppend(msg.text);
3243
+ return;
3244
+ case "FINALIZE":
3245
+ await finalizeAllBlocks();
3246
+ return;
3247
+ case "DEBUG_STATE": {
3248
+ const blockTypeCounts = {};
3249
+ for (const block of blocks) {
3250
+ const key = block.type ?? "unknown";
3251
+ blockTypeCounts[key] = (blockTypeCounts[key] || 0) + 1;
3252
+ }
3253
+ let lastBlockType;
3254
+ let lastBlockRange;
3255
+ let lastBlockRawTail;
3256
+ const headingTexts = [];
3257
+ const tailBlocks = [];
3258
+ const headBlocks = [];
3259
+ const duplicateBlockIds = [];
3260
+ const seenBlockIds = /* @__PURE__ */ new Set();
3261
+ const headingIndices = {};
3262
+ for (const block of blocks) {
3263
+ if (seenBlockIds.has(block.id)) {
3264
+ if (duplicateBlockIds.length < 8) {
3265
+ duplicateBlockIds.push(block.id);
3266
+ }
3267
+ } else {
3268
+ seenBlockIds.add(block.id);
3269
+ }
3270
+ }
3271
+ if (blocks.length > 0) {
3272
+ const lastBlock = blocks[blocks.length - 1];
3273
+ lastBlockType = lastBlock.type;
3274
+ const range = lastBlock.payload.range;
3275
+ if (range && typeof range.from === "number" && typeof range.to === "number") {
3276
+ lastBlockRange = { from: range.from, to: range.to };
3277
+ }
3278
+ const raw = typeof lastBlock.payload.raw === "string" ? lastBlock.payload.raw : "";
3279
+ lastBlockRawTail = raw ? raw.slice(Math.max(0, raw.length - 240)) : void 0;
3280
+ for (const block of blocks) {
3281
+ if (block.type === "heading" && typeof block.payload.raw === "string") {
3282
+ headingTexts.push(block.payload.raw);
3283
+ }
3284
+ }
3285
+ const targets = ["HTML and MDX Testing", "Inline Code", "Code Blocks", "Media", "Tables", "Footnotes"];
3286
+ for (let i = 0; i < blocks.length; i++) {
3287
+ const block = blocks[i];
3288
+ if (block.type === "heading" && typeof block.payload.raw === "string") {
3289
+ if (targets.includes(block.payload.raw)) {
3290
+ headingIndices[block.payload.raw] = i;
3291
+ }
3292
+ }
3293
+ }
3294
+ const tailStart = Math.max(0, blocks.length - 8);
3295
+ for (let i = tailStart; i < blocks.length; i++) {
3296
+ const block = blocks[i];
3297
+ const raw2 = typeof block.payload.raw === "string" ? block.payload.raw : "";
3298
+ tailBlocks.push({
3299
+ id: block.id,
3300
+ type: block.type,
3301
+ raw: raw2.slice(0, 120)
3302
+ });
3303
+ }
3304
+ const headEnd = Math.min(8, blocks.length);
3305
+ for (let i = 0; i < headEnd; i++) {
3306
+ const block = blocks[i];
3307
+ const raw2 = typeof block.payload.raw === "string" ? block.payload.raw : "";
3308
+ headBlocks.push({
3309
+ id: block.id,
3310
+ type: block.type,
3311
+ raw: raw2.slice(0, 120)
3312
+ });
3313
+ }
3314
+ }
3315
+ const contentTail = currentContent.slice(Math.max(0, currentContent.length - 500));
3316
+ postMessage({
3317
+ type: "DEBUG_STATE",
3318
+ state: {
3319
+ contentLength: currentContent.length,
3320
+ contentTail,
3321
+ blockCount: blocks.length,
3322
+ blockTypeCounts,
3323
+ lastBlockType,
3324
+ lastBlockRange,
3325
+ lastBlockRawTail,
3326
+ headingTexts,
3327
+ headBlocks,
3328
+ tailBlocks,
3329
+ headingIndices,
3330
+ lastDiffSummary,
3331
+ lastStructuralDiffSummary,
3332
+ maxEmittedBlockCount,
3333
+ maxDeferredQueueSize,
3334
+ maxStructuralBlockCount,
3335
+ lastHighCountNoStructural,
3336
+ duplicateBlockIds,
3337
+ duplicateBlockCount: duplicateBlockIds.length,
3338
+ hasInlineCodeHeading: currentContent.includes("# Inline Code"),
3339
+ hasCodeBlocksHeading: currentContent.includes("# Code Blocks"),
3340
+ hasMediaHeading: currentContent.includes("# Media")
3341
+ }
3342
+ });
3343
+ return;
3344
+ }
3345
+ case "DUMP_BLOCKS": {
3346
+ postMessage({
3347
+ type: "DUMP_BLOCKS",
3348
+ blocks,
3349
+ tocHeadings
3350
+ });
3351
+ return;
3352
+ }
3353
+ case "TOKENIZE_RANGE": {
3354
+ const priority = msg.priority === "prefetch" ? "prefetch" : "visible";
3355
+ enqueueLazyTokenization({
3356
+ blockId: msg.blockId,
3357
+ startLine: msg.startLine,
3358
+ endLine: msg.endLine,
3359
+ priority,
3360
+ requestedAt: now()
3361
+ });
3362
+ return;
3363
+ }
3364
+ case "MDX_COMPILED":
3365
+ handleMdxStatus(msg.blockId, {
3366
+ compiledRef: { id: msg.compiledId },
3367
+ status: "compiled"
3368
+ });
3369
+ return;
3370
+ case "MDX_ERROR":
3371
+ handleMdxStatus(msg.blockId, {
3372
+ compiledRef: void 0,
3373
+ status: "error",
3374
+ error: msg.error
3375
+ });
3376
+ return;
3377
+ case "SET_CREDITS":
3378
+ workerCredits = Math.max(0, Math.min(1, Number(msg.credits ?? 0)));
3379
+ if (workerCredits > 0) {
3380
+ flushDeferredPatches();
3381
+ }
3382
+ return;
3383
+ default:
3384
+ console.warn("Unknown message type:", msg);
3385
+ }
3386
+ }
3387
+ var import_markdown, import_engine_javascript, import_shiki, import_core, import_core2, import_core3, import_patch_batching, import_plugins, highlighter, blocks, lastTree, currentContent, inlineParser, performanceTimer, documentPluginState, txCounter, workerGrammarEngine, workerCredits, lastDiffSummary, lastStructuralDiffSummary, maxEmittedBlockCount, maxDeferredQueueSize, maxStructuralBlockCount, tocHeadings, tocSignature, headingIdCounts, lastHighCountNoStructural, MAX_DEFERRED_PATCHES, deferredPatchQueue, MAX_DEFERRED_FLUSH_PATCHES, CODE_HIGHLIGHT_CACHE, MAX_CODE_HIGHLIGHT_CACHE_ENTRIES, incrementalHighlightStates, lazyTokenizationStates, lazyTokenizationQueue, lazyTokenizationScheduled, lazyTokenizationProcessing, DEFAULT_LAZY_TOKENIZATION_THRESHOLD, MIN_LAZY_TOKENIZATION_THRESHOLD, MAX_LAZY_TOKENIZATION_THRESHOLD, lazyTokenizationEnabled, lazyTokenizationThresholdLines, CODE_HIGHLIGHT_THEMES, mdxCompileMode, formatAnticipationConfig, codeHighlightingMode, highlightOutputMode, emitHighlightTokens, emitDiffBlocks, liveTokenizationEnabled, enableMath, mdxComponentAllowlist, WORKER_MDX_CACHE, WORKER_MDX_INFLIGHT, MAX_WORKER_MDX_CACHE_ENTRIES, loggedMdxSkipCount, MAX_MDX_SKIP_LOGS, MIXED_CONTENT_AUTOCLOSE_NEWLINES, DEFAULT_APPEND_CHUNK_SIZE, DEFAULT_APPEND_BATCH_MS, MIN_APPEND_CHUNK_SIZE, MAX_APPEND_CHUNK_SIZE, MIN_APPEND_BATCH_MS, MAX_APPEND_BATCH_MS, APPEND_NEWLINE_GRACE, APPEND_CHUNK_SIZE, APPEND_BATCH_MS, DEBUG_MDX, sharedTextEncoder, shouldTrackInlineStatus, shouldAllowMixedStreaming, WorkerMetricsCollector, activeMetricsCollector, FONT_STYLE_ITALIC, FONT_STYLE_BOLD, FONT_STYLE_UNDERLINE, FONT_STYLE_STRIKETHROUGH, messageQueue;
3388
+ var init_worker = __esm({
3389
+ "src/worker.ts"() {
3390
+ "use strict";
3391
+ init_worker_dom_stub();
3392
+ import_markdown = require("@lezer/markdown");
3393
+ import_engine_javascript = require("@shikijs/engine-javascript");
3394
+ import_shiki = require("shiki");
3395
+ import_core = require("@stream-mdx/core");
3396
+ import_core2 = require("@stream-mdx/core");
3397
+ import_core3 = require("@stream-mdx/core");
3398
+ import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
3399
+ import_plugins = require("@stream-mdx/plugins");
3400
+ init_block_types();
3401
+ init_patch_heuristics();
3402
+ init_lazy_tokenization();
3403
+ init_mdx_compile();
3404
+ highlighter = null;
3405
+ blocks = [];
3406
+ lastTree = null;
3407
+ currentContent = "";
3408
+ documentPluginState = {};
3409
+ txCounter = 0;
3410
+ workerGrammarEngine = "js";
3411
+ workerCredits = 1;
3412
+ lastDiffSummary = null;
3413
+ lastStructuralDiffSummary = null;
3414
+ maxEmittedBlockCount = 0;
3415
+ maxDeferredQueueSize = 0;
3416
+ maxStructuralBlockCount = 0;
3417
+ tocHeadings = [];
3418
+ tocSignature = "";
3419
+ headingIdCounts = /* @__PURE__ */ new Map();
3420
+ lastHighCountNoStructural = null;
3421
+ MAX_DEFERRED_PATCHES = 400;
3422
+ deferredPatchQueue = [];
3423
+ MAX_DEFERRED_FLUSH_PATCHES = 120;
3424
+ CODE_HIGHLIGHT_CACHE = /* @__PURE__ */ new Map();
3425
+ MAX_CODE_HIGHLIGHT_CACHE_ENTRIES = 200;
3426
+ incrementalHighlightStates = /* @__PURE__ */ new Map();
3427
+ lazyTokenizationStates = /* @__PURE__ */ new Map();
3428
+ lazyTokenizationQueue = /* @__PURE__ */ new Map();
3429
+ lazyTokenizationScheduled = false;
3430
+ lazyTokenizationProcessing = false;
3431
+ DEFAULT_LAZY_TOKENIZATION_THRESHOLD = 200;
3432
+ MIN_LAZY_TOKENIZATION_THRESHOLD = 50;
3433
+ MAX_LAZY_TOKENIZATION_THRESHOLD = 1e4;
3434
+ lazyTokenizationEnabled = true;
3435
+ lazyTokenizationThresholdLines = DEFAULT_LAZY_TOKENIZATION_THRESHOLD;
3436
+ CODE_HIGHLIGHT_THEMES = { dark: "github-dark", light: "github-light" };
3437
+ mdxCompileMode = "server";
3438
+ formatAnticipationConfig = (0, import_core.normalizeFormatAnticipation)(false);
3439
+ codeHighlightingMode = "final";
3440
+ highlightOutputMode = "html";
3441
+ emitHighlightTokens = true;
3442
+ emitDiffBlocks = false;
3443
+ liveTokenizationEnabled = true;
3444
+ enableMath = true;
3445
+ mdxComponentAllowlist = null;
3446
+ WORKER_MDX_CACHE = /* @__PURE__ */ new Map();
3447
+ WORKER_MDX_INFLIGHT = /* @__PURE__ */ new Map();
3448
+ MAX_WORKER_MDX_CACHE_ENTRIES = 128;
3449
+ loggedMdxSkipCount = 0;
3450
+ MAX_MDX_SKIP_LOGS = 20;
3451
+ MIXED_CONTENT_AUTOCLOSE_NEWLINES = 2;
3452
+ DEFAULT_APPEND_CHUNK_SIZE = 1400;
3453
+ DEFAULT_APPEND_BATCH_MS = 12;
3454
+ MIN_APPEND_CHUNK_SIZE = 256;
3455
+ MAX_APPEND_CHUNK_SIZE = 12e3;
3456
+ MIN_APPEND_BATCH_MS = 4;
3457
+ MAX_APPEND_BATCH_MS = 50;
3458
+ APPEND_NEWLINE_GRACE = 200;
3459
+ APPEND_CHUNK_SIZE = clampInt(
3460
+ readNumericEnv("NEXT_PUBLIC_STREAMING_APPEND_CHUNK") ?? DEFAULT_APPEND_CHUNK_SIZE,
3461
+ MIN_APPEND_CHUNK_SIZE,
3462
+ MAX_APPEND_CHUNK_SIZE
3463
+ );
3464
+ APPEND_BATCH_MS = clampInt(
3465
+ readNumericEnv("NEXT_PUBLIC_STREAMING_APPEND_BATCH_MS") ?? DEFAULT_APPEND_BATCH_MS,
3466
+ MIN_APPEND_BATCH_MS,
3467
+ MAX_APPEND_BATCH_MS
3468
+ );
3469
+ DEBUG_MDX = isDebugEnabled("mdx");
3470
+ sharedTextEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
3471
+ shouldTrackInlineStatus = () => formatAnticipationConfig.inline || formatAnticipationConfig.mathInline || formatAnticipationConfig.mathBlock;
3472
+ shouldAllowMixedStreaming = () => formatAnticipationConfig.html || formatAnticipationConfig.mdx;
3473
+ WorkerMetricsCollector = class {
3474
+ constructor(grammarEngine) {
3475
+ this.parseStart = null;
3476
+ this.diffStart = null;
3477
+ this.serializeStart = null;
3478
+ this.parseMs = 0;
3479
+ this.enrichMs = 0;
3480
+ this.diffMs = 0;
3481
+ this.serializeMs = 0;
3482
+ this.shikiMs = 0;
3483
+ this.mdxDetectMs = 0;
3484
+ this.patchBytes = 0;
3485
+ this.patchCount = 0;
3486
+ this.queueDepth = 0;
3487
+ this.blocksProduced = 0;
3488
+ this.blockTypeCounts = /* @__PURE__ */ new Map();
3489
+ this.blockTypeDurations = /* @__PURE__ */ new Map();
3490
+ this.blockTypeSizes = /* @__PURE__ */ new Map();
3491
+ this.highlightByLanguage = /* @__PURE__ */ new Map();
3492
+ this.appendLineBatchCount = 0;
3493
+ this.appendLineTotalLines = 0;
3494
+ this.appendLineMaxLines = 0;
3495
+ this.lazyTokenizationRequests = 0;
3496
+ this.lazyTokenizationRangeTotal = 0;
3497
+ this.lazyTokenizationRangeMax = 0;
3498
+ this.lazyTokenizationLatencyTotal = 0;
3499
+ this.lazyTokenizationLatencyMax = 0;
3500
+ this.lazyTokenizationMaxQueue = 0;
3501
+ this.grammarEngine = grammarEngine;
3502
+ this.startedAt = now();
3503
+ }
3504
+ markParseStart() {
3505
+ this.parseStart = now();
3506
+ }
3507
+ markParseEnd() {
3508
+ if (this.parseStart === null) return;
3509
+ this.parseMs += Math.max(0, now() - this.parseStart);
3510
+ this.parseStart = null;
3511
+ }
3512
+ recordEnrich(duration) {
3513
+ if (!Number.isFinite(duration ?? Number.NaN)) return;
3514
+ this.enrichMs += Math.max(0, Number(duration));
3515
+ }
3516
+ recordShiki(duration) {
3517
+ if (!Number.isFinite(duration ?? Number.NaN)) return;
3518
+ this.shikiMs += Math.max(0, Number(duration));
3519
+ }
3520
+ recordMdxDetect(duration) {
3521
+ if (!Number.isFinite(duration ?? Number.NaN)) return;
3522
+ this.mdxDetectMs += Math.max(0, Number(duration));
3523
+ }
3524
+ countBlockType(type) {
3525
+ if (!type) return;
3526
+ this.blockTypeCounts.set(type, (this.blockTypeCounts.get(type) ?? 0) + 1);
3527
+ }
3528
+ recordBlockSize(type, size) {
3529
+ if (!type || !Number.isFinite(size ?? Number.NaN)) return;
3530
+ const current = this.blockTypeSizes.get(type) ?? 0;
3531
+ this.blockTypeSizes.set(type, current + Math.max(0, Number(size)));
3532
+ }
3533
+ recordBlockEnrich(type, duration) {
3534
+ if (!type || !Number.isFinite(duration ?? Number.NaN)) return;
3535
+ const previous = this.blockTypeDurations.get(type) ?? 0;
3536
+ this.blockTypeDurations.set(type, previous + Math.max(0, Number(duration)));
3537
+ }
3538
+ recordHighlightForLanguage(lang, duration) {
3539
+ if (!lang || !Number.isFinite(duration ?? Number.NaN)) return;
3540
+ const entry = this.highlightByLanguage.get(lang) ?? { time: 0, count: 0 };
3541
+ entry.time += Math.max(0, Number(duration));
3542
+ entry.count += 1;
3543
+ this.highlightByLanguage.set(lang, entry);
3544
+ }
3545
+ recordAppendLines(lineCount) {
3546
+ if (!Number.isFinite(lineCount ?? Number.NaN)) return;
3547
+ const normalized = Math.max(0, Math.floor(Number(lineCount)));
3548
+ if (normalized <= 0) return;
3549
+ this.appendLineBatchCount += 1;
3550
+ this.appendLineTotalLines += normalized;
3551
+ if (normalized > this.appendLineMaxLines) {
3552
+ this.appendLineMaxLines = normalized;
3553
+ }
3554
+ }
3555
+ recordLazyTokenization(rangeLines, latencyMs, queueDepth) {
3556
+ if (Number.isFinite(rangeLines ?? Number.NaN)) {
3557
+ const normalized = Math.max(0, Math.floor(Number(rangeLines)));
3558
+ this.lazyTokenizationRequests += 1;
3559
+ this.lazyTokenizationRangeTotal += normalized;
3560
+ if (normalized > this.lazyTokenizationRangeMax) {
3561
+ this.lazyTokenizationRangeMax = normalized;
3562
+ }
3563
+ }
3564
+ if (Number.isFinite(latencyMs ?? Number.NaN)) {
3565
+ const normalizedLatency = Math.max(0, Number(latencyMs));
3566
+ this.lazyTokenizationLatencyTotal += normalizedLatency;
3567
+ if (normalizedLatency > this.lazyTokenizationLatencyMax) {
3568
+ this.lazyTokenizationLatencyMax = normalizedLatency;
3569
+ }
3570
+ }
3571
+ if (Number.isFinite(queueDepth ?? Number.NaN)) {
3572
+ const normalizedQueue = Math.max(0, Math.floor(Number(queueDepth)));
3573
+ if (normalizedQueue > this.lazyTokenizationMaxQueue) {
3574
+ this.lazyTokenizationMaxQueue = normalizedQueue;
3575
+ }
3576
+ }
3577
+ }
3578
+ markDiffStart() {
3579
+ this.diffStart = now();
3580
+ }
3581
+ markDiffEnd() {
3582
+ if (this.diffStart === null) return;
3583
+ this.diffMs += Math.max(0, now() - this.diffStart);
3584
+ this.diffStart = null;
3585
+ }
3586
+ beginSerialize() {
3587
+ this.serializeStart = now();
3588
+ }
3589
+ endSerialize() {
3590
+ if (this.serializeStart === null) return;
3591
+ this.serializeMs += Math.max(0, now() - this.serializeStart);
3592
+ this.serializeStart = null;
3593
+ }
3594
+ finalizePatch(tx, patchCount, queueDepth, patchBytes) {
3595
+ this.tx = tx;
3596
+ this.patchCount = patchCount;
3597
+ this.queueDepth = queueDepth;
3598
+ this.patchBytes = patchBytes;
3599
+ }
3600
+ setBlocksProduced(count) {
3601
+ this.blocksProduced = count;
3602
+ }
3603
+ toPatchMetrics(changedBlocks) {
3604
+ return {
3605
+ patchCount: this.patchCount,
3606
+ changedBlocks,
3607
+ diffTime: roundMetric(this.diffMs),
3608
+ parseTime: roundMetric(this.parseMs),
3609
+ enrichTime: this.enrichMs ? roundMetric(this.enrichMs) : void 0,
3610
+ queueDepth: this.queueDepth || void 0,
3611
+ patchBytes: this.patchBytes || void 0,
3612
+ appendLineBatches: this.appendLineBatchCount || void 0,
3613
+ appendLineTotalLines: this.appendLineTotalLines || void 0,
3614
+ appendLineMaxLines: this.appendLineMaxLines || void 0
3615
+ };
3616
+ }
3617
+ toPerformanceMetrics() {
3618
+ return {
3619
+ tx: this.tx,
3620
+ timestamp: this.startedAt,
3621
+ parseMs: roundMetric(this.parseMs),
3622
+ parseTime: roundMetric(this.parseMs),
3623
+ enrichMs: this.enrichMs ? roundMetric(this.enrichMs) : void 0,
3624
+ diffMs: this.diffMs ? roundMetric(this.diffMs) : void 0,
3625
+ serializeMs: this.serializeMs ? roundMetric(this.serializeMs) : void 0,
3626
+ highlightTime: this.shikiMs ? roundMetric(this.shikiMs) : void 0,
3627
+ shikiMs: this.shikiMs ? roundMetric(this.shikiMs) : void 0,
3628
+ mdxDetectMs: this.mdxDetectMs ? roundMetric(this.mdxDetectMs) : void 0,
3629
+ patchBytes: this.patchBytes || void 0,
3630
+ patchCount: this.patchCount || void 0,
3631
+ queueDepth: this.queueDepth || void 0,
3632
+ blocksProduced: this.blocksProduced || void 0,
3633
+ grammarEngine: this.grammarEngine,
3634
+ blockCountByType: mapToNumberRecord(this.blockTypeCounts),
3635
+ blockEnrichMsByType: mapToNumberRecord(this.blockTypeDurations, true),
3636
+ blockSizeByType: mapToNumberRecord(this.blockTypeSizes),
3637
+ highlightByLanguage: mapToHighlightRecord(this.highlightByLanguage),
3638
+ appendLineBatches: this.appendLineBatchCount || void 0,
3639
+ appendLineTotalLines: this.appendLineTotalLines || void 0,
3640
+ appendLineMaxLines: this.appendLineMaxLines || void 0,
3641
+ lazyTokenization: this.lazyTokenizationRequests > 0 ? {
3642
+ requests: this.lazyTokenizationRequests,
3643
+ avgRangeLines: roundMetric(this.lazyTokenizationRangeTotal / this.lazyTokenizationRequests),
3644
+ maxRangeLines: this.lazyTokenizationRangeMax,
3645
+ avgLatencyMs: roundMetric(this.lazyTokenizationLatencyTotal / this.lazyTokenizationRequests),
3646
+ maxLatencyMs: roundMetric(this.lazyTokenizationLatencyMax),
3647
+ maxQueue: this.lazyTokenizationMaxQueue
3648
+ } : void 0
3649
+ };
3650
+ }
3651
+ };
3652
+ activeMetricsCollector = null;
3653
+ FONT_STYLE_ITALIC = 1;
3654
+ FONT_STYLE_BOLD = 2;
3655
+ FONT_STYLE_UNDERLINE = 4;
3656
+ FONT_STYLE_STRIKETHROUGH = 8;
3657
+ messageQueue = Promise.resolve();
3658
+ self.onmessage = (e) => {
3659
+ const msg = e.data;
3660
+ const task = messageQueue.then(async () => {
3661
+ try {
3662
+ await processWorkerMessage(msg);
3663
+ } catch (error) {
3664
+ console.error("Worker error:", error);
3665
+ reportWorkerError(error, msg.type, { phase: msg.type });
3666
+ }
3667
+ }).catch((error) => {
3668
+ console.error("Worker queue error:", error);
3669
+ reportWorkerError(error, msg.type, { phase: msg.type, queue: true });
3670
+ });
3671
+ messageQueue = task;
3672
+ return task;
3673
+ };
3674
+ }
3675
+ });
3676
+
3677
+ // src/direct.ts
3678
+ var direct_exports = {};
3679
+ __export(direct_exports, {
3680
+ compileMarkdownSnapshotDirect: () => compileMarkdownSnapshotDirect
3681
+ });
3682
+ module.exports = __toCommonJS(direct_exports);
3683
+
3684
+ // src/direct-compile.ts
3685
+ var import_core4 = require("@stream-mdx/core");
3686
+ var globalBridge = globalThis;
3687
+ var runtimePromise = null;
3688
+ var nodeCacheRuntimePromise = null;
3689
+ var activeQueue = null;
3690
+ var compileChain = Promise.resolve();
3691
+ var originalPostMessage;
3692
+ function withCompileLock(fn) {
3693
+ let resolveChain;
3694
+ const next = new Promise((resolve) => {
3695
+ resolveChain = resolve;
3696
+ });
3697
+ const previous = compileChain;
3698
+ compileChain = next;
3699
+ return previous.catch(() => void 0).then(async () => {
3700
+ try {
3701
+ return await fn();
3702
+ } finally {
3703
+ resolveChain();
3704
+ }
3705
+ });
3706
+ }
3707
+ async function ensureWorkerRuntimeLoaded() {
3708
+ if (runtimePromise) return runtimePromise;
3709
+ runtimePromise = (async () => {
3710
+ const scope = globalBridge.self ?? {};
3711
+ globalBridge.self = scope;
3712
+ if (!originalPostMessage && typeof globalBridge.postMessage === "function") {
3713
+ originalPostMessage = globalBridge.postMessage.bind(globalBridge);
3714
+ }
3715
+ globalBridge.postMessage = (message) => {
3716
+ if (activeQueue) {
3717
+ activeQueue.push(message);
3718
+ return;
3719
+ }
3720
+ if (originalPostMessage) {
3721
+ try {
3722
+ originalPostMessage(message);
3723
+ } catch {
3724
+ }
3725
+ }
3726
+ };
3727
+ await Promise.resolve().then(() => (init_worker(), worker_exports));
3728
+ if (typeof scope.onmessage !== "function") {
3729
+ throw new Error("[stream-mdx] direct compile runtime failed to initialize worker onmessage.");
3730
+ }
3731
+ return scope;
3732
+ })();
3733
+ return runtimePromise;
3734
+ }
3735
+ function isPromiseLike(value) {
3736
+ return Boolean(value && typeof value.then === "function");
3737
+ }
3738
+ async function dispatchWorkerMessage(scope, message, timeoutMs) {
3739
+ const queue = [];
3740
+ activeQueue = queue;
3741
+ try {
3742
+ const task = scope.onmessage ? scope.onmessage({ data: message }) : void 0;
3743
+ const awaited = isPromiseLike(task) ? task.then(() => void 0) : Promise.resolve();
3744
+ await withTimeout(awaited, timeoutMs, `[stream-mdx] direct compile timed out during ${message.type}`);
3745
+ await waitForQueueSettle(queue);
3746
+ return queue;
3747
+ } finally {
3748
+ activeQueue = null;
3749
+ }
3750
+ }
3751
+ async function waitForQueueSettle(queue, maxWaitMs = 64) {
3752
+ const start = Date.now();
3753
+ let lastLength = queue.length;
3754
+ let idleTicks = 0;
3755
+ while (Date.now() - start < maxWaitMs) {
3756
+ await new Promise((resolve) => setTimeout(resolve, 0));
3757
+ if (queue.length === lastLength) {
3758
+ idleTicks += 1;
3759
+ } else {
3760
+ lastLength = queue.length;
3761
+ idleTicks = 0;
3762
+ }
3763
+ if (idleTicks >= 2) return;
3764
+ }
3765
+ }
3766
+ async function withTimeout(promise, timeoutMs, label) {
3767
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
3768
+ return await promise;
3769
+ }
3770
+ return await new Promise((resolve, reject) => {
3771
+ const timer = setTimeout(() => reject(new Error(label)), timeoutMs);
3772
+ promise.then(
3773
+ (value) => {
3774
+ clearTimeout(timer);
3775
+ resolve(value);
3776
+ },
3777
+ (error) => {
3778
+ clearTimeout(timer);
3779
+ reject(error);
3780
+ }
3781
+ );
3782
+ });
3783
+ }
3784
+ function firstWorkerError(messages) {
3785
+ const errorMessage = messages.find((message) => message.type === "ERROR");
3786
+ if (!errorMessage) return null;
3787
+ return `Worker error (${errorMessage.phase}): ${errorMessage.error.message}`;
3788
+ }
3789
+ function extractPatches(messages) {
3790
+ return messages.filter((message) => message.type === "PATCH");
3791
+ }
3792
+ function stableStringify(value) {
3793
+ if (value === null || typeof value !== "object") {
3794
+ const primitive = JSON.stringify(value);
3795
+ return primitive === void 0 ? "null" : primitive;
3796
+ }
3797
+ if (Array.isArray(value)) {
3798
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
3799
+ }
3800
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
3801
+ const body = entries.map(([key, val]) => `${JSON.stringify(key)}:${stableStringify(val)}`).join(",");
3802
+ return `{${body}}`;
3803
+ }
3804
+ function fallbackHash(text) {
3805
+ let hash = 2166136261;
3806
+ for (let i = 0; i < text.length; i += 1) {
3807
+ hash ^= text.charCodeAt(i);
3808
+ hash = Math.imul(hash, 16777619);
3809
+ }
3810
+ return `fnv1a_${(hash >>> 0).toString(16).padStart(8, "0")}`;
3811
+ }
3812
+ async function sha256Hex(input) {
3813
+ try {
3814
+ const subtle = globalThis.crypto?.subtle;
3815
+ if (!subtle) return fallbackHash(input);
3816
+ const bytes = new TextEncoder().encode(input);
3817
+ const digest = await subtle.digest("SHA-256", bytes);
3818
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
3819
+ } catch {
3820
+ return fallbackHash(input);
3821
+ }
3822
+ }
3823
+ async function buildHashes(text, init, hashSalt) {
3824
+ const hashPayload = stableStringify({
3825
+ text,
3826
+ init: init ?? null,
3827
+ salt: hashSalt ?? null
3828
+ });
3829
+ const configPayload = stableStringify({
3830
+ init: init ?? null,
3831
+ salt: hashSalt ?? null
3832
+ });
3833
+ const [hash, contentHash, configHash] = await Promise.all([
3834
+ sha256Hex(hashPayload),
3835
+ sha256Hex(text),
3836
+ sha256Hex(configPayload)
3837
+ ]);
3838
+ return { hash, contentHash, configHash };
3839
+ }
3840
+ async function getNodeCacheRuntime() {
3841
+ if (nodeCacheRuntimePromise) return nodeCacheRuntimePromise;
3842
+ nodeCacheRuntimePromise = (async () => {
3843
+ try {
3844
+ const dynamicImport = new Function("specifier", "return import(specifier);");
3845
+ const fsModule = await dynamicImport("node:fs/promises");
3846
+ const pathModule = await dynamicImport("node:path");
3847
+ const pathApi = pathModule.default ?? pathModule;
3848
+ return {
3849
+ fs: fsModule,
3850
+ path: pathApi
3851
+ };
3852
+ } catch {
3853
+ return null;
3854
+ }
3855
+ })();
3856
+ return nodeCacheRuntimePromise;
3857
+ }
3858
+ async function resolveCachePath(cache, cacheKey) {
3859
+ if (!cache) return null;
3860
+ const runtime = await getNodeCacheRuntime();
3861
+ if (!runtime) return null;
3862
+ return runtime.path.join(cache.dir, `${sanitizeCacheKey(cacheKey)}.json`);
3863
+ }
3864
+ async function readSnapshotCache(cachePath, hash) {
3865
+ const runtime = await getNodeCacheRuntime();
3866
+ if (!runtime) return null;
3867
+ try {
3868
+ const raw = await runtime.fs.readFile(cachePath, "utf8");
3869
+ const parsed = JSON.parse(raw);
3870
+ if (!parsed || parsed.version !== 1 || parsed.hash !== hash || !Array.isArray(parsed.blocks)) {
3871
+ return null;
3872
+ }
3873
+ return parsed;
3874
+ } catch {
3875
+ return null;
3876
+ }
3877
+ }
3878
+ async function writeSnapshotCache(cachePath, artifact) {
3879
+ const runtime = await getNodeCacheRuntime();
3880
+ if (!runtime) return;
3881
+ try {
3882
+ await runtime.fs.mkdir(runtime.path.dirname(cachePath), { recursive: true });
3883
+ await runtime.fs.writeFile(cachePath, JSON.stringify(artifact, null, 2), "utf8");
3884
+ } catch {
3885
+ }
3886
+ }
3887
+ function sanitizeCacheKey(value) {
3888
+ return value.replace(/[^a-z0-9._-]+/gi, "_");
3889
+ }
3890
+ async function compileMarkdownSnapshotDirect(options) {
3891
+ return await withCompileLock(async () => {
3892
+ const { text, init, hashSalt, cache, timeoutMs = 3e4, settleMs = 50, finalize = true } = options;
3893
+ const { hash, contentHash, configHash } = await buildHashes(text, init, hashSalt);
3894
+ const cacheKey = cache?.key ?? hash;
3895
+ const cachePath = await resolveCachePath(cache, cacheKey);
3896
+ if (cachePath) {
3897
+ const cached = await readSnapshotCache(cachePath, hash);
3898
+ if (cached) {
3899
+ const snapshot2 = (0, import_core4.createInitialSnapshot)(cached.blocks);
3900
+ return {
3901
+ blocks: snapshot2.blocks,
3902
+ snapshot: snapshot2,
3903
+ artifact: cached,
3904
+ fromCache: true
3905
+ };
3906
+ }
3907
+ }
3908
+ const scope = await ensureWorkerRuntimeLoaded();
3909
+ const initMessages = await dispatchWorkerMessage(
3910
+ scope,
3911
+ {
3912
+ type: "INIT",
3913
+ initialContent: text,
3914
+ prewarmLangs: init?.prewarmLangs,
3915
+ docPlugins: init?.docPlugins,
3916
+ mdx: init?.mdx
3917
+ },
3918
+ timeoutMs
3919
+ );
3920
+ const initError = firstWorkerError(initMessages);
3921
+ if (initError) {
3922
+ throw new Error(initError);
3923
+ }
3924
+ const initialized = initMessages.find((message) => message.type === "INITIALIZED");
3925
+ if (!initialized) {
3926
+ throw new Error("[stream-mdx] direct compile failed: worker did not emit INITIALIZED.");
3927
+ }
3928
+ const snapshot = (0, import_core4.createInitialSnapshot)(initialized.blocks);
3929
+ for (const patch of extractPatches(initMessages)) {
3930
+ snapshot.blocks = (0, import_core4.applyPatchBatch)(snapshot, patch.patches);
3931
+ }
3932
+ if (finalize) {
3933
+ const finalizeMessages = await dispatchWorkerMessage(scope, { type: "FINALIZE" }, timeoutMs);
3934
+ const finalizeError = firstWorkerError(finalizeMessages);
3935
+ if (finalizeError) {
3936
+ throw new Error(finalizeError);
3937
+ }
3938
+ for (const patch of extractPatches(finalizeMessages)) {
3939
+ snapshot.blocks = (0, import_core4.applyPatchBatch)(snapshot, patch.patches);
3940
+ }
3941
+ } else {
3942
+ await new Promise((resolve) => setTimeout(resolve, settleMs));
3943
+ }
3944
+ const artifact = {
3945
+ version: 1,
3946
+ schemaId: "streammdx.snapshot.v1",
3947
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3948
+ hash,
3949
+ contentHash,
3950
+ configHash,
3951
+ hashSalt: hashSalt ?? void 0,
3952
+ blocks: snapshot.blocks,
3953
+ tocHeadings: (() => {
3954
+ const root = snapshot.nodes.get(import_core4.PATCH_ROOT_ID);
3955
+ const maybe = root?.props?.tocHeadings;
3956
+ return Array.isArray(maybe) ? maybe : void 0;
3957
+ })(),
3958
+ init: init ? {
3959
+ docPlugins: init.docPlugins,
3960
+ mdx: init.mdx,
3961
+ prewarmLangs: init.prewarmLangs
3962
+ } : void 0
3963
+ };
3964
+ if (cachePath && !cache?.readOnly) {
3965
+ await writeSnapshotCache(cachePath, artifact);
3966
+ }
3967
+ return {
3968
+ blocks: snapshot.blocks,
3969
+ snapshot,
3970
+ artifact,
3971
+ fromCache: false
3972
+ };
3973
+ });
3974
+ }
3975
+ // Annotate the CommonJS export names for ESM import in node:
3976
+ 0 && (module.exports = {
3977
+ compileMarkdownSnapshotDirect
3978
+ });