Skip to content
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

Merged
merged 17 commits into from
Jun 5, 2021

Conversation

D8H
Copy link
Collaborator

@D8H D8H commented Jun 3, 2021

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.

Copy link
Owner

@4ian 4ian left a 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.
Copy link
Owner

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) {
Copy link
Owner

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
Copy link
Owner

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();
Copy link
Owner

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?

Copy link
Collaborator Author

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();
Copy link
Owner

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) => {
Copy link
Owner

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 =
Copy link
Owner

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.
Copy link
Owner

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;
Copy link
Owner

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>
Copy link
Owner

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);
Copy link
Owner

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);
Copy link
Owner

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.

@D8H
Copy link
Collaborator Author

D8H commented Jun 3, 2021

There is still one flow issue. I don't know how to make gdExpressionCompletionDescription has the new enum element Text. I bound it but I must have missed something.

@D8H D8H changed the title String (Scene and Layer) autocompletion in expressions String autocompletion for scene names, layer names and choices in expressions Jun 3, 2021
@D8H D8H changed the title String autocompletion for scene names, layer names and choices in expressions String autocompletion in expressions for scene names, layer names and choices Jun 3, 2021
Copy link
Owner

@4ian 4ian left a 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_,
Copy link
Owner

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 = &parameterMetadata_;).
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).

@D8H
Copy link
Collaborator Author

D8H commented Jun 3, 2021

It creates a lot of file when executing cmake -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE from repository root.
Is this why you said to create this directory mkdir build-tests ?
Should I clone something in it?

@D8H D8H force-pushed the ValueExpressionAutocompletion branch from 708e1d8 to c7711ea Compare June 3, 2021 22:12
@4ian
Copy link
Owner

4ian commented Jun 3, 2021

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 ..:
mkdir build-tests, cd build-tests, cmake .. -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE, make -j 4 and run the tests: Core/GDCore_tests

The mkdir build-tests && cd build-tests is to avoid building directly in the sources directory. This is called an "out of source build". It means that all the generated/compiled binaries files will be in this folder (useful if you want to restart your build from scratch, just delete the folder. It's a bit like deleted the node_modules folder).

Give it another try and let me now if the cmake works better now (with the ..). In theory it should generate a bunch of files in build-tests, notably a few folders and a "Makefile". You can then run the makefile with make -j 4.
All of this is automated by GDevelop.js when targeting Webassembly, but we should probably offer a few shortcuts or documentation for the native tests build like this.

@D8H
Copy link
Collaborator Author

D8H commented Jun 4, 2021

The cmake shows no errors, the makefile is not created (I'm on windows, should I try with WSL?).

build-tests> make -j 4
make: *** No targets specified and no makefile found.  Stop.

Edit: I tried with WSL Ubuntu 20.04. It produces the Makefile but Make crashed at 7% because of this I guess:

In file included from /mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/Unix/GlxContext.hpp:32,
                 from /mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/GlContext.cpp:51:
/mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/Unix/GlxExtensions.hpp:123:3: error: conflicting declaration ‘typedef struct GLXHyperpipeNetworkSGIX GLXHyperpipeNetworkSGIX’
  123 | } GLXHyperpipeNetworkSGIX;
      |   ^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/GL/glx.h:333,
                 from /mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/Unix/GlxExtensions.hpp:36,
                 from /mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/Unix/GlxContext.hpp:32,
                 from /mnt/c/WorkspaceGDevelop/GDevelop/ExtLibs/SFML/src/SFML/Window/GlContext.cpp:51:
/usr/include/GL/glxext.h:771:3: note: previous declaration as ‘typedef struct GLXHyperpipeNetworkSGIX GLXHyperpipeNetworkSGIX’
  771 | } GLXHyperpipeNetworkSGIX;
      |   ^~~~~~~~~~~~~~~~~~~~~~~

From a fresh install, I installed these libs:

sudo apt install libx11-dev libxrandr-dev libgl1-mesa-dev libudev-dev libjpeg-dev libopenal-dev libflac-dev libvorbis-dev libfreetype-dev

@D8H D8H marked this pull request as ready for review June 4, 2021 19:54
@D8H
Copy link
Collaborator Author

D8H commented Jun 4, 2021

The tests seem to be ok now.
New tests are needed to cover the new cases but I would rather being able to launch them locally before writing new ones (unless it blocks this PR).
I used this project to test it manually:
UndefinedVariableTest.zip

@4ian
Copy link
Owner

4ian commented Jun 4, 2021

Edit: I tried with WSL Ubuntu 20.04. It produces the Makefile but Make crash at 7% because of this I guess:

Right, seems like the unused by outdated SFML dependency is causing us issue on Linux in general. We should remove it..
I don't have an immediate solution, so I suggest you can write a TODO with tests to add, I can maybe write a few and then we go ahead and merge this, as the rest looks very nice :)

@4ian 4ian added the 🏁PR almost ready: final fixes The PR is almost ready and needs final fixes and/or polishing before merge label Jun 4, 2021
if (nodeAtLocation == nullptr) {
std::vector<ExpressionCompletionDescription> emptyCompletions;
return emptyCompletions;
}

gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition);
gd::ExpressionCompletionFinder autocompletionProvider(searchedPosition, *parentNodeAtLocation);
Copy link
Owner

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; }
Copy link
Owner

@4ian 4ian Jun 5, 2021

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
@4ian 4ian merged commit 4f91545 into 4ian:master Jun 5, 2021
@4ian
Copy link
Owner

4ian commented Jun 5, 2021

Merged! Thanks a lot for working on this, that's a great improvement over what we had 👍

@D8H D8H deleted the ValueExpressionAutocompletion branch June 7, 2021 12:27
@D8H D8H mentioned this pull request Jul 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏁PR almost ready: final fixes The PR is almost ready and needs final fixes and/or polishing before merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants