mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:12:43 +00:00 
			
		
		
		
	LibGUI+HackStudio: Add an opt-in autocompletion interface to TextEditor
...and use that to implement autocomplete in HackStudio. Now everyone can have autocomplete :^)
This commit is contained in:
		
							parent
							
								
									7e457b98c3
								
							
						
					
					
						commit
						20b74e4ede
					
				
					 19 changed files with 211 additions and 162 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/cpp-identifier.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/cpp-identifier.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 337 B | 
							
								
								
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/unspecified-identifier.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/unspecified-identifier.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/unspecified-unspecified.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Base/res/icons/16x16/completion/unspecified-unspecified.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.6 KiB | 
|  | @ -28,45 +28,36 @@ | ||||||
| 
 | 
 | ||||||
| #include <AK/String.h> | #include <AK/String.h> | ||||||
| #include <AK/Types.h> | #include <AK/Types.h> | ||||||
|  | #include <LibGUI/AutocompleteProvider.h> | ||||||
| #include <LibIPC/Decoder.h> | #include <LibIPC/Decoder.h> | ||||||
| #include <LibIPC/Encoder.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 { | namespace IPC { | ||||||
| 
 | 
 | ||||||
| template<> | 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 << response.completion; | ||||||
|     encoder << (u64)response.partial_input_length; |     encoder << (u64)response.partial_input_length; | ||||||
|     encoder << (u32)response.kind; |     encoder << (u32)response.kind; | ||||||
|  |     encoder << (u32)response.language; | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<> | template<> | ||||||
| inline bool decode(IPC::Decoder& decoder, HackStudio::AutoCompleteResponse& response) | inline bool decode(IPC::Decoder& decoder, GUI::AutocompleteProvider::Entry& response) | ||||||
| { | { | ||||||
|     u32 kind = 0; |     u32 kind = 0; | ||||||
|  |     u32 language = 0; | ||||||
|     u64 partial_input_length = 0; |     u64 partial_input_length = 0; | ||||||
|     bool ok = decoder.decode(response.completion) |     bool ok = decoder.decode(response.completion) | ||||||
|         && decoder.decode(partial_input_length) |         && decoder.decode(partial_input_length) | ||||||
|         && decoder.decode(kind); |         && decoder.decode(kind) | ||||||
|  |         && decoder.decode(language); | ||||||
| 
 | 
 | ||||||
|     if (ok) { |     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; |         response.partial_input_length = partial_input_length; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ add_subdirectory(LanguageServers) | ||||||
| add_subdirectory(LanguageClients) | add_subdirectory(LanguageClients) | ||||||
| 
 | 
 | ||||||
| set(SOURCES | set(SOURCES | ||||||
|     AutoCompleteBox.cpp |  | ||||||
|     CodeDocument.cpp |     CodeDocument.cpp | ||||||
|     CursorTool.cpp |     CursorTool.cpp | ||||||
|     Debugger/BacktraceModel.cpp |     Debugger/BacktraceModel.cpp | ||||||
|  |  | ||||||
|  | @ -62,8 +62,6 @@ Editor::Editor() | ||||||
|     m_documentation_tooltip_window->set_rect(0, 0, 500, 400); |     m_documentation_tooltip_window->set_rect(0, 0, 500, 400); | ||||||
|     m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip); |     m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip); | ||||||
|     m_documentation_page_view = m_documentation_tooltip_window->set_main_widget<Web::OutOfProcessWebView>(); |     m_documentation_page_view = m_documentation_tooltip_window->set_main_widget<Web::OutOfProcessWebView>(); | ||||||
| 
 |  | ||||||
|     m_autocomplete_box = make<AutoCompleteBox>(*this); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Editor::~Editor() | Editor::~Editor() | ||||||
|  | @ -315,50 +313,6 @@ void Editor::mousedown_event(GUI::MouseEvent& event) | ||||||
|     GUI::TextEditor::mousedown_event(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) | void Editor::enter_event(Core::Event& event) | ||||||
| { | { | ||||||
|     m_hovering_editor = true; |     m_hovering_editor = true; | ||||||
|  | @ -486,6 +440,7 @@ void Editor::set_document(GUI::TextDocument& doc) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (m_language_client) { |     if (m_language_client) { | ||||||
|  |         set_autocomplete_provider(make<LanguageServerAidedAutocompleteProvider>(*m_language_client)); | ||||||
|         dbgln("Opening {}", code_document.file_path()); |         dbgln("Opening {}", code_document.file_path()); | ||||||
|         int fd = open(code_document.file_path().characters(), O_RDONLY | O_NOCTTY); |         int fd = open(code_document.file_path().characters(), O_RDONLY | O_NOCTTY); | ||||||
|         if (fd < 0) { |         if (fd < 0) { | ||||||
|  | @ -505,39 +460,21 @@ Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data( | ||||||
|     return Editor::AutoCompleteRequestData { cursor() }; |     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) |     auto& editor = static_cast<Editor&>(*m_editor).wrapper().editor(); | ||||||
|         return; |     auto data = editor.get_autocomplete_request_data(); | ||||||
|  |     if (!data.has_value()) | ||||||
|  |         callback({}); | ||||||
| 
 | 
 | ||||||
|     m_language_client->on_autocomplete_suggestions = [=, this](auto suggestions) { |     m_language_client.on_autocomplete_suggestions = [callback = move(callback)](auto suggestions) { | ||||||
|         if (suggestions.is_empty()) { |         callback(suggestions); | ||||||
|             close_autocomplete(); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         show_autocomplete(data); |  | ||||||
| 
 |  | ||||||
|         m_autocomplete_box->update_suggestions(move(suggestions)); |  | ||||||
|         m_autocomplete_in_focus = true; |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     m_language_client->request_autocomplete( |     m_language_client.request_autocomplete( | ||||||
|         code_document().file_path(), |         editor.code_document().file_path(), | ||||||
|         data.position.line(), |         data.value().position.line(), | ||||||
|         data.position.column()); |         data.value().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; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Editor::on_edit_action(const GUI::Command& command) | void Editor::on_edit_action(const GUI::Command& command) | ||||||
|  |  | ||||||
|  | @ -26,7 +26,6 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "AutoCompleteBox.h" |  | ||||||
| #include "CodeDocument.h" | #include "CodeDocument.h" | ||||||
| #include "Debugger/BreakpointCallback.h" | #include "Debugger/BreakpointCallback.h" | ||||||
| #include "LanguageClient.h" | #include "LanguageClient.h" | ||||||
|  | @ -72,7 +71,6 @@ private: | ||||||
|     virtual void paint_event(GUI::PaintEvent&) override; |     virtual void paint_event(GUI::PaintEvent&) override; | ||||||
|     virtual void mousemove_event(GUI::MouseEvent&) override; |     virtual void mousemove_event(GUI::MouseEvent&) override; | ||||||
|     virtual void mousedown_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 enter_event(Core::Event&) override; | ||||||
|     virtual void leave_event(Core::Event&) override; |     virtual void leave_event(Core::Event&) override; | ||||||
| 
 | 
 | ||||||
|  | @ -87,18 +85,26 @@ private: | ||||||
|         GUI::TextPosition position; |         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 { } | ||||||
| 
 | 
 | ||||||
|     void update_autocomplete(const AutoCompleteRequestData&); |     private: | ||||||
|     void show_autocomplete(const AutoCompleteRequestData&); |         virtual void provide_completions(Function<void(Vector<Entry>)> callback) override; | ||||||
|     void close_autocomplete(); |         LanguageClient& m_language_client; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     Optional<AutoCompleteRequestData> get_autocomplete_request_data(); | ||||||
| 
 | 
 | ||||||
|     void flush_file_content_to_langauge_server(); |     void flush_file_content_to_langauge_server(); | ||||||
| 
 | 
 | ||||||
|     explicit Editor(); |     explicit Editor(); | ||||||
| 
 | 
 | ||||||
|     RefPtr<GUI::Window> m_documentation_tooltip_window; |     RefPtr<GUI::Window> m_documentation_tooltip_window; | ||||||
|     OwnPtr<AutoCompleteBox> m_autocomplete_box; |  | ||||||
|     RefPtr<Web::OutOfProcessWebView> m_documentation_page_view; |     RefPtr<Web::OutOfProcessWebView> m_documentation_page_view; | ||||||
|     String m_last_parsed_token; |     String m_last_parsed_token; | ||||||
|     GUI::TextPosition m_previous_text_position { 0, 0 }; |     GUI::TextPosition m_previous_text_position { 0, 0 }; | ||||||
|  |  | ||||||
|  | @ -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)); |     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) |     if (on_autocomplete_suggestions) | ||||||
|         on_autocomplete_suggestions(suggestions); |         on_autocomplete_suggestions(suggestions); | ||||||
|  |  | ||||||
|  | @ -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 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); |     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: | private: | ||||||
|     ServerConnection& m_connection; |     ServerConnection& m_connection; | ||||||
|  |  | ||||||
|  | @ -32,10 +32,10 @@ | ||||||
| 
 | 
 | ||||||
| namespace LanguageServers::Cpp { | 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); |     auto lines = code.split('\n', true); | ||||||
|     Lexer lexer(code); |     Cpp::Lexer lexer(code); | ||||||
|     auto tokens = lexer.lex(); |     auto tokens = lexer.lex(); | ||||||
| 
 | 
 | ||||||
|     auto index_of_target_token = token_in_position(tokens, autocomplete_position); |     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 {}; |     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]); |     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
 |     HashTable<String> suggestions_lookup; // To avoid duplicate results
 | ||||||
| 
 | 
 | ||||||
|  | @ -88,7 +88,7 @@ Vector<AutoCompleteResponse> AutoComplete::identifier_prefixes(const Vector<Stri | ||||||
|             continue; |             continue; | ||||||
|         auto text = text_of_token(lines, token); |         auto text = text_of_token(lines, token); | ||||||
|         if (text.starts_with(partial_input) && suggestions_lookup.set(text) == AK::HashSetResult::InsertedNewEntry) { |         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; |     return suggestions; | ||||||
|  |  | ||||||
|  | @ -35,18 +35,17 @@ | ||||||
| namespace LanguageServers::Cpp { | namespace LanguageServers::Cpp { | ||||||
| 
 | 
 | ||||||
| using namespace ::Cpp; | using namespace ::Cpp; | ||||||
| using ::HackStudio::AutoCompleteResponse; |  | ||||||
| 
 | 
 | ||||||
| class AutoComplete { | class AutoComplete { | ||||||
| public: | public: | ||||||
|     AutoComplete() = delete; |     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: | private: | ||||||
|     static Optional<size_t> token_in_position(const Vector<Cpp::Token>&, const GUI::TextPosition&); |     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 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); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| endpoint LanguageClient = 8002 | endpoint LanguageClient = 8002 | ||||||
| { | { | ||||||
|     AutoCompleteSuggestions(Vector<HackStudio::AutoCompleteResponse> suggestions) =| |     AutoCompleteSuggestions(Vector<GUI::AutocompleteProvider::Entry> suggestions) =| | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ | ||||||
| 
 | 
 | ||||||
| namespace LanguageServers::Shell { | 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!
 |     // FIXME: No need to reparse this every time!
 | ||||||
|     auto ast = ::Shell::Parser { code }.parse(); |     auto ast = ::Shell::Parser { code }.parse(); | ||||||
|  | @ -49,7 +49,7 @@ Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, s | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     auto result = ast->complete_for_editor(m_shell, offset); |     auto result = ast->complete_for_editor(m_shell, offset); | ||||||
|     Vector<AutoCompleteResponse> completions; |     Vector<GUI::AutocompleteProvider::Entry> completions; | ||||||
|     for (auto& entry : result) { |     for (auto& entry : result) { | ||||||
| #ifdef DEBUG_AUTOCOMPLETE | #ifdef DEBUG_AUTOCOMPLETE | ||||||
|         dbgln("Suggestion: '{}' starting at {}", entry.text_string, entry.input_offset); |         dbgln("Suggestion: '{}' starting at {}", entry.text_string, entry.input_offset); | ||||||
|  |  | ||||||
|  | @ -34,8 +34,6 @@ | ||||||
| 
 | 
 | ||||||
| namespace LanguageServers::Shell { | namespace LanguageServers::Shell { | ||||||
| 
 | 
 | ||||||
| using namespace ::HackStudio; |  | ||||||
| 
 |  | ||||||
| class AutoComplete { | class AutoComplete { | ||||||
| public: | public: | ||||||
|     AutoComplete() |     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: | private: | ||||||
|     NonnullRefPtr<::Shell::Shell> m_shell; |     NonnullRefPtr<::Shell::Shell> m_shell; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| /*
 | /*
 | ||||||
|  * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com> |  * Copyright (c) 2020, the SerenityOS developers. | ||||||
|  * All rights reserved. |  * All rights reserved. | ||||||
|  * |  * | ||||||
|  * Redistribution and use in source and binary forms, with or without |  * 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. |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include "AutoCompleteBox.h" | #include <LibGUI/AutocompleteProvider.h> | ||||||
| #include "Editor.h" |  | ||||||
| #include <LibGUI/Model.h> | #include <LibGUI/Model.h> | ||||||
| #include <LibGUI/TableView.h> | #include <LibGUI/TableView.h> | ||||||
|  | #include <LibGUI/TextEditor.h> | ||||||
| #include <LibGUI/Window.h> | #include <LibGUI/Window.h> | ||||||
| #include <LibGfx/Bitmap.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: | public: | ||||||
|     explicit AutoCompleteSuggestionModel(Vector<AutoCompleteResponse>&& suggestions) |     explicit AutocompleteSuggestionModel(Vector<AutocompleteProvider::Entry>&& suggestions) | ||||||
|         : m_suggestions(move(suggestions)) |         : m_suggestions(move(suggestions)) | ||||||
|     { |     { | ||||||
|     } |     } | ||||||
|  | @ -64,11 +65,20 @@ public: | ||||||
|                 return suggestion.completion; |                 return suggestion.completion; | ||||||
|             } |             } | ||||||
|             if (index.column() == Column::Icon) { |             if (index.column() == Column::Icon) { | ||||||
|                 // TODO: Have separate icons for fields, functions, methods etc
 |                 // TODO
 | ||||||
|                 // FIXME: Probably should have different icons for the different kinds, rather than for "c++".
 |                 if (suggestion.language == GUI::AutocompleteProvider::Language::Cpp) { | ||||||
|                 if (suggestion.kind == CompletionKind::Identifier) |                     if (!s_cpp_identifier_icon) { | ||||||
|                     return *s_cplusplus_icon; |                         s_cpp_identifier_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/completion/cpp-identifier.png"); | ||||||
|                 return *s_cplusplus_icon; |                     } | ||||||
|  |                     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 {}; |     virtual void update() override {}; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     Vector<AutoCompleteResponse> m_suggestions; |     Vector<AutocompleteProvider::Entry> m_suggestions; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| AutoCompleteBox::~AutoCompleteBox() { } | AutocompleteBox::~AutocompleteBox() { } | ||||||
| 
 | 
 | ||||||
| AutoCompleteBox::AutoCompleteBox(WeakPtr<Editor> editor) | AutocompleteBox::AutocompleteBox(TextEditor& editor) | ||||||
|     : m_editor(move(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 = GUI::Window::construct(); | ||||||
|     m_popup_window->set_window_type(GUI::WindowType::Tooltip); |     m_popup_window->set_window_type(GUI::WindowType::Tooltip); | ||||||
|     m_popup_window->set_rect(0, 0, 200, 100); |     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); |     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()) |     if (suggestions.is_empty()) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|     bool has_suggestions = !suggestions.is_empty(); |     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) |     if (!has_suggestions) | ||||||
|         m_suggestion_view->selection().clear(); |         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)); |         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->move_to(suggstion_box_location); | ||||||
|     m_popup_window->show(); |     m_popup_window->show(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AutoCompleteBox::close() | void AutocompleteBox::close() | ||||||
| { | { | ||||||
|     m_popup_window->hide(); |     m_popup_window->hide(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AutoCompleteBox::next_suggestion() | void AutocompleteBox::next_suggestion() | ||||||
| { | { | ||||||
|     GUI::ModelIndex new_index = m_suggestion_view->selection().first(); |     GUI::ModelIndex new_index = m_suggestion_view->selection().first(); | ||||||
|     if (new_index.is_valid()) |     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(); |     GUI::ModelIndex new_index = m_suggestion_view->selection().first(); | ||||||
|     if (new_index.is_valid()) |     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()) |     if (m_editor.is_null()) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|  |     if (!m_editor->is_editable()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|     auto selected_index = m_suggestion_view->selection().first(); |     auto selected_index = m_suggestion_view->selection().first(); | ||||||
|     if (!selected_index.is_valid()) |     if (!selected_index.is_valid()) | ||||||
|         return; |         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(); |     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); |     ASSERT(suggestion.length() >= partial_length); | ||||||
|     auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length); |     auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length); | ||||||
|     m_editor->insert_at_cursor_or_replace_selection(completion); |     m_editor->insert_at_cursor_or_replace_selection(completion); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| }; | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| /*
 | /*
 | ||||||
|  * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com> |  * Copyright (c) 2020, the SerenityOS developers. | ||||||
|  * All rights reserved. |  * All rights reserved. | ||||||
|  * |  * | ||||||
|  * Redistribution and use in source and binary forms, with or without |  * Redistribution and use in source and binary forms, with or without | ||||||
|  | @ -26,20 +26,57 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "AutoCompleteResponse.h" | #include <LibGUI/Forward.h> | ||||||
| #include <AK/WeakPtr.h> | #include <LibGUI/TextEditor.h> | ||||||
| #include <LibGUI/Widget.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: | public: | ||||||
|     explicit AutoCompleteBox(WeakPtr<Editor>); |     virtual ~AutocompleteProvider() { } | ||||||
|     ~AutoCompleteBox(); |  | ||||||
| 
 | 
 | ||||||
|     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 show(Gfx::IntPoint suggstion_box_location); | ||||||
|     void close(); |     void close(); | ||||||
| 
 | 
 | ||||||
|  | @ -48,10 +85,9 @@ public: | ||||||
|     void apply_suggestion(); |     void apply_suggestion(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void complete_suggestion(const GUI::ModelIndex&); |     WeakPtr<TextEditor> m_editor; | ||||||
| 
 |  | ||||||
|     WeakPtr<Editor> m_editor; |  | ||||||
|     RefPtr<GUI::Window> m_popup_window; |     RefPtr<GUI::Window> m_popup_window; | ||||||
|     RefPtr<GUI::TableView> m_suggestion_view; |     RefPtr<GUI::TableView> m_suggestion_view; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -6,9 +6,10 @@ set(SOURCES | ||||||
|     Action.cpp |     Action.cpp | ||||||
|     ActionGroup.cpp |     ActionGroup.cpp | ||||||
|     Application.cpp |     Application.cpp | ||||||
|  |     AutocompleteProvider.cpp | ||||||
|     BoxLayout.cpp |     BoxLayout.cpp | ||||||
|     Button.cpp |  | ||||||
|     BreadcrumbBar.cpp |     BreadcrumbBar.cpp | ||||||
|  |     Button.cpp | ||||||
|     Calendar.cpp |     Calendar.cpp | ||||||
|     CheckBox.cpp |     CheckBox.cpp | ||||||
|     Clipboard.cpp |     Clipboard.cpp | ||||||
|  |  | ||||||
|  | @ -25,10 +25,12 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <AK/QuickSort.h> | #include <AK/QuickSort.h> | ||||||
|  | #include <AK/ScopeGuard.h> | ||||||
| #include <AK/StringBuilder.h> | #include <AK/StringBuilder.h> | ||||||
| #include <AK/TemporaryChange.h> | #include <AK/TemporaryChange.h> | ||||||
| #include <LibCore/Timer.h> | #include <LibCore/Timer.h> | ||||||
| #include <LibGUI/Action.h> | #include <LibGUI/Action.h> | ||||||
|  | #include <LibGUI/AutocompleteProvider.h> | ||||||
| #include <LibGUI/Clipboard.h> | #include <LibGUI/Clipboard.h> | ||||||
| #include <LibGUI/InputBox.h> | #include <LibGUI/InputBox.h> | ||||||
| #include <LibGUI/Menu.h> | #include <LibGUI/Menu.h> | ||||||
|  | @ -711,6 +713,27 @@ void TextEditor::sort_selected_lines() | ||||||
| 
 | 
 | ||||||
| void TextEditor::keydown_event(KeyEvent& event) | 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) |     if (is_single_line() && event.key() == KeyCode::Key_Tab) | ||||||
|         return ScrollableWidget::keydown_event(event); |         return ScrollableWidget::keydown_event(event); | ||||||
| 
 | 
 | ||||||
|  | @ -720,6 +743,14 @@ void TextEditor::keydown_event(KeyEvent& event) | ||||||
|         return; |         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 (event.key() == KeyCode::Key_Escape) { | ||||||
|         if (on_escape_pressed) |         if (on_escape_pressed) | ||||||
|             on_escape_pressed(); |             on_escape_pressed(); | ||||||
|  | @ -997,6 +1028,18 @@ void TextEditor::keydown_event(KeyEvent& event) | ||||||
|         return; |         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) { |     if (is_editable() && !event.ctrl() && !event.alt() && event.code_point() != 0) { | ||||||
|         StringBuilder sb; |         StringBuilder sb; | ||||||
|         sb.append_code_point(event.code_point()); |         sb.append_code_point(event.code_point()); | ||||||
|  | @ -1759,6 +1802,25 @@ void TextEditor::set_syntax_highlighter(OwnPtr<SyntaxHighlighter> highlighter) | ||||||
|         document().set_spans({}); |         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 | int TextEditor::line_height() const | ||||||
| { | { | ||||||
|     return font().glyph_height() + m_line_spacing; |     return font().glyph_height() + m_line_spacing; | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ | ||||||
| #include <AK/NonnullOwnPtrVector.h> | #include <AK/NonnullOwnPtrVector.h> | ||||||
| #include <AK/NonnullRefPtrVector.h> | #include <AK/NonnullRefPtrVector.h> | ||||||
| #include <LibCore/ElapsedTimer.h> | #include <LibCore/ElapsedTimer.h> | ||||||
|  | #include <LibGUI/Forward.h> | ||||||
| #include <LibGUI/ScrollableWidget.h> | #include <LibGUI/ScrollableWidget.h> | ||||||
| #include <LibGUI/TextDocument.h> | #include <LibGUI/TextDocument.h> | ||||||
| #include <LibGUI/TextRange.h> | #include <LibGUI/TextRange.h> | ||||||
|  | @ -159,6 +160,9 @@ public: | ||||||
|     const SyntaxHighlighter* syntax_highlighter() const; |     const SyntaxHighlighter* syntax_highlighter() const; | ||||||
|     void set_syntax_highlighter(OwnPtr<SyntaxHighlighter>); |     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; } |     bool is_in_drag_select() const { return m_in_drag_select; } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|  | @ -317,6 +321,8 @@ private: | ||||||
|     NonnullOwnPtrVector<LineVisualData> m_line_visual_data; |     NonnullOwnPtrVector<LineVisualData> m_line_visual_data; | ||||||
| 
 | 
 | ||||||
|     OwnPtr<SyntaxHighlighter> m_highlighter; |     OwnPtr<SyntaxHighlighter> m_highlighter; | ||||||
|  |     OwnPtr<AutocompleteProvider> m_autocomplete_provider; | ||||||
|  |     OwnPtr<AutocompleteBox> m_autocomplete_box; | ||||||
| 
 | 
 | ||||||
|     RefPtr<Core::Timer> m_automatic_selection_scroll_timer; |     RefPtr<Core::Timer> m_automatic_selection_scroll_timer; | ||||||
|     Gfx::IntPoint m_last_mousemove_position; |     Gfx::IntPoint m_last_mousemove_position; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 AnotherTest
						AnotherTest