@bilig/headless 0.1.102 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ import { orderWorkPaperCellChanges } from './change-order.js';
6
6
  import { WorkPaperConfigValueTooBigError, WorkPaperConfigValueTooSmallError, WorkPaperEvaluationSuspendedError, WorkPaperExpectedValueOfTypeError, WorkPaperOperationError, WorkPaperParseError, WorkPaperSheetError, WorkPaperExpectedOneOfValuesError, WorkPaperFunctionPluginValidationError, WorkPaperInvalidArgumentsError, WorkPaperLanguageAlreadyRegisteredError, WorkPaperLanguageNotRegisteredError, WorkPaperNamedExpressionDoesNotExistError, WorkPaperNamedExpressionNameIsAlreadyTakenError, WorkPaperNamedExpressionNameIsInvalidError, WorkPaperNoOperationToRedoError, WorkPaperNoOperationToUndoError, WorkPaperNoRelativeAddressesAllowedError, WorkPaperNoSheetWithIdError, WorkPaperNoSheetWithNameError, WorkPaperNotAFormulaError, WorkPaperNothingToPasteError, WorkPaperSheetNameAlreadyTakenError, WorkPaperSheetSizeLimitExceededError, WorkPaperUnableToParseError, } from './work-paper-errors.js';
7
7
  import { buildMatrixMutationPlan } from './matrix-mutation-plan.js';
8
8
  import { captureTrackedEngineEvent } from './tracked-engine-event-refs.js';
9
+ import { materializeTrackedIndexChangesWithMetadata } from './tracked-cell-index-changes.js';
9
10
  import { calculateWorkPaperFormulaInScratchWorkbook } from './work-paper-scratch-evaluator.js';
10
11
  import { replaceWorkPaperSheetContent } from './work-paper-sheet-replacement.js';
11
12
  const EMPTY_NAMED_EXPRESSION_VALUES = new Map();
@@ -1884,6 +1885,12 @@ export class WorkPaper {
1884
1885
  if (!this.isItPossibleToAddRows(sheetId, ...indexes)) {
1885
1886
  throw new WorkPaperOperationError('Rows cannot be added');
1886
1887
  }
1888
+ if (indexes.length === 1 && this.canUseTrackedStructuralFastPath()) {
1889
+ const [start, amount] = indexes[0];
1890
+ return this.captureTrackedChangesWithoutVisibilityCache(() => {
1891
+ this.engine.insertRows(this.sheetName(sheetId), start, amount);
1892
+ });
1893
+ }
1887
1894
  return this.batchStructuralChanges(() => {
1888
1895
  indexes.forEach(([start, amount]) => {
1889
1896
  this.engine.insertRows(this.sheetName(sheetId), start, amount);
@@ -1895,6 +1902,12 @@ export class WorkPaper {
1895
1902
  if (!this.isItPossibleToRemoveRows(sheetId, ...indexes)) {
1896
1903
  throw new WorkPaperOperationError('Rows cannot be removed');
1897
1904
  }
1905
+ if (indexes.length === 1 && this.canUseTrackedStructuralFastPath()) {
1906
+ const [start, amount] = indexes[0];
1907
+ return this.captureTrackedChangesWithoutVisibilityCache(() => {
1908
+ this.engine.deleteRows(this.sheetName(sheetId), start, amount);
1909
+ });
1910
+ }
1898
1911
  return this.batchStructuralChanges(() => {
1899
1912
  indexes
1900
1913
  .toSorted((left, right) => right[0] - left[0])
@@ -1908,6 +1921,12 @@ export class WorkPaper {
1908
1921
  if (!this.isItPossibleToAddColumns(sheetId, ...indexes)) {
1909
1922
  throw new WorkPaperOperationError('Columns cannot be added');
1910
1923
  }
1924
+ if (indexes.length === 1 && this.canUseTrackedStructuralFastPath()) {
1925
+ const [start, amount] = indexes[0];
1926
+ return this.captureTrackedChangesWithoutVisibilityCache(() => {
1927
+ this.engine.insertColumns(this.sheetName(sheetId), start, amount);
1928
+ });
1929
+ }
1911
1930
  return this.batchStructuralChanges(() => {
1912
1931
  indexes.forEach(([start, amount]) => {
1913
1932
  this.engine.insertColumns(this.sheetName(sheetId), start, amount);
@@ -1919,6 +1938,12 @@ export class WorkPaper {
1919
1938
  if (!this.isItPossibleToRemoveColumns(sheetId, ...indexes)) {
1920
1939
  throw new WorkPaperOperationError('Columns cannot be removed');
1921
1940
  }
1941
+ if (indexes.length === 1 && this.canUseTrackedStructuralFastPath()) {
1942
+ const [start, amount] = indexes[0];
1943
+ return this.captureTrackedChangesWithoutVisibilityCache(() => {
1944
+ this.engine.deleteColumns(this.sheetName(sheetId), start, amount);
1945
+ });
1946
+ }
1922
1947
  return this.batchStructuralChanges(() => {
1923
1948
  indexes
1924
1949
  .toSorted((left, right) => right[0] - left[0])
@@ -1946,7 +1971,7 @@ export class WorkPaper {
1946
1971
  throw new WorkPaperOperationError('Rows cannot be moved');
1947
1972
  }
1948
1973
  return this.canUseTrackedStructuralFastPath()
1949
- ? this.batchStructuralChanges(() => {
1974
+ ? this.captureTrackedChangesWithoutVisibilityCache(() => {
1950
1975
  this.engine.moveRows(this.sheetName(sheetId), start, count, target);
1951
1976
  })
1952
1977
  : this.captureChanges(undefined, () => {
@@ -1958,7 +1983,7 @@ export class WorkPaper {
1958
1983
  throw new WorkPaperOperationError('Columns cannot be moved');
1959
1984
  }
1960
1985
  return this.canUseTrackedStructuralFastPath()
1961
- ? this.batchStructuralChanges(() => {
1986
+ ? this.captureTrackedChangesWithoutVisibilityCache(() => {
1962
1987
  this.engine.moveColumns(this.sheetName(sheetId), start, count, target);
1963
1988
  })
1964
1989
  : this.captureChanges(undefined, () => {
@@ -2500,44 +2525,85 @@ export class WorkPaper {
2500
2525
  });
2501
2526
  return orderWorkPaperCellChanges(cellChanges, this.listSheetRecords());
2502
2527
  }
2503
- readTrackedCellChange(cellIndex) {
2504
- const sheetId = this.engine.workbook.cellStore.sheetIds[cellIndex];
2505
- const row = this.engine.workbook.cellStore.rows[cellIndex];
2506
- const col = this.engine.workbook.cellStore.cols[cellIndex];
2507
- if (sheetId === undefined || row === undefined || col === undefined) {
2508
- return undefined;
2528
+ materializeTrackedEventChanges(event) {
2529
+ if (event.patches && event.patches.length > 0) {
2530
+ const cellPatches = event.patches.filter((patch) => patch.kind === 'cell');
2531
+ return { changes: cellPatches, canReusePublicChanges: false, ordered: false };
2509
2532
  }
2510
- const sheetName = this.engine.workbook.getSheetNameById(sheetId);
2511
- if (sheetName === undefined) {
2533
+ const materialized = materializeTrackedIndexChangesWithMetadata(this.engine, event.changedCellIndices, {
2534
+ explicitChangedCount: event.explicitChangedCount,
2535
+ });
2536
+ return {
2537
+ changes: materialized.changes,
2538
+ canReusePublicChanges: true,
2539
+ ordered: materialized.ordered,
2540
+ };
2541
+ }
2542
+ readSingleTrackedCellChange(cellIndex) {
2543
+ const cellStore = this.engine.workbook.cellStore;
2544
+ const sheetId = cellStore.sheetIds[cellIndex];
2545
+ if (sheetId === undefined) {
2512
2546
  return undefined;
2513
2547
  }
2548
+ const sheet = this.engine.workbook.getSheetById(sheetId);
2549
+ const sheetName = sheet?.name ?? this.engine.workbook.getSheetNameById(sheetId);
2550
+ let row;
2551
+ let col;
2552
+ if (!sheet || sheet.structureVersion === 1) {
2553
+ row = cellStore.rows[cellIndex];
2554
+ col = cellStore.cols[cellIndex];
2555
+ }
2556
+ else {
2557
+ const position = this.engine.workbook.getCellPosition(cellIndex);
2558
+ if (!position) {
2559
+ return undefined;
2560
+ }
2561
+ row = position.row;
2562
+ col = position.col;
2563
+ }
2564
+ const tag = cellStore.tags[cellIndex] ?? ValueTag.Empty;
2565
+ let newValue;
2566
+ switch (tag) {
2567
+ case ValueTag.Number:
2568
+ newValue = { tag: ValueTag.Number, value: cellStore.numbers[cellIndex] ?? 0 };
2569
+ break;
2570
+ case ValueTag.Boolean:
2571
+ newValue = { tag: ValueTag.Boolean, value: (cellStore.numbers[cellIndex] ?? 0) !== 0 };
2572
+ break;
2573
+ case ValueTag.String:
2574
+ newValue = cellStore.getValue(cellIndex, (stringId) => this.engine.strings.get(stringId));
2575
+ break;
2576
+ case ValueTag.Error:
2577
+ newValue = { tag: ValueTag.Error, code: cellStore.errors[cellIndex] };
2578
+ break;
2579
+ case ValueTag.Empty:
2580
+ default:
2581
+ newValue = { tag: ValueTag.Empty };
2582
+ break;
2583
+ }
2514
2584
  return {
2515
2585
  kind: 'cell',
2516
2586
  address: { sheet: sheetId, row, col },
2517
2587
  sheetName,
2518
2588
  a1: formatAddress(row, col),
2519
- newValue: this.engine.workbook.cellStore.getValue(cellIndex, (id) => this.engine.strings.get(id)),
2589
+ newValue,
2520
2590
  };
2521
2591
  }
2522
- materializeTrackedEventChanges(event) {
2523
- if (event.patches && event.patches.length > 0) {
2524
- const cellPatches = event.patches.filter((patch) => patch.kind === 'cell');
2525
- return cellPatches;
2526
- }
2527
- const changes = [];
2528
- for (let index = 0; index < event.changedCellIndices.length; index += 1) {
2529
- const change = this.readTrackedCellChange(event.changedCellIndices[index]);
2530
- if (change) {
2531
- changes.push(change);
2532
- }
2533
- }
2534
- return changes;
2535
- }
2536
- computeCellChangesFromTrackedEvents(beforeVisibility, events) {
2592
+ computeCellChangesFromTrackedEvents(beforeVisibility, events, updateVisibility = true) {
2537
2593
  if (events.some((event) => event.invalidation === 'full')) {
2538
2594
  return null;
2539
2595
  }
2540
2596
  const nextVisibility = beforeVisibility;
2597
+ const sheetOrders = new Map();
2598
+ const sheetOrderFor = (sheetId) => {
2599
+ const existing = sheetOrders.get(sheetId);
2600
+ if (existing !== undefined) {
2601
+ return existing;
2602
+ }
2603
+ const order = this.sheetRecord(sheetId).order;
2604
+ sheetOrders.set(sheetId, order);
2605
+ return order;
2606
+ };
2541
2607
  const ensureMutableSheet = (sheetId, sheetName) => {
2542
2608
  const existing = nextVisibility.get(sheetId);
2543
2609
  if (existing) {
@@ -2554,68 +2620,96 @@ export class WorkPaper {
2554
2620
  };
2555
2621
  if (events.length === 1) {
2556
2622
  const event = events[0];
2557
- const eventChanges = this.materializeTrackedEventChanges(event);
2558
- let hasDuplicateCellKey = false;
2559
- if (eventChanges.length <= 4) {
2560
- for (let index = 0; index < eventChanges.length; index += 1) {
2561
- const change = eventChanges[index];
2562
- const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2563
- for (let priorIndex = 0; priorIndex < index; priorIndex += 1) {
2564
- const prior = eventChanges[priorIndex];
2565
- if (makeCellKey(prior.address.sheet, prior.address.row, prior.address.col) === cellKey) {
2566
- hasDuplicateCellKey = true;
2567
- break;
2623
+ if (event.invalidation !== 'full' &&
2624
+ event.patches === undefined &&
2625
+ event.changedCellIndices.length === 1 &&
2626
+ event.explicitChangedCount === 1 &&
2627
+ event.changedInputCount === 1 &&
2628
+ !event.hasInvalidatedRanges &&
2629
+ !event.hasInvalidatedRows &&
2630
+ !event.hasInvalidatedColumns) {
2631
+ const change = this.readSingleTrackedCellChange(event.changedCellIndices[0]);
2632
+ if (change) {
2633
+ if (updateVisibility) {
2634
+ const sheet = ensureMutableSheet(change.address.sheet, change.sheetName);
2635
+ const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2636
+ if (change.newValue.tag === ValueTag.Empty) {
2637
+ sheet.cells.delete(cellKey);
2638
+ }
2639
+ else {
2640
+ sheet.cells.set(cellKey, change.newValue);
2568
2641
  }
2569
2642
  }
2570
- if (hasDuplicateCellKey) {
2571
- break;
2572
- }
2643
+ return { changes: [change], nextVisibility };
2573
2644
  }
2574
2645
  }
2575
- else {
2576
- const seenCellKeys = new Set();
2577
- for (let index = 0; index < eventChanges.length; index += 1) {
2578
- const change = eventChanges[index];
2579
- const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2646
+ const materializedEventChanges = this.materializeTrackedEventChanges(event);
2647
+ const eventChanges = materializedEventChanges.changes;
2648
+ if (!updateVisibility && materializedEventChanges.canReusePublicChanges && materializedEventChanges.ordered) {
2649
+ return {
2650
+ changes: [...eventChanges],
2651
+ nextVisibility,
2652
+ };
2653
+ }
2654
+ const directChanges = [];
2655
+ const seenCellKeys = eventChanges.length > 4 && eventChanges.length <= 64 ? new Set() : undefined;
2656
+ const smallCellKeys = eventChanges.length > 1 && eventChanges.length <= 4 ? [] : undefined;
2657
+ let hasDuplicateCellKey = false;
2658
+ let alreadySorted = true;
2659
+ let previousSheetOrder = -1;
2660
+ let previousRow = -1;
2661
+ let previousCol = -1;
2662
+ for (let index = 0; index < eventChanges.length; index += 1) {
2663
+ const change = eventChanges[index];
2664
+ const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2665
+ if (seenCellKeys) {
2580
2666
  if (seenCellKeys.has(cellKey)) {
2581
2667
  hasDuplicateCellKey = true;
2582
2668
  break;
2583
2669
  }
2584
2670
  seenCellKeys.add(cellKey);
2585
2671
  }
2586
- }
2587
- if (!hasDuplicateCellKey) {
2588
- const directChanges = [];
2589
- let alreadySorted = true;
2590
- let previousSheetOrder = -1;
2591
- let previousRow = -1;
2592
- let previousCol = -1;
2593
- for (let index = 0; index < eventChanges.length; index += 1) {
2594
- const change = eventChanges[index];
2595
- const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2596
- const sheet = ensureMutableSheet(change.address.sheet, change.sheetName);
2597
- if (sheet.order < previousSheetOrder ||
2598
- (sheet.order === previousSheetOrder &&
2599
- (change.address.row < previousRow || (change.address.row === previousRow && change.address.col < previousCol)))) {
2600
- alreadySorted = false;
2672
+ else if (smallCellKeys) {
2673
+ for (let priorIndex = 0; priorIndex < index; priorIndex += 1) {
2674
+ if (smallCellKeys[priorIndex] === cellKey) {
2675
+ hasDuplicateCellKey = true;
2676
+ break;
2677
+ }
2601
2678
  }
2679
+ if (hasDuplicateCellKey) {
2680
+ break;
2681
+ }
2682
+ smallCellKeys[index] = cellKey;
2683
+ }
2684
+ const sheet = updateVisibility ? ensureMutableSheet(change.address.sheet, change.sheetName) : undefined;
2685
+ const sheetOrder = sheet?.order ?? sheetOrderFor(change.address.sheet);
2686
+ if (sheetOrder < previousSheetOrder ||
2687
+ (sheetOrder === previousSheetOrder &&
2688
+ (change.address.row < previousRow || (change.address.row === previousRow && change.address.col < previousCol)))) {
2689
+ alreadySorted = false;
2690
+ }
2691
+ if (sheet) {
2602
2692
  if (change.newValue.tag === ValueTag.Empty) {
2603
2693
  sheet.cells.delete(cellKey);
2604
2694
  }
2605
2695
  else {
2606
2696
  sheet.cells.set(cellKey, change.newValue);
2607
2697
  }
2608
- directChanges[index] = {
2698
+ }
2699
+ directChanges[index] = materializedEventChanges.canReusePublicChanges
2700
+ ? change
2701
+ : {
2609
2702
  kind: 'cell',
2610
2703
  address: change.address,
2611
2704
  sheetName: change.sheetName,
2612
2705
  a1: change.a1,
2613
2706
  newValue: change.newValue,
2614
2707
  };
2615
- previousSheetOrder = sheet.order;
2616
- previousRow = change.address.row;
2617
- previousCol = change.address.col;
2618
- }
2708
+ previousSheetOrder = sheetOrder;
2709
+ previousRow = change.address.row;
2710
+ previousCol = change.address.col;
2711
+ }
2712
+ if (!hasDuplicateCellKey) {
2619
2713
  return {
2620
2714
  changes: alreadySorted
2621
2715
  ? directChanges
@@ -2626,7 +2720,7 @@ export class WorkPaper {
2626
2720
  }
2627
2721
  const latestChangesByKey = new Map();
2628
2722
  for (const event of events) {
2629
- const eventChanges = this.materializeTrackedEventChanges(event);
2723
+ const eventChanges = this.materializeTrackedEventChanges(event).changes;
2630
2724
  for (let index = 0; index < eventChanges.length; index += 1) {
2631
2725
  const change = eventChanges[index];
2632
2726
  const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
@@ -2640,14 +2734,16 @@ export class WorkPaper {
2640
2734
  });
2641
2735
  }
2642
2736
  }
2643
- for (const change of latestChangesByKey.values()) {
2644
- const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2645
- const sheet = ensureMutableSheet(change.address.sheet, change.sheetName);
2646
- if (change.newValue.tag === ValueTag.Empty) {
2647
- sheet.cells.delete(cellKey);
2648
- }
2649
- else {
2650
- sheet.cells.set(cellKey, change.newValue);
2737
+ if (updateVisibility) {
2738
+ for (const change of latestChangesByKey.values()) {
2739
+ const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
2740
+ const sheet = ensureMutableSheet(change.address.sheet, change.sheetName);
2741
+ if (change.newValue.tag === ValueTag.Empty) {
2742
+ sheet.cells.delete(cellKey);
2743
+ }
2744
+ else {
2745
+ sheet.cells.set(cellKey, change.newValue);
2746
+ }
2651
2747
  }
2652
2748
  }
2653
2749
  const directChanges = [...latestChangesByKey.values()];
@@ -2691,7 +2787,7 @@ export class WorkPaper {
2691
2787
  this.batchUsesTrackedFastPath = false;
2692
2788
  }
2693
2789
  computeTrackedChangesWithoutVisibilityCache(events) {
2694
- const fastPath = this.computeCellChangesFromTrackedEvents(new Map(), events);
2790
+ const fastPath = this.computeCellChangesFromTrackedEvents(new Map(), events, false);
2695
2791
  if (!fastPath) {
2696
2792
  throw new WorkPaperOperationError('Mutation emitted an unsupported invalidation pattern for tracked changes');
2697
2793
  }