GDevelop Core
Core library for developing platforms and tools compatible with GDevelop.
ExpressionCompletionFinder.h
1 /*
2  * GDevelop Core
3  * Copyright 2008-present Florian Rival ([email protected]). All rights
4  * reserved. This project is released under the MIT License.
5  */
6 #pragma once
7 
8 #include <memory>
9 #include <vector>
10 
11 #include "GDCore/Events/Parsers/ExpressionParser2.h"
12 #include "GDCore/Events/Parsers/ExpressionParser2Node.h"
13 #include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
14 #include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
15 #include "GDCore/Events/Parsers/GrammarTerminals.h"
16 #include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
17 #include "GDCore/Extensions/Metadata/InstructionMetadata.h"
18 #include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
19 #include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
20 #include "GDCore/IDE/Events/ExpressionTypeFinder.h"
21 #include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
22 #include "GDCore/IDE/Events/ExpressionVariablePathFinder.h"
23 #include "GDCore/Project/ProjectScopedContainers.h"
24 #include "GDCore/Project/Variable.h"
25 
26 namespace gd {
27 class Expression;
28 class ObjectsContainer;
29 class Platform;
30 class ParameterMetadata;
31 class ExpressionMetadata;
32 class ObjectConfiguration;
33 } // namespace gd
34 
35 namespace gd {
36 
40 struct GD_CORE_API ExpressionCompletionDescription {
41  public:
49  Object,
50  BehaviorWithPrefix,
51  ExpressionWithPrefix,
52  Variable,
53  TextWithPrefix,
54  Property,
55  Parameter
56  };
57 
63  const gd::String& prefix_,
64  size_t replacementStartPosition_,
65  size_t replacementEndPosition_,
66  const gd::String& objectName_) {
68  BehaviorWithPrefix, replacementStartPosition_, replacementEndPosition_);
69  description.SetPrefix(prefix_);
70  description.SetObjectName(objectName_);
71  return description;
72  }
73 
78  const gd::String& type_,
79  const gd::ParameterMetadata& parameterMetadata_,
80  const gd::String& prefix_,
81  size_t replacementStartPosition_,
82  size_t replacementEndPosition_,
83  const bool isLastParameter_,
84  const gd::String& objectName_ = "") {
85  auto description = ExpressionCompletionDescription(
86  TextWithPrefix, replacementStartPosition_, replacementEndPosition_);
87  description.SetObjectName(objectName_);
88  description.SetType(type_);
89  description.SetPrefix(prefix_);
90  description.SetIsLastParameter(isLastParameter_);
91  description.SetParameterMetadata(parameterMetadata_);
92  return description;
93  }
94 
100  const gd::String& type_,
101  const gd::String& prefix_,
102  size_t replacementStartPosition_,
103  size_t replacementEndPosition_,
104  const gd::String& objectName_ = "",
105  const gd::String& behaviorName_ = "") {
106  ExpressionCompletionDescription description(ExpressionWithPrefix,
107  replacementStartPosition_,
108  replacementEndPosition_);
109  description.SetObjectName(objectName_);
110  description.SetBehaviorName(behaviorName_);
111  description.SetType(type_);
112  description.SetPrefix(prefix_);
113  return description;
114  }
115 
117  bool operator==(const ExpressionCompletionDescription& other) const {
118  return completionKind == other.completionKind && type == other.type &&
119  variableType == other.variableType && prefix == other.prefix &&
120  objectName == other.objectName && completion == other.completion &&
121  behaviorName == other.behaviorName;
122  };
123 
125  CompletionKind GetCompletionKind() const { return completionKind; }
126 
131  const gd::String& GetType() const { return type; }
132 
133  ExpressionCompletionDescription& SetType(const gd::String& type_) {
134  type = type_;
135  return *this;
136  }
137 
141  gd::Variable::Type GetVariableType() const { return variableType; }
142 
143  ExpressionCompletionDescription& SetVariableType(
144  gd::Variable::Type variableType_) {
145  variableType = variableType_;
146  return *this;
147  }
148 
152  gd::VariablesContainer::SourceType GetVariableScope() const {
153  return variableScope;
154  }
155 
157  SetVariableScope(gd::VariablesContainer::SourceType variableScope_) {
158  variableScope = variableScope_;
159  return *this;
160  }
161 
165  const gd::String& GetPrefix() const { return prefix; }
166 
167  ExpressionCompletionDescription& SetPrefix(const gd::String& prefix_) {
168  prefix = prefix_;
169  return *this;
170  }
171 
175  const gd::String& GetCompletion() const { return completion; }
176 
177  ExpressionCompletionDescription& SetCompletion(
178  const gd::String& completion_) {
179  completion = completion_;
180  return *this;
181  }
182 
187  const gd::String& GetObjectName() const { return objectName; }
188 
189  ExpressionCompletionDescription& SetObjectName(
190  const gd::String& objectName_) {
191  objectName = objectName_;
192  return *this;
193  }
194 
202  const gd::String& GetBehaviorName() const { return behaviorName; }
203 
204  ExpressionCompletionDescription& SetBehaviorName(
205  const gd::String& behaviorName_) {
206  behaviorName = behaviorName_;
207  return *this;
208  }
209 
216  isExact = isExact_;
217  return *this;
218  }
219 
225  bool IsExact() const { return isExact; }
226 
231  return replacementStartPosition;
232  }
233 
237  size_t GetReplacementEndPosition() const { return replacementEndPosition; }
238 
243  isLastParameter = isLastParameter_;
244  return *this;
245  }
246 
250  bool IsLastParameter() const { return isLastParameter; }
251 
257  const gd::ParameterMetadata& parameterMetadata_) {
258  parameterMetadata = &parameterMetadata_;
259  return *this;
260  }
261 
265  bool HasParameterMetadata() const {
266  return parameterMetadata != &badParameterMetadata;
267  }
268 
274  return *parameterMetadata;
275  }
276 
282  const gd::ObjectConfiguration* objectConfiguration_) {
283  objectConfiguration = objectConfiguration_;
284  if (!objectConfiguration) objectConfiguration = &badObjectConfiguration;
285  return *this;
286  }
287 
291  bool HasObjectConfiguration() const {
292  return objectConfiguration != &badObjectConfiguration;
293  }
294 
300  return *objectConfiguration;
301  }
302 
303  gd::String ToString() const {
304  return "{ " + gd::String::From(GetCompletionKind()) + ", " +
305  (GetType() || "no type") + ", " +
306  gd::String::From(GetVariableType()) + ", " +
307  (GetPrefix() || "no prefix") + ", " +
308  (GetCompletion() || "no completion") + ", " +
309  (GetObjectName() || "no object name") + ", " +
310  (GetBehaviorName() || "no behavior name") + ", " +
311  (IsExact() ? "exact" : "non-exact") + ", " +
312  (IsLastParameter() ? "last parameter" : "not last parameter") +
313  ", " +
314  (HasParameterMetadata() ? "with parameter metadata"
315  : "no parameter metadata") +
316  ", " +
317  (HasObjectConfiguration() ? "with object configuration"
318  : "no object configuration") +
319  " }";
320  }
321 
324 
325  ExpressionCompletionDescription(CompletionKind completionKind_,
326  size_t replacementStartPosition_,
327  size_t replacementEndPosition_)
328  : completionKind(completionKind_),
329  variableType(gd::Variable::Number),
330  variableScope(gd::VariablesContainer::Unknown),
331  replacementStartPosition(replacementStartPosition_),
332  replacementEndPosition(replacementEndPosition_),
333  isExact(false),
334  isLastParameter(false),
335  parameterMetadata(&badParameterMetadata),
336  objectConfiguration(&badObjectConfiguration) {}
337 
338  private:
339  CompletionKind completionKind;
340  gd::Variable::Type variableType = gd::Variable::Unknown;
341  gd::VariablesContainer::SourceType variableScope = gd::VariablesContainer::Unknown;
342  gd::String type;
343  gd::String prefix;
344  gd::String completion;
345  size_t replacementStartPosition = 0;
346  size_t replacementEndPosition = 0;
347  gd::String objectName;
348  gd::String behaviorName;
349  bool isExact = false;
350  bool isLastParameter = false;
351  const gd::ParameterMetadata* parameterMetadata = &badParameterMetadata;
352  const gd::ObjectConfiguration* objectConfiguration = &badObjectConfiguration;
353 
354  static const gd::ParameterMetadata badParameterMetadata;
355  static const gd::ObjectConfiguration badObjectConfiguration;
356 };
357 
361 GD_CORE_API std::ostream& operator<<(
362  std::ostream& os, ExpressionCompletionDescription const& value);
363 
369 class GD_CORE_API ExpressionCompletionFinder
370  : public ExpressionParser2NodeWorker {
371  public:
376  static std::vector<ExpressionCompletionDescription>
378  const gd::Platform& platform,
379  const gd::ProjectScopedContainers& projectScopedContainers,
380  const gd::String& rootType,
381  gd::ExpressionNode& node,
382  size_t searchedPosition) {
383  gd::ExpressionNodeLocationFinder finder(searchedPosition);
384  node.Visit(finder);
385  gd::ExpressionNode* nodeAtLocation = finder.GetNode();
386 
387  if (nodeAtLocation == nullptr) {
388  std::vector<ExpressionCompletionDescription> emptyCompletions;
389  return emptyCompletions;
390  }
391 
392  gd::ExpressionNode* maybeParentNodeAtLocation = finder.GetParentNode();
393  gd::ExpressionCompletionFinder autocompletionProvider(
394  platform,
395  projectScopedContainers,
396  rootType,
397  searchedPosition,
398  maybeParentNodeAtLocation);
399  nodeAtLocation->Visit(autocompletionProvider);
400  return autocompletionProvider.GetCompletionDescriptions();
401  }
402 
406  const std::vector<ExpressionCompletionDescription>&
408  return completions;
409  };
410 
411  virtual ~ExpressionCompletionFinder(){};
412 
413  protected:
414  void OnVisitSubExpressionNode(SubExpressionNode& node) override {
415  auto type = gd::ExpressionTypeFinder::GetType(
416  platform, projectScopedContainers, rootType, node);
417 
418  AddCompletionsForAllIdentifiersMatchingSearch("", type);
419  completions.push_back(
421  type, "", searchedPosition + 1, searchedPosition + 1));
422  }
423  void OnVisitOperatorNode(OperatorNode& node) override {
424  // No completions.
425  }
426  void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
428  platform, projectScopedContainers, rootType, node);
429 
430  AddCompletionsForAllIdentifiersMatchingSearch("", type);
431  completions.push_back(
433  type, "", searchedPosition + 1, searchedPosition + 1));
434  }
435  void OnVisitNumberNode(NumberNode& node) override {
436  // No completions
437  }
438 
439  void OnVisitTextNode(TextNode& node) override {
440  // Completions are searched in the case the text node is a parameter of a
441  // function call.
442  FunctionCallNode* functionCall =
443  dynamic_cast<FunctionCallNode*>(maybeParentNodeAtLocation);
444  if (functionCall != nullptr) {
445  int parameterIndex = -1;
446  for (int i = 0; i < functionCall->parameters.size(); i++) {
447  if (functionCall->parameters.at(i).get() == &node) {
448  parameterIndex = i;
449  break;
450  }
451  }
452  if (parameterIndex < 0) {
453  return;
454  }
455  // Search the parameter metadata index skipping invisible ones.
456  size_t visibleParameterIndex = 0;
457  size_t metadataParameterIndex =
459  functionCall->objectName, functionCall->behaviorName);
460  const auto& objectsContainersList =
461  projectScopedContainers.GetObjectsContainersList();
462  const gd::ExpressionMetadata& metadata =
463  MetadataProvider::GetFunctionCallMetadata(
464  platform, objectsContainersList, *functionCall);
465 
466  const gd::ParameterMetadata *parameterMetadata = nullptr;
467  while (metadataParameterIndex <
468  metadata.GetParameters().GetParametersCount()) {
469  if (!metadata.GetParameters()
470  .GetParameter(metadataParameterIndex)
471  .IsCodeOnly()) {
472  if (visibleParameterIndex == parameterIndex) {
473  parameterMetadata =
474  &metadata.GetParameters().GetParameter(metadataParameterIndex);
475  }
476  visibleParameterIndex++;
477  }
478  metadataParameterIndex++;
479  }
480  const int visibleParameterCount = visibleParameterIndex;
481  if (parameterMetadata == nullptr) {
482  // There are too many parameters in the expression, this text node is
483  // not actually linked to a parameter expected by the function call.
484  return;
485  }
486 
487  const gd::String& type = parameterMetadata->GetType();
488  if (type == "string") {
489  // No completions for an arbitrary string.
490  return;
491  }
492 
493  bool isLastParameter = parameterIndex == visibleParameterCount - 1;
495  type,
496  *parameterMetadata,
497  node.text,
498  node.location.GetStartPosition(),
499  node.location.GetEndPosition(),
500  isLastParameter,
501  functionCall->objectName));
502  }
503  }
504  void OnVisitVariableNode(VariableNode& node) override {
505  const auto& objectsContainersList =
506  projectScopedContainers.GetObjectsContainersList();
508  platform, projectScopedContainers, rootType, node);
509 
510  // Only attempt to complete with the children of the variable
511  // if it's the last child (no more `.AnotherVariable` written after).
512  bool eagerlyCompleteIfExactMatch = node.child == nullptr;
513 
515  if (type == "globalvar" || type == "scenevar") {
516  const auto* variablesContainer =
517  type == "globalvar"
518  ? projectScopedContainers.GetVariablesContainersList()
519  .GetTopMostVariablesContainer()
520  : projectScopedContainers.GetVariablesContainersList()
521  .GetBottomMostVariablesContainer();
522  if (variablesContainer) {
523  AddCompletionsForVariablesMatchingSearch(*variablesContainer,
524  node.name,
525  node.nameLocation,
526  eagerlyCompleteIfExactMatch);
527  }
528  } else if (type == "objectvar") {
530  platform, objectsContainersList, rootObjectName, node);
531 
532  AddCompletionsForObjectOrGroupVariablesMatchingSearch(
533  objectsContainersList,
534  objectName,
535  node.name,
536  node.nameLocation,
537  eagerlyCompleteIfExactMatch);
538  }
539  } else {
540  AddCompletionsForObjectsAndVariablesMatchingSearch(
541  node.name, type, node.nameLocation, eagerlyCompleteIfExactMatch);
542  }
543  }
544  void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
545  VariableAndItsParent variableAndItsParent =
546  gd::ExpressionVariablePathFinder::GetLastParentOfNode(
547  platform, projectScopedContainers, node);
548 
549  // If no child, we're at the end of a variable (like `GrandChild` in
550  // `Something.Child.GrandChild`) so we can complete eagerly children if we
551  // can.
552  gd::String eagerlyCompleteForVariableName =
553  node.child == nullptr ? node.name : "";
554  AddCompletionsForChildrenVariablesOf(variableAndItsParent,
555  node.nameLocation,
556  eagerlyCompleteForVariableName);
557  }
558  void OnVisitVariableBracketAccessorNode(
559  VariableBracketAccessorNode& node) override {}
560  void OnVisitIdentifierNode(IdentifierNode& node) override {
561  const auto& objectsContainersList =
562  projectScopedContainers.GetObjectsContainersList();
564  platform, projectScopedContainers, rootType, node);
566  // Only show completions of objects if an object is required.
567  AddCompletionsForObjectMatchingSearch(
568  node.identifierName, type, node.location);
570  if (type == "globalvar" || type == "scenevar") {
571  const auto* variablesContainer =
572  type == "globalvar"
573  ? projectScopedContainers.GetVariablesContainersList()
574  .GetTopMostVariablesContainer()
575  : projectScopedContainers.GetVariablesContainersList()
576  .GetBottomMostVariablesContainer();
577  if (variablesContainer) {
578  if (IsCaretOn(node.identifierNameDotLocation) ||
579  IsCaretOn(node.childIdentifierNameLocation)) {
580  // Complete a potential child variable:
581  if (variablesContainer->Has(node.identifierName)) {
582  AddCompletionsForChildrenVariablesOf(
583  &variablesContainer->Get(node.identifierName),
584  node.childIdentifierNameLocation,
585  node.childIdentifierName);
586  }
587  } else {
588  // Complete a root variable of the scene or project.
589 
590  // Don't attempt to complete children variables if there is
591  // already a dot written (`MyVariable.`).
592  bool eagerlyCompleteIfPossible =
593  !node.identifierNameDotLocation.IsValid();
594  AddCompletionsForVariablesMatchingSearch(
595  *variablesContainer,
596  node.identifierName,
597  node.identifierNameLocation,
598  eagerlyCompleteIfPossible);
599  }
600  }
601  } else if (type == "objectvar") {
603  platform, objectsContainersList, rootObjectName, node);
604 
605  if (IsCaretOn(node.identifierNameDotLocation) ||
606  IsCaretOn(node.childIdentifierNameLocation)) {
607  // Complete a potential child variable:
608  const auto* variablesContainer =
609  objectsContainersList.GetObjectOrGroupVariablesContainer(
610  objectName);
611  if (variablesContainer &&
612  variablesContainer->Has(node.identifierName)) {
613  AddCompletionsForChildrenVariablesOf(
614  &variablesContainer->Get(node.identifierName),
615  node.childIdentifierNameLocation,
616  node.childIdentifierName);
617  }
618  } else {
619  // Complete a root variable of the object.
620 
621  // Don't attempt to complete children variables if there is
622  // already a dot written (`MyVariable.`).
623  bool eagerlyCompleteIfPossible =
624  !node.identifierNameDotLocation.IsValid();
625  AddCompletionsForObjectOrGroupVariablesMatchingSearch(
626  objectsContainersList,
627  objectName,
628  node.identifierName,
629  node.identifierNameLocation,
630  eagerlyCompleteIfPossible);
631  }
632  }
633  } else {
634  // Object function, behavior name, variable, object variable.
635  if (IsCaretOn(node.identifierNameLocation)) {
636  // Don't attempt to complete children variables if there is
637  // already a dot written (`MyVariable.`).
638  bool eagerlyCompleteIfPossible =
639  !node.identifierNameDotLocation.IsValid();
640  AddCompletionsForAllIdentifiersMatchingSearch(
641  node.identifierName,
642  type,
643  node.identifierNameLocation,
644  eagerlyCompleteIfPossible);
645  if (!node.identifierNameDotLocation.IsValid()) {
646  completions.push_back(
648  type,
649  node.identifierName,
650  node.identifierNameLocation.GetStartPosition(),
651  node.identifierNameLocation.GetEndPosition()));
652  }
653  } else if (IsCaretOn(node.identifierNameDotLocation) ||
654  IsCaretOn(node.childIdentifierNameLocation)) {
655  // Might be:
656  // - An object variable, object behavior or object expression.
657  // - Or a variable with a child.
658  projectScopedContainers.MatchIdentifierWithName<void>(
659  node.identifierName,
660  [&]() {
661  // This is an object.
662  const gd::String& objectName = node.identifierName;
663  AddCompletionsForObjectOrGroupVariablesMatchingSearch(
664  objectsContainersList,
665  objectName,
666  node.childIdentifierName,
667  node.childIdentifierNameLocation,
668  true);
669 
670  completions.push_back(
672  node.childIdentifierName,
673  node.childIdentifierNameLocation.GetStartPosition(),
674  node.childIdentifierNameLocation.GetEndPosition(),
675  objectName));
676  completions.push_back(
678  type,
679  node.childIdentifierName,
680  node.childIdentifierNameLocation.GetStartPosition(),
681  node.childIdentifierNameLocation.GetEndPosition(),
682  objectName));
683  },
684  [&]() {
685  // This is a variable.
686  VariableAndItsParent variableAndItsParent =
687  gd::ExpressionVariablePathFinder::GetLastParentOfNode(
688  platform, projectScopedContainers, node);
689 
690  AddCompletionsForChildrenVariablesOf(
691  variableAndItsParent,
692  node.childIdentifierNameLocation,
693  node.childIdentifierName);
694  },
695  [&]() {
696  // Ignore properties here.
697  // There is no support for "children" of properties.
698  },
699  [&]() {
700  // Ignore parameters here.
701  // There is no support for "children" of parameters.
702  },
703  [&]() {
704  // Ignore unrecognised identifiers here.
705  });
706  }
707  }
708  }
709  void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
711  platform, projectScopedContainers, rootType, node);
712  if (!node.behaviorFunctionName.empty() ||
713  node.behaviorNameNamespaceSeparatorLocation.IsValid()) {
714  // Behavior function (or behavior function being written, with the
715  // function name missing)
716  if (IsCaretOn(node.objectNameLocation)) {
717  AddCompletionsForObjectMatchingSearch(
718  node.objectName, type, node.objectNameLocation);
719  } else if (IsCaretOn(node.objectNameDotLocation) ||
720  IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
721  completions.push_back(
723  node.objectFunctionOrBehaviorName,
724  node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
725  node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
726  node.objectName));
727  } else if (IsCaretOn(node.behaviorNameNamespaceSeparatorLocation) ||
728  IsCaretOn(node.behaviorFunctionNameLocation)) {
729  completions.push_back(
731  type,
732  node.behaviorFunctionName,
733  node.behaviorFunctionNameLocation.GetStartPosition(),
734  node.behaviorFunctionNameLocation.GetEndPosition(),
735  node.objectName,
736  node.objectFunctionOrBehaviorName));
737  }
738  } else {
739  // Object function or behavior name
740  if (IsCaretOn(node.objectNameLocation)) {
741  AddCompletionsForObjectMatchingSearch(
742  node.objectName, type, node.objectNameLocation);
743  } else if (IsCaretOn(node.objectNameDotLocation) ||
744  IsCaretOn(node.objectFunctionOrBehaviorNameLocation)) {
745  completions.push_back(
747  node.objectFunctionOrBehaviorName,
748  node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
749  node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
750  node.objectName));
751  completions.push_back(
753  type,
754  node.objectFunctionOrBehaviorName,
755  node.objectFunctionOrBehaviorNameLocation.GetStartPosition(),
756  node.objectFunctionOrBehaviorNameLocation.GetEndPosition(),
757  node.objectName));
758  }
759  }
760  }
761  void OnVisitFunctionCallNode(FunctionCallNode& node) override {
763  platform, projectScopedContainers, rootType, node);
764  bool isCaretOnParenthesis = IsCaretOn(node.openingParenthesisLocation) ||
765  IsCaretOn(node.closingParenthesisLocation);
766 
767  if (!node.behaviorName.empty()) {
768  // Behavior function
769  if (IsCaretOn(node.objectNameLocation)) {
770  AddCompletionsForObjectMatchingSearch(
771  node.objectName, type, node.objectNameLocation);
772  } else if (IsCaretOn(node.objectNameDotLocation) ||
773  IsCaretOn(node.behaviorNameLocation)) {
774  completions.push_back(
776  node.behaviorName,
777  node.behaviorNameLocation.GetStartPosition(),
778  node.behaviorNameLocation.GetEndPosition(),
779  node.objectName));
780  } else {
781  completions.push_back(
783  type,
784  node.functionName,
785  node.functionNameLocation.GetStartPosition(),
786  node.functionNameLocation.GetEndPosition(),
787  node.objectName,
788  node.behaviorName)
789  .SetIsExact(isCaretOnParenthesis));
790  }
791  } else if (!node.objectName.empty()) {
792  // Object function
793  if (IsCaretOn(node.objectNameLocation)) {
794  AddCompletionsForObjectMatchingSearch(
795  node.objectName, type, node.objectNameLocation);
796  } else {
797  // Add completions for behaviors, because we could imagine that the user
798  // wants to move from an object function to a behavior function, and so
799  // need behavior completions. Do this unless we're on the parenthesis
800  // (at which point we're only showing informative message about the
801  // function).
802  if (!isCaretOnParenthesis) {
803  completions.push_back(
805  node.functionName,
806  node.objectNameLocation.GetStartPosition(),
807  node.objectNameLocation.GetEndPosition(),
808  node.objectName));
809  }
810 
811  completions.push_back(
813  type,
814  node.functionName,
815  node.functionNameLocation.GetStartPosition(),
816  node.functionNameLocation.GetEndPosition(),
817  node.objectName)
818  .SetIsExact(isCaretOnParenthesis));
819  }
820  } else {
821  // Free function
822  completions.push_back(
824  type,
825  node.functionName,
826  node.functionNameLocation.GetStartPosition(),
827  node.functionNameLocation.GetEndPosition())
828  .SetIsExact(isCaretOnParenthesis));
829  }
830  }
831  void OnVisitEmptyNode(EmptyNode& node) override {
833  platform, projectScopedContainers, rootType, node);
834 
835  AddCompletionsForAllIdentifiersMatchingSearch(
836  node.text, type, node.location);
837  completions.push_back(
839  type,
840  node.text,
841  node.location.GetStartPosition(),
842  node.location.GetEndPosition()));
843  }
844 
845  private:
846  bool IsCaretOn(const ExpressionParserLocation& location,
847  bool inclusive = false) {
848  if (!location.IsValid()) return false;
849 
850  return (location.GetStartPosition() <= searchedPosition &&
851  ((!inclusive && searchedPosition < location.GetEndPosition()) ||
852  (inclusive && searchedPosition <= location.GetEndPosition())));
853  }
854 
859  bool IsIdentifierSafe(const gd::String& name) {
860  if (name.empty()) return false;
861 
862  for (auto character : name) {
863  if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
864  return false;
865  }
866  }
867 
868  return true;
869  }
870 
871  void AddCompletionsForChildrenVariablesOf(
872  VariableAndItsParent variableAndItsParent,
873  const ExpressionParserLocation& location,
874  gd::String eagerlyCompleteForVariableName = "") {
875  if (variableAndItsParent.parentVariable) {
876  AddCompletionsForChildrenVariablesOf(variableAndItsParent.parentVariable,
877  location,
878  eagerlyCompleteForVariableName);
879  } else if (variableAndItsParent.parentVariablesContainer) {
880  AddCompletionsForVariablesMatchingSearch(
881  *variableAndItsParent.parentVariablesContainer, "", location);
882  }
883  }
884 
885  void AddCompletionsForChildrenVariablesOf(
886  const gd::Variable* variable,
887  const ExpressionParserLocation& location,
888  gd::String eagerlyCompleteForVariableName = "") {
889  if (!variable) return;
890 
891  if (variable->GetType() == gd::Variable::Structure) {
892  for (const auto& name : variable->GetAllChildrenNames()) {
893  if (!IsIdentifierSafe(name)) continue;
894 
895  const auto& childVariable = variable->GetChild(name);
896  ExpressionCompletionDescription description(
897  ExpressionCompletionDescription::Variable,
898  location.GetStartPosition(),
899  location.GetEndPosition());
900  description.SetCompletion(name);
901  description.SetVariableType(childVariable.GetType());
902  completions.push_back(description);
903 
904  if (name == eagerlyCompleteForVariableName) {
905  AddEagerCompletionForVariableChildren(childVariable, name, location);
906  }
907  }
908  } else {
909  // TODO: we could do a "comment only completion" to indicate that nothing
910  // can/should be completed?
911  }
912  }
913 
914  void AddEagerCompletionForVariableChildren(
915  const gd::Variable& variable,
916  const gd::String& variableName,
917  const ExpressionParserLocation& location) {
918  if (variable.GetType() == gd::Variable::Structure) {
919  gd::String prefix = variableName + ".";
920  for (const auto& name : variable.GetAllChildrenNames()) {
921  gd::String completion =
922  IsIdentifierSafe(name)
923  ? (prefix + name)
924  : (variableName + "[" +
925  gd::ExpressionParser2NodePrinter::PrintStringLiteral(name) +
926  "]");
927 
928  const auto& childVariable = variable.GetChild(name);
929  ExpressionCompletionDescription description(
930  ExpressionCompletionDescription::Variable,
931  location.GetStartPosition(),
932  location.GetEndPosition());
933  description.SetCompletion(completion);
934  description.SetVariableType(childVariable.GetType());
935  completions.push_back(description);
936  }
937  }
938  }
939 
940  void AddCompletionsForVariablesMatchingSearch(
941  const gd::VariablesContainer& variablesContainer,
942  const gd::String& search,
943  const ExpressionParserLocation& location,
944  bool eagerlyCompleteIfExactMatch = false) {
945  variablesContainer.ForEachVariableMatchingSearch(
946  search,
947  [&](const gd::String& variableName, const gd::Variable& variable) {
948  ExpressionCompletionDescription description(
949  ExpressionCompletionDescription::Variable,
950  location.GetStartPosition(),
951  location.GetEndPosition());
952  description.SetCompletion(variableName);
953  description.SetVariableType(variable.GetType());
954  description.SetVariableScope(variablesContainer.GetSourceType());
955  completions.push_back(description);
956 
957  if (eagerlyCompleteIfExactMatch && variableName == search) {
958  AddEagerCompletionForVariableChildren(
959  variable, variableName, location);
960  }
961  });
962  }
963 
964  void AddCompletionsForObjectOrGroupVariablesMatchingSearch(
965  const gd::ObjectsContainersList& objectsContainersList,
966  const gd::String& objectOrGroupName,
967  const gd::String& search,
968  const ExpressionParserLocation& location,
969  bool eagerlyCompleteIfExactMatch) {
970  objectsContainersList.ForEachObjectOrGroupVariableMatchingSearch(
971  objectOrGroupName,
972  search,
973  [&](const gd::String& variableName, const gd::Variable& variable) {
974  ExpressionCompletionDescription description(
975  ExpressionCompletionDescription::Variable,
976  location.GetStartPosition(),
977  location.GetEndPosition());
978  description.SetCompletion(variableName);
979  description.SetVariableType(variable.GetType());
980  description.SetVariableScope(gd::VariablesContainer::Object);
981  completions.push_back(description);
982 
983  if (eagerlyCompleteIfExactMatch && variableName == search) {
984  AddEagerCompletionForVariableChildren(
985  variable, variableName, location);
986  }
987  });
988  }
989 
990  void AddCompletionsForObjectMatchingSearch(
991  const gd::String& search,
992  const gd::String& type,
993  const ExpressionParserLocation& location) {
994  projectScopedContainers.GetObjectsContainersList()
995  .ForEachNameMatchingSearch(
996  search,
997  [&](const gd::String& name,
998  const gd::ObjectConfiguration* objectConfiguration) {
999  ExpressionCompletionDescription description(
1000  ExpressionCompletionDescription::Object,
1001  location.GetStartPosition(),
1002  location.GetEndPosition());
1003  description.SetObjectConfiguration(objectConfiguration);
1004  description.SetCompletion(name);
1005  description.SetType(type);
1006  completions.push_back(description);
1007  });
1008  }
1009 
1010  void AddCompletionsForObjectsAndVariablesMatchingSearch(
1011  const gd::String& search,
1012  const gd::String& type,
1013  const ExpressionParserLocation& location,
1014  bool eagerlyCompleteIfExactMatch) {
1015  projectScopedContainers.ForEachIdentifierMatchingSearch(
1016  search,
1017  [&](const gd::String& objectName,
1018  const ObjectConfiguration* objectConfiguration) {
1019  ExpressionCompletionDescription description(
1020  ExpressionCompletionDescription::Object,
1021  location.GetStartPosition(),
1022  location.GetEndPosition());
1023  description.SetObjectConfiguration(objectConfiguration);
1024  description.SetCompletion(objectName);
1025  description.SetType(type);
1026  completions.push_back(description);
1027  },
1028  [&](const gd::String& variableName, const gd::Variable& variable) {
1029  ExpressionCompletionDescription description(
1030  ExpressionCompletionDescription::Variable,
1031  location.GetStartPosition(),
1032  location.GetEndPosition());
1033  description.SetCompletion(variableName);
1034  description.SetVariableType(variable.GetType());
1035  description.SetVariableScope(
1036  projectScopedContainers.GetVariablesContainersList()
1037  .GetVariablesContainerFromVariableName(variableName)
1038  .GetSourceType());
1039  completions.push_back(description);
1040 
1041  if (eagerlyCompleteIfExactMatch && variableName == search) {
1042  AddEagerCompletionForVariableChildren(
1043  variable, variableName, location);
1044  }
1045  },
1046  [&](const gd::NamedPropertyDescriptor& property) {
1047  // Ignore properties here.
1048  },
1049  [&](const gd::ParameterMetadata& parameter) {
1050  // Ignore parameters here.
1051  });
1052  }
1053 
1054  void AddCompletionsForAllIdentifiersMatchingSearch(const gd::String& search,
1055  const gd::String& type) {
1056  AddCompletionsForAllIdentifiersMatchingSearch(
1057  search,
1058  type,
1059  ExpressionParserLocation(searchedPosition + 1, searchedPosition + 1));
1060  }
1061 
1062  void AddCompletionsForAllIdentifiersMatchingSearch(
1063  const gd::String& search,
1064  const gd::String& type,
1065  const ExpressionParserLocation& location,
1066  bool eagerlyCompleteIfExactMatch = false) {
1067  projectScopedContainers.ForEachIdentifierMatchingSearch(
1068  search,
1069  [&](const gd::String &objectName,
1070  const ObjectConfiguration *objectConfiguration) {
1071  ExpressionCompletionDescription description(
1072  ExpressionCompletionDescription::Object,
1073  location.GetStartPosition(),
1074  location.GetEndPosition());
1075  description.SetObjectConfiguration(objectConfiguration);
1076  description.SetCompletion(objectName);
1077  description.SetType(type);
1078  completions.push_back(description);
1079  },
1080  [&](const gd::String &variableName, const gd::Variable &variable) {
1081  ExpressionCompletionDescription description(
1082  ExpressionCompletionDescription::Variable,
1083  location.GetStartPosition(),
1084  location.GetEndPosition());
1085  description.SetCompletion(variableName);
1086  description.SetVariableType(variable.GetType());
1087  description.SetVariableScope(
1088  projectScopedContainers.GetVariablesContainersList()
1089  .GetVariablesContainerFromVariableName(variableName)
1090  .GetSourceType());
1091  completions.push_back(description);
1092 
1093  if (eagerlyCompleteIfExactMatch && variableName == search) {
1094  AddEagerCompletionForVariableChildren(
1095  variable, variableName, location);
1096  }
1097  },
1098  [&](const gd::NamedPropertyDescriptor &property) {
1100  property.GetType());
1101  if (gd::ValueTypeMetadata::IsTypeValue("number", propertyType) ||
1102  gd::ValueTypeMetadata::IsTypeValue("string", propertyType)) {
1103  ExpressionCompletionDescription description(
1104  ExpressionCompletionDescription::Property,
1105  location.GetStartPosition(), location.GetEndPosition());
1106  description.SetCompletion(property.GetName());
1107  description.SetType(property.GetType());
1108  completions.push_back(description);
1109  }
1110  },
1111  [&](const gd::ParameterMetadata &parameter) {
1112  if (parameter.GetValueTypeMetadata().IsNumber() ||
1113  parameter.GetValueTypeMetadata().IsString()) {
1114  ExpressionCompletionDescription description(
1115  ExpressionCompletionDescription::Parameter,
1116  location.GetStartPosition(), location.GetEndPosition());
1117  description.SetCompletion(parameter.GetName());
1118  description.SetType(parameter.GetType());
1119  completions.push_back(description);
1120  }
1121  });
1122  }
1123 
1124  ExpressionCompletionFinder(
1125  const gd::Platform& platform_,
1126  const gd::ProjectScopedContainers& projectScopedContainers_,
1127  const gd::String& rootType_,
1128  size_t searchedPosition_,
1129  gd::ExpressionNode* maybeParentNodeAtLocation_)
1130  : searchedPosition(searchedPosition_),
1131  maybeParentNodeAtLocation(maybeParentNodeAtLocation_),
1132  platform(platform_),
1133  projectScopedContainers(projectScopedContainers_),
1134  rootType(rootType_),
1135  rootObjectName("") // Always empty, might be changed if variable fields
1136  // in the editor are changed to use completion.
1137  {};
1138 
1139  std::vector<ExpressionCompletionDescription> completions;
1140  size_t searchedPosition;
1141  gd::ExpressionNode* maybeParentNodeAtLocation;
1142 
1143  const gd::Platform& platform;
1144  const gd::ProjectScopedContainers& projectScopedContainers;
1145  const gd::String rootType;
1146  const gd::String rootObjectName;
1147 };
1148 
1149 } // namespace gd
Returns the list of completion descriptions for an expression node.
Definition: ExpressionCompletionFinder.h:370
const std::vector< ExpressionCompletionDescription > & GetCompletionDescriptions()
Return the completions found for the visited node.
Definition: ExpressionCompletionFinder.h:407
static std::vector< ExpressionCompletionDescription > GetCompletionDescriptionsFor(const gd::Platform &platform, const gd::ProjectScopedContainers &projectScopedContainers, const gd::String &rootType, gd::ExpressionNode &node, size_t searchedPosition)
Given the expression, find the node at the specified location and returns completions for it.
Definition: ExpressionCompletionFinder.h:377
Describe user-friendly information about an expression, its parameters and the function name as well ...
Definition: ExpressionMetadata.h:47
Find the deepest node at the specified location in an expression.
Definition: ExpressionNodeLocationFinder.h:30
ExpressionNode * GetParentNode()
Return the parent of deepest node found at the search position, if any.
Definition: ExpressionNodeLocationFinder.h:74
ExpressionNode * GetNode()
Return the deepest node found at the search position, if any.
Definition: ExpressionNodeLocationFinder.h:68
static size_t WrittenParametersFirstIndex(const gd::String &objectName, const gd::String &behaviorName)
Definition: ExpressionParser2.h:73
The interface for any worker class ("visitor" pattern) that want to interact with the nodes of a pars...
Definition: ExpressionParser2NodeWorker.h:36
static const gd::String GetType(const gd::Platform &platform, const gd::ProjectScopedContainers &projectScopedContainers, const gd::String &rootType, gd::ExpressionNode &node)
Helper function to find the type of the expression or sub-expression that a given node represents.
Definition: ExpressionTypeFinder.h:52
const gd::String & GetObjectName()
Get all the errors.
Definition: ExpressionVariableOwnerFinder.h:75
Used to describe a property shown in a property grid.
Definition: NamedPropertyDescriptor.h:21
Base class used to represent an object configuration. For example, this can be the animations in a sp...
Definition: ObjectConfiguration.h:38
Represent an object of a platform.
Definition: Object.h:37
A list of objects containers, useful for accessing objects in a scoped way, along with methods to acc...
Definition: ObjectsContainersList.h:29
void ForEachObjectOrGroupVariableMatchingSearch(const gd::String &objectOrGroupName, const gd::String &search, std::function< void(const gd::String &variableName, const gd::Variable &variable)> fn) const
Call the callback for each variable of the object (or group) matching the search passed in parameter.
Definition: ObjectsContainersList.cpp:274
gd::ParameterMetadata & GetParameter(const gd::String &name)
Get the function with the specified name.
Definition: ParameterMetadataContainer.h:44
std::size_t GetParametersCount() const
Return the number of functions.
Definition: ParameterMetadataContainer.h:79
Describe a parameter of an instruction (action, condition) or of an expression: type,...
Definition: ParameterMetadata.h:27
static bool IsObject(const gd::String &parameterType)
Return true if the type of the parameter is representing one object (or more, i.e: an object group).
Definition: ParameterMetadata.h:192
bool IsCodeOnly() const
Return true if the parameter is only meant to be completed during compilation and must not be display...
Definition: ParameterMetadata.h:144
const gd::String & GetType() const
Return the type of the parameter.
Definition: ParameterMetadata.h:61
Base class for implementing a platform.
Definition: Platform.h:42
Holds references to variables, objects, properties and other containers.
Definition: ProjectScopedContainers.h:34
String represents an UTF8 encoded string.
Definition: String.h:33
static String From(T value)
Method to create a gd::String from a number (float, double, int, ...)
Definition: String.h:221
void push_back(value_type character)
Add a character (from its codepoint) at the end of the String.
Definition: String.cpp:223
bool empty() const
Returns true if the string is empty.
Definition: String.h:157
static const gd::String & ConvertPropertyTypeToValueType(const gd::String &propertyType)
Return the ValueTypeMetadata name for a property type.
Definition: ValueTypeMetadata.cpp:83
static bool IsTypeLegacyPreScopedVariable(const gd::String &type)
Return true if the type is a variable but from a specific scope (scene, project or object)....
Definition: ValueTypeMetadata.h:165
static bool IsTypeValue(const gd::String &type, const gd::String &parameterType)
Return true if the type is a value of the given primitive type from the function events point of view...
Definition: ValueTypeMetadata.h:247
Defines a variable which can be used by an object, a layout or a project.
Definition: Variable.h:29
Type
Definition: Variable.h:32
Variable & GetChild(const gd::String &name)
Return the child with the specified name.
Definition: Variable.cpp:148
Type GetType() const
Get the type of the variable.
Definition: Variable.h:64
std::vector< gd::String > GetAllChildrenNames() const
Get the names of all children.
Definition: Variable.cpp:358
Class defining a container for gd::Variable.
Definition: VariablesContainer.h:28
void ForEachVariableMatchingSearch(const gd::String &search, std::function< void(const gd::String &name, const gd::Variable &variable)> fn) const
Call the callback for each variable with a name matching the specified search.
Definition: VariablesContainer.cpp:170
bool IsAllowedInIdentifier(gd::String::value_type character)
Definition: GrammarTerminals.h:99
Definition: CommonTools.h:24
std::ostream & operator<<(std::ostream &os, ExpressionCompletionDescription const &value)
Turn an ExpressionCompletionDescription to a string.
Definition: ExpressionCompletionFinder.cpp:19
Describe completions to be shown to the user.
Definition: ExpressionCompletionFinder.h:40
const gd::String & GetCompletion() const
Return the completion that must be inserted.
Definition: ExpressionCompletionFinder.h:175
const gd::String & GetPrefix() const
Return the prefix that must be completed.
Definition: ExpressionCompletionFinder.h:165
bool operator==(const ExpressionCompletionDescription &other) const
Definition: ExpressionCompletionFinder.h:117
ExpressionCompletionDescription()
Definition: ExpressionCompletionFinder.h:323
bool IsExact() const
Check if the completion description is exact, i.e: it's not used to complete anything....
Definition: ExpressionCompletionFinder.h:225
const gd::String & GetObjectName() const
Return the object name, if completing an object expression or a behavior.
Definition: ExpressionCompletionFinder.h:187
ExpressionCompletionDescription & SetObjectConfiguration(const gd::ObjectConfiguration *objectConfiguration_)
Set the object configuration, in the case the completion is about an object.
Definition: ExpressionCompletionFinder.h:281
ExpressionCompletionDescription & SetParameterMetadata(const gd::ParameterMetadata &parameterMetadata_)
Set the parameter metadata, in the case the completion is about a parameter of a function call.
Definition: ExpressionCompletionFinder.h:256
gd::VariablesContainer::SourceType GetVariableScope() const
Return the scope of the variable, for a variable completion.
Definition: ExpressionCompletionFinder.h:152
static ExpressionCompletionDescription ForBehaviorWithPrefix(const gd::String &prefix_, size_t replacementStartPosition_, size_t replacementEndPosition_, const gd::String &objectName_)
Create a completion for a behavior with the given prefix, for the specified object.
Definition: ExpressionCompletionFinder.h:62
bool HasParameterMetadata() const
Check if the completion is about a parameter of a function call.
Definition: ExpressionCompletionFinder.h:265
ExpressionCompletionDescription & SetIsLastParameter(bool isLastParameter_)
Set if the expression is the last child of a function call.
Definition: ExpressionCompletionFinder.h:242
const gd::ObjectConfiguration & GetObjectConfiguration() const
Return the parameter metadata, if the completion is about a object. Returns an empty configuration ot...
Definition: ExpressionCompletionFinder.h:299
CompletionKind
Definition: ExpressionCompletionFinder.h:48
size_t GetReplacementStartPosition() const
Return the first character index of the autocompleted part.
Definition: ExpressionCompletionFinder.h:230
const gd::String & GetBehaviorName() const
Return the behavior name, if completing an object behavior expression.
Definition: ExpressionCompletionFinder.h:202
gd::Variable::Type GetVariableType() const
Return the type of the variable, for a variable completion.
Definition: ExpressionCompletionFinder.h:141
size_t GetReplacementEndPosition() const
Return the first character index after the autocompleted part.
Definition: ExpressionCompletionFinder.h:237
static ExpressionCompletionDescription ForTextWithPrefix(const gd::String &type_, const gd::ParameterMetadata &parameterMetadata_, const gd::String &prefix_, size_t replacementStartPosition_, size_t replacementEndPosition_, const bool isLastParameter_, const gd::String &objectName_="")
Create a completion for a text with the given prefix.
Definition: ExpressionCompletionFinder.h:77
CompletionKind GetCompletionKind() const
Return the kind of the completion.
Definition: ExpressionCompletionFinder.h:125
const gd::String & GetType() const
Return the type of the completion (same type as types supported in expressions). For properties,...
Definition: ExpressionCompletionFinder.h:131
ExpressionCompletionDescription & SetIsExact(bool isExact_)
Set if the completion description is exact, i.e: it's not used to complete anything....
Definition: ExpressionCompletionFinder.h:215
bool HasObjectConfiguration() const
Check if the completion is about an object.
Definition: ExpressionCompletionFinder.h:291
static ExpressionCompletionDescription ForExpressionWithPrefix(const gd::String &type_, const gd::String &prefix_, size_t replacementStartPosition_, size_t replacementEndPosition_, const gd::String &objectName_="", const gd::String &behaviorName_="")
Create a completion for an expression (free, object or behavior expression) with the given prefix.
Definition: ExpressionCompletionFinder.h:99
const gd::ParameterMetadata & GetParameterMetadata() const
Return the parameter metadata, if the completion is about a parameter of a function call....
Definition: ExpressionCompletionFinder.h:273
bool IsLastParameter() const
Check if the expression is the last child of a function call.
Definition: ExpressionCompletionFinder.h:250
The base node, from which all nodes in the tree of an expression inherits from.
Definition: ExpressionParser2Node.h:100