From c1d51c5bc8ed0d45d1a71af0deb00e25eb96792e Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 25 Mar 2024 15:43:41 +0800 Subject: [PATCH 01/13] fix(formula): use ref range formula --- .../controllers/update-formula.controller.ts | 132 +++++++++--------- .../controllers/utils/ref-range-formula.ts | 25 ++-- 2 files changed, 84 insertions(+), 73 deletions(-) diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 7d9dea361a2..4410f1c415d 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -111,8 +111,9 @@ import { import { Inject, Injector } from '@wendellhu/redi'; import type { IRefRangeWithPosition } from './utils/offset-formula-data'; -import { offsetArrayFormula, offsetFormula, removeFormulaData } from './utils/offset-formula-data'; +import { removeFormulaData } from './utils/offset-formula-data'; import { handleRedoUndoMoveRange } from './utils/redo-undo-formula-data'; +import { refRangeFormula } from './utils/ref-range-formula'; interface IUnitRangeWithOffset extends IUnitRange { refOffsetX: number; @@ -215,7 +216,7 @@ export class UpdateFormulaController extends Disposable { (command.params as IMutationCommonParams)?.trigger === UndoCommand.id || (command.params as IMutationCommonParams)?.trigger === RedoCommand.id ) { - this._handleRedoUndo(command); // TODO: handle in set range values + // this._handleRedoUndo(command); // TODO: handle in set range values } }) ); @@ -400,77 +401,80 @@ export class UpdateFormulaController extends Disposable { if (result) { const { unitSheetNameMap } = this._formulaDataModel.getCalculateData(); - let oldFormulaData = this._formulaDataModel.getFormulaData(); + const oldFormulaData = this._formulaDataModel.getFormulaData(); const oldNumfmtItemMap = this._formulaDataModel.getNumfmtItemMap(); // change formula reference - const { newFormulaData: formulaData, refRanges } = this._getFormulaReferenceMoveInfo( + const { newFormulaData, refRanges } = this._getFormulaReferenceMoveInfo( oldFormulaData, unitSheetNameMap, result ); // TODO@Dushusir: handle offset formula data - // const {redos, undos} = refRangeFormula(oldFormulaData, newFormulaData, result); - - const workbook = this._currentUniverService.getCurrentUniverSheetInstance(); - const unitId = workbook.getUnitId(); - const sheetId = workbook.getActiveSheet().getSheetId(); - const selections = this._selectionManagerService.getSelections(); - - // offset arrayFormula - const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange(); - const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); - - // First use arrayFormulaCellData and the original arrayFormulaRange to calculate the offset of arrayFormulaCellData, otherwise the offset of arrayFormulaRange will be inaccurate. - const offsetArrayFormulaCellData = offsetFormula( - arrayFormulaCellData, - command, - unitId, - sheetId, - selections, - arrayFormulaRange, - refRanges - ); - let offsetArrayFormulaRange = offsetFormula( - arrayFormulaRange, - command, - unitId, - sheetId, - selections, - arrayFormulaRange - ); - offsetArrayFormulaRange = offsetArrayFormula(offsetArrayFormulaRange, command, unitId, sheetId); - - // Synchronous to the worker thread - this._commandService.executeCommand( - SetArrayFormulaDataMutation.id, - { - arrayFormulaRange: offsetArrayFormulaRange, - arrayFormulaCellData: offsetArrayFormulaCellData, - }, - { - onlyLocal: true, - } - ); - - // offset formulaData - oldFormulaData = offsetFormula(oldFormulaData, command, unitId, sheetId, selections); - const offsetFormulaData = offsetFormula(formulaData, command, unitId, sheetId, selections); - - // TODO@Dushusir: Here we take the redos incremental data, - // Synchronously to the worker thread, and update the dependency cache. - this._commandService.executeCommand(SetFormulaDataMutation.id, { - formulaData: this._formulaDataModel.getFormulaData(), - }); - - // offset numfmtItemMap - const offsetNumfmtItemMap = offsetFormula(oldNumfmtItemMap, command, unitId, sheetId, selections); - this._commandService.executeCommand(SetNumfmtFormulaDataMutation.id, { - numfmtItemMap: offsetNumfmtItemMap, - }); - - return this._getUpdateFormulaMutations(oldFormulaData, offsetFormulaData); + const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, result); + + // console.info('redoFormulaData==', redoFormulaData); + // console.info('undoFormulaData==', undoFormulaData); + + // const workbook = this._currentUniverService.getCurrentUniverSheetInstance(); + // const unitId = workbook.getUnitId(); + // const sheetId = workbook.getActiveSheet().getSheetId(); + // const selections = this._selectionManagerService.getSelections(); + + // // offset arrayFormula + // const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange(); + // const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); + + // // First use arrayFormulaCellData and the original arrayFormulaRange to calculate the offset of arrayFormulaCellData, otherwise the offset of arrayFormulaRange will be inaccurate. + // const offsetArrayFormulaCellData = offsetFormula( + // arrayFormulaCellData, + // command, + // unitId, + // sheetId, + // selections, + // arrayFormulaRange, + // refRanges + // ); + // let offsetArrayFormulaRange = offsetFormula( + // arrayFormulaRange, + // command, + // unitId, + // sheetId, + // selections, + // arrayFormulaRange + // ); + // offsetArrayFormulaRange = offsetArrayFormula(offsetArrayFormulaRange, command, unitId, sheetId); + + // // Synchronous to the worker thread + // this._commandService.executeCommand( + // SetArrayFormulaDataMutation.id, + // { + // arrayFormulaRange: offsetArrayFormulaRange, + // arrayFormulaCellData: offsetArrayFormulaCellData, + // }, + // { + // onlyLocal: true, + // } + // ); + + // // offset formulaData + // oldFormulaData = offsetFormula(oldFormulaData, command, unitId, sheetId, selections); + // const offsetFormulaData = offsetFormula(formulaData, command, unitId, sheetId, selections); + + // // TODO@Dushusir: Here we take the redos incremental data, + // // Synchronously to the worker thread, and update the dependency cache. + // this._commandService.executeCommand(SetFormulaDataMutation.id, { + // formulaData: this._formulaDataModel.getFormulaData(), + // }); + + // // offset numfmtItemMap + // const offsetNumfmtItemMap = offsetFormula(oldNumfmtItemMap, command, unitId, sheetId, selections); + // this._commandService.executeCommand(SetNumfmtFormulaDataMutation.id, { + // numfmtItemMap: offsetNumfmtItemMap, + // }); + + // return this._getUpdateFormulaMutations(oldFormulaData, offsetFormulaData); } return { diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 698f3f1a3f2..e9458334f93 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { IMutationInfo, IRange } from '@univerjs/core'; +import type { IRange } from '@univerjs/core'; import { ObjectMatrix, Tools } from '@univerjs/core'; import type { IFormulaData } from '@univerjs/engine-formula'; @@ -83,20 +83,25 @@ export function refRangeFormula(oldFormulaData: IFormulaData, } else if (type === FormulaReferenceMoveType.InsertMoveRight) { // TODO } + + return { + redoFormulaData: {}, + undoFormulaData: {}, + }; } function handleInsertCol(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - const redos: IMutationInfo[] = []; - const undos: IMutationInfo[] = []; + const redoFormulaData = {}; + const undoFormulaData = {}; const { type, unitId, sheetId, range, from, to } = formulaReferenceMoveParam; if (!Tools.isDefine(oldFormulaData)) { return { - redos, - undos, + redoFormulaData, + undoFormulaData, }; } @@ -104,11 +109,13 @@ function handleInsertCol(oldFormulaData: IFormulaData, if (formulaDataKeys.length === 0) { return { - redos, - undos, + redoFormulaData, + undoFormulaData, }; } + const rangeList = []; + for (const unitId of formulaDataKeys) { const sheetData = oldFormulaData[unitId]; @@ -129,7 +136,7 @@ function handleInsertCol(oldFormulaData: IFormulaData, } return { - redos, - undos, + redoFormulaData, + undoFormulaData, }; } From 86334bfa50124cf1c3988b8d9c0aa95ba62ebdda Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Fri, 29 Mar 2024 21:35:53 +0800 Subject: [PATCH 02/13] fix(formula): update formula offset insert column --- .../core/src/shared/__test__/common.spec.ts | 26 ++- packages/core/src/shared/common.ts | 12 +- packages/core/src/types/interfaces/i-range.ts | 3 +- .../mutations/set-formula-data.mutation.ts | 6 +- .../src/controller/calculate.controller.ts | 4 +- .../src/models/formula-data.model.ts | 81 ++++++--- .../controllers/update-formula.controller.ts | 56 ++++-- .../utils/__tests__/ref-range-formula.spec.ts | 86 +++++++++ .../controllers/utils/ref-range-formula.ts | 163 +++++++++++++++--- 9 files changed, 372 insertions(+), 65 deletions(-) create mode 100644 packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts diff --git a/packages/core/src/shared/__test__/common.spec.ts b/packages/core/src/shared/__test__/common.spec.ts index d33930e9eda..f12ed29d0a3 100644 --- a/packages/core/src/shared/__test__/common.spec.ts +++ b/packages/core/src/shared/__test__/common.spec.ts @@ -15,10 +15,34 @@ */ import { describe, expect, it } from 'vitest'; -import { cellToRange } from '../common'; +import { cellToRange, isFormulaId, isFormulaString } from '../common'; describe('Test common', () => { it('Test cellToRange', () => { expect(cellToRange(0, 1)).toStrictEqual({ startRow: 0, startColumn: 1, endRow: 0, endColumn: 1 }); }); + + it('Test isFormulaString', () => { + expect(isFormulaString('=SUM(1)')).toBe(true); + expect(isFormulaString('SUM(1)')).toBe(false); + expect(isFormulaString('=')).toBe(false); + expect(isFormulaString('')).toBe(false); + expect(isFormulaString(1)).toBe(false); + expect(isFormulaString(null)).toBe(false); + expect(isFormulaString(undefined)).toBe(false); + expect(isFormulaString(true)).toBe(false); + expect(isFormulaString({})).toBe(false); + expect(isFormulaString({ f: '' })).toBe(false); + }); + + it('Test isFormulaId', () => { + expect(isFormulaId('id1')).toBe(true); + expect(isFormulaId('')).toBe(false); + expect(isFormulaId(1)).toBe(false); + expect(isFormulaId(null)).toBe(false); + expect(isFormulaId(undefined)).toBe(false); + expect(isFormulaId(true)).toBe(false); + expect(isFormulaId({})).toBe(false); + expect(isFormulaId({ f: '' })).toBe(false); + }); }); diff --git a/packages/core/src/shared/common.ts b/packages/core/src/shared/common.ts index 0601d180bc8..27631985102 100644 --- a/packages/core/src/shared/common.ts +++ b/packages/core/src/shared/common.ts @@ -146,12 +146,22 @@ export function getColorStyle(color: Nullable): Nullable { return null; } +/** + * A string starting with an equal sign is a formula + * @param value + * @returns + */ export function isFormulaString(value: any): boolean { return Tools.isString(value) && value.substring(0, 1) === '=' && value.length > 1; } +/** + * any string + * @param value + * @returns + */ export function isFormulaId(value: any): boolean { - return Tools.isString(value) && value.indexOf('=') === -1 && value.length === 6; + return Tools.isString(value) && value.length > 0; } /** diff --git a/packages/core/src/types/interfaces/i-range.ts b/packages/core/src/types/interfaces/i-range.ts index bb5f3087556..7ddbda2286a 100644 --- a/packages/core/src/types/interfaces/i-range.ts +++ b/packages/core/src/types/interfaces/i-range.ts @@ -214,8 +214,9 @@ export interface IOptionData { */ contentsOnly?: boolean; /** - * Whether to clear only the comments. + * Auxiliary data for updating local memory data without having to deal with coordination conflicts */ + [key: PropertyKey]: any; } /** diff --git a/packages/engine-formula/src/commands/mutations/set-formula-data.mutation.ts b/packages/engine-formula/src/commands/mutations/set-formula-data.mutation.ts index 30f8dbab60f..d83f30eb9fc 100644 --- a/packages/engine-formula/src/commands/mutations/set-formula-data.mutation.ts +++ b/packages/engine-formula/src/commands/mutations/set-formula-data.mutation.ts @@ -19,18 +19,18 @@ import { CommandType } from '@univerjs/core'; import type { IAccessor } from '@wendellhu/redi'; import type { IFormulaData } from '../../basics/common'; -import { FormulaDataModel } from '../../models/formula-data.model'; export interface ISetFormulaDataMutationParams { formulaData: IFormulaData; } +/** + * There is no need to process data here, it is used as the main thread to send data to the worker. The main thread has already updated the data in advance, and there is no need to update it again here. + */ export const SetFormulaDataMutation: IMutation = { id: 'formula.mutation.set-formula-data', type: CommandType.MUTATION, handler: (accessor: IAccessor, params: ISetFormulaDataMutationParams) => { - const formulaDataModel = accessor.get(FormulaDataModel); - formulaDataModel.setFormulaData(params.formulaData); return true; }, }; diff --git a/packages/engine-formula/src/controller/calculate.controller.ts b/packages/engine-formula/src/controller/calculate.controller.ts index b769eb15f0d..fba94844083 100644 --- a/packages/engine-formula/src/controller/calculate.controller.ts +++ b/packages/engine-formula/src/controller/calculate.controller.ts @@ -64,7 +64,9 @@ export class CalculateController extends Disposable { this._calculateFormulaService.stopFormulaExecution(); } else if (command.id === SetFormulaDataMutation.id) { const formulaData = (command.params as ISetFormulaDataMutationParams).formulaData as IFormulaData; - this._formulaDataModel.setFormulaData(formulaData); + + // formulaData is the incremental data sent from the main thread and needs to be merged into formulaDataModel + this._formulaDataModel.mergeFormulaData(formulaData); } else if (command.id === SetFormulaCalculationStartMutation.id) { const params = command.params as ISetFormulaCalculationStartMutation; diff --git a/packages/engine-formula/src/models/formula-data.model.ts b/packages/engine-formula/src/models/formula-data.model.ts index 42a34b30282..21c07ff052e 100644 --- a/packages/engine-formula/src/models/formula-data.model.ts +++ b/packages/engine-formula/src/models/formula-data.model.ts @@ -116,19 +116,8 @@ export class FormulaDataModel extends Disposable { Object.keys(sheetData).forEach((sheetId) => { const cellMatrixData = sheetData[sheetId]; // The runtime data for array formula value calculated by the formula engine. - let arrayFormulaRangeMatrix = new ObjectMatrix(); // Original array formula range. - - let arrayFormulaCellMatrixData = new ObjectMatrix>(); // Original array formula cell data. - - if (this._arrayFormulaRange[unitId]?.[sheetId] != null) { - arrayFormulaRangeMatrix = new ObjectMatrix(this._arrayFormulaRange[unitId]?.[sheetId]); - } - - if (this._arrayFormulaCellData[unitId]?.[sheetId] != null) { - arrayFormulaCellMatrixData = new ObjectMatrix>( - this._arrayFormulaCellData[unitId]?.[sheetId] - ); - } + const arrayFormulaRangeMatrix = new ObjectMatrix(this._arrayFormulaRange[unitId]?.[sheetId]); // Original array formula range. + const arrayFormulaCellMatrixData = new ObjectMatrix>(this._arrayFormulaCellData[unitId]?.[sheetId]); // Original array formula cell data. /** * If the calculated value of the array formula is updated, clear the values within the original data formula range. @@ -238,12 +227,7 @@ export class FormulaDataModel extends Disposable { Object.keys(sheetData).forEach((sheetId) => { const arrayFormula = new ObjectMatrix(sheetData[sheetId]); - - let rangeMatrix = new ObjectMatrix(); - - if (this._arrayFormulaRange[unitId]?.[sheetId]) { - rangeMatrix = new ObjectMatrix(this._arrayFormulaRange[unitId]?.[sheetId]); - } + const rangeMatrix = new ObjectMatrix(this._arrayFormulaRange[unitId]?.[sheetId]); arrayFormula.forValue((r, c, v) => { rangeMatrix.setValue(r, c, v); @@ -256,6 +240,37 @@ export class FormulaDataModel extends Disposable { }); } + mergeFormulaData(formulaData: IFormulaData) { + Object.keys(formulaData).forEach((unitId) => { + const sheetData = formulaData[unitId]; + + if (sheetData == null) { + return true; + } + + if (!this._formulaData[unitId]) { + this._formulaData[unitId] = {}; + } + + Object.keys(sheetData).forEach((sheetId) => { + const sheetFormula = new ObjectMatrix(sheetData[sheetId]); + const formulaMatrix = new ObjectMatrix(this._formulaData[unitId]?.[sheetId]); + + sheetFormula.forValue((r, c, v) => { + if (v == null) { + formulaMatrix.realDeleteValue(r, c); + } else { + formulaMatrix.setValue(r, c, v); + } + }); + + if (this._formulaData[unitId]) { + this._formulaData[unitId]![sheetId] = formulaMatrix.getData(); + } + }); + }); + } + deleteArrayFormulaRange(unitId: string, sheetId: string, row: number, column: number) { const cellMatrixData = this._arrayFormulaRange[unitId]?.[sheetId]; if (cellMatrixData == null) { @@ -349,6 +364,7 @@ export class FormulaDataModel extends Disposable { } const sheetFormulaDataMatrix = new ObjectMatrix(workbookFormulaData[sheetId]); + const newSheetFormulaDataMatrix = new ObjectMatrix(); cellMatrix.forValue((r, c, cell) => { const formulaString = cell?.f || ''; @@ -364,26 +380,38 @@ export class FormulaDataModel extends Disposable { }); formulaIdMap.set(formulaId, { f: formulaString, r, c }); + + newSheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + si: formulaId, + }); } else if (checkFormulaString && !checkFormulaId) { sheetFormulaDataMatrix.setValue(r, c, { f: formulaString, }); + newSheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + }); } else if (!checkFormulaString && checkFormulaId) { sheetFormulaDataMatrix.setValue(r, c, { f: '', si: formulaId, }); - } else if (!checkFormulaString && !checkFormulaId && sheetFormulaDataMatrix.getValue(r, c)) { + } + // When cell is null or cell.f cell.si is null, delete formulaDataItem + else if (((cell?.f === null && cell?.si === null) || cell === null) && sheetFormulaDataMatrix.getValue(r, c)) { const currentFormulaInfo = sheetFormulaDataMatrix.getValue(r, c); const f = currentFormulaInfo?.f || ''; const si = currentFormulaInfo?.si || ''; // The id that needs to be offset + // When the cell containing the formulas f and si is deleted, f and si lose their association, and f needs to be moved to the next cell containing the same si. if (isFormulaString(f) && isFormulaId(si)) { deleteFormulaIdMap.set(si, f); } sheetFormulaDataMatrix.realDeleteValue(r, c); + newSheetFormulaDataMatrix.setValue(r, c, null); } }); @@ -400,11 +428,14 @@ export class FormulaDataModel extends Disposable { const f = formulaInfo.f; const x = c - formulaInfo.c; const y = r - formulaInfo.r; + sheetFormulaDataMatrix.setValue(r, c, { f, si: formulaId, x, y }); + newSheetFormulaDataMatrix.setValue(r, c, { f, si: formulaId, x, y }); } else if (typeof deleteFormula === 'string') { const x = cell.x || 0; const y = cell.y || 0; const offsetFormula = this._lexerTreeBuilder.moveFormulaRefOffset(deleteFormula, x, y); + deleteFormulaIdMap.set(formulaId, { r, c, @@ -412,18 +443,28 @@ export class FormulaDataModel extends Disposable { }); sheetFormulaDataMatrix.setValue(r, c, { f: offsetFormula, si: formulaId }); + newSheetFormulaDataMatrix.setValue(r, c, { f: offsetFormula, si: formulaId }); } else if (typeof deleteFormula === 'object') { const x = c - deleteFormula.c; const y = r - deleteFormula.r; + sheetFormulaDataMatrix.setValue(r, c, { f: deleteFormula.f, si: formulaId, x, y, }); + newSheetFormulaDataMatrix.setValue(r, c, { + f: deleteFormula.f, + si: formulaId, + x, + y, + }); } } }); + + return newSheetFormulaDataMatrix.clone(); } updateArrayFormulaRange( diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 4410f1c415d..ced0cbb55d4 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -162,7 +162,7 @@ enum OriginRangeEdgeType { * 2. Use refRange to offset the formula position and return undo/redo data to setRangeValues mutation - Redo data: Delete the old value at the old position on the match, and add the new value at the new position (the new value first checks whether the old position has offset content, if so, use the new offset content, if not, take the old value) - - Undo data: the old position on the match saves the old value, and the new position is left blank + - Undo data: the old position on the match saves the old value, and the new position delete value 3. onCommandExecuted, before formula calculation, use the setRangeValues information to delete the old formulaData, ArrayFormula and ArrayFormulaCellData, and send the worker (complementary setRangeValues after collaborative conflicts, normal operation triggers formula update, undo/redo are captured and processed here) */ @@ -199,6 +199,7 @@ export class UpdateFormulaController extends Disposable { if (command.id === SetRangeValuesMutation.id) { const params = command.params as ISetRangeValuesMutationParams; + if ( (options && options.onlyLocal === true) || params.trigger === SetStyleCommand.id || @@ -253,25 +254,25 @@ export class UpdateFormulaController extends Disposable { private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams, options?: IExecutionOptions) { const { subUnitId: sheetId, unitId, cellValue } = params; - if ( - (options && options.onlyLocal === true) || - params.trigger === SetStyleCommand.id || - params.trigger === SetBorderCommand.id || - params.trigger === ClearSelectionFormatCommand.id || - cellValue == null - ) { + if (cellValue == null) { return; } - this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue); + const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue); + const newFormulaData = { + [unitId]: { + [sheetId]: newSheetFormulaData, + }, + }; + this._formulaDataModel.updateArrayFormulaCellData(unitId, sheetId, cellValue); this._formulaDataModel.updateArrayFormulaRange(unitId, sheetId, cellValue); - this._formulaDataModel.updateNumfmtData(unitId, sheetId, cellValue); + this._formulaDataModel.updateNumfmtData(unitId, sheetId, cellValue); // TODO: move model to snapshot this._commandService.executeCommand( SetFormulaDataMutation.id, { - formulaData: this._formulaDataModel.getFormulaData(), + formulaData: newFormulaData, }, { onlyLocal: true, @@ -402,21 +403,48 @@ export class UpdateFormulaController extends Disposable { if (result) { const { unitSheetNameMap } = this._formulaDataModel.getCalculateData(); const oldFormulaData = this._formulaDataModel.getFormulaData(); - const oldNumfmtItemMap = this._formulaDataModel.getNumfmtItemMap(); + // const oldNumfmtItemMap = this._formulaDataModel.getNumfmtItemMap(); // change formula reference - const { newFormulaData, refRanges } = this._getFormulaReferenceMoveInfo( + const { newFormulaData } = this._getFormulaReferenceMoveInfo( oldFormulaData, unitSheetNameMap, result ); - // TODO@Dushusir: handle offset formula data const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, result); // console.info('redoFormulaData==', redoFormulaData); // console.info('undoFormulaData==', undoFormulaData); + const { sheetId: subUnitId, unitId } = result; + const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: redoFormulaData, + }; + + const redoMutation = { + id: SetRangeValuesMutation.id, + params: redoSetRangeValuesMutationParams, + }; + + const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: undoFormulaData, + }; + + const undoMutation = { + id: SetRangeValuesMutation.id, + params: undoSetRangeValuesMutationParams, + }; + + return { + undos: [undoMutation], + redos: [redoMutation], + }; + // const workbook = this._currentUniverService.getCurrentUniverSheetInstance(); // const unitId = workbook.getUnitId(); // const sheetId = workbook.getActiveSheet().getSheetId(); diff --git a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts new file mode 100644 index 00000000000..1f85dded65c --- /dev/null +++ b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts @@ -0,0 +1,86 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; + +import type { IFormulaDataItem } from '@univerjs/engine-formula'; +import { formulaDataItemToCellData } from '../ref-range-formula'; + +describe('Ref range formula test', () => { + describe('Util function', () => { + it('Function formulaDataItemToCellData', () => { + let formulaDataItem: IFormulaDataItem = { + f: '=SUM(1)', + }; + + let result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toStrictEqual({ + f: '=SUM(1)', + }); + + formulaDataItem = { + f: '', + si: 'id1', + }; + + result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toStrictEqual({ + si: 'id1', + }); + + formulaDataItem = { + f: '=SUM(1)', + si: 'id1', + }; + + result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toStrictEqual({ + f: '=SUM(1)', + si: 'id1', + }); + + formulaDataItem = { + f: '=SUM(1)', + si: 'id1', + x: 0, + y: 0, + }; + + result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toStrictEqual({ + f: '=SUM(1)', + si: 'id1', + }); + + formulaDataItem = { + f: '=SUM(1)', + si: 'id1', + x: 0, + y: 1, + }; + + result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toStrictEqual({ + si: 'id1', + }); + }); + }); +}); diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index e9458334f93..22d8b68e522 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -14,9 +14,11 @@ * limitations under the License. */ -import type { IRange } from '@univerjs/core'; -import { ObjectMatrix, Tools } from '@univerjs/core'; -import type { IFormulaData } from '@univerjs/engine-formula'; +import type { ICellData, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; +import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix } from '@univerjs/core'; +import type { IFormulaData, IFormulaDataItem } from '@univerjs/engine-formula'; +import { EffectRefRangId, handleInsertCol, runRefRangeMutations } from '@univerjs/sheets'; +import { checkFormulaDataNull } from './offset-formula-data'; export enum FormulaReferenceMoveType { MoveRange, // range @@ -44,6 +46,11 @@ export interface IFormulaReferenceMoveParam { sheetName?: string; } +interface IRangeChange { + oldCell: IRange; + newCell: IRange; +} + /** * For different Command operations, it may be necessary to perform traversal in reverse or in forward order, so first determine the type of Command and then perform traversal. * @param oldFormulaData @@ -69,7 +76,7 @@ export function refRangeFormula(oldFormulaData: IFormulaData, } else if (type === FormulaReferenceMoveType.InsertRow) { // TODO } else if (type === FormulaReferenceMoveType.InsertColumn) { - return handleInsertCol(oldFormulaData, newFormulaData, formulaReferenceMoveParam); + return handleRefInsertCol(oldFormulaData, newFormulaData, formulaReferenceMoveParam); } else if (type === FormulaReferenceMoveType.RemoveRow) { // TODO } else if (type === FormulaReferenceMoveType.RemoveColumn) { @@ -90,53 +97,161 @@ export function refRangeFormula(oldFormulaData: IFormulaData, }; } -function handleInsertCol(oldFormulaData: IFormulaData, +function handleRefInsertCol(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - const redoFormulaData = {}; - const undoFormulaData = {}; + let redoFormulaData: IObjectMatrixPrimitiveType> = {}; + let undoFormulaData: IObjectMatrixPrimitiveType> = {}; const { type, unitId, sheetId, range, from, to } = formulaReferenceMoveParam; - if (!Tools.isDefine(oldFormulaData)) { + if (range === undefined) { return { redoFormulaData, undoFormulaData, }; } - const formulaDataKeys = Object.keys(oldFormulaData); - - if (formulaDataKeys.length === 0) { + if (checkFormulaDataNull(oldFormulaData, unitId, sheetId)) { return { redoFormulaData, undoFormulaData, }; } - const rangeList = []; + const currentOldFormulaData = oldFormulaData[unitId]![sheetId]; + const currentNewFormulaData = newFormulaData[unitId]![sheetId]; + + const oldFormulaMatrix = new ObjectMatrix(currentOldFormulaData); + const newFormulaMatrix = new ObjectMatrix(currentNewFormulaData); + + // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. + const rangeList: IRangeChange[] = []; + + oldFormulaMatrix.forValue((row, column, cell) => { + const formulaString = cell?.f || ''; + const formulaId = cell?.si || ''; - for (const unitId of formulaDataKeys) { - const sheetData = oldFormulaData[unitId]; + const checkFormulaString = isFormulaString(formulaString); + const checkFormulaId = isFormulaId(formulaId); - if (sheetData == null) { - continue; + // Offset is only needed when there is a formula + if (!checkFormulaString && !checkFormulaId) { + return; } - const sheetDataKeys = Object.keys(sheetData); - for (const sheetId of sheetDataKeys) { - const matrixData = new ObjectMatrix(sheetData[sheetId]); + const oldCell = cellToRange(row, column); - matrixData.forValue((row, column, formulaDataItem) => { - if (!formulaDataItem) return true; + const operators = handleInsertCol( + { + id: EffectRefRangId.InsertColCommandId, + params: { range, unitId: '', subUnitId: '', direction: Direction.RIGHT }, + }, + oldCell + ); - const { f: formulaString, x, y, si } = formulaDataItem; - }); + const newCell = runRefRangeMutations(operators, oldCell); + + if (newCell == null) { + return; } - } + + rangeList.push({ + oldCell, + newCell, + }); + }); + + redoFormulaData = getRedoFormulaData(rangeList.reverse(), oldFormulaMatrix, newFormulaMatrix); + undoFormulaData = getUndoFormulaData(rangeList, oldFormulaMatrix, newFormulaMatrix); return { redoFormulaData, undoFormulaData, }; } + +/** + * Delete the old value at the old position on the match, and add the new value at the new position (the new value first checks whether the old position has offset content, if so, use the new offset content, if not, take the old value) + * @param rangeList + * @param oldFormulaData + * @param newFormulaData + */ +function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix, newFormulaMatrix: ObjectMatrix) { + const redoFormulaData = new ObjectMatrix({}); + + rangeList.forEach((item) => { + const { oldCell, newCell } = item; + + const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; + + const newFormula = newFormulaMatrix.getValue(oldStartRow, oldStartColumn) || oldFormulaMatrix.getValue(oldStartRow, oldStartColumn); + const newValue = formulaDataItemToCellData(newFormula); + + redoFormulaData.setValue(newStartRow, newStartColumn, newValue); + redoFormulaData.setValue(oldStartRow, oldStartColumn, null); + }); + + return redoFormulaData.clone(); +} + +/** + * The old position on the match saves the old value, and the new position delete value(for formulaData) + * @param rangeList + * @param oldFormulaData + * @param newFormulaData + */ +function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix, newFormulaMatrix: ObjectMatrix) { + const undoFormulaData = new ObjectMatrix({}); + + rangeList.forEach((item) => { + const { oldCell, newCell } = item; + + const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; + + const oldFormula = oldFormulaMatrix.getValue(oldStartRow, oldStartColumn); + const oldValue = formulaDataItemToCellData(oldFormula); + + // When undoing, setRangeValues is executed before the position changes, so we must store the old value in the new position so that the old value can be restored to the correct position after the snapshot position changes. + + // For formulaData, it should be necessary to restore the old value at the old position and delete the value at the new position, which is the opposite of the situation in undo, so we set a position exchange information in the mutation information of undo, and identify and process it in the update logic of formulaData. + undoFormulaData.setValue(oldStartRow, oldStartColumn, null); + undoFormulaData.setValue(newStartRow, newStartColumn, oldValue); + }); + + return undoFormulaData.clone(); +} + +/** + * Transfer the formulaDataItem to the cellData + * ┌────────────────────────────────┬─────────────────┐ + * │ IFormulaDataItem │ ICellData │ + * ├──────────────────┬─────┬───┬───┼───────────┬─────┤ + * │ f │ si │ x │ y │ f │ si │ + * ├──────────────────┼─────┼───┼───┼───────────┼─────┤ + * │ =SUM(1) │ │ │ │ =SUM(1) │ │ + * │ │ id1 │ │ │ │ id1 │ + * │ =SUM(1) │ id1 │ │ │ =SUM(1) │ id1 │ + * │ =SUM(1) │ id1 │ 0 │ 0 │ =SUM(1) │ id1 │ + * │ =SUM(1) │ id1 │ 0 │ 1 │ │ id1 │ + * └──────────────────┴─────┴───┴───┴───────────┴─────┘ + */ +export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): ICellData { + const { f, si, x = 0, y = 0 } = formulaDataItem; + const checkFormulaString = isFormulaString(f); + const checkFormulaId = isFormulaId(si); + + const cellData: ICellData = {}; + + if (checkFormulaId) { + cellData.si = si; + } + + if (checkFormulaString && x === 0 && y === 0) { + cellData.f = f; + } + + return cellData; +} From d359865ca2c22dedf54ab58f6624730e4cb474b8 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Sat, 30 Mar 2024 22:50:31 +0800 Subject: [PATCH 03/13] fix(formula): undo exchange position --- packages/engine-formula/src/index.ts | 2 ++ .../src/models/formula-data.model.ts | 21 +++++++++++++++++-- .../controllers/update-formula.controller.ts | 14 ++++++++----- .../controllers/utils/ref-range-formula.ts | 12 +++++++++-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index 281ffa9d509..6581f481d1e 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -147,3 +147,5 @@ export { IFormulaRuntimeService, FormulaRuntimeService } from './services/runtim export { IFormulaCurrentConfigService, FormulaCurrentConfigService } from './services/current-data.service'; export { IActiveDirtyManagerService } from './services/active-dirty-manager.service'; + +export type { IExchangePosition } from './models/formula-data.model'; diff --git a/packages/engine-formula/src/models/formula-data.model.ts b/packages/engine-formula/src/models/formula-data.model.ts index 21c07ff052e..af91b1c569c 100644 --- a/packages/engine-formula/src/models/formula-data.model.ts +++ b/packages/engine-formula/src/models/formula-data.model.ts @@ -37,6 +37,10 @@ export interface IFormulaIdMap { c: number; } +export interface IExchangePosition { + [key: string]: IRange; +} + export class FormulaDataModel extends Disposable { private _formulaData: IFormulaData = {}; @@ -347,7 +351,7 @@ export class FormulaDataModel extends Disposable { }; } - updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>) { + updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>, exchangePosition?: IExchangePosition) { const cellMatrix = new ObjectMatrix(cellValue); const formulaIdMap = this.getFormulaIdMap(unitId, sheetId); // Connect the formula and ID @@ -366,7 +370,20 @@ export class FormulaDataModel extends Disposable { const sheetFormulaDataMatrix = new ObjectMatrix(workbookFormulaData[sheetId]); const newSheetFormulaDataMatrix = new ObjectMatrix(); - cellMatrix.forValue((r, c, cell) => { + cellMatrix.forValue((row, column, cell) => { + let r = row; + let c = column; + + // Exchange the position of the cell + if (exchangePosition) { + const key = `${row}_${column}`; + if (exchangePosition[key]) { + const { startRow, startColumn } = exchangePosition[key]; + r = startRow; + c = startColumn; + } + } + const formulaString = cell?.f || ''; const formulaId = cell?.si || ''; diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index ced0cbb55d4..28ff9ad07ab 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -208,7 +208,7 @@ export class UpdateFormulaController extends Disposable { ) { return; } - this._handleSetRangeValuesMutation(params as ISetRangeValuesMutationParams, options); + this._handleSetRangeValuesMutation(params as ISetRangeValuesMutationParams); } else if (command.id === RemoveSheetMutation.id) { this._handleRemoveSheetMutation(command.params as IRemoveSheetMutationParams); } else if (command.id === InsertSheetMutation.id) { @@ -251,14 +251,15 @@ export class UpdateFormulaController extends Disposable { }); } - private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams, options?: IExecutionOptions) { - const { subUnitId: sheetId, unitId, cellValue } = params; + private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams) { + const { subUnitId: sheetId, unitId, cellValue, options } = params; + const { exchangePosition } = options || {}; if (cellValue == null) { return; } - const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue); + const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue, exchangePosition); const newFormulaData = { [unitId]: { [sheetId]: newSheetFormulaData, @@ -412,7 +413,7 @@ export class UpdateFormulaController extends Disposable { result ); - const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, result); + const { redoFormulaData, undoFormulaData, exchangePosition } = refRangeFormula(oldFormulaData, newFormulaData, result); // console.info('redoFormulaData==', redoFormulaData); // console.info('undoFormulaData==', undoFormulaData); @@ -433,6 +434,9 @@ export class UpdateFormulaController extends Disposable { subUnitId, unitId, cellValue: undoFormulaData, + options: { + exchangePosition, + }, }; const undoMutation = { diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 22d8b68e522..78b84a1b64a 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -16,7 +16,7 @@ import type { ICellData, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix } from '@univerjs/core'; -import type { IFormulaData, IFormulaDataItem } from '@univerjs/engine-formula'; +import type { IExchangePosition, IFormulaData, IFormulaDataItem } from '@univerjs/engine-formula'; import { EffectRefRangId, handleInsertCol, runRefRangeMutations } from '@univerjs/sheets'; import { checkFormulaDataNull } from './offset-formula-data'; @@ -46,7 +46,7 @@ export interface IFormulaReferenceMoveParam { sheetName?: string; } -interface IRangeChange { +export interface IRangeChange { oldCell: IRange; newCell: IRange; } @@ -94,6 +94,7 @@ export function refRangeFormula(oldFormulaData: IFormulaData, return { redoFormulaData: {}, undoFormulaData: {}, + exchangePosition: {}, }; } @@ -102,6 +103,7 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { let redoFormulaData: IObjectMatrixPrimitiveType> = {}; let undoFormulaData: IObjectMatrixPrimitiveType> = {}; + const exchangePosition: IExchangePosition = {}; const { type, unitId, sheetId, range, from, to } = formulaReferenceMoveParam; @@ -109,6 +111,7 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, return { redoFormulaData, undoFormulaData, + exchangePosition, }; } @@ -116,6 +119,7 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, return { redoFormulaData, undoFormulaData, + exchangePosition, }; } @@ -160,6 +164,9 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, oldCell, newCell, }); + + exchangePosition[`${row}_${column}`] = oldCell; + exchangePosition[`${newCell.startRow}_${newCell.startColumn}`] = newCell; }); redoFormulaData = getRedoFormulaData(rangeList.reverse(), oldFormulaMatrix, newFormulaMatrix); @@ -168,6 +175,7 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, return { redoFormulaData, undoFormulaData, + exchangePosition, }; } From 124123d013092b30ca5b81a14985ed2b62dc3dad Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 1 Apr 2024 21:11:44 +0800 Subject: [PATCH 04/13] fix(formula): offset formula move range,column,row --- .../set-array-formula-data.mutation.ts | 19 +- packages/engine-formula/src/index.ts | 3 +- .../src/models/formula-data.model.ts | 153 ++++----- .../src/models/utils/formula-data-util.ts | 86 +++++ .../controllers/update-formula.controller.ts | 13 +- .../controllers/utils/ref-range-formula.ts | 302 +++++++++++++----- .../commands/commands/move-range.command.ts | 4 +- .../commands/move-rows-cols.command.ts | 4 +- 8 files changed, 380 insertions(+), 204 deletions(-) create mode 100644 packages/engine-formula/src/models/utils/formula-data-util.ts diff --git a/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts b/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts index 44e7152ab35..a28ed892ddb 100644 --- a/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts +++ b/packages/engine-formula/src/commands/mutations/set-array-formula-data.mutation.ts @@ -15,34 +15,23 @@ */ import type { IMutation } from '@univerjs/core'; -import { CommandType, Tools } from '@univerjs/core'; +import { CommandType } from '@univerjs/core'; import type { IAccessor } from '@wendellhu/redi'; import type { IArrayFormulaRangeType, IArrayFormulaUnitCellType } from '../../basics/common'; -import { FormulaDataModel } from '../../models/formula-data.model'; export interface ISetArrayFormulaDataMutationParams { arrayFormulaRange: IArrayFormulaRangeType; arrayFormulaCellData: IArrayFormulaUnitCellType; } -export const SetArrayFormulaDataUndoMutationFactory = (accessor: IAccessor): ISetArrayFormulaDataMutationParams => { - const formulaDataModel = accessor.get(FormulaDataModel); - const arrayFormulaRange = Tools.deepClone(formulaDataModel.getArrayFormulaRange()); - const arrayFormulaCellData = Tools.deepClone(formulaDataModel.getArrayFormulaCellData()); - return { - arrayFormulaRange, - arrayFormulaCellData, - }; -}; - +/** + * There is no need to process data here, it is used as the main thread to send data to the worker. The main thread has already updated the data in advance, and there is no need to update it again here. + */ export const SetArrayFormulaDataMutation: IMutation = { id: 'formula.mutation.set-array-formula-data', type: CommandType.MUTATION, handler: (accessor: IAccessor, params: ISetArrayFormulaDataMutationParams) => { - const formulaDataModel = accessor.get(FormulaDataModel); - formulaDataModel.setArrayFormulaRange(params.arrayFormulaRange); - formulaDataModel.setArrayFormulaCellData(params.arrayFormulaCellData); return true; }, }; diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index 6581f481d1e..1962b119765 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -41,7 +41,6 @@ export { RegisterFunctionMutation } from './commands/mutations/register-function export { type ISetArrayFormulaDataMutationParams, SetArrayFormulaDataMutation, - SetArrayFormulaDataUndoMutationFactory, } from './commands/mutations/set-array-formula-data.mutation'; export { RemoveDefinedNameMutation, SetDefinedNameMutation, type ISetDefinedNameMutationSearchParam, type ISetDefinedNameMutationParam } from './commands/mutations/set-defined-name.mutation'; @@ -148,4 +147,4 @@ export { IFormulaCurrentConfigService, FormulaCurrentConfigService } from './ser export { IActiveDirtyManagerService } from './services/active-dirty-manager.service'; -export type { IExchangePosition } from './models/formula-data.model'; +export type { IRangeChange } from './models/formula-data.model'; diff --git a/packages/engine-formula/src/models/formula-data.model.ts b/packages/engine-formula/src/models/formula-data.model.ts index af91b1c569c..cb7d720c0cc 100644 --- a/packages/engine-formula/src/models/formula-data.model.ts +++ b/packages/engine-formula/src/models/formula-data.model.ts @@ -30,15 +30,12 @@ import type { IUnitSheetNameMap, } from '../basics/common'; import { LexerTreeBuilder } from '../engine/analysis/lexer-tree-builder'; +import type { IFormulaIdMap } from './utils/formula-data-util'; +import { clearArrayFormulaCellDataByCell, updateFormulaDataByCellValue } from './utils/formula-data-util'; -export interface IFormulaIdMap { - f: string; - r: number; - c: number; -} - -export interface IExchangePosition { - [key: string]: IRange; +export interface IRangeChange { + oldCell: IRange; + newCell: IRange; } export class FormulaDataModel extends Disposable { @@ -351,7 +348,7 @@ export class FormulaDataModel extends Disposable { }; } - updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>, exchangePosition?: IExchangePosition) { + updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>, rangeList?: IRangeChange[], isReverse?: boolean) { const cellMatrix = new ObjectMatrix(cellValue); const formulaIdMap = this.getFormulaIdMap(unitId, sheetId); // Connect the formula and ID @@ -370,67 +367,25 @@ export class FormulaDataModel extends Disposable { const sheetFormulaDataMatrix = new ObjectMatrix(workbookFormulaData[sheetId]); const newSheetFormulaDataMatrix = new ObjectMatrix(); - cellMatrix.forValue((row, column, cell) => { - let r = row; - let c = column; - - // Exchange the position of the cell - if (exchangePosition) { - const key = `${row}_${column}`; - if (exchangePosition[key]) { - const { startRow, startColumn } = exchangePosition[key]; - r = startRow; - c = startColumn; - } + if (rangeList) { + if (isReverse) { + rangeList.reverse(); } - const formulaString = cell?.f || ''; - const formulaId = cell?.si || ''; + rangeList.forEach(({ oldCell, newCell }) => { + const { startRow: r, startColumn: c } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - const checkFormulaString = isFormulaString(formulaString); - const checkFormulaId = isFormulaId(formulaId); + const cell = cellMatrix.getValue(newStartRow, newStartColumn); - if (checkFormulaString && checkFormulaId) { - sheetFormulaDataMatrix.setValue(r, c, { - f: formulaString, - si: formulaId, - }); - - formulaIdMap.set(formulaId, { f: formulaString, r, c }); - - newSheetFormulaDataMatrix.setValue(r, c, { - f: formulaString, - si: formulaId, - }); - } else if (checkFormulaString && !checkFormulaId) { - sheetFormulaDataMatrix.setValue(r, c, { - f: formulaString, - }); - newSheetFormulaDataMatrix.setValue(r, c, { - f: formulaString, - }); - } else if (!checkFormulaString && checkFormulaId) { - sheetFormulaDataMatrix.setValue(r, c, { - f: '', - si: formulaId, - }); - } - // When cell is null or cell.f cell.si is null, delete formulaDataItem - else if (((cell?.f === null && cell?.si === null) || cell === null) && sheetFormulaDataMatrix.getValue(r, c)) { - const currentFormulaInfo = sheetFormulaDataMatrix.getValue(r, c); - const f = currentFormulaInfo?.f || ''; - const si = currentFormulaInfo?.si || ''; - - // The id that needs to be offset - // When the cell containing the formulas f and si is deleted, f and si lose their association, and f needs to be moved to the next cell containing the same si. - if (isFormulaString(f) && isFormulaId(si)) { - deleteFormulaIdMap.set(si, f); - } - - sheetFormulaDataMatrix.realDeleteValue(r, c); - newSheetFormulaDataMatrix.setValue(r, c, null); - } - }); + updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, r, c, cell); + updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, newStartRow, newStartColumn, null); + }); + } else { + cellMatrix.forValue((r, c, cell) => { + updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, r, c, cell); + }); + } // Convert the formula ID to formula string sheetFormulaDataMatrix.forValue((r, c, cell) => { @@ -487,7 +442,8 @@ export class FormulaDataModel extends Disposable { updateArrayFormulaRange( unitId: string, sheetId: string, - cellValue: IObjectMatrixPrimitiveType> + cellValue: IObjectMatrixPrimitiveType>, + rangeList?: IRangeChange[], isReverse?: boolean ) { // remove the array formula range when cell value is null @@ -496,30 +452,32 @@ export class FormulaDataModel extends Disposable { if (!arrayFormulaRange) return; const arrayFormulaRangeMatrix = new ObjectMatrix(arrayFormulaRange); - const cellMatrix = new ObjectMatrix(cellValue); - cellMatrix.forValue((r, c, cell) => { - const arrayFormulaRangeValue = arrayFormulaRangeMatrix?.getValue(r, c); - if (arrayFormulaRangeValue == null) { - return true; - } - const formulaString = cell?.f || ''; - const formulaId = cell?.si || ''; + if (rangeList) { + if (isReverse) { + rangeList.reverse(); + } - const checkFormulaString = isFormulaString(formulaString); - const checkFormulaId = isFormulaId(formulaId); + rangeList.forEach(({ oldCell, newCell }) => { + const { startRow: r, startColumn: c } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - if (!checkFormulaString && !checkFormulaId) { arrayFormulaRangeMatrix.realDeleteValue(r, c); - } - }); + arrayFormulaRangeMatrix.realDeleteValue(newStartRow, newStartColumn); + }); + } else { + cellMatrix.forValue((r, c, cell) => { + arrayFormulaRangeMatrix.realDeleteValue(r, c); + }); + } } updateArrayFormulaCellData( unitId: string, sheetId: string, - cellValue: IObjectMatrixPrimitiveType> + cellValue: IObjectMatrixPrimitiveType>, + rangeList?: IRangeChange[], isReverse?: boolean ) { // remove the array formula range when cell value is null @@ -536,27 +494,24 @@ export class FormulaDataModel extends Disposable { const arrayFormulaCellDataMatrix = new ObjectMatrix(arrayFormulaCellData); const cellMatrix = new ObjectMatrix(cellValue); - cellMatrix.forValue((r, c, cell) => { - const arrayFormulaRangeValue = arrayFormulaRangeMatrix?.getValue(r, c); - if (arrayFormulaRangeValue == null) { - return true; + + if (rangeList) { + if (isReverse) { + rangeList.reverse(); } - const formulaString = cell?.f || ''; - const formulaId = cell?.si || ''; + rangeList.forEach(({ oldCell, newCell }) => { + const { startRow: r, startColumn: c } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - const checkFormulaString = isFormulaString(formulaString); - const checkFormulaId = isFormulaId(formulaId); - - if (!checkFormulaString && !checkFormulaId) { - const { startRow, startColumn, endRow, endColumn } = arrayFormulaRangeValue; - for (let r = startRow; r <= endRow; r++) { - for (let c = startColumn; c <= endColumn; c++) { - arrayFormulaCellDataMatrix.realDeleteValue(r, c); - } - } - } - }); + clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, r, c); + clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, newStartRow, newStartColumn); + }); + } else { + cellMatrix.forValue((r, c, cell) => { + clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, r, c); + }); + } } updateNumfmtData( diff --git a/packages/engine-formula/src/models/utils/formula-data-util.ts b/packages/engine-formula/src/models/utils/formula-data-util.ts new file mode 100644 index 00000000000..ed40080fce1 --- /dev/null +++ b/packages/engine-formula/src/models/utils/formula-data-util.ts @@ -0,0 +1,86 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICellData, IRange, Nullable, ObjectMatrix } from '@univerjs/core'; +import { isFormulaId, isFormulaString } from '@univerjs/core'; +import type { IFormulaDataItem } from '../../basics/common'; + +export interface IFormulaIdMap { + f: string; + r: number; + c: number; +} + +export function updateFormulaDataByCellValue(sheetFormulaDataMatrix: ObjectMatrix, newSheetFormulaDataMatrix: ObjectMatrix, formulaIdMap: Map, deleteFormulaIdMap: Map, r: number, c: number, cell: Nullable) { + const formulaString = cell?.f || ''; + const formulaId = cell?.si || ''; + + const checkFormulaString = isFormulaString(formulaString); + const checkFormulaId = isFormulaId(formulaId); + + if (checkFormulaString && checkFormulaId) { + sheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + si: formulaId, + }); + + formulaIdMap.set(formulaId, { f: formulaString, r, c }); + + newSheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + si: formulaId, + }); + } else if (checkFormulaString && !checkFormulaId) { + sheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + }); + newSheetFormulaDataMatrix.setValue(r, c, { + f: formulaString, + }); + } else if (!checkFormulaString && checkFormulaId) { + sheetFormulaDataMatrix.setValue(r, c, { + f: '', + si: formulaId, + }); + } else if (!checkFormulaString && !checkFormulaId && sheetFormulaDataMatrix.getValue(r, c)) { + const currentFormulaInfo = sheetFormulaDataMatrix.getValue(r, c); + const f = currentFormulaInfo?.f || ''; + const si = currentFormulaInfo?.si || ''; + + // The id that needs to be offset + // When the cell containing the formulas f and si is deleted, f and si lose their association, and f needs to be moved to the next cell containing the same si. + if (isFormulaString(f) && isFormulaId(si)) { + deleteFormulaIdMap.set(si, f); + } + + sheetFormulaDataMatrix.realDeleteValue(r, c); + newSheetFormulaDataMatrix.setValue(r, c, null); + } +} + +export function clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix: ObjectMatrix, arrayFormulaCellDataMatrix: ObjectMatrix>, r: number, c: number) { + const arrayFormulaRangeValue = arrayFormulaRangeMatrix?.getValue(r, c); + if (arrayFormulaRangeValue == null) { + return true; + } + + const { startRow, startColumn, endRow, endColumn } = arrayFormulaRangeValue; + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + arrayFormulaCellDataMatrix.realDeleteValue(r, c); + } + } +} diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 28ff9ad07ab..894c538805f 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -253,21 +253,21 @@ export class UpdateFormulaController extends Disposable { private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams) { const { subUnitId: sheetId, unitId, cellValue, options } = params; - const { exchangePosition } = options || {}; + const { rangeList, isReverse } = options || {}; if (cellValue == null) { return; } - const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue, exchangePosition); + const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue, rangeList, isReverse); const newFormulaData = { [unitId]: { [sheetId]: newSheetFormulaData, }, }; - this._formulaDataModel.updateArrayFormulaCellData(unitId, sheetId, cellValue); - this._formulaDataModel.updateArrayFormulaRange(unitId, sheetId, cellValue); + this._formulaDataModel.updateArrayFormulaCellData(unitId, sheetId, cellValue, rangeList, isReverse); + this._formulaDataModel.updateArrayFormulaRange(unitId, sheetId, cellValue, rangeList, isReverse); this._formulaDataModel.updateNumfmtData(unitId, sheetId, cellValue); // TODO: move model to snapshot this._commandService.executeCommand( @@ -413,7 +413,7 @@ export class UpdateFormulaController extends Disposable { result ); - const { redoFormulaData, undoFormulaData, exchangePosition } = refRangeFormula(oldFormulaData, newFormulaData, result); + const { redoFormulaData, undoFormulaData, rangeList, isReverse } = refRangeFormula(oldFormulaData, newFormulaData, result); // console.info('redoFormulaData==', redoFormulaData); // console.info('undoFormulaData==', undoFormulaData); @@ -435,7 +435,8 @@ export class UpdateFormulaController extends Disposable { unitId, cellValue: undoFormulaData, options: { - exchangePosition, + rangeList, + isReverse, }, }; diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 78b84a1b64a..7ab1cdc8adf 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -15,9 +15,9 @@ */ import type { ICellData, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; -import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix } from '@univerjs/core'; -import type { IExchangePosition, IFormulaData, IFormulaDataItem } from '@univerjs/engine-formula'; -import { EffectRefRangId, handleInsertCol, runRefRangeMutations } from '@univerjs/sheets'; +import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix, Tools } from '@univerjs/core'; +import type { IFormulaData, IFormulaDataItem, IRangeChange } from '@univerjs/engine-formula'; +import { EffectRefRangId, handleDeleteRangeMoveLeft, handleDeleteRangeMoveUp, handleInsertCol, handleInsertRangeMoveDown, handleInsertRangeMoveRight, handleInsertRow, handleIRemoveCol, handleIRemoveRow, handleMoveCols, handleMoveRange, handleMoveRows, runRefRangeMutations } from '@univerjs/sheets'; import { checkFormulaDataNull } from './offset-formula-data'; export enum FormulaReferenceMoveType { @@ -46,11 +46,6 @@ export interface IFormulaReferenceMoveParam { sheetName?: string; } -export interface IRangeChange { - oldCell: IRange; - newCell: IRange; -} - /** * For different Command operations, it may be necessary to perform traversal in reverse or in forward order, so first determine the type of Command and then perform traversal. * @param oldFormulaData @@ -59,67 +54,22 @@ export interface IRangeChange { * @returns */ export function refRangeFormula(oldFormulaData: IFormulaData, - newFormulaData: IFormulaData, - formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - const type = formulaReferenceMoveParam.type; - - if (type === FormulaReferenceMoveType.SetName) { - // TODO - } else if (type === FormulaReferenceMoveType.RemoveSheet) { - // TODO - } else if (type === FormulaReferenceMoveType.MoveRange) { - // TODO - } else if (type === FormulaReferenceMoveType.MoveRows) { - // TODO - } else if (type === FormulaReferenceMoveType.MoveCols) { - // TODO - } else if (type === FormulaReferenceMoveType.InsertRow) { - // TODO - } else if (type === FormulaReferenceMoveType.InsertColumn) { - return handleRefInsertCol(oldFormulaData, newFormulaData, formulaReferenceMoveParam); - } else if (type === FormulaReferenceMoveType.RemoveRow) { - // TODO - } else if (type === FormulaReferenceMoveType.RemoveColumn) { - // TODO - } else if (type === FormulaReferenceMoveType.DeleteMoveLeft) { - // TODO - } else if (type === FormulaReferenceMoveType.DeleteMoveUp) { - // TODO - } else if (type === FormulaReferenceMoveType.InsertMoveDown) { - // TODO - } else if (type === FormulaReferenceMoveType.InsertMoveRight) { - // TODO - } - - return { - redoFormulaData: {}, - undoFormulaData: {}, - exchangePosition: {}, - }; -} - -function handleRefInsertCol(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { let redoFormulaData: IObjectMatrixPrimitiveType> = {}; let undoFormulaData: IObjectMatrixPrimitiveType> = {}; - const exchangePosition: IExchangePosition = {}; + // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. + const rangeList: IRangeChange[] = []; + let isReverse = false; const { type, unitId, sheetId, range, from, to } = formulaReferenceMoveParam; - if (range === undefined) { - return { - redoFormulaData, - undoFormulaData, - exchangePosition, - }; - } - if (checkFormulaDataNull(oldFormulaData, unitId, sheetId)) { return { redoFormulaData, undoFormulaData, - exchangePosition, + rangeList, + isReverse, }; } @@ -129,9 +79,6 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, const oldFormulaMatrix = new ObjectMatrix(currentOldFormulaData); const newFormulaMatrix = new ObjectMatrix(currentNewFormulaData); - // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. - const rangeList: IRangeChange[] = []; - oldFormulaMatrix.forValue((row, column, cell) => { const formulaString = cell?.f || ''; const formulaId = cell?.si || ''; @@ -145,40 +92,239 @@ function handleRefInsertCol(oldFormulaData: IFormulaData, } const oldCell = cellToRange(row, column); + let newCell = null; + + switch (type) { + case FormulaReferenceMoveType.SetName: + // TODO + break; + case FormulaReferenceMoveType.RemoveSheet: + // TODO + break; + case FormulaReferenceMoveType.MoveRange: + if (from == null || to == null) { + return; + } + newCell = handleRefMoveRange(from, to, oldCell); + break; + case FormulaReferenceMoveType.MoveRows: + if (from == null || to == null) { + return; + } + newCell = handleRefMoveRows(from, to, oldCell); + break; + case FormulaReferenceMoveType.MoveCols: + if (from == null || to == null) { + return; + } + newCell = handleRefMoveCols(from, to, oldCell); + break; + default: + break; + } - const operators = handleInsertCol( - { - id: EffectRefRangId.InsertColCommandId, - params: { range, unitId: '', subUnitId: '', direction: Direction.RIGHT }, - }, - oldCell - ); - - const newCell = runRefRangeMutations(operators, oldCell); + if (Tools.isDefine(range)) { + switch (type) { + case FormulaReferenceMoveType.InsertRow: + newCell = handleRefInsertRow(range, oldCell); + isReverse = true; + break; + case FormulaReferenceMoveType.InsertColumn: + newCell = handleRefInsertCol(range, oldCell); + isReverse = true; + break; + case FormulaReferenceMoveType.RemoveRow: + newCell = handleRefRemoveRow(range, oldCell); + break; + case FormulaReferenceMoveType.RemoveColumn: + newCell = handleRefMoveCol(range, oldCell); + break; + case FormulaReferenceMoveType.DeleteMoveLeft: + newCell = handleRefDeleteMoveLeft(range, oldCell); + break; + case FormulaReferenceMoveType.DeleteMoveUp: + newCell = handleRefDeleteMoveUp(range, oldCell); + break; + case FormulaReferenceMoveType.InsertMoveDown: + newCell = handleRefInsertMoveDown(range, oldCell); + isReverse = true; + break; + case FormulaReferenceMoveType.InsertMoveRight: + newCell = handleRefInsertMoveRight(range, oldCell); + isReverse = true; + break; + default: + break; + } + } if (newCell == null) { return; } - rangeList.push({ - oldCell, - newCell, - }); + const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell; + const { startRow: newStartRow, startColumn: newStartColumn } = newCell; + + if (oldStartRow === newStartRow && oldStartColumn === newStartColumn) { + return; + } - exchangePosition[`${row}_${column}`] = oldCell; - exchangePosition[`${newCell.startRow}_${newCell.startColumn}`] = newCell; + if (isReverse) { + rangeList.unshift({ + oldCell, + newCell, + }); + } else { + rangeList.push({ + oldCell, + newCell, + }); + } }); - redoFormulaData = getRedoFormulaData(rangeList.reverse(), oldFormulaMatrix, newFormulaMatrix); + redoFormulaData = getRedoFormulaData(rangeList, oldFormulaMatrix, newFormulaMatrix); undoFormulaData = getUndoFormulaData(rangeList, oldFormulaMatrix, newFormulaMatrix); return { redoFormulaData, undoFormulaData, - exchangePosition, + rangeList, + isReverse, }; } +function handleRefMoveRange(from: IRange, to: IRange, oldCell: IRange) { + const operators = handleMoveRange( + { + id: EffectRefRangId.MoveRangeCommandId, + params: { toRange: to, fromRange: from }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefMoveRows(from: IRange, to: IRange, oldCell: IRange) { + const operators = handleMoveRows( + { + id: EffectRefRangId.MoveRowsCommandId, + params: { toRange: to, fromRange: from }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefMoveCols(from: IRange, to: IRange, oldCell: IRange) { + const operators = handleMoveCols( + { + id: EffectRefRangId.MoveColsCommandId, + params: { toRange: to, fromRange: from }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefInsertRow(range: IRange, oldCell: IRange) { + const operators = handleInsertRow( + { + id: EffectRefRangId.InsertRowCommandId, + params: { range, unitId: '', subUnitId: '', direction: Direction.DOWN }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefInsertCol(range: IRange, oldCell: IRange) { + const operators = handleInsertCol( + { + id: EffectRefRangId.InsertColCommandId, + params: { range, unitId: '', subUnitId: '', direction: Direction.RIGHT }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefRemoveRow(range: IRange, oldCell: IRange) { + const operators = handleIRemoveRow( + { + id: EffectRefRangId.RemoveRowCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefMoveCol(range: IRange, oldCell: IRange) { + const operators = handleIRemoveCol( + { + id: EffectRefRangId.RemoveColCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefDeleteMoveLeft(range: IRange, oldCell: IRange) { + const operators = handleDeleteRangeMoveLeft( + { + id: EffectRefRangId.DeleteRangeMoveLeftCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefDeleteMoveUp(range: IRange, oldCell: IRange) { + const operators = handleDeleteRangeMoveUp( + { + id: EffectRefRangId.DeleteRangeMoveUpCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefInsertMoveDown(range: IRange, oldCell: IRange) { + const operators = handleInsertRangeMoveDown( + { + id: EffectRefRangId.InsertRangeMoveDownCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + +function handleRefInsertMoveRight(range: IRange, oldCell: IRange) { + const operators = handleInsertRangeMoveRight( + { + id: EffectRefRangId.InsertRangeMoveRightCommandId, + params: { range }, + }, + oldCell + ); + + return runRefRangeMutations(operators, oldCell); +} + /** * Delete the old value at the old position on the match, and add the new value at the new position (the new value first checks whether the old position has offset content, if so, use the new offset content, if not, take the old value) * @param rangeList diff --git a/packages/sheets/src/commands/commands/move-range.command.ts b/packages/sheets/src/commands/commands/move-range.command.ts index 53c9fba0984..0cfc3b4f955 100644 --- a/packages/sheets/src/commands/commands/move-range.command.ts +++ b/packages/sheets/src/commands/commands/move-range.command.ts @@ -85,6 +85,8 @@ export const MoveRangeCommand: ICommand = { }, ]; const undos = [ + ...interceptorCommands.undos, + ...moveRangeMutations.undos, { id: SetSelectionsOperation.id, params: { @@ -94,8 +96,6 @@ export const MoveRangeCommand: ICommand = { selections: [{ range: params.fromRange, primary: getPrimaryForRange(params.fromRange, worksheet) }], } as ISetSelectionsOperationParams, }, - ...moveRangeMutations.undos, - ...interceptorCommands.undos, ]; const result = sequenceExecute(redos, commandService).result; diff --git a/packages/sheets/src/commands/commands/move-rows-cols.command.ts b/packages/sheets/src/commands/commands/move-rows-cols.command.ts index 69e29e89730..462aeb16af8 100644 --- a/packages/sheets/src/commands/commands/move-rows-cols.command.ts +++ b/packages/sheets/src/commands/commands/move-rows-cols.command.ts @@ -150,9 +150,9 @@ export const MoveRowsCommand: ICommand = { ]; const undos = [ + ...interceptorCommands.undos, { id: MoveRowsMutation.id, params: undoMoveRowsParams }, { id: SetSelectionsOperation.id, params: undoSetSelectionsParam }, - ...interceptorCommands.undos, ]; const result = sequenceExecute(redos, commandService); @@ -277,9 +277,9 @@ export const MoveColsCommand: ICommand = { ]; const undos = [ + ...interceptorCommands.undos, { id: MoveColsMutation.id, params: undoMoveColsParams }, { id: SetSelectionsOperation.id, params: undoSetSelectionsParam }, - ...interceptorCommands.undos, ]; const result = sequenceExecute(redos, commandService); From 82eb2b2d88361e5db9377706b66c454bd5539210 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 2 Apr 2024 21:26:35 +0800 Subject: [PATCH 05/13] fix(sheet): ref range command use preUndos and preRedos --- .../commands/commands/delete-range-move-left.command.ts | 6 +++--- .../src/commands/commands/delete-range-move-up.command.ts | 6 +++--- .../commands/commands/insert-range-move-down.command.ts | 5 ++++- .../commands/commands/insert-range-move-right.command.ts | 6 +++++- .../src/commands/commands/insert-row-col.command.ts | 8 +++++--- .../sheets/src/commands/commands/move-range.command.ts | 4 +++- .../src/commands/commands/move-rows-cols.command.ts | 8 ++++++-- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/sheets/src/commands/commands/delete-range-move-left.command.ts b/packages/sheets/src/commands/commands/delete-range-move-left.command.ts index 066f6b10ee8..7991363b9ef 100644 --- a/packages/sheets/src/commands/commands/delete-range-move-left.command.ts +++ b/packages/sheets/src/commands/commands/delete-range-move-left.command.ts @@ -82,11 +82,11 @@ export const DeleteRangeMoveLeftCommand: ICommand = { accessor, deleteRangeMutationParams ); - const redos: IMutationInfo[] = [...removeRangeRedo]; - const undos: IMutationInfo[] = [...removeRangeUndo]; + const redos: IMutationInfo[] = [...(sheetInterceptor.preRedos ?? []), ...removeRangeRedo]; + const undos: IMutationInfo[] = [...sheetInterceptor.undos, ...removeRangeUndo]; redos.push(...sheetInterceptor.redos); redos.push(followSelectionOperation(range, workbook, worksheet)); - undos.push(...sheetInterceptor.undos); + undos.push(...(sheetInterceptor.preUndos ?? [])); // execute do mutations and add undo mutations to undo stack if completed const result = sequenceExecute(redos, commandService).result; diff --git a/packages/sheets/src/commands/commands/delete-range-move-up.command.ts b/packages/sheets/src/commands/commands/delete-range-move-up.command.ts index 72e254f514f..f06052594b7 100644 --- a/packages/sheets/src/commands/commands/delete-range-move-up.command.ts +++ b/packages/sheets/src/commands/commands/delete-range-move-up.command.ts @@ -78,11 +78,11 @@ export const DeleteRangeMoveUpCommand: ICommand = { accessor, deleteRangeMutationParams ); - const redos: IMutationInfo[] = [...removeRangeRedo]; - const undos: IMutationInfo[] = [...removeRangeUndo]; + const redos: IMutationInfo[] = [...(sheetInterceptor.preRedos ?? []), ...removeRangeRedo]; + const undos: IMutationInfo[] = [...sheetInterceptor.undos, ...removeRangeUndo]; redos.push(...sheetInterceptor.redos); redos.push(followSelectionOperation(range, workbook, worksheet)); - undos.push(...sheetInterceptor.undos); + undos.push(...(sheetInterceptor.preUndos ?? [])); const result = await sequenceExecute(redos, commandService).result; if (result) { diff --git a/packages/sheets/src/commands/commands/insert-range-move-down.command.ts b/packages/sheets/src/commands/commands/insert-range-move-down.command.ts index b95700f669b..907ef90fb94 100644 --- a/packages/sheets/src/commands/commands/insert-range-move-down.command.ts +++ b/packages/sheets/src/commands/commands/insert-range-move-down.command.ts @@ -159,7 +159,10 @@ export const InsertRangeMoveDownCommand: ICommand = { }); redoMutations.push(...sheetInterceptor.redos); redoMutations.push(followSelectionOperation(range, workbook, worksheet)); - undoMutations.push(...sheetInterceptor.undos); + undoMutations.push(...(sheetInterceptor.preUndos ?? [])); + + redoMutations.unshift(...(sheetInterceptor.preRedos ?? [])); + undoMutations.unshift(...sheetInterceptor.undos); // execute do mutations and add undo mutations to undo stack if completed const result = sequenceExecute(redoMutations, commandService); diff --git a/packages/sheets/src/commands/commands/insert-range-move-right.command.ts b/packages/sheets/src/commands/commands/insert-range-move-right.command.ts index 076a936efb9..6e445bd50d3 100644 --- a/packages/sheets/src/commands/commands/insert-range-move-right.command.ts +++ b/packages/sheets/src/commands/commands/insert-range-move-right.command.ts @@ -156,7 +156,11 @@ export const InsertRangeMoveRightCommand: ICommand = { }); redoMutations.push(...sheetInterceptor.redos); redoMutations.push(followSelectionOperation(range, workbook, worksheet)); - undoMutations.push(...sheetInterceptor.undos); + undoMutations.push(...(sheetInterceptor.preUndos ?? [])); + + redoMutations.unshift(...(sheetInterceptor.preRedos ?? [])); + undoMutations.unshift(...sheetInterceptor.undos); + // execute do mutations and add undo mutations to undo stack if completed const result = sequenceExecute(redoMutations, commandService); if (result.result) { diff --git a/packages/sheets/src/commands/commands/insert-row-col.command.ts b/packages/sheets/src/commands/commands/insert-row-col.command.ts index a975cbaede0..966c6e20d3a 100644 --- a/packages/sheets/src/commands/commands/insert-row-col.command.ts +++ b/packages/sheets/src/commands/commands/insert-row-col.command.ts @@ -117,8 +117,8 @@ export const InsertRowCommand: ICommand = { if (result.result) { undoRedoService.pushUndoRedo({ unitID: params.unitId, - undoMutations: [{ id: RemoveRowMutation.id, params: undoRowInsertionParams }, ...intercepted.undos], - redoMutations: [{ id: InsertRowMutation.id, params: insertRowParams }, ...intercepted.redos], + undoMutations: [...(intercepted.preUndos ?? []), { id: RemoveRowMutation.id, params: undoRowInsertionParams }, ...intercepted.undos], + redoMutations: [...(intercepted.preRedos ?? []), { id: InsertRowMutation.id, params: insertRowParams }, ...intercepted.redos], }); return true; @@ -287,7 +287,7 @@ export const InsertColCommand: ICommand = { const result = sequenceExecute( [ - + ...(intercepted.preRedos ?? []), { id: InsertColMutation.id, params: insertColParams }, ...intercepted.redos, followSelectionOperation(range, workbook, worksheet), @@ -299,6 +299,7 @@ export const InsertColCommand: ICommand = { undoRedoService.pushUndoRedo({ unitID: params.unitId, undoMutations: [ + ...(intercepted.preUndos ?? []), { id: RemoveColMutation.id, params: undoColInsertionParams, @@ -306,6 +307,7 @@ export const InsertColCommand: ICommand = { ...intercepted.undos, ].filter(Boolean), redoMutations: [ + ...(intercepted.preRedos ?? []), { id: InsertColMutation.id, params: insertColParams }, ...intercepted.redos, ].filter(Boolean), diff --git a/packages/sheets/src/commands/commands/move-range.command.ts b/packages/sheets/src/commands/commands/move-range.command.ts index 0cfc3b4f955..6c53baea6c0 100644 --- a/packages/sheets/src/commands/commands/move-range.command.ts +++ b/packages/sheets/src/commands/commands/move-range.command.ts @@ -72,6 +72,7 @@ export const MoveRangeCommand: ICommand = { }); const redos = [ + ...(interceptorCommands.preRedos ?? []), ...moveRangeMutations.redos, ...interceptorCommands.redos, { @@ -85,8 +86,9 @@ export const MoveRangeCommand: ICommand = { }, ]; const undos = [ - ...interceptorCommands.undos, + ...(interceptorCommands.preUndos ?? []), ...moveRangeMutations.undos, + ...interceptorCommands.undos, { id: SetSelectionsOperation.id, params: { diff --git a/packages/sheets/src/commands/commands/move-rows-cols.command.ts b/packages/sheets/src/commands/commands/move-rows-cols.command.ts index 462aeb16af8..c96f325e5b6 100644 --- a/packages/sheets/src/commands/commands/move-rows-cols.command.ts +++ b/packages/sheets/src/commands/commands/move-rows-cols.command.ts @@ -144,15 +144,17 @@ export const MoveRowsCommand: ICommand = { const interceptorCommands = sheetInterceptorService.onCommandExecute({ id: MoveRowsCommand.id, params }); const redos = [ + ...(interceptorCommands.preRedos ?? []), { id: MoveRowsMutation.id, params: moveRowsParams }, { id: SetSelectionsOperation.id, params: setSelectionsParam }, ...interceptorCommands.redos, ]; const undos = [ - ...interceptorCommands.undos, + ...(interceptorCommands.preUndos ?? []), { id: MoveRowsMutation.id, params: undoMoveRowsParams }, { id: SetSelectionsOperation.id, params: undoSetSelectionsParam }, + ...interceptorCommands.undos, ]; const result = sequenceExecute(redos, commandService); @@ -271,15 +273,17 @@ export const MoveColsCommand: ICommand = { const interceptorCommands = sheetInterceptorService.onCommandExecute({ id: MoveColsCommand.id, params }); const redos = [ + ...(interceptorCommands.preRedos ?? []), { id: MoveColsMutation.id, params: moveColsParams }, { id: SetSelectionsOperation.id, params: setSelectionsParam }, ...interceptorCommands.redos, ]; const undos = [ - ...interceptorCommands.undos, + ...(interceptorCommands.preUndos ?? []), { id: MoveColsMutation.id, params: undoMoveColsParams }, { id: SetSelectionsOperation.id, params: undoSetSelectionsParam }, + ...interceptorCommands.undos, ]; const result = sequenceExecute(redos, commandService); From 4b249f2011c6c9dbf062c05d0bc6c1b7d2be5bf9 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Wed, 3 Apr 2024 14:39:41 +0800 Subject: [PATCH 06/13] fix(formula): set name, remove sheet preundos --- packages/core/src/types/interfaces/i-range.ts | 4 -- .../src/models/formula-data.model.ts | 70 ++++-------------- .../controllers/update-formula.controller.ts | 49 +++---------- .../controllers/utils/ref-range-formula.ts | 71 +++++++++++++++---- .../commands/commands/remove-sheet.command.ts | 4 +- .../commands/set-worksheet-name.command.ts | 4 +- 6 files changed, 83 insertions(+), 119 deletions(-) diff --git a/packages/core/src/types/interfaces/i-range.ts b/packages/core/src/types/interfaces/i-range.ts index 7ddbda2286a..ec7a505db00 100644 --- a/packages/core/src/types/interfaces/i-range.ts +++ b/packages/core/src/types/interfaces/i-range.ts @@ -213,10 +213,6 @@ export interface IOptionData { * */ contentsOnly?: boolean; - /** - * Auxiliary data for updating local memory data without having to deal with coordination conflicts - */ - [key: PropertyKey]: any; } /** diff --git a/packages/engine-formula/src/models/formula-data.model.ts b/packages/engine-formula/src/models/formula-data.model.ts index cb7d720c0cc..1e1ea832cdc 100644 --- a/packages/engine-formula/src/models/formula-data.model.ts +++ b/packages/engine-formula/src/models/formula-data.model.ts @@ -348,7 +348,7 @@ export class FormulaDataModel extends Disposable { }; } - updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>, rangeList?: IRangeChange[], isReverse?: boolean) { + updateFormulaData(unitId: string, sheetId: string, cellValue: IObjectMatrixPrimitiveType>) { const cellMatrix = new ObjectMatrix(cellValue); const formulaIdMap = this.getFormulaIdMap(unitId, sheetId); // Connect the formula and ID @@ -367,25 +367,9 @@ export class FormulaDataModel extends Disposable { const sheetFormulaDataMatrix = new ObjectMatrix(workbookFormulaData[sheetId]); const newSheetFormulaDataMatrix = new ObjectMatrix(); - if (rangeList) { - if (isReverse) { - rangeList.reverse(); - } - - rangeList.forEach(({ oldCell, newCell }) => { - const { startRow: r, startColumn: c } = oldCell; - const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - - const cell = cellMatrix.getValue(newStartRow, newStartColumn); - - updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, r, c, cell); - updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, newStartRow, newStartColumn, null); - }); - } else { - cellMatrix.forValue((r, c, cell) => { - updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, r, c, cell); - }); - } + cellMatrix.forValue((r, c, cell) => { + updateFormulaDataByCellValue(sheetFormulaDataMatrix, newSheetFormulaDataMatrix, formulaIdMap, deleteFormulaIdMap, r, c, cell); + }); // Convert the formula ID to formula string sheetFormulaDataMatrix.forValue((r, c, cell) => { @@ -442,8 +426,7 @@ export class FormulaDataModel extends Disposable { updateArrayFormulaRange( unitId: string, sheetId: string, - cellValue: IObjectMatrixPrimitiveType>, - rangeList?: IRangeChange[], isReverse?: boolean + cellValue: IObjectMatrixPrimitiveType> ) { // remove the array formula range when cell value is null @@ -454,30 +437,15 @@ export class FormulaDataModel extends Disposable { const arrayFormulaRangeMatrix = new ObjectMatrix(arrayFormulaRange); const cellMatrix = new ObjectMatrix(cellValue); - if (rangeList) { - if (isReverse) { - rangeList.reverse(); - } - - rangeList.forEach(({ oldCell, newCell }) => { - const { startRow: r, startColumn: c } = oldCell; - const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - - arrayFormulaRangeMatrix.realDeleteValue(r, c); - arrayFormulaRangeMatrix.realDeleteValue(newStartRow, newStartColumn); - }); - } else { - cellMatrix.forValue((r, c, cell) => { - arrayFormulaRangeMatrix.realDeleteValue(r, c); - }); - } + cellMatrix.forValue((r, c, cell) => { + arrayFormulaRangeMatrix.realDeleteValue(r, c); + }); } updateArrayFormulaCellData( unitId: string, sheetId: string, - cellValue: IObjectMatrixPrimitiveType>, - rangeList?: IRangeChange[], isReverse?: boolean + cellValue: IObjectMatrixPrimitiveType> ) { // remove the array formula range when cell value is null @@ -495,23 +463,9 @@ export class FormulaDataModel extends Disposable { const cellMatrix = new ObjectMatrix(cellValue); - if (rangeList) { - if (isReverse) { - rangeList.reverse(); - } - - rangeList.forEach(({ oldCell, newCell }) => { - const { startRow: r, startColumn: c } = oldCell; - const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - - clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, r, c); - clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, newStartRow, newStartColumn); - }); - } else { - cellMatrix.forValue((r, c, cell) => { - clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, r, c); - }); - } + cellMatrix.forValue((r, c, cell) => { + clearArrayFormulaCellDataByCell(arrayFormulaRangeMatrix, arrayFormulaCellDataMatrix, r, c); + }); } updateNumfmtData( diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 894c538805f..e638dfc3864 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -113,7 +113,7 @@ import { Inject, Injector } from '@wendellhu/redi'; import type { IRefRangeWithPosition } from './utils/offset-formula-data'; import { removeFormulaData } from './utils/offset-formula-data'; import { handleRedoUndoMoveRange } from './utils/redo-undo-formula-data'; -import { refRangeFormula } from './utils/ref-range-formula'; +import { getFormulaReferenceMoveUndoRedo } from './utils/ref-range-formula'; interface IUnitRangeWithOffset extends IUnitRange { refOffsetX: number; @@ -162,7 +162,7 @@ enum OriginRangeEdgeType { * 2. Use refRange to offset the formula position and return undo/redo data to setRangeValues mutation - Redo data: Delete the old value at the old position on the match, and add the new value at the new position (the new value first checks whether the old position has offset content, if so, use the new offset content, if not, take the old value) - - Undo data: the old position on the match saves the old value, and the new position delete value + - Undo data: the old position on the match saves the old value, and the new position delete value. Using undos when undoing will operate the data after the offset position. 3. onCommandExecuted, before formula calculation, use the setRangeValues information to delete the old formulaData, ArrayFormula and ArrayFormulaCellData, and send the worker (complementary setRangeValues after collaborative conflicts, normal operation triggers formula update, undo/redo are captured and processed here) */ @@ -252,22 +252,21 @@ export class UpdateFormulaController extends Disposable { } private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams) { - const { subUnitId: sheetId, unitId, cellValue, options } = params; - const { rangeList, isReverse } = options || {}; + const { subUnitId: sheetId, unitId, cellValue } = params; if (cellValue == null) { return; } - const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue, rangeList, isReverse); + const newSheetFormulaData = this._formulaDataModel.updateFormulaData(unitId, sheetId, cellValue); const newFormulaData = { [unitId]: { [sheetId]: newSheetFormulaData, }, }; - this._formulaDataModel.updateArrayFormulaCellData(unitId, sheetId, cellValue, rangeList, isReverse); - this._formulaDataModel.updateArrayFormulaRange(unitId, sheetId, cellValue, rangeList, isReverse); + this._formulaDataModel.updateArrayFormulaCellData(unitId, sheetId, cellValue); + this._formulaDataModel.updateArrayFormulaRange(unitId, sheetId, cellValue); this._formulaDataModel.updateNumfmtData(unitId, sheetId, cellValue); // TODO: move model to snapshot this._commandService.executeCommand( @@ -413,41 +412,11 @@ export class UpdateFormulaController extends Disposable { result ); - const { redoFormulaData, undoFormulaData, rangeList, isReverse } = refRangeFormula(oldFormulaData, newFormulaData, result); - - // console.info('redoFormulaData==', redoFormulaData); - // console.info('undoFormulaData==', undoFormulaData); - - const { sheetId: subUnitId, unitId } = result; - const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { - subUnitId, - unitId, - cellValue: redoFormulaData, - }; - - const redoMutation = { - id: SetRangeValuesMutation.id, - params: redoSetRangeValuesMutationParams, - }; - - const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { - subUnitId, - unitId, - cellValue: undoFormulaData, - options: { - rangeList, - isReverse, - }, - }; - - const undoMutation = { - id: SetRangeValuesMutation.id, - params: undoSetRangeValuesMutationParams, - }; + const { undos, redos } = getFormulaReferenceMoveUndoRedo(oldFormulaData, newFormulaData, result); return { - undos: [undoMutation], - redos: [redoMutation], + undos, + redos, }; // const workbook = this._currentUniverService.getCurrentUniverSheetInstance(); diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 7ab1cdc8adf..6525f962d74 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -17,7 +17,8 @@ import type { ICellData, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix, Tools } from '@univerjs/core'; import type { IFormulaData, IFormulaDataItem, IRangeChange } from '@univerjs/engine-formula'; -import { EffectRefRangId, handleDeleteRangeMoveLeft, handleDeleteRangeMoveUp, handleInsertCol, handleInsertRangeMoveDown, handleInsertRangeMoveRight, handleInsertRow, handleIRemoveCol, handleIRemoveRow, handleMoveCols, handleMoveRange, handleMoveRows, runRefRangeMutations } from '@univerjs/sheets'; +import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; +import { EffectRefRangId, handleDeleteRangeMoveLeft, handleDeleteRangeMoveUp, handleInsertCol, handleInsertRangeMoveDown, handleInsertRangeMoveRight, handleInsertRow, handleIRemoveCol, handleIRemoveRow, handleMoveCols, handleMoveRange, handleMoveRows, runRefRangeMutations, SetRangeValuesMutation } from '@univerjs/sheets'; import { checkFormulaDataNull } from './offset-formula-data'; export enum FormulaReferenceMoveType { @@ -46,6 +47,51 @@ export interface IFormulaReferenceMoveParam { sheetName?: string; } +export function getFormulaReferenceMoveUndoRedo(oldFormulaData: IFormulaData, + newFormulaData: IFormulaData, + formulaReferenceMoveParam: IFormulaReferenceMoveParam) { + const { type, sheetId: subUnitId, unitId, range, from, to } = formulaReferenceMoveParam; + + if (type === FormulaReferenceMoveType.SetName) { + // TODO + return; + } else if (type === FormulaReferenceMoveType.RemoveSheet) { + // TODO + return; + } + + const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, formulaReferenceMoveParam); + + // console.info('redoFormulaData==', redoFormulaData); + // console.info('undoFormulaData==', undoFormulaData); + + const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: redoFormulaData, + }; + + const redoMutation = { + id: SetRangeValuesMutation.id, + params: redoSetRangeValuesMutationParams, + }; + + const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: undoFormulaData, + }; + + const undoMutation = { + id: SetRangeValuesMutation.id, + params: undoSetRangeValuesMutationParams, + }; + + return { + undos: [undoMutation], + redos: [redoMutation], + }; +} /** * For different Command operations, it may be necessary to perform traversal in reverse or in forward order, so first determine the type of Command and then perform traversal. * @param oldFormulaData @@ -58,9 +104,6 @@ export function refRangeFormula(oldFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { let redoFormulaData: IObjectMatrixPrimitiveType> = {}; let undoFormulaData: IObjectMatrixPrimitiveType> = {}; - // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. - const rangeList: IRangeChange[] = []; - let isReverse = false; const { type, unitId, sheetId, range, from, to } = formulaReferenceMoveParam; @@ -68,8 +111,6 @@ export function refRangeFormula(oldFormulaData: IFormulaData, return { redoFormulaData, undoFormulaData, - rangeList, - isReverse, }; } @@ -79,6 +120,10 @@ export function refRangeFormula(oldFormulaData: IFormulaData, const oldFormulaMatrix = new ObjectMatrix(currentOldFormulaData); const newFormulaMatrix = new ObjectMatrix(currentNewFormulaData); + // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. + const rangeList: IRangeChange[] = []; + let isReverse = false; + oldFormulaMatrix.forValue((row, column, cell) => { const formulaString = cell?.f || ''; const formulaId = cell?.si || ''; @@ -188,8 +233,6 @@ export function refRangeFormula(oldFormulaData: IFormulaData, return { redoFormulaData, undoFormulaData, - rangeList, - isReverse, }; } @@ -368,11 +411,8 @@ function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM const oldFormula = oldFormulaMatrix.getValue(oldStartRow, oldStartColumn); const oldValue = formulaDataItemToCellData(oldFormula); - // When undoing, setRangeValues is executed before the position changes, so we must store the old value in the new position so that the old value can be restored to the correct position after the snapshot position changes. - - // For formulaData, it should be necessary to restore the old value at the old position and delete the value at the new position, which is the opposite of the situation in undo, so we set a position exchange information in the mutation information of undo, and identify and process it in the update logic of formulaData. - undoFormulaData.setValue(oldStartRow, oldStartColumn, null); - undoFormulaData.setValue(newStartRow, newStartColumn, oldValue); + undoFormulaData.setValue(oldStartRow, oldStartColumn, oldValue); + undoFormulaData.setValue(newStartRow, newStartColumn, null); }); return undoFormulaData.clone(); @@ -409,3 +449,8 @@ export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): IC return cellData; } + +// export function handleSetNameFormula(oldFormulaData: IFormulaData, +// newFormulaData: IFormulaData { + +// } diff --git a/packages/sheets/src/commands/commands/remove-sheet.command.ts b/packages/sheets/src/commands/commands/remove-sheet.command.ts index 98b4e7479ff..5c840df7c9e 100644 --- a/packages/sheets/src/commands/commands/remove-sheet.command.ts +++ b/packages/sheets/src/commands/commands/remove-sheet.command.ts @@ -89,8 +89,8 @@ export const RemoveSheetCommand: ICommand = { id: RemoveSheetCommand.id, params: { unitId, subUnitId }, }); - const redos = [{ id: RemoveSheetMutation.id, params: RemoveSheetMutationParams }, ...intercepted.redos]; - const undos = [...intercepted.undos, { id: InsertSheetMutation.id, params: InsertSheetMutationParams }]; + const redos = [...(intercepted.preRedos ?? []), { id: RemoveSheetMutation.id, params: RemoveSheetMutationParams }, ...intercepted.redos]; + const undos = [...(intercepted.preUndos ?? []), { id: InsertSheetMutation.id, params: InsertSheetMutationParams }, ...intercepted.undos]; const result = sequenceExecute(redos, commandService); if (result) { diff --git a/packages/sheets/src/commands/commands/set-worksheet-name.command.ts b/packages/sheets/src/commands/commands/set-worksheet-name.command.ts index db426e8e58b..237e55ab1c7 100644 --- a/packages/sheets/src/commands/commands/set-worksheet-name.command.ts +++ b/packages/sheets/src/commands/commands/set-worksheet-name.command.ts @@ -66,8 +66,8 @@ export const SetWorksheetNameCommand: ICommand = { params, }); - const redos = [{ id: SetWorksheetNameMutation.id, params: redoMutationParams }, ...interceptorCommands.redos]; - const undos = [...interceptorCommands.undos, { id: SetWorksheetNameMutation.id, params: undoMutationParams }]; + const redos = [...(interceptorCommands.preRedos ?? []), { id: SetWorksheetNameMutation.id, params: redoMutationParams }, ...interceptorCommands.redos]; + const undos = [...(interceptorCommands.preUndos ?? []), { id: SetWorksheetNameMutation.id, params: undoMutationParams }, ...interceptorCommands.undos]; const result = await sequenceExecute(redos, commandService).result; if (result) { From b765b7dcd9bfd77bd8bd3d3a0ef7da21ec7f50fa Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Sun, 7 Apr 2024 07:41:51 +0800 Subject: [PATCH 07/13] fix(formula): update formula model after insert sheet and remove sheet --- .../src/controller/calculate.controller.ts | 3 +- .../src/models/formula-data.model.ts | 40 +++- .../controllers/update-formula.controller.ts | 224 +++--------------- .../utils/__tests__/ref-range-formula.spec.ts | 11 + .../controllers/utils/offset-formula-data.ts | 5 + .../controllers/utils/ref-range-formula.ts | 140 +++++++++-- 6 files changed, 192 insertions(+), 231 deletions(-) diff --git a/packages/engine-formula/src/controller/calculate.controller.ts b/packages/engine-formula/src/controller/calculate.controller.ts index fba94844083..4278fb5dba1 100644 --- a/packages/engine-formula/src/controller/calculate.controller.ts +++ b/packages/engine-formula/src/controller/calculate.controller.ts @@ -85,6 +85,7 @@ export class CalculateController extends Disposable { } const { arrayFormulaRange, arrayFormulaCellData } = params; + // TODO@Dushusir: Merge the array formula data into the formulaDataModel this._formulaDataModel.setArrayFormulaRange(arrayFormulaRange); this._formulaDataModel.setArrayFormulaCellData(arrayFormulaCellData); } @@ -116,8 +117,6 @@ export class CalculateController extends Disposable { const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); - // Synchronous to the main thread - // this._commandService.executeCommand(SetFormulaDataMutation.id, { formulaData }); this._calculateFormulaService.execute({ formulaData, arrayFormulaCellData, diff --git a/packages/engine-formula/src/models/formula-data.model.ts b/packages/engine-formula/src/models/formula-data.model.ts index 1e1ea832cdc..43df28e9341 100644 --- a/packages/engine-formula/src/models/formula-data.model.ts +++ b/packages/engine-formula/src/models/formula-data.model.ts @@ -245,8 +245,13 @@ export class FormulaDataModel extends Disposable { Object.keys(formulaData).forEach((unitId) => { const sheetData = formulaData[unitId]; - if (sheetData == null) { - return true; + if (sheetData === undefined) { + return; + } + + if (sheetData === null) { + delete this._formulaData[unitId]; + return; } if (!this._formulaData[unitId]) { @@ -254,7 +259,18 @@ export class FormulaDataModel extends Disposable { } Object.keys(sheetData).forEach((sheetId) => { - const sheetFormula = new ObjectMatrix(sheetData[sheetId]); + const currentSheetData = sheetData[sheetId]; + + if (currentSheetData === undefined) { + return; + } + + if (currentSheetData === null) { + delete this._formulaData[unitId]?.[sheetId]; + return; + } + + const sheetFormula = new ObjectMatrix(currentSheetData); const formulaMatrix = new ObjectMatrix(this._formulaData[unitId]?.[sheetId]); sheetFormula.forValue((r, c, v) => { @@ -265,9 +281,7 @@ export class FormulaDataModel extends Disposable { } }); - if (this._formulaData[unitId]) { - this._formulaData[unitId]![sheetId] = formulaMatrix.getData(); - } + this._formulaData[unitId]![sheetId] = formulaMatrix.clone(); }); }); } @@ -618,7 +632,17 @@ export function initSheetFormulaData( } }); - if (formulaData[unitId]) { - formulaData[unitId]![sheetId] = sheetFormulaDataMatrix.getData(); + if (!formulaData[unitId]) { + formulaData[unitId] = {}; } + + const newSheetFormulaData = sheetFormulaDataMatrix.clone(); + + formulaData[unitId]![sheetId] = newSheetFormulaData; + + return { + [unitId]: { + [sheetId]: newSheetFormulaData, + }, + }; } diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index e638dfc3864..11fd8be8d2c 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -15,10 +15,8 @@ */ import type { - ICellData, ICommandInfo, IExecutionOptions, - IMutationCommonParams, IRange, IUnitRange, Nullable, @@ -27,16 +25,13 @@ import { Direction, Disposable, ICommandService, - isFormulaString, IUniverInstanceService, LifecycleStages, ObjectMatrix, OnLifecycle, RANGE_TYPE, Rectangle, - RedoCommand, Tools, - UndoCommand, } from '@univerjs/core'; import type { IFormulaData, IFormulaDataItem, ISequenceNode, IUnitSheetNameMap } from '@univerjs/engine-formula'; import { @@ -60,7 +55,6 @@ import type { IInsertSheetMutationParams, IMoveColsCommandParams, IMoveRangeCommandParams, - IMoveRangeMutationParams, IMoveRowsCommandParams, InsertRangeMoveDownCommandParams, InsertRangeMoveRightCommandParams, @@ -93,7 +87,6 @@ import { InsertSheetMutation, MoveColsCommand, MoveRangeCommand, - MoveRangeMutation, MoveRowsCommand, RemoveColCommand, RemoveRowCommand, @@ -103,7 +96,6 @@ import { SelectionManagerService, SetBorderCommand, SetRangeValuesMutation, - SetRangeValuesUndoMutationFactory, SetStyleCommand, SetWorksheetNameCommand, SheetInterceptorService, @@ -112,7 +104,6 @@ import { Inject, Injector } from '@wendellhu/redi'; import type { IRefRangeWithPosition } from './utils/offset-formula-data'; import { removeFormulaData } from './utils/offset-formula-data'; -import { handleRedoUndoMoveRange } from './utils/redo-undo-formula-data'; import { getFormulaReferenceMoveUndoRedo } from './utils/ref-range-formula'; interface IUnitRangeWithOffset extends IUnitRange { @@ -213,44 +204,11 @@ export class UpdateFormulaController extends Disposable { this._handleRemoveSheetMutation(command.params as IRemoveSheetMutationParams); } else if (command.id === InsertSheetMutation.id) { this._handleInsertSheetMutation(command.params as IInsertSheetMutationParams); - } else if ( - (command.params as IMutationCommonParams)?.trigger === UndoCommand.id || - (command.params as IMutationCommonParams)?.trigger === RedoCommand.id - ) { - // this._handleRedoUndo(command); // TODO: handle in set range values } }) ); } - private _handleRedoUndo(command: ICommandInfo) { - const { id } = command; - const formulaData = this._formulaDataModel.getFormulaData(); - const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange(); - const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); - - switch (id) { - case MoveRangeMutation.id: - handleRedoUndoMoveRange( - command as ICommandInfo, - formulaData, - arrayFormulaRange, - arrayFormulaCellData - ); - break; - - // TODO:@Dushusir handle other mutations - } - - this._commandService.executeCommand(SetFormulaDataMutation.id, { - formulaData, - }); - this._commandService.executeCommand(SetArrayFormulaDataMutation.id, { - arrayFormulaRange, - arrayFormulaCellData, - }); - } - private _handleSetRangeValuesMutation(params: ISetRangeValuesMutationParams) { const { subUnitId: sheetId, unitId, cellValue } = params; @@ -306,33 +264,38 @@ export class UpdateFormulaController extends Disposable { const { subUnitId: sheetId, unitId } = params; const formulaData = this._formulaDataModel.getFormulaData(); - removeFormulaData(formulaData, unitId, sheetId); + const newFormulaData = removeFormulaData(formulaData, unitId, sheetId); const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange(); - removeFormulaData(arrayFormulaRange, unitId, sheetId); + const newArrayFormulaRange = removeFormulaData(arrayFormulaRange, unitId, sheetId); const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); - removeFormulaData(arrayFormulaCellData, unitId, sheetId); + const newArrayFormulaCellData = removeFormulaData(arrayFormulaCellData, unitId, sheetId); + + if (newFormulaData) { + this._commandService.executeCommand( + SetFormulaDataMutation.id, + { + formulaData: newFormulaData, + }, + { + onlyLocal: true, + } + ); + } - this._commandService.executeCommand( - SetFormulaDataMutation.id, - { - formulaData, - }, - { - onlyLocal: true, - } - ); - this._commandService.executeCommand( - SetArrayFormulaDataMutation.id, - { - arrayFormulaRange, - arrayFormulaCellData, - }, - { - onlyLocal: true, - } - ); + if (newArrayFormulaRange && newArrayFormulaCellData) { + this._commandService.executeCommand( + SetArrayFormulaDataMutation.id, + { + arrayFormulaRange, + arrayFormulaCellData, + }, + { + onlyLocal: true, + } + ); + } } private _handleInsertSheetMutation(params: IInsertSheetMutationParams) { @@ -341,12 +304,12 @@ export class UpdateFormulaController extends Disposable { const formulaData = this._formulaDataModel.getFormulaData(); const { id: sheetId, cellData } = sheet; const cellMatrix = new ObjectMatrix(cellData); - initSheetFormulaData(formulaData, unitId, sheetId, cellMatrix); + const newFormulaData = initSheetFormulaData(formulaData, unitId, sheetId, cellMatrix); this._commandService.executeCommand( SetFormulaDataMutation.id, { - formulaData, + formulaData: newFormulaData, }, { onlyLocal: true, @@ -418,65 +381,6 @@ export class UpdateFormulaController extends Disposable { undos, redos, }; - - // const workbook = this._currentUniverService.getCurrentUniverSheetInstance(); - // const unitId = workbook.getUnitId(); - // const sheetId = workbook.getActiveSheet().getSheetId(); - // const selections = this._selectionManagerService.getSelections(); - - // // offset arrayFormula - // const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange(); - // const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData(); - - // // First use arrayFormulaCellData and the original arrayFormulaRange to calculate the offset of arrayFormulaCellData, otherwise the offset of arrayFormulaRange will be inaccurate. - // const offsetArrayFormulaCellData = offsetFormula( - // arrayFormulaCellData, - // command, - // unitId, - // sheetId, - // selections, - // arrayFormulaRange, - // refRanges - // ); - // let offsetArrayFormulaRange = offsetFormula( - // arrayFormulaRange, - // command, - // unitId, - // sheetId, - // selections, - // arrayFormulaRange - // ); - // offsetArrayFormulaRange = offsetArrayFormula(offsetArrayFormulaRange, command, unitId, sheetId); - - // // Synchronous to the worker thread - // this._commandService.executeCommand( - // SetArrayFormulaDataMutation.id, - // { - // arrayFormulaRange: offsetArrayFormulaRange, - // arrayFormulaCellData: offsetArrayFormulaCellData, - // }, - // { - // onlyLocal: true, - // } - // ); - - // // offset formulaData - // oldFormulaData = offsetFormula(oldFormulaData, command, unitId, sheetId, selections); - // const offsetFormulaData = offsetFormula(formulaData, command, unitId, sheetId, selections); - - // // TODO@Dushusir: Here we take the redos incremental data, - // // Synchronously to the worker thread, and update the dependency cache. - // this._commandService.executeCommand(SetFormulaDataMutation.id, { - // formulaData: this._formulaDataModel.getFormulaData(), - // }); - - // // offset numfmtItemMap - // const offsetNumfmtItemMap = offsetFormula(oldNumfmtItemMap, command, unitId, sheetId, selections); - // this._commandService.executeCommand(SetNumfmtFormulaDataMutation.id, { - // numfmtItemMap: offsetNumfmtItemMap, - // }); - - // return this._getUpdateFormulaMutations(oldFormulaData, offsetFormulaData); } return { @@ -726,76 +630,6 @@ export class UpdateFormulaController extends Disposable { }; } - private _getUpdateFormulaMutations(oldFormulaData: IFormulaData, formulaData: IFormulaData) { - const redos = []; - const undos = []; - const accessor = { - get: this._injector.get.bind(this._injector), - }; - - const formulaDataKeys = Object.keys(formulaData); - - for (const unitId of formulaDataKeys) { - const sheetData = formulaData[unitId]; - - if (sheetData == null) { - continue; - } - - const sheetDataKeys = Object.keys(sheetData); - - for (const subUnitId of sheetDataKeys) { - const value = oldFormulaData[unitId]?.[subUnitId]; - - const oldFormulaMatrix = new ObjectMatrix(value); - const formulaMatrix = new ObjectMatrix(sheetData[subUnitId]); - const cellMatrix = new ObjectMatrix(); - - formulaMatrix.forValue((r, c, formulaItem) => { - const formulaString = formulaItem?.f || ''; - const oldFormulaString = oldFormulaMatrix.getValue(r, c)?.f || ''; - - if (isFormulaString(formulaString)) { - // formula with formula id - if (isFormulaString(oldFormulaString) && formulaString !== oldFormulaString) { - cellMatrix.setValue(r, c, { f: formulaString }); - } else { - // formula with only id - cellMatrix.setValue(r, c, { f: formulaString, si: null }); - } - } - }); - - const cellValue = cellMatrix.getData(); - if (Tools.isEmptyObject(cellValue)) continue; - - const setRangeValuesMutationParams: ISetRangeValuesMutationParams = { - subUnitId, - unitId, - cellValue, - }; - - redos.push({ - id: SetRangeValuesMutation.id, - params: setRangeValuesMutationParams, - }); - - const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = - SetRangeValuesUndoMutationFactory(accessor, setRangeValuesMutationParams); - - undos.push({ - id: SetRangeValuesMutation.id, - params: undoSetRangeValuesMutationParams, - }); - } - } - - return { - redos, - undos, - }; - } - private _getFormulaReferenceMoveInfo( formulaData: IFormulaData, unitSheetNameMap: IUnitSheetNameMap, diff --git a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts index 1f85dded65c..9e1c138ba96 100644 --- a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts +++ b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts @@ -81,6 +81,17 @@ describe('Ref range formula test', () => { expect(result).toStrictEqual({ si: 'id1', }); + + formulaDataItem = { + f: '', + si: '', + x: 0, + y: 1, + }; + + result = formulaDataItemToCellData(formulaDataItem); + + expect(result).toBeNull(); }); }); }); diff --git a/packages/sheets-formula/src/controllers/utils/offset-formula-data.ts b/packages/sheets-formula/src/controllers/utils/offset-formula-data.ts index 0c68f403580..c691ddcb7fb 100644 --- a/packages/sheets-formula/src/controllers/utils/offset-formula-data.ts +++ b/packages/sheets-formula/src/controllers/utils/offset-formula-data.ts @@ -397,6 +397,11 @@ export function checkFormulaDataNull(formulaData: IFormulaDataGenerics, un export function removeFormulaData(formulaData: IFormulaDataGenerics, unitId: string, sheetId: string) { if (formulaData && formulaData[unitId] && formulaData[unitId]?.[sheetId]) { delete formulaData[unitId]![sheetId]; + return { + [unitId]: { + [sheetId]: null, + }, + }; } } export function removeValueFormulaArray(formulaRange: IRange, formulaMatrix: ObjectMatrix) { diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 6525f962d74..fca41a93584 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ICellData, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; +import type { ICellData, IMutationInfo, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; import { cellToRange, Direction, isFormulaId, isFormulaString, ObjectMatrix, Tools } from '@univerjs/core'; import type { IFormulaData, IFormulaDataItem, IRangeChange } from '@univerjs/engine-formula'; import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; @@ -50,20 +50,101 @@ export interface IFormulaReferenceMoveParam { export function getFormulaReferenceMoveUndoRedo(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - const { type, sheetId: subUnitId, unitId, range, from, to } = formulaReferenceMoveParam; + const { type } = formulaReferenceMoveParam; - if (type === FormulaReferenceMoveType.SetName) { - // TODO - return; - } else if (type === FormulaReferenceMoveType.RemoveSheet) { - // TODO - return; + if (type === FormulaReferenceMoveType.SetName || type === FormulaReferenceMoveType.RemoveSheet) { + return getFormulaReferenceSheet(oldFormulaData, newFormulaData); + } else { + return getFormulaReferenceRange(oldFormulaData, newFormulaData, formulaReferenceMoveParam); } +} + +export function getFormulaReferenceSheet(oldFormulaData: IFormulaData, + newFormulaData: IFormulaData) { + const undos: IMutationInfo[] = []; + const redos: IMutationInfo[] = []; + + Object.keys(newFormulaData).forEach((unitId) => { + const newSheetData = newFormulaData[unitId]; + const oldSheetData = oldFormulaData[unitId]; + + if (newSheetData == null) { + return true; + } + + if (oldSheetData == null) { + return true; + } + + Object.keys(newSheetData).forEach((subUnitId) => { + const newSheetFormula = new ObjectMatrix(newSheetData[subUnitId]); + const oldSheetFormula = new ObjectMatrix(oldSheetData[subUnitId]); + const redoFormulaMatrix = new ObjectMatrix(); + const undoFormulaMatrix = new ObjectMatrix(); + + newSheetFormula.forValue((r, c, cell) => { + const newValue = formulaDataItemToCellData(cell); + + if (newValue === null) { + return; + } + redoFormulaMatrix.setValue(r, c, newValue); + undoFormulaMatrix.setValue(r, c, oldSheetFormula.getValue(r, c)); + }); + + if (redoFormulaMatrix.getSizeOf() === 0) { + return; + } + + const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: redoFormulaMatrix.clone(), + }; + + const redoMutation = { + id: SetRangeValuesMutation.id, + params: redoSetRangeValuesMutationParams, + }; + + redos.push(redoMutation); + + const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: undoFormulaMatrix.clone(), + }; + + const undoMutation = { + id: SetRangeValuesMutation.id, + params: undoSetRangeValuesMutationParams, + }; + + undos.push(undoMutation); + }); + }); + + return { + undos, + redos, + }; +} + +export function getFormulaReferenceMoveRemoveSheet(oldFormulaData: IFormulaData, + newFormulaData: IFormulaData, + formulaReferenceMoveParam: IFormulaReferenceMoveParam) { + +} + +export function getFormulaReferenceRange(oldFormulaData: IFormulaData, + newFormulaData: IFormulaData, + formulaReferenceMoveParam: IFormulaReferenceMoveParam) { + const { type, sheetId: subUnitId, unitId, range, from, to } = formulaReferenceMoveParam; const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, formulaReferenceMoveParam); - // console.info('redoFormulaData==', redoFormulaData); - // console.info('undoFormulaData==', undoFormulaData); + // console.info('redoFormulaData==', redoFormulaData); + // console.info('undoFormulaData==', undoFormulaData); const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { subUnitId, @@ -92,6 +173,7 @@ export function getFormulaReferenceMoveUndoRedo(oldFormulaData: IFormulaData, redos: [redoMutation], }; } + /** * For different Command operations, it may be necessary to perform traversal in reverse or in forward order, so first determine the type of Command and then perform traversal. * @param oldFormulaData @@ -125,14 +207,8 @@ export function refRangeFormula(oldFormulaData: IFormulaData, let isReverse = false; oldFormulaMatrix.forValue((row, column, cell) => { - const formulaString = cell?.f || ''; - const formulaId = cell?.si || ''; - - const checkFormulaString = isFormulaString(formulaString); - const checkFormulaId = isFormulaId(formulaId); - // Offset is only needed when there is a formula - if (!checkFormulaString && !checkFormulaId) { + if (!isFormulaDataItem(cell)) { return; } @@ -140,12 +216,6 @@ export function refRangeFormula(oldFormulaData: IFormulaData, let newCell = null; switch (type) { - case FormulaReferenceMoveType.SetName: - // TODO - break; - case FormulaReferenceMoveType.RemoveSheet: - // TODO - break; case FormulaReferenceMoveType.MoveRange: if (from == null || to == null) { return; @@ -228,7 +298,7 @@ export function refRangeFormula(oldFormulaData: IFormulaData, }); redoFormulaData = getRedoFormulaData(rangeList, oldFormulaMatrix, newFormulaMatrix); - undoFormulaData = getUndoFormulaData(rangeList, oldFormulaMatrix, newFormulaMatrix); + undoFormulaData = getUndoFormulaData(rangeList, oldFormulaMatrix); return { redoFormulaData, @@ -399,7 +469,7 @@ function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM * @param oldFormulaData * @param newFormulaData */ -function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix, newFormulaMatrix: ObjectMatrix) { +function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectMatrix) { const undoFormulaData = new ObjectMatrix({}); rangeList.forEach((item) => { @@ -432,11 +502,15 @@ function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM * │ =SUM(1) │ id1 │ 0 │ 1 │ │ id1 │ * └──────────────────┴─────┴───┴───┴───────────┴─────┘ */ -export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): ICellData { +export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): ICellData | null { const { f, si, x = 0, y = 0 } = formulaDataItem; const checkFormulaString = isFormulaString(f); const checkFormulaId = isFormulaId(si); + if (!checkFormulaString && !checkFormulaId) { + return null; + } + const cellData: ICellData = {}; if (checkFormulaId) { @@ -454,3 +528,17 @@ export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): IC // newFormulaData: IFormulaData { // } + +export function isFormulaDataItem(cell: IFormulaDataItem) { + const formulaString = cell?.f || ''; + const formulaId = cell?.si || ''; + + const checkFormulaString = isFormulaString(formulaString); + const checkFormulaId = isFormulaId(formulaId); + + if (checkFormulaString || checkFormulaId) { + return true; + } + + return false; +} From 31da2e41931d633e879a24cecdc84dfcc8f8e570 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Sun, 7 Apr 2024 17:57:31 +0800 Subject: [PATCH 08/13] fix(formula): test update formula --- .../update-formula.controller.spec.ts | 283 ++++++++++++++++-- .../controllers/update-formula.controller.ts | 1 - .../utils/__tests__/ref-range-formula.spec.ts | 9 +- .../controllers/utils/ref-range-formula.ts | 5 - 4 files changed, 269 insertions(+), 29 deletions(-) diff --git a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts index d5d82ed91de..8022e29856f 100644 --- a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts +++ b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts @@ -15,9 +15,9 @@ */ import type { ICellData, IWorkbookData, Nullable, Univer } from '@univerjs/core'; -import { cellToRange, Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE } from '@univerjs/core'; -import type { IInsertColCommandParams } from '@univerjs/sheets'; -import { InsertColCommand, MoveRowsCommand, NORMAL_SELECTION_PLUGIN_NAME, SelectionManagerService } from '@univerjs/sheets'; +import { Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE } from '@univerjs/core'; +import type { IDeleteRangeMoveLeftCommandParams, IDeleteRangeMoveUpCommandParams, IInsertColCommandParams, IInsertRowCommandParams, IMoveColsCommandParams, IMoveRangeCommandParams, IMoveRowsCommandParams, InsertRangeMoveDownCommandParams, InsertRangeMoveRightCommandParams, IRemoveRowColCommandParams, IRemoveSheetCommandParams, ISetWorksheetNameCommandParams } from '@univerjs/sheets'; +import { DeleteRangeMoveLeftCommand, DeleteRangeMoveUpCommand, InsertColCommand, InsertColMutation, InsertRangeMoveDownCommand, InsertRangeMoveRightCommand, InsertRowCommand, InsertRowMutation, MoveColsCommand, MoveColsMutation, MoveRangeCommand, MoveRangeMutation, MoveRowsCommand, MoveRowsMutation, NORMAL_SELECTION_PLUGIN_NAME, RemoveColCommand, RemoveColMutation, RemoveRowCommand, RemoveRowMutation, RemoveSheetCommand, RemoveSheetMutation, SelectionManagerService, SetRangeValuesMutation, SetSelectionsOperation, SetWorksheetNameCommand, SetWorksheetNameMutation } from '@univerjs/sheets'; import type { Injector } from '@wendellhu/redi'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; @@ -32,17 +32,29 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ sheet1: { id: 'sheet1', cellData: { - 0: { - 3: { - f: '=A1:C1', + 2: { + 2: { + f: '=A1:B2', }, }, + 4: { + 2: { + f: '=Sheet2!A1:B2', + }, + }, + }, + name: 'Sheet1', + }, + sheet2: { + id: 'sheet2', + cellData: { }, + name: 'Sheet2', }, }, locale: LocaleType.ZH_CN, name: '', - sheetOrder: [], + sheetOrder: ['sheet1', 'sheet2'], styles: {}, }); @@ -72,8 +84,39 @@ describe('Test insert function operation', () => { get = testBed.get; commandService = get(ICommandService); + commandService.registerCommand(MoveRangeCommand); + commandService.registerCommand(MoveRangeMutation); + commandService.registerCommand(MoveRowsCommand); + commandService.registerCommand(MoveRowsMutation); + + commandService.registerCommand(MoveColsCommand); + commandService.registerCommand(MoveColsMutation); + + commandService.registerCommand(InsertRowCommand); + commandService.registerCommand(InsertRowMutation); + commandService.registerCommand(InsertColCommand); + commandService.registerCommand(InsertColMutation); + + commandService.registerCommand(RemoveRowCommand); + commandService.registerCommand(RemoveRowMutation); + + commandService.registerCommand(RemoveColCommand); + commandService.registerCommand(RemoveColMutation); + + commandService.registerCommand(DeleteRangeMoveLeftCommand); + commandService.registerCommand(DeleteRangeMoveUpCommand); + commandService.registerCommand(InsertRangeMoveDownCommand); + commandService.registerCommand(InsertRangeMoveRightCommand); + + commandService.registerCommand(SetWorksheetNameCommand); + commandService.registerCommand(SetWorksheetNameMutation); + commandService.registerCommand(RemoveSheetCommand); + commandService.registerCommand(RemoveSheetMutation); + + commandService.registerCommand(SetSelectionsOperation); + commandService.registerCommand(SetRangeValuesMutation); commandService.registerCommand(SetFormulaDataMutation); commandService.registerCommand(SetArrayFormulaDataMutation); commandService.registerCommand(SetNumfmtFormulaDataMutation); @@ -108,7 +151,30 @@ describe('Test insert function operation', () => { describe('update formula', () => { describe('correct situations', () => { - it('move rows update formula', async () => { + it('Move range', async () => { + const params: IMoveRangeCommandParams = { + fromRange: { + startRow: 2, + startColumn: 2, + endRow: 2, + endColumn: 2, + rangeType: 0, + }, + toRange: { + startRow: 2, + startColumn: 3, + endRow: 2, + endColumn: 3, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 2, 2, 3); + expect(oldValue).toStrictEqual([[{}, { f: '=A1:B2' }]]); + }); + + it('Move rows', async () => { const selectionManager = get(SelectionManagerService); selectionManager.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, @@ -119,20 +185,35 @@ describe('Test insert function operation', () => { // A1 selectionManager.add([ { - range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0, rangeType: RANGE_TYPE.NORMAL }, + range: { startRow: 1, startColumn: 0, endRow: 1, endColumn: 0, rangeType: RANGE_TYPE.ROW }, primary: null, style: null, }, ]); - const params = { - fromRange: cellToRange(0, 1), - toRange: cellToRange(1, 1), + const params: IMoveRowsCommandParams = { + fromRange: { + startRow: 1, + startColumn: 0, + endRow: 1, + endColumn: 19, + rangeType: 1, + }, + toRange: { + startRow: 4, + startColumn: 0, + endRow: 4, + endColumn: 19, + rangeType: 1, + }, }; - await commandService.executeCommand(MoveRowsCommand.id, params); + expect(await commandService.executeCommand(MoveRowsCommand.id, params)).toBeTruthy(); + const oldValue = getValues(1, 2, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:B4' }], [{ }]]); }); - it('Insert column', async () => { + + it('Move columns', async () => { const selectionManager = get(SelectionManagerService); selectionManager.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, @@ -143,26 +224,184 @@ describe('Test insert function operation', () => { // A1 selectionManager.add([ { - range: { startRow: 0, startColumn: 1, endRow: 0, endColumn: 1, rangeType: RANGE_TYPE.NORMAL }, + range: { startRow: 0, startColumn: 1, endRow: 0, endColumn: 1, rangeType: RANGE_TYPE.COLUMN }, primary: null, style: null, }, ]); + const params: IMoveColsCommandParams = { + fromRange: { + startRow: 0, + startColumn: 1, + endRow: 999, + endColumn: 1, + rangeType: 2, + }, + toRange: { + startRow: 0, + startColumn: 4, + endRow: 999, + endColumn: 4, + rangeType: 2, + }, + }; + + expect(await commandService.executeCommand(MoveColsCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 1, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:D2' }, { }]]); + }); + + it('Insert row', async () => { + const params: IInsertRowCommandParams = { + unitId: 'test', + subUnitId: 'sheet1', + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + direction: Direction.UP, + }; + + expect(await commandService.executeCommand(InsertRowCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 2, 3, 2); + expect(oldValue).toStrictEqual([[{}], [{ f: '=A1:B3' }]]); + }); + + it('Insert column', async () => { const params: IInsertColCommandParams = { unitId: 'test', subUnitId: 'sheet1', - range: cellToRange(0, 1), + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 2, + }, direction: Direction.LEFT, }; - // FXIME why InsertColCommand sequenceExecute returns result false - await commandService.executeCommand(InsertColCommand.id, params); - const oldValue = getValueByPosition(0, 3, 0, 3); - expect(oldValue?.f).toBe('=A1:C1'); + expect(await commandService.executeCommand(InsertColCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 2, 2, 3); + expect(oldValue).toStrictEqual([[{}, { f: '=A1:C2' }]]); + }); + + it('Remove row', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + }; + + expect(await commandService.executeCommand(RemoveRowCommand.id, params)).toBeTruthy(); + const oldValue = getValues(1, 2, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + }); + + it('Remove column', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 2, + }, + }; + + expect(await commandService.executeCommand(RemoveColCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 1, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + }); + + it('Delete move left', async () => { + const params: IDeleteRangeMoveLeftCommandParams = { + range: { + startRow: 2, + startColumn: 1, + endRow: 2, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 1, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + }); + + it('Delete move up', async () => { + const params: IDeleteRangeMoveUpCommandParams = { + range: { + startRow: 1, + startColumn: 2, + endRow: 1, + endColumn: 2, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); + const oldValue = getValues(1, 2, 2, 2); + expect(oldValue).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); + }); + + it('Insert move down', async () => { + const params: InsertRangeMoveDownCommandParams = { + range: { + startRow: 1, + startColumn: 2, + endRow: 1, + endColumn: 2, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 2, 3, 2); + expect(oldValue).toStrictEqual([[{}], [{ f: '=A1:B2' }]]); + }); + + it('Insert move right', async () => { + const params: InsertRangeMoveRightCommandParams = { + range: { + startRow: 2, + startColumn: 1, + endRow: 2, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); + const oldValue = getValues(2, 2, 2, 3); + expect(oldValue).toStrictEqual([[{}, { f: '=A1:B2' }]]); + }); + + it('set name', async () => { + const params: ISetWorksheetNameCommandParams = { + subUnitId: 'sheet2', + name: 'Sheet2Rename', + }; + + expect(await commandService.executeCommand(SetWorksheetNameCommand.id, params)).toBeTruthy(); + const oldValue = getValues(4, 2, 4, 2); + expect(oldValue).toStrictEqual([[{ f: '=Sheet2Rename!A1:B2' }]]); + }); + + it('remove sheet', async () => { + const params: IRemoveSheetCommandParams = { + unitId: 'test', + subUnitId: 'sheet2', + }; - const newValue = getValueByPosition(0, 4, 0, 4); - // expect(newValue).toBe('=A1:D1'); + expect(await commandService.executeCommand(RemoveSheetCommand.id, params)).toBeTruthy(); + const oldValue = getValues(4, 2, 4, 2); + expect(oldValue).toStrictEqual([[{ f: '=#REF!' }]]); }); }); }); diff --git a/packages/sheets-formula/src/controllers/update-formula.controller.ts b/packages/sheets-formula/src/controllers/update-formula.controller.ts index 11fd8be8d2c..c6371ce4599 100644 --- a/packages/sheets-formula/src/controllers/update-formula.controller.ts +++ b/packages/sheets-formula/src/controllers/update-formula.controller.ts @@ -366,7 +366,6 @@ export class UpdateFormulaController extends Disposable { if (result) { const { unitSheetNameMap } = this._formulaDataModel.getCalculateData(); const oldFormulaData = this._formulaDataModel.getFormulaData(); - // const oldNumfmtItemMap = this._formulaDataModel.getNumfmtItemMap(); // change formula reference const { newFormulaData } = this._getFormulaReferenceMoveInfo( diff --git a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts index 9e1c138ba96..7531a1b4570 100644 --- a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts +++ b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts @@ -17,7 +17,7 @@ import { describe, expect, it } from 'vitest'; import type { IFormulaDataItem } from '@univerjs/engine-formula'; -import { formulaDataItemToCellData } from '../ref-range-formula'; +import { formulaDataItemToCellData, isFormulaDataItem } from '../ref-range-formula'; describe('Ref range formula test', () => { describe('Util function', () => { @@ -93,5 +93,12 @@ describe('Ref range formula test', () => { expect(result).toBeNull(); }); + + it('isFormulaDataItem', () => { + expect(isFormulaDataItem({ f: '=SUM(1)' })).toBeTruthy(); + expect(isFormulaDataItem({ f: '' })).toBeFalsy(); + expect(isFormulaDataItem({ f: '', si: 'id1' })).toBeTruthy(); + expect(isFormulaDataItem({ f: '', si: undefined })).toBeFalsy(); + }); }); }); diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index fca41a93584..4282aa34407 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -524,11 +524,6 @@ export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): IC return cellData; } -// export function handleSetNameFormula(oldFormulaData: IFormulaData, -// newFormulaData: IFormulaData { - -// } - export function isFormulaDataItem(cell: IFormulaDataItem) { const formulaString = cell?.f || ''; const formulaId = cell?.si || ''; From ce9584af2abcd48dcf37224dc1474a91d4011465 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Sun, 7 Apr 2024 18:41:00 +0800 Subject: [PATCH 09/13] fix(formula): return blank array after no change formula --- .../controllers/utils/ref-range-formula.ts | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 4282aa34407..5976b57d097 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -131,46 +131,48 @@ export function getFormulaReferenceSheet(oldFormulaData: IFormulaData, }; } -export function getFormulaReferenceMoveRemoveSheet(oldFormulaData: IFormulaData, - newFormulaData: IFormulaData, - formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - -} - export function getFormulaReferenceRange(oldFormulaData: IFormulaData, newFormulaData: IFormulaData, formulaReferenceMoveParam: IFormulaReferenceMoveParam) { - const { type, sheetId: subUnitId, unitId, range, from, to } = formulaReferenceMoveParam; + const { sheetId: subUnitId, unitId } = formulaReferenceMoveParam; const { redoFormulaData, undoFormulaData } = refRangeFormula(oldFormulaData, newFormulaData, formulaReferenceMoveParam); - // console.info('redoFormulaData==', redoFormulaData); - // console.info('undoFormulaData==', undoFormulaData); + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; - const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { - subUnitId, - unitId, - cellValue: redoFormulaData, - }; + if (Object.keys(redoFormulaData).length !== 0) { + const redoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: redoFormulaData, + }; - const redoMutation = { - id: SetRangeValuesMutation.id, - params: redoSetRangeValuesMutationParams, - }; + const redoMutation = { + id: SetRangeValuesMutation.id, + params: redoSetRangeValuesMutationParams, + }; - const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { - subUnitId, - unitId, - cellValue: undoFormulaData, - }; + redos.push(redoMutation); + } - const undoMutation = { - id: SetRangeValuesMutation.id, - params: undoSetRangeValuesMutationParams, - }; + if (Object.keys(undoFormulaData).length !== 0) { + const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = { + subUnitId, + unitId, + cellValue: undoFormulaData, + }; + + const undoMutation = { + id: SetRangeValuesMutation.id, + params: undoSetRangeValuesMutationParams, + }; + + undos.push(undoMutation); + } return { - undos: [undoMutation], - redos: [redoMutation], + undos, + redos, }; } From 0ab2eb0c2c48e419fce5414d130c80c964fbfda5 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Sun, 7 Apr 2024 21:12:52 +0800 Subject: [PATCH 10/13] fix(formula): add test cases for update formula --- .../update-formula.controller.spec.ts | 217 +++++++++++++++--- 1 file changed, 181 insertions(+), 36 deletions(-) diff --git a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts index 8022e29856f..5e3fab00274 100644 --- a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts +++ b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts @@ -15,7 +15,7 @@ */ import type { ICellData, IWorkbookData, Nullable, Univer } from '@univerjs/core'; -import { Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE } from '@univerjs/core'; +import { Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE, RedoCommand, UndoCommand } from '@univerjs/core'; import type { IDeleteRangeMoveLeftCommandParams, IDeleteRangeMoveUpCommandParams, IInsertColCommandParams, IInsertRowCommandParams, IMoveColsCommandParams, IMoveRangeCommandParams, IMoveRowsCommandParams, InsertRangeMoveDownCommandParams, InsertRangeMoveRightCommandParams, IRemoveRowColCommandParams, IRemoveSheetCommandParams, ISetWorksheetNameCommandParams } from '@univerjs/sheets'; import { DeleteRangeMoveLeftCommand, DeleteRangeMoveUpCommand, InsertColCommand, InsertColMutation, InsertRangeMoveDownCommand, InsertRangeMoveRightCommand, InsertRowCommand, InsertRowMutation, MoveColsCommand, MoveColsMutation, MoveRangeCommand, MoveRangeMutation, MoveRowsCommand, MoveRowsMutation, NORMAL_SELECTION_PLUGIN_NAME, RemoveColCommand, RemoveColMutation, RemoveRowCommand, RemoveRowMutation, RemoveSheetCommand, RemoveSheetMutation, SelectionManagerService, SetRangeValuesMutation, SetSelectionsOperation, SetWorksheetNameCommand, SetWorksheetNameMutation } from '@univerjs/sheets'; import type { Injector } from '@wendellhu/redi'; @@ -32,7 +32,15 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ sheet1: { id: 'sheet1', cellData: { + 1: { + 2: { + v: 1, + }, + }, 2: { + 1: { + v: 1, + }, 2: { f: '=A1:B2', }, @@ -42,6 +50,19 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ f: '=Sheet2!A1:B2', }, }, + 5: { + 2: { + f: '=SUM(A1:B2)', + }, + 3: { + v: 1, + }, + }, + 6: { + 2: { + v: 1, + }, + }, }, name: 'Sheet1', }, @@ -154,24 +175,32 @@ describe('Test insert function operation', () => { it('Move range', async () => { const params: IMoveRangeCommandParams = { fromRange: { - startRow: 2, + startRow: 5, startColumn: 2, - endRow: 2, + endRow: 5, endColumn: 2, rangeType: 0, }, toRange: { - startRow: 2, + startRow: 5, startColumn: 3, - endRow: 2, + endRow: 5, endColumn: 3, rangeType: 0, }, }; expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 2, 2, 3); - expect(oldValue).toStrictEqual([[{}, { f: '=A1:B2' }]]); + const values = getValues(5, 2, 5, 3); + expect(values).toStrictEqual([[{}, { f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 3); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 2, 5, 3); + expect(valuesRedo).toStrictEqual([[{}, { f: '=A1:B2' }]]); }); it('Move rows', async () => { @@ -200,17 +229,31 @@ describe('Test insert function operation', () => { rangeType: 1, }, toRange: { - startRow: 4, + startRow: 9, startColumn: 0, - endRow: 4, + endRow: 9, endColumn: 19, rangeType: 1, }, }; expect(await commandService.executeCommand(MoveRowsCommand.id, params)).toBeTruthy(); - const oldValue = getValues(1, 2, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:B4' }], [{ }]]); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B9' }], [{ }]]); + const values2 = getValues(4, 2, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(1, 2, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B9' }], [{ }]]); + const valuesRedo2 = getValues(4, 2, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1 }]]); }); it('Move columns', async () => { @@ -240,16 +283,30 @@ describe('Test insert function operation', () => { }, toRange: { startRow: 0, - startColumn: 4, + startColumn: 9, endRow: 999, - endColumn: 4, + endColumn: 9, rangeType: 2, }, }; expect(await commandService.executeCommand(MoveColsCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 1, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:D2' }, { }]]); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:I2' }, { }]]); + const values2 = getValues(5, 1, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:I2' }, { }]]); + const valuesRedo2 = getValues(5, 1, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1 }]]); }); it('Insert row', async () => { @@ -266,8 +323,22 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(InsertRowCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 2, 3, 2); - expect(oldValue).toStrictEqual([[{}], [{ f: '=A1:B3' }]]); + const values = getValues(2, 2, 3, 2); + expect(values).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B3' }]]); + const values2 = getValues(6, 2, 7, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 3, 2); + expect(valuesRedo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B3' }]]); + const valuesRedo2 = getValues(6, 2, 7, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1 }]]); }); it('Insert column', async () => { @@ -284,8 +355,22 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(InsertColCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 2, 2, 3); - expect(oldValue).toStrictEqual([[{}, { f: '=A1:C2' }]]); + const values = getValues(2, 2, 2, 3); + expect(values).toStrictEqual([[{ v: 1 }, { f: '=A1:C2' }]]); + const values2 = getValues(5, 3, 5, 4); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 2, 3); + expect(valuesRedo).toStrictEqual([[{ v: 1 }, { f: '=A1:C2' }]]); + const valuesRedo2 = getValues(5, 3, 5, 4); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1 }]]); }); it('Remove row', async () => { @@ -299,8 +384,22 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(RemoveRowCommand.id, params)).toBeTruthy(); - const oldValue = getValues(1, 2, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + const values2 = getValues(4, 2, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(1, 2, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + const valuesRedo2 = getValues(4, 2, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1 }]]); }); it('Remove column', async () => { @@ -314,11 +413,25 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(RemoveColCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 1, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + const values2 = getValues(5, 1, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + const valuesRedo2 = getValues(5, 1, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1 }]]); }); - it('Delete move left', async () => { + it('Delete move left, value on the left', async () => { const params: IDeleteRangeMoveLeftCommandParams = { range: { startRow: 2, @@ -330,8 +443,40 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 1, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + }); + + it('Delete move left, value on the right', async () => { + const params: IDeleteRangeMoveLeftCommandParams = { + range: { + startRow: 5, + startColumn: 1, + endRow: 5, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); + const values = getValues(5, 1, 5, 2); + expect(values).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 3); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 1, 5, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); }); it('Delete move up', async () => { @@ -346,8 +491,8 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); - const oldValue = getValues(1, 2, 2, 2); - expect(oldValue).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); }); it('Insert move down', async () => { @@ -362,8 +507,8 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 2, 3, 2); - expect(oldValue).toStrictEqual([[{}], [{ f: '=A1:B2' }]]); + const values = getValues(2, 2, 3, 2); + expect(values).toStrictEqual([[{}], [{ f: '=A1:B2' }]]); }); it('Insert move right', async () => { @@ -378,8 +523,8 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); - const oldValue = getValues(2, 2, 2, 3); - expect(oldValue).toStrictEqual([[{}, { f: '=A1:B2' }]]); + const values = getValues(2, 2, 2, 3); + expect(values).toStrictEqual([[{}, { f: '=A1:B2' }]]); }); it('set name', async () => { @@ -389,8 +534,8 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(SetWorksheetNameCommand.id, params)).toBeTruthy(); - const oldValue = getValues(4, 2, 4, 2); - expect(oldValue).toStrictEqual([[{ f: '=Sheet2Rename!A1:B2' }]]); + const values = getValues(4, 2, 4, 2); + expect(values).toStrictEqual([[{ f: '=Sheet2Rename!A1:B2' }]]); }); it('remove sheet', async () => { @@ -400,8 +545,8 @@ describe('Test insert function operation', () => { }; expect(await commandService.executeCommand(RemoveSheetCommand.id, params)).toBeTruthy(); - const oldValue = getValues(4, 2, 4, 2); - expect(oldValue).toStrictEqual([[{ f: '=#REF!' }]]); + const values = getValues(4, 2, 4, 2); + expect(values).toStrictEqual([[{ f: '=#REF!' }]]); }); }); }); From cc182e8154f095594ebfe8dda7062dcb5eb4f6e2 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 8 Apr 2024 15:08:55 +0800 Subject: [PATCH 11/13] test(formula): add insert,remove test cases for formula update --- .../update-formula.controller.spec.ts | 825 ++++++++++-------- .../utils/__tests__/ref-range-formula.spec.ts | 2 +- .../controllers/utils/ref-range-formula.ts | 10 +- 3 files changed, 460 insertions(+), 377 deletions(-) diff --git a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts index 5e3fab00274..c7b44e22d71 100644 --- a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts +++ b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts @@ -15,7 +15,7 @@ */ import type { ICellData, IWorkbookData, Nullable, Univer } from '@univerjs/core'; -import { Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE, RedoCommand, UndoCommand } from '@univerjs/core'; +import { CellValueType, Direction, ICommandService, IUniverInstanceService, LocaleType, RANGE_TYPE, RedoCommand, UndoCommand } from '@univerjs/core'; import type { IDeleteRangeMoveLeftCommandParams, IDeleteRangeMoveUpCommandParams, IInsertColCommandParams, IInsertRowCommandParams, IMoveColsCommandParams, IMoveRangeCommandParams, IMoveRowsCommandParams, InsertRangeMoveDownCommandParams, InsertRangeMoveRightCommandParams, IRemoveRowColCommandParams, IRemoveSheetCommandParams, ISetWorksheetNameCommandParams } from '@univerjs/sheets'; import { DeleteRangeMoveLeftCommand, DeleteRangeMoveUpCommand, InsertColCommand, InsertColMutation, InsertRangeMoveDownCommand, InsertRangeMoveRightCommand, InsertRowCommand, InsertRowMutation, MoveColsCommand, MoveColsMutation, MoveRangeCommand, MoveRangeMutation, MoveRowsCommand, MoveRowsMutation, NORMAL_SELECTION_PLUGIN_NAME, RemoveColCommand, RemoveColMutation, RemoveRowCommand, RemoveRowMutation, RemoveSheetCommand, RemoveSheetMutation, SelectionManagerService, SetRangeValuesMutation, SetSelectionsOperation, SetWorksheetNameCommand, SetWorksheetNameMutation } from '@univerjs/sheets'; import type { Injector } from '@wendellhu/redi'; @@ -35,32 +35,36 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ 1: { 2: { v: 1, + t: CellValueType.NUMBER, }, }, 2: { 1: { v: 1, + t: CellValueType.NUMBER, }, 2: { f: '=A1:B2', }, }, - 4: { - 2: { - f: '=Sheet2!A1:B2', - }, - }, 5: { 2: { f: '=SUM(A1:B2)', }, 3: { v: 1, + t: CellValueType.NUMBER, }, }, 6: { 2: { v: 1, + t: CellValueType.NUMBER, + }, + }, + 14: { + 2: { + f: '=Sheet2!A1:B2', }, }, }, @@ -79,17 +83,10 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ styles: {}, }); -// TODO@Dushusir: add move range,insert range,delete range test case describe('Test insert function operation', () => { let univer: Univer; let get: Injector['get']; let commandService: ICommandService; - let getValueByPosition: ( - startRow: number, - startColumn: number, - endRow: number, - endColumn: number - ) => Nullable; let getValues: ( startRow: number, startColumn: number, @@ -142,17 +139,6 @@ describe('Test insert function operation', () => { commandService.registerCommand(SetArrayFormulaDataMutation); commandService.registerCommand(SetNumfmtFormulaDataMutation); - getValueByPosition = ( - startRow: number, - startColumn: number, - endRow: number, - endColumn: number - ): Nullable => - get(IUniverInstanceService) - .getUniverSheetInstance('test') - ?.getSheetBySheetId('sheet1') - ?.getRange(startRow, startColumn, endRow, endColumn) - .getValue(); getValues = ( startRow: number, startColumn: number, @@ -171,383 +157,478 @@ describe('Test insert function operation', () => { }); describe('update formula', () => { - describe('correct situations', () => { - it('Move range', async () => { - const params: IMoveRangeCommandParams = { - fromRange: { - startRow: 5, - startColumn: 2, - endRow: 5, - endColumn: 2, - rangeType: 0, - }, - toRange: { - startRow: 5, - startColumn: 3, - endRow: 5, - endColumn: 3, - rangeType: 0, - }, - }; + it('Move range', async () => { + const params: IMoveRangeCommandParams = { + fromRange: { + startRow: 5, + startColumn: 2, + endRow: 5, + endColumn: 2, + rangeType: 0, + }, + toRange: { + startRow: 5, + startColumn: 3, + endRow: 5, + endColumn: 3, + rangeType: 0, + }, + }; - expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); - const values = getValues(5, 2, 5, 3); - expect(values).toStrictEqual([[{}, { f: '=A1:B2' }]]); + expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); + const values = getValues(5, 2, 5, 3); + expect(values).toStrictEqual([[{}, { f: '=SUM(A1:B2)' }]]); - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(5, 2, 5, 3); - expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }], [{ v: 1 }]]); + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 3); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(5, 2, 5, 3); - expect(valuesRedo).toStrictEqual([[{}, { f: '=A1:B2' }]]); - }); + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 2, 5, 3); + expect(valuesRedo).toStrictEqual([[{}, { f: '=SUM(A1:B2)' }]]); + }); - it('Move rows', async () => { - const selectionManager = get(SelectionManagerService); - selectionManager.setCurrentSelection({ - pluginName: NORMAL_SELECTION_PLUGIN_NAME, - unitId: 'test', - sheetId: 'sheet1', - }); - - // A1 - selectionManager.add([ - { - range: { startRow: 1, startColumn: 0, endRow: 1, endColumn: 0, rangeType: RANGE_TYPE.ROW }, - primary: null, - style: null, - }, - ]); - - const params: IMoveRowsCommandParams = { - fromRange: { - startRow: 1, - startColumn: 0, - endRow: 1, - endColumn: 19, - rangeType: 1, - }, - toRange: { - startRow: 9, - startColumn: 0, - endRow: 9, - endColumn: 19, - rangeType: 1, - }, - }; - - expect(await commandService.executeCommand(MoveRowsCommand.id, params)).toBeTruthy(); - const values = getValues(1, 2, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:B9' }], [{ }]]); - const values2 = getValues(4, 2, 5, 2); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(1, 2, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 6, 2); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(1, 2, 2, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:B9' }], [{ }]]); - const valuesRedo2 = getValues(4, 2, 5, 2); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1 }]]); + it('Move rows', async () => { + const selectionManager = get(SelectionManagerService); + selectionManager.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', }); - it('Move columns', async () => { - const selectionManager = get(SelectionManagerService); - selectionManager.setCurrentSelection({ - pluginName: NORMAL_SELECTION_PLUGIN_NAME, - unitId: 'test', - sheetId: 'sheet1', - }); - - // A1 - selectionManager.add([ - { - range: { startRow: 0, startColumn: 1, endRow: 0, endColumn: 1, rangeType: RANGE_TYPE.COLUMN }, - primary: null, - style: null, - }, - ]); - - const params: IMoveColsCommandParams = { - fromRange: { - startRow: 0, - startColumn: 1, - endRow: 999, - endColumn: 1, - rangeType: 2, - }, - toRange: { - startRow: 0, - startColumn: 9, - endRow: 999, - endColumn: 9, - rangeType: 2, - }, - }; - - expect(await commandService.executeCommand(MoveColsCommand.id, params)).toBeTruthy(); - const values = getValues(2, 1, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:I2' }, { }]]); - const values2 = getValues(5, 1, 5, 2); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(2, 1, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 5, 3); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(2, 1, 2, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:I2' }, { }]]); - const valuesRedo2 = getValues(5, 1, 5, 2); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1 }]]); - }); + // A1 + selectionManager.add([ + { + range: { startRow: 1, startColumn: 0, endRow: 1, endColumn: 0, rangeType: RANGE_TYPE.ROW }, + primary: null, + style: null, + }, + ]); + + const params: IMoveRowsCommandParams = { + fromRange: { + startRow: 1, + startColumn: 0, + endRow: 1, + endColumn: 19, + rangeType: 1, + }, + toRange: { + startRow: 9, + startColumn: 0, + endRow: 9, + endColumn: 19, + rangeType: 1, + }, + }; + + expect(await commandService.executeCommand(MoveRowsCommand.id, params)).toBeTruthy(); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B9' }], [{}]]); + const values2 = getValues(4, 2, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(1, 2, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B9' }], [{}]]); + const valuesRedo2 = getValues(4, 2, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + }); - it('Insert row', async () => { - const params: IInsertRowCommandParams = { - unitId: 'test', - subUnitId: 'sheet1', - range: { - startRow: 1, - endRow: 1, - startColumn: 0, - endColumn: 19, - }, - direction: Direction.UP, - }; - - expect(await commandService.executeCommand(InsertRowCommand.id, params)).toBeTruthy(); - const values = getValues(2, 2, 3, 2); - expect(values).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B3' }]]); - const values2 = getValues(6, 2, 7, 2); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(1, 2, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 6, 2); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(2, 2, 3, 2); - expect(valuesRedo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B3' }]]); - const valuesRedo2 = getValues(6, 2, 7, 2); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1 }]]); + it('Move columns', async () => { + const selectionManager = get(SelectionManagerService); + selectionManager.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', }); - it('Insert column', async () => { - const params: IInsertColCommandParams = { - unitId: 'test', - subUnitId: 'sheet1', - range: { - startColumn: 1, - endColumn: 1, - startRow: 0, - endRow: 2, - }, - direction: Direction.LEFT, - }; - - expect(await commandService.executeCommand(InsertColCommand.id, params)).toBeTruthy(); - const values = getValues(2, 2, 2, 3); - expect(values).toStrictEqual([[{ v: 1 }, { f: '=A1:C2' }]]); - const values2 = getValues(5, 3, 5, 4); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(2, 1, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 5, 3); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(2, 2, 2, 3); - expect(valuesRedo).toStrictEqual([[{ v: 1 }, { f: '=A1:C2' }]]); - const valuesRedo2 = getValues(5, 3, 5, 4); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1 }]]); - }); + // A1 + selectionManager.add([ + { + range: { startRow: 0, startColumn: 1, endRow: 0, endColumn: 1, rangeType: RANGE_TYPE.COLUMN }, + primary: null, + style: null, + }, + ]); + + const params: IMoveColsCommandParams = { + fromRange: { + startRow: 0, + startColumn: 1, + endRow: 999, + endColumn: 1, + rangeType: 2, + }, + toRange: { + startRow: 0, + startColumn: 9, + endRow: 999, + endColumn: 9, + rangeType: 2, + }, + }; + + expect(await commandService.executeCommand(MoveColsCommand.id, params)).toBeTruthy(); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:I2' }, {}]]); + const values2 = getValues(5, 1, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:I2' }, {}]]); + const valuesRedo2 = getValues(5, 1, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1, t: CellValueType.NUMBER }]]); + }); - it('Remove row', async () => { - const params: IRemoveRowColCommandParams = { - range: { - startRow: 1, - endRow: 1, - startColumn: 0, - endColumn: 19, - }, - }; - - expect(await commandService.executeCommand(RemoveRowCommand.id, params)).toBeTruthy(); - const values = getValues(1, 2, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); - const values2 = getValues(4, 2, 5, 2); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(1, 2, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }], [{ f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 6, 2); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(1, 2, 2, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); - const valuesRedo2 = getValues(4, 2, 5, 2); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1 }]]); - }); + it('Insert row', async () => { + const params: IInsertRowCommandParams = { + unitId: 'test', + subUnitId: 'sheet1', + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + direction: Direction.UP, + }; + + expect(await commandService.executeCommand(InsertRowCommand.id, params)).toBeTruthy(); + const values = getValues(2, 2, 3, 2); + expect(values).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B3' }]]); + const values2 = getValues(6, 2, 7, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 3, 2); + expect(valuesRedo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B3' }]]); + const valuesRedo2 = getValues(6, 2, 7, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + }); - it('Remove column', async () => { - const params: IRemoveRowColCommandParams = { - range: { - startColumn: 1, - endColumn: 1, - startRow: 0, - endRow: 2, - }, - }; - - expect(await commandService.executeCommand(RemoveColCommand.id, params)).toBeTruthy(); - const values = getValues(2, 1, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:A2' }, {}]]); - const values2 = getValues(5, 1, 5, 2); - expect(values2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(2, 1, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); - const valuesUndo2 = getValues(5, 2, 5, 3); - expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1 }]]); - - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(2, 1, 2, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }, {}]]); - const valuesRedo2 = getValues(5, 1, 5, 2); - expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1 }]]); - }); + it('Insert column', async () => { + const params: IInsertColCommandParams = { + unitId: 'test', + subUnitId: 'sheet1', + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 14, + }, + direction: Direction.LEFT, + cellValue: {}, + }; + + expect(await commandService.executeCommand(InsertColCommand.id, params)).toBeTruthy(); + const values = getValues(2, 2, 2, 3); + expect(values).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:C2' }]]); + const values2 = getValues(5, 3, 5, 4); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 2, 3); + expect(valuesRedo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:C2' }]]); + const valuesRedo2 = getValues(5, 3, 5, 4); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1, t: CellValueType.NUMBER }]]); + }); - it('Delete move left, value on the left', async () => { - const params: IDeleteRangeMoveLeftCommandParams = { - range: { - startRow: 2, - startColumn: 1, - endRow: 2, - endColumn: 1, - rangeType: 0, - }, - }; + it('Remove row', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + }; + + expect(await commandService.executeCommand(RemoveRowCommand.id, params)).toBeTruthy(); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + const values2 = getValues(4, 2, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 6, 2); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(1, 2, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B1' }], [{}]]); + const valuesRedo2 = getValues(4, 2, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + }); - expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); - const values = getValues(2, 1, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + it('Remove column', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 2, + }, + }; + + expect(await commandService.executeCommand(RemoveColCommand.id, params)).toBeTruthy(); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + const values2 = getValues(5, 1, 5, 2); + expect(values2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + const valuesUndo2 = getValues(5, 2, 5, 3); + expect(valuesUndo2).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }, {}]]); + const valuesRedo2 = getValues(5, 1, 5, 2); + expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:A2)' }, { v: 1, t: CellValueType.NUMBER }]]); + }); - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(2, 1, 2, 2); - expect(valuesUndo).toStrictEqual([[{ v: 1 }, { f: '=A1:B2' }]]); + it('Delete move left, value on the left', async () => { + const params: IDeleteRangeMoveLeftCommandParams = { + range: { + startRow: 2, + startColumn: 1, + endRow: 2, + endColumn: 1, + rangeType: 0, + }, + }; - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(2, 1, 2, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }, {}]]); - }); + expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); + const values = getValues(2, 1, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B2' }, {}]]); - it('Delete move left, value on the right', async () => { - const params: IDeleteRangeMoveLeftCommandParams = { - range: { - startRow: 5, - startColumn: 1, - endRow: 5, - endColumn: 1, - rangeType: 0, - }, - }; + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); - expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); - const values = getValues(5, 1, 5, 2); - expect(values).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 1, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }, {}]]); + }); - expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); - const valuesUndo = getValues(5, 2, 5, 3); - expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); + it('Delete move left, value on the right', async () => { + const params: IDeleteRangeMoveLeftCommandParams = { + range: { + startRow: 5, + startColumn: 1, + endRow: 5, + endColumn: 1, + rangeType: 0, + }, + }; - expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); - const valuesRedo = getValues(5, 1, 5, 2); - expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }, { v: 1 }]]); - }); + expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); + const values = getValues(5, 1, 5, 2); + expect(values).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); - it('Delete move up', async () => { - const params: IDeleteRangeMoveUpCommandParams = { - range: { - startRow: 1, - startColumn: 2, - endRow: 1, - endColumn: 2, - rangeType: 0, - }, - }; + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 3); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); - expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); - const values = getValues(1, 2, 2, 2); - expect(values).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); - }); + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 1, 5, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + }); - it('Insert move down', async () => { - const params: InsertRangeMoveDownCommandParams = { - range: { - startRow: 1, - startColumn: 2, - endRow: 1, - endColumn: 2, - rangeType: 0, - }, - }; + it('Delete move up, value on the top', async () => { + const params: IDeleteRangeMoveUpCommandParams = { + range: { + startRow: 1, + startColumn: 2, + endRow: 1, + endColumn: 2, + rangeType: 0, + }, + }; - expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); - const values = getValues(2, 2, 3, 2); - expect(values).toStrictEqual([[{}], [{ f: '=A1:B2' }]]); - }); + expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); + const values = getValues(1, 2, 2, 2); + expect(values).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); - it('Insert move right', async () => { - const params: InsertRangeMoveRightCommandParams = { - range: { - startRow: 2, - startColumn: 1, - endRow: 2, - endColumn: 1, - rangeType: 0, - }, - }; + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); - expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); - const values = getValues(2, 2, 2, 3); - expect(values).toStrictEqual([[{}, { f: '=A1:B2' }]]); - }); + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(1, 2, 2, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B2' }], [{}]]); + }); - it('set name', async () => { - const params: ISetWorksheetNameCommandParams = { - subUnitId: 'sheet2', - name: 'Sheet2Rename', - }; + it('Delete move up, value on the bottom', async () => { + const params: IDeleteRangeMoveUpCommandParams = { + range: { + startRow: 4, + startColumn: 2, + endRow: 4, + endColumn: 2, + rangeType: 0, + }, + }; - expect(await commandService.executeCommand(SetWorksheetNameCommand.id, params)).toBeTruthy(); - const values = getValues(4, 2, 4, 2); - expect(values).toStrictEqual([[{ f: '=Sheet2Rename!A1:B2' }]]); - }); + expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); + const values = getValues(4, 2, 5, 2); + expect(values).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); - it('remove sheet', async () => { - const params: IRemoveSheetCommandParams = { - unitId: 'test', - subUnitId: 'sheet2', - }; + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 6, 2); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); - expect(await commandService.executeCommand(RemoveSheetCommand.id, params)).toBeTruthy(); - const values = getValues(4, 2, 4, 2); - expect(values).toStrictEqual([[{ f: '=#REF!' }]]); - }); + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(4, 2, 5, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + }); + + it('Insert move down, value on the top', async () => { + const params: InsertRangeMoveDownCommandParams = { + range: { + startRow: 1, + startColumn: 2, + endRow: 1, + endColumn: 2, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); + const values = getValues(2, 2, 3, 2); + expect(values).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(1, 2, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 3, 2); + expect(valuesRedo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }], [{ f: '=A1:B2' }]]); + }); + + it('Insert move down, value on the bottom', async () => { + const params: InsertRangeMoveDownCommandParams = { + range: { + startRow: 4, + startColumn: 2, + endRow: 4, + endColumn: 2, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); + const values = getValues(6, 2, 7, 2); + expect(values).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 6, 2); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(6, 2, 7, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); + }); + + it('Insert move right, value on the left', async () => { + const params: InsertRangeMoveRightCommandParams = { + range: { + startRow: 2, + startColumn: 1, + endRow: 2, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); + const values = getValues(2, 2, 2, 3); + expect(values).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(2, 1, 2, 2); + expect(valuesUndo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(2, 2, 2, 3); + expect(valuesRedo).toStrictEqual([[{ v: 1, t: CellValueType.NUMBER }, { f: '=A1:B2' }]]); + }); + + it('Insert move right, value on the right', async () => { + const params: InsertRangeMoveRightCommandParams = { + range: { + startRow: 5, + startColumn: 1, + endRow: 5, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); + const values = getValues(5, 3, 5, 4); + expect(values).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 3); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 3, 5, 4); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); + }); + + it('set name', async () => { + const params: ISetWorksheetNameCommandParams = { + subUnitId: 'sheet2', + name: 'Sheet2Rename', + }; + + expect(await commandService.executeCommand(SetWorksheetNameCommand.id, params)).toBeTruthy(); + const values = getValues(14, 2, 14, 2); + expect(values).toStrictEqual([[{ f: '=Sheet2Rename!A1:B2' }]]); + }); + + it('remove sheet', async () => { + const params: IRemoveSheetCommandParams = { + unitId: 'test', + subUnitId: 'sheet2', + }; + + expect(await commandService.executeCommand(RemoveSheetCommand.id, params)).toBeTruthy(); + const values = getValues(14, 2, 14, 2); + expect(values).toStrictEqual([[{ f: '=#REF!' }]]); }); }); }); diff --git a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts index 7531a1b4570..eb5a0fc890e 100644 --- a/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts +++ b/packages/sheets-formula/src/controllers/utils/__tests__/ref-range-formula.spec.ts @@ -91,7 +91,7 @@ describe('Ref range formula test', () => { result = formulaDataItemToCellData(formulaDataItem); - expect(result).toBeNull(); + expect(result).toStrictEqual({ f: null, si: null }); }); it('isFormulaDataItem', () => { diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 5976b57d097..6bd8c6698a1 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -207,7 +207,6 @@ export function refRangeFormula(oldFormulaData: IFormulaData, // When undoing and redoing, the traversal order may be different. Record the range list of all single formula offsets, and then retrieve the traversal as needed. const rangeList: IRangeChange[] = []; let isReverse = false; - oldFormulaMatrix.forValue((row, column, cell) => { // Offset is only needed when there is a formula if (!isFormulaDataItem(cell)) { @@ -459,7 +458,7 @@ function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM const newValue = formulaDataItemToCellData(newFormula); redoFormulaData.setValue(newStartRow, newStartColumn, newValue); - redoFormulaData.setValue(oldStartRow, oldStartColumn, null); + redoFormulaData.setValue(oldStartRow, oldStartColumn, { f: null, si: null }); }); return redoFormulaData.clone(); @@ -484,7 +483,7 @@ function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM const oldValue = formulaDataItemToCellData(oldFormula); undoFormulaData.setValue(oldStartRow, oldStartColumn, oldValue); - undoFormulaData.setValue(newStartRow, newStartColumn, null); + undoFormulaData.setValue(newStartRow, newStartColumn, { f: null, si: null }); }); return undoFormulaData.clone(); @@ -510,7 +509,10 @@ export function formulaDataItemToCellData(formulaDataItem: IFormulaDataItem): IC const checkFormulaId = isFormulaId(si); if (!checkFormulaString && !checkFormulaId) { - return null; + return { + f: null, + si: null, + }; } const cellData: ICellData = {}; From c7594c21c5468bde1067279fb174ab2b18774efa Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Mon, 8 Apr 2024 19:21:49 +0800 Subject: [PATCH 12/13] fix(formula): formula update reference --- .../update-formula.controller.spec.ts | 342 +++++++++++++++++- .../controllers/utils/ref-range-formula.ts | 12 +- 2 files changed, 338 insertions(+), 16 deletions(-) diff --git a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts index c7b44e22d71..8355330a067 100644 --- a/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts +++ b/packages/sheets-formula/src/controllers/__tests__/update-formula.controller.spec.ts @@ -32,6 +32,11 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ sheet1: { id: 'sheet1', cellData: { + 0: { + 6: { + f: '=A1:B2', + }, + }, 1: { 2: { v: 1, @@ -63,6 +68,9 @@ const TEST_WORKBOOK_DATA_DEMO = (): IWorkbookData => ({ }, }, 14: { + 0: { + f: '=A1:B2', + }, 2: { f: '=Sheet2!A1:B2', }, @@ -157,7 +165,38 @@ describe('Test insert function operation', () => { }); describe('update formula', () => { - it('Move range', async () => { + it('Move range, update reference', async () => { + const params: IMoveRangeCommandParams = { + fromRange: { + startRow: 0, + startColumn: 0, + endRow: 1, + endColumn: 1, + rangeType: 0, + }, + toRange: { + startRow: 0, + startColumn: 3, + endRow: 1, + endColumn: 4, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(MoveRangeCommand.id, params)).toBeTruthy(); + const values = getValues(5, 2, 5, 2); + expect(values).toStrictEqual([[{ f: '=SUM(D1:E2)' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 2); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 2, 5, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(D1:E2)' }]]); + }); + + it('Move range, update position', async () => { const params: IMoveRangeCommandParams = { fromRange: { startRow: 5, @@ -188,7 +227,54 @@ describe('Test insert function operation', () => { expect(valuesRedo).toStrictEqual([[{}, { f: '=SUM(A1:B2)' }]]); }); - it('Move rows', async () => { + it('Move rows, update reference', async () => { + const selectionManager = get(SelectionManagerService); + selectionManager.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', + }); + + // A1 + selectionManager.add([ + { + range: { startRow: 1, startColumn: 0, endRow: 1, endColumn: 0, rangeType: RANGE_TYPE.ROW }, + primary: null, + style: null, + }, + ]); + + const params: IMoveRowsCommandParams = { + fromRange: { + startRow: 1, + startColumn: 0, + endRow: 1, + endColumn: 19, + rangeType: 1, + }, + toRange: { + startRow: 4, + startColumn: 0, + endRow: 4, + endColumn: 19, + rangeType: 1, + }, + }; + + expect(await commandService.executeCommand(MoveRowsCommand.id, params)).toBeTruthy(); + const values = getValues(5, 2, 5, 2); + expect(values).toStrictEqual([[{ f: '=SUM(A1:B4)' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(5, 2, 5, 2); + expect(valuesUndo).toStrictEqual([[{ f: '=SUM(A1:B2)' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(5, 2, 5, 2); + expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B4)' }]]); + }); + + it('Move rows, update reference and position', async () => { const selectionManager = get(SelectionManagerService); selectionManager.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, @@ -241,7 +327,54 @@ describe('Test insert function operation', () => { expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B9)' }], [{ v: 1, t: CellValueType.NUMBER }]]); }); - it('Move columns', async () => { + it('Move columns, update reference', async () => { + const selectionManager = get(SelectionManagerService); + selectionManager.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', + }); + + // A1 + selectionManager.add([ + { + range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0, rangeType: RANGE_TYPE.COLUMN }, + primary: null, + style: null, + }, + ]); + + const params: IMoveColsCommandParams = { + fromRange: { + startRow: 0, + startColumn: 0, + endRow: 999, + endColumn: 0, + rangeType: 2, + }, + toRange: { + startRow: 0, + startColumn: 4, + endRow: 999, + endColumn: 4, + rangeType: 2, + }, + }; + + expect(await commandService.executeCommand(MoveColsCommand.id, params)).toBeTruthy(); + const values = getValues(0, 6, 0, 6); + expect(values).toStrictEqual([[{ f: '=A1:A2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(0, 6, 0, 6); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(0, 6, 0, 6); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }]]); + }); + + it('Move columns, update reference and position', async () => { const selectionManager = get(SelectionManagerService); selectionManager.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, @@ -294,7 +427,33 @@ describe('Test insert function operation', () => { expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:I2)' }, { v: 1, t: CellValueType.NUMBER }]]); }); - it('Insert row', async () => { + it('Insert row, update reference', async () => { + const params: IInsertRowCommandParams = { + unitId: 'test', + subUnitId: 'sheet1', + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + direction: Direction.UP, + }; + + expect(await commandService.executeCommand(InsertRowCommand.id, params)).toBeTruthy(); + const values = getValues(0, 6, 0, 6); + expect(values).toStrictEqual([[{ f: '=A1:B3' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(0, 6, 0, 6); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(0, 6, 0, 6); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B3' }]]); + }); + + it('Insert row, update reference and position', async () => { const params: IInsertRowCommandParams = { unitId: 'test', subUnitId: 'sheet1', @@ -326,7 +485,34 @@ describe('Test insert function operation', () => { expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B3)' }], [{ v: 1, t: CellValueType.NUMBER }]]); }); - it('Insert column', async () => { + it('Insert column, update reference', async () => { + const params: IInsertColCommandParams = { + unitId: 'test', + subUnitId: 'sheet1', + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 14, + }, + direction: Direction.LEFT, + cellValue: {}, + }; + + expect(await commandService.executeCommand(InsertColCommand.id, params)).toBeTruthy(); + const values = getValues(14, 0, 14, 0); + expect(values).toStrictEqual([[{ f: '=A1:C2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(14, 0, 14, 0); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(14, 0, 14, 0); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:C2' }]]); + }); + + it('Insert column, update reference and position', async () => { const params: IInsertColCommandParams = { unitId: 'test', subUnitId: 'sheet1', @@ -359,7 +545,30 @@ describe('Test insert function operation', () => { expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:C2)' }, { v: 1, t: CellValueType.NUMBER }]]); }); - it('Remove row', async () => { + it('Remove row, update reference', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 19, + }, + }; + + expect(await commandService.executeCommand(RemoveRowCommand.id, params)).toBeTruthy(); + const values = getValues(0, 6, 0, 6); + expect(values).toStrictEqual([[{ f: '=A1:B1' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(0, 6, 0, 6); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(0, 6, 0, 6); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B1' }]]); + }); + + it('Remove row, update reference and position', async () => { const params: IRemoveRowColCommandParams = { range: { startRow: 1, @@ -388,7 +597,30 @@ describe('Test insert function operation', () => { expect(valuesRedo2).toStrictEqual([[{ f: '=SUM(A1:B1)' }], [{ v: 1, t: CellValueType.NUMBER }]]); }); - it('Remove column', async () => { + it('Remove column, update reference', async () => { + const params: IRemoveRowColCommandParams = { + range: { + startColumn: 1, + endColumn: 1, + startRow: 0, + endRow: 2, + }, + }; + + expect(await commandService.executeCommand(RemoveColCommand.id, params)).toBeTruthy(); + const values = getValues(14, 0, 14, 0); + expect(values).toStrictEqual([[{ f: '=A1:A2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(14, 0, 14, 0); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(14, 0, 14, 0); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }]]); + }); + + it('Remove column, update reference and position', async () => { const params: IRemoveRowColCommandParams = { range: { startColumn: 1, @@ -465,6 +697,30 @@ describe('Test insert function operation', () => { expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); }); + it('Delete move left, update reference', async () => { + const params: IDeleteRangeMoveLeftCommandParams = { + range: { + startRow: 0, + startColumn: 1, + endRow: 1, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(DeleteRangeMoveLeftCommand.id, params)).toBeTruthy(); + const values = getValues(14, 0, 14, 0); + expect(values).toStrictEqual([[{ f: '=A1:A2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(14, 0, 14, 0); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(14, 0, 14, 0); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:A2' }]]); + }); + it('Delete move up, value on the top', async () => { const params: IDeleteRangeMoveUpCommandParams = { range: { @@ -513,6 +769,30 @@ describe('Test insert function operation', () => { expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); }); + it('Delete move up, update reference', async () => { + const params: IDeleteRangeMoveUpCommandParams = { + range: { + startRow: 1, + startColumn: 0, + endRow: 1, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(DeleteRangeMoveUpCommand.id, params)).toBeTruthy(); + const values = getValues(0, 6, 0, 6); + expect(values).toStrictEqual([[{ f: '=A1:B1' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(0, 6, 0, 6); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(0, 6, 0, 6); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B1' }]]); + }); + it('Insert move down, value on the top', async () => { const params: InsertRangeMoveDownCommandParams = { range: { @@ -561,6 +841,30 @@ describe('Test insert function operation', () => { expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }], [{ v: 1, t: CellValueType.NUMBER }]]); }); + it('Insert move down, update reference', async () => { + const params: InsertRangeMoveDownCommandParams = { + range: { + startRow: 1, + startColumn: 0, + endRow: 1, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveDownCommand.id, params)).toBeTruthy(); + const values = getValues(0, 6, 0, 6); + expect(values).toStrictEqual([[{ f: '=A1:B3' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(0, 6, 0, 6); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(0, 6, 0, 6); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:B3' }]]); + }); + it('Insert move right, value on the left', async () => { const params: InsertRangeMoveRightCommandParams = { range: { @@ -609,6 +913,30 @@ describe('Test insert function operation', () => { expect(valuesRedo).toStrictEqual([[{ f: '=SUM(A1:B2)' }, { v: 1, t: CellValueType.NUMBER }]]); }); + it('Insert move right, update reference', async () => { + const params: InsertRangeMoveRightCommandParams = { + range: { + startRow: 0, + startColumn: 1, + endRow: 1, + endColumn: 1, + rangeType: 0, + }, + }; + + expect(await commandService.executeCommand(InsertRangeMoveRightCommand.id, params)).toBeTruthy(); + const values = getValues(14, 0, 14, 0); + expect(values).toStrictEqual([[{ f: '=A1:C2' }]]); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const valuesUndo = getValues(14, 0, 14, 0); + expect(valuesUndo).toStrictEqual([[{ f: '=A1:B2' }]]); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + const valuesRedo = getValues(14, 0, 14, 0); + expect(valuesRedo).toStrictEqual([[{ f: '=A1:C2' }]]); + }); + it('set name', async () => { const params: ISetWorksheetNameCommandParams = { subUnitId: 'sheet2', diff --git a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts index 6bd8c6698a1..23990aba40a 100644 --- a/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts +++ b/packages/sheets-formula/src/controllers/utils/ref-range-formula.ts @@ -278,13 +278,7 @@ export function refRangeFormula(oldFormulaData: IFormulaData, return; } - const { startRow: oldStartRow, startColumn: oldStartColumn } = oldCell; - const { startRow: newStartRow, startColumn: newStartColumn } = newCell; - - if (oldStartRow === newStartRow && oldStartColumn === newStartColumn) { - return; - } - + // Note: The formula may only update the reference and not offset the position. The situation where the position is not shifted cannot be intercepted here. if (isReverse) { rangeList.unshift({ oldCell, @@ -457,8 +451,8 @@ function getRedoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM const newFormula = newFormulaMatrix.getValue(oldStartRow, oldStartColumn) || oldFormulaMatrix.getValue(oldStartRow, oldStartColumn); const newValue = formulaDataItemToCellData(newFormula); - redoFormulaData.setValue(newStartRow, newStartColumn, newValue); redoFormulaData.setValue(oldStartRow, oldStartColumn, { f: null, si: null }); + redoFormulaData.setValue(newStartRow, newStartColumn, newValue); }); return redoFormulaData.clone(); @@ -482,8 +476,8 @@ function getUndoFormulaData(rangeList: IRangeChange[], oldFormulaMatrix: ObjectM const oldFormula = oldFormulaMatrix.getValue(oldStartRow, oldStartColumn); const oldValue = formulaDataItemToCellData(oldFormula); - undoFormulaData.setValue(oldStartRow, oldStartColumn, oldValue); undoFormulaData.setValue(newStartRow, newStartColumn, { f: null, si: null }); + undoFormulaData.setValue(oldStartRow, oldStartColumn, oldValue); }); return undoFormulaData.clone(); From 5b854031adf0eaa70f4581d7aac544317908429a Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 9 Apr 2024 17:58:49 +0800 Subject: [PATCH 13/13] docs(formula): add formulas i18n,algorithm contributing guide --- packages/sheets-formula/README-zh.md | 22 ++++++++++------- packages/sheets-formula/README.md | 36 ++++++++++++++++------------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/sheets-formula/README-zh.md b/packages/sheets-formula/README-zh.md index 3fb6cba3b00..60e62932bfd 100644 --- a/packages/sheets-formula/README-zh.md +++ b/packages/sheets-formula/README-zh.md @@ -181,7 +181,7 @@ univerAPI.unregisterFunction({ Uniscript 底层使用了 `@univerjs/facade`,你也可以直接在项目中使用类似 Uniscript 的 API,请参考 [注册公式](/guides/facade/register-function)。 -## 如何在初始化 Univer 时添加公式 +### 如何在初始化 Univer 时添加公式 按照以下步骤来实现一个自定义公式 `CUSTOMSUM`。 @@ -719,7 +719,7 @@ univer.registerPlugin(UniverSheetsCustomFunctionPlugin); - `description` 参数需要综合下内容进行提取,因为有的 Excel 描述很长,需要简化 - `abstract` 和 `links` 基本上不需要做改动 - `aliasFunctionName` 是可选参数,大部分公式不需要填写(也可以只设置某个国家的别名),暂时还未找到有公式别名文档来参考。目前找到一个函数翻译插件可能提供类似功能 [Excel 函数翻译工具](https://support.microsoft.com/zh-cn/office/excel-%E5%87%BD%E6%95%B0%E7%BF%BB%E8%AF%91%E5%B7%A5%E5%85%B7-f262d0c0-991c-485b-89b6-32cc8d326889) - - `functionParameter` 中需要为每个参数设定一个名称,我们推荐根据参数的含义进行变化,比如数值类型的 `key` 为 `number`(仅有一个数值参数的时候)或者 `number1`、`number2`(有多个数值参数的时候),范围为 `range`,条件为 `criteria`,求和范围为 `sum_range`(多个单词之间用 `_` 分割) + - `functionParameter` 中需要为每个参数设定一个名称,我们推荐根据参数的含义进行变化,比如数值类型的 `key` 为 `number`(仅有一个数值参数的时候)或者 `number1`、`number2`(有多个数值参数的时候),范围为 `range`,条件为 `criteria`,求和范围为 `sumRange`,采用驼峰式命名法。对于具体的参数内容, `name` 英文格式就使用带下划线的格式 `sum_range`,其他语言采用翻译的文本, `detail` 全部采用翻译。 - Office 函数文档中文翻译猜测用的机翻,部分翻译不容易理解,需要自己修改,一部分专用名词如下。 - 单元格参考 => 单元格引用 - 数字类型的参数统一翻译为:数值 @@ -745,11 +745,11 @@ univer.registerPlugin(UniverSheetsCustomFunctionPlugin); 位置在 [packages/engine-formula/src/functions/math/sumif/index.ts](https://github.com/dream-num/univer/blob/dev/packages/engine-formula/src/functions/math/sumif/index.ts)。 - 在当前公式的分类文件夹下新建公式文件夹,一个公式一个文件夹。然后新建 `index.ts` 文件来写公式算法,公式 `class` 的名称采用大驼峰的写法,认为公式是一个单词,带 `_` 或者 `.` 的公式认为是两个单词,比如 + 在当前公式的分类文件夹下新建公式文件夹,文件夹名称与公式相同,采用短横线命名,一个公式一个文件夹。然后新建 `index.ts` 文件来写公式算法,公式 `class` 的名称采用帕斯卡命名法(又叫大驼峰),认为公式是一个单词,带 `_` 或者 `.` 的公式认为是两个单词,比如 - - `SUMIF` => `Sumif` - - `NETWORKDAYS.INTL` => `Networkdays_Intl` - - `ARRAY_CONSTRAIN` => `Array_Constrain` + - `SUMIF` => 文件夹 `sumif`, 类 `Sumif` + - `NETWORKDAYS.INTL` => 文件夹 `networkdays-intl`, 类 `NetworkdaysIntl` + - `ARRAY_CONSTRAIN` => 文件夹 `array-constrain`, 类 `ArrayConstrain` 同级新建 `__tests__` 来写编写单元测试。写完之后,记得在分类目录下的 `function-map` 文件中添加公式算法和函数名映射用于注册这个函数算法。 @@ -776,7 +776,8 @@ univer.registerPlugin(UniverSheetsCustomFunctionPlugin); ### 公式实现注意事项 -- 任何公式的入参和出参都可以是 `A1`、`A1:B10`,调研 Excel 的时候需要把所有情况考虑到,比如 `=SIN(A1:B10)`,会展开一个正弦计算后的范围。 +- 大部分的公式规则请参考最新版本的 Excel,如果有不合理的地方,再参考 Google Sheets。 +- 任何公式的入参和出参都可以是 `A1`、`A1:B10`,单元格内容也可能是数字、字符串、布尔值、空单元格、错误值、数组等,虽然公式教程中说明了识别固定的数据类型,但是程序上实现是需要都兼容的,调研 Excel 的时候需要把所有情况考虑到,比如 `=SIN(A1:B10)`,会展开一个正弦计算后的范围。 - 例如 `XLOOKUP` 函数,要求两个入参的行或列至少又一个大小相等,这样才能进行矩阵计算。 - 例如 `SUMIF` 函数,大家以为是求和,但是它是可以根据第二个参数进行展开的 @@ -788,8 +789,13 @@ univer.registerPlugin(UniverSheetsCustomFunctionPlugin); - 公式的数值计算,需要使用内置的方法,尽量不要获取值自行计算。因为公式的参数可以是值、数组、引用。可以参考已有的 `sum`、`minus` 函数。 - 精度问题,公式引入了 `big.js`,使用内置方法会调用该库,但是相比原生计算会慢接近 100 倍,所以像 `sin` 等 `js` 方法,尽量用原生实现。 - 需要自定义计算,使用 `product` 函数,适合两个入参的计算,调用 `map` 对值自身进行迭代计算,适合对一个入参本身的值进行改变。 +- 公式算法支持两种配置 `needsExpandParams` 和 `needsReferenceObject` + - `needsExpandParams`: 函数是否需要扩展参数,主要处理类似 `LOOKUP` 函数需要处理不同大小向量的情况 + - `needsReferenceObject`:函数是否需要传入引用对象,设置之后 `BaseReferenceObject` 不会转化为 `ArrayValueObject` 而是直接传入公式算法,比如 `OFFSET` 函数 +- 公式计算错误会返回固定类型的错误,比如 `#NAME?`、`#VALUE!`,需要对齐 Excel,因为有判断报错类型的函数 `ISERR`、`ISNA`等,类型指定不对,结果就可能不一样。 +- 公式算法中,即使是必选参数,也需要拦截为 `null` 的情况并返回错误 `#N/A`,因为用户有可能不输入任何参数。这个行为在 Excel 中会被拦截,在 Google Sheets 中返回 `#N/A`,我们参照 Google Sheets。 -#### 公式基础工具 +### 公式基础工具 1. `ValueObjectFactory` 用来自动识别参数格式创建一个参数实例,范围类型的数据用 `RangeReferenceObject` 来创建参数实例 2. 数组 `toArrayValueObject` 可以与值直接运算,得到新的数组 diff --git a/packages/sheets-formula/README.md b/packages/sheets-formula/README.md index cf8bbdf81fe..179ec20291c 100644 --- a/packages/sheets-formula/README.md +++ b/packages/sheets-formula/README.md @@ -719,7 +719,7 @@ To implement a formula, you need to add formula description, internationalizatio - Extract the `description` from the content, as some Excel descriptions are lengthy and need simplification. - `abstract` and `links` generally do not need modification. - `aliasFunctionName` is optional; most formulas do not need to be filled (or can be set for aliases in specific countries). Currently, there is no documentation for formula aliases. Currently I have found a function translation plug-in that may provide similar functions [Excel Functions Translator](https://support.microsoft.com/en-us/office/excel-functions-translator-f262d0c0-991c-485b-89b6-32cc8d326889) - - `functionParameter` needs a name for each parameter. We recommend varying names based on the parameter's meaning, e.g., use `number` for a numeric parameter (if there is only one) or `number1`, `number2` for multiple numeric parameters. Use `range` for a range, `criteria` for conditions, and `sum_range` for the sum range (separated by `_` for multiple words). + - `functionParameter` needs a name for each parameter. We recommend varying names based on the parameter's meaning, e.g., use `number` for a numeric parameter (if there is only one) or `number1`, `number2` for multiple numeric parameters. Use `range` for a range, `criteria` for conditions, and `sumRange` for the sum range, use `camelCase`. For specific parameter content, the English format of `name` uses the underlined format `sum_range`, other languages use the translated text, and `detail` uses all translations. - Some Chinese translations in the Office function documentation are machine-translated and may be unclear. Modify as needed. For example, `单元格参考` (Cell Reference) should be translated as `单元格引用`. Numeric type parameters are uniformly translated as: `数值`. - Do not end `abstract` with a period (used in the search list when users input cells), but end `description` and `detail` with a period (used in descriptions). - Capitalize the first letter of English sentences. @@ -743,11 +743,11 @@ To implement a formula, you need to add formula description, internationalizatio Location: [packages/engine-formula/src/functions/math/sumif/index.ts](https://github.com/dream-num/univer/blob/dev/packages/engine-formula/src/functions/math/sumif/index.ts). - Create a new folder for the formula under the current formula category, with one folder per formula. Then create an `index.ts` file to write the formula algorithm. Use camel case for the formula `class` name, considering the formula as one word. If a formula contains `_` or `.`, treat it as two words, such as: + Create a new formula folder under the classification folder of the current formula. The folder name is the same as the formula, named with `kebab-case`, one folder for each formula. Then create a new `index.ts` file to write the formula algorithm. The name of the formula `class` adopts `PascalCase`. The formula is considered to be one word, and the formula with `_` or `.` is considered to be two words such as - - `SUMIF` => `Sumif` - - `NETWORKDAYS.INTL` => `Networkdays_Intl` - - `ARRAY_CONSTRAIN` => `Array_Constrain` + - `SUMIF` => folder `sumif`, class `Sumif` + - `NETWORKDAYS.INTL` => folder `networkdays-intl`, class `NetworkdaysIntl` + - `ARRAY_CONSTRAIN` => folder `array-constrain`, class `ArrayConstrain` Create a `__tests__` folder at the same level to write unit tests. After writing, remember to add the formula algorithm and function name mapping in the `function-map` file in the category directory to register the formula algorithm. @@ -772,20 +772,26 @@ To implement a formula, you need to add formula description, internationalizatio - After selecting `SUMIF` or entering `=sumif(`, trigger the formula details popup and carefully check the contents. - Select the data range, trigger the calculation, and check if the formula calculation result is correct. -#### Considerations for Formula Implementation +### Considerations for Formula Implementation -- Any formula's input and output can be `A1`, `A1:B10`, etc. When researching Excel, consider all cases, such as `=SIN(A1:B10)`, which expands to the calculated range. - - For example, the `XLOOKUP` function requires at least one of the rows or columns of its two inputs to be of equal size for matrix calculation. - - For example, the `SUMIF` function, although commonly used for summation, can expand based on the second parameter. +- For most formula rules, please refer to the latest version of Excel. If there are any unreasonable rules, please refer to Google Sheets. +- The input and output parameters of any formula can be `A1`, `A1:B10`, and the cell content may also be numbers, strings, Boolean values, empty cells, error values, arrays, etc., although the formula tutorial explains In order to identify fixed data types, the program implementation needs to be compatible. When researching Excel, consider all cases, such as `=SIN(A1:B10)`, which expands to the calculated range. + - For example, the `XLOOKUP` function requires at least one of the rows or columns of its two inputs to be of equal size for matrix calculation. + - For example, the `SUMIF` function, although commonly used for summation, can expand based on the second parameter. ![sumif array](./assets/sumif-array.png) ![sumif array result](./assets/sumif-array-result.png) - - Excel formula calculation is becoming more like numpy, for example: + - Excel formula calculation is becoming more like numpy, for example: ![numpy](./assets/numpy.png) -- For numerical calculations in formulas, use built-in methods and try to avoid obtaining values for manual calculation. Because formula parameters can be values, arrays, or references. You can refer to existing `sum` and `minus` functions. -- Precision issues: The formula introduces `big.js`, and using built-in methods will call this library. However, it is nearly 100 times slower than native calculations. Therefore, for methods like `sin`, it is advisable to use native implementations. -- For custom calculations, use the `product` function, suitable for calculating two input parameters. Call `map` to iterate over the values for changes to a parameter's own values. - -#### Formula Basic Tools +- For numerical calculations in formulas, use built-in methods and try to avoid obtaining values for manual calculation. Because formula parameters can be values, arrays, or references. You can refer to existing `sum` and `minus` functions. +- Precision issues: The formula introduces `big.js`, and using built-in methods will call this library. However, it is nearly 100 times slower than native calculations. Therefore, for methods like `sin`, it is advisable to use native implementations. +- For custom calculations, use the `product` function, suitable for calculating two input parameters. Call `map` to iterate over the values for changes to a parameter's own values. +- Formula algorithm supports two configurations `needsExpandParams` and `needsReferenceObject` + - `needsExpandParams`: Whether the function needs to expand parameters, mainly handles situations where the `LOOKUP` function needs to handle vectors of different sizes + - `needsReferenceObject`: Whether the function needs to pass in a reference object. After setting, `BaseReferenceObject` will not be converted into `ArrayValueObject` but will be passed directly into the formula algorithm, such as the `OFFSET` function +- Formula calculation errors will return fixed types of errors, such as `#NAME?`, `#VALUE!`, which need to be aligned with Excel, because there are functions `ISERR`, `ISNA`, etc. that determine the error type. If the type is not specified correctly, the result will be It may be different. +- In the formula algorithm, even if it is a required parameter, it is necessary to intercept the case of `null` and return the error `#N/A`, because the user may not enter any parameters. This behavior will be intercepted in Excel and `#N/A` will be returned in Google Sheets. We refer to Google Sheets. + +### Formula Basic Tools 1. `ValueObjectFactory` is used to automatically recognize parameter formats and create a parameter instance. Use `RangeReferenceObject` to create parameter instances for range-type data. 2. The array `toArrayValueObject` can be operated directly with values to get a new array.