1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 04:37:34 +00:00

Playground: Enable automatic autocomplete in the editor

This makes it a bit more useful, as the user doesn't have to explicitly
ask for completion, it just provides completions, and tries really hard
to avoid suggesting things where they're not expected, for instance:
(cursor positions denoted as pipes)
```
@G | {|
    foo: bar |
    foo |
}
```

The user does not expect any suggestions in any of those cursor positions,
so provide no suggestions for such cases. This prevents the automatic autocomplete
getting in the way of the user, esp. when they try to press return fully
expecting to go to a new line.
This commit is contained in:
AnotherTest 2021-01-01 17:28:22 +03:30 committed by Andreas Kling
parent a4a238ddc8
commit 7785d9715d

View file

@ -64,11 +64,13 @@ private:
Vector<String> class_names; Vector<String> class_names;
Vector<State> previous_states; Vector<State> previous_states;
bool should_push_state { true }; bool should_push_state { true };
GUI::GMLToken* last_seen_token { nullptr };
for (auto& token : all_tokens) { for (auto& token : all_tokens) {
if (token.m_start.line > cursor.line() || (token.m_start.line == cursor.line() && token.m_start.column > cursor.column())) if (token.m_start.line > cursor.line() || (token.m_start.line == cursor.line() && token.m_start.column > cursor.column()))
break; break;
last_seen_token = &token;
switch (state) { switch (state) {
case Free: case Free:
if (token.m_type == GUI::GMLToken::Type::ClassName) { if (token.m_type == GUI::GMLToken::Type::ClassName) {
@ -102,9 +104,12 @@ private:
} }
break; break;
case InIdentifier: case InIdentifier:
state = AfterIdentifier; if (token.m_type == GUI::GMLToken::Type::Colon)
state = AfterIdentifier;
break; break;
case AfterIdentifier: case AfterIdentifier:
if (token.m_type == GUI::GMLToken::Type::RightCurly || token.m_type == GUI::GMLToken::Type::LeftCurly)
break;
if (token.m_type == GUI::GMLToken::Type::ClassMarker) { if (token.m_type == GUI::GMLToken::Type::ClassMarker) {
previous_states.append(AfterClassName); previous_states.append(AfterClassName);
state = Free; state = Free;
@ -119,6 +124,11 @@ private:
Vector<GUI::AutocompleteProvider::Entry> entries; Vector<GUI::AutocompleteProvider::Entry> entries;
switch (state) { switch (state) {
case Free: case Free:
if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
// After some token, but with extra space, not on a new line.
// Nothing to put here.
break;
}
GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) { GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
entries.empend(String::formatted("@{}", registration.class_name()), 0u); entries.empend(String::formatted("@{}", registration.class_name()), 0u);
}); });
@ -126,6 +136,11 @@ private:
case InClassName: case InClassName:
if (class_names.is_empty()) if (class_names.is_empty())
break; break;
if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
// After a class name, but haven't seen braces.
// TODO: Suggest braces?
break;
}
GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) { GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
if (registration.class_name().starts_with(class_names.last())) if (registration.class_name().starts_with(class_names.last()))
entries.empend(registration.class_name(), class_names.last().length()); entries.empend(registration.class_name(), class_names.last().length());
@ -134,6 +149,11 @@ private:
case InIdentifier: case InIdentifier:
if (class_names.is_empty()) if (class_names.is_empty())
break; break;
if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
// After an identifier, but with extra space
// TODO: Maybe suggest a colon?
break;
}
if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) { if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) {
auto instance = registration->construct(); auto instance = registration->construct();
for (auto& it : instance->properties()) { for (auto& it : instance->properties()) {
@ -143,6 +163,13 @@ private:
} }
break; break;
case AfterClassName: case AfterClassName:
if (last_seen_token && last_seen_token->m_end.line == cursor.line()) {
if (last_seen_token->m_type != GUI::GMLToken::Type::Identifier || last_seen_token->m_end.column + 1 != cursor.column()) {
// Inside braces, but on the same line as some other stuff (and not the continuation of one!)
// The user expects nothing here.
break;
}
}
if (!class_names.is_empty()) { if (!class_names.is_empty()) {
if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) { if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) {
auto instance = registration->construct(); auto instance = registration->construct();
@ -191,6 +218,7 @@ int main(int argc, char** argv)
editor.set_syntax_highlighter(make<GUI::GMLSyntaxHighlighter>()); editor.set_syntax_highlighter(make<GUI::GMLSyntaxHighlighter>());
editor.set_autocomplete_provider(make<GMLAutocompleteProvider>()); editor.set_autocomplete_provider(make<GMLAutocompleteProvider>());
editor.set_should_autocomplete_automatically(true);
editor.set_automatic_indentation_enabled(true); editor.set_automatic_indentation_enabled(true);
editor.set_text(R"~~~(@GUI::Widget { editor.set_text(R"~~~(@GUI::Widget {
layout: @GUI::VerticalBoxLayout { layout: @GUI::VerticalBoxLayout {