From 9e73b0b696721706129a0d20a4603f681c905ab0 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sat, 3 Oct 2020 16:30:16 +0330 Subject: [PATCH] HackStudio: Relay completions requests to the language server unfiltered Previously, the client would decide when to ask the server for completions, and it would only do so for identifiers that had spans (determined via the highlighter!). Swap this around and make the server decide if it wants to complete something. This commit also adds a CompletionKind (which only has one value: Identifier), to work with other kinds of completions as well. --- DevTools/HackStudio/AutoCompleteBox.cpp | 33 +++++--- DevTools/HackStudio/AutoCompleteBox.h | 5 +- DevTools/HackStudio/AutoCompleteResponse.h | 76 +++++++++++++++++++ DevTools/HackStudio/Editor.cpp | 24 +----- DevTools/HackStudio/Editor.h | 1 - DevTools/HackStudio/LanguageClient.cpp | 2 +- DevTools/HackStudio/LanguageClient.h | 8 +- .../LanguageServers/Cpp/AutoComplete.cpp | 10 +-- .../LanguageServers/Cpp/AutoComplete.h | 6 +- .../LanguageServers/Cpp/ClientConnection.cpp | 2 +- .../LanguageServers/Cpp/ClientConnection.h | 6 +- .../LanguageServers/LanguageClient.ipc | 2 +- 12 files changed, 125 insertions(+), 50 deletions(-) create mode 100644 DevTools/HackStudio/AutoCompleteResponse.h diff --git a/DevTools/HackStudio/AutoCompleteBox.cpp b/DevTools/HackStudio/AutoCompleteBox.cpp index 444a83038e..d5950c125c 100644 --- a/DevTools/HackStudio/AutoCompleteBox.cpp +++ b/DevTools/HackStudio/AutoCompleteBox.cpp @@ -37,7 +37,7 @@ static RefPtr s_cplusplus_icon; class AutoCompleteSuggestionModel final : public GUI::Model { public: - explicit AutoCompleteSuggestionModel(Vector&& suggestions) + explicit AutoCompleteSuggestionModel(Vector&& suggestions) : m_suggestions(move(suggestions)) { } @@ -48,6 +48,12 @@ public: __Column_Count, }; + enum InternalRole { + __ModelRoleCustom = (int)GUI::ModelRole::Custom, + PartialInputLength, + Kind, + }; + virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_suggestions.size(); } virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Column_Count; } virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override @@ -55,21 +61,29 @@ public: auto& suggestion = m_suggestions.at(index.row()); if (role == GUI::ModelRole::Display) { if (index.column() == Column::Name) { - return suggestion; + return suggestion.completion; } if (index.column() == Column::Icon) { // TODO: Have separate icons for fields, functions, methods etc - if (suggestion.ends_with(".cpp")) + // 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; } } + + if ((int)role == InternalRole::Kind) + return (u32)suggestion.kind; + + if ((int)role == InternalRole::PartialInputLength) + return (i64)suggestion.partial_input_length; + return {}; } virtual void update() override {}; private: - Vector m_suggestions; + Vector m_suggestions; }; AutoCompleteBox::~AutoCompleteBox() { } @@ -89,9 +103,8 @@ AutoCompleteBox::AutoCompleteBox(WeakPtr editor) m_suggestion_view->set_column_headers_visible(false); } -void AutoCompleteBox::update_suggestions(String partial_input, Vector&& suggestions) +void AutoCompleteBox::update_suggestions(Vector&& suggestions) { - m_partial_input = partial_input; if (suggestions.is_empty()) return; @@ -112,7 +125,6 @@ void AutoCompleteBox::show(Gfx::IntPoint suggstion_box_location) void AutoCompleteBox::close() { - m_partial_input.empty(); m_popup_window->hide(); } @@ -155,11 +167,10 @@ void AutoCompleteBox::apply_suggestion() 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(); - ASSERT(!m_partial_input.is_null()); - ASSERT(suggestion.starts_with(m_partial_input)); - - auto completion = suggestion.substring(m_partial_input.length(), suggestion.length() - m_partial_input.length()); + 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/DevTools/HackStudio/AutoCompleteBox.h index 0fe715aa12..ef4bdcf4a5 100644 --- a/DevTools/HackStudio/AutoCompleteBox.h +++ b/DevTools/HackStudio/AutoCompleteBox.h @@ -26,18 +26,20 @@ #pragma once +#include "AutoCompleteResponse.h" #include #include namespace HackStudio { class Editor; + class AutoCompleteBox final { public: explicit AutoCompleteBox(WeakPtr); ~AutoCompleteBox(); - void update_suggestions(const String partial_input, Vector&& suggestions); + void update_suggestions(Vector&& suggestions); void show(Gfx::IntPoint suggstion_box_location); void close(); @@ -51,6 +53,5 @@ private: WeakPtr m_editor; RefPtr m_popup_window; RefPtr m_suggestion_view; - String m_partial_input; }; } diff --git a/DevTools/HackStudio/AutoCompleteResponse.h b/DevTools/HackStudio/AutoCompleteResponse.h new file mode 100644 index 0000000000..e1fd298dec --- /dev/null +++ b/DevTools/HackStudio/AutoCompleteResponse.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#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) +{ + encoder << response.completion; + encoder << (u64)response.partial_input_length; + encoder << (u32)response.kind; + return true; +} + +template<> +inline bool decode(IPC::Decoder& decoder, HackStudio::AutoCompleteResponse& response) +{ + u32 kind = 0; + u64 partial_input_length = 0; + bool ok = decoder.decode(response.completion) + && decoder.decode(partial_input_length) + && decoder.decode(kind); + + if (ok) { + response.kind = static_cast(kind); + response.partial_input_length = partial_input_length; + } + + return ok; +} + +} diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index c52a8d1a3b..45398c1b35 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -494,28 +494,10 @@ void Editor::set_document(GUI::TextDocument& doc) Optional Editor::get_autocomplete_request_data() { - auto highlighter = wrapper().editor().syntax_highlighter(); - if (!highlighter) + if (!wrapper().editor().m_language_client) 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 {}; + return Editor::AutoCompleteRequestData { { cursor().line(), cursor().column() > 0 ? cursor().column() - 1 : 0 } }; } void Editor::update_autocomplete(const AutoCompleteRequestData& data) @@ -531,7 +513,7 @@ void Editor::update_autocomplete(const AutoCompleteRequestData& data) show_autocomplete(data); - m_autocomplete_box->update_suggestions(data.partial_input, move(suggestions)); + m_autocomplete_box->update_suggestions(move(suggestions)); m_autocomplete_in_focus = true; }; diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h index fb208a2fa0..90273ff27f 100644 --- a/DevTools/HackStudio/Editor.h +++ b/DevTools/HackStudio/Editor.h @@ -86,7 +86,6 @@ private: struct AutoCompleteRequestData { GUI::TextPosition position; - String partial_input; }; Optional get_autocomplete_request_data(); diff --git a/DevTools/HackStudio/LanguageClient.cpp b/DevTools/HackStudio/LanguageClient.cpp index 36539802d3..f394dc674d 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 905c09c951..7d0f56d228 100644 --- a/DevTools/HackStudio/LanguageClient.h +++ b/DevTools/HackStudio/LanguageClient.h @@ -26,12 +26,14 @@ #pragma once +#include "AutoCompleteResponse.h" #include #include #include +#include + #include #include -#include namespace HackStudio { @@ -104,9 +106,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 1ebdd9a8d6..6fda4cff65 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp +++ b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp @@ -32,7 +32,7 @@ namespace LanguageServers::Cpp { -Vector AutoComplete::get_suggestions(const String& code, GUI::TextPosition autocomplete_position) +Vector AutoComplete::get_suggestions(const String& code, GUI::TextPosition autocomplete_position) { auto lines = code.split('\n', true); Lexer lexer(code); @@ -46,7 +46,7 @@ Vector AutoComplete::get_suggestions(const String& code, GUI::TextPositi #ifdef DEBUG_AUTOCOMPLETE for (auto& suggestion : suggestions) { - dbg() << "suggestion: " << suggestion; + dbg() << "suggestion: " << suggestion.completion; } #endif @@ -71,10 +71,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 @@ -85,7 +85,7 @@ Vector AutoComplete::identifier_prefixes(const Vector lines, con auto text = text_of_token(lines, token); if (text.starts_with(partial_input) && !suggestions_lookup.contains(text)) { suggestions_lookup.set(text); - suggestions.append(text); + suggestions.append({ text, partial_input.length(), HackStudio::CompletionKind::Identifier }); } } return suggestions; diff --git a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h index ad0104f5e1..d0b772b0ad 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h +++ b/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h @@ -28,23 +28,25 @@ #include #include +#include #include #include namespace LanguageServers::Cpp { using namespace ::Cpp; +using ::HackStudio::AutoCompleteResponse; class AutoComplete { public: AutoComplete() = delete; - static Vector get_suggestions(const String& code, GUI::TextPosition autocomplete_position); + static Vector get_suggestions(const String& code, GUI::TextPosition autocomplete_position); private: static Optional token_in_position(const Vector&, GUI::TextPosition); static String 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/Cpp/ClientConnection.cpp b/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp index 5ffc750b8b..f57719f8cd 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp +++ b/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp @@ -159,7 +159,7 @@ void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSugges return; } - Vector suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)message.cursor_column() }); + auto suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)message.cursor_column() }); post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions))); } diff --git a/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h b/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h index 006688a524..2df3519d92 100644 --- a/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h +++ b/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h @@ -28,11 +28,13 @@ #include #include -#include -#include +#include #include #include +#include +#include + namespace LanguageServers::Cpp { class ClientConnection final diff --git a/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/DevTools/HackStudio/LanguageServers/LanguageClient.ipc index dbd0d33d46..66a63d53fb 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) =| }