From 0b289a4dc9d6c62667bfffce043f6238ea941cb3 Mon Sep 17 00:00:00 2001 From: Rashesh Date: Mon, 26 Aug 2024 20:17:44 +0530 Subject: [PATCH] calc: fix: named-range can't be used in function like SUM - also fixes the bug where nested formulas were not getting autocompleted Signed-off-by: Rashesh Change-Id: I80433d244296c14ce610e02a1f544bd9e01c1c91 --- .../Control.FormulaAutoCompletePopup.ts | 85 +++++++++++++++++-- browser/src/layer/tile/CanvasTileLayer.js | 23 ++++- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/browser/src/control/Control.FormulaAutoCompletePopup.ts b/browser/src/control/Control.FormulaAutoCompletePopup.ts index fe1263f45d91..ce3844ac28e1 100644 --- a/browser/src/control/Control.FormulaAutoCompletePopup.ts +++ b/browser/src/control/Control.FormulaAutoCompletePopup.ts @@ -60,15 +60,90 @@ class FormulaAutoCompletePopup extends L.Control.AutoCompletePopup { return entries; } + getAutocompleteText( + currentCellFormula: string, + functionName: string, + endIndex: number, + ): string { + // Step-1: Find indexes of all the '(', ';', '-', '+', '*', '/' + const openBracketIndex: number[] = []; + const semicolonIndex: number[] = []; + const plusIndex: number[] = []; + const multiplyIndex: number[] = []; + const divideIndex: number[] = []; + const minusIndex: number[] = []; + const equalIndex: number = 0; + + for (let i = 0; i < currentCellFormula.length; i++) { + const char = currentCellFormula.charAt(i); + if (char === '(') openBracketIndex.push(i); + else if (char === ';') semicolonIndex.push(i); + else if (char === '+') plusIndex.push(i); + else if (char === '*') multiplyIndex.push(i); + else if (char === '/') divideIndex.push(i); + else if (char === '-') minusIndex.push(i); + } + + // Step-2: Find smallest difference between endIndex and indexes of all the '(', ';' + // that will give us the startIndex + let minDiff: number = Number.MAX_VALUE; + let startIndex: number; + + const updateMinDiff = (index: number) => { + const tmp = endIndex - index; + if (tmp >= 0 && tmp < minDiff) { + minDiff = tmp; + startIndex = index + 1; + } + }; + + updateMinDiff(equalIndex); + openBracketIndex.forEach(updateMinDiff); + semicolonIndex.forEach(updateMinDiff); + plusIndex.forEach(updateMinDiff); + minusIndex.forEach(updateMinDiff); + multiplyIndex.forEach(updateMinDiff); + divideIndex.forEach(updateMinDiff); + + // Step-3: extract the text we want to complete using startIndex and endIndex + const partialText: string = currentCellFormula + .substring(startIndex, endIndex + 1) + .trim(); + + // Step-4: compare partialText and functionName to find remaining text need to autocomplete + let autoCompleteFunctionName: string = ''; + for ( + let i = 0; + i < Math.max(partialText.length, functionName.length); + i++ + ) { + if ( + partialText.charAt(i).toLowerCase() != + functionName.charAt(i).toLowerCase() + ) { + autoCompleteFunctionName = functionName.substring(i); + break; + } + } + + return autoCompleteFunctionName; + } + callback(objectType: any, eventType: any, object: any, index: number) { if (eventType === 'close') { this.closePopup(); } else if (eventType === 'select' || eventType === 'activate') { - var currentText = this.map._docLayer._lastFormula; - var chIndex = currentText.length - 1; - var functionName = this.functionList[index].name; - var namedRange = this.functionList[index].namedRange; - functionName = functionName.substring(chIndex); + const namedRange: string = this.functionList[index].namedRange; + const currentText: string = this.map._docLayer._lastFormula; + const addedCharacterIndex: number = + this.map._docLayer._newFormulaDiffIndex; + + const functionName: string = this.getAutocompleteText( + currentText, + this.functionList[index].name, + addedCharacterIndex, + ); + if (namedRange) this.map._textInput._sendText(functionName); else this.map._textInput._sendText(functionName + '('); this.closePopup(); diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index ce7fbdf0f1d7..690ca001e4cd 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -1794,9 +1794,26 @@ L.CanvasTileLayer = L.Layer.extend({ // This is done because coolwsd will send several 'cellformula' // messages during text composition, and resetting the contents // of the clipboard container mid-composition will easily break it. - var formula = textMsg.substring(13); - this._lastFormula = formula; - this._map.fire('cellformula', {formula: formula}); + + let newFormula = textMsg.substring(13); + if (this._lastFormula) { + let minLength = Math.min(newFormula.length, this._lastFormula.length); + let index = -1; + for (let i = 0; i < minLength; i++) { + if (newFormula.charAt(i) !== this._lastFormula.charAt(i)) { + index = i; + break; + } + } + if (index === -1) + index = minLength; + + // newFormulaDiffIndex have index of last added character in formula + // It is used during Formula Autocomplete to find partial remaining text + this._newFormulaDiffIndex = index; + } + this._lastFormula = newFormula; + this._map.fire('cellformula', {formula: newFormula}); }, _onCalcFunctionUsageMsg: function (textMsg) {