From 20b74e4ede98f8c98e43dd3e765272338c183417 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Wed, 30 Dec 2020 13:55:06 +0330 Subject: [PATCH] LibGUI+HackStudio: Add an opt-in autocompletion interface to TextEditor ...and use that to implement autocomplete in HackStudio. Now everyone can have autocomplete :^) --- .../icons/16x16/completion/cpp-identifier.png | Bin 0 -> 337 bytes .../completion/unspecified-identifier.png | Bin 0 -> 6132 bytes .../completion/unspecified-unspecified.png | Bin 0 -> 5735 bytes DevTools/HackStudio/AutoCompleteResponse.h | 27 ++---- DevTools/HackStudio/CMakeLists.txt | 1 - DevTools/HackStudio/Editor.cpp | 87 +++--------------- DevTools/HackStudio/Editor.h | 20 ++-- DevTools/HackStudio/LanguageClient.cpp | 2 +- DevTools/HackStudio/LanguageClient.h | 4 +- .../LanguageServers/Cpp/AutoComplete.cpp | 10 +- .../LanguageServers/Cpp/AutoComplete.h | 5 +- .../LanguageServers/LanguageClient.ipc | 2 +- .../LanguageServers/Shell/AutoComplete.cpp | 4 +- .../LanguageServers/Shell/AutoComplete.h | 4 +- .../LibGUI/AutocompleteProvider.cpp | 74 +++++++++------ .../LibGUI/AutocompleteProvider.h | 62 ++++++++++--- Libraries/LibGUI/CMakeLists.txt | 3 +- Libraries/LibGUI/TextEditor.cpp | 62 +++++++++++++ Libraries/LibGUI/TextEditor.h | 6 ++ 19 files changed, 211 insertions(+), 162 deletions(-) create mode 100644 Base/res/icons/16x16/completion/cpp-identifier.png create mode 100644 Base/res/icons/16x16/completion/unspecified-identifier.png create mode 100644 Base/res/icons/16x16/completion/unspecified-unspecified.png rename DevTools/HackStudio/AutoCompleteBox.cpp => Libraries/LibGUI/AutocompleteProvider.cpp (70%) rename DevTools/HackStudio/AutoCompleteBox.h => Libraries/LibGUI/AutocompleteProvider.h (57%) diff --git a/Base/res/icons/16x16/completion/cpp-identifier.png b/Base/res/icons/16x16/completion/cpp-identifier.png new file mode 100644 index 0000000000000000000000000000000000000000..9805fb180e748980c9c9f9f9c608cceeb7681f69 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7B|KdmLo7}&4Ytl>G8Cxo zD`K2AMRVstC9x?=9sz-G%$9WQX!^~v#N^sGkK*<((E%58RJeP#WEL}eaX(SEa23h6 zzt=h;{A|y${Iq*LjrC2UvJ*L8dq?flyXl&vB)E-hBFAkP9tQJ$3WALtbL^{ROv}n& za~GZSuDih>+kUlhj`2KMZ_#Ck%L~pOl*qo?Cn_(k8|yaDm7(F}WllHmCy^{$!z-do z6So|B?taEgN`*Kemw z=o8%qK^6p6lAXG?f5FxePiWZ r)`V#Olh>aeoxk$bzxML$kEKiIELc>z>z^hA0|SGntDnm{r-UW|0tt)t literal 0 HcmV?d00001 diff --git a/Base/res/icons/16x16/completion/unspecified-identifier.png b/Base/res/icons/16x16/completion/unspecified-identifier.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc73d22e81b0d9dd74e70d70fe9b45e0eecc657 GIT binary patch literal 6132 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 literal 0 HcmV?d00001 diff --git a/Base/res/icons/16x16/completion/unspecified-unspecified.png b/Base/res/icons/16x16/completion/unspecified-unspecified.png new file mode 100644 index 0000000000000000000000000000000000000000..74345c444af6d3b772f7f816c70b2accf9563381 GIT binary patch literal 5735 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 My85}Sb4q9e09_r5-2eap literal 0 HcmV?d00001 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 #include +#include #include #include -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(kind); + response.kind = static_cast(kind); + response.language = static_cast(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(); - - m_autocomplete_box = make(*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(*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::get_autocomplete_request_data( return Editor::AutoCompleteRequestData { cursor() }; } -void Editor::update_autocomplete(const AutoCompleteRequestData& data) +void Editor::LanguageServerAidedAutocompleteProvider::provide_completions(Function)> callback) { - if (!m_language_client) - return; + auto& editor = static_cast(*m_editor).wrapper().editor(); + auto data = editor.get_autocomplete_request_data(); + if (!data.has_value()) + callback({}); - m_language_client->on_autocomplete_suggestions = [=, this](auto suggestions) { - if (suggestions.is_empty()) { - close_autocomplete(); - return; - } - - 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 get_autocomplete_request_data(); + class LanguageServerAidedAutocompleteProvider final : virtual public GUI::AutocompleteProvider { + public: + LanguageServerAidedAutocompleteProvider(LanguageClient& client) + : m_language_client(client) + { + } + virtual ~LanguageServerAidedAutocompleteProvider() override { } - void update_autocomplete(const AutoCompleteRequestData&); - void show_autocomplete(const AutoCompleteRequestData&); - void close_autocomplete(); + private: + virtual void provide_completions(Function)> callback) override; + LanguageClient& m_language_client; + }; + + Optional get_autocomplete_request_data(); void flush_file_content_to_langauge_server(); explicit Editor(); RefPtr m_documentation_tooltip_window; - OwnPtr m_autocomplete_box; RefPtr 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& suggestions) +void LanguageClient::provide_autocomplete_suggestions(const Vector& 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&); + void provide_autocomplete_suggestions(const Vector&); - Function)> on_autocomplete_suggestions; + Function)> 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 AutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position) +Vector 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 AutoComplete::token_in_position(const Vector& token return {}; } -Vector AutoComplete::identifier_prefixes(const Vector& lines, const Vector& tokens, size_t target_token_index) +Vector AutoComplete::identifier_prefixes(const Vector& lines, const Vector& tokens, size_t target_token_index) { auto partial_input = text_of_token(lines, tokens[target_token_index]); - Vector suggestions; + Vector suggestions; HashTable suggestions_lookup; // To avoid duplicate results @@ -88,7 +88,7 @@ Vector AutoComplete::identifier_prefixes(const Vector get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position); + static Vector get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position); private: static Optional token_in_position(const Vector&, const GUI::TextPosition&); static StringView text_of_token(const Vector& lines, const Cpp::Token&); - static Vector identifier_prefixes(const Vector& lines, const Vector&, size_t target_token_index); + static Vector identifier_prefixes(const Vector& lines, const Vector&, 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 suggestions) =| + AutoCompleteSuggestions(Vector 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 AutoComplete::get_suggestions(const String& code, size_t offset) +Vector 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 AutoComplete::get_suggestions(const String& code, s #endif auto result = ast->complete_for_editor(m_shell, offset); - Vector completions; + Vector 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 get_suggestions(const String& code, size_t autocomplete_position); + Vector 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 similarity index 70% rename from DevTools/HackStudio/AutoCompleteBox.cpp rename to 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. + * 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 #include #include +#include #include #include -namespace HackStudio { +static RefPtr s_cpp_identifier_icon; +static RefPtr s_unspecified_identifier_icon; -static RefPtr s_cplusplus_icon; +namespace GUI { -class AutoCompleteSuggestionModel final : public GUI::Model { +class AutocompleteSuggestionModel final : public GUI::Model { public: - explicit AutoCompleteSuggestionModel(Vector&& suggestions) + explicit AutocompleteSuggestionModel(Vector&& 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 m_suggestions; + Vector m_suggestions; }; -AutoCompleteBox::~AutoCompleteBox() { } +AutocompleteBox::~AutocompleteBox() { } -AutoCompleteBox::AutoCompleteBox(WeakPtr 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) m_suggestion_view->set_column_headers_visible(false); } -void AutoCompleteBox::update_suggestions(Vector&& suggestions) +void AutocompleteBox::update_suggestions(Vector&& 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&& 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 similarity index 57% rename from DevTools/HackStudio/AutoCompleteBox.h rename to 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. + * 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 -#include +#include +#include +#include -namespace HackStudio { +namespace GUI { -class Editor; +class AutocompleteProvider { + AK_MAKE_NONCOPYABLE(AutocompleteProvider); + AK_MAKE_NONMOVABLE(AutocompleteProvider); -class AutoCompleteBox final { public: - explicit AutoCompleteBox(WeakPtr); - ~AutoCompleteBox(); + virtual ~AutocompleteProvider() { } - void update_suggestions(Vector&& 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)>) = 0; + + void attach(TextEditor& editor) + { + ASSERT(!m_editor); + m_editor = editor; + } + void detach() { m_editor.clear(); } + +protected: + AutocompleteProvider() { } + + WeakPtr m_editor; +}; + +class AutocompleteBox final { +public: + explicit AutocompleteBox(TextEditor&); + ~AutocompleteBox(); + + void update_suggestions(Vector&& 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 m_editor; + WeakPtr m_editor; RefPtr m_popup_window; RefPtr 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 +#include #include #include #include #include +#include #include #include #include @@ -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 highlighter) document().set_spans({}); } +const AutocompleteProvider* TextEditor::autocomplete_provider() const +{ + return m_autocomplete_provider.ptr(); +} + +void TextEditor::set_autocomplete_provider(OwnPtr&& 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(*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 #include #include +#include #include #include #include @@ -159,6 +160,9 @@ public: const SyntaxHighlighter* syntax_highlighter() const; void set_syntax_highlighter(OwnPtr); + const AutocompleteProvider* autocomplete_provider() const; + void set_autocomplete_provider(OwnPtr&&); + bool is_in_drag_select() const { return m_in_drag_select; } protected: @@ -317,6 +321,8 @@ private: NonnullOwnPtrVector m_line_visual_data; OwnPtr m_highlighter; + OwnPtr m_autocomplete_provider; + OwnPtr m_autocomplete_box; RefPtr m_automatic_selection_scroll_timer; Gfx::IntPoint m_last_mousemove_position;