diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2021-01-01 17:29:11 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-02 11:37:01 +0100 |
commit | a4a238ddc87d3e1a5e776a1d4b98891ee5871aee (patch) | |
tree | a57333f23d0210099355696d3af56224384d048b /Libraries | |
parent | 60f5f48dd14cad81ece4837f6517a2dfc4c32476 (diff) | |
download | serenity-a4a238ddc87d3e1a5e776a1d4b98891ee5871aee.zip |
LibGUI: Add an optional "automatic" autocomplete feature to TextEditor
This aims to be a "smart" autocomplete that tries to present the user
with useful suggestions without being in the way (too much).
Here is its current configuration:
- Show suggestions 800ms after something is inserted in the editor
- if something else is inserted in that period, reset it back to 800ms
to allow the user to type uninterrupted
- cancel any shown autocomplete (and the timer) on external changes
(paste, cut, etc)
Diffstat (limited to 'Libraries')
-rw-r--r-- | Libraries/LibGUI/AutocompleteProvider.cpp | 26 | ||||
-rw-r--r-- | Libraries/LibGUI/TextEditor.cpp | 51 | ||||
-rw-r--r-- | Libraries/LibGUI/TextEditor.h | 10 |
3 files changed, 75 insertions, 12 deletions
diff --git a/Libraries/LibGUI/AutocompleteProvider.cpp b/Libraries/LibGUI/AutocompleteProvider.cpp index 9ca6b14372..b2d6266ff2 100644 --- a/Libraries/LibGUI/AutocompleteProvider.cpp +++ b/Libraries/LibGUI/AutocompleteProvider.cpp @@ -92,6 +92,8 @@ public: } virtual void update() override {}; + void set_suggestions(Vector<AutocompleteProvider::Entry>&& suggestions) { m_suggestions = move(suggestions); } + private: Vector<AutocompleteProvider::Entry> m_suggestions; }; @@ -111,16 +113,21 @@ AutocompleteBox::AutocompleteBox(TextEditor& editor) 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)))); + if (m_suggestion_view->model()) { + auto& model = *static_cast<AutocompleteSuggestionModel*>(m_suggestion_view->model()); + model.set_suggestions(move(suggestions)); + } else { + m_suggestion_view->set_model(adopt(*new AutocompleteSuggestionModel(move(suggestions)))); + m_suggestion_view->update(); + if (has_suggestions) + m_suggestion_view->set_cursor(m_suggestion_view->model()->index(0), GUI::AbstractView::SelectionUpdate::Set); + } + m_suggestion_view->model()->update(); + m_suggestion_view->update(); if (!has_suggestions) - m_suggestion_view->selection().clear(); - else - m_suggestion_view->selection().set(m_suggestion_view->model()->index(0)); + close(); } bool AutocompleteBox::is_visible() const @@ -130,7 +137,12 @@ bool AutocompleteBox::is_visible() const void AutocompleteBox::show(Gfx::IntPoint suggstion_box_location) { + if (!m_suggestion_view->model() || m_suggestion_view->model()->row_count() == 0) + return; + m_popup_window->move_to(suggstion_box_location); + if (!is_visible()) + m_suggestion_view->move_cursor(GUI::AbstractView::CursorMovement::Home, GUI::AbstractTableView::SelectionUpdate::Set); m_popup_window->show(); } diff --git a/Libraries/LibGUI/TextEditor.cpp b/Libraries/LibGUI/TextEditor.cpp index 1a5feede4d..114800f800 100644 --- a/Libraries/LibGUI/TextEditor.cpp +++ b/Libraries/LibGUI/TextEditor.cpp @@ -713,6 +713,7 @@ void TextEditor::sort_selected_lines() void TextEditor::keydown_event(KeyEvent& event) { + TemporaryChange change { m_should_keep_autocomplete_box, true }; 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(); @@ -979,6 +980,8 @@ void TextEditor::keydown_event(KeyEvent& event) if (event.key() == KeyCode::Key_Backspace) { if (!is_editable()) return; + if (m_autocomplete_box) + m_autocomplete_box->close(); if (has_selection()) { delete_selection(); did_update_selection(); @@ -1017,6 +1020,8 @@ void TextEditor::keydown_event(KeyEvent& event) if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) { if (!is_editable()) return; + if (m_autocomplete_box) + m_autocomplete_box->close(); delete_current_line(); return; } @@ -1024,17 +1029,15 @@ void TextEditor::keydown_event(KeyEvent& event) if (event.key() == KeyCode::Key_Delete) { if (!is_editable()) return; + if (m_autocomplete_box) + m_autocomplete_box->close(); do_delete(); 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); - }); + try_show_autocomplete(); update_autocomplete.disarm(); return; } @@ -1044,6 +1047,8 @@ void TextEditor::keydown_event(KeyEvent& event) StringBuilder sb; sb.append_code_point(event.code_point()); + if (should_autocomplete_automatically()) + m_autocomplete_timer->start(); insert_at_cursor_or_replace_selection(sb.to_string()); return; } @@ -1428,6 +1433,19 @@ void TextEditor::undefer_reflow() } } +void TextEditor::try_show_autocomplete() +{ + if (m_autocomplete_provider) { + m_autocomplete_provider->provide_completions([&](auto completions) { + auto has_completions = !completions.is_empty(); + 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)); + if (has_completions) + m_autocomplete_box->show(position); + }); + } +} + void TextEditor::enter_event(Core::Event&) { m_automatic_selection_scroll_timer->stop(); @@ -1437,6 +1455,10 @@ void TextEditor::leave_event(Core::Event&) { if (m_in_drag_select) m_automatic_selection_scroll_timer->start(); + if (m_autocomplete_timer) + m_autocomplete_timer->stop(); + if (m_autocomplete_box) + m_autocomplete_box->close(); } void TextEditor::did_change() @@ -1445,6 +1467,10 @@ void TextEditor::did_change() recompute_all_visual_lines(); m_undo_action->set_enabled(can_undo()); m_redo_action->set_enabled(can_redo()); + if (m_autocomplete_box && !m_should_keep_autocomplete_box) { + m_autocomplete_timer->stop(); + m_autocomplete_box->close(); + } if (!m_has_pending_change_notification) { m_has_pending_change_notification = true; deferred_invoke([this](auto&) { @@ -1848,4 +1874,19 @@ void TextEditor::set_visualize_trailing_whitespace(bool enabled) update(); } +void TextEditor::set_should_autocomplete_automatically(bool value) +{ + if (value == should_autocomplete_automatically()) + return; + + if (value) { + ASSERT(m_autocomplete_provider); + m_autocomplete_timer = Core::Timer::create_single_shot(m_automatic_autocomplete_delay_ms, [this] { try_show_autocomplete(); }); + return; + } + + remove_child(*m_autocomplete_timer); + m_autocomplete_timer = nullptr; +} + } diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h index f5b8b2284c..7a0dcb0456 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 <LibCore/Timer.h> #include <LibGUI/Forward.h> #include <LibGUI/ScrollableWidget.h> #include <LibGUI/TextDocument.h> @@ -163,6 +164,9 @@ public: const AutocompleteProvider* autocomplete_provider() const; void set_autocomplete_provider(OwnPtr<AutocompleteProvider>&&); + bool should_autocomplete_automatically() const { return m_autocomplete_timer; } + void set_should_autocomplete_automatically(bool); + bool is_in_drag_select() const { return m_in_drag_select; } protected: @@ -212,6 +216,8 @@ private: void defer_reflow(); void undefer_reflow(); + void try_show_autocomplete(); + int icon_size() const { return 16; } int icon_padding() const { return 2; } @@ -323,8 +329,12 @@ private: OwnPtr<SyntaxHighlighter> m_highlighter; OwnPtr<AutocompleteProvider> m_autocomplete_provider; OwnPtr<AutocompleteBox> m_autocomplete_box; + bool m_should_keep_autocomplete_box { false }; + size_t m_automatic_autocomplete_delay_ms { 800 }; RefPtr<Core::Timer> m_automatic_selection_scroll_timer; + RefPtr<Core::Timer> m_autocomplete_timer; + Gfx::IntPoint m_last_mousemove_position; RefPtr<Gfx::Bitmap> m_icon; |