@pierre/diffs 1.2.1 → 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.
Files changed (67) hide show
  1. package/dist/components/CodeView.d.ts +22 -7
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +202 -105
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +2 -0
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +13 -9
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +2 -0
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +12 -6
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  14. package/dist/components/VirtualizedFile.d.ts +4 -2
  15. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  16. package/dist/components/VirtualizedFile.js +23 -6
  17. package/dist/components/VirtualizedFile.js.map +1 -1
  18. package/dist/components/VirtualizedFileDiff.d.ts +9 -8
  19. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  20. package/dist/components/VirtualizedFileDiff.js +329 -142
  21. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  22. package/dist/constants.d.ts.map +1 -1
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +3 -3
  25. package/dist/react/index.d.ts +2 -2
  26. package/dist/renderers/DiffHunksRenderer.d.ts +1 -0
  27. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  28. package/dist/renderers/DiffHunksRenderer.js +19 -9
  29. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  30. package/dist/renderers/FileRenderer.d.ts +1 -0
  31. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  32. package/dist/renderers/FileRenderer.js +12 -6
  33. package/dist/renderers/FileRenderer.js.map +1 -1
  34. package/dist/ssr/index.d.ts +2 -2
  35. package/dist/types.d.ts +7 -1
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/utils/computeEstimatedDiffHeights.d.ts +28 -0
  38. package/dist/utils/computeEstimatedDiffHeights.d.ts.map +1 -0
  39. package/dist/utils/computeEstimatedDiffHeights.js +111 -0
  40. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -0
  41. package/dist/utils/getDiffHunksRendererOptions.d.ts +8 -0
  42. package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -0
  43. package/dist/utils/getDiffHunksRendererOptions.js +31 -0
  44. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -0
  45. package/dist/utils/getFileRendererOptions.d.ts +8 -0
  46. package/dist/utils/getFileRendererOptions.d.ts.map +1 -0
  47. package/dist/utils/getFileRendererOptions.js +24 -0
  48. package/dist/utils/getFileRendererOptions.js.map +1 -0
  49. package/dist/utils/iterateOverDiff.js +29 -30
  50. package/dist/utils/iterateOverDiff.js.map +1 -1
  51. package/dist/utils/parsePatchFiles.js +8 -1
  52. package/dist/utils/parsePatchFiles.js.map +1 -1
  53. package/dist/utils/virtualDiffLayout.d.ts +65 -0
  54. package/dist/utils/virtualDiffLayout.d.ts.map +1 -0
  55. package/dist/utils/virtualDiffLayout.js +94 -0
  56. package/dist/utils/virtualDiffLayout.js.map +1 -0
  57. package/dist/worker/WorkerPoolManager.d.ts +4 -1
  58. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  59. package/dist/worker/WorkerPoolManager.js +49 -24
  60. package/dist/worker/WorkerPoolManager.js.map +1 -1
  61. package/dist/worker/types.d.ts +2 -0
  62. package/dist/worker/types.d.ts.map +1 -1
  63. package/dist/worker/worker-portable.js +163 -40
  64. package/dist/worker/worker-portable.js.map +1 -1
  65. package/dist/worker/worker.js +60 -30
  66. package/dist/worker/worker.js.map +1 -1
  67. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
- import { CORE_CSS_ATTRIBUTE, DEFAULT_CODE_VIEW_FILE_METRICS, DEFAULT_CODE_VIEW_LAYOUT, DEFAULT_SMOOTH_SCROLL_SETTINGS, DEFAULT_THEMES, DIFFS_DEVELOPMENT_BUILD, DIFFS_TAG_NAME, THEME_CSS_ATTRIBUTE, UNSAFE_CSS_ATTRIBUTE } from "../constants.js";
1
+ import { CORE_CSS_ATTRIBUTE, DEFAULT_CODE_VIEW_FILE_METRICS, DEFAULT_CODE_VIEW_LAYOUT, DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, DEFAULT_SMOOTH_SCROLL_SETTINGS, DEFAULT_THEMES, DIFFS_DEVELOPMENT_BUILD, DIFFS_TAG_NAME, THEME_CSS_ATTRIBUTE, UNSAFE_CSS_ATTRIBUTE } from "../constants.js";
2
2
  import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
3
3
  import { areObjectsEqual } from "../utils/areObjectsEqual.js";
4
- import { areSelectionsEqual } from "../utils/areSelectionsEqual.js";
5
4
  import { areThemesEqual } from "../utils/areThemesEqual.js";
5
+ import { areOptionsEqual } from "../utils/areOptionsEqual.js";
6
+ import { areSelectionsEqual } from "../utils/areSelectionsEqual.js";
6
7
  import { createWindowFromScrollPosition } from "../utils/createWindowFromScrollPosition.js";
7
8
  import { isStyleNode } from "../utils/isStyleNode.js";
8
9
  import { prefersReducedMotion } from "../utils/prefersReducedMotion.js";
@@ -83,6 +84,26 @@ const CODE_VIEW_SELECTION_CALLBACK_KEYS = [
83
84
  "onLineSelectionChange",
84
85
  "onLineSelectionEnd"
85
86
  ];
87
+ const CODE_VIEW_ITEM_OPTIONS_STATE = Symbol("CodeView.itemOptionsState");
88
+ function defineOptionsState(options, state) {
89
+ Object.defineProperty(options, CODE_VIEW_ITEM_OPTIONS_STATE, {
90
+ configurable: false,
91
+ enumerable: false,
92
+ value: state
93
+ });
94
+ }
95
+ function getItemOptionsState(options) {
96
+ return options[CODE_VIEW_ITEM_OPTIONS_STATE];
97
+ }
98
+ function defineItemOption(target, key, get) {
99
+ Object.defineProperty(target, key, {
100
+ configurable: false,
101
+ enumerable: true,
102
+ get() {
103
+ return get(this);
104
+ }
105
+ });
106
+ }
86
107
  const DEFAULT_SCROLL_INTERACTION_RESTORE_DELAY_MS = 120;
87
108
  const SCROLLING_CODE_OVERFLOW_FIX_VARIABLE = "--diffs-overflow-override";
88
109
  const SCROLL_REBASE_CONTAINER_HEIGHT = 12e6;
@@ -90,7 +111,6 @@ const SCROLL_REBASE_TRIGGER_TOP = 1e6;
90
111
  const SCROLL_REBASE_TARGET_TOP = 2e6;
91
112
  const SCROLL_REBASE_TARGET_BOTTOM = SCROLL_REBASE_CONTAINER_HEIGHT - SCROLL_REBASE_TARGET_TOP;
92
113
  const SCROLL_REBASE_THRESHOLD = SCROLL_REBASE_CONTAINER_HEIGHT - SCROLL_REBASE_TRIGGER_TOP;
93
- const CODE_VIEW_ELEMENT_POOL_LIMIT = 32;
94
114
  const MOBILE_SAFARI = (() => {
95
115
  const { navigator } = globalThis;
96
116
  const userAgent = navigator.userAgent;
@@ -112,6 +132,8 @@ var CodeView = class CodeView {
112
132
  selectedLines = null;
113
133
  instanceToItem = /* @__PURE__ */ new Map();
114
134
  layoutDirtyIndex;
135
+ pendingLayoutReset;
136
+ renderOptionsRevision = 0;
115
137
  slotCoordinator;
116
138
  slotSnapshot;
117
139
  scrollListeners = /* @__PURE__ */ new Set();
@@ -138,6 +160,8 @@ var CodeView = class CodeView {
138
160
  stickyBottom: -1
139
161
  };
140
162
  itemMetricsCache = DEFAULT_CODE_VIEW_FILE_METRICS;
163
+ fileOptionsPrototype;
164
+ diffOptionsPrototype;
141
165
  pendingScrollTarget;
142
166
  pendingLayoutAnchor;
143
167
  scrollAnimation;
@@ -147,6 +171,8 @@ var CodeView = class CodeView {
147
171
  stickyContainer = document.createElement("div");
148
172
  stickyOffset = document.createElement("div");
149
173
  elementPool = [];
174
+ elementPoolVersion = 0;
175
+ elementPoolTracker = /* @__PURE__ */ new WeakMap();
150
176
  pendingElementPool = [];
151
177
  options;
152
178
  workerManager;
@@ -154,6 +180,8 @@ var CodeView = class CodeView {
154
180
  constructor(options = { theme: DEFAULT_THEMES }, workerManager, isContainerManaged = false) {
155
181
  this.options = options;
156
182
  this.computeMetricsCache(options.itemMetrics);
183
+ this.fileOptionsPrototype = this.createFileOptionsPrototype();
184
+ this.diffOptionsPrototype = this.createDiffOptionsPrototype();
157
185
  this.workerManager = workerManager;
158
186
  this.isContainerManaged = isContainerManaged;
159
187
  this.stickyOffset.style.contain = "layout size";
@@ -249,6 +277,7 @@ var CodeView = class CodeView {
249
277
  }
250
278
  setup(root) {
251
279
  if (this.root != null) throw new Error("CodeView.setup: already setup");
280
+ this.workerManager?.subscribeToThemeChanges(this);
252
281
  this.root = root;
253
282
  this.root.style.overflowAnchor = "none";
254
283
  this.container ??= document.createElement("div");
@@ -291,6 +320,7 @@ var CodeView = class CodeView {
291
320
  this.idToItem.clear();
292
321
  this.instanceToItem.clear();
293
322
  this.layoutDirtyIndex = void 0;
323
+ this.pendingLayoutReset = void 0;
294
324
  this.stickyContainer.textContent = "";
295
325
  this.stickyOffset.style.height = "";
296
326
  this.container?.style.removeProperty("height");
@@ -313,6 +343,7 @@ var CodeView = class CodeView {
313
343
  this.reset();
314
344
  this.clearElementPool();
315
345
  this.restoreScrollInteractions();
346
+ this.workerManager?.unsubscribeToThemeChanges(this);
316
347
  this.resizeObserver?.disconnect();
317
348
  this.resizeObserver = void 0;
318
349
  this.root?.removeEventListener("scroll", this.handleScroll);
@@ -342,9 +373,18 @@ var CodeView = class CodeView {
342
373
  if (item == null) return;
343
374
  item.instance.primeHighlightCache();
344
375
  }
376
+ getElementPoolLimit() {
377
+ const viewportSize = this.getHeight() + this.config.overscrollSize * 2;
378
+ const { diffHeaderHeight } = this.itemMetricsCache;
379
+ return Math.max(8, Math.ceil(viewportSize / Math.max(diffHeaderHeight, 10)) + 1) * (this.isContainerManaged ? 2 : 1);
380
+ }
345
381
  acquireElement() {
346
382
  this.promotePendingPooledElements();
347
- return this.elementPool.pop() ?? document.createElement(DIFFS_TAG_NAME);
383
+ let element = this.elementPool.pop();
384
+ while (element != null && !this.isElementPoolGenerationCurrent(element)) element = this.elementPool.pop();
385
+ element ??= document.createElement(DIFFS_TAG_NAME);
386
+ this.markElementPoolGenerationCurrent(element);
387
+ return element;
348
388
  }
349
389
  releaseRenderedItem(item) {
350
390
  const { element } = item;
@@ -352,10 +392,10 @@ var CodeView = class CodeView {
352
392
  item.element = void 0;
353
393
  if (element == null) return;
354
394
  element.remove();
355
- this.cleanElementForPool(element);
395
+ this.cleanElement(element);
356
396
  this.queueElementForPool(element);
357
397
  }
358
- cleanElementForPool(element) {
398
+ cleanElement(element) {
359
399
  const { shadowRoot } = element;
360
400
  if (shadowRoot != null) {
361
401
  for (const child of Array.from(shadowRoot.children)) if (!isPooledShadowChild(child)) child.remove();
@@ -363,7 +403,8 @@ var CodeView = class CodeView {
363
403
  if (!this.isContainerManaged) element.replaceChildren();
364
404
  }
365
405
  queueElementForPool(element) {
366
- if (this.getElementPoolSize() >= CODE_VIEW_ELEMENT_POOL_LIMIT) return;
406
+ const poolLimit = this.getElementPoolLimit();
407
+ if (!this.isElementPoolGenerationCurrent(element) || this.getElementPoolSize() >= poolLimit) return;
367
408
  if (this.isElementClean(element)) this.elementPool.push(element);
368
409
  else this.pendingElementPool.push(element);
369
410
  }
@@ -371,8 +412,9 @@ var CodeView = class CodeView {
371
412
  if (this.pendingElementPool.length === 0) return;
372
413
  const { pendingElementPool: pendingElements } = this;
373
414
  this.pendingElementPool = [];
374
- for (const element of pendingElements) if (this.isElementClean(element) && this.elementPool.length < CODE_VIEW_ELEMENT_POOL_LIMIT) this.elementPool.push(element);
375
- else if (this.getElementPoolSize() < CODE_VIEW_ELEMENT_POOL_LIMIT) this.pendingElementPool.push(element);
415
+ const poolLimit = this.getElementPoolLimit();
416
+ for (const element of pendingElements) if (this.isElementPoolGenerationCurrent(element) && this.isElementClean(element) && this.elementPool.length < poolLimit) this.elementPool.push(element);
417
+ else if (this.isElementPoolGenerationCurrent(element) && this.getElementPoolSize() < poolLimit) this.pendingElementPool.push(element);
376
418
  }
377
419
  isElementClean(element) {
378
420
  return element.childNodes.length === 0;
@@ -384,6 +426,16 @@ var CodeView = class CodeView {
384
426
  this.elementPool.length = 0;
385
427
  this.pendingElementPool.length = 0;
386
428
  }
429
+ invalidateElementPool() {
430
+ this.elementPoolVersion++;
431
+ this.clearElementPool();
432
+ }
433
+ markElementPoolGenerationCurrent(element) {
434
+ this.elementPoolTracker.set(element, this.elementPoolVersion);
435
+ }
436
+ isElementPoolGenerationCurrent(element) {
437
+ return this.elementPoolTracker.get(element) === this.elementPoolVersion;
438
+ }
387
439
  resolveEffectiveScrollBehavior(target, destination) {
388
440
  if (prefersReducedMotion()) return "instant";
389
441
  if (target.behavior !== "smooth-auto") return target.behavior ?? "instant";
@@ -446,8 +498,7 @@ var CodeView = class CodeView {
446
498
  this.idToItem.delete(oldId);
447
499
  item.item.id = newId;
448
500
  this.idToItem.set(newId, item);
449
- if (item.type === "diff") item.instance.setOptions(this.createOptions(item.item));
450
- else item.instance.setOptions(this.createOptions(item.item));
501
+ this.updateItemOptionsId(item.instance.options, newId);
451
502
  if (this.selectedLines?.id === oldId) {
452
503
  this.selectedLines = {
453
504
  ...this.selectedLines,
@@ -504,25 +555,36 @@ var CodeView = class CodeView {
504
555
  canSkipRenderForAppend(appendedTop) {
505
556
  return this.container != null && this.renderState.firstIndex !== -1 && this.pendingScrollTarget == null && this.scrollAnimation == null && this.layoutDirtyIndex == null && appendedTop > this.windowSpecs.bottom;
506
557
  }
558
+ onThemeChange() {
559
+ this.invalidateElementPool();
560
+ }
507
561
  setOptions(options) {
508
562
  if (options == null) return;
509
563
  this.capturePendingLayoutAnchor();
564
+ const { options: prevOptions } = this;
510
565
  const previousLayout = this.getLayout();
511
566
  const { itemMetricsCache: previousItemMetrics } = this;
512
- if (shouldClearPool(this.options, options)) this.clearElementPool();
567
+ if (shouldClearPool(prevOptions, options)) this.invalidateElementPool();
513
568
  this.options = options;
514
569
  const nextItemMetrics = this.computeMetricsCache(options.itemMetrics);
515
570
  const itemMetricsChanged = !areObjectsEqual(previousItemMetrics, nextItemMetrics);
516
- if (!areObjectsEqual(previousLayout, this.getLayout())) this.syncLayout();
517
- for (let index = 0; index < this.items.length; index++) {
518
- const item = this.items[index];
519
- if (item == null) throw new Error("CodeView.setOptions: invalid item index");
520
- if (itemMetricsChanged) item.instance.setMetrics(nextItemMetrics, true);
521
- if (item.type === "diff") item.instance.setOptions(this.createOptions(item.item));
522
- else item.instance.setOptions(this.createOptions(item.item));
571
+ const layoutChanged = !areObjectsEqual(previousLayout, this.getLayout());
572
+ if (layoutChanged) this.syncLayout();
573
+ const itemLayoutChanged = itemMetricsChanged || hasItemLayoutOptionChanged(prevOptions, options);
574
+ if (itemLayoutChanged) {
575
+ const previousReset = this.pendingLayoutReset;
576
+ this.pendingLayoutReset = {
577
+ metrics: itemMetricsChanged ? nextItemMetrics : previousReset?.metrics,
578
+ resetFileLayoutCache: true,
579
+ resetDiffLayoutCache: true,
580
+ includeEstimatedDiffHeights: previousReset?.includeEstimatedDiffHeights === true || itemMetricsChanged || hasCodeViewDiffEstimateOptionChanged(prevOptions, options)
581
+ };
523
582
  }
524
- this.markLayoutDirtyFromIndex(0);
525
- this.scrollDirty = true;
583
+ if (layoutChanged || itemLayoutChanged) {
584
+ this.markLayoutDirtyFromIndex(0);
585
+ this.scrollDirty = true;
586
+ }
587
+ if (!areOptionsEqual(prevOptions, options)) this.renderOptionsRevision++;
526
588
  if (!this.isContainerManaged && this.items.length > 0) this.render();
527
589
  }
528
590
  capturePendingLayoutAnchor() {
@@ -601,41 +663,33 @@ var CodeView = class CodeView {
601
663
  }
602
664
  createItem(input, index, top) {
603
665
  const { itemMetricsCache: itemMetrics } = this;
604
- if (input.type === "diff") return {
605
- type: "diff",
606
- item: input,
607
- version: input.version,
608
- index,
609
- instance: new VirtualizedFileDiff(this.createOptions(input), this, itemMetrics, this.workerManager, this.isContainerManaged),
610
- top,
611
- height: 0,
612
- element: void 0
613
- };
666
+ if (input.type === "diff") {
667
+ const instance$1 = new VirtualizedFileDiff(this.createDiffOptions(input.id), this, itemMetrics, this.workerManager, this.isContainerManaged);
668
+ return {
669
+ type: "diff",
670
+ item: input,
671
+ version: input.version,
672
+ index,
673
+ top,
674
+ height: 0,
675
+ element: void 0,
676
+ renderedOptionsRevision: this.renderOptionsRevision,
677
+ instance: instance$1
678
+ };
679
+ }
680
+ const instance = new VirtualizedFile(this.createFileOptions(input.id), this, itemMetrics, this.workerManager, this.isContainerManaged);
614
681
  return {
615
682
  type: "file",
616
683
  item: input,
617
684
  version: input.version,
618
685
  index,
619
- instance: new VirtualizedFile(this.createOptions(input), this, itemMetrics, this.workerManager, this.isContainerManaged),
620
686
  top,
621
687
  height: 0,
622
- element: void 0
688
+ element: void 0,
689
+ renderedOptionsRevision: this.renderOptionsRevision,
690
+ instance
623
691
  };
624
692
  }
625
- getItemById(itemId) {
626
- const item = this.idToItem.get(itemId);
627
- if (item == null) console.error(`CodeView.getItemById: unknown item id "${itemId}"`);
628
- return item;
629
- }
630
- getItemByMode(itemId, mode) {
631
- const item = this.getItemById(itemId);
632
- if (item == null) return;
633
- if (item.type !== mode) {
634
- console.error(`CodeView.getItemByMode: item id "${itemId}" is not a ${mode}`);
635
- return;
636
- }
637
- return item;
638
- }
639
693
  applySelectedLines(selection, options) {
640
694
  const { selectedLines: prevSelection } = this;
641
695
  if (selection == null && prevSelection == null || selection != null && prevSelection?.id === selection.id && areSelectionsEqual(prevSelection.range, selection.range)) return;
@@ -663,57 +717,86 @@ var CodeView = class CodeView {
663
717
  renamePendingLayoutAnchor(oldId, newId) {
664
718
  if (this.pendingLayoutAnchor?.id === oldId) this.pendingLayoutAnchor.id = newId;
665
719
  }
666
- wrapCallbackWithContext(mode, itemId, callback) {
667
- return (...args) => {
668
- const item = this.getItemByMode(itemId, mode);
669
- if (item == null) return;
670
- return callback(...args, item);
671
- };
720
+ createFileOptionsPrototype() {
721
+ const prototype = {};
722
+ for (const key of CODE_VIEW_FILE_OPTION_KEYS) defineItemOption(prototype, key, () => this.options[key]);
723
+ defineItemOption(prototype, "stickyHeader", () => this.options.stickyHeaders);
724
+ defineItemOption(prototype, "collapsed", (receiver) => this.getItemOptions(getItemOptionsState(receiver), "file")?.item.collapsed === true);
725
+ for (const key of CODE_VIEW_SHARED_CALLBACK_KEYS) this.defineItemSharedCallback(prototype, "file", key);
726
+ for (const key of CODE_VIEW_SELECTION_CALLBACK_KEYS) this.defineItemSelectionCallback(prototype, "file", key);
727
+ return prototype;
728
+ }
729
+ createDiffOptionsPrototype() {
730
+ const prototype = {};
731
+ for (const key of CODE_VIEW_DIFF_OPTION_KEYS) defineItemOption(prototype, key, () => this.options[key]);
732
+ defineItemOption(prototype, "stickyHeader", () => this.options.stickyHeaders);
733
+ defineItemOption(prototype, "hunkSeparators", () => this.options.hunkSeparators);
734
+ defineItemOption(prototype, "collapsed", (receiver) => this.getItemOptions(getItemOptionsState(receiver), "diff")?.item.collapsed === true);
735
+ for (const key of CODE_VIEW_SHARED_CALLBACK_KEYS) this.defineItemSharedCallback(prototype, "diff", key);
736
+ for (const key of CODE_VIEW_SELECTION_CALLBACK_KEYS) this.defineItemSelectionCallback(prototype, "diff", key);
737
+ return prototype;
738
+ }
739
+ createFileOptions(id) {
740
+ const options = Object.create(this.fileOptionsPrototype);
741
+ defineOptionsState(options, { id });
742
+ return options;
672
743
  }
673
- getWrappedOptionCallback(mode, key, itemId) {
674
- const callback = this.options[key];
675
- if (callback == null) return;
676
- return this.wrapCallbackWithContext(mode, itemId, callback);
677
- }
678
- getWrappedSelectionOptionCallback(mode, key, itemId) {
679
- if (this.options.enableLineSelection !== true) return;
680
- const callback = this.options[key];
681
- return ((range) => {
682
- const item = this.getItemByMode(itemId, mode);
683
- if (item == null) return;
684
- const selection = range == null ? null : {
685
- id: itemId,
686
- range
687
- };
688
- if (this.options.controlledSelection !== true) {
689
- if (range != null || this.selectedLines?.id === itemId) this.applySelectedLines(selection, { notify: false });
744
+ createDiffOptions(id) {
745
+ const options = Object.create(this.diffOptionsPrototype);
746
+ defineOptionsState(options, { id });
747
+ return options;
748
+ }
749
+ updateItemOptionsId(options, id) {
750
+ getItemOptionsState(options).id = id;
751
+ }
752
+ getItemOptions(state, mode) {
753
+ const item = this.idToItem.get(state.id);
754
+ if (item == null || item.type !== mode) return;
755
+ return item;
756
+ }
757
+ defineItemSharedCallback(options, mode, key) {
758
+ defineItemOption(options, key, (receiver) => {
759
+ if (this.options[key] == null) return;
760
+ const state = getItemOptionsState(receiver);
761
+ const callbackCache = state.callbackCache ??= {};
762
+ let wrapped = callbackCache[key];
763
+ if (wrapped == null) {
764
+ wrapped = ((...args) => {
765
+ const latest = this.getItemOptions(state, mode);
766
+ if (latest == null) return;
767
+ const callback = this.options[key];
768
+ return callback?.(...args, latest);
769
+ });
770
+ callbackCache[key] = wrapped;
690
771
  }
691
- this.options.onSelectedLinesChange?.(selection);
692
- return callback?.(range, item);
772
+ return wrapped;
693
773
  });
694
774
  }
695
- createOptions(item) {
696
- const { id: itemId, type: mode } = item;
697
- const options = mode === "file" ? { stickyHeader: this.options.stickyHeaders } : {
698
- stickyHeader: this.options.stickyHeaders,
699
- hunkSeparators: this.options.hunkSeparators
700
- };
701
- const target = options;
702
- const passThroughKeys = mode === "file" ? CODE_VIEW_FILE_OPTION_KEYS : CODE_VIEW_DIFF_OPTION_KEYS;
703
- for (const key of passThroughKeys) {
704
- const value = this.options[key];
705
- if (value !== void 0) target[key] = value;
706
- }
707
- target.collapsed = item.collapsed === true;
708
- for (const key of CODE_VIEW_SHARED_CALLBACK_KEYS) {
709
- const callback = this.getWrappedOptionCallback(mode, key, itemId);
710
- if (callback !== void 0) target[key] = callback;
711
- }
712
- for (const key of CODE_VIEW_SELECTION_CALLBACK_KEYS) {
713
- const callback = this.getWrappedSelectionOptionCallback(mode, key, itemId);
714
- if (callback !== void 0) target[key] = callback;
715
- }
716
- return options;
775
+ defineItemSelectionCallback(options, mode, key) {
776
+ defineItemOption(options, key, (receiver) => {
777
+ if (this.options.enableLineSelection !== true) return;
778
+ const state = getItemOptionsState(receiver);
779
+ const callbackCache = state.callbackCache ??= {};
780
+ let wrapped = callbackCache[key];
781
+ if (wrapped == null) {
782
+ wrapped = ((range) => {
783
+ const latest = this.getItemOptions(state, mode);
784
+ if (latest == null) return;
785
+ const selection = range == null ? null : {
786
+ id: latest.item.id,
787
+ range
788
+ };
789
+ if (this.options.controlledSelection !== true) {
790
+ if (range != null || this.selectedLines?.id === latest.item.id) this.applySelectedLines(selection, { notify: false });
791
+ }
792
+ this.options.onSelectedLinesChange?.(selection);
793
+ const callback = this.options[key];
794
+ return callback?.(range, latest);
795
+ });
796
+ callbackCache[key] = wrapped;
797
+ }
798
+ return wrapped;
799
+ });
717
800
  }
718
801
  /**
719
802
  * Track the earliest index whose measured layout may now be stale. Later
@@ -815,8 +898,7 @@ var CodeView = class CodeView {
815
898
  if (item.version === nextItem.version) return false;
816
899
  item.item = nextItem;
817
900
  item.version = nextItem.version;
818
- if (item.type === "diff") item.instance.setOptions(this.createOptions(item.item));
819
- else item.instance.setOptions(this.createOptions(item.item));
901
+ item.renderedOptionsRevision = -1;
820
902
  return true;
821
903
  }
822
904
  getMaxScrollTopForHeight(scrollHeight) {
@@ -1095,8 +1177,9 @@ var CodeView = class CodeView {
1095
1177
  let computeScrollCorrection = this.pendingLayoutAnchor != null;
1096
1178
  let scrollAnchor = this.getScrollAnchor(scrollTopAfterLayout);
1097
1179
  if (this.layoutDirtyIndex != null) {
1098
- this.recomputeLayout(this.layoutDirtyIndex);
1180
+ this.recomputeLayout(this.layoutDirtyIndex, this.pendingLayoutReset);
1099
1181
  this.layoutDirtyIndex = void 0;
1182
+ this.pendingLayoutReset = void 0;
1100
1183
  computeScrollCorrection = true;
1101
1184
  }
1102
1185
  if (computeScrollCorrection && scrollAnchor != null) {
@@ -1146,11 +1229,17 @@ var CodeView = class CodeView {
1146
1229
  item.element = this.acquireElement();
1147
1230
  syncRenderedItemOrder(this.stickyContainer, item.element, prevElement);
1148
1231
  instance.virtualizedSetup();
1149
- if (renderItem(item, item.element)) updatedItems.add(item);
1232
+ if (renderItem(item, item.element)) {
1233
+ item.renderedOptionsRevision = this.renderOptionsRevision;
1234
+ updatedItems.add(item);
1235
+ }
1150
1236
  prevElement = item.element;
1151
1237
  } else {
1152
1238
  syncRenderedItemOrder(this.stickyContainer, item.element, prevElement);
1153
- if (renderItem(item)) updatedItems.add(item);
1239
+ if (renderItem(item, void 0, item.renderedOptionsRevision !== this.renderOptionsRevision)) {
1240
+ item.renderedOptionsRevision = this.renderOptionsRevision;
1241
+ updatedItems.add(item);
1242
+ }
1154
1243
  prevElement = item.element;
1155
1244
  }
1156
1245
  }
@@ -1475,7 +1564,7 @@ var CodeView = class CodeView {
1475
1564
  * onward is remeasured so downstream positions and total scroll height stay
1476
1565
  * consistent after inserts, removals, or versioned item updates.
1477
1566
  */
1478
- recomputeLayout(startIndex = 0) {
1567
+ recomputeLayout(startIndex = 0, reset) {
1479
1568
  if (this.items.length === 0) {
1480
1569
  this.scrollHeight = 0;
1481
1570
  return;
@@ -1491,8 +1580,8 @@ var CodeView = class CodeView {
1491
1580
  const item = this.items[index];
1492
1581
  if (item == null) throw new Error("CodeView.recomputeLayout: invalid item index");
1493
1582
  item.top = runningTop;
1494
- if (item.type === "diff") item.height = item.instance.prepareVirtualizedItem(item.item.fileDiff);
1495
- else item.height = item.instance.prepareVirtualizedItem(item.item.file);
1583
+ if (item.type === "diff") item.height = item.instance.prepareCodeViewItem(item.item.fileDiff, runningTop, reset);
1584
+ else item.height = item.instance.prepareCodeViewItem(item.item.file, runningTop, reset);
1496
1585
  runningTop += item.height;
1497
1586
  if (index < this.items.length - 1) runningTop += layout.gap;
1498
1587
  }
@@ -1513,12 +1602,18 @@ var CodeView = class CodeView {
1513
1602
  };
1514
1603
  function prepareItemInstance(item) {
1515
1604
  item.instance.cleanUp(true);
1516
- if (item.type === "diff") return item.instance.prepareVirtualizedItem(item.item.fileDiff);
1517
- else return item.instance.prepareVirtualizedItem(item.item.file);
1605
+ if (item.type === "diff") return item.instance.prepareCodeViewItem(item.item.fileDiff, item.top);
1606
+ else return item.instance.prepareCodeViewItem(item.item.file, item.top);
1518
1607
  }
1519
1608
  function shouldClearPool(previousOptions, nextOptions) {
1520
1609
  return !areThemesEqual(previousOptions.theme ?? DEFAULT_THEMES, nextOptions.theme ?? DEFAULT_THEMES) || (previousOptions.themeType ?? "system") !== (nextOptions.themeType ?? "system") || previousOptions.unsafeCSS !== nextOptions.unsafeCSS;
1521
1610
  }
1611
+ function hasItemLayoutOptionChanged(previousOptions, nextOptions) {
1612
+ return (previousOptions.overflow ?? "scroll") !== (nextOptions.overflow ?? "scroll") || (previousOptions.disableLineNumbers ?? false) !== (nextOptions.disableLineNumbers ?? false) || (previousOptions.disableFileHeader ?? false) !== (nextOptions.disableFileHeader ?? false) || previousOptions.unsafeCSS !== nextOptions.unsafeCSS || (previousOptions.diffStyle ?? "split") !== (nextOptions.diffStyle ?? "split") || (previousOptions.diffIndicators ?? "bars") !== (nextOptions.diffIndicators ?? "bars") || (previousOptions.hunkSeparators ?? "line-info") !== (nextOptions.hunkSeparators ?? "line-info") || (previousOptions.expandUnchanged ?? false) !== (nextOptions.expandUnchanged ?? false) || (previousOptions.collapsedContextThreshold ?? DEFAULT_COLLAPSED_CONTEXT_THRESHOLD) !== (nextOptions.collapsedContextThreshold ?? DEFAULT_COLLAPSED_CONTEXT_THRESHOLD);
1613
+ }
1614
+ function hasCodeViewDiffEstimateOptionChanged(previousOptions, nextOptions) {
1615
+ return (previousOptions.disableFileHeader ?? false) !== (nextOptions.disableFileHeader ?? false) || (previousOptions.hunkSeparators ?? "line-info") !== (nextOptions.hunkSeparators ?? "line-info") || (previousOptions.expandUnchanged ?? false) !== (nextOptions.expandUnchanged ?? false) || (previousOptions.collapsedContextThreshold ?? DEFAULT_COLLAPSED_CONTEXT_THRESHOLD) !== (nextOptions.collapsedContextThreshold ?? DEFAULT_COLLAPSED_CONTEXT_THRESHOLD);
1616
+ }
1522
1617
  function isPooledShadowChild(child) {
1523
1618
  if (child instanceof SVGElement) return true;
1524
1619
  return isStyleNode(child) && (child.hasAttribute(CORE_CSS_ATTRIBUTE) || child.hasAttribute(THEME_CSS_ATTRIBUTE) || child.hasAttribute(UNSAFE_CSS_ATTRIBUTE));
@@ -1532,17 +1627,19 @@ function formatSelectedLinePoint(lineNumber, side) {
1532
1627
  if (side == null) return `${lineNumber}`;
1533
1628
  return `${side === "deletions" ? "D" : "A"}${lineNumber}`;
1534
1629
  }
1535
- function renderItem(item, fileContainer) {
1630
+ function renderItem(item, fileContainer, forceRender = false) {
1536
1631
  if (item.type === "diff") return item.instance.render({
1537
1632
  deferManagers: true,
1538
1633
  fileContainer,
1539
1634
  fileDiff: item.item.fileDiff,
1635
+ forceRender,
1540
1636
  lineAnnotations: item.item.annotations
1541
1637
  });
1542
1638
  else return item.instance.render({
1543
1639
  deferManagers: true,
1544
1640
  fileContainer,
1545
1641
  file: item.item.file,
1642
+ forceRender,
1546
1643
  lineAnnotations: item.item.annotations
1547
1644
  });
1548
1645
  }