diff --git a/src/components/composer/composer/abstract_composer_store.ts b/src/components/composer/composer/abstract_composer_store.ts index 75e863c097..5d756bb53d 100644 --- a/src/components/composer/composer/abstract_composer_store.ts +++ b/src/components/composer/composer/abstract_composer_store.ts @@ -324,11 +324,15 @@ export abstract class AbstractComposerStore extends SpreadsheetStore { hoveredFormula = `=${hoveredFormula}`; } const canonicalFormula = canonicalizeNumberContent(hoveredFormula, this.getters.getLocale()); - const result = this.getters.evaluateFormulaResult(this.sheetId, canonicalFormula); + const result = this.evaluateCanonicalFormula(canonicalFormula); this.hoveredTokens = hoveredContextTokens; this.hoveredContentEvaluation = this.evaluationResultToDisplayString(result); } + protected evaluateCanonicalFormula(canonicalFormula: string) { + return this.getters.evaluateFormulaResult(this.sheetId, canonicalFormula); + } + private getRelatedTokens(tokens: EnrichedToken[], tokenIndex: number): EnrichedToken[] { try { const ast = parseTokens(tokens); diff --git a/src/components/composer/composer/cell_composer_store.ts b/src/components/composer/composer/cell_composer_store.ts index caa24d1ead..3de7e322a2 100644 --- a/src/components/composer/composer/cell_composer_store.ts +++ b/src/components/composer/composer/cell_composer_store.ts @@ -245,7 +245,11 @@ export class CellComposerStore extends AbstractComposerStore { return; } - const evaluated = this.getters.evaluateFormula(this.sheetId, content); + const evaluated = this.getters.evaluateFormula(this.sheetId, content, { + sheetId: this.sheetId, + col: this.col, + row: this.row, + }); if (!isMatrix(evaluated)) { return; } @@ -295,4 +299,12 @@ export class CellComposerStore extends AbstractComposerStore { } return true; } + + protected evaluateCanonicalFormula(canonicalFormula: string) { + return this.getters.evaluateFormulaResult(this.sheetId, canonicalFormula, { + sheetId: this.sheetId, + col: this.col, + row: this.row, + }); + } } diff --git a/src/components/composer/composer/composer.ts b/src/components/composer/composer/composer.ts index e46a419ac1..091ec7b8ac 100644 --- a/src/components/composer/composer/composer.ts +++ b/src/components/composer/composer/composer.ts @@ -26,8 +26,7 @@ import { AutoCompleteStore } from "../autocomplete_dropdown/autocomplete_dropdow import { ContentEditableHelper } from "../content_editable_helper"; import { FunctionDescriptionProvider } from "../formula_assistant/formula_assistant"; import { SpeechBubble } from "../speech_bubble/speech_bubble"; -import { DEFAULT_TOKEN_COLOR } from "./abstract_composer_store"; -import { CellComposerStore } from "./cell_composer_store"; +import { AbstractComposerStore, DEFAULT_TOKEN_COLOR } from "./abstract_composer_store"; const functions = functionRegistry.content; @@ -133,7 +132,7 @@ export interface CellComposerProps { onComposerCellFocused?: (content: String) => void; onInputContextMenu?: (event: MouseEvent) => void; isDefaultFocus?: boolean; - composerStore: Store; + composerStore: Store; placeholder?: string; } diff --git a/src/components/composer/standalone_composer/standalone_composer_store.ts b/src/components/composer/standalone_composer/standalone_composer_store.ts index a7e3484f0b..37454011f4 100644 --- a/src/components/composer/standalone_composer/standalone_composer_store.ts +++ b/src/components/composer/standalone_composer/standalone_composer_store.ts @@ -84,4 +84,12 @@ export class StandaloneComposerStore extends AbstractComposerStore { } return super.getTokenColor(token); } + + hoverToken() { + /** + * Some functions can only be evaluated in the context of the grid, which is not feasible + * in the standalone composer. + */ + return; + } } diff --git a/src/plugins/ui_core_views/cell_evaluation/evaluation_plugin.ts b/src/plugins/ui_core_views/cell_evaluation/evaluation_plugin.ts index 3681050236..0b4ca158c5 100644 --- a/src/plugins/ui_core_views/cell_evaluation/evaluation_plugin.ts +++ b/src/plugins/ui_core_views/cell_evaluation/evaluation_plugin.ts @@ -217,8 +217,12 @@ export class EvaluationPlugin extends CoreViewPlugin { // Getters // --------------------------------------------------------------------------- - evaluateFormula(sheetId: UID, formulaString: string): CellValue | Matrix { - const result = this.evaluateFormulaResult(sheetId, formulaString); + evaluateFormula( + sheetId: UID, + formulaString: string, + cellPosition?: CellPosition + ): CellValue | Matrix { + const result = this.evaluateFormulaResult(sheetId, formulaString, cellPosition); if (isMatrix(result)) { return matrixMap(result, (cell) => cell.value); } @@ -227,9 +231,10 @@ export class EvaluationPlugin extends CoreViewPlugin { evaluateFormulaResult( sheetId: UID, - formulaString: string + formulaString: string, + cellPosition?: CellPosition ): Matrix | FunctionResultObject { - return this.evaluator.evaluateFormulaResult(sheetId, formulaString); + return this.evaluator.evaluateFormulaResult(sheetId, formulaString, cellPosition); } evaluateCompiledFormula( diff --git a/src/plugins/ui_core_views/cell_evaluation/evaluator.ts b/src/plugins/ui_core_views/cell_evaluation/evaluator.ts index fee14fa701..86aad9f9eb 100644 --- a/src/plugins/ui_core_views/cell_evaluation/evaluator.ts +++ b/src/plugins/ui_core_views/cell_evaluation/evaluator.ts @@ -125,6 +125,22 @@ export class Evaluator { } } + private updateCompilationParametersForIsolatedFormula(originCellPosition?: CellPosition) { + this.compilationParams = buildCompilationParameters( + this.context, + this.getters, + this.computeAndSave.bind(this) + ); + this.compilationParams.evalContext.__originCellPosition = originCellPosition; + this.compilationParams.evalContext.updateDependencies = undefined; + this.compilationParams.evalContext.addDependencies = undefined; + this.compilationParams.evalContext.lookupCaches = this.compilationParams.evalContext + .lookupCaches || { + forwardSearch: new Map(), + reverseSearch: new Map(), + }; + } + private updateCompilationParameters() { // rebuild the compilation parameters (with a clean cache) this.compilationParams = buildCompilationParameters( @@ -217,14 +233,15 @@ export class Evaluator { evaluateFormulaResult( sheetId: UID, - formulaString: string + formulaString: string, + originCellPosition?: CellPosition ): FunctionResultObject | Matrix { const compiledFormula = compile(formulaString); const ranges: Range[] = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc) ); - this.updateCompilationParameters(); + this.updateCompilationParametersForIsolatedFormula(originCellPosition); return this.evaluateCompiledFormula(sheetId, { ...compiledFormula, dependencies: ranges, diff --git a/tests/composer/composer_hover.test.ts b/tests/composer/composer_hover.test.ts index 2c92afa10d..6f75b8bea5 100644 --- a/tests/composer/composer_hover.test.ts +++ b/tests/composer/composer_hover.test.ts @@ -1,16 +1,19 @@ import { SpreadsheetChildEnv } from "../../src"; import { CellComposerStore } from "../../src/components/composer/composer/cell_composer_store"; +import { toZone } from "../../src/helpers"; import { Model } from "../../src/model"; import { Store } from "../../src/store_engine"; import { setCellContent, setFormat, updateLocale } from "../test_helpers/commands_helpers"; import { FR_LOCALE } from "../test_helpers/constants"; -import { getElStyle, keyDown, triggerMouseEvent } from "../test_helpers/dom_helper"; +import { click, getElStyle, keyDown, triggerMouseEvent } from "../test_helpers/dom_helper"; import { getEvaluatedCell } from "../test_helpers/getters_helpers"; import { ComposerWrapper, + editStandaloneComposer, mountComposerWrapper, mountSpreadsheet, nextTick, + toRangeData, typeInComposerGrid, typeInComposerHelper, } from "../test_helpers/helpers"; @@ -363,4 +366,46 @@ describe("Composer hover integration test", () => { expect(".o-grid-composer .o-composer.active").toHaveCount(1); expect(".o-speech-bubble").toHaveCount(0); }); + + test("Can hover functions that require the cell position as context in grid composer", async () => { + setCellContent(model, "B2", "5"); + await typeInComposerGrid("=ROW() + COLUMN() + B2"); + await hoverComposerContent("ROW"); + expect(".o-speech-bubble").toHaveText("1"); + await hoverComposerContent("COLUMN"); + expect(".o-speech-bubble").toHaveText("1"); + await hoverComposerContent("="); + expect(".o-speech-bubble").toHaveText("7"); + }); + + test("Hover is deactivated in a standalone composer", async () => { + const sheetId = model.getters.getActiveSheetId(); + model.dispatch("ADD_CONDITIONAL_FORMAT", { + cf: { + rule: { + type: "CellIsRule", + operator: "Equal", + values: ["=ROW() + COLUMN() + B2"], + style: { fillColor: "#b6d7a8" }, + }, + id: "1", + }, + ranges: [toRangeData(sheetId, "B1:D3")], + sheetId: sheetId, + }); + + env.openSidePanel("ConditionalFormatting", { selection: [toZone("B1")] }); + await nextTick(); + + const composerSelector = ".o-sidePanel .o-composer"; + await editStandaloneComposer(composerSelector, "=ROW() + COLUMN() + B2"); + await click(fixture, composerSelector); + + await hoverComposerContent("ROW"); + expect(".o-speech-bubble").toHaveCount(0); + await hoverComposerContent("COLUMN"); + expect(".o-speech-bubble").toHaveCount(0); + await hoverComposerContent("="); + expect(".o-speech-bubble").toHaveCount(0); + }); }); diff --git a/tests/composer/composer_store.test.ts b/tests/composer/composer_store.test.ts index a2c15fe1ab..b346b10da7 100644 --- a/tests/composer/composer_store.test.ts +++ b/tests/composer/composer_store.test.ts @@ -1086,6 +1086,28 @@ describe("edition", () => { expect(getCellContent(model, cellOnLastCol)).toBe("0"); }); + describe("Row addition works with position dependant functions", () => { + test("Adding rows below with ROW function", () => { + const sheetId = model.getters.getActiveSheetId(); + const numberOfRows = model.getters.getNumberRows(sheetId); + + const cellOnLastRow = toXC(0, numberOfRows - 1); + editCell(model, cellOnLastRow, "=MUNIT(ROW())"); + expect(model.getters.getNumberRows(sheetId)).toBe(numberOfRows * 2 + 50 - 1); + expect(getCellContent(model, cellOnLastRow)).toBe("1"); + }); + + test("Adding rows below with ROW function", () => { + const sheetId = model.getters.getActiveSheetId(); + const numberOfCols = model.getters.getNumberCols(sheetId); + + const cellOnLastCols = toXC(numberOfCols - 1, 0); + editCell(model, cellOnLastCols, "=MUNIT(COLUMN())"); + expect(model.getters.getNumberCols(sheetId)).toBe(numberOfCols * 2 + 20 - 1); + expect(getCellContent(model, cellOnLastCols)).toBe("1"); + }); + }); + test("Can undo/redo after adding a spreading formula at the end of the sheet", () => { const sheetId = model.getters.getActiveSheetId(); const numberOfCols = model.getters.getNumberCols(sheetId);