-
Notifications
You must be signed in to change notification settings - Fork 860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
String autocompletion in expressions for scene names, layer names and choices #2702
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool! Added some comments/nitpickings/small things to fix.
It uses the delimiters found by the parser to replace the existing text to avoid to have a pseudo parser on UI side.
Very nice! This pseudo parser of separators on the UI side has always been a pain :) Very happy to see this gone.
Passing the positions of the replacement is a better idea.
Two things important to know:
- In the IDE, the typing is using just
number
. There is a also a few places to update, see https://semaphoreci.com/4ian/gd/branches/pull-request-2702/builds/1 - There are also C++ tests that you can run by compiling the native tests:
mkdir build-tests
,cd build-tests
,cmake .. -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE
,make -j 4
and run the tests:Core/GDCore_tests
.- This is not well documented, sorry! I can help fixing these tests, so concentrate on the Flow typing errors for now :)
I've also seen the issue with globalObjectsContainer casted to gdProject, I think we can avoid this as this assumption may/will not always hold, I'll post a comment about this too.
Overall great improvement, looking forward to this being complete :)
size_t GetReplacementEndPosition() const { return replacementEndPosition; } | ||
|
||
/** | ||
* \brief Set if the expession is the last child of a function call. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(here and below) expression
} | ||
void OnVisitNumberNode(NumberNode& node) override { | ||
// No completions | ||
} | ||
void OnVisitTextNode(TextNode& node) override { | ||
// No completions | ||
FunctionCallNode* functionCall = dynamic_cast<FunctionCallNode*>(parentNodeAtLocation); | ||
if (functionCall != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: prefer nullptr
if (parameterIndex < 0) { | ||
return; | ||
} | ||
// Search the metadata parameter index skiping invisible ones |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: skipping + period at the end of the sentence.
} | ||
metadataParameterIndex++; | ||
} | ||
const gd::String &type = functionCall->expressionMetadata.parameters[metadataParameterIndex].GetType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this crash if there is for example no parameter in the expression metadata but you still tried to write a parameter in the function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it only worked because no completion is asked by the GUI when there is a parsing error.
} | ||
metadataParameterIndex++; | ||
} | ||
const gd::String &type = functionCall->expressionMetadata.parameters[metadataParameterIndex].GetType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I like the idea of looking at the expression metadata 👍👍
|
||
const lowercaseSearchText = searchText.toLowerCase(); | ||
|
||
return list.filter((variableName: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why variableName
?
) { | ||
wordEndPosition++; | ||
} | ||
let wordStartPosition: integer = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use const
+ number
.
@@ -345,47 +430,17 @@ export const insertAutocompletionInExpression = ( | |||
}; | |||
} | |||
|
|||
// Start from the character just before the caret. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much much better without this 👍 So happy to see this gone.
gd::ExpressionNode* nodeAtLocation = finder.GetNode(); | ||
gd::ExpressionNode* parentNodeAtLocation = finder.GetParentNode(); | ||
|
||
std::cout << "GetCompletionDescriptionsFor Test" << std::endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this if not useful anymore
@@ -6,6 +6,8 @@ | |||
#ifndef GDCORE_EXPRESSIONAUTOCOMPLETIONPROVIDER_H | |||
#define GDCORE_EXPRESSIONAUTOCOMPLETIONPROVIDER_H | |||
|
|||
#include <fstream> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove these headers if you remove the std::cout <<
?
autocompletionTexts.push(`"${layout.getLayerAt(index).getName()}"`); | ||
} | ||
} else if (type === 'sceneName') { | ||
const project = (globalObjectsContainer: gdProject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to pass the project
in the expressionAutocompletionContext
when calling getAutocompletionsFromDescriptions
:)
|
||
let autocompletionTexts: string[] = []; | ||
if (type === 'layer') { | ||
const layout = (objectsContainer: gdLayout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as the project: we should be able to pass the layout
when calling getAutocompletionsFromDescriptions
and have it passed down thanks to the context. You can find it into the scope
(of type EventsScope
) in GenericExpressionField
:)
But we have to be even more cautious! It's possible that we're in a context where we don't have a layout (so the type will be a ?gdLayout
), for example when editing a function.
There is still one flow issue. I don't know how to make gdExpressionCompletionDescription has the new enum element |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how to make gdExpressionCompletionDescription has the new enum element Text. I bound it but I must have missed something.
Yes this is manually typed actually! See generate-types.js
in GDevelop.js/scripts.
@@ -80,13 +80,15 @@ struct ExpressionCompletionDescription { | |||
*/ | |||
static ExpressionCompletionDescription ForText( | |||
const gd::String& type_, | |||
const gd::ParameterMetadata *parameterMetadata_, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uber nitpicking: using a pointer to store an "optional reference to something, that you're not owning" is fine, but prefer to keep using references in the interface (i.e: in ForText and in the SetParameterMetadata/GetParameterMetadata).
In other words, you can pass a const gd::ParameterMetadata & parameterMetadata_
(and store its pointer parameterMetadata = ¶meterMetadata_;
).
With a pointer, there is a possible misunderstanding about if the class is taking ownership of the pointer or not.
(You can create a "HasParameterMetadata" if you need to know if this was set or not).
It creates a lot of file when executing |
708e1d8
to
c7711ea
Compare
You should not need to clone anything, but I forgot in my instruction to tell CMake (the build generator system) that the sources root are in the parent directory. This should work better with the The Give it another try and let me now if the cmake works better now (with the |
The cmake shows no errors, the makefile is not created (I'm on windows, should I try with WSL?).
Edit: I tried with WSL Ubuntu 20.04. It produces the Makefile but Make crashed at 7% because of this I guess:
From a fresh install, I installed these libs:
|
The tests seem to be ok now. |
Right, seems like the unused by outdated SFML dependency is causing us issue on Linux in general. We should remove it.. |
if (nodeAtLocation == nullptr) { | ||
std::vector<ExpressionCompletionDescription> emptyCompletions; | ||
return emptyCompletions; | ||
} | ||
|
||
gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition); | ||
gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition, *parentNodeAtLocation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is dereferencing a pointer that can (and is) null. I'm fixing this, as this can generate horrible crashs if used.
/** | ||
* \brief Return the parameter metadata if the completion is about a parameter. | ||
*/ | ||
const gd::ParameterMetadata& GetParameterMetadata() const { return *parameterMetadata; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally a good idea to be safe to use the "null object pattern".
We keep a pointer internally (so that we can modify what is pointed easily) but instead of setting it to nullptr
when it's empty, we point it to an empty object (here, a ParameterMetadata constructed without anything and stored as a static member of the class).
This is almost equally performant (to check if the parameter metadata is defined, you just check if the pointer is not pointing to this empty object, called badParameterMetadata
).
And this is a bit safer, in the sense that if someone tries to read the parameter metadata in a context where it's not valid, it's not crashing.
Because these classes are used in the IDE, it's fine being a bit more "forgiving" in case of misusage.
(An better alternative, safe and always correct would be to use an Optional
like type, so that you would be forced to check if the parameter metadata is defined before using it. But we use this "null object pattern" a lot and it works well enough!)
EDIT: I fixed this myself :)
…ests for the parent node finder and completion finder
Merged! Thanks a lot for working on this, that's a great improvement over what we had 👍 |
This add autocompletion for strings in expressions.
It uses the delimiters found by the parser to replace the existing text to avoid to have a pseudo parser on UI side.