@ai-matrx/office 0.8.20 → 0.8.21
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/CHANGELOG.md +20 -2
- package/dist/collab-sheets-transform.cjs +354 -16
- package/dist/collab-sheets-transform.cjs.map +1 -1
- package/dist/collab-sheets-transform.d.cts +32 -4
- package/dist/collab-sheets-transform.d.ts +32 -4
- package/dist/collab-sheets-transform.js +354 -16
- package/dist/collab-sheets-transform.js.map +1 -1
- package/dist/univer-collab.cjs +414 -27
- package/dist/univer-collab.cjs.map +1 -1
- package/dist/univer-collab.d.cts +6 -1
- package/dist/univer-collab.d.ts +6 -1
- package/dist/univer-collab.js +414 -27
- package/dist/univer-collab.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.21
|
|
4
|
+
### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
|
|
5
|
+
|
|
6
|
+
- **Merge divergence across chained row/column deletes, fixed at its cause.** Univer 1.0.4's structural commands only fix the merges that intersect the COMMAND's range on the other axis (any `-by-range` call with a partial range, an API call) and only while its ref-range registration is current (never refreshed after a content load or a remove-merge). A cut merge it skipped stayed put on the sender while every transform moved it, so seats ended a column apart (the session fuzz's merge at column -1 vs 0). Capture now completes the command (`mergeFixupsOwed`): the cut merges still on the sheet are removed right before the structural mutation and re-added at their model position right after it, in the traveling group and on the sender alike. Multi-seat session fuzz: 0 hash mismatches (was 2–18 per run).
|
|
7
|
+
- **Number formats travel** (`sheet.mutation.set.numfmt` / `remove.numfmt`, family `numfmt`): typing a date or a currency no longer rolls back. Ranges follow row/column inserts, removes and moves cell by cell; against another write of the style key `n` (a numfmt, a style with `n`, a style clear, a cell delete) the later-committed op wins.
|
|
8
|
+
- **Row/column counts travel** (`set-worksheet-row-count` / `-column-count`, family `count`): a concurrent insert/remove on that axis shifts the count; two counts at once, the larger wins. "Refresh from table" growing a sheet is no longer rolled back.
|
|
9
|
+
- **Sheets: add, remove, order travel** (family `sheet`): the sheet order is transformed as one more axis. Capture records a removed sheet's index and how many sheets are left (`annotateSheetOps`, side-car `sheetIndex`, `sheetsLeft`); an insert-sheet's styles are inlined (`inlineSheetStyles`). An edit to a sheet removed at the same time is dropped and its author told; a formula naming a removed sheet, and two removes that would leave no sheet, are barriers.
|
|
10
|
+
- **Selections: a seat's own row/column/count/sheet change keeps its selection inside the sheet** (API calls set no selection; the editor crashed with "Range is out of bounds"); a remote count/sheet change clamps it too.
|
|
11
|
+
- **Transform speed:** the group transform walks the OT grid iteratively; untouched cell ops pass through by identity (no overlap, no formula, change elsewhere); barrier checks are cached per op; undo-stack rebase reuses each list's traveling shape and compares by identity.
|
|
12
|
+
- **Tests:** new chained TP1 fuzz (`sheet-transform-chain-fuzz.test.ts`, 3–5 real commands per side, plus capture fidelity); the multi-seat session fuzz is seed-repeatable (`createSheetsOtAdapter` hook `stateHash`, a synchronous hash in tests) and asserts zero incidents. `SHEET_TRANSFORMER_VERSION` = `c5c-1`.
|
|
13
|
+
- `SHEETS_COLLAB_PROTO` = 2: a room opened by this build is raised to proto 2 (when its checkpoint is at head) and older tabs open it read-only, so seats with and without merge completion never share a room.
|
|
14
|
+
- Consumer action: none.
|
|
15
|
+
|
|
3
16
|
## 0.8.20
|
|
4
17
|
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
19
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
20
|
+
Diff: `git diff 046a506e40 150104f478 -- apps/shared/office/src`
|
|
21
|
+
|
|
22
|
+
Changed shipped source files (2):
|
|
23
|
+
- `apps/shared/office/src/sheets/workbook-ot-room.ts`
|
|
24
|
+
- `apps/shared/office/src/univer/collab/WorkbookCollabSession.ts`
|
|
7
25
|
|
|
8
26
|
## 0.8.19
|
|
9
27
|
### C7a: the browser editor's plugin resources
|
|
@@ -28,19 +28,24 @@ __export(transform_exports, {
|
|
|
28
28
|
SIDE_CAR_PARAMS: () => SIDE_CAR_PARAMS,
|
|
29
29
|
allowParams: () => allowParams,
|
|
30
30
|
annotateFormulas: () => annotateFormulas,
|
|
31
|
+
annotateSheetOps: () => annotateSheetOps,
|
|
31
32
|
cellRef: () => cellRef,
|
|
32
33
|
completeCellContent: () => completeCellContent,
|
|
33
34
|
exactMergeRemovals: () => exactMergeRemovals,
|
|
34
35
|
forApply: () => forApply,
|
|
35
36
|
formulaBarrier: () => formulaBarrier,
|
|
37
|
+
inlineSheetStyles: () => inlineSheetStyles,
|
|
36
38
|
isBarrier: () => isBarrier,
|
|
37
39
|
isFormulaText: () => isFormulaText,
|
|
40
|
+
isStructuralMutation: () => isStructuralMutation,
|
|
38
41
|
mapIndex: () => mapIndex,
|
|
42
|
+
mergeFixupsOwed: () => mergeFixupsOwed,
|
|
39
43
|
mergeThrough: () => mergeThrough,
|
|
40
44
|
refSheetsAfter: () => refSheetsAfter,
|
|
41
45
|
refSheetsFor: () => refSheetsFor,
|
|
42
46
|
rewriteFormula: () => rewriteFormula,
|
|
43
47
|
sheetTransformer: () => sheetTransformer,
|
|
48
|
+
spanThrough: () => spanThrough,
|
|
44
49
|
transformAxis: () => transformAxis,
|
|
45
50
|
transformGroup: () => transformGroup,
|
|
46
51
|
transformMutation: () => transformMutation
|
|
@@ -73,6 +78,14 @@ var SHEET_MUTATIONS = {
|
|
|
73
78
|
"sheet.mutation.set-worksheet-row-auto-height": covered("size", "rowsAutoHeightInfo"),
|
|
74
79
|
"sheet.mutation.set-worksheet-col-width": covered("size", "ranges", "colWidth"),
|
|
75
80
|
"sheet.mutation.set-worksheet-name": covered("name", "name"),
|
|
81
|
+
// ── covered (C5c) ────────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
"sheet.mutation.set.numfmt": covered("numfmt", "values", "refMap"),
|
|
83
|
+
"sheet.mutation.remove.numfmt": covered("numfmt", "ranges"),
|
|
84
|
+
"sheet.mutation.set-worksheet-row-count": covered("count", "rowCount"),
|
|
85
|
+
"sheet.mutation.set-worksheet-column-count": covered("count", "columnCount"),
|
|
86
|
+
"sheet.mutation.insert-sheet": covered("sheet", "index", "sheet"),
|
|
87
|
+
"sheet.mutation.remove-sheet": covered("sheet", "sheetIndex", "sheetsLeft"),
|
|
88
|
+
"sheet.mutation.set-worksheet-order": covered("sheet", "fromOrder", "toOrder"),
|
|
76
89
|
// ── never travels ────────────────────────────────────────────────────────────────────────────────
|
|
77
90
|
"formula.mutation.set-formula-calculation-start": never(FORMULA_RESULT),
|
|
78
91
|
"formula.mutation.set-trigger-formula-calculation-start": never(FORMULA_RESULT),
|
|
@@ -107,12 +120,7 @@ var SHEET_MUTATIONS = {
|
|
|
107
120
|
"thread-comment.mutation.delete-comment": never(UNIVER_COMMENTS),
|
|
108
121
|
// ── barriers: X (no transform yet, design §10) ───────────────────────────────────────────────────
|
|
109
122
|
"sheet.mutation.reorder-range": barrier("X", "sort: a permutation, not transformed yet"),
|
|
110
|
-
"sheet.mutation.set-worksheet-row-count": barrier("X", "absolute row count; needs delta semantics"),
|
|
111
|
-
"sheet.mutation.set-worksheet-column-count": barrier("X", "absolute column count; needs delta semantics"),
|
|
112
|
-
"sheet.mutation.set-worksheet-order": barrier("X", "absolute sheet order; needs move semantics"),
|
|
113
123
|
"sheet.mutation.move-range": barrier("X", "cut-paste carries the sender's cell matrices"),
|
|
114
|
-
"sheet.mutation.insert-sheet": barrier("X", "sheet add: not in C5a"),
|
|
115
|
-
"sheet.mutation.remove-sheet": barrier("X", "sheet remove: not in C5a"),
|
|
116
124
|
"sheet.mutation.set-worksheet-hidden": barrier("X", "sheet-level setting: not in C5a"),
|
|
117
125
|
"sheet.mutation.set-tab-color": barrier("X", "sheet-level setting: not in C5a"),
|
|
118
126
|
"sheet.mutation.set-workbook-name": barrier("X", "workbook-level setting: not in C5a"),
|
|
@@ -127,8 +135,6 @@ var SHEET_MUTATIONS = {
|
|
|
127
135
|
"sheet.mutation.set-row-visible": barrier("X", "row visibility: not in C5a"),
|
|
128
136
|
"sheet.mutation.set-col-hidden": barrier("X", "column visibility: not in C5a"),
|
|
129
137
|
"sheet.mutation.set-col-visible": barrier("X", "column visibility: not in C5a"),
|
|
130
|
-
"sheet.mutation.set.numfmt": barrier("X", "number formats by range: not in C5a"),
|
|
131
|
-
"sheet.mutation.remove.numfmt": barrier("X", "number formats by range: not in C5a"),
|
|
132
138
|
"sheet.mutation.add-range-protection": barrier("X", "permissions: not in C5a"),
|
|
133
139
|
"sheet.mutation.set-range-protection": barrier("X", "permissions: not in C5a"),
|
|
134
140
|
"sheet.mutation.delete-range-protection": barrier("X", "permissions: not in C5a"),
|
|
@@ -523,7 +529,7 @@ function refSheetsFor(texts, sheets) {
|
|
|
523
529
|
}
|
|
524
530
|
|
|
525
531
|
// src/collab/sheets/transform/transform.ts
|
|
526
|
-
var SHEET_TRANSFORMER_VERSION = "
|
|
532
|
+
var SHEET_TRANSFORMER_VERSION = "c5c-1";
|
|
527
533
|
var ID = {
|
|
528
534
|
values: "sheet.mutation.set-range-values",
|
|
529
535
|
insertRow: "sheet.mutation.insert-row",
|
|
@@ -540,7 +546,14 @@ var ID = {
|
|
|
540
546
|
colWidth: "sheet.mutation.set-worksheet-col-width",
|
|
541
547
|
name: "sheet.mutation.set-worksheet-name",
|
|
542
548
|
setName: "formula.mutation.set-defined-name",
|
|
543
|
-
removeName: "formula.mutation.remove-defined-name"
|
|
549
|
+
removeName: "formula.mutation.remove-defined-name",
|
|
550
|
+
setNumfmt: "sheet.mutation.set.numfmt",
|
|
551
|
+
removeNumfmt: "sheet.mutation.remove.numfmt",
|
|
552
|
+
rowCount: "sheet.mutation.set-worksheet-row-count",
|
|
553
|
+
colCount: "sheet.mutation.set-worksheet-column-count",
|
|
554
|
+
insertSheet: "sheet.mutation.insert-sheet",
|
|
555
|
+
removeSheet: "sheet.mutation.remove-sheet",
|
|
556
|
+
sheetOrder: "sheet.mutation.set-worksheet-order"
|
|
544
557
|
};
|
|
545
558
|
var PAIR_BARRIERS = {};
|
|
546
559
|
var pairKey = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
|
|
@@ -652,6 +665,20 @@ function mergeThrough(s, m) {
|
|
|
652
665
|
}
|
|
653
666
|
return { range: m, fixup: false };
|
|
654
667
|
}
|
|
668
|
+
function mergeFixupsOwed(m, mergesBefore) {
|
|
669
|
+
const st = structuralOf(m);
|
|
670
|
+
if (!st) return null;
|
|
671
|
+
const remove = [];
|
|
672
|
+
const add = [];
|
|
673
|
+
for (const q of mergesBefore) {
|
|
674
|
+
const t = mergeThrough(st, q);
|
|
675
|
+
if (!t.fixup) continue;
|
|
676
|
+
remove.push({ startRow: q.startRow, endRow: q.endRow, startColumn: q.startColumn, endColumn: q.endColumn, ...q.rangeType !== void 0 ? { rangeType: q.rangeType } : {} });
|
|
677
|
+
if (t.range) add.push({ ...t.range });
|
|
678
|
+
}
|
|
679
|
+
return remove.length ? { remove, add } : null;
|
|
680
|
+
}
|
|
681
|
+
var isStructuralMutation = (m) => structuralOf(m) !== null;
|
|
655
682
|
function exactMergeRemovals(group, merges) {
|
|
656
683
|
const sim = new Map(Object.entries(merges).map(([k, v]) => [k, v.map((r) => ({ ...r }))]));
|
|
657
684
|
const out = [];
|
|
@@ -698,8 +725,24 @@ function cellsMutation(template, cells) {
|
|
|
698
725
|
function putCell(cells, r, c, v) {
|
|
699
726
|
(cells[r] ??= {})[c] = v;
|
|
700
727
|
}
|
|
728
|
+
function cellsOverlap(a, b) {
|
|
729
|
+
for (const r of Object.keys(a)) {
|
|
730
|
+
const br = b[r];
|
|
731
|
+
if (!br) continue;
|
|
732
|
+
for (const c of Object.keys(a[r] ?? {})) if (c in br) return true;
|
|
733
|
+
}
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
701
736
|
var isEmptyMatrix = (cells) => Object.values(cells).every((row) => Object.keys(row).length === 0);
|
|
737
|
+
var cellBarrierCache = /* @__PURE__ */ new WeakMap();
|
|
702
738
|
function cellBarrier(m) {
|
|
739
|
+
const hit = cellBarrierCache.get(m);
|
|
740
|
+
if (hit !== void 0) return hit;
|
|
741
|
+
const why = cellBarrierOf(m);
|
|
742
|
+
cellBarrierCache.set(m, why);
|
|
743
|
+
return why;
|
|
744
|
+
}
|
|
745
|
+
function cellBarrierOf(m) {
|
|
703
746
|
if (m.params.isOverrideStyle) return "set-range-values with isOverrideStyle (whole-style replace) is not covered";
|
|
704
747
|
let why = null;
|
|
705
748
|
forEachCell(cellsOf(m), (_r, _c, v) => {
|
|
@@ -781,6 +824,7 @@ function transformCell(X, Y, side) {
|
|
|
781
824
|
function transformCellsOverCells(x, y, side) {
|
|
782
825
|
if (sheetOf(x) !== sheetOf(y)) return ok([x]);
|
|
783
826
|
const ycells = cellsOf(y);
|
|
827
|
+
if (!cellsOverlap(cellsOf(x), ycells)) return ok([x]);
|
|
784
828
|
const xd = isDerived(x);
|
|
785
829
|
const yd = isDerived(y);
|
|
786
830
|
const eff = derivedSide(x, y, side);
|
|
@@ -808,7 +852,7 @@ function transformCellsOverCells(x, y, side) {
|
|
|
808
852
|
const op = passes.filter((p) => !isEmptyMatrix(p)).map((p) => cellsMutation(x, p));
|
|
809
853
|
return ok(op, lost, dropped);
|
|
810
854
|
}
|
|
811
|
-
var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived"];
|
|
855
|
+
var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived", "sheetIndex", "sheetsLeft"];
|
|
812
856
|
var isDerived = (m) => m.params.derived === true;
|
|
813
857
|
function derivedSide(x, y, side) {
|
|
814
858
|
const xd = isDerived(x);
|
|
@@ -830,7 +874,15 @@ function withRefSheets(params, rs) {
|
|
|
830
874
|
return out;
|
|
831
875
|
}
|
|
832
876
|
var hasFormulaText = (v) => !!v && (isFormulaText(v.f) || typeof v.ref === "string");
|
|
877
|
+
var hasFormulaCache = /* @__PURE__ */ new WeakMap();
|
|
833
878
|
function hasFormula(m) {
|
|
879
|
+
const hit = hasFormulaCache.get(m);
|
|
880
|
+
if (hit !== void 0) return hit;
|
|
881
|
+
const any = hasFormulaOf(m);
|
|
882
|
+
hasFormulaCache.set(m, any);
|
|
883
|
+
return any;
|
|
884
|
+
}
|
|
885
|
+
function hasFormulaOf(m) {
|
|
834
886
|
let any = false;
|
|
835
887
|
forEachCell(cellsOf(m), (_r, _c, v) => {
|
|
836
888
|
any ||= hasFormulaText(v);
|
|
@@ -869,15 +921,18 @@ function cellThrough(sheet, change, r, c) {
|
|
|
869
921
|
return nr === null || nc === null ? null : [nr, nc];
|
|
870
922
|
}
|
|
871
923
|
function cellsOverChange(x, change) {
|
|
924
|
+
if (!hasFormula(x) && (change.kind !== "axis" || change.sheet !== sheetOf(x))) return ok([x]);
|
|
872
925
|
const ctx = formulaCtx(x);
|
|
873
926
|
const out = {};
|
|
874
927
|
const dropped = [];
|
|
875
928
|
let why = null;
|
|
929
|
+
let changed = false;
|
|
876
930
|
forEachCell(cellsOf(x), (r, c, v) => {
|
|
877
931
|
if (why) return;
|
|
878
932
|
const at = cellThrough(sheetOf(x), change, r, c);
|
|
879
933
|
if (!at) {
|
|
880
934
|
dropped.push(cellRef(sheetOf(x), r, c));
|
|
935
|
+
changed = true;
|
|
881
936
|
return;
|
|
882
937
|
}
|
|
883
938
|
let e = v;
|
|
@@ -888,14 +943,19 @@ function cellsOverChange(x, change) {
|
|
|
888
943
|
return;
|
|
889
944
|
}
|
|
890
945
|
e = res.entry;
|
|
946
|
+
if (res.changed) changed = true;
|
|
891
947
|
}
|
|
948
|
+
if (at[0] !== r || at[1] !== c) changed = true;
|
|
892
949
|
putCell(out, at[0], at[1], e);
|
|
893
950
|
});
|
|
894
951
|
if (why) return barrier2(why);
|
|
895
|
-
const
|
|
952
|
+
const rsAfter = refSheetsAfter(ctx.refSheets, change);
|
|
953
|
+
if (!changed && JSON.stringify(rsAfter ?? null) === JSON.stringify(ctx.refSheets ?? null)) return ok([x]);
|
|
954
|
+
const params = { ...withRefSheets(x.params, rsAfter), cellValue: out };
|
|
896
955
|
return ok(isEmptyMatrix(out) ? [] : [{ id: x.id, params }], [], dropped);
|
|
897
956
|
}
|
|
898
957
|
function rewritesOf(y, change) {
|
|
958
|
+
if (!hasFormula(y)) return null;
|
|
899
959
|
const ctx = formulaCtx(y);
|
|
900
960
|
const out = {};
|
|
901
961
|
let why = null;
|
|
@@ -1041,6 +1101,255 @@ function buildSize(template, values) {
|
|
|
1041
1101
|
const value = uniform !== void 0 && [...values.values()].every((v) => v === uniform) ? uniform : Object.fromEntries(values);
|
|
1042
1102
|
return { id: template.id, params: { ...p, ranges, [field]: value } };
|
|
1043
1103
|
}
|
|
1104
|
+
var isNumfmt = (m) => m.id === ID.setNumfmt || m.id === ID.removeNumfmt;
|
|
1105
|
+
function nfParts(m) {
|
|
1106
|
+
if (m.id === ID.removeNumfmt) return [{ key: null, ranges: (m.params.ranges ?? []).map((r) => ({ ...r })) }];
|
|
1107
|
+
const values = m.params.values ?? {};
|
|
1108
|
+
return Object.keys(values).map((k) => ({ key: k, ranges: (values[k]?.ranges ?? []).map((r) => ({ ...r })) }));
|
|
1109
|
+
}
|
|
1110
|
+
function nfBuild(m, parts) {
|
|
1111
|
+
const keep = parts.filter((p) => p.ranges.length);
|
|
1112
|
+
if (!keep.length) return [];
|
|
1113
|
+
if (m.id === ID.removeNumfmt) return [{ id: m.id, params: { ...m.params, ranges: keep[0].ranges } }];
|
|
1114
|
+
const values = {};
|
|
1115
|
+
const refMap = {};
|
|
1116
|
+
const srcValues = m.params.values ?? {};
|
|
1117
|
+
const srcRef = m.params.refMap ?? {};
|
|
1118
|
+
for (const p of keep) {
|
|
1119
|
+
values[p.key] = { ...srcValues[p.key], ranges: p.ranges };
|
|
1120
|
+
refMap[p.key] = srcRef[p.key];
|
|
1121
|
+
}
|
|
1122
|
+
return [{ id: m.id, params: { ...m.params, values, refMap } }];
|
|
1123
|
+
}
|
|
1124
|
+
function nfValueAt(m, row, col) {
|
|
1125
|
+
for (const p of nfParts(m))
|
|
1126
|
+
if (p.ranges.some((r) => r.startRow <= row && row <= r.endRow && r.startColumn <= col && col <= r.endColumn)) {
|
|
1127
|
+
if (p.key === null) return null;
|
|
1128
|
+
const ref = (m.params.refMap ?? {})[p.key];
|
|
1129
|
+
return { pattern: ref?.pattern };
|
|
1130
|
+
}
|
|
1131
|
+
return void 0;
|
|
1132
|
+
}
|
|
1133
|
+
function spanThrough(op, lo, hi) {
|
|
1134
|
+
const pieces = [];
|
|
1135
|
+
if (op.kind === "insert") pieces.push([0, op.at - 1, 0], [op.at, Infinity, op.count]);
|
|
1136
|
+
else if (op.kind === "remove") pieces.push([0, op.start - 1, 0], [op.start + op.count, Infinity, -op.count]);
|
|
1137
|
+
else if (op.count <= 0 || op.to >= op.from && op.to <= op.from + op.count) pieces.push([0, Infinity, 0]);
|
|
1138
|
+
else if (op.to > op.from) {
|
|
1139
|
+
const n = op.count;
|
|
1140
|
+
pieces.push([0, op.from - 1, 0], [op.from, op.from + n - 1, op.to - n - op.from], [op.from + n, op.to - 1, -n], [op.to, Infinity, 0]);
|
|
1141
|
+
} else {
|
|
1142
|
+
const n = op.count;
|
|
1143
|
+
pieces.push([0, op.to - 1, 0], [op.to, op.from - 1, n], [op.from, op.from + n - 1, op.to - op.from], [op.from + n, Infinity, 0]);
|
|
1144
|
+
}
|
|
1145
|
+
const out = [];
|
|
1146
|
+
for (const [a, b, d] of pieces) {
|
|
1147
|
+
const x0 = Math.max(a, lo);
|
|
1148
|
+
const x1 = Math.min(b, hi);
|
|
1149
|
+
if (x0 <= x1) out.push([x0 + d, x1 + d]);
|
|
1150
|
+
}
|
|
1151
|
+
out.sort((p, q) => p[0] - q[0]);
|
|
1152
|
+
const merged = [];
|
|
1153
|
+
for (const p of out) {
|
|
1154
|
+
const last = merged[merged.length - 1];
|
|
1155
|
+
if (last && last[1] + 1 >= p[0]) last[1] = Math.max(last[1], p[1]);
|
|
1156
|
+
else merged.push([p[0], p[1]]);
|
|
1157
|
+
}
|
|
1158
|
+
return merged;
|
|
1159
|
+
}
|
|
1160
|
+
function rectsThrough(st, rects) {
|
|
1161
|
+
const [a0, a1] = S(st.axis);
|
|
1162
|
+
const out = [];
|
|
1163
|
+
for (const r of rects) for (const [lo, hi] of spanThrough(st.op, r[a0], r[a1])) out.push({ ...r, [a0]: lo, [a1]: hi });
|
|
1164
|
+
return out;
|
|
1165
|
+
}
|
|
1166
|
+
function rectMinus(a, b) {
|
|
1167
|
+
if (a.startRow > b.endRow || b.startRow > a.endRow || a.startColumn > b.endColumn || b.startColumn > a.endColumn) return [a];
|
|
1168
|
+
const out = [];
|
|
1169
|
+
if (a.startRow < b.startRow) out.push({ ...a, endRow: b.startRow - 1 });
|
|
1170
|
+
if (a.endRow > b.endRow) out.push({ ...a, startRow: b.endRow + 1 });
|
|
1171
|
+
const r0 = Math.max(a.startRow, b.startRow);
|
|
1172
|
+
const r1 = Math.min(a.endRow, b.endRow);
|
|
1173
|
+
if (a.startColumn < b.startColumn) out.push({ ...a, startRow: r0, endRow: r1, endColumn: b.startColumn - 1 });
|
|
1174
|
+
if (a.endColumn > b.endColumn) out.push({ ...a, startRow: r0, endRow: r1, startColumn: b.endColumn + 1 });
|
|
1175
|
+
return out;
|
|
1176
|
+
}
|
|
1177
|
+
function rectsMinus(as, bs) {
|
|
1178
|
+
let cur = as;
|
|
1179
|
+
for (const b of bs) cur = cur.flatMap((a) => rectMinus(a, b));
|
|
1180
|
+
return cur;
|
|
1181
|
+
}
|
|
1182
|
+
function nWrites(m) {
|
|
1183
|
+
const out = [];
|
|
1184
|
+
forEachCell(cellsOf(m), (r, c, v) => {
|
|
1185
|
+
if (v === null || v.s === null || v.s && typeof v.s === "object" && "n" in v.s)
|
|
1186
|
+
out.push({ startRow: r, endRow: r, startColumn: c, endColumn: c });
|
|
1187
|
+
});
|
|
1188
|
+
return out;
|
|
1189
|
+
}
|
|
1190
|
+
function numfmtPair(x, y, side, sx, sy) {
|
|
1191
|
+
const nx = isNumfmt(x);
|
|
1192
|
+
const ny = isNumfmt(y);
|
|
1193
|
+
if (!nx && !ny) return null;
|
|
1194
|
+
if (nx && sy) return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsThrough(sy, p.ranges) }))));
|
|
1195
|
+
if (ny && sx) return ok([x]);
|
|
1196
|
+
if (nx && ny) {
|
|
1197
|
+
if (side === "left") return ok([x]);
|
|
1198
|
+
const ys = nfParts(y).flatMap((p) => p.ranges);
|
|
1199
|
+
return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) }))));
|
|
1200
|
+
}
|
|
1201
|
+
if (nx && y.id === ID.values) {
|
|
1202
|
+
if (side === "left") return ok([x]);
|
|
1203
|
+
const ys = nWrites(y);
|
|
1204
|
+
return ys.length ? ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) })))) : ok([x]);
|
|
1205
|
+
}
|
|
1206
|
+
if (ny && x.id === ID.values) {
|
|
1207
|
+
if (side === "left") return ok([x]);
|
|
1208
|
+
const out = {};
|
|
1209
|
+
const tail = {};
|
|
1210
|
+
let touched = false;
|
|
1211
|
+
forEachCell(cellsOf(x), (r, c, v) => {
|
|
1212
|
+
const n = nfValueAt(y, r, c);
|
|
1213
|
+
if (n === void 0) return putCell(out, r, c, v);
|
|
1214
|
+
if (v === null || v.s === null) {
|
|
1215
|
+
putCell(out, r, c, v);
|
|
1216
|
+
if (n) {
|
|
1217
|
+
putCell(tail, r, c, { s: { n } });
|
|
1218
|
+
touched = true;
|
|
1219
|
+
}
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (v.s && typeof v.s === "object" && "n" in v.s) {
|
|
1223
|
+
const { n: _drop, ...rest } = v.s;
|
|
1224
|
+
const e = { ...v };
|
|
1225
|
+
if (Object.keys(rest).length) e.s = rest;
|
|
1226
|
+
else delete e.s;
|
|
1227
|
+
touched = true;
|
|
1228
|
+
if (Object.keys(e).length) putCell(out, r, c, e);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
putCell(out, r, c, v);
|
|
1232
|
+
});
|
|
1233
|
+
if (!touched) return ok([x]);
|
|
1234
|
+
const ops = [];
|
|
1235
|
+
if (!isEmptyMatrix(out)) ops.push(cellsMutation(x, out));
|
|
1236
|
+
if (!isEmptyMatrix(tail)) ops.push({ id: ID.values, params: { subUnitId: sheetOf(x), cellValue: tail } });
|
|
1237
|
+
return ok(ops);
|
|
1238
|
+
}
|
|
1239
|
+
return ok([x]);
|
|
1240
|
+
}
|
|
1241
|
+
var isCount = (m) => m.id === ID.rowCount || m.id === ID.colCount;
|
|
1242
|
+
var countAxis = (m) => m.id === ID.rowCount ? "row" : "col";
|
|
1243
|
+
var countKey = (m) => m.id === ID.rowCount ? "rowCount" : "columnCount";
|
|
1244
|
+
function countPair(x, y, sy) {
|
|
1245
|
+
if (!isCount(x)) return isCount(y) ? ok([x]) : null;
|
|
1246
|
+
const k = countKey(x);
|
|
1247
|
+
const v = Number(x.params[k]);
|
|
1248
|
+
if (sy) {
|
|
1249
|
+
if (sy.axis !== countAxis(x) || sy.op.kind === "move") return ok([x]);
|
|
1250
|
+
const d = sy.op.kind === "insert" ? sy.op.count : -sy.op.count;
|
|
1251
|
+
return ok([{ id: x.id, params: { ...x.params, [k]: Math.max(1, v + d) } }]);
|
|
1252
|
+
}
|
|
1253
|
+
if (isCount(y) && y.id === x.id) {
|
|
1254
|
+
const w = Number(y.params[k]);
|
|
1255
|
+
return ok([w > v ? { id: x.id, params: { ...x.params, [k]: w } } : x]);
|
|
1256
|
+
}
|
|
1257
|
+
return ok([x]);
|
|
1258
|
+
}
|
|
1259
|
+
var isSheetOp = (m) => m.id === ID.insertSheet || m.id === ID.removeSheet || m.id === ID.sheetOrder;
|
|
1260
|
+
var sheetIdOf = (m) => m.id === ID.insertSheet ? String(m.params.sheet?.id ?? "") : sheetOf(m);
|
|
1261
|
+
function sheetAxisOf(m) {
|
|
1262
|
+
const p = m.params;
|
|
1263
|
+
if (m.id === ID.insertSheet) return typeof p.index === "number" ? { kind: "insert", at: p.index, count: 1 } : null;
|
|
1264
|
+
if (m.id === ID.removeSheet) return typeof p.sheetIndex === "number" ? { kind: "remove", start: p.sheetIndex, count: 1 } : null;
|
|
1265
|
+
if (m.id === ID.sheetOrder) {
|
|
1266
|
+
const f = Number(p.fromOrder);
|
|
1267
|
+
const t = Number(p.toOrder);
|
|
1268
|
+
return { kind: "move", from: f, count: 1, to: t > f ? t + 1 : t };
|
|
1269
|
+
}
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
function buildSheetOp(template, op) {
|
|
1273
|
+
if (op.kind === "insert") return { id: template.id, params: { ...template.params, index: op.at } };
|
|
1274
|
+
if (op.kind === "remove") return { id: template.id, params: { ...template.params, sheetIndex: op.start } };
|
|
1275
|
+
return { id: template.id, params: { ...template.params, fromOrder: op.from, toOrder: op.to > op.from ? op.to - 1 : op.to } };
|
|
1276
|
+
}
|
|
1277
|
+
function namesSheet(m, id) {
|
|
1278
|
+
const rs = refSheetsOf(m);
|
|
1279
|
+
return !!rs && Object.values(rs).includes(id) && (m.id === ID.setName || hasFormula(m));
|
|
1280
|
+
}
|
|
1281
|
+
function sheetPair(x, y, side) {
|
|
1282
|
+
const xs = isSheetOp(x);
|
|
1283
|
+
const ys = isSheetOp(y);
|
|
1284
|
+
if (!xs && !ys) return null;
|
|
1285
|
+
if (xs && ys) {
|
|
1286
|
+
const a = sheetAxisOf(x);
|
|
1287
|
+
const b = sheetAxisOf(y);
|
|
1288
|
+
if (!a || !b) return barrier2("a sheet add or remove without its index (capture must call annotateSheetOps)");
|
|
1289
|
+
if (x.id === ID.insertSheet && y.id === ID.insertSheet && sheetIdOf(x) === sheetIdOf(y)) {
|
|
1290
|
+
return JSON.stringify(x.params) === JSON.stringify(y.params) ? ok([]) : barrier2("the same sheet id added twice with different content");
|
|
1291
|
+
}
|
|
1292
|
+
if (x.id === ID.removeSheet && typeof x.params.sheetsLeft === "number" && sheetIdOf(x) !== sheetIdOf(y) && (y.id === ID.removeSheet || y.id === ID.insertSheet)) {
|
|
1293
|
+
const left = x.params.sheetsLeft + (y.id === ID.insertSheet ? 1 : -1);
|
|
1294
|
+
if (left < 1) return barrier2("concurrent sheet removes would leave the workbook without a sheet");
|
|
1295
|
+
const out2 = transformAxis(a, b, side);
|
|
1296
|
+
if (!out2) return barrier2("sheet order: no transform");
|
|
1297
|
+
return ok(out2.map((o) => buildSheetOp({ id: x.id, params: { ...x.params, sheetsLeft: left } }, o)));
|
|
1298
|
+
}
|
|
1299
|
+
const out = transformAxis(a, b, side);
|
|
1300
|
+
if (!out) return barrier2("sheet order: no transform");
|
|
1301
|
+
return ok(out.map((o) => buildSheetOp(x, o)), [], out.length === 0 && x.id === ID.sheetOrder ? [`${sheetIdOf(x)}: sheet order`] : []);
|
|
1302
|
+
}
|
|
1303
|
+
if (ys) {
|
|
1304
|
+
if (y.id !== ID.removeSheet) return ok([x]);
|
|
1305
|
+
const gone2 = sheetOf(y);
|
|
1306
|
+
if (namesSheet(x, gone2)) return barrier2("a formula names a sheet removed at the same time");
|
|
1307
|
+
if (sheetOf(x) === gone2 && x.id !== ID.setName && x.id !== ID.removeName)
|
|
1308
|
+
return ok([], [], isDerived(x) ? [] : [`${gone2}: the sheet was removed`]);
|
|
1309
|
+
return ok([x]);
|
|
1310
|
+
}
|
|
1311
|
+
if (x.id !== ID.removeSheet) return ok([x]);
|
|
1312
|
+
const gone = sheetOf(x);
|
|
1313
|
+
if (namesSheet(y, gone)) return barrier2("a formula names a sheet removed at the same time");
|
|
1314
|
+
return ok([x], sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : []);
|
|
1315
|
+
}
|
|
1316
|
+
function annotateSheetOps(group, sheetOrder) {
|
|
1317
|
+
const order = [...sheetOrder];
|
|
1318
|
+
return group.map((m) => {
|
|
1319
|
+
if (m.id === ID.insertSheet) {
|
|
1320
|
+
const index = typeof m.params.index === "number" ? Math.min(m.params.index, order.length) : order.length;
|
|
1321
|
+
order.splice(index, 0, sheetIdOf(m));
|
|
1322
|
+
return index === m.params.index ? m : { id: m.id, params: { ...m.params, index } };
|
|
1323
|
+
}
|
|
1324
|
+
if (m.id === ID.removeSheet) {
|
|
1325
|
+
const i = order.indexOf(sheetOf(m));
|
|
1326
|
+
if (i >= 0) order.splice(i, 1);
|
|
1327
|
+
return i >= 0 ? { id: m.id, params: { ...m.params, sheetIndex: i, sheetsLeft: order.length } } : m;
|
|
1328
|
+
}
|
|
1329
|
+
if (m.id === ID.sheetOrder) {
|
|
1330
|
+
const f = Number(m.params.fromOrder);
|
|
1331
|
+
const [id] = order.splice(f, 1);
|
|
1332
|
+
order.splice(Number(m.params.toOrder), 0, id ?? sheetOf(m));
|
|
1333
|
+
}
|
|
1334
|
+
return m;
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function inlineSheetStyles(sheet, styleOf) {
|
|
1338
|
+
const out = JSON.parse(JSON.stringify(sheet));
|
|
1339
|
+
const fix = (o) => {
|
|
1340
|
+
if (o && typeof o.s === "string") {
|
|
1341
|
+
const st = styleOf(o.s);
|
|
1342
|
+
if (st) o.s = JSON.parse(JSON.stringify(st));
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
for (const row of Object.values(out.cellData ?? {})) for (const cell of Object.values(row ?? {})) fix(cell);
|
|
1346
|
+
for (const key of ["rowData", "columnData"]) for (const v of Object.values(out[key] ?? {})) fix(v);
|
|
1347
|
+
if (typeof out.defaultStyle === "string") {
|
|
1348
|
+
const st = styleOf(out.defaultStyle);
|
|
1349
|
+
if (st) out.defaultStyle = JSON.parse(JSON.stringify(st));
|
|
1350
|
+
}
|
|
1351
|
+
return out;
|
|
1352
|
+
}
|
|
1044
1353
|
function familyOf(id) {
|
|
1045
1354
|
const e = SHEET_MUTATIONS[id];
|
|
1046
1355
|
return e && e.kind === "covered" ? e.family : "";
|
|
@@ -1057,6 +1366,8 @@ function transformMutation(x, y, side) {
|
|
|
1057
1366
|
}
|
|
1058
1367
|
const pb = PAIR_BARRIERS[pairKey(x.id, y.id)];
|
|
1059
1368
|
if (pb) return barrier2(`${x.id} \xD7 ${y.id}: ${pb.reason}${pb.seed === null ? "" : ` (fuzz seed ${pb.seed})`}`);
|
|
1369
|
+
const sp = sheetPair(x, y, side);
|
|
1370
|
+
if (sp) return sp;
|
|
1060
1371
|
const fp = formulaPair(x, y, side);
|
|
1061
1372
|
if (fp) return fp;
|
|
1062
1373
|
if (sheetOf(x) !== sheetOf(y)) return ok([x]);
|
|
@@ -1064,6 +1375,10 @@ function transformMutation(x, y, side) {
|
|
|
1064
1375
|
const fy = familyOf(y.id);
|
|
1065
1376
|
const sy = structuralOf(y);
|
|
1066
1377
|
const sx = structuralOf(x);
|
|
1378
|
+
const cp = countPair(x, y, sy);
|
|
1379
|
+
if (cp) return cp;
|
|
1380
|
+
const np = numfmtPair(x, y, side, sx, sy);
|
|
1381
|
+
if (np) return np;
|
|
1067
1382
|
if (sx && sy) {
|
|
1068
1383
|
if (sx.axis !== sy.axis) return ok([x]);
|
|
1069
1384
|
const out = transformAxis(sx.op, sy.op, side);
|
|
@@ -1171,22 +1486,45 @@ function tSeq(xs, ys, side, acc) {
|
|
|
1171
1486
|
acc.dropped.push(...a.dropped);
|
|
1172
1487
|
return [a.op, b.op];
|
|
1173
1488
|
}
|
|
1489
|
+
const outX = [];
|
|
1490
|
+
let curY = ys;
|
|
1491
|
+
for (const x of xs) {
|
|
1492
|
+
let curX = [x];
|
|
1493
|
+
const nextY = [];
|
|
1494
|
+
for (const y of curY) {
|
|
1495
|
+
if (curX.length === 0) {
|
|
1496
|
+
nextY.push(y);
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
const r = tSeq(curX, [y], side, acc);
|
|
1500
|
+
if (blocked(r)) return r;
|
|
1501
|
+
curX = r[0];
|
|
1502
|
+
for (const m of r[1]) nextY.push(m);
|
|
1503
|
+
}
|
|
1504
|
+
for (const m of curX) outX.push(m);
|
|
1505
|
+
curY = nextY;
|
|
1506
|
+
}
|
|
1507
|
+
return [outX, curY];
|
|
1508
|
+
}
|
|
1509
|
+
function tSeqOld(xs, ys, side, acc) {
|
|
1510
|
+
if (xs.length === 0 || ys.length === 0) return [xs, ys];
|
|
1511
|
+
if (xs.length === 1 && ys.length === 1) return tSeq(xs, ys, side, acc);
|
|
1174
1512
|
if (xs.length > 1) {
|
|
1175
|
-
const first2 =
|
|
1513
|
+
const first2 = tSeqOld([xs[0]], ys, side, acc);
|
|
1176
1514
|
if (blocked(first2)) return first2;
|
|
1177
|
-
const rest2 =
|
|
1515
|
+
const rest2 = tSeqOld(xs.slice(1), first2[1], side, acc);
|
|
1178
1516
|
if (blocked(rest2)) return rest2;
|
|
1179
1517
|
return [[...first2[0], ...rest2[0]], rest2[1]];
|
|
1180
1518
|
}
|
|
1181
|
-
const first =
|
|
1519
|
+
const first = tSeqOld(xs, [ys[0]], side, acc);
|
|
1182
1520
|
if (blocked(first)) return first;
|
|
1183
|
-
const rest =
|
|
1521
|
+
const rest = tSeqOld(first[0], ys.slice(1), side, acc);
|
|
1184
1522
|
if (blocked(rest)) return rest;
|
|
1185
1523
|
return [rest[0], [...first[1], ...rest[1]]];
|
|
1186
1524
|
}
|
|
1187
1525
|
function transformGroup(x, y, side) {
|
|
1188
1526
|
const acc = { lost: [], dropped: [] };
|
|
1189
|
-
const r = tSeq([...x], [...y], side, acc);
|
|
1527
|
+
const r = (process.env.OLD_TSEQ ? tSeqOld : tSeq)([...x], [...y], side, acc);
|
|
1190
1528
|
if (blocked(r)) return r;
|
|
1191
1529
|
return { op: r[0], lost: [...new Set(acc.lost)], dropped: [...new Set(acc.dropped)] };
|
|
1192
1530
|
}
|