From fa18010477821f114953015677daa789580a4683 Mon Sep 17 00:00:00 2001 From: Itamar Date: Sat, 23 Jan 2021 16:55:19 +0200 Subject: HackStudio: Integate with C++ parser-based autocomplete By default, C++ auto completion will still be performed by the lexer-based logic. However, the parser-based logic can be switched on via the menubar. --- Userland/DevTools/HackStudio/HackStudioWidget.cpp | 9 ++ Userland/DevTools/HackStudio/HackStudioWidget.h | 1 + Userland/DevTools/HackStudio/LanguageClient.cpp | 6 ++ Userland/DevTools/HackStudio/LanguageClient.h | 1 + .../LanguageServers/Cpp/AutoComplete.cpp | 96 ---------------------- .../HackStudio/LanguageServers/Cpp/AutoComplete.h | 51 ------------ .../HackStudio/LanguageServers/Cpp/CMakeLists.txt | 3 +- .../LanguageServers/Cpp/ClientConnection.cpp | 25 +++++- .../LanguageServers/Cpp/ClientConnection.h | 8 ++ .../LanguageServers/Cpp/LexerAutoComplete.cpp | 96 ++++++++++++++++++++++ .../LanguageServers/Cpp/LexerAutoComplete.h | 51 ++++++++++++ .../HackStudio/LanguageServers/LanguageServer.ipc | 1 + .../LanguageServers/Shell/ClientConnection.h | 1 + 13 files changed, 199 insertions(+), 150 deletions(-) delete mode 100644 Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp delete mode 100644 Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h create mode 100644 Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.cpp create mode 100644 Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.h (limited to 'Userland/DevTools') diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.cpp b/Userland/DevTools/HackStudio/HackStudioWidget.cpp index 7ef0eb33d2..15dfd5a7d9 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.cpp +++ b/Userland/DevTools/HackStudio/HackStudioWidget.cpp @@ -841,6 +841,7 @@ void HackStudioWidget::create_project_menubar(GUI::MenuBar& menubar) { auto& project_menu = menubar.add_menu("Project"); project_menu.add_action(*m_new_action); + project_menu.add_action(*create_set_autocomplete_mode_action()); } void HackStudioWidget::create_edit_menubar(GUI::MenuBar& menubar) @@ -919,6 +920,14 @@ NonnullRefPtr HackStudioWidget::create_stop_action() return action; } +NonnullRefPtr HackStudioWidget::create_set_autocomplete_mode_action() +{ + auto action = GUI::Action::create_checkable("AutoComplete C++ with Parser", [this](auto& action) { + get_language_client(project().root_path())->set_autocomplete_mode(action.is_checked() ? "Parser" : "Lexer"); + }); + return action; +} + void HackStudioWidget::initialize_menubar(GUI::MenuBar& menubar) { create_app_menubar(menubar); diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.h b/Userland/DevTools/HackStudio/HackStudioWidget.h index 29ef3701e4..d35ec85284 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.h +++ b/Userland/DevTools/HackStudio/HackStudioWidget.h @@ -94,6 +94,7 @@ private: NonnullRefPtr create_build_action(); NonnullRefPtr create_run_action(); NonnullRefPtr create_stop_action(); + NonnullRefPtr create_set_autocomplete_mode_action(); void add_new_editor(GUI::Widget& parent); NonnullRefPtr get_editor_of_file(const String& file_name); diff --git a/Userland/DevTools/HackStudio/LanguageClient.cpp b/Userland/DevTools/HackStudio/LanguageClient.cpp index e02b2c98cf..98341a046f 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.cpp +++ b/Userland/DevTools/HackStudio/LanguageClient.cpp @@ -25,6 +25,7 @@ */ #include "LanguageClient.h" +#include "DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h" #include #include @@ -72,4 +73,9 @@ void LanguageClient::provide_autocomplete_suggestions(const Vector&); diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp deleted file mode 100644 index b864563b4e..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2020, Itamar S. - * 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. - */ - -#include "AutoComplete.h" -#include -#include -#include - -namespace LanguageServers::Cpp { - -Vector AutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position) -{ - auto lines = code.split('\n', true); - Cpp::Lexer lexer(code); - auto tokens = lexer.lex(); - - auto index_of_target_token = token_in_position(tokens, autocomplete_position); - if (!index_of_target_token.has_value()) - return {}; - - auto suggestions = identifier_prefixes(lines, tokens, index_of_target_token.value()); - -#if AUTOCOMPLETE_DEBUG - for (auto& suggestion : suggestions) { - dbgln("suggestion: {}", suggestion.completion); - } -#endif - - return suggestions; -} - -StringView AutoComplete::text_of_token(const Vector& lines, const Cpp::Token& token) -{ - ASSERT(token.m_start.line == token.m_end.line); - ASSERT(token.m_start.column <= token.m_end.column); - return lines[token.m_start.line].substring_view(token.m_start.column, token.m_end.column - token.m_start.column + 1); -} - -Optional AutoComplete::token_in_position(const Vector& tokens, const GUI::TextPosition& position) -{ - for (size_t token_index = 0; token_index < tokens.size(); ++token_index) { - auto& token = tokens[token_index]; - if (token.m_start.line != token.m_end.line) - continue; - if (token.m_start.line != position.line()) - continue; - if (token.m_start.column + 1 > position.column() || token.m_end.column + 1 < position.column()) - continue; - return token_index; - } - return {}; -} - -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; - - HashTable suggestions_lookup; // To avoid duplicate results - - for (size_t i = 0; i < target_token_index; ++i) { - auto& token = tokens[i]; - if (token.m_type != Cpp::Token::Type::Identifier) - continue; - auto text = text_of_token(lines, token); - if (text.starts_with(partial_input) && suggestions_lookup.set(text) == AK::HashSetResult::InsertedNewEntry) { - suggestions.append({ text, partial_input.length(), GUI::AutocompleteProvider::CompletionKind::Identifier }); - } - } - return suggestions; -} - -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h deleted file mode 100644 index b4096bd793..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2020, Itamar S. - * 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 -#include - -namespace LanguageServers::Cpp { - -using namespace ::Cpp; - -class AutoComplete { -public: - AutoComplete() = delete; - - 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); -}; - -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt index de9569d8ca..7d34577447 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt @@ -1,7 +1,8 @@ set(SOURCES ClientConnection.cpp main.cpp - AutoComplete.cpp + LexerAutoComplete.cpp + ParserAutoComplete.cpp ) set(GENERATED_SOURCES diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp index 5881dbbeee..c3cdedeef4 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp @@ -25,7 +25,8 @@ */ #include "ClientConnection.h" -#include "AutoComplete.h" +#include "LexerAutoComplete.h" +#include "ParserAutoComplete.h" #include #include #include @@ -146,7 +147,16 @@ void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSugges return; } - auto suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)max(message.cursor_column(), message.cursor_column() - 1) }); + Vector suggestions; + switch (m_auto_complete_mode) { + case AutoCompleteMode::Lexer: + suggestions = LexerAutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)max(message.cursor_column(), message.cursor_column() - 1) }); + break; + case AutoCompleteMode::Parser: { + auto engine = ParserAutoComplete(document->text()); + suggestions = engine.get_suggestions({ (size_t)message.cursor_line(), (size_t)max(message.cursor_column(), message.cursor_column() - 1) }); + } + } post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions))); } @@ -170,4 +180,15 @@ void ClientConnection::handle(const Messages::LanguageServer::SetFileContent& me document->set_text(content.view()); } +void ClientConnection::handle(const Messages::LanguageServer::SetAutoCompleteMode& message) +{ +#ifdef DEBUG_CPP_LANGUAGE_SERVER + dbgln("SetAutoCompleteMode: {}", message.mode()); +#endif + if (message.mode() == "Parser") + m_auto_complete_mode = AutoCompleteMode::Parser; + else + m_auto_complete_mode = AutoCompleteMode::Lexer; +} + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h index d0a7b4558c..3389ba8231 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h @@ -55,10 +55,18 @@ private: virtual void handle(const Messages::LanguageServer::FileEditRemoveText&) override; virtual void handle(const Messages::LanguageServer::SetFileContent&) override; virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override; + virtual void handle(const Messages::LanguageServer::SetAutoCompleteMode&) override; RefPtr document_for(const String& file_name); HashMap> m_open_files; + + enum class AutoCompleteMode { + Lexer, + Parser + }; + + AutoCompleteMode m_auto_complete_mode { AutoCompleteMode::Lexer }; }; } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.cpp new file mode 100644 index 0000000000..9a31679728 --- /dev/null +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020, Itamar S. + * 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. + */ + +#include "LexerAutoComplete.h" +#include +#include +#include + +namespace LanguageServers::Cpp { + +Vector LexerAutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position) +{ + auto lines = code.split('\n', true); + Cpp::Lexer lexer(code); + auto tokens = lexer.lex(); + + auto index_of_target_token = token_in_position(tokens, autocomplete_position); + if (!index_of_target_token.has_value()) + return {}; + + auto suggestions = identifier_prefixes(lines, tokens, index_of_target_token.value()); + +#if AUTOCOMPLETE_DEBUG + for (auto& suggestion : suggestions) { + dbgln("suggestion: {}", suggestion.completion); + } +#endif + + return suggestions; +} + +StringView LexerAutoComplete::text_of_token(const Vector& lines, const Cpp::Token& token) +{ + ASSERT(token.m_start.line == token.m_end.line); + ASSERT(token.m_start.column <= token.m_end.column); + return lines[token.m_start.line].substring_view(token.m_start.column, token.m_end.column - token.m_start.column + 1); +} + +Optional LexerAutoComplete::token_in_position(const Vector& tokens, const GUI::TextPosition& position) +{ + for (size_t token_index = 0; token_index < tokens.size(); ++token_index) { + auto& token = tokens[token_index]; + if (token.m_start.line != token.m_end.line) + continue; + if (token.m_start.line != position.line()) + continue; + if (token.m_start.column + 1 > position.column() || token.m_end.column + 1 < position.column()) + continue; + return token_index; + } + return {}; +} + +Vector LexerAutoComplete::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; + + HashTable suggestions_lookup; // To avoid duplicate results + + for (size_t i = 0; i < target_token_index; ++i) { + auto& token = tokens[i]; + if (token.m_type != Cpp::Token::Type::Identifier) + continue; + auto text = text_of_token(lines, token); + if (text.starts_with(partial_input) && suggestions_lookup.set(text) == AK::HashSetResult::InsertedNewEntry) { + suggestions.append({ text, partial_input.length(), GUI::AutocompleteProvider::CompletionKind::Identifier }); + } + } + return suggestions; +} + +} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.h new file mode 100644 index 0000000000..e482818cfc --- /dev/null +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/LexerAutoComplete.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Itamar S. + * 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 +#include + +namespace LanguageServers::Cpp { + +using namespace ::Cpp; + +class LexerAutoComplete { +public: + LexerAutoComplete() = delete; + + 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); +}; + +} diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc index de16fa14ec..405dbbab88 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc @@ -8,4 +8,5 @@ endpoint LanguageServer = 8001 SetFileContent(String file_name, String content) =| AutoCompleteSuggestions(String file_name, i32 cursor_line, i32 cursor_column) =| + SetAutoCompleteMode(String mode) =| } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h index bb7fc14bd9..cea922037e 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h @@ -56,6 +56,7 @@ private: virtual void handle(const Messages::LanguageServer::FileEditRemoveText&) override; virtual void handle(const Messages::LanguageServer::SetFileContent&) override; virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override; + virtual void handle(const Messages::LanguageServer::SetAutoCompleteMode&) override { } RefPtr document_for(const String& file_name); -- cgit v1.2.3