From ad56b2975fdac55429258974cf9d55a50cb5f073 Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Thu, 30 Nov 2023 13:31:30 +0100 Subject: [PATCH] Allow completion of children variables with string literals when needed --- .../Events/Parsers/ExpressionParser2NodePrinter.h | 15 +++++++++------ .../IDE/Events/ExpressionCompletionFinder.h | 15 +++++++++++---- .../IDE/Events/ExpressionVariableParentFinder.h | 1 - .../__tests__/ExpressionCompletionFinder.spec.js | 6 +++++- .../ExpressionAutocompletionsDisplayer.js | 4 +++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2NodePrinter.h b/Core/GDCore/Events/Parsers/ExpressionParser2NodePrinter.h index aa6ccb311b71..7a03d8078a72 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2NodePrinter.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2NodePrinter.h @@ -8,6 +8,7 @@ #include #include + #include "GDCore/Events/Parsers/ExpressionParser2Node.h" #include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h" namespace gd { @@ -43,6 +44,11 @@ class GD_CORE_API ExpressionParser2NodePrinter */ const gd::String& GetOutput() { return output; }; + static gd::String PrintStringLiteral(const gd::String& str) { + return "\"" + + str.FindAndReplace("\\", "\\\\").FindAndReplace("\"", "\\\"") + "\""; + } + protected: void OnVisitSubExpressionNode(SubExpressionNode& node) override { output += "("; @@ -69,10 +75,7 @@ class GD_CORE_API ExpressionParser2NodePrinter } void OnVisitNumberNode(NumberNode& node) override { output += node.number; } void OnVisitTextNode(TextNode& node) override { - output += - "\"" + - node.text.FindAndReplace("\\", "\\\\").FindAndReplace("\"", "\\\"") + - "\""; + output += PrintStringLiteral(node.text); } void OnVisitVariableNode(VariableNode& node) override { output += node.name; @@ -97,8 +100,8 @@ class GD_CORE_API ExpressionParser2NodePrinter } void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override { if (!node.behaviorFunctionName.empty()) { - output += - node.objectName + "." + node.objectFunctionOrBehaviorName + "::" + node.behaviorFunctionName; + output += node.objectName + "." + node.objectFunctionOrBehaviorName + + "::" + node.behaviorFunctionName; } else { output += node.objectName + "." + node.objectFunctionOrBehaviorName; } diff --git a/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h b/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h index 948d6e985dcd..b4460df23543 100644 --- a/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h +++ b/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h @@ -615,6 +615,8 @@ class GD_CORE_API ExpressionCompletionFinder } else { // Object function, behavior name, variable, object variable. if (IsCaretOn(node.identifierNameLocation)) { + // Don't attempt to complete children variables if there is + // already a dot written (`MyVariable.`). bool eagerlyCompleteIfPossible = !node.identifierNameDotLocation.IsValid(); AddCompletionsForAllIdentifiersMatchingSearch( @@ -833,8 +835,8 @@ class GD_CORE_API ExpressionCompletionFinder } /** - * A slightly less strict check than `gd::Project::IsNameSafe` as child variables can be completed - * even if they start with a number. + * A slightly less strict check than `gd::Project::IsNameSafe` as child + * variables can be completed even if they start with a number. */ bool IsIdentifierSafe(const gd::String& name) { if (name.empty()) return false; @@ -898,14 +900,19 @@ class GD_CORE_API ExpressionCompletionFinder if (variable.GetType() == gd::Variable::Structure) { gd::String prefix = variableName + "."; for (const auto& name : variable.GetAllChildrenNames()) { - if (!IsIdentifierSafe(name)) continue; + gd::String completion = + IsIdentifierSafe(name) + ? (prefix + name) + : (variableName + "[" + + gd::ExpressionParser2NodePrinter::PrintStringLiteral(name) + + "]"); const auto& childVariable = variable.GetChild(name); ExpressionCompletionDescription description( ExpressionCompletionDescription::Variable, location.GetStartPosition(), location.GetEndPosition()); - description.SetCompletion(prefix + name); + description.SetCompletion(completion); description.SetVariableType(childVariable.GetType()); completions.push_back(description); } diff --git a/Core/GDCore/IDE/Events/ExpressionVariableParentFinder.h b/Core/GDCore/IDE/Events/ExpressionVariableParentFinder.h index f0572ab406e0..6db7eb724a02 100644 --- a/Core/GDCore/IDE/Events/ExpressionVariableParentFinder.h +++ b/Core/GDCore/IDE/Events/ExpressionVariableParentFinder.h @@ -347,7 +347,6 @@ class GD_CORE_API ExpressionVariableParentFinder const gd::String& firstChildName = *childVariableNames.begin(); -// TODO: move again? const gd::Variable* variable = variablesContainer.Has(firstChildName) ? &variablesContainer.Get(firstChildName) : nullptr; if (childVariableNames.size() == 1 || !variable) diff --git a/GDevelop.js/__tests__/ExpressionCompletionFinder.spec.js b/GDevelop.js/__tests__/ExpressionCompletionFinder.spec.js index 048d7469a1c4..6d648adcd812 100644 --- a/GDevelop.js/__tests__/ExpressionCompletionFinder.spec.js +++ b/GDevelop.js/__tests__/ExpressionCompletionFinder.spec.js @@ -83,7 +83,7 @@ describe('gd.ExpressionCompletionFinder', function () { structureChild1.getChild('Child1StructureChild2'); structureChild1.getChild('Child1StructureChild3'); structureChild1.getChild( - 'Child1 Unsafe Because of Spaces so not listed' + 'Child1Structure with unsafe "`/+ characters and spaces' ); // With a child array, containing 2 structures and a number. @@ -272,6 +272,7 @@ describe('gd.ExpressionCompletionFinder', function () { ).toMatchInlineSnapshot(` [ "{ 3, no type, 3, no prefix, Child1Structure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", + "{ 3, no type, 1, no prefix, Child1Structure["Child1Structure with unsafe \\"\`/+ characters and spaces"], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild1, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild2, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild3, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", @@ -317,6 +318,7 @@ describe('gd.ExpressionCompletionFinder', function () { ).toMatchInlineSnapshot(` [ "{ 3, no type, 3, no prefix, Child1Structure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", + "{ 3, no type, 1, no prefix, Child1Structure["Child1Structure with unsafe \\"\`/+ characters and spaces"], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild1, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild2, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild3, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", @@ -829,6 +831,7 @@ describe('gd.ExpressionCompletionFinder', function () { ).toMatchInlineSnapshot(` [ "{ 3, no type, 3, no prefix, Child1Structure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", + "{ 3, no type, 1, no prefix, Child1Structure["Child1Structure with unsafe \\"\`/+ characters and spaces"], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild1, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild2, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild3, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", @@ -865,6 +868,7 @@ describe('gd.ExpressionCompletionFinder', function () { ).toMatchInlineSnapshot(` [ "{ 3, no type, 3, no prefix, Child1Structure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", + "{ 3, no type, 1, no prefix, Child1Structure["Child1Structure with unsafe \\"\`/+ characters and spaces"], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild1, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild2, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", "{ 3, no type, 1, no prefix, Child1Structure.Child1StructureChild3, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }", diff --git a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/ExpressionAutocompletionsDisplayer.js b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/ExpressionAutocompletionsDisplayer.js index 3cbec4c9b78e..a5e6a6e182ef 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/ExpressionAutocompletionsDisplayer.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/ExpressionAutocompletionsDisplayer.js @@ -98,6 +98,8 @@ const AutocompletionRow = React.forwardRef( |}, ref ) => { + const trimmedLabel = label.length > 46 ? label.substr(0, 46) + '…' : label; + return ( : null)} - {isSelected ? {label} : label} + {isSelected ? {trimmedLabel} : trimmedLabel} {parametersLabel && ( <> ({parametersLabel})