Improve variable parameter autocompletion (#9164)

This commit is contained in:
D8H
2026-10-08 18:21:33 +02:00
committed by GitHub
parent 0ea60bd34a
commit 57cfbb2982
23 changed files with 1131 additions and 1145 deletions
@@ -378,6 +378,8 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& rootType,
const gd::String& rootExtraInfo,
const gd::String& rootObjectName,
gd::ExpressionNode& node,
size_t searchedPosition) {
gd::ExpressionNodeLocationFinder finder(searchedPosition);
@@ -394,6 +396,8 @@ class GD_CORE_API ExpressionCompletionFinder
platform,
projectScopedContainers,
rootType,
rootExtraInfo,
rootObjectName,
searchedPosition,
maybeParentNodeAtLocation);
nodeAtLocation->Visit(autocompletionProvider);
@@ -504,8 +508,11 @@ class GD_CORE_API ExpressionCompletionFinder
void OnVisitVariableNode(VariableNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, rootType, node);
const auto typeAndExtraInfo = gd::ExpressionTypeFinder::GetTypeAndExtraInfo(
platform, projectScopedContainers, rootType, rootExtraInfo, node);
const auto type = typeAndExtraInfo.type;
const auto extraInfo = typeAndExtraInfo.extraInfo;
// Only attempt to complete with the children of the variable
// if it's the last child (no more `.AnotherVariable` written after).
@@ -521,7 +528,7 @@ class GD_CORE_API ExpressionCompletionFinder
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, "", node.name, node.nameLocation,
*variablesContainer, extraInfo, node.name, node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else if (type == "objectvar") {
@@ -529,7 +536,7 @@ class GD_CORE_API ExpressionCompletionFinder
platform, objectsContainersList, rootObjectName, node);
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, "", node.name, node.nameLocation,
objectsContainersList, objectName, extraInfo, node.name, node.nameLocation,
eagerlyCompleteIfExactMatch);
}
} else {
@@ -543,7 +550,7 @@ class GD_CORE_API ExpressionCompletionFinder
platform, projectScopedContainers, node);
const auto typeAndExtraInfo = gd::ExpressionTypeFinder::GetTypeAndExtraInfo(
platform, projectScopedContainers, rootType, node);
platform, projectScopedContainers, rootType, rootExtraInfo, node);
const auto type = typeAndExtraInfo.type;
const auto extraInfo = typeAndExtraInfo.extraInfo;
@@ -558,151 +565,11 @@ class GD_CORE_API ExpressionCompletionFinder
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
const auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
const auto typeAndExtraInfo = gd::ExpressionTypeFinder::GetTypeAndExtraInfo(
platform, projectScopedContainers, rootType, node);
const auto type = typeAndExtraInfo.type;
const auto extraInfo = typeAndExtraInfo.extraInfo;
if (gd::ParameterMetadata::IsObject(type)) {
// Only show completions of objects if an object is required.
AddCompletionsForObjectMatchingSearch(
node.identifierName, type, node.location);
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
if (variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
variablesContainer->Get(node.identifierName), extraInfo,
node.childIdentifierNameLocation, node.childIdentifierName);
}
} else {
// Complete a root variable of the scene or project.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, extraInfo, node.identifierName,
node.identifierNameLocation, eagerlyCompleteIfPossible);
}
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, rootObjectName, node);
if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Complete a potential child variable:
const auto* variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
if (variablesContainer &&
variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
variablesContainer->Get(node.identifierName), extraInfo,
node.childIdentifierNameLocation, node.childIdentifierName);
}
} else {
// Complete a root variable of the object.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!node.identifierNameDotLocation.IsValid();
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, extraInfo, node.identifierName,
node.identifierNameLocation, eagerlyCompleteIfPossible);
}
}
} 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(
node.identifierName,
type,
extraInfo,
node.identifierNameLocation,
eagerlyCompleteIfPossible);
if (!node.identifierNameDotLocation.IsValid() &&
!gd::ValueTypeMetadata::IsTypeExpression("variable", type)) {
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type, node.identifierName,
node.identifierNameLocation.GetStartPosition(),
node.identifierNameLocation.GetEndPosition()));
}
} else if (IsCaretOn(node.identifierNameDotLocation) ||
IsCaretOn(node.childIdentifierNameLocation)) {
// Might be:
// - An object variable, object behavior or object expression.
// - Or a variable with a child.
projectScopedContainers.MatchIdentifierWithName<void>(
node.identifierName,
[&]() {
// This is an object.
const gd::String& objectName = node.identifierName;
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, extraInfo,
node.childIdentifierName, node.childIdentifierNameLocation,
true);
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.childIdentifierName,
node.childIdentifierNameLocation.GetStartPosition(),
node.childIdentifierNameLocation.GetEndPosition(),
objectName));
},
[&]() {
// This is a variable.
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariablePathFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
AddCompletionsForChildrenVariablesOf(
variableAndItsParent,
extraInfo,
node.childIdentifierNameLocation,
node.childIdentifierName);
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of parameters.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
void OnVisitIdentifierNode(IdentifierNode &node) override {
AddAutocompletionForIdentifier(
node, node.identifierName, node.childIdentifierName,
node.identifierNameLocation, node.identifierNameDotLocation,
node.childIdentifierNameLocation);
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(
@@ -827,17 +694,9 @@ class GD_CORE_API ExpressionCompletionFinder
}
}
void OnVisitEmptyNode(EmptyNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, rootType, node);
AddCompletionsForAllIdentifiersMatchingSearch(
node.text, type, "", node.location);
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
node.text,
node.location.GetStartPosition(),
node.location.GetEndPosition()));
gd::ExpressionParserLocation invalidLocation;
AddAutocompletionForIdentifier(node, "", "", node.location, invalidLocation,
invalidLocation);
}
private:
@@ -1182,10 +1041,164 @@ class GD_CORE_API ExpressionCompletionFinder
});
}
void AddAutocompletionForIdentifier(gd::ExpressionNode &node,
const gd::String &identifierName,
const gd::String &childIdentifierName,
gd::ExpressionParserLocation identifierNameLocation,
gd::ExpressionParserLocation identifierNameDotLocation,
gd::ExpressionParserLocation childIdentifierNameLocation) {
const auto &objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
const auto typeAndExtraInfo = gd::ExpressionTypeFinder::GetTypeAndExtraInfo(
platform, projectScopedContainers, rootType, rootExtraInfo, node);
const auto type = typeAndExtraInfo.type;
const auto extraInfo = typeAndExtraInfo.extraInfo;
if (gd::ParameterMetadata::IsObject(type)) {
// Only show completions of objects if an object is required.
AddCompletionsForObjectMatchingSearch(
identifierName, type, node.location);
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
type == "globalvar"
? projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer()
: projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
if (variablesContainer) {
if (IsCaretOn(identifierNameDotLocation) ||
IsCaretOn(childIdentifierNameLocation)) {
// Complete a potential child variable:
if (variablesContainer->Has(identifierName)) {
AddCompletionsForChildrenVariablesOf(
variablesContainer->Get(identifierName), extraInfo,
childIdentifierNameLocation, childIdentifierName);
}
} else {
// Complete a root variable of the scene or project.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!identifierNameDotLocation.IsValid();
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, extraInfo, identifierName,
identifierNameLocation, eagerlyCompleteIfPossible);
}
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, rootObjectName, node);
if (IsCaretOn(identifierNameDotLocation) ||
IsCaretOn(childIdentifierNameLocation)) {
// Complete a potential child variable:
const auto* variablesContainer =
objectsContainersList.GetObjectOrGroupVariablesContainer(
objectName);
if (variablesContainer &&
variablesContainer->Has(identifierName)) {
AddCompletionsForChildrenVariablesOf(
variablesContainer->Get(identifierName), extraInfo,
childIdentifierNameLocation, childIdentifierName);
}
} else {
// Complete a root variable of the object.
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!identifierNameDotLocation.IsValid();
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, extraInfo, identifierName,
identifierNameLocation, eagerlyCompleteIfPossible);
}
}
} else {
// Object function, behavior name, variable, object variable.
if (IsCaretOn(identifierNameLocation) || identifierName.empty()) {
// Don't attempt to complete children variables if there is
// already a dot written (`MyVariable.`).
bool eagerlyCompleteIfPossible =
!identifierNameDotLocation.IsValid();
AddCompletionsForAllIdentifiersMatchingSearch(
identifierName,
type,
extraInfo,
identifierNameLocation,
eagerlyCompleteIfPossible);
if (!identifierNameDotLocation.IsValid() &&
!gd::ValueTypeMetadata::IsTypeExpression("variable", type)) {
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type, identifierName,
identifierNameLocation.GetStartPosition(),
identifierNameLocation.GetEndPosition()));
}
} else if (IsCaretOn(identifierNameDotLocation) ||
IsCaretOn(childIdentifierNameLocation)) {
// Might be:
// - An object variable, object behavior or object expression.
// - Or a variable with a child.
projectScopedContainers.MatchIdentifierWithName<void>(
identifierName,
[&]() {
// This is an object.
const gd::String& objectName = identifierName;
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, extraInfo,
childIdentifierName, childIdentifierNameLocation,
true);
completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
childIdentifierName,
childIdentifierNameLocation.GetStartPosition(),
childIdentifierNameLocation.GetEndPosition(),
objectName));
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
type,
childIdentifierName,
childIdentifierNameLocation.GetStartPosition(),
childIdentifierNameLocation.GetEndPosition(),
objectName));
},
[&]() {
// This is a variable.
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariablePathFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);
AddCompletionsForChildrenVariablesOf(
variableAndItsParent,
extraInfo,
childIdentifierNameLocation,
childIdentifierName);
},
[&]() {
// Ignore properties here.
// There is no support for "children" of properties.
},
[&]() {
// Ignore parameters here.
// There is no support for "children" of parameters.
},
[&]() {
// Ignore unrecognised identifiers here.
});
}
}
}
ExpressionCompletionFinder(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String& rootType_,
const gd::String& rootExtraInfo_,
const gd::String& rootObjectName_,
size_t searchedPosition_,
gd::ExpressionNode* maybeParentNodeAtLocation_)
: searchedPosition(searchedPosition_),
@@ -1193,8 +1206,8 @@ class GD_CORE_API ExpressionCompletionFinder
platform(platform_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
rootObjectName("") // Always empty, might be changed if variable fields
// in the editor are changed to use completion.
rootExtraInfo(rootExtraInfo_),
rootObjectName(rootObjectName_)
{};
std::vector<ExpressionCompletionDescription> completions;
@@ -1204,6 +1217,7 @@ class GD_CORE_API ExpressionCompletionFinder
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String rootType;
const gd::String rootExtraInfo;
const gd::String rootObjectName;
};
+17 -14
View File
@@ -58,7 +58,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, projectScopedContainers, rootType);
platform, projectScopedContainers, rootType, emptyExtraInfo);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -70,9 +70,10 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
static const gd::TypeAndExtraInfo GetTypeAndExtraInfo(
const gd::Platform &platform,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType, gd::ExpressionNode &node) {
const gd::String &rootType, const gd::String &rootExtraInfo,
gd::ExpressionNode &node) {
gd::ExpressionTypeFinder typeFinder(platform, projectScopedContainers,
rootType);
rootType, rootExtraInfo);
node.Visit(typeFinder);
return {.type = typeFinder.GetType(),
.extraInfo = typeFinder.GetExtraInfo()};
@@ -81,18 +82,17 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
virtual ~ExpressionTypeFinder(){};
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
ExpressionTypeFinder(
const gd::Platform &platform_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_, const gd::String &rootExtraInfo_)
: platform(platform_), projectScopedContainers(projectScopedContainers_),
rootType(rootType_), rootExtraInfo(rootExtraInfo_),
type(ExpressionTypeFinder::unknownType), child(nullptr) {};
const gd::String &GetType() {
return gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(type);
};
const gd::String &GetType() {
return gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(type);
};
const gd::String &GetExtraInfo() {
return extraInfo;
@@ -194,10 +194,12 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
}
else {
type = rootType;
extraInfo = rootExtraInfo;
}
}
else {
type = rootType;
extraInfo = rootExtraInfo;
}
}
@@ -215,6 +217,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::Platform &platform;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
const gd::String rootExtraInfo;
};
} // namespace gd
@@ -108,7 +108,12 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
variableNode = &node;
node.parent->Visit(*this);
}
void OnVisitEmptyNode(EmptyNode& node) override {}
void OnVisitEmptyNode(EmptyNode& node) override {
if (node.parent == nullptr) {
objectName = rootObjectName;
return;
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {}
+5
View File
@@ -489,6 +489,11 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
"GetVariableAsNumber", "Get me a variable value", "", "", "")
.AddParameter("scenevar", "Scene variable")
.SetFunctionName("returnVariable");
extension
->AddExpression(
"GetGlobalVariableAsNumber", "Get me a variable value", "", "", "")
.AddParameter("globalvar", "Global variable")
.SetFunctionName("returnGlobalVariable");
extension
->AddExpression(
"GetAnyVariableAsNumber", "Get me a variable value", "", "", "")
+588 -338
View File
@@ -21,6 +21,18 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetVariables().InsertNew("myGlobalVariable");
{
auto &structureVariable = project.GetVariables().InsertNew("myGlobalStructure");
structureVariable.GetChild("myNumberChild").SetValue(0);
structureVariable.GetChild("myStructureChild")
.GetChild("myNumberChild")
.SetValue(0);
structureVariable.GetChild("myStructureChild")
.GetChild("myStructureChild")
.GetChild("myNumberChild")
.SetValue(0);
}
auto &layout1 = project.InsertNewLayout("Layout1", 0);
layout1.GetVariables().InsertNew("myVariable");
{
@@ -56,12 +68,15 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
auto getCompletionsFor = [&](const gd::String& type,
const gd::String& expression,
size_t location) {
size_t location,
const gd::String& rootExtraInfo = "",
const gd::String& rootObjectName = "") {
auto node = parser.ParseExpression(expression);
REQUIRE(node != nullptr);
auto completions =
gd::ExpressionCompletionFinder::GetCompletionDescriptionsFor(
platform, projectScopedContainers, type, *node, location);
platform, projectScopedContainers, type, rootExtraInfo,
rootObjectName, *node, location);
std::vector<gd::String> completionsAsString;
for (const auto& completion : completions) {
@@ -79,6 +94,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, string, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "My", 0, 2).ToString()
};
// clang-format on
@@ -92,6 +109,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, number, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number", "My", 0, 2).ToString()
};
// clang-format on
@@ -105,6 +124,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, number|string, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number|string", "My", 0, 2).ToString()
};
// clang-format on
@@ -121,6 +142,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, string, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("string", "My", 0, 2).ToString()
};
// clang-format on
@@ -143,6 +166,8 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, number, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("number", "My", 0, 2).ToString()
};
// clang-format on
@@ -197,7 +222,565 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) ==
expectedEmptyCompletions);
}
SECTION("Empty variable name") {
SECTION("Empty variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "", 0) == expectedCompletions);
}
SECTION("Empty variableOrProperty parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variableOrProperty", "", 0) ==
expectedCompletions);
}
SECTION("Empty variableOrPropertyOrParameter parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variableOrPropertyOrParameter", "", 0) ==
expectedCompletions);
}
SECTION("Empty object variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "", 0, "", "MyObject") ==
expectedCompletions);
}
SECTION("Empty object variable parameter with unknown object") {
// clang-format off
std::vector<gd::String> expectedCompletions{};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "", 0) ==
expectedCompletions);
}
SECTION("Empty legacy scene variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("scenevar", "", 0) ==
expectedCompletions);
}
SECTION("Empty legacy global variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("globalvar", "", 0) ==
expectedCompletions);
}
}
SECTION("Variable partial name") {
SECTION("Variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "my", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetAnyVariableAsNumber(my",
36) == expectedCompletions);
}
SECTION("Primitive variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "my", 0, "primitive") ==
expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetPrimitiveVariableAsNumber(my",
42) == expectedCompletions);
}
SECTION("Collection variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "my", 0, "collection") ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number", "MyExtension::GetCollectionVariableAsNumber(my",
43) == expectedCompletions);
}
SECTION("Legacy scene variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("scenevar", "my", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(my",
33) == expectedCompletions);
}
SECTION("Legacy global variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("globalvar", "my", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetGlobalVariableAsNumber(my",
40) == expectedCompletions);
}
}
SECTION("Variable full name") {
SECTION("Variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure", 0) ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number", "MyExtension::GetAnyVariableAsNumber(myStructure",
36) == expectedCompletions);
}
SECTION("Primitive variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure", 0, "primitive") ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetPrimitiveVariableAsNumber(myStructure",
42) == expectedCompletions);
}
SECTION("Collection variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure", 0, "collection") ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetCollectionVariableAsNumber(myStructure",
43) == expectedCompletions);
}
SECTION("Legacy scene variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("scenevar", "myStructure", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myStructure",
33) == expectedCompletions);
}
SECTION("Legacy global variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("globalvar", "myGlobalStructure", 0) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetGlobalVariableAsNumber(myGlobalStructure",
40) == expectedCompletions);
}
}
SECTION("Child variable partial name") {
SECTION("Variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.", 11) ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetAnyVariableAsNumber(myStructure.",
47) == expectedCompletions);
}
SECTION("Primitive variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.", 11, "primitive") ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetPrimitiveVariableAsNumber(myStructure.",
53) == expectedCompletions);
}
SECTION("Collection variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.", 11, "collection") ==
expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetCollectionVariableAsNumber(myStructure.",
54) == expectedCompletions);
}
SECTION("Legacy scene variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("scenevar", "myStructure.", 11) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myStructure.",
44) == expectedCompletions);
}
SECTION("Legacy global variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("globalvar", "myGlobalStructure.", 17) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetGlobalVariableAsNumber(myGlobalStructure.",
56) == expectedCompletions);
}
}
SECTION("Child variable full name") {
SECTION("Variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.myStructureChild",
11) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetAnyVariableAsNumber("
"myStructure.myStructureChild",
47) == expectedCompletions);
}
SECTION("Primitive variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.myStructureChild", 11,
"primitive") == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetPrimitiveVariableAsNumber("
"myStructure.myStructureChild",
53) == expectedCompletions);
}
SECTION("Collection variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("variable", "myStructure.myStructureChild", 11,
"collection") == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetCollectionVariableAsNumber("
"myStructure.myStructureChild",
54) == expectedCompletions);
}
SECTION("Legacy scene variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("scenevar", "myStructure.myStructureChild", 11) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(myStructure.myStructureChild",
44) == expectedCompletions);
}
SECTION("Legacy global variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("globalvar", "myGlobalStructure.myStructureChild", 17) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyExtension::GetGlobalVariableAsNumber(myGlobalStructure.myStructureChild",
56) == expectedCompletions);
}
}
SECTION("Object variable partial name") {
SECTION("Object variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "my", 0, "", "MyObject") ==
expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myObj",
35) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(my",
35) == expectedCompletions);
}
SECTION("Primitive object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "my", 0, "primitive", "MyObject") ==
expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetPrimitiveObjectVariableAsNumber(my",
44) == expectedCompletions);
}
SECTION("Collection object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "my", 0, "collection", "MyObject") ==
expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetCollectionObjectVariableAsNumber(my",
45) == expectedCompletions);
}
}
SECTION("Object variable full name") {
SECTION("Object variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myObjectStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure", 0, "",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number", "MyObject.GetObjectVariableAsNumber(myObjectStructure",
35) == expectedCompletions);
}
SECTION("Primitive object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myObjectStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure", 0, "primitive",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure",
44) == expectedCompletions);
}
SECTION("Collection object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure", 0, "collection",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure",
45) == expectedCompletions);
}
}
SECTION("Object child variable partial name") {
SECTION("Object variable parameter") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.", 17, "",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetObjectVariableAsNumber(myObjectStructure.",
52) == expectedCompletions);
}
SECTION("Primitive object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.", 17, "primitive",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure.",
61) == expectedCompletions);
}
SECTION("Collection object variable") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.", 17, "collection",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure.",
62) == expectedCompletions);
}
}
SECTION("Object child variable full name") {
SECTION("Object function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.myStructureChild",
18, "", "MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myObjectStructure.myStructureChild",
53) == expectedCompletions);
}
SECTION("Object function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.myStructureChild",
18, "primitive",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure.myStructureChild",
62) == expectedCompletions);
}
SECTION("Object function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("objectvar", "myObjectStructure.myStructureChild",
18, "collection",
"MyObject") == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure.myStructureChild",
63) == expectedCompletions);
}
}
SECTION("Free function") {
SECTION("Test 1") {
std::vector<gd::String> expectedCompletions{
@@ -233,347 +816,14 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
"{ 0, unknown, 3, no prefix, MyObject, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, with object configuration }",
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myGlobalVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myGlobalStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
gd::ExpressionCompletionDescription::ForExpressionWithPrefix("unknown", "My", 9, 10).ToString()
};
// clang-format on
REQUIRE(getCompletionsFor("string", "Function(My", 10) ==
expectedCompletions);
}
SECTION("Function with a legacy variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetVariableAsNumber(my",
33) == expectedCompletions);
}
SECTION("Variable partial name") {
SECTION("Function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetAnyVariableAsNumber(my",
36) == expectedCompletions);
}
SECTION("Function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor(
"number", "MyExtension::GetPrimitiveVariableAsNumber(my",
42) == expectedCompletions);
}
SECTION("Function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor(
"number", "MyExtension::GetCollectionVariableAsNumber(my",
43) == expectedCompletions);
}
}
SECTION("Variable full name") {
SECTION("Function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number", "MyExtension::GetAnyVariableAsNumber(myStructure",
36) == expectedCompletions);
}
SECTION("Function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetPrimitiveVariableAsNumber(myStructure",
42) == expectedCompletions);
}
SECTION("Function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetCollectionVariableAsNumber(myStructure",
43) == expectedCompletions);
}
}
SECTION("Child variable partial name") {
SECTION("Function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetAnyVariableAsNumber(myStructure.",
47) == expectedCompletions);
}
SECTION("Function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetPrimitiveVariableAsNumber(myStructure.",
53) == expectedCompletions);
}
SECTION("Function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyExtension::GetCollectionVariableAsNumber(myStructure.",
54) == expectedCompletions);
}
}
SECTION("Child variable full name") {
SECTION("Function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetAnyVariableAsNumber("
"myStructure.myStructureChild",
47) == expectedCompletions);
}
SECTION("Function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetPrimitiveVariableAsNumber("
"myStructure.myStructureChild",
53) == expectedCompletions);
}
SECTION("Function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyExtension::GetCollectionVariableAsNumber("
"myStructure.myStructureChild",
54) == expectedCompletions);
}
}
SECTION("Object variable partial name") {
SECTION("Object function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myObj",
35) == expectedCompletions);
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(my",
35) == expectedCompletions);
}
SECTION("Object function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myObjectVariable, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetPrimitiveObjectVariableAsNumber(my",
44) == expectedCompletions);
}
SECTION("Object function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetCollectionObjectVariableAsNumber(my",
45) == expectedCompletions);
}
}
SECTION("Object variable full name") {
SECTION("Object function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myObjectStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number", "MyObject.GetObjectVariableAsNumber(myObjectStructure",
35) == expectedCompletions);
}
SECTION("Object function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myObjectStructure.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure",
44) == expectedCompletions);
}
SECTION("Object function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myObjectStructure, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myObjectStructure[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure",
45) == expectedCompletions);
}
}
SECTION("Object child variable partial name") {
SECTION("Object function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetObjectVariableAsNumber(myObjectStructure.",
52) == expectedCompletions);
}
SECTION("Object function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure.",
61) == expectedCompletions);
}
SECTION("Object function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor(
"number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure.",
62) == expectedCompletions);
}
}
SECTION("Object child variable full name") {
SECTION("Object function with a variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetObjectVariableAsNumber(myObjectStructure.myStructureChild",
53) == expectedCompletions);
}
SECTION("Object function with a primitive variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 3, no prefix, myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 3, no prefix, myStructureChild.myNumberChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
// The structure is suggested because one of its children might be a number.
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetPrimitiveObjectVariableAsNumber(myObjectStructure.myStructureChild",
62) == expectedCompletions);
}
SECTION("Object function with a collection variable as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
"{ 3, no type, 5, no prefix, myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild.myStructureChild, no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }",
"{ 3, no type, 5, no prefix, myStructureChild[], no object name, no behavior name, non-exact, not last parameter, no parameter metadata, no object configuration }"
};
// clang-format on
REQUIRE(getCompletionsFor("number",
"MyObject.GetCollectionObjectVariableAsNumber(myObjectStructure.myStructureChild",
63) == expectedCompletions);
}
}
SECTION("Function with a Layer as argument") {
// clang-format off
std::vector<gd::String> expectedCompletions{
+8 -1
View File
@@ -3348,7 +3348,14 @@ interface VectorExpressionCompletionDescription {
};
interface ExpressionCompletionFinder {
[Value] VectorExpressionCompletionDescription STATIC_GetCompletionDescriptionsFor([Const, Ref] Platform platform, [Const, Ref] ProjectScopedContainers projectScopedContainers, [Const] DOMString rootType, [Ref] ExpressionNode node, unsigned long location);
[Value] VectorExpressionCompletionDescription STATIC_GetCompletionDescriptionsFor(
[Const, Ref] Platform platform,
[Const, Ref] ProjectScopedContainers projectScopedContainers,
[Const] DOMString rootType,
[Const] DOMString rootExtraInfo,
[Const] DOMString rootObjectName,
[Ref] ExpressionNode node,
unsigned long location);
[Const, Ref] VectorExpressionCompletionDescription GetCompletionDescriptions();
@@ -41,6 +41,8 @@ describe('gd.ExpressionCompletionFinder', function () {
gd.JsPlatform.get(),
projectScopedContainers,
type,
'',
'',
expressionNode,
// We're looking for completion for the character just before the caret.
Math.max(0, caretPosition - 1)
+1 -1
View File
@@ -2400,7 +2400,7 @@ export class VectorExpressionCompletionDescription extends EmscriptenObject {
}
export class ExpressionCompletionFinder extends EmscriptenObject {
static getCompletionDescriptionsFor(platform: Platform, projectScopedContainers: ProjectScopedContainers, rootType: string, node: ExpressionNode, location: number): VectorExpressionCompletionDescription;
static getCompletionDescriptionsFor(platform: Platform, projectScopedContainers: ProjectScopedContainers, rootType: string, rootExtraInfo: string, rootObjectName: string, node: ExpressionNode, location: number): VectorExpressionCompletionDescription;
getCompletionDescriptions(): VectorExpressionCompletionDescription;
}
@@ -1,6 +1,6 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdExpressionCompletionFinder {
static getCompletionDescriptionsFor(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, rootType: string, node: gdExpressionNode, location: number): gdVectorExpressionCompletionDescription;
static getCompletionDescriptionsFor(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, rootType: string, rootExtraInfo: string, rootObjectName: string, node: gdExpressionNode, location: number): gdVectorExpressionCompletionDescription;
getCompletionDescriptions(): gdVectorExpressionCompletionDescription;
delete(): void;
ptr: number;
@@ -4,7 +4,6 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
import VariableField, {
getRootVariableName,
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog';
@@ -14,14 +13,13 @@ import {
type ParameterFieldInterface,
type FieldFocusFunction,
} from './ParameterFieldCommons';
import { enumerateVariablesOfContainersList } from './EnumerateVariables';
import { mapFor } from '../../Utils/MapFor';
const gd: libGDevelop = global.gd;
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function AnyVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
variableEditorOpen,
setVariableEditorOpen,
@@ -43,14 +41,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
value,
} = props;
const enumerateGlobalAndSceneVariables = React.useCallback(
() =>
enumerateVariablesOfContainersList(
projectScopedContainersAccessor.get().getVariablesContainersList()
),
[projectScopedContainersAccessor]
);
const variablesContainers = React.useMemo(
() => {
const variablesContainersList = projectScopedContainersAccessor
@@ -82,7 +72,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
// The variable editor may have refactor the events for a variable type
// change which may have change the currently edited instruction type.
if (onInstructionTypeChanged) onInstructionTypeChanged();
if (field.current) field.current.updateAutocompletions();
},
[onChange, onInstructionTypeChanged, value]
);
@@ -106,7 +95,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
project={project}
instruction={instruction}
variablesContainers={variablesContainers}
enumerateVariables={enumerateGlobalAndSceneVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -128,6 +116,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
getVariableSourceFromIdentifier={getVariableSourceFromIdentifier}
editEventsFunctionParameter={null}
openEventsBasedEntityPropertyEditorDialog={null}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{variableEditorOpen &&
(variableSourceType === gd.VariablesContainer.Local ? (
@@ -4,7 +4,6 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
import VariableField, {
getRootVariableName,
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog';
@@ -14,14 +13,13 @@ import {
type ParameterFieldInterface,
type FieldFocusFunction,
} from './ParameterFieldCommons';
import { enumerateVariablesOrPropertiesOfContainersList } from './EnumerateVariables';
import { mapFor } from '../../Utils/MapFor';
const gd: libGDevelop = global.gd;
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function AnyVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
variableEditorOpen,
setVariableEditorOpen,
@@ -44,14 +42,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
openEventsBasedEntityPropertyEditorDialog,
} = props;
const enumerateGlobalAndSceneVariables = React.useCallback(
() =>
enumerateVariablesOrPropertiesOfContainersList(
projectScopedContainersAccessor.get().getVariablesContainersList()
),
[projectScopedContainersAccessor]
);
const variablesContainers = React.useMemo(
() => {
const variablesContainersList = projectScopedContainersAccessor
@@ -81,7 +71,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
// The variable editor may have refactor the events for a variable type
// change which may have change the currently edited instruction type.
if (onInstructionTypeChanged) onInstructionTypeChanged();
if (field.current) field.current.updateAutocompletions();
},
[onChange, onInstructionTypeChanged, value]
);
@@ -105,7 +94,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
project={project}
instruction={instruction}
variablesContainers={variablesContainers}
enumerateVariables={enumerateGlobalAndSceneVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -129,6 +117,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
openEventsBasedEntityPropertyEditorDialog={
openEventsBasedEntityPropertyEditorDialog || null
}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{variableEditorOpen &&
(variableSourceType === gd.VariablesContainer.Local ? (
@@ -4,7 +4,6 @@ import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flo
import VariableField, {
getRootVariableName,
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog';
@@ -14,14 +13,13 @@ import {
type ParameterFieldInterface,
type FieldFocusFunction,
} from './ParameterFieldCommons';
import { enumerateVariablesOrPropertiesOrParametersOfContainersList } from './EnumerateVariables';
import { mapFor } from '../../Utils/MapFor';
const gd: libGDevelop = global.gd;
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function AnyVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
variableEditorOpen,
setVariableEditorOpen,
@@ -45,14 +43,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
openEventsBasedEntityPropertyEditorDialog,
} = props;
const enumerateGlobalAndSceneVariables = React.useCallback(
() =>
enumerateVariablesOrPropertiesOrParametersOfContainersList(
projectScopedContainersAccessor.get().getVariablesContainersList()
),
[projectScopedContainersAccessor]
);
const variablesContainers = React.useMemo(
() => {
const variablesContainersList = projectScopedContainersAccessor
@@ -78,7 +68,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
// The variable editor may have refactor the events for a variable type
// change which may have change the currently edited instruction type.
if (onInstructionTypeChanged) onInstructionTypeChanged();
if (field.current) field.current.updateAutocompletions();
},
[onChange, onInstructionTypeChanged, value]
);
@@ -102,7 +91,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
project={project}
instruction={instruction}
variablesContainers={variablesContainers}
enumerateVariables={enumerateGlobalAndSceneVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -126,6 +114,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
openEventsBasedEntityPropertyEditorDialog={
openEventsBasedEntityPropertyEditorDialog || null
}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{variableEditorOpen &&
(variableSourceType === gd.VariablesContainer.Local ? (
@@ -1,171 +0,0 @@
// @flow
import flatten from 'lodash/flatten';
import { mapFor } from '../../Utils/MapFor';
import flatMap from 'lodash/flatMap';
import uniqBy from 'lodash/uniqBy';
import sortBy from 'lodash/sortBy';
import reverse from 'lodash/reverse';
const gd: libGDevelop = global.gd;
// Note that in theory we could have this function inside gd.ExpressionParser2,
// to be sure it's following perfectly the grammar of the expression parser. In
// practice, the disallowed characters are not changing often, so it's fine to have
// them here.
const isValidIdentifier = (variableName: string) => {
const identifierDisallowedCharacters = ',."()[]{}()[],+-<>?^=:!+-/* \'';
for (let i = 0; i < variableName.length; ++i) {
if (identifierDisallowedCharacters.indexOf(variableName[i]) !== -1)
return false;
}
return true;
};
const convertToStringLiteral = (variableName: string) => {
return '"' + variableName.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
};
export type EnumeratedVariable = {|
name: string,
isValidName: boolean,
type: Variable_Type,
source: VariablesContainer_SourceType,
|};
// $FlowFixMe[missing-local-annot]
const variableSort = [variable => variable.name.toLowerCase()];
const enumerateVariablesOfContainersListExcludingSourceTypes = (
variablesContainersList: ?gdVariablesContainersList,
excludedSourceType: Array<VariablesContainer_SourceType>
): Array<EnumeratedVariable> => {
if (!variablesContainersList) {
return [];
}
return sortBy(
uniqBy(
flatten(
reverse(
mapFor(
0,
variablesContainersList.getVariablesContainersCount(),
i => {
const variablesContainer = variablesContainersList.getVariablesContainer(
i
);
if (
excludedSourceType.includes(variablesContainer.getSourceType())
) {
return [];
}
return enumerateVariables(variablesContainer);
}
)
)
),
'name'
),
variableSort
);
};
export const enumerateVariablesOfContainersList = (
variablesContainersList: ?gdVariablesContainersList
): Array<EnumeratedVariable> => {
return enumerateVariablesOfContainersListExcludingSourceTypes(
variablesContainersList,
[gd.VariablesContainer.Parameters, gd.VariablesContainer.Properties]
);
};
export const enumerateVariablesOrPropertiesOfContainersList = (
variablesContainersList: ?gdVariablesContainersList
): Array<EnumeratedVariable> => {
return enumerateVariablesOfContainersListExcludingSourceTypes(
variablesContainersList,
[gd.VariablesContainer.Parameters]
);
};
export const enumerateVariablesOrPropertiesOrParametersOfContainersList = (
variablesContainersList: ?gdVariablesContainersList
): Array<EnumeratedVariable> => {
return enumerateVariablesOfContainersListExcludingSourceTypes(
variablesContainersList,
[]
);
};
export const enumerateVariables = (
variablesContainer: ?gdVariablesContainer
): Array<EnumeratedVariable> => {
if (!variablesContainer) {
return [];
}
const enumerateVariableAndChildrenNames = (
source: VariablesContainer_SourceType,
fullName: string,
variable: gdVariable,
isTopLevel: boolean,
isFullNameValid: boolean
): Array<EnumeratedVariable> => {
// When a variable is top level, it should in theory not contain
// any special character making it unusable in an expression.
const isValidName = isTopLevel
? isValidIdentifier(fullName)
: isFullNameValid;
const type = variable.getType();
const enumeratedVariable = { name: fullName, isValidName, type, source };
if (type === gd.Variable.Structure) {
return [
enumeratedVariable,
...flatMap(variable.getAllChildrenNames().toJSArray(), childName =>
enumerateVariableAndChildrenNames(
source,
isValidIdentifier(childName)
? `${fullName}.${childName}`
: `${fullName}[${convertToStringLiteral(childName)}]`,
variable.getChild(childName),
false,
isValidName
)
),
];
} else if (type === gd.Variable.Array) {
return [
enumeratedVariable,
...flatMap(
new Array<string>(variable.getChildrenCount()).fill(''),
(_, index) =>
enumerateVariableAndChildrenNames(
source,
`${fullName}[${index}]`,
variable.getAtIndex(index),
false,
isValidName
)
),
];
} else {
// Variable of primitive type
return [enumeratedVariable];
}
};
return flatten(
mapFor(0, variablesContainer.count(), i => {
return enumerateVariableAndChildrenNames(
variablesContainer.getSourceType(),
variablesContainer.getNameAt(i),
variablesContainer.getAt(i),
true,
true
);
})
);
};
@@ -1,77 +0,0 @@
// @flow
import { enumerateVariables } from './EnumerateVariables';
const gd: libGDevelop = global.gd;
describe('EnumerateVariables', () => {
it('can enumerate variables, including children', () => {
const container = new gd.VariablesContainer(gd.VariablesContainer.Unknown);
container
.insert('Variable1', new gd.Variable(), 0)
.setString('A multiline\nstr value');
container.insert('Variable2', new gd.Variable(), 1).setString('123456');
const variable3 = new gd.Variable();
variable3.getChild('Child1').setString('Child1 str value');
variable3.getChild('Child2').setString('7891011');
variable3
.getChild('Child3')
.getChild('SubChild1')
.setString('Hello\nMultiline\nWorld');
container.insert('Variable3', variable3, 2);
const variable4 = new gd.Variable();
variable4.pushNew().setString('First item');
variable4.pushNew().setValue(2);
const variable4Item3 = variable4.pushNew();
variable4Item3.pushNew().setString('a');
variable4Item3.pushNew().setString('b');
variable4Item3.pushNew().setString('c');
container.insert('Variable4', variable4, 3);
const allNames = enumerateVariables(container).map(({ name }) => name);
expect(allNames).toHaveLength(14);
expect(allNames).toContain('Variable1');
expect(allNames).toContain('Variable2');
expect(allNames).toContain('Variable3');
expect(allNames).toContain('Variable3.Child1');
expect(allNames).toContain('Variable3.Child2');
expect(allNames).toContain('Variable3.Child3');
expect(allNames).toContain('Variable3.Child3.SubChild1');
expect(allNames).toContain('Variable4');
expect(allNames).toContain('Variable4[0]');
expect(allNames).toContain('Variable4[1]');
expect(allNames).toContain('Variable4[2]');
expect(allNames).toContain('Variable4[2][0]');
expect(allNames).toContain('Variable4[2][1]');
expect(allNames).toContain('Variable4[2][2]');
});
it('can enumerate "invalid" variable names, including children', () => {
const container = new gd.VariablesContainer(gd.VariablesContainer.Unknown);
container.insert('ValidName', new gd.Variable(), 0);
container.insert('Invalid!Name', new gd.Variable(), 1);
const variable3 = new gd.Variable();
variable3.getChild('Valid name');
variable3.getChild('Also a \\valid\\ "name"!');
container.insert('Variable3', variable3, 2);
const variable4 = new gd.Variable();
variable4.getChild('ValidName');
container.insert('==InvalidName==', variable4, 3);
const enumeratedVariables = enumerateVariables(container);
expect(enumeratedVariables).toHaveLength(7);
expect(enumeratedVariables[0].name).toBe('ValidName');
expect(enumeratedVariables[0].isValidName).toBe(true);
expect(enumeratedVariables[1].name).toBe('Invalid!Name');
expect(enumeratedVariables[1].isValidName).toBe(false);
expect(enumeratedVariables[2].name).toBe('Variable3');
expect(enumeratedVariables[2].isValidName).toBe(true);
expect(enumeratedVariables[3].name).toBe(
'Variable3["Also a \\\\valid\\\\ \\"name\\"!"]'
);
expect(enumeratedVariables[3].isValidName).toBe(true);
expect(enumeratedVariables[4].name).toBe('Variable3["Valid name"]');
expect(enumeratedVariables[4].isValidName).toBe(true);
expect(enumeratedVariables[5].name).toBe('==InvalidName==');
expect(enumeratedVariables[5].isValidName).toBe(false);
expect(enumeratedVariables[6].name).toBe('==InvalidName==.ValidName');
expect(enumeratedVariables[6].isValidName).toBe(false);
});
});
@@ -23,6 +23,7 @@ import { getVariableSourceIcon } from '../../ParameterFields/VariableField';
import PropertyIcon from '../../../UI/CustomSvgIcons/Settings';
import ParameterIcon from '../../../UI/CustomSvgIcons/Parameter';
import { LineStackLayout } from '../../../UI/Layout';
import { type AutocompletionAction } from './ExpressionAutocompletionsHandler';
const gd: libGDevelop = global.gd;
@@ -202,6 +203,7 @@ type Props = {|
expressionAutocompletions: Array<ExpressionAutocompletion>,
remainingCount: number,
selectedCompletionIndex: number,
additionalActions?: Array<AutocompletionAction>,
anchorEl: Element,
onChoose: (chosenExpressionAutocompletion: ExpressionAutocompletion) => void,
onScroll: () => void,
@@ -240,6 +242,7 @@ export default function ExpressionAutocompletionsDisplayer({
expressionAutocompletions,
remainingCount,
selectedCompletionIndex,
additionalActions,
anchorEl,
onChoose,
onScroll,
@@ -259,7 +262,11 @@ export default function ExpressionAutocompletionsDisplayer({
[scrollView, selectedAutocompletionElement, selectedCompletionIndex]
);
if (expressionAutocompletions.length === 0) return null;
if (
expressionAutocompletions.length === 0 &&
(!additionalActions || additionalActions.length === 0)
)
return null;
return (
<I18n>
@@ -390,9 +397,33 @@ export default function ExpressionAutocompletionsDisplayer({
</Text>
</Column>
)}
{additionalActions &&
additionalActions.map((action, index) => {
const isSelected =
selectedCompletionIndex ===
index + expressionAutocompletions.length;
const ref = isSelected
? selectedAutocompletionElement
: undefined;
return (
<AutocompletionRow
key={index}
icon={action.renderIcon()}
iconSrc={null}
secondaryIcon={null}
label={i18n._(action.translatableValue)}
parametersLabel={null}
onClick={() => action.onClick()}
isSelected={isSelected}
ref={ref}
/>
);
})}
</ScrollView>
</Paper>
{selectedCompletionIndex != null &&
selectedCompletionIndex < expressionAutocompletions.length &&
expressionAutocompletions[selectedCompletionIndex].kind ===
'Expression' &&
!expressionAutocompletions[selectedCompletionIndex].isExact && (
@@ -5,9 +5,21 @@ import {
shouldValidate,
shouldFocusNextField,
} from '../../../UI/KeyboardShortcuts/InteractionKeys';
import ListIcon from '../../../UI/ListIcon';
import SvgIcon from '@material-ui/core/SvgIcon';
import { type MessageDescriptor } from '../../../Utils/i18n/MessageDescriptor.flow';
export type AutocompletionAction = {
id: string,
translatableValue: MessageDescriptor,
// $FlowFixMe[prop-missing]
renderIcon: () => React.Element<typeof ListIcon | typeof SvgIcon>,
onClick: () => void,
};
export type AutocompletionsState = {|
autocompletions: Array<ExpressionAutocompletion>,
additionalActions: Array<AutocompletionAction>,
selectedCompletionIndex: number,
renderEverything: boolean,
|};
@@ -15,6 +27,7 @@ export type AutocompletionsState = {|
export const getAutocompletionsInitialState = (): AutocompletionsState => {
return {
autocompletions: [],
additionalActions: [],
selectedCompletionIndex: 0,
// By default, only render some completions.
// This is to avoid rendering a lot (100+) completions,
@@ -49,7 +62,8 @@ export const getRenderedAutocompletions = (
export const setNewAutocompletions = (
state: AutocompletionsState,
autocompletions: Array<ExpressionAutocompletion>
autocompletions: Array<ExpressionAutocompletion>,
additionalActions: Array<AutocompletionAction> = []
): AutocompletionsState => {
const completionsChanged =
state.autocompletions.length !== autocompletions.length;
@@ -61,6 +75,7 @@ export const setNewAutocompletions = (
return {
autocompletions,
additionalActions,
selectedCompletionIndex,
renderEverything: completionsChanged ? false : state.renderEverything,
};
@@ -97,7 +112,9 @@ export const handleAutocompletionsKeyDown = (
onUpdateAutocompletions();
}
if (!state.autocompletions.length) return state;
const length = state.autocompletions.length + state.additionalActions.length;
if (length === 0) return state;
if (event.key === 'ArrowDown') {
event.preventDefault();
@@ -107,8 +124,7 @@ export const handleAutocompletionsKeyDown = (
// If there is a browsing in the autocompletions,
// we need to render all the completions.
renderEverything: true,
selectedCompletionIndex:
(state.selectedCompletionIndex + 1) % state.autocompletions.length,
selectedCompletionIndex: (state.selectedCompletionIndex + 1) % length,
};
} else if (event.key === 'ArrowUp') {
event.preventDefault();
@@ -119,8 +135,7 @@ export const handleAutocompletionsKeyDown = (
// we need to render all the completions.
renderEverything: true,
selectedCompletionIndex:
(state.autocompletions.length + state.selectedCompletionIndex - 1) %
state.autocompletions.length,
(length + state.selectedCompletionIndex - 1) % length,
};
} else if (shouldCloseOrCancel(event)) {
// Stop propagation to avoid closing the modal the
@@ -137,6 +152,18 @@ export const handleAutocompletionsKeyDown = (
if (autocompletion.isExact) return state;
onInsertAutocompletion(autocompletion);
// Stop propagation to avoid closing the modal the
// field is contained in.
event.stopPropagation();
} else {
const additionalAction =
state.additionalActions[
state.selectedCompletionIndex - state.autocompletions.length
];
if (additionalAction) {
additionalAction.onClick();
}
// Stop propagation to avoid closing the modal the
// field is contained in.
event.stopPropagation();
@@ -39,6 +39,7 @@ import {
handleAutocompletionsScroll,
getRenderedAutocompletions,
getNonRenderedCount,
type AutocompletionAction,
} from './ExpressionAutocompletionsHandler';
import ExpressionAutocompletionsDisplayer from './ExpressionAutocompletionsDisplayer';
import { ResponsiveWindowMeasurer } from '../../../UI/Responsive/ResponsiveWindowMeasurer';
@@ -110,11 +111,22 @@ type State = {|
|};
type Props = {|
expressionType: 'number' | 'string',
expressionType:
| 'number'
| 'string'
| 'variable'
| 'variableOrProperty'
| 'variableOrPropertyOrParameter'
| 'objectvar'
| 'scenevar'
| 'globalvar',
/** An optional callback that can be used to provide additional autocompletions. */
onGetAdditionalAutocompletions?: (
currentExpression: string
) => Array<ExpressionAutocompletion>,
onGetAdditionalActions?: (
currentExpression: string
) => Array<AutocompletionAction>,
/** An optional callback that can be used to show a custom error message. */
onExtractAdditionalErrors?: (
currentExpression: string,
@@ -256,6 +268,16 @@ export default class ExpressionField extends React.Component<Props, State> {
}
};
getInputValue = (): string => {
if (!this._inputElement) {
return '';
}
return this._inputElement.value;
};
hasAnyError = (): boolean =>
this.state.errorText && this.state.errorText.length > 0;
_openExpressionPopover = () => {
this.setState({
popoverOpen: true,
@@ -472,6 +494,7 @@ export default class ExpressionField extends React.Component<Props, State> {
projectScopedContainersAccessor,
expressionType,
parameterMetadata,
instruction,
scope,
onGetAdditionalAutocompletions,
onExtractAdditionalErrors,
@@ -486,6 +509,14 @@ export default class ExpressionField extends React.Component<Props, State> {
const parser = new gd.ExpressionParser2();
const expressionNode = parser.parseExpression(expression).get();
const objectName = instruction
? gd.InstructionValidator.getObjectNameForParameter(
projectScopedContainersAccessor.get(),
instruction,
expressionType
)
: '';
const showDeprecatedInstructionWarning = this.context
? this.context.values.showDeprecatedInstructionWarning
: 'icon';
@@ -497,7 +528,7 @@ export default class ExpressionField extends React.Component<Props, State> {
expressionType,
parameterMetadata,
expressionNode,
'',
objectName,
showDeprecatedInstructionWarning
);
const extraErrorText = onExtractAdditionalErrors
@@ -522,6 +553,11 @@ export default class ExpressionField extends React.Component<Props, State> {
return;
}
const isVariableParameter = gd.ValueTypeMetadata.isTypeExpression(
'variable',
expressionType
);
const cursorPosition = this._inputElement
? this._inputElement.selectionStart
: 0;
@@ -529,8 +565,10 @@ export default class ExpressionField extends React.Component<Props, State> {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
expressionType,
parameterMetadata ? parameterMetadata.getExtraInfo() : '',
objectName,
expressionNode,
cursorPosition - 1
isVariableParameter ? Math.max(0, cursorPosition - 1) : cursorPosition - 1
);
const newAutocompletions = getAutocompletionsFromDescriptions(
@@ -552,13 +590,18 @@ export default class ExpressionField extends React.Component<Props, State> {
parser.delete();
const additionalActions = this.props.onGetAdditionalActions
? this.props.onGetAdditionalActions(this.state.validatedValue)
: [];
this.setState(state => ({
errorText: formattedErrorText,
errorHighlights,
isOnlyWarning,
autocompletions: setNewAutocompletions(
state.autocompletions,
allNewAutocompletions
allNewAutocompletions,
additionalActions
),
}));
};
@@ -735,6 +778,9 @@ export default class ExpressionField extends React.Component<Props, State> {
selectedCompletionIndex={
this.state.autocompletions.selectedCompletionIndex
}
additionalActions={
this.state.autocompletions.additionalActions
}
onScroll={this._onExpressionAutocompletionsScroll}
onChoose={expressionAutocompletion => {
this._insertAutocompletion(expressionAutocompletion);
@@ -757,22 +803,23 @@ export default class ExpressionField extends React.Component<Props, State> {
style,
onChange: this._handleBlur,
})}
{!this.props.isInline && (
<RaisedButton
id={`open-${expressionType}-expression-popover-button`}
icon={<Functions />}
label={
expressionType === 'string'
? '"ABC"'
: expressionType === 'number'
? '123'
: ''
}
primary
style={style}
onClick={this._openExpressionPopover}
/>
)}
{!this.props.isInline &&
!this.props.shouldHideExpressionListButton && (
<RaisedButton
id={`open-${expressionType}-expression-popover-button`}
icon={<Functions />}
label={
expressionType === 'string'
? '"ABC"'
: expressionType === 'number'
? '123'
: ''
}
primary
style={style}
onClick={this._openExpressionPopover}
/>
)}
</React.Fragment>
)}
/>
@@ -1,10 +1,7 @@
// @flow
import * as React from 'react';
import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow';
import VariableField, {
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import VariableField, { renderVariableWithIcon } from './VariableField';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import GlobalVariablesDialog from '../../VariablesList/GlobalVariablesDialog';
import {
@@ -12,13 +9,12 @@ import {
type ParameterFieldInterface,
type FieldFocusFunction,
} from './ParameterFieldCommons';
import { enumerateVariables } from './EnumerateVariables';
const gd: libGDevelop = global.gd;
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function GlobalVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
variableEditorOpen,
setVariableEditorOpen,
@@ -39,18 +35,10 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
[project]
);
const enumerateGlobaleVariables = React.useCallback(
() => {
return project ? enumerateVariables(project.getVariables()) : [];
},
[project]
);
return (
<React.Fragment>
<VariableField
variablesContainers={variablesContainers}
enumerateVariables={enumerateGlobaleVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -67,6 +55,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
getVariableSourceFromIdentifier={getVariableSourceFromIdentifier}
editEventsFunctionParameter={null}
openEventsBasedEntityPropertyEditorDialog={null}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{variableEditorOpen && project && (
<GlobalVariablesDialog
@@ -81,7 +75,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
props.onChange(selectedVariableName);
}
setVariableEditorOpen(null);
if (field.current) field.current.updateAutocompletions();
}}
initiallySelectedVariable={variableEditorOpen}
hotReloadPreviewButtonProps={null}
@@ -1,10 +1,7 @@
// @flow
import * as React from 'react';
import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow';
import VariableField, {
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import VariableField, { renderVariableWithIcon } from './VariableField';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import ObjectVariablesDialog from '../../VariablesList/ObjectVariablesDialog';
import ObjectGroupVariablesDialog from '../../VariablesList/ObjectGroupVariablesDialog';
@@ -16,8 +13,6 @@ import {
import { getLastObjectParameterValue } from './ParameterMetadataTools';
import getObjectByName from '../../Utils/GetObjectByName';
import getObjectGroupByName from '../../Utils/GetObjectGroupByName';
import { enumerateVariables } from './EnumerateVariables';
import { intersectionBy } from 'lodash';
import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder';
const gd: libGDevelop = global.gd;
@@ -60,7 +55,7 @@ export const getObjectOrGroupVariablesContainers = (
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function ObjectVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
editorOpen,
setEditorOpen,
@@ -138,16 +133,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
]
);
const enumerateObjectVariables = React.useCallback(
() =>
variablesContainers.length > 0
? variablesContainers
.map(variablesContainer => enumerateVariables(variablesContainer))
.reduce((a, b) => intersectionBy(a, b, 'name'))
: [],
[variablesContainers]
);
const onVariableEditorApply = React.useCallback(
(selectedVariableName: string | null) => {
if (selectedVariableName && selectedVariableName.startsWith(value)) {
@@ -157,7 +142,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
// The variable editor may have refactored the events for a variable type
// change which may have changed the currently edited instruction type.
if (onInstructionTypeChanged) onInstructionTypeChanged();
if (field.current) field.current.updateAutocompletions();
},
[onChange, onInstructionTypeChanged, value]
);
@@ -182,7 +166,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
project={project}
instruction={instruction}
variablesContainers={variablesContainers}
enumerateVariables={enumerateObjectVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -206,6 +189,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
getVariableSourceFromIdentifier={getVariableSourceFromIdentifier}
editEventsFunctionParameter={null}
openEventsBasedEntityPropertyEditorDialog={null}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{editorOpen &&
project &&
@@ -26,6 +26,7 @@ type CommonProps = {|
objectsContainer: gdObjectsContainer,
projectScopedContainersAccessor: ProjectScopedContainersAccessor,
isInline?: boolean,
shouldHideExpressionListButton?: boolean,
onRequestClose?: () => void,
onApply?: () => void,
resourceManagementProps?: ResourceManagementProps,
@@ -1,10 +1,7 @@
// @flow
import * as React from 'react';
import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow';
import VariableField, {
renderVariableWithIcon,
type VariableFieldInterface,
} from './VariableField';
import VariableField, { renderVariableWithIcon } from './VariableField';
import SceneVariablesDialog from '../../VariablesList/SceneVariablesDialog';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import {
@@ -12,14 +9,13 @@ import {
type ParameterFieldInterface,
type FieldFocusFunction,
} from './ParameterFieldCommons';
import { enumerateVariables } from './EnumerateVariables';
import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog';
const gd: libGDevelop = global.gd;
export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
function SceneVariableField(props: ParameterFieldProps, ref) {
const field = React.useRef<?VariableFieldInterface>(null);
const field = React.useRef<?ParameterFieldInterface>(null);
const [
variableEditorOpen,
setVariableEditorOpen,
@@ -51,24 +47,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
[eventsFunctionsExtension, layout]
);
const enumerateSceneVariables = React.useCallback(
() => {
return layout
? enumerateVariables(layout.getVariables())
: eventsFunctionsExtension
? enumerateVariables(eventsFunctionsExtension.getSceneVariables())
: [];
},
[eventsFunctionsExtension, layout]
);
const onVariableEditorApply = React.useCallback(
(selectedVariableName: string | null) => {
if (selectedVariableName && selectedVariableName.startsWith(value)) {
onChange(selectedVariableName);
}
setVariableEditorOpen(null);
if (field.current) field.current.updateAutocompletions();
},
[onChange, value]
);
@@ -77,7 +61,6 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
<React.Fragment>
<VariableField
variablesContainers={variablesContainers}
enumerateVariables={enumerateSceneVariables}
parameterMetadata={props.parameterMetadata}
value={props.value}
onChange={props.onChange}
@@ -99,6 +82,12 @@ export default (React.forwardRef<ParameterFieldProps, ParameterFieldInterface>(
getVariableSourceFromIdentifier={getVariableSourceFromIdentifier}
editEventsFunctionParameter={null}
openEventsBasedEntityPropertyEditorDialog={null}
instructionMetadata={props.instructionMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
/>
{variableEditorOpen && layout && project && (
<SceneVariablesDialog
@@ -15,10 +15,6 @@ import {
nameAndIconContainer,
instructionParameter,
} from '../EventsTree/ClassNames';
import SemiControlledAutoComplete, {
type SemiControlledAutoCompleteInterface,
type DataSource,
} from '../../UI/SemiControlledAutoComplete';
import { TextFieldWithButtonLayout } from '../../UI/Layout';
import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow';
import {
@@ -37,8 +33,6 @@ import VariableBooleanIcon from '../../VariablesList/Icons/VariableBooleanIcon';
import VariableArrayIcon from '../../VariablesList/Icons/VariableArrayIcon';
import VariableStructureIcon from '../../VariablesList/Icons/VariableStructureIcon';
import UnknownTypeIcon from '../../UI/CustomSvgIcons/Cross';
import { type EnumeratedVariable } from './EnumerateVariables';
import { LineStackLayout } from '../../UI/Layout';
import GlobalVariableIcon from '../../UI/CustomSvgIcons/GlobalVariable';
import SceneVariableIcon from '../../UI/CustomSvgIcons/SceneVariable';
import ObjectVariableIcon from '../../UI/CustomSvgIcons/ObjectVariable';
@@ -47,8 +41,8 @@ import PropertyIcon from '../../UI/CustomSvgIcons/Settings';
import ParameterIcon from '../../UI/CustomSvgIcons/Parameter';
import Add from '../../UI/CustomSvgIcons/Add';
import { type VariableDialogOpeningProps } from '../../VariablesList/VariablesEditorDialog';
import { extractErrors } from './GenericExpressionField';
import { useDebounce } from '../../Utils/UseDebounce';
import GenericExpressionField from './GenericExpressionField';
import { type AutocompletionAction } from './GenericExpressionField/ExpressionAutocompletionsHandler';
const gd: libGDevelop = global.gd;
@@ -73,7 +67,6 @@ type Props = {
identifier: string,
projectScopedContainers: gdProjectScopedContainers
) => VariablesContainer_SourceType,
enumerateVariables: () => Array<EnumeratedVariable>,
openVariableEditorDialog: (VariableDialogOpeningProps => void) | null,
editEventsFunctionParameter: (VariableDialogOpeningProps => void) | null,
openEventsBasedEntityPropertyEditorDialog:
@@ -81,11 +74,6 @@ type Props = {
| null,
};
export type VariableFieldInterface = {|
...ParameterFieldInterface,
updateAutocompletions: () => void,
|};
export const getRootVariableName = (name: string): string => {
const dotPosition = name.indexOf('.');
const squareBracketPosition = name.indexOf('[');
@@ -157,20 +145,17 @@ export const getVariableTypeIcon = (variableType: Variable_Type): any => {
// ensure we 100% validate and can autocomplete what is entered (and we can have also a simpler
// selector that offers the variables in the scope).
export default (React.forwardRef<Props, VariableFieldInterface>(
export default (React.forwardRef<Props, ParameterFieldInterface>(
function VariableField(props: Props, ref) {
const {
project,
projectScopedContainersAccessor,
variablesContainers,
enumerateVariables,
instruction,
value,
onChange,
isInline,
parameterMetadata,
onRequestClose,
onApply,
id,
onInstructionTypeChanged,
getVariableSourceFromIdentifier,
@@ -179,62 +164,14 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
openEventsBasedEntityPropertyEditorDialog,
} = props;
const field = React.useRef<?SemiControlledAutoCompleteInterface>(null);
const [
autocompletionVariableNames,
setAutocompletionVariableNames,
] = React.useState<DataSource>([]);
/**
* Can be called to set up or force updating the variables list.
*/
const updateAutocompletions = React.useCallback(
() => {
setAutocompletionVariableNames(
enumerateVariables()
.map(variable =>
variable.isValidName
? variable
: // Hide invalid variable names - they would not
// be parsed correctly anyway.
null
)
.filter(Boolean)
.map(variable => ({
text: variable.name,
value: variable.name,
renderIcon: () => {
const VariableSourceIcon = getVariableSourceIcon(
variable.source
);
const VariableTypeIcon = getVariableTypeIcon(variable.type);
return (
<LineStackLayout>
<VariableSourceIcon fontSize="small" />
<VariableTypeIcon fontSize="small" />
</LineStackLayout>
);
},
}))
);
},
[enumerateVariables]
);
const field = React.useRef<?GenericExpressionField>(null);
const focus: FieldFocusFunction = options => {
if (field.current) field.current.focus(options);
};
React.useImperativeHandle(ref, () => ({
focus,
updateAutocompletions,
}));
React.useEffect(
() => {
updateAutocompletions();
},
[updateAutocompletions]
);
const openVariableEditor = React.useCallback(
() => {
if (!openVariableEditorDialog) {
@@ -340,83 +277,6 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
]
);
const description = parameterMetadata
? parameterMetadata.getDescription()
: undefined;
const [errorText, setErrorText] = React.useState<?string>(null);
const inputValue = React.useRef<string | null>(null);
const doValidation = React.useCallback(
() => {
if (!project || !parameterMetadata || !instruction) return null;
// Parsing can be time consuming (~1ms for simple expression,
// a few milliseconds for complex ones).
const parser = new gd.ExpressionParser2();
const expressionNode = parser
.parseExpression(inputValue.current || value)
.get();
const expressionType = parameterMetadata
.getValueTypeMetadata()
.getName();
const objectName = gd.InstructionValidator.getObjectNameForParameter(
projectScopedContainersAccessor.get(),
instruction,
expressionType
);
const { errorText } = extractErrors(
gd.JsPlatform.get(),
project,
projectScopedContainersAccessor,
expressionType,
parameterMetadata,
expressionNode,
objectName,
'no'
);
parser.delete();
setErrorText(errorText);
},
[
instruction,
parameterMetadata,
project,
projectScopedContainersAccessor,
value,
]
);
const enqueueValidation = useDebounce(() => {
doValidation();
}, 250);
React.useEffect(
() => {
enqueueValidation();
},
[enqueueValidation]
);
const handleValueChange = React.useCallback(
(value: string) => {
inputValue.current = null;
onChange(value);
},
[onChange]
);
const handleInputValueChange = React.useCallback(
(value: string) => {
inputValue.current = value;
enqueueValidation();
},
[enqueueValidation]
);
const isSwitchableInstruction =
instruction &&
gd.VariableInstructionSwitcher.isSwitchableVariableInstruction(
@@ -436,16 +296,13 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
variableType !== gd.Variable.Number &&
variableType !== gd.Variable.String &&
variableType !== gd.Variable.Boolean &&
!errorText &&
field.current &&
!field.current.hasAnyError() &&
value;
const filterOptionById = React.useCallback(
(id: string) => {
// Access to the input directly because the value
// may not have been sent to onChange yet.
const fieldCurrentValue = field.current
? field.current.getInputValue()
: value;
const onGetAdditionalActions = React.useCallback(
(fieldCurrentValue: string): Array<AutocompletionAction> => {
const actions = [];
const variableSourceType = getVariableSourceFromIdentifier(
fieldCurrentValue,
@@ -454,19 +311,90 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
const isVariableDeclared =
variableSourceType !== gd.VariablesContainer.Unknown;
const optionIds = isVariableDeclared
? variableSourceType === gd.VariablesContainer.Parameters
? ['edit-parameters']
: variableSourceType === gd.VariablesContainer.Properties
? ['edit-properties']
: ['edit-variables']
: fieldCurrentValue
? ['add-parameter', 'add-property', 'add-variable']
: ['edit-or-add-properties', 'edit-or-add-variables'];
return optionIds.includes(id);
if (isVariableDeclared) {
if (variableSourceType === gd.VariablesContainer.Parameters) {
if (editEventsFunctionParameter) {
actions.push({
id: 'edit-parameters',
translatableValue: t`Edit parameters...`,
renderIcon: () => <Add />,
onClick: openParameterEditor,
});
}
} else if (variableSourceType === gd.VariablesContainer.Properties) {
if (openEventsBasedEntityPropertyEditorDialog) {
actions.push({
id: 'edit-properties',
translatableValue: t`Edit properties...`,
renderIcon: () => <Add />,
onClick: openPropertyEditor,
});
}
} else {
if (openVariableEditorDialog) {
actions.push({
id: 'edit-variables',
translatableValue: t`Edit variables...`,
renderIcon: () => <Add />,
onClick: openVariableEditor,
});
}
}
} else if (fieldCurrentValue) {
if (editEventsFunctionParameter) {
actions.push({
id: 'add-parameter',
translatableValue: t`Add parameter...`,
renderIcon: () => <Add />,
onClick: openParameterEditor,
});
}
if (openEventsBasedEntityPropertyEditorDialog) {
actions.push({
id: 'add-property',
translatableValue: t`Add property...`,
renderIcon: () => <Add />,
onClick: openPropertyEditor,
});
}
if (openVariableEditorDialog) {
actions.push({
id: 'add-variable',
translatableValue: t`Add variable...`,
renderIcon: () => <Add />,
onClick: openVariableEditor,
});
}
} else {
if (openEventsBasedEntityPropertyEditorDialog) {
actions.push({
id: 'edit-or-add-properties',
translatableValue: t`Edit or add properties...`,
renderIcon: () => <Add />,
onClick: openPropertyEditor,
});
}
if (openVariableEditorDialog) {
actions.push({
id: 'edit-or-add-variables',
translatableValue: t`Edit or add variables...`,
renderIcon: () => <Add />,
onClick: openVariableEditor,
});
}
}
return actions;
},
[getVariableSourceFromIdentifier, projectScopedContainersAccessor, value]
[
editEventsFunctionParameter,
getVariableSourceFromIdentifier,
openEventsBasedEntityPropertyEditorDialog,
openParameterEditor,
openPropertyEditor,
openVariableEditor,
openVariableEditorDialog,
projectScopedContainersAccessor,
]
);
return (
@@ -475,104 +403,43 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
<ColumnStackLayout noMargin expand>
<TextFieldWithButtonLayout
renderTextField={() => (
<SemiControlledAutoComplete
margin={isInline ? 'none' : 'dense'}
floatingLabelText={description}
helperMarkdownText={
<GenericExpressionField
expressionType={
parameterMetadata
? parameterMetadata.getLongDescription()
: undefined
? // $FlowFixMe[incompatible-type]
parameterMetadata.getValueTypeMetadata().getName()
: // $FlowFixMe[incompatible-type]
''
}
errorText={errorText}
fullWidth
value={value}
onChange={handleValueChange}
onInputValueChange={handleInputValueChange}
onRequestClose={onRequestClose}
onApply={onApply}
filterOptionById={filterOptionById}
dataSource={[
...autocompletionVariableNames,
...(editEventsFunctionParameter
? [
{
id: 'edit-parameters',
translatableValue: t`Edit parameters...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openParameterEditor,
},
{
id: 'add-parameter',
translatableValue: t`Add parameter...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openParameterEditor,
},
]
: []),
...(openEventsBasedEntityPropertyEditorDialog
? [
{
id: 'edit-properties',
translatableValue: t`Edit properties...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openPropertyEditor,
},
{
id: 'add-property',
translatableValue: t`Add property...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openPropertyEditor,
},
{
id: 'edit-or-add-properties',
translatableValue: t`Edit or add properties...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openPropertyEditor,
},
]
: []),
...(openVariableEditorDialog
? [
{
id: 'edit-variables',
translatableValue: t`Edit variables...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openVariableEditor,
},
{
id: 'add-variable',
translatableValue: t`Add variable...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openVariableEditor,
},
{
id: 'edit-or-add-variables',
translatableValue: t`Edit or add variables...`,
text: '',
value: '',
renderIcon: () => <Add />,
onClick: openVariableEditor,
},
]
: []),
]}
openOnFocus={!isInline}
ref={field}
id={id}
globalObjectsContainer={props.globalObjectsContainer}
objectsContainer={props.objectsContainer}
onChange={onChange}
project={project}
projectScopedContainersAccessor={
projectScopedContainersAccessor
}
scope={props.scope}
value={value}
instruction={props.instruction}
instructionMetadata={props.instructionMetadata}
parameterMetadata={parameterMetadata}
expression={props.expression}
expressionMetadata={props.expressionMetadata}
parameterIndex={props.parameterIndex}
onInstructionTypeChanged={onInstructionTypeChanged}
editEventsFunctionParameter={editEventsFunctionParameter}
openEventsBasedEntityPropertyEditorDialog={
openEventsBasedEntityPropertyEditorDialog
}
isInline={isInline}
shouldHideExpressionListButton={true}
onRequestClose={props.onRequestClose}
onApply={props.onApply}
resourceManagementProps={props.resourceManagementProps}
parameterRenderingService={props.parameterRenderingService}
onGetAdditionalActions={onGetAdditionalActions}
/>
)}
renderButton={style =>
@@ -632,7 +499,7 @@ export default (React.forwardRef<Props, VariableFieldInterface>(
}
): React.ComponentType<{
...Props,
+ref?: React.RefSetter<VariableFieldInterface>,
+ref?: React.RefSetter<ParameterFieldInterface>,
}>);
export const renderVariableWithIcon = (
@@ -77,6 +77,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
1
);
@@ -113,6 +115,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode2,
1
);
@@ -153,6 +157,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'string',
'',
'',
expressionNode,
1
);
@@ -211,6 +217,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
9
);
@@ -249,6 +257,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'string',
'',
'',
expressionNode,
16
);
@@ -291,6 +301,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'string',
'',
'',
expressionNode,
'MySpriteObjectWithBehaviors.Speed'.length - 1
);
@@ -343,6 +355,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
24
);
@@ -383,6 +397,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
28
);
@@ -424,6 +440,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
28
);
@@ -465,6 +483,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'number',
'',
'',
expressionNode,
28
);
@@ -511,6 +531,8 @@ describe('ExpressionAutocompletion', () => {
gd.JsPlatform.get(),
projectScopedContainersAccessor.get(),
'string',
'',
'',
expressionNode,
47
);