diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index 275fe3cdf5..2e1a0fa188 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -25,6 +25,7 @@ */ #include "Editor.h" +#include "CppAutoComplete.h" #include "EditorWrapper.h" #include #include @@ -53,6 +54,8 @@ 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(make_weak_ptr()); } Editor::~Editor() @@ -307,9 +310,49 @@ void Editor::mousedown_event(GUI::MouseEvent& 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.key() == Key_Control) m_holding_ctrl = true; + + if (m_holding_ctrl && event.key() == Key_Space) { + autocomplete_action(); + } GUI::TextEditor::keydown_event(event); + + if (m_autocomplete_in_focus) { + autocomplete_action(); + } } void Editor::keyup_event(GUI::KeyEvent& event) @@ -421,4 +464,55 @@ void Editor::set_document(GUI::TextDocument& doc) GUI::TextEditor::set_document(doc); } +Optional Editor::get_autocomplete_request_data() +{ + auto highlighter = wrapper().editor().syntax_highlighter(); + if (!highlighter) + return {}; + auto& spans = document().spans(); + for (size_t span_index = 2; span_index < spans.size(); ++span_index) { + auto& span = spans[span_index]; + if (!span.range.contains(cursor())) { + continue; + } + + if (highlighter->is_identifier(spans[span_index - 1].data)) { + auto completion_span = spans[span_index - 1]; + + auto adjusted_range = completion_span.range; + auto end_line_length = document().line(completion_span.range.end().line()).length(); + adjusted_range.end().set_column(min(end_line_length, adjusted_range.end().column() + 1)); + auto text_in_span = document().text_in_range(adjusted_range); + + return AutoCompleteRequestData { completion_span.range.end(), text_in_span }; + } + } + return {}; +} + +void Editor::update_autocomplete(const AutoCompleteRequestData& data) +{ + // TODO: Move this part to a language server component :) + auto suggestions = CppAutoComplete::get_suggestions(text(), data.position); + if (suggestions.is_empty()) { + close_autocomplete(); + return; + } + + m_autocomplete_box->update_suggestions(data.partial_input, move(suggestions)); + m_autocomplete_in_focus = true; +} + +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; +} + } diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h index e5f1cf335d..e034772c8d 100644 --- a/DevTools/HackStudio/Editor.h +++ b/DevTools/HackStudio/Editor.h @@ -26,9 +26,11 @@ #pragma once +#include "AutoCompleteBox.h" #include "CodeDocument.h" #include "Debugger/BreakpointCallback.h" #include +#include #include #include @@ -78,15 +80,28 @@ private: static const Gfx::Bitmap& breakpoint_icon_bitmap(); static const Gfx::Bitmap& current_position_icon_bitmap(); + struct AutoCompleteRequestData { + GUI::TextPosition position; + String partial_input; + }; + + Optional get_autocomplete_request_data(); + + void update_autocomplete(const AutoCompleteRequestData&); + void show_autocomplete(const AutoCompleteRequestData&); + void close_autocomplete(); + 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 }; bool m_hovering_editor { false }; bool m_hovering_link { false }; bool m_holding_ctrl { false }; + bool m_autocomplete_in_focus { false }; }; } diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h index 0bdb68258a..175361487d 100644 --- a/Libraries/LibGUI/TextEditor.h +++ b/Libraries/LibGUI/TextEditor.h @@ -186,6 +186,8 @@ protected: TextPosition text_position_at(const Gfx::IntPoint&) const; bool ruler_visible() const { return m_ruler_visible; } + Gfx::IntRect content_rect_for_position(const TextPosition&) const; + int ruler_width() const; private: friend class TextDocumentLine; @@ -231,7 +233,6 @@ private: Gfx::IntRect line_content_rect(size_t item_index) const; Gfx::IntRect line_widget_rect(size_t line_index) const; Gfx::IntRect cursor_content_rect() const; - Gfx::IntRect content_rect_for_position(const TextPosition&) const; void update_cursor(); const NonnullOwnPtrVector& lines() const { return document().lines(); } NonnullOwnPtrVector& lines() { return document().lines(); } @@ -239,7 +240,6 @@ private: const TextDocumentLine& line(size_t index) const { return document().line(index); } TextDocumentLine& current_line() { return line(m_cursor.line()); } const TextDocumentLine& current_line() const { return line(m_cursor.line()); } - int ruler_width() const; void toggle_selection_if_needed_for_event(const KeyEvent&); void delete_selection(); void did_update_selection();