diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-12-30 13:55:06 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-12-30 12:53:39 +0100 |
commit | 20b74e4ede98f8c98e43dd3e765272338c183417 (patch) | |
tree | 76cf13673d440c651284799551528f0e478e5252 | |
parent | 7e457b98c32f58e7f9529878a4e76832aaa80368 (diff) | |
download | serenity-20b74e4ede98f8c98e43dd3e765272338c183417.zip |
LibGUI+HackStudio: Add an opt-in autocompletion interface to TextEditor
...and use that to implement autocomplete in HackStudio.
Now everyone can have autocomplete :^)
19 files changed, 211 insertions, 162 deletions
diff --git a/Base/res/icons/16x16/completion/cpp-identifier.png b/Base/res/icons/16x16/completion/cpp-identifier.png Binary files differnew file mode 100644 index 0000000000..9805fb180e --- /dev/null +++ b/Base/res/icons/16x16/completion/cpp-identifier.png diff --git a/Base/res/icons/16x16/completion/unspecified-identifier.png b/Base/res/icons/16x16/completion/unspecified-identifier.png Binary files differnew file mode 100644 index 0000000000..9fc73d22e8 --- /dev/null +++ b/Base/res/icons/16x16/completion/unspecified-identifier.png diff --git a/Base/res/icons/16x16/completion/unspecified-unspecified.png b/Base/res/icons/16x16/completion/unspecified-unspecified.png Binary files differnew file mode 100644 index 0000000000..74345c444a --- /dev/null +++ b/Base/res/icons/16x16/completion/unspecified-unspecified.png diff --git a/DevTools/HackStudio/AutoCompleteResponse.h b/DevTools/HackStudio/AutoCompleteResponse.h index e1fd298dec..70023bf7ec 100644 --- a/DevTools/HackStudio/AutoCompleteResponse.h +++ b/DevTools/HackStudio/AutoCompleteResponse.h @@ -28,45 +28,36 @@ #include <AK/String.h> #include <AK/Types.h> +#include <LibGUI/AutocompleteProvider.h> #include <LibIPC/Decoder.h> #include <LibIPC/Encoder.h> -namespace HackStudio { - -enum class CompletionKind { - Identifier, -}; - -struct AutoCompleteResponse { - String completion; - size_t partial_input_length { 0 }; - CompletionKind kind { CompletionKind::Identifier }; -}; - -} - namespace IPC { template<> -inline bool encode(IPC::Encoder& encoder, const HackStudio::AutoCompleteResponse& response) +inline bool encode(IPC::Encoder& encoder, const GUI::AutocompleteProvider::Entry& response) { encoder << response.completion; encoder << (u64)response.partial_input_length; encoder << (u32)response.kind; + encoder << (u32)response.language; return true; } template<> -inline bool decode(IPC::Decoder& decoder, HackStudio::AutoCompleteResponse& response) +inline bool decode(IPC::Decoder& decoder, GUI::AutocompleteProvider::Entry& response) { u32 kind = 0; + u32 language = 0; u64 partial_input_length = 0; bool ok = decoder.decode(response.completion) && decoder.decode(partial_input_length) - && decoder.decode(kind); + && decoder.decode(kind) + && decoder.decode(language); if (ok) { - response.kind = static_cast<HackStudio::CompletionKind>(kind); + response.kind = static_cast<GUI::AutocompleteProvider::CompletionKind>(kind); + response.language = static_cast<GUI::AutocompleteProvider::Language>(language); response.partial_input_length = partial_input_length; } diff --git a/DevTools/HackStudio/CMakeLists.txt b/DevTools/HackStudio/CMakeLists.txt index 6f78d59f91..59406cbcbc 100644 --- a/DevTools/HackStudio/CMakeLists.txt +++ b/DevTools/HackStudio/CMakeLists.txt @@ -2,7 +2,6 @@ add_subdirectory(LanguageServers) add_subdirectory(LanguageClients) set(SOURCES - AutoCompleteBox.cpp CodeDocument.cpp CursorTool.cpp Debugger/BacktraceModel.cpp diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index 79f59d403e..ecfbb17e3a 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -62,8 +62,6 @@ Editor::Editor() m_documentation_tooltip_window->set_rect(0, 0, 500, 400); m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip); m_documentation_page_view = m_documentation_tooltip_window->set_main_widget<Web::OutOfProcessWebView>(); - - m_autocomplete_box = make<AutoCompleteBox>(*this); } Editor::~Editor() @@ -315,50 +313,6 @@ void Editor::mousedown_event(GUI::MouseEvent& event) GUI::TextEditor::mousedown_event(event); } -void Editor::keydown_event(GUI::KeyEvent& event) -{ - if (m_autocomplete_in_focus) { - if (event.key() == Key_Escape) { - m_autocomplete_in_focus = false; - m_autocomplete_box->close(); - return; - } - if (event.key() == Key_Down) { - m_autocomplete_box->next_suggestion(); - return; - } - if (event.key() == Key_Up) { - m_autocomplete_box->previous_suggestion(); - return; - } - if (event.key() == Key_Return || event.key() == Key_Tab) { - m_autocomplete_box->apply_suggestion(); - close_autocomplete(); - return; - } - } - - auto autocomplete_action = [this]() { - auto data = get_autocomplete_request_data(); - if (data.has_value()) { - update_autocomplete(data.value()); - if (m_autocomplete_in_focus) - show_autocomplete(data.value()); - } else { - close_autocomplete(); - } - }; - - if (event.ctrl() && event.key() == Key_Space) { - autocomplete_action(); - } - GUI::TextEditor::keydown_event(event); - - if (m_autocomplete_in_focus) { - autocomplete_action(); - } -} - void Editor::enter_event(Core::Event& event) { m_hovering_editor = true; @@ -486,6 +440,7 @@ void Editor::set_document(GUI::TextDocument& doc) } if (m_language_client) { + set_autocomplete_provider(make<LanguageServerAidedAutocompleteProvider>(*m_language_client)); dbgln("Opening {}", code_document.file_path()); int fd = open(code_document.file_path().characters(), O_RDONLY | O_NOCTTY); if (fd < 0) { @@ -505,39 +460,21 @@ Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data( return Editor::AutoCompleteRequestData { cursor() }; } -void Editor::update_autocomplete(const AutoCompleteRequestData& data) +void Editor::LanguageServerAidedAutocompleteProvider::provide_completions(Function<void(Vector<Entry>)> callback) { - if (!m_language_client) - return; - - m_language_client->on_autocomplete_suggestions = [=, this](auto suggestions) { - if (suggestions.is_empty()) { - close_autocomplete(); - return; - } + auto& editor = static_cast<Editor&>(*m_editor).wrapper().editor(); + auto data = editor.get_autocomplete_request_data(); + if (!data.has_value()) + callback({}); - show_autocomplete(data); - - m_autocomplete_box->update_suggestions(move(suggestions)); - m_autocomplete_in_focus = true; + m_language_client.on_autocomplete_suggestions = [callback = move(callback)](auto suggestions) { + callback(suggestions); }; - m_language_client->request_autocomplete( - code_document().file_path(), - data.position.line(), - data.position.column()); -} - -void Editor::show_autocomplete(const AutoCompleteRequestData& data) -{ - auto suggestion_box_location = content_rect_for_position(data.position).bottom_right().translated(screen_relative_rect().top_left().translated(ruler_width(), 0).translated(10, 5)); - m_autocomplete_box->show(suggestion_box_location); -} - -void Editor::close_autocomplete() -{ - m_autocomplete_box->close(); - m_autocomplete_in_focus = false; + m_language_client.request_autocomplete( + editor.code_document().file_path(), + data.value().position.line(), + data.value().position.column()); } void Editor::on_edit_action(const GUI::Command& command) diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h index 3c5c78a667..10c83a59d0 100644 --- a/DevTools/HackStudio/Editor.h +++ b/DevTools/HackStudio/Editor.h @@ -26,7 +26,6 @@ #pragma once -#include "AutoCompleteBox.h" #include "CodeDocument.h" #include "Debugger/BreakpointCallback.h" #include "LanguageClient.h" @@ -72,7 +71,6 @@ private: virtual void paint_event(GUI::PaintEvent&) override; virtual void mousemove_event(GUI::MouseEvent&) override; virtual void mousedown_event(GUI::MouseEvent&) override; - virtual void keydown_event(GUI::KeyEvent&) override; virtual void enter_event(Core::Event&) override; virtual void leave_event(Core::Event&) override; @@ -87,18 +85,26 @@ private: GUI::TextPosition position; }; - Optional<AutoCompleteRequestData> get_autocomplete_request_data(); + class LanguageServerAidedAutocompleteProvider final : virtual public GUI::AutocompleteProvider { + public: + LanguageServerAidedAutocompleteProvider(LanguageClient& client) + : m_language_client(client) + { + } + virtual ~LanguageServerAidedAutocompleteProvider() override { } + + private: + virtual void provide_completions(Function<void(Vector<Entry>)> callback) override; + LanguageClient& m_language_client; + }; - void update_autocomplete(const AutoCompleteRequestData&); - void show_autocomplete(const AutoCompleteRequestData&); - void close_autocomplete(); + Optional<AutoCompleteRequestData> get_autocomplete_request_data(); void flush_file_content_to_langauge_server(); explicit Editor(); RefPtr<GUI::Window> m_documentation_tooltip_window; - OwnPtr<AutoCompleteBox> m_autocomplete_box; RefPtr<Web::OutOfProcessWebView> m_documentation_page_view; String m_last_parsed_token; GUI::TextPosition m_previous_text_position { 0, 0 }; diff --git a/DevTools/HackStudio/LanguageClient.cpp b/DevTools/HackStudio/LanguageClient.cpp index 462f5b9791..a10df6d4b6 100644 --- a/DevTools/HackStudio/LanguageClient.cpp +++ b/DevTools/HackStudio/LanguageClient.cpp @@ -61,7 +61,7 @@ void LanguageClient::request_autocomplete(const String& path, size_t cursor_line m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column)); } -void LanguageClient::provide_autocomplete_suggestions(const Vector<AutoCompleteResponse>& suggestions) +void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions) { if (on_autocomplete_suggestions) on_autocomplete_suggestions(suggestions); diff --git a/DevTools/HackStudio/LanguageClient.h b/DevTools/HackStudio/LanguageClient.h index bd3298d7a8..bf294a7ea6 100644 --- a/DevTools/HackStudio/LanguageClient.h +++ b/DevTools/HackStudio/LanguageClient.h @@ -104,9 +104,9 @@ public: virtual void remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column); virtual void request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column); - void provide_autocomplete_suggestions(const Vector<AutoCompleteResponse>&); + void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&); - Function<void(Vector<AutoCompleteResponse>)> on_autocomplete_suggestions; + Function<void(Vector<GUI::AutocompleteProvider::Entry>)> on_autocomplete_suggestions; private: ServerConnection& m_connection; diff --git a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp index 90d48756d6..a6fae1c425 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp +++ b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp @@ -32,10 +32,10 @@ namespace LanguageServers::Cpp { -Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position) +Vector<GUI::AutocompleteProvider::Entry> AutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position) { auto lines = code.split('\n', true); - Lexer lexer(code); + Cpp::Lexer lexer(code); auto tokens = lexer.lex(); auto index_of_target_token = token_in_position(tokens, autocomplete_position); @@ -75,10 +75,10 @@ Optional<size_t> AutoComplete::token_in_position(const Vector<Cpp::Token>& token return {}; } -Vector<AutoCompleteResponse> AutoComplete::identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>& tokens, size_t target_token_index) +Vector<GUI::AutocompleteProvider::Entry> AutoComplete::identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>& tokens, size_t target_token_index) { auto partial_input = text_of_token(lines, tokens[target_token_index]); - Vector<AutoCompleteResponse> suggestions; + Vector<GUI::AutocompleteProvider::Entry> suggestions; HashTable<String> suggestions_lookup; // To avoid duplicate results @@ -88,7 +88,7 @@ Vector<AutoCompleteResponse> AutoComplete::identifier_prefixes(const Vector<Stri continue; auto text = text_of_token(lines, token); if (text.starts_with(partial_input) && suggestions_lookup.set(text) == AK::HashSetResult::InsertedNewEntry) { - suggestions.append({ text, partial_input.length(), HackStudio::CompletionKind::Identifier }); + suggestions.append({ text, partial_input.length(), GUI::AutocompleteProvider::CompletionKind::Identifier }); } } return suggestions; diff --git a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h index 650afc36a2..b4096bd793 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h +++ b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h @@ -35,18 +35,17 @@ namespace LanguageServers::Cpp { using namespace ::Cpp; -using ::HackStudio::AutoCompleteResponse; class AutoComplete { public: AutoComplete() = delete; - static Vector<AutoCompleteResponse> get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position); + static Vector<GUI::AutocompleteProvider::Entry> get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position); private: static Optional<size_t> token_in_position(const Vector<Cpp::Token>&, const GUI::TextPosition&); static StringView text_of_token(const Vector<String>& lines, const Cpp::Token&); - static Vector<AutoCompleteResponse> identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>&, size_t target_token_index); + static Vector<GUI::AutocompleteProvider::Entry> identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>&, size_t target_token_index); }; } diff --git a/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/DevTools/HackStudio/LanguageServers/LanguageClient.ipc index 66a63d53fb..31b5d5b2c5 100644 --- a/DevTools/HackStudio/LanguageServers/LanguageClient.ipc +++ b/DevTools/HackStudio/LanguageServers/LanguageClient.ipc @@ -1,4 +1,4 @@ endpoint LanguageClient = 8002 { - AutoCompleteSuggestions(Vector<HackStudio::AutoCompleteResponse> suggestions) =| + AutoCompleteSuggestions(Vector<GUI::AutocompleteProvider::Entry> suggestions) =| } diff --git a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp index 25e5334aae..89df78365a 100644 --- a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp +++ b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp @@ -35,7 +35,7 @@ namespace LanguageServers::Shell { -Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, size_t offset) +Vector<GUI::AutocompleteProvider::Entry> AutoComplete::get_suggestions(const String& code, size_t offset) { // FIXME: No need to reparse this every time! auto ast = ::Shell::Parser { code }.parse(); @@ -49,7 +49,7 @@ Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, s #endif auto result = ast->complete_for_editor(m_shell, offset); - Vector<AutoCompleteResponse> completions; + Vector<GUI::AutocompleteProvider::Entry> completions; for (auto& entry : result) { #ifdef DEBUG_AUTOCOMPLETE dbgln("Suggestion: '{}' starting at {}", entry.text_string, entry.input_offset); diff --git a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h index 924864bcf4..fece37dcb7 100644 --- a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h +++ b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h @@ -34,8 +34,6 @@ namespace LanguageServers::Shell { -using namespace ::HackStudio; - class AutoComplete { public: AutoComplete() @@ -43,7 +41,7 @@ public: { } - Vector<AutoCompleteResponse> get_suggestions(const String& code, size_t autocomplete_position); + Vector<GUI::AutocompleteProvider::Entry> get_suggestions(const String& code, size_t autocomplete_position); private: NonnullRefPtr<::Shell::Shell> m_shell; diff --git a/DevTools/HackStudio/AutoCompleteBox.cpp b/Libraries/LibGUI/AutocompleteProvider.cpp index d5950c125c..9ca6b14372 100644 --- a/DevTools/HackStudio/AutoCompleteBox.cpp +++ b/Libraries/LibGUI/AutocompleteProvider.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com> + * Copyright (c) 2020, the SerenityOS developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,20 +24,21 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "AutoCompleteBox.h" -#include "Editor.h" +#include <LibGUI/AutocompleteProvider.h> #include <LibGUI/Model.h> #include <LibGUI/TableView.h> +#include <LibGUI/TextEditor.h> #include <LibGUI/Window.h> #include <LibGfx/Bitmap.h> -namespace HackStudio { +static RefPtr<Gfx::Bitmap> s_cpp_identifier_icon; +static RefPtr<Gfx::Bitmap> s_unspecified_identifier_icon; -static RefPtr<Gfx::Bitmap> s_cplusplus_icon; +namespace GUI { -class AutoCompleteSuggestionModel final : public GUI::Model { +class AutocompleteSuggestionModel final : public GUI::Model { public: - explicit AutoCompleteSuggestionModel(Vector<AutoCompleteResponse>&& suggestions) + explicit AutocompleteSuggestionModel(Vector<AutocompleteProvider::Entry>&& suggestions) : m_suggestions(move(suggestions)) { } @@ -64,11 +65,20 @@ public: return suggestion.completion; } if (index.column() == Column::Icon) { - // TODO: Have separate icons for fields, functions, methods etc - // FIXME: Probably should have different icons for the different kinds, rather than for "c++". - if (suggestion.kind == CompletionKind::Identifier) - return *s_cplusplus_icon; - return *s_cplusplus_icon; + // TODO + if (suggestion.language == GUI::AutocompleteProvider::Language::Cpp) { + if (!s_cpp_identifier_icon) { + s_cpp_identifier_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/completion/cpp-identifier.png"); + } + return *s_cpp_identifier_icon; + } + if (suggestion.language == GUI::AutocompleteProvider::Language::Unspecified) { + if (!s_unspecified_identifier_icon) { + s_unspecified_identifier_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/completion/unspecified-identifier.png"); + } + return *s_unspecified_identifier_icon; + } + return {}; } } @@ -83,18 +93,14 @@ public: virtual void update() override {}; private: - Vector<AutoCompleteResponse> m_suggestions; + Vector<AutocompleteProvider::Entry> m_suggestions; }; -AutoCompleteBox::~AutoCompleteBox() { } +AutocompleteBox::~AutocompleteBox() { } -AutoCompleteBox::AutoCompleteBox(WeakPtr<Editor> editor) - : m_editor(move(editor)) +AutocompleteBox::AutocompleteBox(TextEditor& editor) + : m_editor(editor) { - if (!s_cplusplus_icon) { - s_cplusplus_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-cplusplus.png"); - } - m_popup_window = GUI::Window::construct(); m_popup_window->set_window_type(GUI::WindowType::Tooltip); m_popup_window->set_rect(0, 0, 200, 100); @@ -103,13 +109,13 @@ AutoCompleteBox::AutoCompleteBox(WeakPtr<Editor> editor) m_suggestion_view->set_column_headers_visible(false); } -void AutoCompleteBox::update_suggestions(Vector<AutoCompleteResponse>&& suggestions) +void AutocompleteBox::update_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions) { if (suggestions.is_empty()) return; bool has_suggestions = !suggestions.is_empty(); - m_suggestion_view->set_model(adopt(*new AutoCompleteSuggestionModel(move(suggestions)))); + m_suggestion_view->set_model(adopt(*new AutocompleteSuggestionModel(move(suggestions)))); if (!has_suggestions) m_suggestion_view->selection().clear(); @@ -117,18 +123,23 @@ void AutoCompleteBox::update_suggestions(Vector<AutoCompleteResponse>&& suggesti m_suggestion_view->selection().set(m_suggestion_view->model()->index(0)); } -void AutoCompleteBox::show(Gfx::IntPoint suggstion_box_location) +bool AutocompleteBox::is_visible() const +{ + return m_popup_window->is_visible(); +} + +void AutocompleteBox::show(Gfx::IntPoint suggstion_box_location) { m_popup_window->move_to(suggstion_box_location); m_popup_window->show(); } -void AutoCompleteBox::close() +void AutocompleteBox::close() { m_popup_window->hide(); } -void AutoCompleteBox::next_suggestion() +void AutocompleteBox::next_suggestion() { GUI::ModelIndex new_index = m_suggestion_view->selection().first(); if (new_index.is_valid()) @@ -142,7 +153,7 @@ void AutoCompleteBox::next_suggestion() } } -void AutoCompleteBox::previous_suggestion() +void AutocompleteBox::previous_suggestion() { GUI::ModelIndex new_index = m_suggestion_view->selection().first(); if (new_index.is_valid()) @@ -156,22 +167,25 @@ void AutoCompleteBox::previous_suggestion() } } -void AutoCompleteBox::apply_suggestion() +void AutocompleteBox::apply_suggestion() { if (m_editor.is_null()) return; + if (!m_editor->is_editable()) + return; + auto selected_index = m_suggestion_view->selection().first(); if (!selected_index.is_valid()) return; - auto suggestion_index = m_suggestion_view->model()->index(selected_index.row(), AutoCompleteSuggestionModel::Column::Name); + auto suggestion_index = m_suggestion_view->model()->index(selected_index.row(), AutocompleteSuggestionModel::Column::Name); auto suggestion = suggestion_index.data().to_string(); - size_t partial_length = suggestion_index.data((GUI::ModelRole)AutoCompleteSuggestionModel::InternalRole::PartialInputLength).to_i64(); + size_t partial_length = suggestion_index.data((GUI::ModelRole)AutocompleteSuggestionModel::InternalRole::PartialInputLength).to_i64(); ASSERT(suggestion.length() >= partial_length); auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length); m_editor->insert_at_cursor_or_replace_selection(completion); } -}; +} diff --git a/DevTools/HackStudio/AutoCompleteBox.h b/Libraries/LibGUI/AutocompleteProvider.h index ef4bdcf4a5..6987f3b0ca 100644 --- a/DevTools/HackStudio/AutoCompleteBox.h +++ b/Libraries/LibGUI/AutocompleteProvider.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com> + * Copyright (c) 2020, the SerenityOS developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,20 +26,57 @@ #pragma once -#include "AutoCompleteResponse.h" -#include <AK/WeakPtr.h> -#include <LibGUI/Widget.h> +#include <LibGUI/Forward.h> +#include <LibGUI/TextEditor.h> +#include <LibGUI/Window.h> -namespace HackStudio { +namespace GUI { -class Editor; +class AutocompleteProvider { + AK_MAKE_NONCOPYABLE(AutocompleteProvider); + AK_MAKE_NONMOVABLE(AutocompleteProvider); -class AutoCompleteBox final { public: - explicit AutoCompleteBox(WeakPtr<Editor>); - ~AutoCompleteBox(); + virtual ~AutocompleteProvider() { } - void update_suggestions(Vector<AutoCompleteResponse>&& suggestions); + enum class CompletionKind { + Identifier, + }; + + enum class Language { + Unspecified, + Cpp, + }; + + struct Entry { + String completion; + size_t partial_input_length { 0 }; + CompletionKind kind { CompletionKind::Identifier }; + Language language { Language::Unspecified }; + }; + + virtual void provide_completions(Function<void(Vector<Entry>)>) = 0; + + void attach(TextEditor& editor) + { + ASSERT(!m_editor); + m_editor = editor; + } + void detach() { m_editor.clear(); } + +protected: + AutocompleteProvider() { } + + WeakPtr<TextEditor> m_editor; +}; + +class AutocompleteBox final { +public: + explicit AutocompleteBox(TextEditor&); + ~AutocompleteBox(); + + void update_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions); + bool is_visible() const; void show(Gfx::IntPoint suggstion_box_location); void close(); @@ -48,10 +85,9 @@ public: void apply_suggestion(); private: - void complete_suggestion(const GUI::ModelIndex&); - - WeakPtr<Editor> m_editor; + WeakPtr<TextEditor> m_editor; RefPtr<GUI::Window> m_popup_window; RefPtr<GUI::TableView> m_suggestion_view; }; + } diff --git a/Libraries/LibGUI/CMakeLists.txt b/Libraries/LibGUI/CMakeLists.txt index a4514e8f1e..3aefaaab94 100644 --- a/Libraries/LibGUI/CMakeLists.txt +++ b/Libraries/LibGUI/CMakeLists.txt @@ -6,9 +6,10 @@ set(SOURCES Action.cpp ActionGroup.cpp Application.cpp + AutocompleteProvider.cpp BoxLayout.cpp - Button.cpp BreadcrumbBar.cpp + Button.cpp Calendar.cpp CheckBox.cpp Clipboard.cpp diff --git a/Libraries/LibGUI/TextEditor.cpp b/Libraries/LibGUI/TextEditor.cpp index 133db5c567..ecbd0913c8 100644 --- a/Libraries/LibGUI/TextEditor.cpp +++ b/Libraries/LibGUI/TextEditor.cpp @@ -25,10 +25,12 @@ */ #include <AK/QuickSort.h> +#include <AK/ScopeGuard.h> #include <AK/StringBuilder.h> #include <AK/TemporaryChange.h> #include <LibCore/Timer.h> #include <LibGUI/Action.h> +#include <LibGUI/AutocompleteProvider.h> #include <LibGUI/Clipboard.h> #include <LibGUI/InputBox.h> #include <LibGUI/Menu.h> @@ -711,6 +713,27 @@ void TextEditor::sort_selected_lines() void TextEditor::keydown_event(KeyEvent& event) { + if (m_autocomplete_box && m_autocomplete_box->is_visible() && (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Tab)) { + m_autocomplete_box->apply_suggestion(); + m_autocomplete_box->close(); + return; + } + + if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Escape) { + m_autocomplete_box->close(); + return; + } + + if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Up) { + m_autocomplete_box->previous_suggestion(); + return; + } + + if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Down) { + m_autocomplete_box->next_suggestion(); + return; + } + if (is_single_line() && event.key() == KeyCode::Key_Tab) return ScrollableWidget::keydown_event(event); @@ -720,6 +743,14 @@ void TextEditor::keydown_event(KeyEvent& event) return; } + ArmedScopeGuard update_autocomplete { [&] { + if (m_autocomplete_box && m_autocomplete_box->is_visible()) { + m_autocomplete_provider->provide_completions([&](auto completions) { + m_autocomplete_box->update_suggestions(move(completions)); + }); + } + } }; + if (event.key() == KeyCode::Key_Escape) { if (on_escape_pressed) on_escape_pressed(); @@ -997,6 +1028,18 @@ void TextEditor::keydown_event(KeyEvent& event) return; } + if (!event.shift() && !event.alt() && event.ctrl() && event.key() == KeyCode::Key_Space) { + if (m_autocomplete_provider) { + m_autocomplete_provider->provide_completions([&](auto completions) { + m_autocomplete_box->update_suggestions(move(completions)); + auto position = content_rect_for_position(cursor()).bottom_right().translated(screen_relative_rect().top_left().translated(ruler_width(), 0).translated(10, 5)); + m_autocomplete_box->show(position); + }); + update_autocomplete.disarm(); + return; + } + } + if (is_editable() && !event.ctrl() && !event.alt() && event.code_point() != 0) { StringBuilder sb; sb.append_code_point(event.code_point()); @@ -1759,6 +1802,25 @@ void TextEditor::set_syntax_highlighter(OwnPtr<SyntaxHighlighter> highlighter) document().set_spans({}); } +const AutocompleteProvider* TextEditor::autocomplete_provider() const +{ + return m_autocomplete_provider.ptr(); +} + +void TextEditor::set_autocomplete_provider(OwnPtr<AutocompleteProvider>&& provider) +{ + if (m_autocomplete_provider) + m_autocomplete_provider->detach(); + m_autocomplete_provider = move(provider); + if (m_autocomplete_provider) { + m_autocomplete_provider->attach(*this); + if (!m_autocomplete_box) + m_autocomplete_box = make<AutocompleteBox>(*this); + } + if (m_autocomplete_box) + m_autocomplete_box->close(); +} + int TextEditor::line_height() const { return font().glyph_height() + m_line_spacing; diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h index 6f55d39a44..f5b8b2284c 100644 --- a/Libraries/LibGUI/TextEditor.h +++ b/Libraries/LibGUI/TextEditor.h @@ -30,6 +30,7 @@ #include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullRefPtrVector.h> #include <LibCore/ElapsedTimer.h> +#include <LibGUI/Forward.h> #include <LibGUI/ScrollableWidget.h> #include <LibGUI/TextDocument.h> #include <LibGUI/TextRange.h> @@ -159,6 +160,9 @@ public: const SyntaxHighlighter* syntax_highlighter() const; void set_syntax_highlighter(OwnPtr<SyntaxHighlighter>); + const AutocompleteProvider* autocomplete_provider() const; + void set_autocomplete_provider(OwnPtr<AutocompleteProvider>&&); + bool is_in_drag_select() const { return m_in_drag_select; } protected: @@ -317,6 +321,8 @@ private: NonnullOwnPtrVector<LineVisualData> m_line_visual_data; OwnPtr<SyntaxHighlighter> m_highlighter; + OwnPtr<AutocompleteProvider> m_autocomplete_provider; + OwnPtr<AutocompleteBox> m_autocomplete_box; RefPtr<Core::Timer> m_automatic_selection_scroll_timer; Gfx::IntPoint m_last_mousemove_position; |