yume-dsl-rich-text 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -801,13 +801,13 @@ const tokens = dsl.parse(input);
801
801
  | Category | Exports |
802
802
  |-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
803
803
  | **Core** | `parseRichText`, `stripRichText`, `createParser`, `parseStructural`, `printStructural`, `buildZones` |
804
- | **Incremental Parsing** | `parseIncremental`, `updateIncremental`, `tryUpdateIncremental`, `createIncrementalSession` |
804
+ | **Incremental Parsing** | `parseIncremental`, `createIncrementalSession` |
805
805
  | **Configuration** | `DEFAULT_SYNTAX`, `createEasySyntax`, `createSyntax`, `DEFAULT_TAG_NAME`, `createTagNameConfig`, `createEasyStableId` |
806
806
  | **Handler Helpers** | `createPipeHandlers`, `createSimpleInlineHandlers`, `createSimpleBlockHandlers`, `createSimpleRawHandlers`, `declareMultilineTags` |
807
807
  | **Handler Utilities** | `parsePipeArgs`, `parsePipeTextArgs`, `parsePipeTextList`, `extractText`, `createTextToken`, `splitTokensByPipe`, `materializeTextTokens`, `unescapeInline`, `readEscapedSequence`, `createToken`, `createTokenGuard` |
808
808
  | **Token Traversal** | `walkTokens`, `mapTokens` |
809
809
  | **Position Tracking** | `buildPositionTracker` |
810
- | **Types** | `TextToken`, `TokenDraft`, `CreateId`, `DslContext`, `TagHandler`, `TagForm`, `ParseOptions`, `ParserBaseOptions`, `StructuralParseOptions`, `Parser`, `SyntaxInput`, `SyntaxConfig`, `TagNameConfig`, `BlockTagInput`, `MultilineForm`, `ErrorCode`, `ParseError`, `StructuralNode`, `SourcePosition`, `SourceSpan`, `PositionTracker`, `PipeArgs`, `PipeHandlerDefinition`, `EasyStableIdOptions`, `PrintOptions`, `TokenVisitContext`, `WalkVisitor`, `MapVisitor`, `Zone`, `IncrementalDocument`, `IncrementalEdit`, `IncrementalParseOptions`, `IncrementalUpdateErrorCode`, `IncrementalUpdateError`, `IncrementalUpdateResult`, `IncrementalSession`, `IncrementalSessionOptions`, `IncrementalSessionApplyMode`, `IncrementalSessionApplyResult`, `IncrementalSessionFallbackReason`, `IncrementalSessionStrategy`, `NarrowToken`, `NarrowDraft`, `NarrowTokenUnion` |
810
+ | **Types** | `TextToken`, `TokenDraft`, `CreateId`, `DslContext`, `TagHandler`, `TagForm`, `ParseOptions`, `ParserBaseOptions`, `StructuralParseOptions`, `Parser`, `SyntaxInput`, `SyntaxConfig`, `TagNameConfig`, `BlockTagInput`, `MultilineForm`, `ErrorCode`, `ParseError`, `StructuralNode`, `SourcePosition`, `SourceSpan`, `PositionTracker`, `PipeArgs`, `PipeHandlerDefinition`, `EasyStableIdOptions`, `PrintOptions`, `TokenVisitContext`, `WalkVisitor`, `MapVisitor`, `Zone`, `IncrementalDocument`, `IncrementalEdit`, `IncrementalParseOptions`, `IncrementalSessionOptions`, `NarrowToken`, `NarrowDraft`, `NarrowTokenUnion` |
811
811
 
812
812
  See the [Exports wiki page](https://github.com/chiba233/yumeDSL/wiki/en-Exports) for full signatures and detailed
813
813
  documentation.
package/dist/index.cjs CHANGED
@@ -56,9 +56,7 @@ __export(index_exports, {
56
56
  resetTokenIdSeed: () => resetTokenIdSeed,
57
57
  splitTokensByPipe: () => splitTokensByPipe,
58
58
  stripRichText: () => stripRichText,
59
- tryUpdateIncremental: () => tryUpdateIncremental,
60
59
  unescapeInline: () => unescapeInline,
61
- updateIncremental: () => updateIncremental,
62
60
  walkTokens: () => walkTokens,
63
61
  withSyntax: () => withSyntax,
64
62
  withTagNameConfig: () => withTagNameConfig
@@ -2252,10 +2250,53 @@ var isIncrementalUpdateError = (error) => {
2252
2250
  const withCode = error;
2253
2251
  return withCode.code === "INVALID_EDIT_RANGE" || withCode.code === "NEW_SOURCE_LENGTH_MISMATCH" || withCode.code === "EDIT_TEXT_MISMATCH" || withCode.code === "UNKNOWN";
2254
2252
  };
2253
+ var isPlainObject = (value) => {
2254
+ if (!value || typeof value !== "object") return false;
2255
+ const prototype = Object.getPrototypeOf(value);
2256
+ return prototype === Object.prototype || prototype === null;
2257
+ };
2258
+ var cloneSnapshotValueInternal = (value, seen) => {
2259
+ if (Array.isArray(value)) {
2260
+ const seenArray = seen.get(value);
2261
+ if (seenArray) return seenArray;
2262
+ const next = new Array(value.length);
2263
+ seen.set(value, next);
2264
+ for (let i = 0; i < value.length; i++) {
2265
+ next[i] = cloneSnapshotValueInternal(value[i], seen);
2266
+ }
2267
+ return next;
2268
+ }
2269
+ if (isPlainObject(value)) {
2270
+ const seenObject = seen.get(value);
2271
+ if (seenObject) return seenObject;
2272
+ const next = {};
2273
+ seen.set(value, next);
2274
+ const keys = Object.keys(value);
2275
+ for (let i = 0; i < keys.length; i++) {
2276
+ const key = keys[i];
2277
+ next[key] = cloneSnapshotValueInternal(value[key], seen);
2278
+ }
2279
+ return next;
2280
+ }
2281
+ return value;
2282
+ };
2283
+ var cloneSnapshotValue = (value) => cloneSnapshotValueInternal(value, /* @__PURE__ */ new WeakMap());
2284
+ var cloneHandlersSnapshot = (handlers) => {
2285
+ if (!handlers) return void 0;
2286
+ const next = {};
2287
+ const keys = Object.keys(handlers);
2288
+ for (let i = 0; i < keys.length; i++) {
2289
+ const key = keys[i];
2290
+ const handler = handlers[key];
2291
+ next[key] = handler ? cloneSnapshotValue(handler) : handler;
2292
+ }
2293
+ return next;
2294
+ };
2255
2295
  var cloneParseOptions = (options) => {
2256
2296
  if (!options) return void 0;
2257
2297
  return {
2258
2298
  ...options,
2299
+ handlers: cloneHandlersSnapshot(options.handlers),
2259
2300
  syntax: options.syntax ? { ...options.syntax } : void 0,
2260
2301
  tagName: options.tagName ? { ...options.tagName } : void 0,
2261
2302
  allowForms: options.allowForms ? [...options.allowForms] : void 0
@@ -2272,28 +2313,53 @@ var getObjectIdentity = (value) => {
2272
2313
  objectIdentityMap.set(value, next);
2273
2314
  return next;
2274
2315
  };
2316
+ var getIdentityForUnknown = (value) => {
2317
+ if (typeof value === "object" && value !== null || typeof value === "function") {
2318
+ return getObjectIdentity(value);
2319
+ }
2320
+ return 0;
2321
+ };
2322
+ var buildHandlersShapeFingerprint = (handlers) => {
2323
+ if (!handlers || typeof handlers !== "object") return 0;
2324
+ const record = handlers;
2325
+ const keys = Object.keys(record).sort();
2326
+ let hash = fnvInit();
2327
+ hash = fnvFeedU32(hash, keys.length);
2328
+ for (let i = 0; i < keys.length; i++) {
2329
+ const key = keys[i];
2330
+ const handler = record[key];
2331
+ hash = fnvFeedU32(hash, hashText(key));
2332
+ if (!handler || typeof handler !== "object") continue;
2333
+ const handlerRecord = handler;
2334
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.inline));
2335
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.raw));
2336
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.block));
2337
+ }
2338
+ return hash >>> 0;
2339
+ };
2340
+ var DEFAULT_PARSE_OPTIONS_FINGERPRINT = fnvFeedU32(fnvInit(), 2654435769);
2275
2341
  var buildParseOptionsFingerprint = (options) => {
2276
- if (!options) return "default";
2342
+ if (!options) return DEFAULT_PARSE_OPTIONS_FINGERPRINT;
2277
2343
  const syntax = options.syntax ?? {};
2278
2344
  const tagName = options.tagName ?? {};
2279
- return JSON.stringify({
2280
- handlersIdentity: getObjectIdentity(options.handlers),
2281
- allowForms: options.allowForms ?? [],
2282
- syntax: {
2283
- tagOpen: syntax.tagOpen ?? "",
2284
- tagClose: syntax.tagClose ?? "",
2285
- endTag: syntax.endTag ?? "",
2286
- tagDivider: syntax.tagDivider ?? "",
2287
- rawOpen: syntax.rawOpen ?? "",
2288
- rawClose: syntax.rawClose ?? "",
2289
- blockOpen: syntax.blockOpen ?? "",
2290
- blockClose: syntax.blockClose ?? ""
2291
- },
2292
- tagName: {
2293
- startIdentity: getObjectIdentity(tagName.isTagStartChar),
2294
- charIdentity: getObjectIdentity(tagName.isTagChar)
2295
- }
2296
- });
2345
+ const allowForms = options.allowForms ?? [];
2346
+ let hash = fnvInit();
2347
+ hash = fnvFeedU32(hash, buildHandlersShapeFingerprint(options.handlers));
2348
+ hash = fnvFeedU32(hash, allowForms.length);
2349
+ for (let i = 0; i < allowForms.length; i++) {
2350
+ hash = fnvFeedU32(hash, hashText(allowForms[i]));
2351
+ }
2352
+ hash = fnvFeedU32(hash, hashText(syntax.tagOpen ?? ""));
2353
+ hash = fnvFeedU32(hash, hashText(syntax.tagClose ?? ""));
2354
+ hash = fnvFeedU32(hash, hashText(syntax.endTag ?? ""));
2355
+ hash = fnvFeedU32(hash, hashText(syntax.tagDivider ?? ""));
2356
+ hash = fnvFeedU32(hash, hashText(syntax.rawOpen ?? ""));
2357
+ hash = fnvFeedU32(hash, hashText(syntax.rawClose ?? ""));
2358
+ hash = fnvFeedU32(hash, hashText(syntax.blockOpen ?? ""));
2359
+ hash = fnvFeedU32(hash, hashText(syntax.blockClose ?? ""));
2360
+ hash = fnvFeedU32(hash, getObjectIdentity(tagName.isTagStartChar));
2361
+ hash = fnvFeedU32(hash, getObjectIdentity(tagName.isTagChar));
2362
+ return hash >>> 0;
2297
2363
  };
2298
2364
  var hasUnsafeZoneCoverageTailGap = (doc, edit) => {
2299
2365
  const lastZone = doc.zones[doc.zones.length - 1];
@@ -2318,8 +2384,10 @@ var flattenZones = (zones) => {
2318
2384
  }
2319
2385
  return tree;
2320
2386
  };
2387
+ var LEFT_LOOKBEHIND_ZONES = 1;
2321
2388
  var RIGHT_REUSE_PROBE_ZONES = 2;
2322
2389
  var RIGHT_REUSE_PROBE_EXTRA_ZONES = 1;
2390
+ var RIGHT_REUSE_PROBE_SIGNATURE_NODE_BUDGET = 4096;
2323
2391
  var NODE_TAG_TEXT = 1;
2324
2392
  var NODE_TAG_ESCAPE = 2;
2325
2393
  var NODE_TAG_SEPARATOR = 3;
@@ -2328,9 +2396,20 @@ var NODE_TAG_RAW = 5;
2328
2396
  var NODE_TAG_BLOCK = 6;
2329
2397
  var ZONE_TAG = 7;
2330
2398
  var zoneSignatureCache = /* @__PURE__ */ new WeakMap();
2399
+ var parseOptionsFingerprintCache = /* @__PURE__ */ new WeakMap();
2331
2400
  var incrementalDebugSink;
2332
2401
  var hashText = (value) => fnv1a(value);
2333
- var nodeSignature = (node) => {
2402
+ var getCachedOptionsFingerprint = (doc) => parseOptionsFingerprintCache.get(doc);
2403
+ var setCachedOptionsFingerprint = (doc, fingerprint) => {
2404
+ parseOptionsFingerprintCache.set(doc, fingerprint);
2405
+ };
2406
+ var tryConsumeSignatureBudget = (budget) => {
2407
+ if (budget.remaining <= 0) return false;
2408
+ budget.remaining -= 1;
2409
+ return true;
2410
+ };
2411
+ var nodeSignature = (node, budget) => {
2412
+ if (budget && !tryConsumeSignatureBudget(budget)) return void 0;
2334
2413
  if (node.type === "text") {
2335
2414
  let hash = fnvInit();
2336
2415
  hash = fnvFeedU32(hash, NODE_TAG_TEXT);
@@ -2353,7 +2432,9 @@ var nodeSignature = (node) => {
2353
2432
  hash = fnvFeedU32(hash, NODE_TAG_INLINE);
2354
2433
  hash = fnvFeedU32(hash, hashText(node.tag));
2355
2434
  for (let i = 0; i < node.children.length; i++) {
2356
- hash = fnvFeedU32(hash, nodeSignature(node.children[i]));
2435
+ const childHash = nodeSignature(node.children[i], budget);
2436
+ if (childHash === void 0) return void 0;
2437
+ hash = fnvFeedU32(hash, childHash);
2357
2438
  }
2358
2439
  return hash >>> 0;
2359
2440
  }
@@ -2364,7 +2445,9 @@ var nodeSignature = (node) => {
2364
2445
  hash = fnvFeedU32(hash, node.content.length);
2365
2446
  hash = fnvFeedU32(hash, hashText(node.content));
2366
2447
  for (let i = 0; i < node.args.length; i++) {
2367
- hash = fnvFeedU32(hash, nodeSignature(node.args[i]));
2448
+ const argHash = nodeSignature(node.args[i], budget);
2449
+ if (argHash === void 0) return void 0;
2450
+ hash = fnvFeedU32(hash, argHash);
2368
2451
  }
2369
2452
  return hash >>> 0;
2370
2453
  }
@@ -2373,26 +2456,36 @@ var nodeSignature = (node) => {
2373
2456
  hash = fnvFeedU32(hash, NODE_TAG_BLOCK);
2374
2457
  hash = fnvFeedU32(hash, hashText(node.tag));
2375
2458
  for (let i = 0; i < node.args.length; i++) {
2376
- hash = fnvFeedU32(hash, nodeSignature(node.args[i]));
2459
+ const argHash = nodeSignature(node.args[i], budget);
2460
+ if (argHash === void 0) return void 0;
2461
+ hash = fnvFeedU32(hash, argHash);
2377
2462
  }
2378
2463
  for (let i = 0; i < node.children.length; i++) {
2379
- hash = fnvFeedU32(hash, nodeSignature(node.children[i]));
2464
+ const childHash = nodeSignature(node.children[i], budget);
2465
+ if (childHash === void 0) return void 0;
2466
+ hash = fnvFeedU32(hash, childHash);
2380
2467
  }
2381
2468
  return hash >>> 0;
2382
2469
  }
2383
2470
  return assertUnreachable(node);
2384
2471
  };
2385
- var zoneSignature = (zone) => {
2386
- const cached = zoneSignatureCache.get(zone);
2387
- if (cached !== void 0) return cached;
2472
+ var zoneSignature = (zone, budget) => {
2473
+ if (!budget) {
2474
+ const cached = zoneSignatureCache.get(zone);
2475
+ if (cached !== void 0) return cached;
2476
+ }
2388
2477
  let hash = fnvInit();
2389
2478
  hash = fnvFeedU32(hash, ZONE_TAG);
2390
2479
  hash = fnvFeedU32(hash, zone.endOffset - zone.startOffset);
2391
2480
  for (let i = 0; i < zone.nodes.length; i++) {
2392
- hash = fnvFeedU32(hash, nodeSignature(zone.nodes[i]));
2481
+ const signature = nodeSignature(zone.nodes[i], budget);
2482
+ if (signature === void 0) return void 0;
2483
+ hash = fnvFeedU32(hash, signature);
2393
2484
  }
2394
2485
  const finalized = hash >>> 0;
2395
- zoneSignatureCache.set(zone, finalized);
2486
+ if (!budget) {
2487
+ zoneSignatureCache.set(zone, finalized);
2488
+ }
2396
2489
  return finalized;
2397
2490
  };
2398
2491
  var isSafeRightReuse = (oldRightZones, newSource, seamNewOffset, delta, tracker, parseOptions) => {
@@ -2414,13 +2507,21 @@ var isSafeRightReuse = (oldRightZones, newSource, seamNewOffset, delta, tracker,
2414
2507
  );
2415
2508
  const probeZones = buildZones(probeTree);
2416
2509
  if (probeZones.length < probeZoneCount) return { ok: false, probeSliceBytes: probeLength };
2510
+ const signatureBudget = {
2511
+ remaining: RIGHT_REUSE_PROBE_SIGNATURE_NODE_BUDGET
2512
+ };
2417
2513
  for (let i = 0; i < probeZoneCount; i++) {
2418
2514
  const expected = oldRightZones[i];
2419
2515
  const actual = probeZones[i];
2420
2516
  if (actual.startOffset !== expected.startOffset + delta) return { ok: false, probeSliceBytes: probeLength };
2421
2517
  if (actual.endOffset !== expected.endOffset + delta) return { ok: false, probeSliceBytes: probeLength };
2422
2518
  if (actual.nodes.length !== expected.nodes.length) return { ok: false, probeSliceBytes: probeLength };
2423
- if (zoneSignature(actual) !== zoneSignature(expected)) return { ok: false, probeSliceBytes: probeLength };
2519
+ const actualSignature = zoneSignature(actual, signatureBudget);
2520
+ if (actualSignature === void 0) return { ok: false, probeSliceBytes: probeLength };
2521
+ const expectedSignature = zoneSignature(expected);
2522
+ if (expectedSignature === void 0 || actualSignature !== expectedSignature) {
2523
+ return { ok: false, probeSliceBytes: probeLength };
2524
+ }
2424
2525
  }
2425
2526
  return { ok: true, probeSliceBytes: probeLength };
2426
2527
  };
@@ -2519,6 +2620,14 @@ var shiftZone = (zone, delta, tracker) => ({
2519
2620
  endOffset: zone.endOffset + delta,
2520
2621
  nodes: zone.nodes.map((node) => shiftNode(node, delta, tracker))
2521
2622
  });
2623
+ var shiftZoneWithSignature = (zone, delta, tracker) => {
2624
+ const shifted = shiftZone(zone, delta, tracker);
2625
+ const signature = zoneSignature(zone);
2626
+ if (signature !== void 0) {
2627
+ zoneSignatureCache.set(shifted, signature);
2628
+ }
2629
+ return shifted;
2630
+ };
2522
2631
  var findDirtyRange = (zones, edit) => {
2523
2632
  let firstOverlap = -1;
2524
2633
  let lastOverlap = -1;
@@ -2531,7 +2640,7 @@ var findDirtyRange = (zones, edit) => {
2531
2640
  }
2532
2641
  if (firstOverlap !== -1) {
2533
2642
  return {
2534
- from: Math.max(0, firstOverlap - 1),
2643
+ from: Math.max(0, firstOverlap - LEFT_LOOKBEHIND_ZONES),
2535
2644
  to: Math.min(zones.length - 1, lastOverlap + 1)
2536
2645
  };
2537
2646
  }
@@ -2543,10 +2652,48 @@ var findDirtyRange = (zones, edit) => {
2543
2652
  }
2544
2653
  }
2545
2654
  return {
2546
- from: Math.max(0, insertionIndex - 1),
2655
+ from: Math.max(0, insertionIndex - LEFT_LOOKBEHIND_ZONES),
2547
2656
  to: Math.min(zones.length - 1, insertionIndex)
2548
2657
  };
2549
2658
  };
2659
+ var reparseDirtyWindowUntilStable = (doc, dirtyFrom, dirtyTo, edit, delta, newSource, tracker, parseOptions, cumulativeBudget, cumulativeReparsedBytes) => {
2660
+ let nextDirtyTo = dirtyTo;
2661
+ let nextDirtyZones = [];
2662
+ let nextCumulativeReparsedBytes = cumulativeReparsedBytes;
2663
+ while (true) {
2664
+ const dirtyStartOld = doc.zones[dirtyFrom].startOffset;
2665
+ const dirtyEndOld = doc.zones[nextDirtyTo].endOffset;
2666
+ const dirtyStartNew = mapOldOffsetToNew(edit, delta, dirtyStartOld);
2667
+ const dirtyEndNew = mapOldOffsetToNew(edit, delta, dirtyEndOld);
2668
+ const reparsedWindowSize = dirtyEndNew - dirtyStartNew;
2669
+ nextCumulativeReparsedBytes += reparsedWindowSize;
2670
+ if (nextCumulativeReparsedBytes > cumulativeBudget) {
2671
+ return {
2672
+ budgetExceeded: true,
2673
+ dirtyTo: nextDirtyTo,
2674
+ dirtyZones: nextDirtyZones,
2675
+ cumulativeReparsedBytes: nextCumulativeReparsedBytes
2676
+ };
2677
+ }
2678
+ const dirtyTree = parseWithPositions(
2679
+ newSource.slice(dirtyStartNew, dirtyEndNew),
2680
+ tracker,
2681
+ parseOptions,
2682
+ dirtyStartNew
2683
+ );
2684
+ nextDirtyZones = buildZones(dirtyTree);
2685
+ const reparsedEnd = nextDirtyZones.length > 0 ? nextDirtyZones[nextDirtyZones.length - 1].endOffset : dirtyStartNew;
2686
+ if (reparsedEnd === dirtyEndNew || nextDirtyTo === doc.zones.length - 1) {
2687
+ return {
2688
+ budgetExceeded: false,
2689
+ dirtyTo: nextDirtyTo,
2690
+ dirtyZones: nextDirtyZones,
2691
+ cumulativeReparsedBytes: nextCumulativeReparsedBytes
2692
+ };
2693
+ }
2694
+ nextDirtyTo += 1;
2695
+ }
2696
+ };
2550
2697
  var assertValidEdit = (doc, edit, newSource) => {
2551
2698
  if (edit.startOffset < 0 || edit.oldEndOffset < edit.startOffset || edit.oldEndOffset > doc.source.length) {
2552
2699
  throw createIncrementalEditError(
@@ -2573,16 +2720,21 @@ var parseIncremental = (source, options) => {
2573
2720
  const tracker = buildPositionTracker(source);
2574
2721
  const tree = parseWithPositions(source, tracker, options);
2575
2722
  const zones = buildZones(tree);
2723
+ for (let i = 0; i < zones.length; i++) {
2724
+ zoneSignature(zones[i]);
2725
+ }
2576
2726
  const parseOptions = cloneParseOptions(options);
2577
- return {
2727
+ const fingerprint = buildParseOptionsFingerprint(parseOptions);
2728
+ const doc = {
2578
2729
  source,
2579
2730
  tree,
2580
2731
  zones,
2581
- parseOptions,
2582
- optionsFingerprint: buildParseOptionsFingerprint(parseOptions)
2732
+ parseOptions
2583
2733
  };
2734
+ setCachedOptionsFingerprint(doc, fingerprint);
2735
+ return doc;
2584
2736
  };
2585
- var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2737
+ var updateIncrementalInternal = (doc, edit, newSource, options, __internalObserver) => {
2586
2738
  assertValidEdit(doc, edit, newSource);
2587
2739
  let cumulativeReparsedBytes = 0;
2588
2740
  let probeSliceBytes = 0;
@@ -2593,23 +2745,24 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2593
2745
  fellBackToFull
2594
2746
  });
2595
2747
  };
2596
- const parseOptions = options ? cloneParseOptions(options) : doc.parseOptions;
2597
- const previousOptionsFingerprint = doc.optionsFingerprint ?? buildParseOptionsFingerprint(doc.parseOptions);
2598
- const nextOptionsFingerprint = buildParseOptionsFingerprint(parseOptions);
2748
+ const previousOptionsFingerprint = getCachedOptionsFingerprint(doc) ?? buildParseOptionsFingerprint(doc.parseOptions);
2749
+ const nextOptionsFingerprint = options ? buildParseOptionsFingerprint(options) : previousOptionsFingerprint;
2750
+ const runtimeParseOptions = options ?? doc.parseOptions;
2751
+ const nextParseOptionsSnapshot = options ? cloneParseOptions(options) : doc.parseOptions;
2599
2752
  if (previousOptionsFingerprint !== nextOptionsFingerprint) {
2600
- const rebuilt = parseIncremental(newSource, parseOptions);
2753
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2601
2754
  emitDebug(true);
2602
2755
  __internalObserver?.("internal-full-rebuild");
2603
2756
  return rebuilt;
2604
2757
  }
2605
2758
  if (doc.zones.length === 0) {
2606
- const rebuilt = parseIncremental(newSource, parseOptions);
2759
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2607
2760
  emitDebug(true);
2608
2761
  __internalObserver?.("internal-full-rebuild");
2609
2762
  return rebuilt;
2610
2763
  }
2611
2764
  if (hasUnsafeZoneCoverageTailGap(doc, edit)) {
2612
- const rebuilt = parseIncremental(newSource, parseOptions);
2765
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2613
2766
  emitDebug(true);
2614
2767
  __internalObserver?.("internal-full-rebuild");
2615
2768
  return rebuilt;
@@ -2618,35 +2771,30 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2618
2771
  const cumulativeBudget = Math.max(newSource.length * 2, 1024);
2619
2772
  const delta = newSource.length - doc.source.length;
2620
2773
  const dirty = findDirtyRange(doc.zones, edit);
2621
- const dirtyFrom = dirty.from;
2774
+ let dirtyFrom = dirty.from;
2622
2775
  let dirtyTo = dirty.to;
2623
2776
  let dirtyZones = [];
2624
- while (true) {
2625
- const dirtyStartOld = doc.zones[dirtyFrom].startOffset;
2626
- const dirtyEndOld = doc.zones[dirtyTo].endOffset;
2627
- const dirtyStartNew = mapOldOffsetToNew(edit, delta, dirtyStartOld);
2628
- const dirtyEndNew = mapOldOffsetToNew(edit, delta, dirtyEndOld);
2629
- const reparsedWindowSize = dirtyEndNew - dirtyStartNew;
2630
- cumulativeReparsedBytes += reparsedWindowSize;
2631
- if (cumulativeReparsedBytes > cumulativeBudget) {
2632
- const rebuilt = parseIncremental(newSource, parseOptions);
2633
- emitDebug(true);
2634
- __internalObserver?.("internal-full-rebuild");
2635
- return rebuilt;
2636
- }
2637
- const dirtyTree = parseWithPositions(
2638
- newSource.slice(dirtyStartNew, dirtyEndNew),
2639
- newTracker,
2640
- parseOptions,
2641
- dirtyStartNew
2642
- );
2643
- dirtyZones = buildZones(dirtyTree);
2644
- const reparsedEnd = dirtyZones.length > 0 ? dirtyZones[dirtyZones.length - 1].endOffset : dirtyStartNew;
2645
- if (reparsedEnd === dirtyEndNew || dirtyTo === doc.zones.length - 1) {
2646
- break;
2647
- }
2648
- dirtyTo += 1;
2777
+ const firstReparse = reparseDirtyWindowUntilStable(
2778
+ doc,
2779
+ dirtyFrom,
2780
+ dirtyTo,
2781
+ edit,
2782
+ delta,
2783
+ newSource,
2784
+ newTracker,
2785
+ runtimeParseOptions,
2786
+ cumulativeBudget,
2787
+ cumulativeReparsedBytes
2788
+ );
2789
+ cumulativeReparsedBytes = firstReparse.cumulativeReparsedBytes;
2790
+ if (firstReparse.budgetExceeded) {
2791
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2792
+ emitDebug(true);
2793
+ __internalObserver?.("internal-full-rebuild");
2794
+ return rebuilt;
2649
2795
  }
2796
+ dirtyTo = firstReparse.dirtyTo;
2797
+ dirtyZones = firstReparse.dirtyZones;
2650
2798
  const leftZones = doc.zones.slice(0, dirtyFrom);
2651
2799
  const oldRightZones = doc.zones.slice(dirtyTo + 1);
2652
2800
  if (oldRightZones.length > 0) {
@@ -2658,34 +2806,34 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2658
2806
  seamNewOffset,
2659
2807
  delta,
2660
2808
  newTracker,
2661
- parseOptions
2809
+ runtimeParseOptions
2662
2810
  );
2663
2811
  probeSliceBytes = rightReuseCheck.probeSliceBytes;
2664
2812
  if (!rightReuseCheck.ok) {
2665
- const rebuilt = parseIncremental(newSource, parseOptions);
2813
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2666
2814
  emitDebug(true);
2667
2815
  __internalObserver?.("internal-full-rebuild");
2668
2816
  return rebuilt;
2669
2817
  }
2670
2818
  }
2671
- const rightZones = oldRightZones.map((zone) => shiftZone(zone, delta, newTracker));
2819
+ const rightZones = oldRightZones.map((zone) => shiftZoneWithSignature(zone, delta, newTracker));
2672
2820
  const zones = [...leftZones, ...dirtyZones, ...rightZones];
2673
2821
  const updated = {
2674
2822
  source: newSource,
2675
2823
  zones,
2676
2824
  tree: flattenZones(zones),
2677
- parseOptions,
2678
- optionsFingerprint: nextOptionsFingerprint
2825
+ parseOptions: nextParseOptionsSnapshot
2679
2826
  };
2827
+ setCachedOptionsFingerprint(updated, nextOptionsFingerprint);
2680
2828
  emitDebug(false);
2681
2829
  __internalObserver?.("incremental");
2682
2830
  return updated;
2683
2831
  };
2684
- var tryUpdateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2832
+ var tryUpdateIncrementalInternal = (doc, edit, newSource, options, __internalObserver) => {
2685
2833
  try {
2686
2834
  return {
2687
2835
  ok: true,
2688
- value: updateIncremental(doc, edit, newSource, options, __internalObserver)
2836
+ value: updateIncrementalInternal(doc, edit, newSource, options, __internalObserver)
2689
2837
  };
2690
2838
  } catch (error) {
2691
2839
  if (isIncrementalUpdateError(error)) {
@@ -2795,17 +2943,28 @@ var createIncrementalSession = (source, options, sessionOptions) => {
2795
2943
  return rebuiltResult2;
2796
2944
  }
2797
2945
  const incrementalStart = now();
2798
- let lastInternalUpdateMode;
2799
- const result = tryUpdateIncremental(currentDoc, edit, newSource, nextOptions, (mode) => {
2800
- lastInternalUpdateMode = mode;
2946
+ let mode;
2947
+ const result = tryUpdateIncrementalInternal(currentDoc, edit, newSource, nextOptions, (nextTelemetry) => {
2948
+ mode = nextTelemetry;
2801
2949
  });
2802
2950
  const incrementalElapsedMs = now() - incrementalStart;
2803
2951
  recordBounded(incrementalDurations, incrementalElapsedMs);
2804
2952
  if (result.ok) {
2805
2953
  currentDoc = result.value;
2806
- recordBounded(fallbackMarks, lastInternalUpdateMode === "internal-full-rebuild" ? 1 : 0);
2954
+ const internalFullRebuild = mode === "internal-full-rebuild";
2955
+ recordBounded(fallbackMarks, internalFullRebuild ? 1 : 0);
2807
2956
  maybeAdaptPolicy();
2808
- return { doc: currentDoc, mode: "incremental" };
2957
+ if (internalFullRebuild) {
2958
+ return {
2959
+ doc: currentDoc,
2960
+ mode: "full-fallback",
2961
+ fallbackReason: "INTERNAL_FULL_REBUILD"
2962
+ };
2963
+ }
2964
+ return {
2965
+ doc: currentDoc,
2966
+ mode: "incremental"
2967
+ };
2809
2968
  }
2810
2969
  recordBounded(fallbackMarks, 1);
2811
2970
  const rebuiltResult = runRebuild(newSource, nextOptions, result.error.code);
@@ -2856,9 +3015,7 @@ var createIncrementalSession = (source, options, sessionOptions) => {
2856
3015
  resetTokenIdSeed,
2857
3016
  splitTokensByPipe,
2858
3017
  stripRichText,
2859
- tryUpdateIncremental,
2860
3018
  unescapeInline,
2861
- updateIncremental,
2862
3019
  walkTokens,
2863
3020
  withSyntax,
2864
3021
  withTagNameConfig
package/dist/index.d.cts CHANGED
@@ -322,26 +322,14 @@ interface IncrementalDocument {
322
322
  tree: StructuralNode[];
323
323
  /** Optional parser config carried forward across updates. */
324
324
  parseOptions?: IncrementalParseOptions;
325
- /** Internal compatibility fingerprint for parse options. */
326
- optionsFingerprint?: string;
327
325
  }
328
326
  type IncrementalUpdateErrorCode = "INVALID_EDIT_RANGE" | "NEW_SOURCE_LENGTH_MISMATCH" | "EDIT_TEXT_MISMATCH" | "UNKNOWN";
329
- interface IncrementalUpdateError extends Error {
330
- code: IncrementalUpdateErrorCode;
331
- }
332
- type IncrementalUpdateResult = {
333
- ok: true;
334
- value: IncrementalDocument;
335
- } | {
336
- ok: false;
337
- error: IncrementalUpdateError;
338
- };
339
327
  /**
340
328
  * Result mode returned by the high-level incremental session API.
341
329
  */
342
330
  type IncrementalSessionApplyMode = "incremental" | "full-fallback";
343
331
  type IncrementalSessionStrategy = "auto" | "incremental-only" | "full-only";
344
- type IncrementalSessionFallbackReason = IncrementalUpdateErrorCode | "FULL_ONLY_STRATEGY" | "AUTO_COOLDOWN" | "AUTO_LARGE_EDIT";
332
+ type IncrementalSessionFallbackReason = IncrementalUpdateErrorCode | "INTERNAL_FULL_REBUILD" | "FULL_ONLY_STRATEGY" | "AUTO_COOLDOWN" | "AUTO_LARGE_EDIT";
345
333
  interface IncrementalSessionOptions {
346
334
  strategy?: IncrementalSessionStrategy;
347
335
  sampleWindowSize?: number;
@@ -825,35 +813,7 @@ declare const createEasyStableId: (options?: EasyStableIdOptions) => CreateId;
825
813
  */
826
814
  declare const buildZones: (nodes: readonly StructuralNode[]) => Zone[];
827
815
 
828
- type InternalUpdateMode = "incremental" | "internal-full-rebuild";
829
- type InternalUpdateObserver = (mode: InternalUpdateMode) => void;
830
816
  declare const parseIncremental: (source: string, options?: IncrementalParseOptions) => IncrementalDocument;
831
- /**
832
- * Update an incremental structural snapshot with one edit and a new full source.
833
- *
834
- * @experimental
835
- * Low-level updater for controlled integration paths.
836
- * For production applications, prefer `createIncrementalSession(...).applyEdit(...)`,
837
- * which guarantees fallback to full rebuild on errors.
838
- *
839
- * Assumption:
840
- * - Left boundary stabilization is conservative but fixed to one-zone lookbehind.
841
- * - Right boundary is expanded until stable (or EOF).
842
- *
843
- * If your edit may invalidate parsing state further left than one zone, prefer a full
844
- * rebuild via `parseIncremental(newSource, options)` for correctness.
845
- *
846
- * @internal `__internalObserver` is for session-level telemetry only.
847
- */
848
- declare const updateIncremental: (doc: IncrementalDocument, edit: IncrementalEdit, newSource: string, options?: IncrementalParseOptions, __internalObserver?: InternalUpdateObserver) => IncrementalDocument;
849
- /**
850
- * @experimental
851
- * Low-level result-style updater.
852
- * For production applications, prefer `createIncrementalSession(...).applyEdit(...)`.
853
- *
854
- * @internal `__internalObserver` is for session-level telemetry only.
855
- */
856
- declare const tryUpdateIncremental: (doc: IncrementalDocument, edit: IncrementalEdit, newSource: string, options?: IncrementalParseOptions, __internalObserver?: InternalUpdateObserver) => IncrementalUpdateResult;
857
817
  declare const createIncrementalSession: (source: string, options?: IncrementalParseOptions, sessionOptions?: IncrementalSessionOptions) => IncrementalSession;
858
818
 
859
- export { type BlockTagInput, type BlockTagLookup, type CreateId, DEFAULT_SYNTAX, DEFAULT_TAG_NAME, type DslContext, type EasyStableIdOptions, type ErrorCode, type IncrementalDocument, type IncrementalEdit, type IncrementalParseOptions, type IncrementalSession, type IncrementalSessionApplyMode, type IncrementalSessionApplyResult, type IncrementalSessionFallbackReason, type IncrementalSessionOptions, type IncrementalSessionStrategy, type IncrementalUpdateError, type IncrementalUpdateErrorCode, type IncrementalUpdateResult, type MapVisitor, type MultilineForm, type NarrowDraft, type NarrowToken, type NarrowTokenUnion, type ParseError, type ParseOptions, type Parser, type ParserBaseOptions, type PipeArgs, type PipeHandlerDefinition, type PositionTracker, type PrintOptions, type SourcePosition, type SourceSpan, type StructuralNode, type StructuralParseOptions, type SyntaxConfig, type SyntaxInput, type TagForm, type TagHandler, type TagNameConfig, type TextToken, type TokenDraft, type TokenVisitContext, type WalkVisitor, type Zone, buildPositionTracker, buildZones, createEasyStableId, createEasySyntax, createIncrementalSession, createParser, createPassthroughTags, createPipeBlockHandlers, createPipeHandlers, createPipeRawHandlers, createSimpleBlockHandlers, createSimpleInlineHandlers, createSimpleRawHandlers, createSyntax, createTagNameConfig, createTextToken, createToken, createTokenGuard, declareMultilineTags, extractText, getSyntax, mapTokens, materializeTextTokens, parseIncremental, parsePipeArgs, parsePipeTextArgs, parsePipeTextList, parseRichText, parseStructural, printStructural, readEscapedSequence, resetTokenIdSeed, splitTokensByPipe, stripRichText, tryUpdateIncremental, unescapeInline, updateIncremental, walkTokens, withSyntax, withTagNameConfig };
819
+ export { type BlockTagInput, type BlockTagLookup, type CreateId, DEFAULT_SYNTAX, DEFAULT_TAG_NAME, type DslContext, type EasyStableIdOptions, type ErrorCode, type IncrementalDocument, type IncrementalEdit, type IncrementalParseOptions, type IncrementalSessionOptions, type MapVisitor, type MultilineForm, type NarrowDraft, type NarrowToken, type NarrowTokenUnion, type ParseError, type ParseOptions, type Parser, type ParserBaseOptions, type PipeArgs, type PipeHandlerDefinition, type PositionTracker, type PrintOptions, type SourcePosition, type SourceSpan, type StructuralNode, type StructuralParseOptions, type SyntaxConfig, type SyntaxInput, type TagForm, type TagHandler, type TagNameConfig, type TextToken, type TokenDraft, type TokenVisitContext, type WalkVisitor, type Zone, buildPositionTracker, buildZones, createEasyStableId, createEasySyntax, createIncrementalSession, createParser, createPassthroughTags, createPipeBlockHandlers, createPipeHandlers, createPipeRawHandlers, createSimpleBlockHandlers, createSimpleInlineHandlers, createSimpleRawHandlers, createSyntax, createTagNameConfig, createTextToken, createToken, createTokenGuard, declareMultilineTags, extractText, getSyntax, mapTokens, materializeTextTokens, parseIncremental, parsePipeArgs, parsePipeTextArgs, parsePipeTextList, parseRichText, parseStructural, printStructural, readEscapedSequence, resetTokenIdSeed, splitTokensByPipe, stripRichText, unescapeInline, walkTokens, withSyntax, withTagNameConfig };
package/dist/index.d.ts CHANGED
@@ -322,26 +322,14 @@ interface IncrementalDocument {
322
322
  tree: StructuralNode[];
323
323
  /** Optional parser config carried forward across updates. */
324
324
  parseOptions?: IncrementalParseOptions;
325
- /** Internal compatibility fingerprint for parse options. */
326
- optionsFingerprint?: string;
327
325
  }
328
326
  type IncrementalUpdateErrorCode = "INVALID_EDIT_RANGE" | "NEW_SOURCE_LENGTH_MISMATCH" | "EDIT_TEXT_MISMATCH" | "UNKNOWN";
329
- interface IncrementalUpdateError extends Error {
330
- code: IncrementalUpdateErrorCode;
331
- }
332
- type IncrementalUpdateResult = {
333
- ok: true;
334
- value: IncrementalDocument;
335
- } | {
336
- ok: false;
337
- error: IncrementalUpdateError;
338
- };
339
327
  /**
340
328
  * Result mode returned by the high-level incremental session API.
341
329
  */
342
330
  type IncrementalSessionApplyMode = "incremental" | "full-fallback";
343
331
  type IncrementalSessionStrategy = "auto" | "incremental-only" | "full-only";
344
- type IncrementalSessionFallbackReason = IncrementalUpdateErrorCode | "FULL_ONLY_STRATEGY" | "AUTO_COOLDOWN" | "AUTO_LARGE_EDIT";
332
+ type IncrementalSessionFallbackReason = IncrementalUpdateErrorCode | "INTERNAL_FULL_REBUILD" | "FULL_ONLY_STRATEGY" | "AUTO_COOLDOWN" | "AUTO_LARGE_EDIT";
345
333
  interface IncrementalSessionOptions {
346
334
  strategy?: IncrementalSessionStrategy;
347
335
  sampleWindowSize?: number;
@@ -825,35 +813,7 @@ declare const createEasyStableId: (options?: EasyStableIdOptions) => CreateId;
825
813
  */
826
814
  declare const buildZones: (nodes: readonly StructuralNode[]) => Zone[];
827
815
 
828
- type InternalUpdateMode = "incremental" | "internal-full-rebuild";
829
- type InternalUpdateObserver = (mode: InternalUpdateMode) => void;
830
816
  declare const parseIncremental: (source: string, options?: IncrementalParseOptions) => IncrementalDocument;
831
- /**
832
- * Update an incremental structural snapshot with one edit and a new full source.
833
- *
834
- * @experimental
835
- * Low-level updater for controlled integration paths.
836
- * For production applications, prefer `createIncrementalSession(...).applyEdit(...)`,
837
- * which guarantees fallback to full rebuild on errors.
838
- *
839
- * Assumption:
840
- * - Left boundary stabilization is conservative but fixed to one-zone lookbehind.
841
- * - Right boundary is expanded until stable (or EOF).
842
- *
843
- * If your edit may invalidate parsing state further left than one zone, prefer a full
844
- * rebuild via `parseIncremental(newSource, options)` for correctness.
845
- *
846
- * @internal `__internalObserver` is for session-level telemetry only.
847
- */
848
- declare const updateIncremental: (doc: IncrementalDocument, edit: IncrementalEdit, newSource: string, options?: IncrementalParseOptions, __internalObserver?: InternalUpdateObserver) => IncrementalDocument;
849
- /**
850
- * @experimental
851
- * Low-level result-style updater.
852
- * For production applications, prefer `createIncrementalSession(...).applyEdit(...)`.
853
- *
854
- * @internal `__internalObserver` is for session-level telemetry only.
855
- */
856
- declare const tryUpdateIncremental: (doc: IncrementalDocument, edit: IncrementalEdit, newSource: string, options?: IncrementalParseOptions, __internalObserver?: InternalUpdateObserver) => IncrementalUpdateResult;
857
817
  declare const createIncrementalSession: (source: string, options?: IncrementalParseOptions, sessionOptions?: IncrementalSessionOptions) => IncrementalSession;
858
818
 
859
- export { type BlockTagInput, type BlockTagLookup, type CreateId, DEFAULT_SYNTAX, DEFAULT_TAG_NAME, type DslContext, type EasyStableIdOptions, type ErrorCode, type IncrementalDocument, type IncrementalEdit, type IncrementalParseOptions, type IncrementalSession, type IncrementalSessionApplyMode, type IncrementalSessionApplyResult, type IncrementalSessionFallbackReason, type IncrementalSessionOptions, type IncrementalSessionStrategy, type IncrementalUpdateError, type IncrementalUpdateErrorCode, type IncrementalUpdateResult, type MapVisitor, type MultilineForm, type NarrowDraft, type NarrowToken, type NarrowTokenUnion, type ParseError, type ParseOptions, type Parser, type ParserBaseOptions, type PipeArgs, type PipeHandlerDefinition, type PositionTracker, type PrintOptions, type SourcePosition, type SourceSpan, type StructuralNode, type StructuralParseOptions, type SyntaxConfig, type SyntaxInput, type TagForm, type TagHandler, type TagNameConfig, type TextToken, type TokenDraft, type TokenVisitContext, type WalkVisitor, type Zone, buildPositionTracker, buildZones, createEasyStableId, createEasySyntax, createIncrementalSession, createParser, createPassthroughTags, createPipeBlockHandlers, createPipeHandlers, createPipeRawHandlers, createSimpleBlockHandlers, createSimpleInlineHandlers, createSimpleRawHandlers, createSyntax, createTagNameConfig, createTextToken, createToken, createTokenGuard, declareMultilineTags, extractText, getSyntax, mapTokens, materializeTextTokens, parseIncremental, parsePipeArgs, parsePipeTextArgs, parsePipeTextList, parseRichText, parseStructural, printStructural, readEscapedSequence, resetTokenIdSeed, splitTokensByPipe, stripRichText, tryUpdateIncremental, unescapeInline, updateIncremental, walkTokens, withSyntax, withTagNameConfig };
819
+ export { type BlockTagInput, type BlockTagLookup, type CreateId, DEFAULT_SYNTAX, DEFAULT_TAG_NAME, type DslContext, type EasyStableIdOptions, type ErrorCode, type IncrementalDocument, type IncrementalEdit, type IncrementalParseOptions, type IncrementalSessionOptions, type MapVisitor, type MultilineForm, type NarrowDraft, type NarrowToken, type NarrowTokenUnion, type ParseError, type ParseOptions, type Parser, type ParserBaseOptions, type PipeArgs, type PipeHandlerDefinition, type PositionTracker, type PrintOptions, type SourcePosition, type SourceSpan, type StructuralNode, type StructuralParseOptions, type SyntaxConfig, type SyntaxInput, type TagForm, type TagHandler, type TagNameConfig, type TextToken, type TokenDraft, type TokenVisitContext, type WalkVisitor, type Zone, buildPositionTracker, buildZones, createEasyStableId, createEasySyntax, createIncrementalSession, createParser, createPassthroughTags, createPipeBlockHandlers, createPipeHandlers, createPipeRawHandlers, createSimpleBlockHandlers, createSimpleInlineHandlers, createSimpleRawHandlers, createSyntax, createTagNameConfig, createTextToken, createToken, createTokenGuard, declareMultilineTags, extractText, getSyntax, mapTokens, materializeTextTokens, parseIncremental, parsePipeArgs, parsePipeTextArgs, parsePipeTextList, parseRichText, parseStructural, printStructural, readEscapedSequence, resetTokenIdSeed, splitTokensByPipe, stripRichText, unescapeInline, walkTokens, withSyntax, withTagNameConfig };
package/dist/index.js CHANGED
@@ -2185,10 +2185,53 @@ var isIncrementalUpdateError = (error) => {
2185
2185
  const withCode = error;
2186
2186
  return withCode.code === "INVALID_EDIT_RANGE" || withCode.code === "NEW_SOURCE_LENGTH_MISMATCH" || withCode.code === "EDIT_TEXT_MISMATCH" || withCode.code === "UNKNOWN";
2187
2187
  };
2188
+ var isPlainObject = (value) => {
2189
+ if (!value || typeof value !== "object") return false;
2190
+ const prototype = Object.getPrototypeOf(value);
2191
+ return prototype === Object.prototype || prototype === null;
2192
+ };
2193
+ var cloneSnapshotValueInternal = (value, seen) => {
2194
+ if (Array.isArray(value)) {
2195
+ const seenArray = seen.get(value);
2196
+ if (seenArray) return seenArray;
2197
+ const next = new Array(value.length);
2198
+ seen.set(value, next);
2199
+ for (let i = 0; i < value.length; i++) {
2200
+ next[i] = cloneSnapshotValueInternal(value[i], seen);
2201
+ }
2202
+ return next;
2203
+ }
2204
+ if (isPlainObject(value)) {
2205
+ const seenObject = seen.get(value);
2206
+ if (seenObject) return seenObject;
2207
+ const next = {};
2208
+ seen.set(value, next);
2209
+ const keys = Object.keys(value);
2210
+ for (let i = 0; i < keys.length; i++) {
2211
+ const key = keys[i];
2212
+ next[key] = cloneSnapshotValueInternal(value[key], seen);
2213
+ }
2214
+ return next;
2215
+ }
2216
+ return value;
2217
+ };
2218
+ var cloneSnapshotValue = (value) => cloneSnapshotValueInternal(value, /* @__PURE__ */ new WeakMap());
2219
+ var cloneHandlersSnapshot = (handlers) => {
2220
+ if (!handlers) return void 0;
2221
+ const next = {};
2222
+ const keys = Object.keys(handlers);
2223
+ for (let i = 0; i < keys.length; i++) {
2224
+ const key = keys[i];
2225
+ const handler = handlers[key];
2226
+ next[key] = handler ? cloneSnapshotValue(handler) : handler;
2227
+ }
2228
+ return next;
2229
+ };
2188
2230
  var cloneParseOptions = (options) => {
2189
2231
  if (!options) return void 0;
2190
2232
  return {
2191
2233
  ...options,
2234
+ handlers: cloneHandlersSnapshot(options.handlers),
2192
2235
  syntax: options.syntax ? { ...options.syntax } : void 0,
2193
2236
  tagName: options.tagName ? { ...options.tagName } : void 0,
2194
2237
  allowForms: options.allowForms ? [...options.allowForms] : void 0
@@ -2205,28 +2248,53 @@ var getObjectIdentity = (value) => {
2205
2248
  objectIdentityMap.set(value, next);
2206
2249
  return next;
2207
2250
  };
2251
+ var getIdentityForUnknown = (value) => {
2252
+ if (typeof value === "object" && value !== null || typeof value === "function") {
2253
+ return getObjectIdentity(value);
2254
+ }
2255
+ return 0;
2256
+ };
2257
+ var buildHandlersShapeFingerprint = (handlers) => {
2258
+ if (!handlers || typeof handlers !== "object") return 0;
2259
+ const record = handlers;
2260
+ const keys = Object.keys(record).sort();
2261
+ let hash = fnvInit();
2262
+ hash = fnvFeedU32(hash, keys.length);
2263
+ for (let i = 0; i < keys.length; i++) {
2264
+ const key = keys[i];
2265
+ const handler = record[key];
2266
+ hash = fnvFeedU32(hash, hashText(key));
2267
+ if (!handler || typeof handler !== "object") continue;
2268
+ const handlerRecord = handler;
2269
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.inline));
2270
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.raw));
2271
+ hash = fnvFeedU32(hash, getIdentityForUnknown(handlerRecord.block));
2272
+ }
2273
+ return hash >>> 0;
2274
+ };
2275
+ var DEFAULT_PARSE_OPTIONS_FINGERPRINT = fnvFeedU32(fnvInit(), 2654435769);
2208
2276
  var buildParseOptionsFingerprint = (options) => {
2209
- if (!options) return "default";
2277
+ if (!options) return DEFAULT_PARSE_OPTIONS_FINGERPRINT;
2210
2278
  const syntax = options.syntax ?? {};
2211
2279
  const tagName = options.tagName ?? {};
2212
- return JSON.stringify({
2213
- handlersIdentity: getObjectIdentity(options.handlers),
2214
- allowForms: options.allowForms ?? [],
2215
- syntax: {
2216
- tagOpen: syntax.tagOpen ?? "",
2217
- tagClose: syntax.tagClose ?? "",
2218
- endTag: syntax.endTag ?? "",
2219
- tagDivider: syntax.tagDivider ?? "",
2220
- rawOpen: syntax.rawOpen ?? "",
2221
- rawClose: syntax.rawClose ?? "",
2222
- blockOpen: syntax.blockOpen ?? "",
2223
- blockClose: syntax.blockClose ?? ""
2224
- },
2225
- tagName: {
2226
- startIdentity: getObjectIdentity(tagName.isTagStartChar),
2227
- charIdentity: getObjectIdentity(tagName.isTagChar)
2228
- }
2229
- });
2280
+ const allowForms = options.allowForms ?? [];
2281
+ let hash = fnvInit();
2282
+ hash = fnvFeedU32(hash, buildHandlersShapeFingerprint(options.handlers));
2283
+ hash = fnvFeedU32(hash, allowForms.length);
2284
+ for (let i = 0; i < allowForms.length; i++) {
2285
+ hash = fnvFeedU32(hash, hashText(allowForms[i]));
2286
+ }
2287
+ hash = fnvFeedU32(hash, hashText(syntax.tagOpen ?? ""));
2288
+ hash = fnvFeedU32(hash, hashText(syntax.tagClose ?? ""));
2289
+ hash = fnvFeedU32(hash, hashText(syntax.endTag ?? ""));
2290
+ hash = fnvFeedU32(hash, hashText(syntax.tagDivider ?? ""));
2291
+ hash = fnvFeedU32(hash, hashText(syntax.rawOpen ?? ""));
2292
+ hash = fnvFeedU32(hash, hashText(syntax.rawClose ?? ""));
2293
+ hash = fnvFeedU32(hash, hashText(syntax.blockOpen ?? ""));
2294
+ hash = fnvFeedU32(hash, hashText(syntax.blockClose ?? ""));
2295
+ hash = fnvFeedU32(hash, getObjectIdentity(tagName.isTagStartChar));
2296
+ hash = fnvFeedU32(hash, getObjectIdentity(tagName.isTagChar));
2297
+ return hash >>> 0;
2230
2298
  };
2231
2299
  var hasUnsafeZoneCoverageTailGap = (doc, edit) => {
2232
2300
  const lastZone = doc.zones[doc.zones.length - 1];
@@ -2251,8 +2319,10 @@ var flattenZones = (zones) => {
2251
2319
  }
2252
2320
  return tree;
2253
2321
  };
2322
+ var LEFT_LOOKBEHIND_ZONES = 1;
2254
2323
  var RIGHT_REUSE_PROBE_ZONES = 2;
2255
2324
  var RIGHT_REUSE_PROBE_EXTRA_ZONES = 1;
2325
+ var RIGHT_REUSE_PROBE_SIGNATURE_NODE_BUDGET = 4096;
2256
2326
  var NODE_TAG_TEXT = 1;
2257
2327
  var NODE_TAG_ESCAPE = 2;
2258
2328
  var NODE_TAG_SEPARATOR = 3;
@@ -2261,9 +2331,20 @@ var NODE_TAG_RAW = 5;
2261
2331
  var NODE_TAG_BLOCK = 6;
2262
2332
  var ZONE_TAG = 7;
2263
2333
  var zoneSignatureCache = /* @__PURE__ */ new WeakMap();
2334
+ var parseOptionsFingerprintCache = /* @__PURE__ */ new WeakMap();
2264
2335
  var incrementalDebugSink;
2265
2336
  var hashText = (value) => fnv1a(value);
2266
- var nodeSignature = (node) => {
2337
+ var getCachedOptionsFingerprint = (doc) => parseOptionsFingerprintCache.get(doc);
2338
+ var setCachedOptionsFingerprint = (doc, fingerprint) => {
2339
+ parseOptionsFingerprintCache.set(doc, fingerprint);
2340
+ };
2341
+ var tryConsumeSignatureBudget = (budget) => {
2342
+ if (budget.remaining <= 0) return false;
2343
+ budget.remaining -= 1;
2344
+ return true;
2345
+ };
2346
+ var nodeSignature = (node, budget) => {
2347
+ if (budget && !tryConsumeSignatureBudget(budget)) return void 0;
2267
2348
  if (node.type === "text") {
2268
2349
  let hash = fnvInit();
2269
2350
  hash = fnvFeedU32(hash, NODE_TAG_TEXT);
@@ -2286,7 +2367,9 @@ var nodeSignature = (node) => {
2286
2367
  hash = fnvFeedU32(hash, NODE_TAG_INLINE);
2287
2368
  hash = fnvFeedU32(hash, hashText(node.tag));
2288
2369
  for (let i = 0; i < node.children.length; i++) {
2289
- hash = fnvFeedU32(hash, nodeSignature(node.children[i]));
2370
+ const childHash = nodeSignature(node.children[i], budget);
2371
+ if (childHash === void 0) return void 0;
2372
+ hash = fnvFeedU32(hash, childHash);
2290
2373
  }
2291
2374
  return hash >>> 0;
2292
2375
  }
@@ -2297,7 +2380,9 @@ var nodeSignature = (node) => {
2297
2380
  hash = fnvFeedU32(hash, node.content.length);
2298
2381
  hash = fnvFeedU32(hash, hashText(node.content));
2299
2382
  for (let i = 0; i < node.args.length; i++) {
2300
- hash = fnvFeedU32(hash, nodeSignature(node.args[i]));
2383
+ const argHash = nodeSignature(node.args[i], budget);
2384
+ if (argHash === void 0) return void 0;
2385
+ hash = fnvFeedU32(hash, argHash);
2301
2386
  }
2302
2387
  return hash >>> 0;
2303
2388
  }
@@ -2306,26 +2391,36 @@ var nodeSignature = (node) => {
2306
2391
  hash = fnvFeedU32(hash, NODE_TAG_BLOCK);
2307
2392
  hash = fnvFeedU32(hash, hashText(node.tag));
2308
2393
  for (let i = 0; i < node.args.length; i++) {
2309
- hash = fnvFeedU32(hash, nodeSignature(node.args[i]));
2394
+ const argHash = nodeSignature(node.args[i], budget);
2395
+ if (argHash === void 0) return void 0;
2396
+ hash = fnvFeedU32(hash, argHash);
2310
2397
  }
2311
2398
  for (let i = 0; i < node.children.length; i++) {
2312
- hash = fnvFeedU32(hash, nodeSignature(node.children[i]));
2399
+ const childHash = nodeSignature(node.children[i], budget);
2400
+ if (childHash === void 0) return void 0;
2401
+ hash = fnvFeedU32(hash, childHash);
2313
2402
  }
2314
2403
  return hash >>> 0;
2315
2404
  }
2316
2405
  return assertUnreachable(node);
2317
2406
  };
2318
- var zoneSignature = (zone) => {
2319
- const cached = zoneSignatureCache.get(zone);
2320
- if (cached !== void 0) return cached;
2407
+ var zoneSignature = (zone, budget) => {
2408
+ if (!budget) {
2409
+ const cached = zoneSignatureCache.get(zone);
2410
+ if (cached !== void 0) return cached;
2411
+ }
2321
2412
  let hash = fnvInit();
2322
2413
  hash = fnvFeedU32(hash, ZONE_TAG);
2323
2414
  hash = fnvFeedU32(hash, zone.endOffset - zone.startOffset);
2324
2415
  for (let i = 0; i < zone.nodes.length; i++) {
2325
- hash = fnvFeedU32(hash, nodeSignature(zone.nodes[i]));
2416
+ const signature = nodeSignature(zone.nodes[i], budget);
2417
+ if (signature === void 0) return void 0;
2418
+ hash = fnvFeedU32(hash, signature);
2326
2419
  }
2327
2420
  const finalized = hash >>> 0;
2328
- zoneSignatureCache.set(zone, finalized);
2421
+ if (!budget) {
2422
+ zoneSignatureCache.set(zone, finalized);
2423
+ }
2329
2424
  return finalized;
2330
2425
  };
2331
2426
  var isSafeRightReuse = (oldRightZones, newSource, seamNewOffset, delta, tracker, parseOptions) => {
@@ -2347,13 +2442,21 @@ var isSafeRightReuse = (oldRightZones, newSource, seamNewOffset, delta, tracker,
2347
2442
  );
2348
2443
  const probeZones = buildZones(probeTree);
2349
2444
  if (probeZones.length < probeZoneCount) return { ok: false, probeSliceBytes: probeLength };
2445
+ const signatureBudget = {
2446
+ remaining: RIGHT_REUSE_PROBE_SIGNATURE_NODE_BUDGET
2447
+ };
2350
2448
  for (let i = 0; i < probeZoneCount; i++) {
2351
2449
  const expected = oldRightZones[i];
2352
2450
  const actual = probeZones[i];
2353
2451
  if (actual.startOffset !== expected.startOffset + delta) return { ok: false, probeSliceBytes: probeLength };
2354
2452
  if (actual.endOffset !== expected.endOffset + delta) return { ok: false, probeSliceBytes: probeLength };
2355
2453
  if (actual.nodes.length !== expected.nodes.length) return { ok: false, probeSliceBytes: probeLength };
2356
- if (zoneSignature(actual) !== zoneSignature(expected)) return { ok: false, probeSliceBytes: probeLength };
2454
+ const actualSignature = zoneSignature(actual, signatureBudget);
2455
+ if (actualSignature === void 0) return { ok: false, probeSliceBytes: probeLength };
2456
+ const expectedSignature = zoneSignature(expected);
2457
+ if (expectedSignature === void 0 || actualSignature !== expectedSignature) {
2458
+ return { ok: false, probeSliceBytes: probeLength };
2459
+ }
2357
2460
  }
2358
2461
  return { ok: true, probeSliceBytes: probeLength };
2359
2462
  };
@@ -2452,6 +2555,14 @@ var shiftZone = (zone, delta, tracker) => ({
2452
2555
  endOffset: zone.endOffset + delta,
2453
2556
  nodes: zone.nodes.map((node) => shiftNode(node, delta, tracker))
2454
2557
  });
2558
+ var shiftZoneWithSignature = (zone, delta, tracker) => {
2559
+ const shifted = shiftZone(zone, delta, tracker);
2560
+ const signature = zoneSignature(zone);
2561
+ if (signature !== void 0) {
2562
+ zoneSignatureCache.set(shifted, signature);
2563
+ }
2564
+ return shifted;
2565
+ };
2455
2566
  var findDirtyRange = (zones, edit) => {
2456
2567
  let firstOverlap = -1;
2457
2568
  let lastOverlap = -1;
@@ -2464,7 +2575,7 @@ var findDirtyRange = (zones, edit) => {
2464
2575
  }
2465
2576
  if (firstOverlap !== -1) {
2466
2577
  return {
2467
- from: Math.max(0, firstOverlap - 1),
2578
+ from: Math.max(0, firstOverlap - LEFT_LOOKBEHIND_ZONES),
2468
2579
  to: Math.min(zones.length - 1, lastOverlap + 1)
2469
2580
  };
2470
2581
  }
@@ -2476,10 +2587,48 @@ var findDirtyRange = (zones, edit) => {
2476
2587
  }
2477
2588
  }
2478
2589
  return {
2479
- from: Math.max(0, insertionIndex - 1),
2590
+ from: Math.max(0, insertionIndex - LEFT_LOOKBEHIND_ZONES),
2480
2591
  to: Math.min(zones.length - 1, insertionIndex)
2481
2592
  };
2482
2593
  };
2594
+ var reparseDirtyWindowUntilStable = (doc, dirtyFrom, dirtyTo, edit, delta, newSource, tracker, parseOptions, cumulativeBudget, cumulativeReparsedBytes) => {
2595
+ let nextDirtyTo = dirtyTo;
2596
+ let nextDirtyZones = [];
2597
+ let nextCumulativeReparsedBytes = cumulativeReparsedBytes;
2598
+ while (true) {
2599
+ const dirtyStartOld = doc.zones[dirtyFrom].startOffset;
2600
+ const dirtyEndOld = doc.zones[nextDirtyTo].endOffset;
2601
+ const dirtyStartNew = mapOldOffsetToNew(edit, delta, dirtyStartOld);
2602
+ const dirtyEndNew = mapOldOffsetToNew(edit, delta, dirtyEndOld);
2603
+ const reparsedWindowSize = dirtyEndNew - dirtyStartNew;
2604
+ nextCumulativeReparsedBytes += reparsedWindowSize;
2605
+ if (nextCumulativeReparsedBytes > cumulativeBudget) {
2606
+ return {
2607
+ budgetExceeded: true,
2608
+ dirtyTo: nextDirtyTo,
2609
+ dirtyZones: nextDirtyZones,
2610
+ cumulativeReparsedBytes: nextCumulativeReparsedBytes
2611
+ };
2612
+ }
2613
+ const dirtyTree = parseWithPositions(
2614
+ newSource.slice(dirtyStartNew, dirtyEndNew),
2615
+ tracker,
2616
+ parseOptions,
2617
+ dirtyStartNew
2618
+ );
2619
+ nextDirtyZones = buildZones(dirtyTree);
2620
+ const reparsedEnd = nextDirtyZones.length > 0 ? nextDirtyZones[nextDirtyZones.length - 1].endOffset : dirtyStartNew;
2621
+ if (reparsedEnd === dirtyEndNew || nextDirtyTo === doc.zones.length - 1) {
2622
+ return {
2623
+ budgetExceeded: false,
2624
+ dirtyTo: nextDirtyTo,
2625
+ dirtyZones: nextDirtyZones,
2626
+ cumulativeReparsedBytes: nextCumulativeReparsedBytes
2627
+ };
2628
+ }
2629
+ nextDirtyTo += 1;
2630
+ }
2631
+ };
2483
2632
  var assertValidEdit = (doc, edit, newSource) => {
2484
2633
  if (edit.startOffset < 0 || edit.oldEndOffset < edit.startOffset || edit.oldEndOffset > doc.source.length) {
2485
2634
  throw createIncrementalEditError(
@@ -2506,16 +2655,21 @@ var parseIncremental = (source, options) => {
2506
2655
  const tracker = buildPositionTracker(source);
2507
2656
  const tree = parseWithPositions(source, tracker, options);
2508
2657
  const zones = buildZones(tree);
2658
+ for (let i = 0; i < zones.length; i++) {
2659
+ zoneSignature(zones[i]);
2660
+ }
2509
2661
  const parseOptions = cloneParseOptions(options);
2510
- return {
2662
+ const fingerprint = buildParseOptionsFingerprint(parseOptions);
2663
+ const doc = {
2511
2664
  source,
2512
2665
  tree,
2513
2666
  zones,
2514
- parseOptions,
2515
- optionsFingerprint: buildParseOptionsFingerprint(parseOptions)
2667
+ parseOptions
2516
2668
  };
2669
+ setCachedOptionsFingerprint(doc, fingerprint);
2670
+ return doc;
2517
2671
  };
2518
- var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2672
+ var updateIncrementalInternal = (doc, edit, newSource, options, __internalObserver) => {
2519
2673
  assertValidEdit(doc, edit, newSource);
2520
2674
  let cumulativeReparsedBytes = 0;
2521
2675
  let probeSliceBytes = 0;
@@ -2526,23 +2680,24 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2526
2680
  fellBackToFull
2527
2681
  });
2528
2682
  };
2529
- const parseOptions = options ? cloneParseOptions(options) : doc.parseOptions;
2530
- const previousOptionsFingerprint = doc.optionsFingerprint ?? buildParseOptionsFingerprint(doc.parseOptions);
2531
- const nextOptionsFingerprint = buildParseOptionsFingerprint(parseOptions);
2683
+ const previousOptionsFingerprint = getCachedOptionsFingerprint(doc) ?? buildParseOptionsFingerprint(doc.parseOptions);
2684
+ const nextOptionsFingerprint = options ? buildParseOptionsFingerprint(options) : previousOptionsFingerprint;
2685
+ const runtimeParseOptions = options ?? doc.parseOptions;
2686
+ const nextParseOptionsSnapshot = options ? cloneParseOptions(options) : doc.parseOptions;
2532
2687
  if (previousOptionsFingerprint !== nextOptionsFingerprint) {
2533
- const rebuilt = parseIncremental(newSource, parseOptions);
2688
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2534
2689
  emitDebug(true);
2535
2690
  __internalObserver?.("internal-full-rebuild");
2536
2691
  return rebuilt;
2537
2692
  }
2538
2693
  if (doc.zones.length === 0) {
2539
- const rebuilt = parseIncremental(newSource, parseOptions);
2694
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2540
2695
  emitDebug(true);
2541
2696
  __internalObserver?.("internal-full-rebuild");
2542
2697
  return rebuilt;
2543
2698
  }
2544
2699
  if (hasUnsafeZoneCoverageTailGap(doc, edit)) {
2545
- const rebuilt = parseIncremental(newSource, parseOptions);
2700
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2546
2701
  emitDebug(true);
2547
2702
  __internalObserver?.("internal-full-rebuild");
2548
2703
  return rebuilt;
@@ -2551,35 +2706,30 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2551
2706
  const cumulativeBudget = Math.max(newSource.length * 2, 1024);
2552
2707
  const delta = newSource.length - doc.source.length;
2553
2708
  const dirty = findDirtyRange(doc.zones, edit);
2554
- const dirtyFrom = dirty.from;
2709
+ let dirtyFrom = dirty.from;
2555
2710
  let dirtyTo = dirty.to;
2556
2711
  let dirtyZones = [];
2557
- while (true) {
2558
- const dirtyStartOld = doc.zones[dirtyFrom].startOffset;
2559
- const dirtyEndOld = doc.zones[dirtyTo].endOffset;
2560
- const dirtyStartNew = mapOldOffsetToNew(edit, delta, dirtyStartOld);
2561
- const dirtyEndNew = mapOldOffsetToNew(edit, delta, dirtyEndOld);
2562
- const reparsedWindowSize = dirtyEndNew - dirtyStartNew;
2563
- cumulativeReparsedBytes += reparsedWindowSize;
2564
- if (cumulativeReparsedBytes > cumulativeBudget) {
2565
- const rebuilt = parseIncremental(newSource, parseOptions);
2566
- emitDebug(true);
2567
- __internalObserver?.("internal-full-rebuild");
2568
- return rebuilt;
2569
- }
2570
- const dirtyTree = parseWithPositions(
2571
- newSource.slice(dirtyStartNew, dirtyEndNew),
2572
- newTracker,
2573
- parseOptions,
2574
- dirtyStartNew
2575
- );
2576
- dirtyZones = buildZones(dirtyTree);
2577
- const reparsedEnd = dirtyZones.length > 0 ? dirtyZones[dirtyZones.length - 1].endOffset : dirtyStartNew;
2578
- if (reparsedEnd === dirtyEndNew || dirtyTo === doc.zones.length - 1) {
2579
- break;
2580
- }
2581
- dirtyTo += 1;
2712
+ const firstReparse = reparseDirtyWindowUntilStable(
2713
+ doc,
2714
+ dirtyFrom,
2715
+ dirtyTo,
2716
+ edit,
2717
+ delta,
2718
+ newSource,
2719
+ newTracker,
2720
+ runtimeParseOptions,
2721
+ cumulativeBudget,
2722
+ cumulativeReparsedBytes
2723
+ );
2724
+ cumulativeReparsedBytes = firstReparse.cumulativeReparsedBytes;
2725
+ if (firstReparse.budgetExceeded) {
2726
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2727
+ emitDebug(true);
2728
+ __internalObserver?.("internal-full-rebuild");
2729
+ return rebuilt;
2582
2730
  }
2731
+ dirtyTo = firstReparse.dirtyTo;
2732
+ dirtyZones = firstReparse.dirtyZones;
2583
2733
  const leftZones = doc.zones.slice(0, dirtyFrom);
2584
2734
  const oldRightZones = doc.zones.slice(dirtyTo + 1);
2585
2735
  if (oldRightZones.length > 0) {
@@ -2591,34 +2741,34 @@ var updateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2591
2741
  seamNewOffset,
2592
2742
  delta,
2593
2743
  newTracker,
2594
- parseOptions
2744
+ runtimeParseOptions
2595
2745
  );
2596
2746
  probeSliceBytes = rightReuseCheck.probeSliceBytes;
2597
2747
  if (!rightReuseCheck.ok) {
2598
- const rebuilt = parseIncremental(newSource, parseOptions);
2748
+ const rebuilt = parseIncremental(newSource, runtimeParseOptions);
2599
2749
  emitDebug(true);
2600
2750
  __internalObserver?.("internal-full-rebuild");
2601
2751
  return rebuilt;
2602
2752
  }
2603
2753
  }
2604
- const rightZones = oldRightZones.map((zone) => shiftZone(zone, delta, newTracker));
2754
+ const rightZones = oldRightZones.map((zone) => shiftZoneWithSignature(zone, delta, newTracker));
2605
2755
  const zones = [...leftZones, ...dirtyZones, ...rightZones];
2606
2756
  const updated = {
2607
2757
  source: newSource,
2608
2758
  zones,
2609
2759
  tree: flattenZones(zones),
2610
- parseOptions,
2611
- optionsFingerprint: nextOptionsFingerprint
2760
+ parseOptions: nextParseOptionsSnapshot
2612
2761
  };
2762
+ setCachedOptionsFingerprint(updated, nextOptionsFingerprint);
2613
2763
  emitDebug(false);
2614
2764
  __internalObserver?.("incremental");
2615
2765
  return updated;
2616
2766
  };
2617
- var tryUpdateIncremental = (doc, edit, newSource, options, __internalObserver) => {
2767
+ var tryUpdateIncrementalInternal = (doc, edit, newSource, options, __internalObserver) => {
2618
2768
  try {
2619
2769
  return {
2620
2770
  ok: true,
2621
- value: updateIncremental(doc, edit, newSource, options, __internalObserver)
2771
+ value: updateIncrementalInternal(doc, edit, newSource, options, __internalObserver)
2622
2772
  };
2623
2773
  } catch (error) {
2624
2774
  if (isIncrementalUpdateError(error)) {
@@ -2728,17 +2878,28 @@ var createIncrementalSession = (source, options, sessionOptions) => {
2728
2878
  return rebuiltResult2;
2729
2879
  }
2730
2880
  const incrementalStart = now();
2731
- let lastInternalUpdateMode;
2732
- const result = tryUpdateIncremental(currentDoc, edit, newSource, nextOptions, (mode) => {
2733
- lastInternalUpdateMode = mode;
2881
+ let mode;
2882
+ const result = tryUpdateIncrementalInternal(currentDoc, edit, newSource, nextOptions, (nextTelemetry) => {
2883
+ mode = nextTelemetry;
2734
2884
  });
2735
2885
  const incrementalElapsedMs = now() - incrementalStart;
2736
2886
  recordBounded(incrementalDurations, incrementalElapsedMs);
2737
2887
  if (result.ok) {
2738
2888
  currentDoc = result.value;
2739
- recordBounded(fallbackMarks, lastInternalUpdateMode === "internal-full-rebuild" ? 1 : 0);
2889
+ const internalFullRebuild = mode === "internal-full-rebuild";
2890
+ recordBounded(fallbackMarks, internalFullRebuild ? 1 : 0);
2740
2891
  maybeAdaptPolicy();
2741
- return { doc: currentDoc, mode: "incremental" };
2892
+ if (internalFullRebuild) {
2893
+ return {
2894
+ doc: currentDoc,
2895
+ mode: "full-fallback",
2896
+ fallbackReason: "INTERNAL_FULL_REBUILD"
2897
+ };
2898
+ }
2899
+ return {
2900
+ doc: currentDoc,
2901
+ mode: "incremental"
2902
+ };
2742
2903
  }
2743
2904
  recordBounded(fallbackMarks, 1);
2744
2905
  const rebuiltResult = runRebuild(newSource, nextOptions, result.error.code);
@@ -2788,9 +2949,7 @@ export {
2788
2949
  resetTokenIdSeed,
2789
2950
  splitTokensByPipe,
2790
2951
  stripRichText,
2791
- tryUpdateIncremental,
2792
2952
  unescapeInline,
2793
- updateIncremental,
2794
2953
  walkTokens,
2795
2954
  withSyntax,
2796
2955
  withTagNameConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yume-dsl-rich-text",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Single-pass recursive rich-text DSL parser without regex, with pluggable tag handlers. Markdown alternative.",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",