Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
laa-odoo committed Jan 9, 2025
1 parent f007715 commit b853485
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 157 deletions.
35 changes: 32 additions & 3 deletions src/components/composer/composer/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { functionRegistry } from "../../../functions/index";
import { clip, setColorAlpha } from "../../../helpers/index";

import { EnrichedToken } from "../../../formulas/composer_tokenizer";
import { argTargeting } from "../../../functions/arguments";
import { Store, useLocalStore, useStore } from "../../../store_engine";
import { DOMFocusableElementStore } from "../../../stores/DOM_focus_store";
import {
Expand Down Expand Up @@ -137,7 +138,7 @@ interface FunctionDescriptionState {
showDescription: boolean;
functionName: string;
functionDescription: FunctionDescription;
argToFocus: number;
argsToFocus: number[];
}

export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv> {
Expand Down Expand Up @@ -179,7 +180,7 @@ export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv>
showDescription: false,
functionName: "",
functionDescription: {} as FunctionDescription,
argToFocus: 0,
argsToFocus: [],
});
assistant = useState({
forcedClosed: false,
Expand Down Expand Up @@ -735,10 +736,38 @@ export class Composer extends Component<CellComposerProps, SpreadsheetChildEnv>
// initialize Formula Assistant
const description = functions[parentFunction];
const argPosition = tokenContext.argPosition;
const nbrArgSupplied = tokenContext.args.length;

this.functionDescriptionState.functionName = parentFunction;
this.functionDescriptionState.functionDescription = description;
this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;

// At this step, we need to indicate in the functionDescriptionState which argument should be focused
// To do that, a helper function 'argTargeting' is used to compute the argument to focus
// But 'argTargeting' depends on the final number of arguments supplied to the function.
// As we are in the composer, the user is probably still typing the formula, so we don't know yet how many arguments the user will supply,
// In consequence, we need to compute not only the argument to focus for the current number of arguments supplied but also for all the possible number of arguments supplied.

const nbrArgSuppliedFirstPossibility = Math.max(nbrArgSupplied, description.minArgRequired);
const nbrArgSuppliedLastPossibility = description.nbrArgRepeating
? description.minArgRequired +
Math.ceil(
(nbrArgSuppliedFirstPossibility - description.minArgRequired) /
description.nbrArgRepeating
) *
description.nbrArgRepeating +
description.nbrArgOptional
: description.maxArgPossible;

const argsToFocus: number[] = [];
for (let i = nbrArgSuppliedFirstPossibility; i <= nbrArgSuppliedLastPossibility; i++) {
const focusedArg = argTargeting(description, i)(argPosition);
if (focusedArg !== undefined) {
argsToFocus.push(focusedArg);
}
}
this.functionDescriptionState.argsToFocus = [...new Set(argsToFocus)];

argTargeting(description, argPosition)(argPosition) ?? -1;
this.functionDescriptionState.showDescription = true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/composer/composer/composer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
t-if="functionDescriptionState.showDescription"
functionName="functionDescriptionState.functionName"
functionDescription="functionDescriptionState.functionDescription"
argToFocus="functionDescriptionState.argToFocus"
argsToFocus="functionDescriptionState.argsToFocus"
/>
<div
t-if="functionDescriptionState.showDescription and autoCompleteState.provider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ css/* scss */ `
interface Props {
functionName: string;
functionDescription: FunctionDescription;
argToFocus: number;
argsToFocus: number[];
}

export class FunctionDescriptionProvider extends Component<Props, SpreadsheetChildEnv> {
static template = "o-spreadsheet-FunctionDescriptionProvider";
static props = {
functionName: String,
functionDescription: Object,
argToFocus: Number,
argsToFocus: Array<Number>,
};

getContext(): Props {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
(
<t t-foreach="context.functionDescription.args" t-as="arg" t-key="arg.name">
<span t-if="arg_index > '0'" t-esc="formulaArgSeparator"/>
<span t-att-class="{ 'o-formula-assistant-focus': context.argToFocus === arg_index }">
<span
t-att-class="{ 'o-formula-assistant-focus': context.argsToFocus.includes(arg_index) }">
<span>
<span t-if="arg.optional || arg.repeating || arg.default">[</span>
<span t-esc="arg.name"/>
Expand Down Expand Up @@ -37,8 +38,8 @@
<div
class="o-formula-assistant-arg p-3 pt-0 display-flex flex-column"
t-att-class="{
'o-formula-assistant-gray': context.argToFocus >= '0',
'o-formula-assistant-focus': context.argToFocus === arg_index,
'o-formula-assistant-gray': context.argsToFocus.length > 0,
'o-formula-assistant-focus': context.argsToFocus.includes(arg_index),
}">
<div>
<span t-esc="arg.name"/>
Expand Down
20 changes: 12 additions & 8 deletions src/formulas/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Token } from ".";
import { argTargeting } from "../functions/arguments";
import { functionRegistry } from "../functions/index";
import { parseNumber, removeStringQuotes, unquote } from "../helpers";
import { _t } from "../translation";
Expand Down Expand Up @@ -125,9 +126,10 @@ function compileTokensOrThrow(tokens: Token[]): CompiledFormula {

const compiledArgs: FunctionCode[] = [];

const argToFocus = argTargeting(functionDefinition, args.length);

for (let i = 0; i < args.length; i++) {
const argToFocus = functionDefinition.getArgToFocus(i + 1) - 1;
const argDefinition = functionDefinition.args[argToFocus];
const argDefinition = functionDefinition.args[argToFocus(i) ?? -1];
const currentArg = args[i];
const argTypes = argDefinition.type || [];

Expand Down Expand Up @@ -344,15 +346,17 @@ function assertEnoughArgs(ast: ASTFuncall) {

const repeatableArgs = functionDefinition.nbrArgRepeating;
if (repeatableArgs > 1) {
const unrepeatableArgs = functionDefinition.args.length - repeatableArgs;
const repeatingArgs = nbrArg - unrepeatableArgs;
if (repeatingArgs % repeatableArgs !== 0) {
const nbrValueRepeating =
repeatableArgs * Math.floor((nbrArg - functionDefinition.minArgRequired) / repeatableArgs);
const nbrValueOptional = nbrArg - functionDefinition.minArgRequired - nbrValueRepeating;
if (nbrValueOptional > functionDefinition.nbrArgOptional) {
throw new BadExpressionError(
_t(
"Invalid number of arguments for the %s function. Expected all arguments after position %s to be supplied by groups of %s arguments",
"Invalid number of arguments for the %s function. Repeatable arguments are expected to be supplied by groups of %s argument(s) with maximum %s optional argument(s), but got %s argument(s) too many.",
functionName,
unrepeatableArgs.toString(),
repeatableArgs.toString()
repeatableArgs.toString(),
functionDefinition.nbrArgOptional.toString(),
nbrValueOptional.toString()
)
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/formulas/composer_tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,11 @@ function mapParentFunction(tokens: EnrichedToken[]): EnrichedToken[] {
pushTokenToFunctionContext(token);
break;
case "ARG_SEPARATOR":
pushTokenToFunctionContext(token);
if (stack.length) {
// increment position on current function
stack[stack.length - 1].argPosition++;
}
pushTokenToFunctionContext(token);
break;
default:
pushTokenToFunctionContext(token);
Expand Down Expand Up @@ -208,8 +208,8 @@ function addArgsAST(tokens: EnrichedToken[]): EnrichedToken[] {
}
for (const argTokens of argsTokens) {
let tokens = argTokens;
if (tokens.at(-1)?.type === "ARG_SEPARATOR") {
tokens = tokens.slice(0, -1);
if (tokens.at(0)?.type === "ARG_SEPARATOR") {
tokens = tokens.slice(1);
}
try {
args.push(parseTokens(tokens));
Expand Down
Loading

0 comments on commit b853485

Please sign in to comment.