Skip to content

Commit

Permalink
feat(sheet): defined name (#1737)
Browse files Browse the repository at this point in the history
* feat(sheet): defined name

* feat(sheet): defined name local

* feat(sheet): defined name

* feat(sheet): defined name local

* feat(sheet): defined name finished

* fix(sheet): defined name experience

* feat(sheet): add command for defined name

* fix(sheet): duplicate command name

* fix(sheet): type error

* fix(sheet): type error

* fix(sheet): review and search

* feat(sheet): relative ref to absolute ref

* feat(sheet): scroll to expand and defined range

* fix(sheet): range select size

* feat(sheet): active sheet and formula judgment

* fix(sheet): defined name ui error

* fix(sheet): fix 1790 and 1791 and panel

* fix(sheet): lambda error

* fix(sheet): defined name input key

* fix(sheet): change event and update sheet id

* fix(sheet): overlay style

* fix(sheet): formula input range error
  • Loading branch information
DR-Univer committed Apr 3, 2024
1 parent d84a618 commit cfa9375
Show file tree
Hide file tree
Showing 68 changed files with 2,467 additions and 109 deletions.
19 changes: 19 additions & 0 deletions packages/core/src/shared/tools.ts
Expand Up @@ -652,4 +652,23 @@ export class Tools {
) {
return range1End >= range2Start && range2End >= range1Start;
}

static isStartValidPosition(name: string): boolean {
const startsWithLetterOrUnderscore = /^[A-Za-z_]/.test(name);

return startsWithLetterOrUnderscore;
}

static isValidParameter(name: string): boolean {
/**
*Validates that the name does not contain spaces or disallowed characters
*Assuming the set of disallowed characters includes some special characters,
*you can modify the regex below according to the actual requirements
*/
const containsInvalidChars = /[~!@#$%^&*()+=\-{}\[\]\|:;"'<>,?\/ ]+/.test(name);

const isValidLength = name.length <= 255;

return !containsInvalidChars && isValidLength;
}
}
5 changes: 5 additions & 0 deletions packages/engine-formula/src/basics/common.ts
Expand Up @@ -89,6 +89,10 @@ export interface IDirtyUnitSheetNameMap {
[unitId: string]: Nullable<{ [sheetId: string]: string }>;
}

export interface IDirtyUnitSheetDefinedNameMap {
[unitId: string]: Nullable<{ [name: string]: string }>;
}

export interface IDirtyUnitFeatureMap {
[unitId: string]: Nullable<{ [sheetId: string]: { [featureId: string]: boolean } }>;
}
Expand Down Expand Up @@ -157,6 +161,7 @@ export interface IFormulaDatasetConfig {
forceCalculate: boolean;
dirtyRanges: IUnitRange[];
dirtyNameMap: IDirtyUnitSheetNameMap;
dirtyDefinedNameMap: IDirtyUnitSheetNameMap;
dirtyUnitFeatureMap: IDirtyUnitFeatureMap;
dirtyUnitOtherFormulaMap: IDirtyUnitOtherFormulaMap;
numfmtItemMap: INumfmtItemMap;
Expand Down
5 changes: 5 additions & 0 deletions packages/engine-formula/src/basics/function.ts
Expand Up @@ -116,6 +116,11 @@ export enum FunctionType {
* User-defined functions
*/
User,

/**
* Defined name
*/
DefinedName,
}

export interface IFunctionParam {
Expand Down
Expand Up @@ -14,16 +14,22 @@
* limitations under the License.
*/

import type { IMutation } from '@univerjs/core';
import type { IMutation, IRange } from '@univerjs/core';
import { CommandType } from '@univerjs/core';
import type { IAccessor } from '@wendellhu/redi';
import { IDefinedNamesService } from '../../services/defined-names.service';

export interface ISetDefinedNameMutationSearchParam {
unitId: string;
name: string;
id: string;
}

export interface ISetDefinedNameMutationParam extends ISetDefinedNameMutationSearchParam {
name: string;
formulaOrRefString: string;
comment?: string;
localSheetId?: string;
hidden?: boolean;
}
/**
* In the formula engine, the mutation is solely responsible for communication between the worker and the main thread.
Expand All @@ -35,8 +41,29 @@ export const SetDefinedNameMutation: IMutation<ISetDefinedNameMutationParam> = {
handler: () => true,
};

export const RemoveDefinedNameMutation: IMutation<ISetDefinedNameMutationSearchParam> = {
export const RemoveDefinedNameMutation: IMutation<ISetDefinedNameMutationParam> = {
id: 'formula.mutation.remove-defined-name',
type: CommandType.MUTATION,
handler: () => true,
};

export interface ISetDefinedNameCurrentMutationParam {
unitId: string;
sheetId: string;
range: IRange;
};

export const SetDefinedNameCurrentMutation: IMutation<ISetDefinedNameCurrentMutationParam> = {
id: 'formula.mutation.set-defined-name-current',
type: CommandType.MUTATION,
handler: (accessor: IAccessor, params: ISetDefinedNameCurrentMutationParam) => {
const definedNamesService = accessor.get(IDefinedNamesService);
const { unitId, sheetId, range } = params;
definedNamesService.setCurrentRange({
range,
unitId,
sheetId,
});
return true;
},
};
Expand Up @@ -20,6 +20,7 @@ import { CommandType } from '@univerjs/core';
import type {
IDirtyUnitFeatureMap,
IDirtyUnitOtherFormulaMap,
IDirtyUnitSheetDefinedNameMap,
IDirtyUnitSheetNameMap,
INumfmtItemMap,
IRuntimeOtherUnitDataType,
Expand All @@ -30,6 +31,7 @@ import type { FormulaExecutedStateType, IExecutionInProgressParams } from '../..
export interface ISetFormulaCalculationStartMutation {
dirtyRanges: IUnitRange[];
dirtyNameMap: IDirtyUnitSheetNameMap;
dirtyDefinedNameMap: IDirtyUnitSheetDefinedNameMap;
dirtyUnitFeatureMap: IDirtyUnitFeatureMap;
dirtyUnitOtherFormulaMap: IDirtyUnitOtherFormulaMap;
options: Nullable<IExecutionOptions>;
Expand Down
Expand Up @@ -18,7 +18,7 @@ import type { ICommandInfo, IUnitRange } from '@univerjs/core';
import { Disposable, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, Tools } from '@univerjs/core';
import { Inject } from '@wendellhu/redi';

import type { IDirtyUnitFeatureMap, IDirtyUnitOtherFormulaMap, IDirtyUnitSheetNameMap, IFormulaData, INumfmtItemMap } from '../basics/common';
import type { IDirtyUnitFeatureMap, IDirtyUnitOtherFormulaMap, IDirtyUnitSheetDefinedNameMap, IDirtyUnitSheetNameMap, IFormulaData, INumfmtItemMap } from '../basics/common';
import type { ISetArrayFormulaDataMutationParams } from '../commands/mutations/set-array-formula-data.mutation';
import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-formula-data.mutation';
import type { ISetFormulaCalculationStartMutation } from '../commands/mutations/set-formula-calculation.mutation';
Expand Down Expand Up @@ -71,9 +71,9 @@ export class CalculateController extends Disposable {
if (params.forceCalculation === true) {
this._calculate(true);
} else {
const { dirtyRanges, dirtyNameMap, dirtyUnitFeatureMap, dirtyUnitOtherFormulaMap, numfmtItemMap } = params;
const { dirtyRanges, dirtyNameMap, dirtyDefinedNameMap, dirtyUnitFeatureMap, dirtyUnitOtherFormulaMap, numfmtItemMap } = params;

this._calculate(false, dirtyRanges, dirtyNameMap, dirtyUnitFeatureMap, dirtyUnitOtherFormulaMap, numfmtItemMap);
this._calculate(false, dirtyRanges, dirtyNameMap, dirtyDefinedNameMap, dirtyUnitFeatureMap, dirtyUnitOtherFormulaMap, numfmtItemMap);
}
} else if (command.id === SetArrayFormulaDataMutation.id) {
const params = command.params as ISetArrayFormulaDataMutationParams;
Expand All @@ -94,13 +94,15 @@ export class CalculateController extends Disposable {
forceCalculate: boolean = false,
dirtyRanges: IUnitRange[] = [],
dirtyNameMap: IDirtyUnitSheetNameMap = {},
dirtyDefinedNameMap: IDirtyUnitSheetDefinedNameMap = {},
dirtyUnitFeatureMap: IDirtyUnitFeatureMap = {},
dirtyUnitOtherFormulaMap: IDirtyUnitOtherFormulaMap = {},
numfmtItemMap: INumfmtItemMap = {}
) {
if (
dirtyRanges.length === 0 &&
Object.keys(dirtyNameMap).length === 0 &&
Object.keys(dirtyDefinedNameMap).length === 0 &&
Object.keys(dirtyUnitFeatureMap).length === 0 &&
Object.keys(dirtyUnitOtherFormulaMap).length === 0 &&
forceCalculate === false
Expand All @@ -120,6 +122,7 @@ export class CalculateController extends Disposable {
forceCalculate,
dirtyRanges,
dirtyNameMap,
dirtyDefinedNameMap,
dirtyUnitFeatureMap,
dirtyUnitOtherFormulaMap,
numfmtItemMap,
Expand Down
3 changes: 2 additions & 1 deletion packages/engine-formula/src/controller/formula.controller.ts
Expand Up @@ -21,7 +21,7 @@ import { Inject, Injector } from '@wendellhu/redi';
import type { IFunctionNames } from '../basics/function';
import { RegisterFunctionMutation } from '../commands/mutations/register-function.mutation';
import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-formula-data.mutation';
import { RemoveDefinedNameMutation, SetDefinedNameMutation } from '../commands/mutations/set-defined-name.mutation';
import { RemoveDefinedNameMutation, SetDefinedNameCurrentMutation, SetDefinedNameMutation } from '../commands/mutations/set-defined-name.mutation';
import {
RemoveFeatureCalculationMutation,
SetFeatureCalculationMutation,
Expand Down Expand Up @@ -90,6 +90,7 @@ export class FormulaController extends Disposable {

SetDefinedNameMutation,
RemoveDefinedNameMutation,
SetDefinedNameCurrentMutation,
SetFeatureCalculationMutation,
RemoveFeatureCalculationMutation,

Expand Down
Expand Up @@ -47,15 +47,22 @@ export class SetDefinedNameController extends Disposable {
if (params == null) {
return;
}
const { unitId, name, formulaOrRefString } = params;
this._definedNamesService.registerDefinedName(unitId, name, formulaOrRefString);
const { id, unitId, name, formulaOrRefString, comment, hidden, localSheetId } = params;
this._definedNamesService.registerDefinedName(unitId, {
id,
name: name.trim(),
formulaOrRefString: formulaOrRefString.trim(),
comment: comment?.trim(),
hidden,
localSheetId,
});
} else if (command.id === RemoveDefinedNameMutation.id) {
const params = command.params as ISetDefinedNameMutationSearchParam;
if (params == null) {
return;
}
const { unitId, name } = params;
this._definedNamesService.removeDefinedName(unitId, name);
const { unitId, id } = params;
this._definedNamesService.removeDefinedName(unitId, id);
}
})
);
Expand Down
Expand Up @@ -16,6 +16,7 @@

import { describe, expect, it } from 'vitest';

import { AbsoluteRefType } from '@univerjs/core';
import type { LexerNode } from '../lexer-node';
import { LexerTreeBuilder } from '../lexer-tree-builder';
import { ErrorType } from '../../../basics/error-type';
Expand Down Expand Up @@ -393,4 +394,41 @@ describe('lexer nodeMaker test', () => {
]);
});
});

describe('convertRefersToAbsolute', () => {
it('Formula All', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('=sum(A1:B1,A1:B1,A1:B1,A1:B1)', AbsoluteRefType.ALL, AbsoluteRefType.ALL);
expect(result).toStrictEqual('=sum($A$1:$B$1,$A$1:$B$1,$A$1:$B$1,$A$1:$B$1)');
});

it('Range All', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('A1:B1,A1:B1,A1:B1,A1:B1', AbsoluteRefType.ALL, AbsoluteRefType.ALL);
expect(result).toStrictEqual('$A$1:$B$1,$A$1:$B$1,$A$1:$B$1,$A$1:$B$1');
});

it('Formula Column', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('=sum(A1:B1,A1:B1,A1:B1,A1:B1)', AbsoluteRefType.COLUMN, AbsoluteRefType.COLUMN);
expect(result).toStrictEqual('=sum($A1:$B1,$A1:$B1,$A1:$B1,$A1:$B1)');
});

it('Range Column', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('A1:B1,A1:B1,A1:B1,A1:B1', AbsoluteRefType.COLUMN, AbsoluteRefType.COLUMN);
expect(result).toStrictEqual('$A1:$B1,$A1:$B1,$A1:$B1,$A1:$B1');
});

it('Formula Row', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('=sum(A1:B1,A1:B1,A1:B1,A1:B1)', AbsoluteRefType.ROW, AbsoluteRefType.ROW);
expect(result).toStrictEqual('=sum(A$1:B$1,A$1:B$1,A$1:B$1,A$1:B$1)');
});

it('Range Row', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('A1:B1,A1:B1,A1:B1,A1:B1', AbsoluteRefType.ROW, AbsoluteRefType.ROW);
expect(result).toStrictEqual('A$1:B$1,A$1:B$1,A$1:B$1,A$1:B$1');
});

it('Complex Formula', () => {
const result = lexerTreeBuilder.convertRefersToAbsolute('=SUM(A1:B10) + LAMBDA(x, y, x*y*x)(A1:B10, A10) + MAX(A1:B10,SUM(A2))', AbsoluteRefType.ALL, AbsoluteRefType.ALL);
expect(result).toStrictEqual('=SUM($A$1:$B$10) + LAMBDA(x, y, x*y*x)($A$1:$B$10,$A$10) + MAX($A$1:$B$10,SUM($A$2))');
});
});
});
Expand Up @@ -20,10 +20,10 @@ import type { Injector } from '@wendellhu/redi';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import { IDefinedNamesService } from '../../../services/defined-names.service';
import { IFormulaRuntimeService } from '../../../services/runtime.service';
import { Lexer } from '../lexer';
import type { LexerNode } from '../lexer-node';
import { LexerTreeBuilder } from '../lexer-tree-builder';
import { IFormulaCurrentConfigService } from '../../../services/current-data.service';
import { createCommandTestBed } from './create-command-test-bed';

const TEST_WORKBOOK_DATA: IWorkbookData = {
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('lexer nodeMaker test', () => {
let get: Injector['get'];
let workbook: Workbook;
let definedNamesService: IDefinedNamesService;
let runtimeService: IFormulaRuntimeService;
let formulaCurrentConfigService: IFormulaCurrentConfigService;
let lexerTreeBuilder: LexerTreeBuilder;

beforeEach(() => {
Expand All @@ -79,13 +79,16 @@ describe('lexer nodeMaker test', () => {

definedNamesService = get(IDefinedNamesService);

runtimeService = get(IFormulaRuntimeService);
formulaCurrentConfigService = get(IFormulaCurrentConfigService);

lexerTreeBuilder = get(LexerTreeBuilder);

runtimeService.setCurrent(0, 0, 4, 1, 'sheet1', 'test');
formulaCurrentConfigService.setExecuteUnitId('test');
formulaCurrentConfigService.setExecuteSubUnitId('sheet1');

lexer = new Lexer(definedNamesService, runtimeService, lexerTreeBuilder);
// runtimeService.setCurrent(0, 0, 4, 1, 'sheet1', 'test');

lexer = new Lexer(definedNamesService, lexerTreeBuilder, formulaCurrentConfigService);
});

afterEach(() => {
Expand All @@ -94,7 +97,7 @@ describe('lexer nodeMaker test', () => {

describe('lexer definedName', () => {
it('simple', () => {
definedNamesService.registerDefinedName('test', 'myName', '$A$10:$C$100');
definedNamesService.registerDefinedName('test', { id: 'test1', name: 'myName', formulaOrRefString: '$A$10:$C$100' });

const node = lexer.treeBuilder('=myName') as LexerNode;

Expand All @@ -104,7 +107,7 @@ describe('lexer nodeMaker test', () => {
});

it('lambda', () => {
definedNamesService.registerDefinedName('test', 'myName', 'lambda(x, y , x*x*y)');
definedNamesService.registerDefinedName('test', { id: 'test2', name: 'myName', formulaOrRefString: 'lambda(x, y , x*x*y)' });

const node = lexer.treeBuilder('=myName(1+sum(A1:B1), 100)') as LexerNode;

Expand Down
Expand Up @@ -64,6 +64,7 @@ describe('Test indirect', () => {
numfmtItemMap: {},
dirtyRanges: [],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
Expand Down
14 changes: 14 additions & 0 deletions packages/engine-formula/src/engine/analysis/lexer-node.ts
Expand Up @@ -46,6 +46,8 @@ export class LexerNode {

private _endIndex: number = -1;

private _definedNames: Array<string> = [];

dispose() {
this._children.forEach((node) => {
if (!(typeof node === 'string')) {
Expand All @@ -57,6 +59,10 @@ export class LexerNode {
this._parent = null;
}

getDefinedNames() {
return this._definedNames;
}

getStartIndex() {
return this._startIndex;
}
Expand Down Expand Up @@ -122,6 +128,14 @@ export class LexerNode {
this._endIndex = ed;
}

setDefinedNames(definedNames: Array<string>) {
this._definedNames = definedNames;
}

hasDefinedNames() {
return this._definedNames.length > 0;
}

replaceChild(lexerNode: LexerNode, newLexerNode: LexerNode) {
const i = this._getIndexInParent(lexerNode);

Expand Down

0 comments on commit cfa9375

Please sign in to comment.