mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
Improve variable parameter autocompletion (#9164)
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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", "", "", "")
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
Vendored
+1
-1
@@ -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 ? (
|
||||
|
||||
+7
-13
@@ -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);
|
||||
});
|
||||
});
|
||||
+32
-1
@@ -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 && (
|
||||
|
||||
+33
-6
@@ -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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user