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