@bilig/headless 0.2.0 → 0.3.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.
- package/dist/fast-range-read.d.ts +4 -0
- package/dist/fast-range-read.js +81 -0
- package/dist/fast-range-read.js.map +1 -0
- package/dist/initial-sheet-load.d.ts +11 -5
- package/dist/initial-sheet-load.js +168 -65
- package/dist/initial-sheet-load.js.map +1 -1
- package/dist/tracked-cell-index-changes.d.ts +32 -6
- package/dist/tracked-cell-index-changes.js +635 -4
- package/dist/tracked-cell-index-changes.js.map +1 -1
- package/dist/tracked-engine-event-refs.d.ts +9 -1
- package/dist/tracked-engine-event-refs.js +152 -8
- package/dist/tracked-engine-event-refs.js.map +1 -1
- package/dist/work-paper-runtime.d.ts +29 -0
- package/dist/work-paper-runtime.js +1146 -106
- package/dist/work-paper-runtime.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,16 +1,68 @@
|
|
|
1
|
-
import { SpreadsheetEngine, attachRuntimeSnapshot, makeCellKey, readRuntimeSnapshot, } from '@bilig/core';
|
|
1
|
+
import { SpreadsheetEngine, attachRuntimeSnapshot, makeCellKey, readRuntimeImage, readRuntimeSnapshot, } from '@bilig/core';
|
|
2
2
|
import { ErrorCode, MAX_COLS, MAX_ROWS, ValueTag, } from '@bilig/protocol';
|
|
3
|
-
import { excelSerialToDateParts, formatAddress, formatRangeAddress, installExternalFunctionAdapter, isArrayValue, isCellReferenceText, parseCellAddress, parseFormula, parseRangeAddress, serializeFormula, translateFormulaReferences, } from '@bilig/formula';
|
|
4
|
-
import { loadInitialMixedSheet, tryLoadInitialLiteralSheet } from './initial-sheet-load.js';
|
|
3
|
+
import { excelSerialToDateParts, formatAddress, formatRangeAddress, indexToColumn, installExternalFunctionAdapter, isArrayValue, isCellReferenceText, parseCellAddress, parseFormula, parseRangeAddress, serializeFormula, translateFormulaReferences, } from '@bilig/formula';
|
|
4
|
+
import { loadInitialLiteralSheet, loadInitialMixedSheet, tryLoadInitialLiteralSheet } from './initial-sheet-load.js';
|
|
5
5
|
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
|
+
import { readFastPhysicalRangeValues } from './fast-range-read.js';
|
|
8
9
|
import { captureTrackedEngineEvent } from './tracked-engine-event-refs.js';
|
|
9
|
-
import { materializeTrackedIndexChangesWithMetadata } from './tracked-cell-index-changes.js';
|
|
10
|
+
import { detachTrackedIndexChanges, hasDeferredTrackedIndexChanges, materializeTrackedIndexChangeSourcesWithMetadata, materializeTrackedIndexChangesWithMetadata, } from './tracked-cell-index-changes.js';
|
|
10
11
|
import { calculateWorkPaperFormulaInScratchWorkbook } from './work-paper-scratch-evaluator.js';
|
|
11
12
|
import { replaceWorkPaperSheetContent } from './work-paper-sheet-replacement.js';
|
|
13
|
+
const TRUSTED_TRACKED_PHYSICAL_SHEET_ID_PROPERTY = '__biligTrackedPhysicalSheetId';
|
|
14
|
+
const TRUSTED_TRACKED_PHYSICAL_SORTED_SPLIT_PROPERTY = '__biligTrackedPhysicalSortedSliceSplit';
|
|
15
|
+
function readTrustedPhysicalTrackedChangeMetadata(changedCellIndices) {
|
|
16
|
+
const trustedPhysicalSheetId = Reflect.get(changedCellIndices, TRUSTED_TRACKED_PHYSICAL_SHEET_ID_PROPERTY);
|
|
17
|
+
if (typeof trustedPhysicalSheetId !== 'number' || !Number.isInteger(trustedPhysicalSheetId) || trustedPhysicalSheetId < 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const trustedSortedSliceSplit = Reflect.get(changedCellIndices, TRUSTED_TRACKED_PHYSICAL_SORTED_SPLIT_PROPERTY);
|
|
21
|
+
return typeof trustedSortedSliceSplit === 'number' && Number.isInteger(trustedSortedSliceSplit) && trustedSortedSliceSplit > 0
|
|
22
|
+
? { trustedPhysicalSheetId, trustedSortedSliceSplit }
|
|
23
|
+
: { trustedPhysicalSheetId };
|
|
24
|
+
}
|
|
12
25
|
const EMPTY_NAMED_EXPRESSION_VALUES = new Map();
|
|
26
|
+
const FAST_EXISTING_NUMERIC_LITERAL_FLAGS = 2 /* CellFlags.HasFormula */ | 4 /* CellFlags.JsOnly */ | 8 /* CellFlags.InCycle */ | 64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */;
|
|
13
27
|
const VISIBILITY_SHEET_STRIDE = MAX_ROWS * MAX_COLS;
|
|
28
|
+
const TINY_TRACKED_CHANGE_LIMIT = 4;
|
|
29
|
+
const RUNTIME_COLUMN_LABEL_CACHE = [];
|
|
30
|
+
const RUNTIME_A1_CACHE_COLUMN_LIMIT = 64;
|
|
31
|
+
const RUNTIME_A1_CACHE_ROW_LIMIT = 4096;
|
|
32
|
+
const RUNTIME_A1_CACHE = [];
|
|
33
|
+
function countPotentialNewTrackedCellMutations(refs) {
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (let index = 0; index < refs.length; index += 1) {
|
|
36
|
+
const ref = refs[index];
|
|
37
|
+
if (ref && ref.cellIndex === undefined && ref.mutation.kind !== 'clearCell') {
|
|
38
|
+
count += 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
43
|
+
function canSkipDimensionUpdateAfterLiteralMutation(refs, potentialNewCells) {
|
|
44
|
+
if (potentialNewCells !== 0 || refs.length !== 1) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const ref = refs[0];
|
|
48
|
+
return ref?.cellIndex !== undefined && ref.mutation.kind === 'setCellValue' && ref.mutation.value !== null;
|
|
49
|
+
}
|
|
50
|
+
function readTrackedRuntimeCellValue(cellStore, cellIndex, strings) {
|
|
51
|
+
const tag = cellStore.tags[cellIndex] ?? ValueTag.Empty;
|
|
52
|
+
switch (tag) {
|
|
53
|
+
case ValueTag.Number:
|
|
54
|
+
return { tag: ValueTag.Number, value: cellStore.numbers[cellIndex] ?? 0 };
|
|
55
|
+
case ValueTag.Boolean:
|
|
56
|
+
return { tag: ValueTag.Boolean, value: (cellStore.numbers[cellIndex] ?? 0) !== 0 };
|
|
57
|
+
case ValueTag.String:
|
|
58
|
+
return cellStore.getValue(cellIndex, (stringId) => strings.get(stringId));
|
|
59
|
+
case ValueTag.Error:
|
|
60
|
+
return { tag: ValueTag.Error, code: cellStore.errors[cellIndex] };
|
|
61
|
+
case ValueTag.Empty:
|
|
62
|
+
default:
|
|
63
|
+
return { tag: ValueTag.Empty };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
14
66
|
const DEFAULT_CONFIG = Object.freeze({
|
|
15
67
|
accentSensitive: false,
|
|
16
68
|
caseSensitive: false,
|
|
@@ -282,9 +334,6 @@ function matrixContainsFormulaContent(content) {
|
|
|
282
334
|
function isDeferredBatchLiteralContent(content) {
|
|
283
335
|
return content === null || typeof content === 'boolean' || typeof content === 'number' || typeof content === 'string';
|
|
284
336
|
}
|
|
285
|
-
function canUseInitialMixedSheetFastPath(content) {
|
|
286
|
-
return content.some((row) => row.some((value) => typeof value === 'string' && value.trim().startsWith('=')));
|
|
287
|
-
}
|
|
288
337
|
function stripLeadingEquals(formula) {
|
|
289
338
|
return formula.trim().startsWith('=') ? formula.trim().slice(1) : formula.trim();
|
|
290
339
|
}
|
|
@@ -509,17 +558,123 @@ function validateWorkPaperConfig(config) {
|
|
|
509
558
|
}
|
|
510
559
|
}
|
|
511
560
|
}
|
|
512
|
-
function
|
|
561
|
+
function inspectSheetDimensionsWithinLimits(sheetName, sheet, config) {
|
|
513
562
|
const height = sheet.length;
|
|
514
|
-
|
|
563
|
+
let width = 0;
|
|
564
|
+
let materializedHeight = 0;
|
|
565
|
+
let materializedWidth = 0;
|
|
566
|
+
for (let rowIndex = 0; rowIndex < sheet.length; rowIndex += 1) {
|
|
567
|
+
const row = sheet[rowIndex];
|
|
568
|
+
if (!Array.isArray(row)) {
|
|
569
|
+
throw new WorkPaperUnableToParseError({ sheetName, reason: 'Rows must be arrays' });
|
|
570
|
+
}
|
|
571
|
+
width = Math.max(width, row.length);
|
|
572
|
+
for (let colIndex = 0; colIndex < row.length; colIndex += 1) {
|
|
573
|
+
if (row[colIndex] !== null) {
|
|
574
|
+
materializedHeight = Math.max(materializedHeight, rowIndex + 1);
|
|
575
|
+
materializedWidth = Math.max(materializedWidth, colIndex + 1);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
515
579
|
if (height > (config.maxRows ?? MAX_ROWS) || width > (config.maxColumns ?? MAX_COLS)) {
|
|
516
580
|
throw new WorkPaperSheetSizeLimitExceededError();
|
|
517
581
|
}
|
|
518
|
-
|
|
582
|
+
return { width: materializedWidth, height: materializedHeight };
|
|
583
|
+
}
|
|
584
|
+
function inspectRuntimeSnapshotSheetDimensionsWithinLimits(args) {
|
|
585
|
+
let materializedHeight = 0;
|
|
586
|
+
let materializedWidth = 0;
|
|
587
|
+
const dimensions = args.runtimeSheetCells?.dimensions;
|
|
588
|
+
if (dimensions &&
|
|
589
|
+
Number.isInteger(dimensions.width) &&
|
|
590
|
+
Number.isInteger(dimensions.height) &&
|
|
591
|
+
dimensions.width >= 0 &&
|
|
592
|
+
dimensions.height >= 0) {
|
|
593
|
+
materializedWidth = dimensions.width;
|
|
594
|
+
materializedHeight = dimensions.height;
|
|
595
|
+
}
|
|
596
|
+
else if (args.runtimeSheetCells?.coords) {
|
|
597
|
+
for (const coords of args.runtimeSheetCells.coords) {
|
|
598
|
+
materializedHeight = Math.max(materializedHeight, coords.row + 1);
|
|
599
|
+
materializedWidth = Math.max(materializedWidth, coords.col + 1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
for (const cell of args.snapshotSheet.cells) {
|
|
604
|
+
const parsed = parseCellAddress(cell.address, args.sheetName);
|
|
605
|
+
materializedHeight = Math.max(materializedHeight, parsed.row + 1);
|
|
606
|
+
materializedWidth = Math.max(materializedWidth, parsed.col + 1);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (materializedHeight > (args.config.maxRows ?? MAX_ROWS) || materializedWidth > (args.config.maxColumns ?? MAX_COLS)) {
|
|
610
|
+
throw new WorkPaperSheetSizeLimitExceededError();
|
|
611
|
+
}
|
|
612
|
+
return { width: materializedWidth, height: materializedHeight };
|
|
613
|
+
}
|
|
614
|
+
function inspectSheetWithinLimits(sheetName, sheet, config) {
|
|
615
|
+
const height = sheet.length;
|
|
616
|
+
let width = 0;
|
|
617
|
+
let materializedHeight = 0;
|
|
618
|
+
let materializedWidth = 0;
|
|
619
|
+
let materializedCellCount = 0;
|
|
620
|
+
let formulaCellCount = 0;
|
|
621
|
+
let hasFormula = false;
|
|
622
|
+
for (let rowIndex = 0; rowIndex < sheet.length; rowIndex += 1) {
|
|
623
|
+
const row = sheet[rowIndex];
|
|
519
624
|
if (!Array.isArray(row)) {
|
|
520
625
|
throw new WorkPaperUnableToParseError({ sheetName, reason: 'Rows must be arrays' });
|
|
521
626
|
}
|
|
522
|
-
|
|
627
|
+
width = Math.max(width, row.length);
|
|
628
|
+
for (let colIndex = 0; colIndex < row.length; colIndex += 1) {
|
|
629
|
+
const cell = row[colIndex];
|
|
630
|
+
if (cell !== null) {
|
|
631
|
+
materializedCellCount += 1;
|
|
632
|
+
materializedHeight = Math.max(materializedHeight, rowIndex + 1);
|
|
633
|
+
materializedWidth = Math.max(materializedWidth, colIndex + 1);
|
|
634
|
+
}
|
|
635
|
+
if (typeof cell === 'string' && cellHasFormulaPrefix(cell)) {
|
|
636
|
+
formulaCellCount += 1;
|
|
637
|
+
hasFormula = true;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (height > (config.maxRows ?? MAX_ROWS) || width > (config.maxColumns ?? MAX_COLS)) {
|
|
642
|
+
throw new WorkPaperSheetSizeLimitExceededError();
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
hasFormula,
|
|
646
|
+
dimensions: { width: materializedWidth, height: materializedHeight },
|
|
647
|
+
materializedCellCount,
|
|
648
|
+
maxColumnCount: width,
|
|
649
|
+
formulaCellCount,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function cellHasFormulaPrefix(value) {
|
|
653
|
+
const first = value.charCodeAt(0);
|
|
654
|
+
if (first === 61) {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
if (first !== 32 && first !== 9 && first !== 10 && first !== 13) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
return value.trimStart().charCodeAt(0) === 61;
|
|
661
|
+
}
|
|
662
|
+
function runtimeSnapshotMatchesSheetNames(sheets, runtimeSnapshot) {
|
|
663
|
+
const sheetNames = Object.keys(sheets);
|
|
664
|
+
if (runtimeSnapshot.sheets.length !== sheetNames.length) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const matchedNames = new Set();
|
|
668
|
+
for (const snapshotSheet of runtimeSnapshot.sheets) {
|
|
669
|
+
if (!Object.prototype.hasOwnProperty.call(sheets, snapshotSheet.name) || matchedNames.has(snapshotSheet.name)) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
matchedNames.add(snapshotSheet.name);
|
|
673
|
+
}
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
function validateSheetWithinLimits(sheetName, sheet, config) {
|
|
677
|
+
inspectSheetWithinLimits(sheetName, sheet, config);
|
|
523
678
|
}
|
|
524
679
|
function functionPluginIds(config) {
|
|
525
680
|
return (config.functionPlugins ?? []).map((plugin) => plugin.id).toSorted();
|
|
@@ -613,6 +768,9 @@ class WorkPaperEmitter {
|
|
|
613
768
|
};
|
|
614
769
|
this.onDetailed(eventName, wrapper);
|
|
615
770
|
}
|
|
771
|
+
hasListeners(eventName) {
|
|
772
|
+
return this.listeners[eventName].size > 0 || this.detailedListeners[eventName].size > 0;
|
|
773
|
+
}
|
|
616
774
|
emitDetailed(event) {
|
|
617
775
|
this.dispatchDetailed(event);
|
|
618
776
|
}
|
|
@@ -709,6 +867,8 @@ export class WorkPaper {
|
|
|
709
867
|
visibilityCache = null;
|
|
710
868
|
namedExpressionValueCache = null;
|
|
711
869
|
sheetRecordsCache = null;
|
|
870
|
+
sheetDimensionsCache = new Map();
|
|
871
|
+
spillSheetIdsCache = null;
|
|
712
872
|
batchDepth = 0;
|
|
713
873
|
batchStartVisibility = null;
|
|
714
874
|
batchStartNamedValues = null;
|
|
@@ -724,9 +884,124 @@ export class WorkPaper {
|
|
|
724
884
|
suspendedCellMutationPotentialNewCells = 0;
|
|
725
885
|
queuedEvents = [];
|
|
726
886
|
trackedEngineEvents = [];
|
|
887
|
+
pendingLazyTrackedChanges = [];
|
|
727
888
|
engineEventCaptureEnabled = true;
|
|
889
|
+
retainedTrackedEngineEventIndicesDepth = 0;
|
|
728
890
|
unsubscribeEngineEvents = null;
|
|
729
891
|
disposed = false;
|
|
892
|
+
getVisibleCellIndex(sheetId, row, col) {
|
|
893
|
+
const sheet = this.engine.workbook.getSheetById(sheetId);
|
|
894
|
+
if (!sheet) {
|
|
895
|
+
return undefined;
|
|
896
|
+
}
|
|
897
|
+
return this.getVisibleCellIndexInSheet(sheet, row, col);
|
|
898
|
+
}
|
|
899
|
+
getVisibleCellIndexInSheet(sheet, row, col) {
|
|
900
|
+
if (sheet.structureVersion === 1) {
|
|
901
|
+
const cellIndex = sheet.grid.getPhysical(row, col);
|
|
902
|
+
return cellIndex === -1 ? undefined : cellIndex;
|
|
903
|
+
}
|
|
904
|
+
return sheet.logical.getVisibleCell(row, col);
|
|
905
|
+
}
|
|
906
|
+
cacheSheetDimensions(sheetId, dimensions) {
|
|
907
|
+
this.sheetDimensionsCache.set(sheetId, { width: dimensions.width, height: dimensions.height });
|
|
908
|
+
}
|
|
909
|
+
cacheInitializedSheetDimensions(sheetId, dimensions) {
|
|
910
|
+
if (this.sheetHasSpills(sheetId)) {
|
|
911
|
+
this.sheetDimensionsCache.delete(sheetId);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
this.cacheSheetDimensions(sheetId, dimensions);
|
|
915
|
+
}
|
|
916
|
+
sheetHasSpills(sheetId) {
|
|
917
|
+
if (this.spillSheetIdsCache === null) {
|
|
918
|
+
const spillSheetIds = new Set();
|
|
919
|
+
this.engine.workbook.listSpills().forEach((spill) => {
|
|
920
|
+
const spillSheet = this.engine.workbook.getSheet(spill.sheetName);
|
|
921
|
+
if (spillSheet) {
|
|
922
|
+
spillSheetIds.add(spillSheet.id);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
this.spillSheetIdsCache = spillSheetIds;
|
|
926
|
+
}
|
|
927
|
+
return this.spillSheetIdsCache.has(sheetId);
|
|
928
|
+
}
|
|
929
|
+
scanSheetDimensions(sheet) {
|
|
930
|
+
let width = 0;
|
|
931
|
+
let height = 0;
|
|
932
|
+
sheet.grid.forEachCellEntry((_cellIndex, row, col) => {
|
|
933
|
+
height = Math.max(height, row + 1);
|
|
934
|
+
width = Math.max(width, col + 1);
|
|
935
|
+
});
|
|
936
|
+
return { width, height };
|
|
937
|
+
}
|
|
938
|
+
invalidateSheetDimensions(sheetId) {
|
|
939
|
+
this.sheetDimensionsCache.delete(sheetId);
|
|
940
|
+
}
|
|
941
|
+
invalidateAllSheetDimensions() {
|
|
942
|
+
this.sheetDimensionsCache.clear();
|
|
943
|
+
this.spillSheetIdsCache = null;
|
|
944
|
+
}
|
|
945
|
+
expandCachedSheetDimensions(sheetId, row, col) {
|
|
946
|
+
const cached = this.sheetDimensionsCache.get(sheetId);
|
|
947
|
+
if (!cached) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
cached.height = Math.max(cached.height, row + 1);
|
|
951
|
+
cached.width = Math.max(cached.width, col + 1);
|
|
952
|
+
}
|
|
953
|
+
invalidateCachedSheetDimensionsIfEdge(sheetId, row, col) {
|
|
954
|
+
const cached = this.sheetDimensionsCache.get(sheetId);
|
|
955
|
+
if (!cached) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (row + 1 >= cached.height || col + 1 >= cached.width) {
|
|
959
|
+
this.invalidateSheetDimensions(sheetId);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
updateSheetDimensionsAfterCellMutationRefs(refs) {
|
|
963
|
+
if (this.sheetDimensionsCache.size === 0) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
if (refs.length === 1) {
|
|
967
|
+
const ref = refs[0];
|
|
968
|
+
const mutation = ref?.mutation;
|
|
969
|
+
if (ref && mutation && mutation.kind !== 'setCellFormula') {
|
|
970
|
+
const cached = this.sheetDimensionsCache.get(ref.sheetId);
|
|
971
|
+
if (!cached) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const noKnownSpills = this.spillSheetIdsCache !== null && !this.spillSheetIdsCache.has(ref.sheetId);
|
|
975
|
+
if (noKnownSpills &&
|
|
976
|
+
(mutation.kind === 'setCellValue'
|
|
977
|
+
? mutation.row + 1 <= cached.height && mutation.col + 1 <= cached.width
|
|
978
|
+
: mutation.row + 1 < cached.height && mutation.col + 1 < cached.width)) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
for (let index = 0; index < refs.length; index += 1) {
|
|
984
|
+
const ref = refs[index];
|
|
985
|
+
if (!ref) {
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
const mutation = ref.mutation;
|
|
989
|
+
if (mutation.kind === 'setCellFormula') {
|
|
990
|
+
this.spillSheetIdsCache = null;
|
|
991
|
+
this.invalidateSheetDimensions(ref.sheetId);
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (this.sheetHasSpills(ref.sheetId)) {
|
|
995
|
+
this.invalidateSheetDimensions(ref.sheetId);
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (mutation.kind === 'clearCell') {
|
|
999
|
+
this.invalidateCachedSheetDimensionsIfEdge(ref.sheetId, mutation.row, mutation.col);
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
this.expandCachedSheetDimensions(ref.sheetId, mutation.row, mutation.col);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
730
1005
|
constructor(configInput = {}) {
|
|
731
1006
|
ensureCustomAdapterInstalled();
|
|
732
1007
|
validateWorkPaperConfig(configInput);
|
|
@@ -739,6 +1014,7 @@ export class WorkPaper {
|
|
|
739
1014
|
useColumnIndex: this.config.useColumnIndex,
|
|
740
1015
|
trackReplicaVersions: false,
|
|
741
1016
|
});
|
|
1017
|
+
this.invalidateAllSheetDimensions();
|
|
742
1018
|
this.attachEngineEventTracking();
|
|
743
1019
|
this.captureFunctionRegistry();
|
|
744
1020
|
this.internals = Object.freeze({
|
|
@@ -812,38 +1088,69 @@ export class WorkPaper {
|
|
|
812
1088
|
static buildFromSheets(sheets, configInput = {}, namedExpressions = []) {
|
|
813
1089
|
const workbook = new WorkPaper(configInput);
|
|
814
1090
|
const runtimeSnapshot = namedExpressions.length === 0 ? readRuntimeSnapshot(sheets) : undefined;
|
|
815
|
-
const runtimeSnapshotMatchesSheets = runtimeSnapshot !== undefined &&
|
|
816
|
-
|
|
817
|
-
runtimeSnapshot.sheets.
|
|
1091
|
+
const runtimeSnapshotMatchesSheets = runtimeSnapshot !== undefined && runtimeSnapshotMatchesSheetNames(sheets, runtimeSnapshot);
|
|
1092
|
+
const runtimeSnapshotSheetsByName = runtimeSnapshotMatchesSheets
|
|
1093
|
+
? new Map(runtimeSnapshot.sheets.map((sheet) => [sheet.name, sheet]))
|
|
1094
|
+
: undefined;
|
|
1095
|
+
const runtimeImageSheetCellsByName = runtimeSnapshotMatchesSheets && runtimeSnapshot
|
|
1096
|
+
? new Map((readRuntimeImage(runtimeSnapshot)?.sheetCells ?? []).map((sheet) => [sheet.sheetName, sheet]))
|
|
1097
|
+
: undefined;
|
|
1098
|
+
const inspectedSheets = new Map();
|
|
818
1099
|
Object.entries(sheets).forEach(([sheetName, sheet]) => {
|
|
819
|
-
|
|
1100
|
+
const snapshotSheet = runtimeSnapshotSheetsByName?.get(sheetName);
|
|
1101
|
+
inspectedSheets.set(sheetName, snapshotSheet
|
|
1102
|
+
? (() => {
|
|
1103
|
+
const dimensions = inspectRuntimeSnapshotSheetDimensionsWithinLimits({
|
|
1104
|
+
sheetName,
|
|
1105
|
+
snapshotSheet,
|
|
1106
|
+
runtimeSheetCells: runtimeImageSheetCellsByName?.get(sheetName),
|
|
1107
|
+
config: workbook.config,
|
|
1108
|
+
});
|
|
1109
|
+
return {
|
|
1110
|
+
hasFormula: false,
|
|
1111
|
+
dimensions,
|
|
1112
|
+
materializedCellCount: runtimeImageSheetCellsByName?.get(sheetName)?.cellCount ?? 0,
|
|
1113
|
+
maxColumnCount: dimensions.width,
|
|
1114
|
+
formulaCellCount: 0,
|
|
1115
|
+
};
|
|
1116
|
+
})()
|
|
1117
|
+
: inspectSheetWithinLimits(sheetName, sheet, workbook.config));
|
|
820
1118
|
});
|
|
821
1119
|
workbook.withEngineEventCaptureDisabled(() => {
|
|
822
1120
|
if (runtimeSnapshot && runtimeSnapshotMatchesSheets) {
|
|
823
1121
|
workbook.engine.importSnapshot(runtimeSnapshot);
|
|
824
|
-
return;
|
|
825
1122
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1123
|
+
else {
|
|
1124
|
+
Object.keys(sheets).forEach((sheetName) => {
|
|
1125
|
+
workbook.engine.createSheet(sheetName);
|
|
1126
|
+
});
|
|
1127
|
+
namedExpressions.forEach((expression) => {
|
|
1128
|
+
workbook.upsertNamedExpressionInternal(expression, { duringInitialization: true });
|
|
1129
|
+
});
|
|
1130
|
+
Object.entries(sheets).forEach(([sheetName, sheet]) => {
|
|
1131
|
+
const sheetId = workbook.requireSheetId(sheetName);
|
|
1132
|
+
const inspected = inspectedSheets.get(sheetName);
|
|
1133
|
+
if (!inspected?.hasFormula) {
|
|
1134
|
+
loadInitialLiteralSheet(workbook.engine, sheetId, sheet, inspected);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const rewriteInitialFormula = workbook.namedExpressions.size === 0 && workbook.functionAliasLookup.size === 0
|
|
1138
|
+
? (formula) => formula
|
|
1139
|
+
: (formula) => workbook.rewriteFormulaForStorage(formula, sheetId);
|
|
838
1140
|
loadInitialMixedSheet({
|
|
839
1141
|
engine: workbook.engine,
|
|
840
1142
|
sheetId,
|
|
841
1143
|
content: sheet,
|
|
842
|
-
rewriteFormula:
|
|
1144
|
+
rewriteFormula: rewriteInitialFormula,
|
|
1145
|
+
inspection: inspected,
|
|
843
1146
|
});
|
|
844
|
-
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
Object.keys(sheets).forEach((sheetName) => {
|
|
1150
|
+
const inspected = inspectedSheets.get(sheetName);
|
|
1151
|
+
if (inspected) {
|
|
1152
|
+
workbook.cacheInitializedSheetDimensions(workbook.requireSheetId(sheetName), inspected.dimensions);
|
|
845
1153
|
}
|
|
846
|
-
workbook.replaceSheetContentInternal(sheetId, sheet, { duringInitialization: true });
|
|
847
1154
|
});
|
|
848
1155
|
});
|
|
849
1156
|
workbook.clearHistoryStacks();
|
|
@@ -1009,6 +1316,7 @@ export class WorkPaper {
|
|
|
1009
1316
|
}
|
|
1010
1317
|
updateConfig(next) {
|
|
1011
1318
|
this.assertNotDisposed();
|
|
1319
|
+
this.materializePendingLazyTrackedChanges();
|
|
1012
1320
|
const merged = {
|
|
1013
1321
|
...this.config,
|
|
1014
1322
|
...cloneConfig(next),
|
|
@@ -1044,9 +1352,11 @@ export class WorkPaper {
|
|
|
1044
1352
|
}
|
|
1045
1353
|
rebuildAndRecalculate() {
|
|
1046
1354
|
this.assertNotDisposed();
|
|
1355
|
+
this.materializePendingLazyTrackedChanges();
|
|
1047
1356
|
if (this.shouldSuppressEvents()) {
|
|
1048
1357
|
try {
|
|
1049
1358
|
this.engine.recalculateNow();
|
|
1359
|
+
this.invalidateAllSheetDimensions();
|
|
1050
1360
|
}
|
|
1051
1361
|
catch (error) {
|
|
1052
1362
|
throw new WorkPaperOperationError(this.messageOf(error, 'Recalculation failed'));
|
|
@@ -1058,6 +1368,7 @@ export class WorkPaper {
|
|
|
1058
1368
|
this.drainTrackedEngineEvents();
|
|
1059
1369
|
try {
|
|
1060
1370
|
this.engine.recalculateNow();
|
|
1371
|
+
this.invalidateAllSheetDimensions();
|
|
1061
1372
|
}
|
|
1062
1373
|
catch (error) {
|
|
1063
1374
|
throw new WorkPaperOperationError(this.messageOf(error, 'Recalculation failed'));
|
|
@@ -1070,13 +1381,14 @@ export class WorkPaper {
|
|
|
1070
1381
|
...this.computeCellChanges(beforeVisibility, afterVisibility),
|
|
1071
1382
|
...this.computeNamedExpressionChanges(beforeNames, afterNames),
|
|
1072
1383
|
];
|
|
1073
|
-
if (changes.length > 0) {
|
|
1384
|
+
if (changes.length > 0 && this.emitter.hasListeners('valuesUpdated')) {
|
|
1074
1385
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
1075
1386
|
}
|
|
1076
1387
|
return changes;
|
|
1077
1388
|
}
|
|
1078
1389
|
batch(batchOperations) {
|
|
1079
1390
|
this.assertNotDisposed();
|
|
1391
|
+
this.materializePendingLazyTrackedChanges();
|
|
1080
1392
|
const isOutermost = this.batchDepth === 0;
|
|
1081
1393
|
if (isOutermost) {
|
|
1082
1394
|
this.batchUsesTrackedFastPath = this.canUseTrackedMutationFastPath();
|
|
@@ -1098,22 +1410,32 @@ export class WorkPaper {
|
|
|
1098
1410
|
finally {
|
|
1099
1411
|
this.batchDepth -= 1;
|
|
1100
1412
|
if (isOutermost) {
|
|
1101
|
-
this.
|
|
1413
|
+
if (this.batchUsesTrackedFastPath) {
|
|
1414
|
+
this.withRetainedTrackedEngineEventIndices(() => {
|
|
1415
|
+
this.flushPendingBatchOps();
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
this.flushPendingBatchOps();
|
|
1420
|
+
}
|
|
1102
1421
|
this.mergeUndoHistory(this.batchUndoStackLength);
|
|
1103
1422
|
}
|
|
1104
1423
|
}
|
|
1105
1424
|
if (!isOutermost) {
|
|
1106
1425
|
return [];
|
|
1107
1426
|
}
|
|
1427
|
+
const shouldEmitValuesUpdated = this.emitter.hasListeners('valuesUpdated');
|
|
1108
1428
|
const changes = this.batchUsesTrackedFastPath
|
|
1109
|
-
? this.computeTrackedChangesWithoutVisibilityCache(this.drainTrackedEngineEvents()
|
|
1429
|
+
? this.computeTrackedChangesWithoutVisibilityCache(this.drainTrackedEngineEvents(), {
|
|
1430
|
+
preferLazyPublicChanges: !shouldEmitValuesUpdated,
|
|
1431
|
+
})
|
|
1110
1432
|
: this.computeChangesAfterMutation(this.batchStartVisibility ?? new Map(), this.batchStartNamedValues ?? new Map());
|
|
1111
1433
|
this.batchUsesTrackedFastPath = false;
|
|
1112
1434
|
this.batchStartVisibility = null;
|
|
1113
1435
|
this.batchStartNamedValues = null;
|
|
1114
1436
|
if (!this.evaluationSuspended) {
|
|
1115
1437
|
this.flushQueuedEvents();
|
|
1116
|
-
if (changes.length > 0) {
|
|
1438
|
+
if (changes.length > 0 && shouldEmitValuesUpdated) {
|
|
1117
1439
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
1118
1440
|
}
|
|
1119
1441
|
}
|
|
@@ -1121,6 +1443,7 @@ export class WorkPaper {
|
|
|
1121
1443
|
}
|
|
1122
1444
|
suspendEvaluation() {
|
|
1123
1445
|
this.assertNotDisposed();
|
|
1446
|
+
this.materializePendingLazyTrackedChanges();
|
|
1124
1447
|
if (this.evaluationSuspended) {
|
|
1125
1448
|
return;
|
|
1126
1449
|
}
|
|
@@ -1141,12 +1464,23 @@ export class WorkPaper {
|
|
|
1141
1464
|
}
|
|
1142
1465
|
resumeEvaluation() {
|
|
1143
1466
|
this.assertNotDisposed();
|
|
1467
|
+
this.materializePendingLazyTrackedChanges();
|
|
1144
1468
|
if (!this.evaluationSuspended) {
|
|
1145
1469
|
return [];
|
|
1146
1470
|
}
|
|
1147
|
-
this.
|
|
1471
|
+
if (this.suspendedUsesTrackedFastPath) {
|
|
1472
|
+
this.withRetainedTrackedEngineEventIndices(() => {
|
|
1473
|
+
this.flushSuspendedCellMutations();
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
this.flushSuspendedCellMutations();
|
|
1478
|
+
}
|
|
1479
|
+
const shouldEmitValuesUpdated = this.emitter.hasListeners('valuesUpdated');
|
|
1148
1480
|
const changes = this.suspendedUsesTrackedFastPath
|
|
1149
|
-
? this.computeTrackedChangesWithoutVisibilityCache(this.drainTrackedEngineEvents()
|
|
1481
|
+
? this.computeTrackedChangesWithoutVisibilityCache(this.drainTrackedEngineEvents(), {
|
|
1482
|
+
preferLazyPublicChanges: !shouldEmitValuesUpdated,
|
|
1483
|
+
})
|
|
1150
1484
|
: this.computeChangesAfterMutation(this.suspendedVisibility ?? new Map(), this.suspendedNamedValues ?? new Map());
|
|
1151
1485
|
this.evaluationSuspended = false;
|
|
1152
1486
|
this.suspendedVisibility = null;
|
|
@@ -1154,7 +1488,7 @@ export class WorkPaper {
|
|
|
1154
1488
|
this.suspendedUsesTrackedFastPath = false;
|
|
1155
1489
|
this.flushQueuedEvents();
|
|
1156
1490
|
this.emitter.emitDetailed({ eventName: 'evaluationResumed', payload: { changes } });
|
|
1157
|
-
if (changes.length > 0) {
|
|
1491
|
+
if (changes.length > 0 && shouldEmitValuesUpdated) {
|
|
1158
1492
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
1159
1493
|
}
|
|
1160
1494
|
return changes;
|
|
@@ -1164,19 +1498,39 @@ export class WorkPaper {
|
|
|
1164
1498
|
}
|
|
1165
1499
|
undo() {
|
|
1166
1500
|
this.assertNotDisposed();
|
|
1501
|
+
const preservesPositions = !this.historyTopIsCellMutations(this.getUndoStack());
|
|
1502
|
+
if (this.canUseTrackedMutationFastPath()) {
|
|
1503
|
+
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1504
|
+
if (!this.engine.undo()) {
|
|
1505
|
+
throw new WorkPaperNoOperationToUndoError();
|
|
1506
|
+
}
|
|
1507
|
+
this.invalidateAllSheetDimensions();
|
|
1508
|
+
}, { preservePendingTrackedPositions: preservesPositions });
|
|
1509
|
+
}
|
|
1167
1510
|
return this.captureChanges(undefined, () => {
|
|
1168
1511
|
if (!this.engine.undo()) {
|
|
1169
1512
|
throw new WorkPaperNoOperationToUndoError();
|
|
1170
1513
|
}
|
|
1171
|
-
|
|
1514
|
+
this.invalidateAllSheetDimensions();
|
|
1515
|
+
}, { preservePendingTrackedPositions: preservesPositions });
|
|
1172
1516
|
}
|
|
1173
1517
|
redo() {
|
|
1174
1518
|
this.assertNotDisposed();
|
|
1519
|
+
const preservesPositions = !this.historyTopIsCellMutations(this.getRedoStack());
|
|
1520
|
+
if (this.canUseTrackedMutationFastPath()) {
|
|
1521
|
+
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1522
|
+
if (!this.engine.redo()) {
|
|
1523
|
+
throw new WorkPaperNoOperationToRedoError();
|
|
1524
|
+
}
|
|
1525
|
+
this.invalidateAllSheetDimensions();
|
|
1526
|
+
}, { preservePendingTrackedPositions: preservesPositions });
|
|
1527
|
+
}
|
|
1175
1528
|
return this.captureChanges(undefined, () => {
|
|
1176
1529
|
if (!this.engine.redo()) {
|
|
1177
1530
|
throw new WorkPaperNoOperationToRedoError();
|
|
1178
1531
|
}
|
|
1179
|
-
|
|
1532
|
+
this.invalidateAllSheetDimensions();
|
|
1533
|
+
}, { preservePendingTrackedPositions: preservesPositions });
|
|
1180
1534
|
}
|
|
1181
1535
|
isThereSomethingToUndo() {
|
|
1182
1536
|
return this.getUndoStack().length > 0;
|
|
@@ -1255,7 +1609,11 @@ export class WorkPaper {
|
|
|
1255
1609
|
}
|
|
1256
1610
|
getCellValue(address) {
|
|
1257
1611
|
this.assertReadable();
|
|
1258
|
-
|
|
1612
|
+
const sheet = this.sheetRecord(address.sheet);
|
|
1613
|
+
const cellIndex = this.getVisibleCellIndexInSheet(sheet, address.row, address.col);
|
|
1614
|
+
return cellIndex === undefined
|
|
1615
|
+
? emptyValue()
|
|
1616
|
+
: readTrackedRuntimeCellValue(this.engine.workbook.cellStore, cellIndex, this.engine.strings);
|
|
1259
1617
|
}
|
|
1260
1618
|
getCellFormula(address) {
|
|
1261
1619
|
this.prepareReadableState();
|
|
@@ -1283,6 +1641,11 @@ export class WorkPaper {
|
|
|
1283
1641
|
}
|
|
1284
1642
|
getRangeValues(range) {
|
|
1285
1643
|
this.assertReadable();
|
|
1644
|
+
assertRange(range);
|
|
1645
|
+
const fastValues = readFastPhysicalRangeValues(this.engine, range);
|
|
1646
|
+
if (fastValues !== undefined) {
|
|
1647
|
+
return fastValues;
|
|
1648
|
+
}
|
|
1286
1649
|
const ref = this.rangeRef(range);
|
|
1287
1650
|
return this.engine.getRangeValues(ref);
|
|
1288
1651
|
}
|
|
@@ -1341,13 +1704,13 @@ export class WorkPaper {
|
|
|
1341
1704
|
getSheetDimensions(sheetId) {
|
|
1342
1705
|
this.prepareReadableState();
|
|
1343
1706
|
const sheet = this.sheetRecord(sheetId);
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
return
|
|
1707
|
+
const cached = this.sheetDimensionsCache.get(sheetId);
|
|
1708
|
+
if (cached) {
|
|
1709
|
+
return { width: cached.width, height: cached.height };
|
|
1710
|
+
}
|
|
1711
|
+
const dimensions = this.scanSheetDimensions(sheet);
|
|
1712
|
+
this.cacheSheetDimensions(sheetId, dimensions);
|
|
1713
|
+
return dimensions;
|
|
1351
1714
|
}
|
|
1352
1715
|
simpleCellAddressFromString(value, defaultSheetId) {
|
|
1353
1716
|
this.assertNotDisposed();
|
|
@@ -1745,6 +2108,81 @@ export class WorkPaper {
|
|
|
1745
2108
|
const { hours, minutes, seconds } = dateTime;
|
|
1746
2109
|
return { hours, minutes, seconds };
|
|
1747
2110
|
}
|
|
2111
|
+
trySetExistingNumericCellContentsWithTrackedFastPath(args) {
|
|
2112
|
+
if (!this.canUseTrackedMutationFastPath() || args.sheet.structureVersion !== 1) {
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
const cellStore = this.engine.workbook.cellStore;
|
|
2116
|
+
if (cellStore.sheetIds[args.cellIndex] !== args.address.sheet ||
|
|
2117
|
+
cellStore.rows[args.cellIndex] !== args.address.row ||
|
|
2118
|
+
cellStore.cols[args.cellIndex] !== args.address.col ||
|
|
2119
|
+
(cellStore.formulaIds[args.cellIndex] ?? 0) !== 0 ||
|
|
2120
|
+
((cellStore.flags[args.cellIndex] ?? 0) & FAST_EXISTING_NUMERIC_LITERAL_FLAGS) !== 0 ||
|
|
2121
|
+
cellStore.tags[args.cellIndex] !== ValueTag.Number) {
|
|
2122
|
+
return null;
|
|
2123
|
+
}
|
|
2124
|
+
const existingNumericMutationEngine = this.engine;
|
|
2125
|
+
if (typeof existingNumericMutationEngine.tryApplyExistingNumericCellMutationAt !== 'function') {
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
if (this.pendingLazyTrackedChanges.length > 0) {
|
|
2129
|
+
this.materializePendingLazyTrackedChanges();
|
|
2130
|
+
}
|
|
2131
|
+
if (this.trackedEngineEvents.length > 0) {
|
|
2132
|
+
this.drainTrackedEngineEvents();
|
|
2133
|
+
}
|
|
2134
|
+
let result = null;
|
|
2135
|
+
const oldNumericValue = cellStore.numbers[args.cellIndex] ?? 0;
|
|
2136
|
+
const previousCaptureEnabled = this.engineEventCaptureEnabled;
|
|
2137
|
+
this.engineEventCaptureEnabled = false;
|
|
2138
|
+
try {
|
|
2139
|
+
if (this.pendingBatchOps.length > 0) {
|
|
2140
|
+
this.flushPendingBatchOps();
|
|
2141
|
+
}
|
|
2142
|
+
const request = {
|
|
2143
|
+
sheetId: args.address.sheet,
|
|
2144
|
+
row: args.address.row,
|
|
2145
|
+
col: args.address.col,
|
|
2146
|
+
cellIndex: args.cellIndex,
|
|
2147
|
+
value: args.value,
|
|
2148
|
+
emitTracked: false,
|
|
2149
|
+
trustedExistingNumericLiteral: true,
|
|
2150
|
+
oldNumericValue,
|
|
2151
|
+
};
|
|
2152
|
+
result = existingNumericMutationEngine.tryApplyExistingNumericCellMutationAt(request);
|
|
2153
|
+
}
|
|
2154
|
+
catch (error) {
|
|
2155
|
+
if (error instanceof Error && WORKPAPER_PUBLIC_ERROR_NAMES.has(error.name)) {
|
|
2156
|
+
throw error;
|
|
2157
|
+
}
|
|
2158
|
+
throw new WorkPaperOperationError(this.messageOf(error, 'Mutation failed'));
|
|
2159
|
+
}
|
|
2160
|
+
finally {
|
|
2161
|
+
this.engineEventCaptureEnabled = previousCaptureEnabled;
|
|
2162
|
+
}
|
|
2163
|
+
if (!result) {
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
if (this.trackedEngineEvents.length > 0) {
|
|
2167
|
+
this.trackedEngineEvents = [];
|
|
2168
|
+
}
|
|
2169
|
+
let changes = this.tryBuildDirectExistingNumericTrackedChanges(result, args.address, args.cellIndex, true, args.sheet.name, args.value);
|
|
2170
|
+
if (changes === null) {
|
|
2171
|
+
const shouldEmitValuesUpdated = this.emitter.hasListeners('valuesUpdated');
|
|
2172
|
+
const events = [this.trackedEventFromExistingNumericMutationResult(result)];
|
|
2173
|
+
changes = this.computeTrackedChangesWithoutVisibilityCache(events, {
|
|
2174
|
+
preferLazyPublicChanges: !shouldEmitValuesUpdated,
|
|
2175
|
+
});
|
|
2176
|
+
if (changes.length > 0 && shouldEmitValuesUpdated) {
|
|
2177
|
+
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2178
|
+
}
|
|
2179
|
+
return changes;
|
|
2180
|
+
}
|
|
2181
|
+
if (changes.length > 0 && this.emitter.hasListeners('valuesUpdated')) {
|
|
2182
|
+
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2183
|
+
}
|
|
2184
|
+
return changes;
|
|
2185
|
+
}
|
|
1748
2186
|
setCellContents(address, content) {
|
|
1749
2187
|
this.assertNotDisposed();
|
|
1750
2188
|
const sheet = this.sheetRecord(address.sheet);
|
|
@@ -1754,15 +2192,40 @@ export class WorkPaper {
|
|
|
1754
2192
|
if (address.row >= (this.config.maxRows ?? MAX_ROWS) || address.col >= (this.config.maxColumns ?? MAX_COLS)) {
|
|
1755
2193
|
throw new WorkPaperOperationError('Cell contents cannot be set');
|
|
1756
2194
|
}
|
|
1757
|
-
const
|
|
1758
|
-
if (this.
|
|
2195
|
+
const visibleCellIndex = this.getVisibleCellIndexInSheet(sheet, address.row, address.col);
|
|
2196
|
+
if (this.evaluationSuspended &&
|
|
2197
|
+
this.enqueueSuspendedLiteralMutation(address.sheet, address.row, address.col, content, visibleCellIndex)) {
|
|
1759
2198
|
return [];
|
|
1760
2199
|
}
|
|
1761
|
-
if (this.enqueueDeferredBatchLiteral(address.sheet, address.row, address.col, content,
|
|
2200
|
+
if (this.batchDepth !== 0 && this.enqueueDeferredBatchLiteral(address.sheet, address.row, address.col, content, visibleCellIndex)) {
|
|
1762
2201
|
return [];
|
|
1763
2202
|
}
|
|
2203
|
+
if (typeof content === 'number' && visibleCellIndex !== undefined) {
|
|
2204
|
+
const fastPathChanges = this.trySetExistingNumericCellContentsWithTrackedFastPath({
|
|
2205
|
+
sheet,
|
|
2206
|
+
address,
|
|
2207
|
+
cellIndex: visibleCellIndex,
|
|
2208
|
+
value: content,
|
|
2209
|
+
});
|
|
2210
|
+
if (fastPathChanges !== null) {
|
|
2211
|
+
return fastPathChanges;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
1764
2214
|
const mutate = () => {
|
|
1765
2215
|
this.flushPendingBatchOps();
|
|
2216
|
+
const existingNumericMutationEngine = this.engine;
|
|
2217
|
+
if (typeof content === 'number' &&
|
|
2218
|
+
visibleCellIndex !== undefined &&
|
|
2219
|
+
sheet.structureVersion === 1 &&
|
|
2220
|
+
existingNumericMutationEngine.tryApplyExistingNumericCellMutationAt?.({
|
|
2221
|
+
sheetId: address.sheet,
|
|
2222
|
+
row: address.row,
|
|
2223
|
+
col: address.col,
|
|
2224
|
+
cellIndex: visibleCellIndex,
|
|
2225
|
+
value: content,
|
|
2226
|
+
})) {
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
1766
2229
|
const mutation = content === null
|
|
1767
2230
|
? { kind: 'clearCell', row: address.row, col: address.col }
|
|
1768
2231
|
: typeof content === 'string' && content.trim().startsWith('=')
|
|
@@ -1778,16 +2241,26 @@ export class WorkPaper {
|
|
|
1778
2241
|
col: address.col,
|
|
1779
2242
|
value: content,
|
|
1780
2243
|
};
|
|
1781
|
-
this.applyCellMutationRefs([{ sheetId: address.sheet, mutation }], {
|
|
2244
|
+
this.applyCellMutationRefs([{ sheetId: address.sheet, mutation, ...(visibleCellIndex !== undefined ? { cellIndex: visibleCellIndex } : {}) }], {
|
|
1782
2245
|
captureUndo: true,
|
|
1783
|
-
potentialNewCells: content === null ||
|
|
2246
|
+
potentialNewCells: content === null || visibleCellIndex !== undefined ? 0 : 1,
|
|
1784
2247
|
source: 'local',
|
|
1785
2248
|
returnUndoOps: false,
|
|
1786
2249
|
reuseRefs: true,
|
|
1787
2250
|
});
|
|
1788
2251
|
};
|
|
1789
2252
|
if (this.canUseTrackedMutationFastPath()) {
|
|
1790
|
-
return this.captureTrackedChangesWithoutVisibilityCache(mutate
|
|
2253
|
+
return this.captureTrackedChangesWithoutVisibilityCache(mutate, {
|
|
2254
|
+
singleLiteralChange: isFormulaContent(content)
|
|
2255
|
+
? undefined
|
|
2256
|
+
: {
|
|
2257
|
+
address: { sheet: address.sheet, row: address.row, col: address.col },
|
|
2258
|
+
...(visibleCellIndex === undefined ? {} : { cellIndex: visibleCellIndex }),
|
|
2259
|
+
isPhysicalSheet: sheet.structureVersion === 1,
|
|
2260
|
+
sheetName: sheet.name,
|
|
2261
|
+
value: content,
|
|
2262
|
+
},
|
|
2263
|
+
});
|
|
1791
2264
|
}
|
|
1792
2265
|
return this.captureChanges(undefined, () => {
|
|
1793
2266
|
mutate();
|
|
@@ -1889,11 +2362,13 @@ export class WorkPaper {
|
|
|
1889
2362
|
const [start, amount] = indexes[0];
|
|
1890
2363
|
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1891
2364
|
this.engine.insertRows(this.sheetName(sheetId), start, amount);
|
|
2365
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1892
2366
|
});
|
|
1893
2367
|
}
|
|
1894
2368
|
return this.batchStructuralChanges(() => {
|
|
1895
2369
|
indexes.forEach(([start, amount]) => {
|
|
1896
2370
|
this.engine.insertRows(this.sheetName(sheetId), start, amount);
|
|
2371
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1897
2372
|
});
|
|
1898
2373
|
});
|
|
1899
2374
|
}
|
|
@@ -1906,6 +2381,7 @@ export class WorkPaper {
|
|
|
1906
2381
|
const [start, amount] = indexes[0];
|
|
1907
2382
|
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1908
2383
|
this.engine.deleteRows(this.sheetName(sheetId), start, amount);
|
|
2384
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1909
2385
|
});
|
|
1910
2386
|
}
|
|
1911
2387
|
return this.batchStructuralChanges(() => {
|
|
@@ -1913,6 +2389,7 @@ export class WorkPaper {
|
|
|
1913
2389
|
.toSorted((left, right) => right[0] - left[0])
|
|
1914
2390
|
.forEach(([start, amount]) => {
|
|
1915
2391
|
this.engine.deleteRows(this.sheetName(sheetId), start, amount);
|
|
2392
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1916
2393
|
});
|
|
1917
2394
|
});
|
|
1918
2395
|
}
|
|
@@ -1925,11 +2402,13 @@ export class WorkPaper {
|
|
|
1925
2402
|
const [start, amount] = indexes[0];
|
|
1926
2403
|
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1927
2404
|
this.engine.insertColumns(this.sheetName(sheetId), start, amount);
|
|
2405
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1928
2406
|
});
|
|
1929
2407
|
}
|
|
1930
2408
|
return this.batchStructuralChanges(() => {
|
|
1931
2409
|
indexes.forEach(([start, amount]) => {
|
|
1932
2410
|
this.engine.insertColumns(this.sheetName(sheetId), start, amount);
|
|
2411
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1933
2412
|
});
|
|
1934
2413
|
});
|
|
1935
2414
|
}
|
|
@@ -1942,6 +2421,7 @@ export class WorkPaper {
|
|
|
1942
2421
|
const [start, amount] = indexes[0];
|
|
1943
2422
|
return this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1944
2423
|
this.engine.deleteColumns(this.sheetName(sheetId), start, amount);
|
|
2424
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1945
2425
|
});
|
|
1946
2426
|
}
|
|
1947
2427
|
return this.batchStructuralChanges(() => {
|
|
@@ -1949,6 +2429,7 @@ export class WorkPaper {
|
|
|
1949
2429
|
.toSorted((left, right) => right[0] - left[0])
|
|
1950
2430
|
.forEach(([start, amount]) => {
|
|
1951
2431
|
this.engine.deleteColumns(this.sheetName(sheetId), start, amount);
|
|
2432
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1952
2433
|
});
|
|
1953
2434
|
});
|
|
1954
2435
|
}
|
|
@@ -1964,6 +2445,8 @@ export class WorkPaper {
|
|
|
1964
2445
|
startAddress: formatAddress(target.row, target.col),
|
|
1965
2446
|
endAddress: formatAddress(target.row + sourceHeight, target.col + sourceWidth),
|
|
1966
2447
|
});
|
|
2448
|
+
this.invalidateSheetDimensions(source.start.sheet);
|
|
2449
|
+
this.invalidateSheetDimensions(target.sheet);
|
|
1967
2450
|
});
|
|
1968
2451
|
}
|
|
1969
2452
|
moveRows(sheetId, start, count, target) {
|
|
@@ -1973,9 +2456,11 @@ export class WorkPaper {
|
|
|
1973
2456
|
return this.canUseTrackedStructuralFastPath()
|
|
1974
2457
|
? this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1975
2458
|
this.engine.moveRows(this.sheetName(sheetId), start, count, target);
|
|
2459
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1976
2460
|
})
|
|
1977
2461
|
: this.captureChanges(undefined, () => {
|
|
1978
2462
|
this.engine.moveRows(this.sheetName(sheetId), start, count, target);
|
|
2463
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1979
2464
|
});
|
|
1980
2465
|
}
|
|
1981
2466
|
moveColumns(sheetId, start, count, target) {
|
|
@@ -1985,13 +2470,16 @@ export class WorkPaper {
|
|
|
1985
2470
|
return this.canUseTrackedStructuralFastPath()
|
|
1986
2471
|
? this.captureTrackedChangesWithoutVisibilityCache(() => {
|
|
1987
2472
|
this.engine.moveColumns(this.sheetName(sheetId), start, count, target);
|
|
2473
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1988
2474
|
})
|
|
1989
2475
|
: this.captureChanges(undefined, () => {
|
|
1990
2476
|
this.engine.moveColumns(this.sheetName(sheetId), start, count, target);
|
|
2477
|
+
this.invalidateSheetDimensions(sheetId);
|
|
1991
2478
|
});
|
|
1992
2479
|
}
|
|
1993
2480
|
addSheet(sheetName) {
|
|
1994
2481
|
this.assertNotDisposed();
|
|
2482
|
+
this.materializePendingLazyTrackedChanges();
|
|
1995
2483
|
const name = sheetName?.trim() || this.nextSheetName();
|
|
1996
2484
|
if (!this.isItPossibleToAddSheet(name)) {
|
|
1997
2485
|
throw new WorkPaperSheetNameAlreadyTakenError(name);
|
|
@@ -2002,6 +2490,7 @@ export class WorkPaper {
|
|
|
2002
2490
|
this.engine.createSheet(name);
|
|
2003
2491
|
this.sheetRecordsCache = null;
|
|
2004
2492
|
const sheetId = this.requireSheetId(name);
|
|
2493
|
+
this.cacheSheetDimensions(sheetId, { width: 0, height: 0 });
|
|
2005
2494
|
const payload = { sheetId, sheetName: name };
|
|
2006
2495
|
if (this.shouldSuppressEvents()) {
|
|
2007
2496
|
this.queuedEvents.push({ eventName: 'sheetAdded', payload });
|
|
@@ -2010,7 +2499,7 @@ export class WorkPaper {
|
|
|
2010
2499
|
this.emitter.emitDetailed({ eventName: 'sheetAdded', payload });
|
|
2011
2500
|
}
|
|
2012
2501
|
const changes = this.computeChangesAfterMutation(beforeVisibility, beforeNames);
|
|
2013
|
-
if (!this.shouldSuppressEvents() && changes.length > 0) {
|
|
2502
|
+
if (!this.shouldSuppressEvents() && changes.length > 0 && this.emitter.hasListeners('valuesUpdated')) {
|
|
2014
2503
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2015
2504
|
}
|
|
2016
2505
|
return name;
|
|
@@ -2030,6 +2519,7 @@ export class WorkPaper {
|
|
|
2030
2519
|
}, () => {
|
|
2031
2520
|
this.engine.deleteSheet(sheetName);
|
|
2032
2521
|
this.sheetRecordsCache = null;
|
|
2522
|
+
this.invalidateSheetDimensions(sheetId);
|
|
2033
2523
|
});
|
|
2034
2524
|
}
|
|
2035
2525
|
clearSheet(sheetId) {
|
|
@@ -2046,6 +2536,7 @@ export class WorkPaper {
|
|
|
2046
2536
|
startAddress: 'A1',
|
|
2047
2537
|
endAddress: formatAddress(dimensions.height - 1, dimensions.width - 1),
|
|
2048
2538
|
});
|
|
2539
|
+
this.cacheSheetDimensions(sheetId, { width: 0, height: 0 });
|
|
2049
2540
|
});
|
|
2050
2541
|
}
|
|
2051
2542
|
setSheetContent(sheetId, content) {
|
|
@@ -2232,6 +2723,7 @@ export class WorkPaper {
|
|
|
2232
2723
|
if (this.disposed) {
|
|
2233
2724
|
return;
|
|
2234
2725
|
}
|
|
2726
|
+
this.materializePendingLazyTrackedChanges();
|
|
2235
2727
|
this.disposed = true;
|
|
2236
2728
|
this.unsubscribeEngineEvents?.();
|
|
2237
2729
|
this.unsubscribeEngineEvents = null;
|
|
@@ -2240,8 +2732,11 @@ export class WorkPaper {
|
|
|
2240
2732
|
this.clipboard = null;
|
|
2241
2733
|
this.visibilityCache = null;
|
|
2242
2734
|
this.namedExpressionValueCache = null;
|
|
2735
|
+
this.sheetDimensionsCache.clear();
|
|
2736
|
+
this.spillSheetIdsCache = null;
|
|
2243
2737
|
this.queuedEvents = [];
|
|
2244
2738
|
this.trackedEngineEvents = [];
|
|
2739
|
+
this.pendingLazyTrackedChanges = [];
|
|
2245
2740
|
this.namedExpressions.clear();
|
|
2246
2741
|
}
|
|
2247
2742
|
attachEngineEventTracking() {
|
|
@@ -2254,9 +2749,21 @@ export class WorkPaper {
|
|
|
2254
2749
|
if (!this.engineEventCaptureEnabled) {
|
|
2255
2750
|
return;
|
|
2256
2751
|
}
|
|
2257
|
-
this.trackedEngineEvents.push(captureTrackedEngineEvent(event
|
|
2752
|
+
this.trackedEngineEvents.push(captureTrackedEngineEvent(event, {
|
|
2753
|
+
borrowChangedCellIndexViews: this.retainedTrackedEngineEventIndicesDepth > 0,
|
|
2754
|
+
cloneChangedCellIndices: this.retainedTrackedEngineEventIndicesDepth === 0,
|
|
2755
|
+
}));
|
|
2258
2756
|
});
|
|
2259
2757
|
}
|
|
2758
|
+
withRetainedTrackedEngineEventIndices(callback) {
|
|
2759
|
+
this.retainedTrackedEngineEventIndicesDepth += 1;
|
|
2760
|
+
try {
|
|
2761
|
+
return callback();
|
|
2762
|
+
}
|
|
2763
|
+
finally {
|
|
2764
|
+
this.retainedTrackedEngineEventIndicesDepth -= 1;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2260
2767
|
withEngineEventCaptureDisabled(callback) {
|
|
2261
2768
|
const previous = this.engineEventCaptureEnabled;
|
|
2262
2769
|
this.engineEventCaptureEnabled = false;
|
|
@@ -2274,6 +2781,77 @@ export class WorkPaper {
|
|
|
2274
2781
|
this.trackedEngineEvents = [];
|
|
2275
2782
|
return events;
|
|
2276
2783
|
}
|
|
2784
|
+
existingNumericMutationChangedCellCount(result) {
|
|
2785
|
+
return result.changedCellIndices?.length ?? result.changedCellCount ?? 0;
|
|
2786
|
+
}
|
|
2787
|
+
existingNumericMutationChangedCellAt(result, index) {
|
|
2788
|
+
if (result.changedCellIndices) {
|
|
2789
|
+
return result.changedCellIndices[index];
|
|
2790
|
+
}
|
|
2791
|
+
if (index === 0) {
|
|
2792
|
+
return result.firstChangedCellIndex;
|
|
2793
|
+
}
|
|
2794
|
+
if (index === 1) {
|
|
2795
|
+
return result.secondChangedCellIndex;
|
|
2796
|
+
}
|
|
2797
|
+
return undefined;
|
|
2798
|
+
}
|
|
2799
|
+
materializeExistingNumericMutationChangedCellIndices(result) {
|
|
2800
|
+
if (result.changedCellIndices) {
|
|
2801
|
+
return result.changedCellIndices;
|
|
2802
|
+
}
|
|
2803
|
+
const count = this.existingNumericMutationChangedCellCount(result);
|
|
2804
|
+
const changed = new Uint32Array(count);
|
|
2805
|
+
for (let index = 0; index < count; index += 1) {
|
|
2806
|
+
changed[index] = this.existingNumericMutationChangedCellAt(result, index) ?? 0;
|
|
2807
|
+
}
|
|
2808
|
+
return changed;
|
|
2809
|
+
}
|
|
2810
|
+
trackedEventFromExistingNumericMutationResult(result) {
|
|
2811
|
+
let sortedDisjoint = true;
|
|
2812
|
+
let previous = -1;
|
|
2813
|
+
let firstChangedCellIndex;
|
|
2814
|
+
let lastChangedCellIndex;
|
|
2815
|
+
const changedCellCount = this.existingNumericMutationChangedCellCount(result);
|
|
2816
|
+
for (let index = 0; index < changedCellCount; index += 1) {
|
|
2817
|
+
const cellIndex = this.existingNumericMutationChangedCellAt(result, index) ?? -1;
|
|
2818
|
+
if (index === 0) {
|
|
2819
|
+
firstChangedCellIndex = cellIndex;
|
|
2820
|
+
}
|
|
2821
|
+
if (!Number.isInteger(cellIndex) || cellIndex < 0 || cellIndex <= previous) {
|
|
2822
|
+
sortedDisjoint = false;
|
|
2823
|
+
}
|
|
2824
|
+
previous = cellIndex;
|
|
2825
|
+
lastChangedCellIndex = cellIndex;
|
|
2826
|
+
}
|
|
2827
|
+
return {
|
|
2828
|
+
invalidation: 'cells',
|
|
2829
|
+
changedCellIndices: this.materializeExistingNumericMutationChangedCellIndices(result),
|
|
2830
|
+
changedInputCount: 1,
|
|
2831
|
+
explicitChangedCount: result.explicitChangedCount,
|
|
2832
|
+
changedCellIndicesSortedDisjoint: sortedDisjoint,
|
|
2833
|
+
...(firstChangedCellIndex === undefined ? {} : { firstChangedCellIndex }),
|
|
2834
|
+
...(lastChangedCellIndex === undefined ? {} : { lastChangedCellIndex }),
|
|
2835
|
+
hasInvalidatedRanges: false,
|
|
2836
|
+
hasInvalidatedRows: false,
|
|
2837
|
+
hasInvalidatedColumns: false,
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
trackLazyTrackedChanges(changes) {
|
|
2841
|
+
if (hasDeferredTrackedIndexChanges(changes)) {
|
|
2842
|
+
this.pendingLazyTrackedChanges.push(changes);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
materializePendingLazyTrackedChanges(options = {}) {
|
|
2846
|
+
if (this.pendingLazyTrackedChanges.length === 0) {
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
const pending = this.pendingLazyTrackedChanges;
|
|
2850
|
+
this.pendingLazyTrackedChanges = [];
|
|
2851
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
2852
|
+
detachTrackedIndexChanges(pending[index], { preservePositions: options.preservePositions });
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2277
2855
|
resetChangeTrackingCaches() {
|
|
2278
2856
|
this.sheetRecordsCache = null;
|
|
2279
2857
|
this.visibilityCache = null;
|
|
@@ -2303,11 +2881,12 @@ export class WorkPaper {
|
|
|
2303
2881
|
this.pendingBatchPotentialNewCells = 0;
|
|
2304
2882
|
this.engine.applyCellMutationsAtWithOptions(ops, {
|
|
2305
2883
|
captureUndo: true,
|
|
2306
|
-
potentialNewCells
|
|
2884
|
+
potentialNewCells,
|
|
2307
2885
|
source: 'local',
|
|
2308
2886
|
returnUndoOps: false,
|
|
2309
2887
|
reuseRefs: true,
|
|
2310
2888
|
});
|
|
2889
|
+
this.updateSheetDimensionsAfterCellMutationRefs(ops);
|
|
2311
2890
|
}
|
|
2312
2891
|
applyCellMutationRefs(refs, options) {
|
|
2313
2892
|
if (this.evaluationSuspended && (options.source ?? 'local') === 'local') {
|
|
@@ -2319,6 +2898,7 @@ export class WorkPaper {
|
|
|
2319
2898
|
const mutation = ref.mutation;
|
|
2320
2899
|
this.suspendedCellMutationRefs.push({
|
|
2321
2900
|
sheetId: ref.sheetId,
|
|
2901
|
+
...(ref.cellIndex !== undefined ? { cellIndex: ref.cellIndex } : {}),
|
|
2322
2902
|
mutation: mutation.kind === 'setCellValue'
|
|
2323
2903
|
? {
|
|
2324
2904
|
kind: 'setCellValue',
|
|
@@ -2340,11 +2920,13 @@ export class WorkPaper {
|
|
|
2340
2920
|
},
|
|
2341
2921
|
});
|
|
2342
2922
|
}
|
|
2343
|
-
this.suspendedCellMutationPotentialNewCells +=
|
|
2344
|
-
options.potentialNewCells ?? refs.reduce((count, ref) => (ref?.mutation.kind === 'clearCell' ? count : count + 1), 0);
|
|
2923
|
+
this.suspendedCellMutationPotentialNewCells += options.potentialNewCells ?? countPotentialNewTrackedCellMutations(refs);
|
|
2345
2924
|
return;
|
|
2346
2925
|
}
|
|
2347
2926
|
this.engine.applyCellMutationsAtWithOptions(refs, options);
|
|
2927
|
+
if (!canSkipDimensionUpdateAfterLiteralMutation(refs, options.potentialNewCells)) {
|
|
2928
|
+
this.updateSheetDimensionsAfterCellMutationRefs(refs);
|
|
2929
|
+
}
|
|
2348
2930
|
}
|
|
2349
2931
|
flushSuspendedCellMutations() {
|
|
2350
2932
|
if (this.suspendedCellMutationRefs.length === 0) {
|
|
@@ -2356,42 +2938,53 @@ export class WorkPaper {
|
|
|
2356
2938
|
this.suspendedCellMutationPotentialNewCells = 0;
|
|
2357
2939
|
this.engine.applyCellMutationsAtWithOptions(refs, {
|
|
2358
2940
|
captureUndo: true,
|
|
2359
|
-
potentialNewCells
|
|
2941
|
+
potentialNewCells,
|
|
2360
2942
|
source: 'local',
|
|
2361
2943
|
returnUndoOps: false,
|
|
2362
2944
|
reuseRefs: true,
|
|
2363
2945
|
});
|
|
2946
|
+
this.updateSheetDimensionsAfterCellMutationRefs(refs);
|
|
2364
2947
|
}
|
|
2365
|
-
enqueueSuspendedLiteralMutation(sheetId, row, col, content,
|
|
2948
|
+
enqueueSuspendedLiteralMutation(sheetId, row, col, content, cellIndex) {
|
|
2366
2949
|
if (!this.evaluationSuspended || !isDeferredBatchLiteralContent(content) || isFormulaContent(content)) {
|
|
2367
2950
|
return false;
|
|
2368
2951
|
}
|
|
2369
2952
|
if (content === null) {
|
|
2370
|
-
this.suspendedCellMutationRefs.push({
|
|
2953
|
+
this.suspendedCellMutationRefs.push({
|
|
2954
|
+
sheetId,
|
|
2955
|
+
mutation: { kind: 'clearCell', row, col },
|
|
2956
|
+
...(cellIndex !== undefined ? { cellIndex } : {}),
|
|
2957
|
+
});
|
|
2371
2958
|
return true;
|
|
2372
2959
|
}
|
|
2373
2960
|
this.suspendedCellMutationRefs.push({
|
|
2374
2961
|
sheetId,
|
|
2375
2962
|
mutation: { kind: 'setCellValue', row, col, value: content },
|
|
2963
|
+
...(cellIndex !== undefined ? { cellIndex } : {}),
|
|
2376
2964
|
});
|
|
2377
|
-
if (
|
|
2965
|
+
if (cellIndex === undefined) {
|
|
2378
2966
|
this.suspendedCellMutationPotentialNewCells += 1;
|
|
2379
2967
|
}
|
|
2380
2968
|
return true;
|
|
2381
2969
|
}
|
|
2382
|
-
enqueueDeferredBatchLiteral(sheetId, row, col, content,
|
|
2970
|
+
enqueueDeferredBatchLiteral(sheetId, row, col, content, cellIndex) {
|
|
2383
2971
|
if (this.batchDepth === 0 || this.evaluationSuspended || !isDeferredBatchLiteralContent(content) || isFormulaContent(content)) {
|
|
2384
2972
|
return false;
|
|
2385
2973
|
}
|
|
2386
2974
|
if (content === null) {
|
|
2387
|
-
this.pendingBatchOps.push({
|
|
2975
|
+
this.pendingBatchOps.push({
|
|
2976
|
+
sheetId,
|
|
2977
|
+
mutation: { kind: 'clearCell', row, col },
|
|
2978
|
+
...(cellIndex !== undefined ? { cellIndex } : {}),
|
|
2979
|
+
});
|
|
2388
2980
|
return true;
|
|
2389
2981
|
}
|
|
2390
2982
|
this.pendingBatchOps.push({
|
|
2391
2983
|
sheetId,
|
|
2392
2984
|
mutation: { kind: 'setCellValue', row, col, value: content },
|
|
2985
|
+
...(cellIndex !== undefined ? { cellIndex } : {}),
|
|
2393
2986
|
});
|
|
2394
|
-
if (
|
|
2987
|
+
if (cellIndex === undefined) {
|
|
2395
2988
|
this.pendingBatchPotentialNewCells += 1;
|
|
2396
2989
|
}
|
|
2397
2990
|
return true;
|
|
@@ -2431,6 +3024,28 @@ export class WorkPaper {
|
|
|
2431
3024
|
a1(address) {
|
|
2432
3025
|
return formatAddress(address.row, address.col);
|
|
2433
3026
|
}
|
|
3027
|
+
trackedA1(row, col) {
|
|
3028
|
+
if (row >= 0 && row < RUNTIME_A1_CACHE_ROW_LIMIT && col >= 0 && col < RUNTIME_A1_CACHE_COLUMN_LIMIT) {
|
|
3029
|
+
const cacheKey = row * RUNTIME_A1_CACHE_COLUMN_LIMIT + col;
|
|
3030
|
+
let cached = RUNTIME_A1_CACHE[cacheKey];
|
|
3031
|
+
if (cached === undefined) {
|
|
3032
|
+
let column = RUNTIME_COLUMN_LABEL_CACHE[col];
|
|
3033
|
+
if (column === undefined) {
|
|
3034
|
+
column = indexToColumn(col);
|
|
3035
|
+
RUNTIME_COLUMN_LABEL_CACHE[col] = column;
|
|
3036
|
+
}
|
|
3037
|
+
cached = `${column}${row + 1}`;
|
|
3038
|
+
RUNTIME_A1_CACHE[cacheKey] = cached;
|
|
3039
|
+
}
|
|
3040
|
+
return cached;
|
|
3041
|
+
}
|
|
3042
|
+
let column = RUNTIME_COLUMN_LABEL_CACHE[col];
|
|
3043
|
+
if (column === undefined) {
|
|
3044
|
+
column = indexToColumn(col);
|
|
3045
|
+
RUNTIME_COLUMN_LABEL_CACHE[col] = column;
|
|
3046
|
+
}
|
|
3047
|
+
return `${column}${row + 1}`;
|
|
3048
|
+
}
|
|
2434
3049
|
rangeRef(range) {
|
|
2435
3050
|
assertRange(range);
|
|
2436
3051
|
return sourceRangeRef(this.sheetName(range.start.sheet), range);
|
|
@@ -2525,14 +3140,22 @@ export class WorkPaper {
|
|
|
2525
3140
|
});
|
|
2526
3141
|
return orderWorkPaperCellChanges(cellChanges, this.listSheetRecords());
|
|
2527
3142
|
}
|
|
2528
|
-
materializeTrackedEventChanges(event) {
|
|
3143
|
+
materializeTrackedEventChanges(event, lazy = false) {
|
|
2529
3144
|
if (event.patches && event.patches.length > 0) {
|
|
2530
3145
|
const cellPatches = event.patches.filter((patch) => patch.kind === 'cell');
|
|
2531
3146
|
return { changes: cellPatches, canReusePublicChanges: false, ordered: false };
|
|
2532
3147
|
}
|
|
3148
|
+
const trustedPhysicalMetadata = lazy && event.changedCellIndices instanceof Uint32Array
|
|
3149
|
+
? readTrustedPhysicalTrackedChangeMetadata(event.changedCellIndices)
|
|
3150
|
+
: undefined;
|
|
2533
3151
|
const materialized = materializeTrackedIndexChangesWithMetadata(this.engine, event.changedCellIndices, {
|
|
2534
3152
|
explicitChangedCount: event.explicitChangedCount,
|
|
3153
|
+
lazy,
|
|
3154
|
+
...trustedPhysicalMetadata,
|
|
2535
3155
|
});
|
|
3156
|
+
if (lazy) {
|
|
3157
|
+
this.trackLazyTrackedChanges(materialized.changes);
|
|
3158
|
+
}
|
|
2536
3159
|
return {
|
|
2537
3160
|
changes: materialized.changes,
|
|
2538
3161
|
canReusePublicChanges: true,
|
|
@@ -2585,11 +3208,182 @@ export class WorkPaper {
|
|
|
2585
3208
|
kind: 'cell',
|
|
2586
3209
|
address: { sheet: sheetId, row, col },
|
|
2587
3210
|
sheetName,
|
|
2588
|
-
a1:
|
|
3211
|
+
a1: this.trackedA1(row, col),
|
|
2589
3212
|
newValue,
|
|
2590
3213
|
};
|
|
2591
3214
|
}
|
|
2592
|
-
|
|
3215
|
+
readTinySortedPhysicalTrackedEventChanges(event) {
|
|
3216
|
+
if (!event.changedCellIndicesSortedDisjoint) {
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
const cellStore = this.engine.workbook.cellStore;
|
|
3220
|
+
const firstCellIndex = event.changedCellIndices[0];
|
|
3221
|
+
if (firstCellIndex === undefined) {
|
|
3222
|
+
return [];
|
|
3223
|
+
}
|
|
3224
|
+
const sheetId = cellStore.sheetIds[firstCellIndex];
|
|
3225
|
+
if (sheetId === undefined) {
|
|
3226
|
+
return [];
|
|
3227
|
+
}
|
|
3228
|
+
const sheet = this.engine.workbook.getSheetById(sheetId);
|
|
3229
|
+
if (sheet && sheet.structureVersion !== 1) {
|
|
3230
|
+
return null;
|
|
3231
|
+
}
|
|
3232
|
+
const sheetName = sheet?.name ?? this.engine.workbook.getSheetNameById(sheetId);
|
|
3233
|
+
if (event.changedCellIndices.length === 1) {
|
|
3234
|
+
const row = cellStore.rows[firstCellIndex];
|
|
3235
|
+
const col = cellStore.cols[firstCellIndex];
|
|
3236
|
+
return [
|
|
3237
|
+
{
|
|
3238
|
+
kind: 'cell',
|
|
3239
|
+
address: { sheet: sheetId, row, col },
|
|
3240
|
+
sheetName,
|
|
3241
|
+
a1: this.trackedA1(row, col),
|
|
3242
|
+
newValue: readTrackedRuntimeCellValue(cellStore, firstCellIndex, this.engine.strings),
|
|
3243
|
+
},
|
|
3244
|
+
];
|
|
3245
|
+
}
|
|
3246
|
+
if (event.changedCellIndices.length === 2) {
|
|
3247
|
+
const secondCellIndex = event.changedCellIndices[1];
|
|
3248
|
+
if (cellStore.sheetIds[secondCellIndex] !== sheetId) {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
const firstRow = cellStore.rows[firstCellIndex];
|
|
3252
|
+
const firstCol = cellStore.cols[firstCellIndex];
|
|
3253
|
+
const secondRow = cellStore.rows[secondCellIndex];
|
|
3254
|
+
const secondCol = cellStore.cols[secondCellIndex];
|
|
3255
|
+
if (secondRow < firstRow || (secondRow === firstRow && secondCol < firstCol)) {
|
|
3256
|
+
return null;
|
|
3257
|
+
}
|
|
3258
|
+
return [
|
|
3259
|
+
{
|
|
3260
|
+
kind: 'cell',
|
|
3261
|
+
address: { sheet: sheetId, row: firstRow, col: firstCol },
|
|
3262
|
+
sheetName,
|
|
3263
|
+
a1: this.trackedA1(firstRow, firstCol),
|
|
3264
|
+
newValue: readTrackedRuntimeCellValue(cellStore, firstCellIndex, this.engine.strings),
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
kind: 'cell',
|
|
3268
|
+
address: { sheet: sheetId, row: secondRow, col: secondCol },
|
|
3269
|
+
sheetName,
|
|
3270
|
+
a1: this.trackedA1(secondRow, secondCol),
|
|
3271
|
+
newValue: readTrackedRuntimeCellValue(cellStore, secondCellIndex, this.engine.strings),
|
|
3272
|
+
},
|
|
3273
|
+
];
|
|
3274
|
+
}
|
|
3275
|
+
const changes = [];
|
|
3276
|
+
let previousRow = -1;
|
|
3277
|
+
let previousCol = -1;
|
|
3278
|
+
for (let index = 0; index < event.changedCellIndices.length; index += 1) {
|
|
3279
|
+
const cellIndex = event.changedCellIndices[index];
|
|
3280
|
+
if (cellStore.sheetIds[cellIndex] !== sheetId) {
|
|
3281
|
+
return null;
|
|
3282
|
+
}
|
|
3283
|
+
const row = cellStore.rows[cellIndex];
|
|
3284
|
+
const col = cellStore.cols[cellIndex];
|
|
3285
|
+
if (row < previousRow || (row === previousRow && col < previousCol)) {
|
|
3286
|
+
return null;
|
|
3287
|
+
}
|
|
3288
|
+
changes.push({
|
|
3289
|
+
kind: 'cell',
|
|
3290
|
+
address: { sheet: sheetId, row, col },
|
|
3291
|
+
sheetName,
|
|
3292
|
+
a1: this.trackedA1(row, col),
|
|
3293
|
+
newValue: readTrackedRuntimeCellValue(cellStore, cellIndex, this.engine.strings),
|
|
3294
|
+
});
|
|
3295
|
+
previousRow = row;
|
|
3296
|
+
previousCol = col;
|
|
3297
|
+
}
|
|
3298
|
+
return changes;
|
|
3299
|
+
}
|
|
3300
|
+
tryReadTinyTrackedEventChangesWithoutVisibility(event) {
|
|
3301
|
+
if (event.patches !== undefined &&
|
|
3302
|
+
event.invalidation !== 'full' &&
|
|
3303
|
+
event.patches.length <= TINY_TRACKED_CHANGE_LIMIT &&
|
|
3304
|
+
!event.hasInvalidatedRanges &&
|
|
3305
|
+
!event.hasInvalidatedRows &&
|
|
3306
|
+
!event.hasInvalidatedColumns) {
|
|
3307
|
+
const changes = [];
|
|
3308
|
+
let alreadySorted = true;
|
|
3309
|
+
let previousSheetId = -1;
|
|
3310
|
+
let previousSheetOrder = -1;
|
|
3311
|
+
let previousRow = -1;
|
|
3312
|
+
let previousCol = -1;
|
|
3313
|
+
for (let index = 0; index < event.patches.length; index += 1) {
|
|
3314
|
+
const patch = event.patches[index];
|
|
3315
|
+
if (!patch || patch.kind !== 'cell') {
|
|
3316
|
+
return null;
|
|
3317
|
+
}
|
|
3318
|
+
const sheetOrder = patch.address.sheet === previousSheetId ? previousSheetOrder : this.sheetRecord(patch.address.sheet).order;
|
|
3319
|
+
if (sheetOrder < previousSheetOrder ||
|
|
3320
|
+
(sheetOrder === previousSheetOrder &&
|
|
3321
|
+
(patch.address.row < previousRow || (patch.address.row === previousRow && patch.address.col < previousCol)))) {
|
|
3322
|
+
alreadySorted = false;
|
|
3323
|
+
}
|
|
3324
|
+
changes.push({
|
|
3325
|
+
kind: 'cell',
|
|
3326
|
+
address: patch.address,
|
|
3327
|
+
sheetName: patch.sheetName,
|
|
3328
|
+
a1: patch.a1,
|
|
3329
|
+
newValue: patch.newValue,
|
|
3330
|
+
});
|
|
3331
|
+
previousSheetId = patch.address.sheet;
|
|
3332
|
+
previousSheetOrder = sheetOrder;
|
|
3333
|
+
previousRow = patch.address.row;
|
|
3334
|
+
previousCol = patch.address.col;
|
|
3335
|
+
}
|
|
3336
|
+
return alreadySorted ? changes : orderWorkPaperCellChanges(changes, this.listSheetRecords(), event.explicitChangedCount);
|
|
3337
|
+
}
|
|
3338
|
+
if (event.invalidation === 'full' ||
|
|
3339
|
+
event.patches !== undefined ||
|
|
3340
|
+
event.changedCellIndices.length > TINY_TRACKED_CHANGE_LIMIT ||
|
|
3341
|
+
event.hasInvalidatedRanges ||
|
|
3342
|
+
event.hasInvalidatedRows ||
|
|
3343
|
+
event.hasInvalidatedColumns) {
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
if (event.changedCellIndices.length === 0) {
|
|
3347
|
+
return [];
|
|
3348
|
+
}
|
|
3349
|
+
const sortedPhysicalChanges = this.readTinySortedPhysicalTrackedEventChanges(event);
|
|
3350
|
+
if (sortedPhysicalChanges) {
|
|
3351
|
+
return sortedPhysicalChanges;
|
|
3352
|
+
}
|
|
3353
|
+
const changes = [];
|
|
3354
|
+
const cellKeys = [];
|
|
3355
|
+
let alreadySorted = true;
|
|
3356
|
+
let previousSheetId = -1;
|
|
3357
|
+
let previousSheetOrder = -1;
|
|
3358
|
+
let previousRow = -1;
|
|
3359
|
+
let previousCol = -1;
|
|
3360
|
+
for (let index = 0; index < event.changedCellIndices.length; index += 1) {
|
|
3361
|
+
const change = this.readSingleTrackedCellChange(event.changedCellIndices[index]);
|
|
3362
|
+
if (!change) {
|
|
3363
|
+
continue;
|
|
3364
|
+
}
|
|
3365
|
+
const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
|
|
3366
|
+
for (let priorIndex = 0; priorIndex < cellKeys.length; priorIndex += 1) {
|
|
3367
|
+
if (cellKeys[priorIndex] === cellKey) {
|
|
3368
|
+
return null;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
cellKeys.push(cellKey);
|
|
3372
|
+
const sheetOrder = change.address.sheet === previousSheetId ? previousSheetOrder : this.sheetRecord(change.address.sheet).order;
|
|
3373
|
+
if (sheetOrder < previousSheetOrder ||
|
|
3374
|
+
(sheetOrder === previousSheetOrder &&
|
|
3375
|
+
(change.address.row < previousRow || (change.address.row === previousRow && change.address.col < previousCol)))) {
|
|
3376
|
+
alreadySorted = false;
|
|
3377
|
+
}
|
|
3378
|
+
changes.push(change);
|
|
3379
|
+
previousSheetId = change.address.sheet;
|
|
3380
|
+
previousSheetOrder = sheetOrder;
|
|
3381
|
+
previousRow = change.address.row;
|
|
3382
|
+
previousCol = change.address.col;
|
|
3383
|
+
}
|
|
3384
|
+
return alreadySorted ? changes : orderWorkPaperCellChanges(changes, this.listSheetRecords(), event.explicitChangedCount);
|
|
3385
|
+
}
|
|
3386
|
+
computeCellChangesFromTrackedEvents(beforeVisibility, events, updateVisibility = true, options = {}) {
|
|
2593
3387
|
if (events.some((event) => event.invalidation === 'full')) {
|
|
2594
3388
|
return null;
|
|
2595
3389
|
}
|
|
@@ -2618,36 +3412,71 @@ export class WorkPaper {
|
|
|
2618
3412
|
nextVisibility.set(sheetId, created);
|
|
2619
3413
|
return created;
|
|
2620
3414
|
};
|
|
3415
|
+
const tryReadSmallTrackedEventChanges = (event) => {
|
|
3416
|
+
if (event.invalidation === 'full' ||
|
|
3417
|
+
event.patches !== undefined ||
|
|
3418
|
+
event.changedCellIndices.length > 4 ||
|
|
3419
|
+
event.hasInvalidatedRanges ||
|
|
3420
|
+
event.hasInvalidatedRows ||
|
|
3421
|
+
event.hasInvalidatedColumns) {
|
|
3422
|
+
return null;
|
|
3423
|
+
}
|
|
3424
|
+
if (event.changedCellIndices.length === 0) {
|
|
3425
|
+
return [];
|
|
3426
|
+
}
|
|
3427
|
+
const changes = [];
|
|
3428
|
+
const cellKeys = [];
|
|
3429
|
+
let alreadySorted = true;
|
|
3430
|
+
let previousSheetOrder = -1;
|
|
3431
|
+
let previousRow = -1;
|
|
3432
|
+
let previousCol = -1;
|
|
3433
|
+
for (let index = 0; index < event.changedCellIndices.length; index += 1) {
|
|
3434
|
+
const change = this.readSingleTrackedCellChange(event.changedCellIndices[index]);
|
|
3435
|
+
if (!change) {
|
|
3436
|
+
continue;
|
|
3437
|
+
}
|
|
3438
|
+
const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
|
|
3439
|
+
for (let priorIndex = 0; priorIndex < cellKeys.length; priorIndex += 1) {
|
|
3440
|
+
if (cellKeys[priorIndex] === cellKey) {
|
|
3441
|
+
return null;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
cellKeys.push(cellKey);
|
|
3445
|
+
const sheet = updateVisibility ? ensureMutableSheet(change.address.sheet, change.sheetName) : undefined;
|
|
3446
|
+
const sheetOrder = sheet?.order ?? sheetOrderFor(change.address.sheet);
|
|
3447
|
+
if (sheetOrder < previousSheetOrder ||
|
|
3448
|
+
(sheetOrder === previousSheetOrder &&
|
|
3449
|
+
(change.address.row < previousRow || (change.address.row === previousRow && change.address.col < previousCol)))) {
|
|
3450
|
+
alreadySorted = false;
|
|
3451
|
+
}
|
|
3452
|
+
if (sheet) {
|
|
3453
|
+
if (change.newValue.tag === ValueTag.Empty) {
|
|
3454
|
+
sheet.cells.delete(cellKey);
|
|
3455
|
+
}
|
|
3456
|
+
else {
|
|
3457
|
+
sheet.cells.set(cellKey, change.newValue);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
changes.push(change);
|
|
3461
|
+
previousSheetOrder = sheetOrder;
|
|
3462
|
+
previousRow = change.address.row;
|
|
3463
|
+
previousCol = change.address.col;
|
|
3464
|
+
}
|
|
3465
|
+
return alreadySorted ? changes : orderWorkPaperCellChanges(changes, this.listSheetRecords(), event.explicitChangedCount);
|
|
3466
|
+
};
|
|
2621
3467
|
if (events.length === 1) {
|
|
2622
3468
|
const event = events[0];
|
|
2623
|
-
if (
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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);
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
return { changes: [change], nextVisibility };
|
|
3469
|
+
if (!options.preferLazyPublicChanges) {
|
|
3470
|
+
const smallChanges = tryReadSmallTrackedEventChanges(event);
|
|
3471
|
+
if (smallChanges) {
|
|
3472
|
+
return { changes: smallChanges, nextVisibility };
|
|
2644
3473
|
}
|
|
2645
3474
|
}
|
|
2646
|
-
const materializedEventChanges = this.materializeTrackedEventChanges(event);
|
|
3475
|
+
const materializedEventChanges = this.materializeTrackedEventChanges(event, !updateVisibility);
|
|
2647
3476
|
const eventChanges = materializedEventChanges.changes;
|
|
2648
3477
|
if (!updateVisibility && materializedEventChanges.canReusePublicChanges && materializedEventChanges.ordered) {
|
|
2649
3478
|
return {
|
|
2650
|
-
changes:
|
|
3479
|
+
changes: eventChanges,
|
|
2651
3480
|
nextVisibility,
|
|
2652
3481
|
};
|
|
2653
3482
|
}
|
|
@@ -2718,6 +3547,33 @@ export class WorkPaper {
|
|
|
2718
3547
|
};
|
|
2719
3548
|
}
|
|
2720
3549
|
}
|
|
3550
|
+
const materializedSources = updateVisibility
|
|
3551
|
+
? null
|
|
3552
|
+
: materializeTrackedIndexChangeSourcesWithMetadata(this.engine, events, {
|
|
3553
|
+
deferLazyDetach: true,
|
|
3554
|
+
lazy: options.preferLazyPublicChanges,
|
|
3555
|
+
});
|
|
3556
|
+
if (materializedSources) {
|
|
3557
|
+
this.trackLazyTrackedChanges(materializedSources.changes);
|
|
3558
|
+
if (updateVisibility) {
|
|
3559
|
+
for (const change of materializedSources.changes) {
|
|
3560
|
+
const cellKey = makeCellKey(change.address.sheet, change.address.row, change.address.col);
|
|
3561
|
+
const sheet = ensureMutableSheet(change.address.sheet, change.sheetName);
|
|
3562
|
+
if (change.newValue.tag === ValueTag.Empty) {
|
|
3563
|
+
sheet.cells.delete(cellKey);
|
|
3564
|
+
}
|
|
3565
|
+
else {
|
|
3566
|
+
sheet.cells.set(cellKey, change.newValue);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return {
|
|
3571
|
+
changes: materializedSources.ordered
|
|
3572
|
+
? materializedSources.changes
|
|
3573
|
+
: orderWorkPaperCellChanges(materializedSources.changes, this.listSheetRecords()),
|
|
3574
|
+
nextVisibility,
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
2721
3577
|
const latestChangesByKey = new Map();
|
|
2722
3578
|
for (const event of events) {
|
|
2723
3579
|
const eventChanges = this.materializeTrackedEventChanges(event).changes;
|
|
@@ -2786,16 +3642,31 @@ export class WorkPaper {
|
|
|
2786
3642
|
this.batchStartNamedValues = this.namedExpressions.size > 0 ? this.ensureNamedExpressionValueCache() : EMPTY_NAMED_EXPRESSION_VALUES;
|
|
2787
3643
|
this.batchUsesTrackedFastPath = false;
|
|
2788
3644
|
}
|
|
2789
|
-
computeTrackedChangesWithoutVisibilityCache(events) {
|
|
2790
|
-
|
|
3645
|
+
computeTrackedChangesWithoutVisibilityCache(events, options = {}) {
|
|
3646
|
+
if (events.length === 1) {
|
|
3647
|
+
const event = events[0];
|
|
3648
|
+
if (!options.preferLazyPublicChanges || event.changedCellIndices.length <= TINY_TRACKED_CHANGE_LIMIT) {
|
|
3649
|
+
const tinyChanges = this.tryReadTinyTrackedEventChangesWithoutVisibility(event);
|
|
3650
|
+
if (tinyChanges) {
|
|
3651
|
+
return tinyChanges;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
const fastPath = this.computeCellChangesFromTrackedEvents(new Map(), events, false, options);
|
|
2791
3656
|
if (!fastPath) {
|
|
2792
3657
|
throw new WorkPaperOperationError('Mutation emitted an unsupported invalidation pattern for tracked changes');
|
|
2793
3658
|
}
|
|
2794
3659
|
return fastPath.changes;
|
|
2795
3660
|
}
|
|
2796
|
-
captureTrackedChangesWithoutVisibilityCache(mutate) {
|
|
3661
|
+
captureTrackedChangesWithoutVisibilityCache(mutate, options = {}) {
|
|
2797
3662
|
this.assertNotDisposed();
|
|
2798
|
-
this.
|
|
3663
|
+
if (this.pendingLazyTrackedChanges.length > 0) {
|
|
3664
|
+
this.materializePendingLazyTrackedChanges({ preservePositions: options.preservePendingTrackedPositions });
|
|
3665
|
+
}
|
|
3666
|
+
if (this.trackedEngineEvents.length > 0) {
|
|
3667
|
+
this.drainTrackedEngineEvents();
|
|
3668
|
+
}
|
|
3669
|
+
this.retainedTrackedEngineEventIndicesDepth += 1;
|
|
2799
3670
|
try {
|
|
2800
3671
|
mutate();
|
|
2801
3672
|
}
|
|
@@ -2805,13 +3676,159 @@ export class WorkPaper {
|
|
|
2805
3676
|
}
|
|
2806
3677
|
throw new WorkPaperOperationError(this.messageOf(error, 'Mutation failed'));
|
|
2807
3678
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
3679
|
+
finally {
|
|
3680
|
+
this.retainedTrackedEngineEventIndicesDepth -= 1;
|
|
3681
|
+
}
|
|
3682
|
+
const events = this.drainTrackedEngineEvents();
|
|
3683
|
+
const directSingleLiteralChanges = this.tryBuildDirectSingleLiteralTrackedChange(events, options.singleLiteralChange);
|
|
3684
|
+
if (directSingleLiteralChanges) {
|
|
3685
|
+
if (directSingleLiteralChanges.length > 0 && this.emitter.hasListeners('valuesUpdated')) {
|
|
3686
|
+
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes: directSingleLiteralChanges } });
|
|
3687
|
+
}
|
|
3688
|
+
return directSingleLiteralChanges;
|
|
3689
|
+
}
|
|
3690
|
+
const shouldEmitValuesUpdated = this.emitter.hasListeners('valuesUpdated');
|
|
3691
|
+
const changes = this.computeTrackedChangesWithoutVisibilityCache(events, {
|
|
3692
|
+
preferLazyPublicChanges: !shouldEmitValuesUpdated,
|
|
3693
|
+
});
|
|
3694
|
+
if (changes.length > 0 && shouldEmitValuesUpdated) {
|
|
2810
3695
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2811
3696
|
}
|
|
2812
3697
|
return changes;
|
|
2813
3698
|
}
|
|
3699
|
+
tryBuildDirectSingleLiteralTrackedChange(events, expected) {
|
|
3700
|
+
if (expected === undefined || expected.cellIndex === undefined || events.length !== 1) {
|
|
3701
|
+
return null;
|
|
3702
|
+
}
|
|
3703
|
+
const event = events[0];
|
|
3704
|
+
if (event.invalidation === 'full' ||
|
|
3705
|
+
event.patches !== undefined ||
|
|
3706
|
+
event.changedCellIndices.length < 1 ||
|
|
3707
|
+
event.changedCellIndices.length > 2 ||
|
|
3708
|
+
event.changedCellIndices[0] !== expected.cellIndex ||
|
|
3709
|
+
event.hasInvalidatedRanges ||
|
|
3710
|
+
event.hasInvalidatedRows ||
|
|
3711
|
+
event.hasInvalidatedColumns) {
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3714
|
+
const literalChange = {
|
|
3715
|
+
kind: 'cell',
|
|
3716
|
+
address: { sheet: expected.address.sheet, row: expected.address.row, col: expected.address.col },
|
|
3717
|
+
sheetName: expected.sheetName,
|
|
3718
|
+
a1: this.trackedA1(expected.address.row, expected.address.col),
|
|
3719
|
+
newValue: scalarValueFromLiteral(expected.value),
|
|
3720
|
+
};
|
|
3721
|
+
if (event.changedCellIndices.length === 1) {
|
|
3722
|
+
return [literalChange];
|
|
3723
|
+
}
|
|
3724
|
+
const formulaCellIndex = event.changedCellIndices[1];
|
|
3725
|
+
const cellStore = this.engine.workbook.cellStore;
|
|
3726
|
+
if (cellStore.sheetIds[formulaCellIndex] !== expected.address.sheet) {
|
|
3727
|
+
return null;
|
|
3728
|
+
}
|
|
3729
|
+
if (!expected.isPhysicalSheet) {
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
const formulaRow = cellStore.rows[formulaCellIndex];
|
|
3733
|
+
const formulaCol = cellStore.cols[formulaCellIndex];
|
|
3734
|
+
if (formulaRow < expected.address.row || (formulaRow === expected.address.row && formulaCol < expected.address.col)) {
|
|
3735
|
+
return null;
|
|
3736
|
+
}
|
|
3737
|
+
return [
|
|
3738
|
+
literalChange,
|
|
3739
|
+
{
|
|
3740
|
+
kind: 'cell',
|
|
3741
|
+
address: { sheet: expected.address.sheet, row: formulaRow, col: formulaCol },
|
|
3742
|
+
sheetName: expected.sheetName,
|
|
3743
|
+
a1: this.trackedA1(formulaRow, formulaCol),
|
|
3744
|
+
newValue: readTrackedRuntimeCellValue(cellStore, formulaCellIndex, this.engine.strings),
|
|
3745
|
+
},
|
|
3746
|
+
];
|
|
3747
|
+
}
|
|
3748
|
+
tryBuildDirectExistingNumericTrackedChanges(result, address, cellIndex, isPhysicalSheet, sheetName, value) {
|
|
3749
|
+
const changedCellCount = result.changedCellIndices?.length ?? result.changedCellCount ?? 0;
|
|
3750
|
+
const firstChangedCellIndex = result.changedCellIndices?.[0] ?? result.firstChangedCellIndex;
|
|
3751
|
+
if (changedCellCount < 1 || firstChangedCellIndex !== cellIndex) {
|
|
3752
|
+
return null;
|
|
3753
|
+
}
|
|
3754
|
+
const literalChange = {
|
|
3755
|
+
kind: 'cell',
|
|
3756
|
+
address: { sheet: address.sheet, row: address.row, col: address.col },
|
|
3757
|
+
sheetName,
|
|
3758
|
+
a1: this.trackedA1(address.row, address.col),
|
|
3759
|
+
newValue: { tag: ValueTag.Number, value },
|
|
3760
|
+
};
|
|
3761
|
+
if (changedCellCount === 1) {
|
|
3762
|
+
return [literalChange];
|
|
3763
|
+
}
|
|
3764
|
+
if (!isPhysicalSheet) {
|
|
3765
|
+
return null;
|
|
3766
|
+
}
|
|
3767
|
+
if (changedCellCount > 2) {
|
|
3768
|
+
const changedCellIndices = result.changedCellIndices;
|
|
3769
|
+
if (changedCellIndices === undefined) {
|
|
3770
|
+
return null;
|
|
3771
|
+
}
|
|
3772
|
+
const cellStore = this.engine.workbook.cellStore;
|
|
3773
|
+
const changes = [];
|
|
3774
|
+
changes.length = changedCellCount;
|
|
3775
|
+
changes[0] = literalChange;
|
|
3776
|
+
let alreadySorted = true;
|
|
3777
|
+
let previousRow = address.row;
|
|
3778
|
+
let previousCol = address.col;
|
|
3779
|
+
for (let index = 1; index < changedCellCount; index += 1) {
|
|
3780
|
+
const changedCellIndex = changedCellIndices[index];
|
|
3781
|
+
if (cellStore.sheetIds[changedCellIndex] !== address.sheet) {
|
|
3782
|
+
return null;
|
|
3783
|
+
}
|
|
3784
|
+
const row = cellStore.rows[changedCellIndex];
|
|
3785
|
+
const col = cellStore.cols[changedCellIndex];
|
|
3786
|
+
if (row === undefined || col === undefined) {
|
|
3787
|
+
return null;
|
|
3788
|
+
}
|
|
3789
|
+
if (row < previousRow || (row === previousRow && col < previousCol)) {
|
|
3790
|
+
alreadySorted = false;
|
|
3791
|
+
}
|
|
3792
|
+
changes[index] = {
|
|
3793
|
+
kind: 'cell',
|
|
3794
|
+
address: { sheet: address.sheet, row, col },
|
|
3795
|
+
sheetName,
|
|
3796
|
+
a1: this.trackedA1(row, col),
|
|
3797
|
+
newValue: readTrackedRuntimeCellValue(cellStore, changedCellIndex, this.engine.strings),
|
|
3798
|
+
};
|
|
3799
|
+
previousRow = row;
|
|
3800
|
+
previousCol = col;
|
|
3801
|
+
}
|
|
3802
|
+
return alreadySorted ? changes : orderWorkPaperCellChanges(changes, this.listSheetRecords(), result.explicitChangedCount);
|
|
3803
|
+
}
|
|
3804
|
+
const formulaCellIndex = result.changedCellIndices?.[1] ?? result.secondChangedCellIndex;
|
|
3805
|
+
if (formulaCellIndex === undefined) {
|
|
3806
|
+
return null;
|
|
3807
|
+
}
|
|
3808
|
+
const cellStore = this.engine.workbook.cellStore;
|
|
3809
|
+
if (cellStore.sheetIds[formulaCellIndex] !== address.sheet) {
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
const formulaRow = result.secondChangedRow ?? cellStore.rows[formulaCellIndex];
|
|
3813
|
+
const formulaCol = result.secondChangedCol ?? cellStore.cols[formulaCellIndex];
|
|
3814
|
+
if (formulaRow < address.row || (formulaRow === address.row && formulaCol < address.col)) {
|
|
3815
|
+
return null;
|
|
3816
|
+
}
|
|
3817
|
+
return [
|
|
3818
|
+
literalChange,
|
|
3819
|
+
{
|
|
3820
|
+
kind: 'cell',
|
|
3821
|
+
address: { sheet: address.sheet, row: formulaRow, col: formulaCol },
|
|
3822
|
+
sheetName,
|
|
3823
|
+
a1: this.trackedA1(formulaRow, formulaCol),
|
|
3824
|
+
newValue: result.secondChangedNumericValue === undefined
|
|
3825
|
+
? readTrackedRuntimeCellValue(cellStore, formulaCellIndex, this.engine.strings)
|
|
3826
|
+
: { tag: ValueTag.Number, value: result.secondChangedNumericValue },
|
|
3827
|
+
},
|
|
3828
|
+
];
|
|
3829
|
+
}
|
|
2814
3830
|
batchStructuralChanges(batchOperations) {
|
|
3831
|
+
this.materializePendingLazyTrackedChanges();
|
|
2815
3832
|
if (!this.canUseTrackedStructuralFastPath()) {
|
|
2816
3833
|
this.downgradeTrackedBatchFastPath();
|
|
2817
3834
|
return this.batch(batchOperations);
|
|
@@ -2821,16 +3838,19 @@ export class WorkPaper {
|
|
|
2821
3838
|
this.drainTrackedEngineEvents();
|
|
2822
3839
|
this.batchDepth += 1;
|
|
2823
3840
|
try {
|
|
2824
|
-
batchOperations
|
|
3841
|
+
this.withRetainedTrackedEngineEventIndices(batchOperations);
|
|
2825
3842
|
}
|
|
2826
3843
|
finally {
|
|
2827
3844
|
this.batchDepth -= 1;
|
|
2828
3845
|
this.flushPendingBatchOps();
|
|
2829
3846
|
this.mergeUndoHistory(undoStackStart);
|
|
2830
3847
|
}
|
|
2831
|
-
const
|
|
3848
|
+
const shouldEmitValuesUpdated = this.emitter.hasListeners('valuesUpdated');
|
|
3849
|
+
const changes = this.computeTrackedChangesWithoutVisibilityCache(this.drainTrackedEngineEvents(), {
|
|
3850
|
+
preferLazyPublicChanges: !shouldEmitValuesUpdated,
|
|
3851
|
+
});
|
|
2832
3852
|
this.flushQueuedEvents();
|
|
2833
|
-
if (changes.length > 0) {
|
|
3853
|
+
if (changes.length > 0 && shouldEmitValuesUpdated) {
|
|
2834
3854
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2835
3855
|
}
|
|
2836
3856
|
return changes;
|
|
@@ -2852,8 +3872,9 @@ export class WorkPaper {
|
|
|
2852
3872
|
this.namedExpressionValueCache = afterNames;
|
|
2853
3873
|
return hasNamedExpressions ? [...cellChanges, ...this.computeNamedExpressionChanges(beforeNames, afterNames)] : cellChanges;
|
|
2854
3874
|
}
|
|
2855
|
-
captureChanges(semanticEvent, mutate) {
|
|
3875
|
+
captureChanges(semanticEvent, mutate, options = {}) {
|
|
2856
3876
|
this.assertNotDisposed();
|
|
3877
|
+
this.materializePendingLazyTrackedChanges({ preservePositions: options.preservePendingTrackedPositions });
|
|
2857
3878
|
this.downgradeTrackedBatchFastPath();
|
|
2858
3879
|
if (semanticEvent !== undefined) {
|
|
2859
3880
|
this.flushPendingBatchOps();
|
|
@@ -2906,7 +3927,7 @@ export class WorkPaper {
|
|
|
2906
3927
|
this.emitter.emitDetailed(event);
|
|
2907
3928
|
}
|
|
2908
3929
|
}
|
|
2909
|
-
if (!this.shouldSuppressEvents() && changes.length > 0) {
|
|
3930
|
+
if (!this.shouldSuppressEvents() && changes.length > 0 && this.emitter.hasListeners('valuesUpdated')) {
|
|
2910
3931
|
this.emitter.emitDetailed({ eventName: 'valuesUpdated', payload: { changes } });
|
|
2911
3932
|
}
|
|
2912
3933
|
return changes;
|
|
@@ -2935,6 +3956,10 @@ export class WorkPaper {
|
|
|
2935
3956
|
}
|
|
2936
3957
|
return stack;
|
|
2937
3958
|
}
|
|
3959
|
+
historyTopIsCellMutations(stack) {
|
|
3960
|
+
const kind = stack.at(-1)?.forward.kind;
|
|
3961
|
+
return kind === 'cell-mutations' || kind === 'single-existing-numeric-cell-mutation';
|
|
3962
|
+
}
|
|
2938
3963
|
clearHistoryStacks() {
|
|
2939
3964
|
this.getUndoStack().length = 0;
|
|
2940
3965
|
this.getRedoStack().length = 0;
|
|
@@ -2945,6 +3970,19 @@ export class WorkPaper {
|
|
|
2945
3970
|
return record.ops;
|
|
2946
3971
|
case 'single-op':
|
|
2947
3972
|
return [record.op];
|
|
3973
|
+
case 'single-existing-numeric-cell-mutation': {
|
|
3974
|
+
const sheetName = this.getSheetName(record.sheetId);
|
|
3975
|
+
return sheetName
|
|
3976
|
+
? [
|
|
3977
|
+
{
|
|
3978
|
+
kind: 'setCellValue',
|
|
3979
|
+
sheetName,
|
|
3980
|
+
address: formatAddress(record.row, record.col),
|
|
3981
|
+
value: record.value,
|
|
3982
|
+
},
|
|
3983
|
+
]
|
|
3984
|
+
: [];
|
|
3985
|
+
}
|
|
2948
3986
|
case 'cell-mutations':
|
|
2949
3987
|
return record.refs.flatMap((ref) => {
|
|
2950
3988
|
const sheetName = this.getSheetName(ref.sheetId);
|
|
@@ -3120,6 +4158,7 @@ export class WorkPaper {
|
|
|
3120
4158
|
});
|
|
3121
4159
|
}
|
|
3122
4160
|
replaceSheetContentInternal(sheetId, content, options) {
|
|
4161
|
+
const dimensions = inspectSheetDimensionsWithinLimits(this.sheetName(sheetId), content, this.config);
|
|
3123
4162
|
replaceWorkPaperSheetContent({
|
|
3124
4163
|
sheetId,
|
|
3125
4164
|
sheetName: this.sheetName(sheetId),
|
|
@@ -3133,9 +4172,10 @@ export class WorkPaper {
|
|
|
3133
4172
|
getUndoStackLength: () => this.getUndoStack().length,
|
|
3134
4173
|
mergeUndoHistory: (undoStackStart) => this.mergeUndoHistory(undoStackStart),
|
|
3135
4174
|
});
|
|
4175
|
+
this.cacheInitializedSheetDimensions(sheetId, dimensions);
|
|
3136
4176
|
}
|
|
3137
4177
|
applyRawContent(address, content) {
|
|
3138
|
-
const
|
|
4178
|
+
const cellIndex = this.getVisibleCellIndex(address.sheet, address.row, address.col);
|
|
3139
4179
|
const mutation = content === null
|
|
3140
4180
|
? { kind: 'clearCell', row: address.row, col: address.col }
|
|
3141
4181
|
: typeof content === 'string' && content.trim().startsWith('=')
|
|
@@ -3151,9 +4191,9 @@ export class WorkPaper {
|
|
|
3151
4191
|
col: address.col,
|
|
3152
4192
|
value: content,
|
|
3153
4193
|
};
|
|
3154
|
-
this.applyCellMutationRefs([{ sheetId: address.sheet, mutation }], {
|
|
4194
|
+
this.applyCellMutationRefs([{ sheetId: address.sheet, mutation, ...(cellIndex !== undefined ? { cellIndex } : {}) }], {
|
|
3155
4195
|
captureUndo: true,
|
|
3156
|
-
potentialNewCells: content === null ||
|
|
4196
|
+
potentialNewCells: content === null || cellIndex !== undefined ? 0 : 1,
|
|
3157
4197
|
source: 'local',
|
|
3158
4198
|
returnUndoOps: false,
|
|
3159
4199
|
reuseRefs: true,
|