diff options
author | Itamar <itamar8910@gmail.com> | 2022-05-14 17:09:24 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-05-21 18:15:58 +0200 |
commit | b35293d9458bd583f81ca1ec2df59f06e4690bf0 (patch) | |
tree | 88e6c1bf229cd54c2ff686c6bd304f06e6d9f8d3 /Userland/DevTools/HackStudio/LanguageServers | |
parent | a2c34554cd5ff3a17583f885088000b7cf975121 (diff) | |
download | serenity-b35293d9458bd583f81ca1ec2df59f06e4690bf0.zip |
LibCodeComprehension: Re-organize code comprehension related code
This moves all code comprehension-related code to a new library,
LibCodeComprehension.
This also moves some types related to code comprehension tasks (such as
autocomplete, find declaration) out of LibGUI and into
LibCodeComprehension.
Diffstat (limited to 'Userland/DevTools/HackStudio/LanguageServers')
27 files changed, 55 insertions, 1912 deletions
diff --git a/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt index 357146c2fe..a8c61a0a21 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt @@ -2,7 +2,6 @@ compile_ipc(LanguageServer.ipc LanguageServerEndpoint.h) compile_ipc(LanguageClient.ipc LanguageClientEndpoint.h) set(SOURCES - CodeComprehensionEngine.cpp ConnectionFromClient.cpp FileDB.cpp) set(GENERATED_SOURCES @@ -10,7 +9,7 @@ set(GENERATED_SOURCES LanguageServerEndpoint.h) serenity_lib(LibLanguageServer language_server) -target_link_libraries(LibLanguageServer LibC) +target_link_libraries(LibLanguageServer LibCodeComprehension LibC) add_subdirectory(Cpp) add_subdirectory(Shell) diff --git a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.cpp b/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.cpp deleted file mode 100644 index 381a918b59..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "CodeComprehensionEngine.h" - -namespace LanguageServers { - -CodeComprehensionEngine::CodeComprehensionEngine(FileDB const& filedb, bool should_store_all_declarations) - : m_filedb(filedb) - , m_store_all_declarations(should_store_all_declarations) -{ -} - -void CodeComprehensionEngine::set_declarations_of_document(String const& filename, Vector<GUI::AutocompleteProvider::Declaration>&& declarations) -{ - // Callback may not be configured if we're running tests - if (!set_declarations_of_document_callback) - return; - - // Optimization - Only notify callback if declarations have changed - if (auto previous_declarations = m_all_declarations.find(filename); previous_declarations != m_all_declarations.end()) { - if (previous_declarations->value == declarations) - return; - } - if (m_store_all_declarations) - m_all_declarations.set(filename, declarations); - set_declarations_of_document_callback(filename, move(declarations)); -} - -void CodeComprehensionEngine::set_todo_entries_of_document(String const& filename, Vector<Cpp::Parser::TodoEntry>&& todo_entries) -{ - // Callback may not be configured if we're running tests - if (!set_todo_entries_of_document_callback) - return; - set_todo_entries_of_document_callback(filename, move(todo_entries)); -} - -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h b/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h deleted file mode 100644 index ddca934520..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "../AutoCompleteResponse.h" -#include "FileDB.h" -#include <LibGUI/AutocompleteProvider.h> -#include <LibGUI/TextPosition.h> - -namespace LanguageServers { - -class ConnectionFromClient; - -class CodeComprehensionEngine { -public: - CodeComprehensionEngine(FileDB const& filedb, bool store_all_declarations = false); - virtual ~CodeComprehensionEngine() = default; - - virtual Vector<GUI::AutocompleteProvider::Entry> get_suggestions(String const& file, const GUI::TextPosition& autocomplete_position) = 0; - - // TODO: In the future we can pass the range that was edited and only re-parse what we have to. - virtual void on_edit([[maybe_unused]] String const& file) {}; - virtual void file_opened([[maybe_unused]] String const& file) {}; - - virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(String const&, const GUI::TextPosition&) { return {}; } - - struct FunctionParamsHint { - Vector<String> params; - size_t current_index { 0 }; - }; - virtual Optional<FunctionParamsHint> get_function_params_hint(String const&, const GUI::TextPosition&) { return {}; } - - virtual Vector<GUI::AutocompleteProvider::TokenInfo> get_tokens_info(String const&) { return {}; } - -public: - Function<void(String const&, Vector<GUI::AutocompleteProvider::Declaration>&&)> set_declarations_of_document_callback; - Function<void(String const&, Vector<Cpp::Parser::TodoEntry>&&)> set_todo_entries_of_document_callback; - -protected: - FileDB const& filedb() const { return m_filedb; } - void set_declarations_of_document(String const&, Vector<GUI::AutocompleteProvider::Declaration>&&); - void set_todo_entries_of_document(String const&, Vector<Cpp::Parser::TodoEntry>&&); - HashMap<String, Vector<GUI::AutocompleteProvider::Declaration>> const& all_declarations() const { return m_all_declarations; } - -private: - HashMap<String, Vector<GUI::AutocompleteProvider::Declaration>> m_all_declarations; - FileDB const& m_filedb; - bool m_store_all_declarations { false }; -}; -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.cpp b/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.cpp index d1f29a4199..7bf24c7cc9 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.cpp @@ -66,25 +66,25 @@ void ConnectionFromClient::file_edit_remove_text(String const& filename, i32 sta m_autocomplete_engine->on_edit(filename); } -void ConnectionFromClient::auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation const& location) +void ConnectionFromClient::auto_complete_suggestions(CodeComprehension::ProjectLocation const& location) { dbgln_if(LANGUAGE_SERVER_DEBUG, "AutoCompleteSuggestions for: {} {}:{}", location.file, location.line, location.column); - auto document = m_filedb.get(location.file); + auto document = m_filedb.get_document(location.file); if (!document) { dbgln("file {} has not been opened", location.file); return; } GUI::TextPosition autocomplete_position = { (size_t)location.line, (size_t)max(location.column, location.column - 1) }; - Vector<GUI::AutocompleteProvider::Entry> suggestions = m_autocomplete_engine->get_suggestions(location.file, autocomplete_position); + Vector<CodeComprehension::AutocompleteResultEntry> suggestions = m_autocomplete_engine->get_suggestions(location.file, autocomplete_position); async_auto_complete_suggestions(move(suggestions)); } void ConnectionFromClient::set_file_content(String const& filename, String const& content) { dbgln_if(LANGUAGE_SERVER_DEBUG, "SetFileContent: {}", filename); - auto document = m_filedb.get(filename); + auto document = m_filedb.get_document(filename); if (!document) { m_filedb.add(filename, content); VERIFY(m_filedb.is_open(filename)); @@ -95,10 +95,10 @@ void ConnectionFromClient::set_file_content(String const& filename, String const m_autocomplete_engine->on_edit(filename); } -void ConnectionFromClient::find_declaration(GUI::AutocompleteProvider::ProjectLocation const& location) +void ConnectionFromClient::find_declaration(CodeComprehension::ProjectLocation const& location) { dbgln_if(LANGUAGE_SERVER_DEBUG, "FindDeclaration: {} {}:{}", location.file, location.line, location.column); - auto document = m_filedb.get(location.file); + auto document = m_filedb.get_document(location.file); if (!document) { dbgln("file {} has not been opened", location.file); return; @@ -112,13 +112,13 @@ void ConnectionFromClient::find_declaration(GUI::AutocompleteProvider::ProjectLo } dbgln_if(LANGUAGE_SERVER_DEBUG, "declaration location: {} {}:{}", decl_location.value().file, decl_location.value().line, decl_location.value().column); - async_declaration_location(GUI::AutocompleteProvider::ProjectLocation { decl_location.value().file, decl_location.value().line, decl_location.value().column }); + async_declaration_location(CodeComprehension::ProjectLocation { decl_location.value().file, decl_location.value().line, decl_location.value().column }); } -void ConnectionFromClient::get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation const& location) +void ConnectionFromClient::get_parameters_hint(CodeComprehension::ProjectLocation const& location) { dbgln_if(LANGUAGE_SERVER_DEBUG, "GetParametersHint: {} {}:{}", location.file, location.line, location.column); - auto document = m_filedb.get(location.file); + auto document = m_filedb.get_document(location.file); if (!document) { dbgln("file {} has not been opened", location.file); return; @@ -143,7 +143,7 @@ void ConnectionFromClient::get_parameters_hint(GUI::AutocompleteProvider::Projec void ConnectionFromClient::get_tokens_info(String const& filename) { dbgln_if(LANGUAGE_SERVER_DEBUG, "GetTokenInfo: {}", filename); - auto document = m_filedb.get(filename); + auto document = m_filedb.get_document(filename); if (!document) { dbgln("file {} has not been opened", filename); return; diff --git a/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h b/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h index 6e909ff185..87563eba11 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h +++ b/Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h @@ -8,10 +8,10 @@ #pragma once #include "../AutoCompleteResponse.h" -#include "CodeComprehensionEngine.h" #include "FileDB.h" #include <AK/HashMap.h> #include <AK/LexicalPath.h> +#include <LibCodeComprehension/CodeComprehensionEngine.h> #include <LibIPC/ConnectionFromClient.h> #include <Userland/DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h> @@ -32,13 +32,13 @@ protected: virtual void file_edit_insert_text(String const&, String const&, i32, i32) override; virtual void file_edit_remove_text(String const&, i32, i32, i32, i32) override; virtual void set_file_content(String const&, String const&) override; - virtual void auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation const&) override; - virtual void find_declaration(GUI::AutocompleteProvider::ProjectLocation const&) override; - virtual void get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation const&) override; + virtual void auto_complete_suggestions(CodeComprehension::ProjectLocation const&) override; + virtual void find_declaration(CodeComprehension::ProjectLocation const&) override; + virtual void get_parameters_hint(CodeComprehension::ProjectLocation const&) override; virtual void get_tokens_info(String const&) override; FileDB m_filedb; - OwnPtr<CodeComprehensionEngine> m_autocomplete_engine; + OwnPtr<CodeComprehension::CodeComprehensionEngine> m_autocomplete_engine; }; } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt index 3ea2679652..4989bc42c1 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt @@ -4,8 +4,6 @@ serenity_component( ) set(SOURCES - CppComprehensionEngine.cpp - Tests.cpp main.cpp ) @@ -17,4 +15,4 @@ serenity_bin(CppLanguageServer) # We link with LibGUI because we use GUI::TextDocument to update # the content of files according to the edit actions we receive over IPC. -target_link_libraries(CppLanguageServer LibIPC LibCpp LibGUI LibLanguageServer LibMain) +target_link_libraries(CppLanguageServer LibIPC LibCpp LibGUI LibLanguageServer LibCppComprehension LibMain) diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ConnectionFromClient.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ConnectionFromClient.h index ff5744c369..13ab563474 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ConnectionFromClient.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ConnectionFromClient.h @@ -6,8 +6,8 @@ #pragma once -#include "CppComprehensionEngine.h" #include <DevTools/HackStudio/LanguageServers/ConnectionFromClient.h> +#include <LibCodeComprehension/Cpp/CppComprehensionEngine.h> namespace LanguageServers::Cpp { @@ -18,11 +18,11 @@ private: ConnectionFromClient(NonnullOwnPtr<Core::Stream::LocalSocket> socket) : LanguageServers::ConnectionFromClient(move(socket)) { - m_autocomplete_engine = make<CppComprehensionEngine>(m_filedb); - m_autocomplete_engine->set_declarations_of_document_callback = [this](String const& filename, Vector<GUI::AutocompleteProvider::Declaration>&& declarations) { + m_autocomplete_engine = adopt_own(*new CodeComprehension::Cpp::CppComprehensionEngine(m_filedb)); + m_autocomplete_engine->set_declarations_of_document_callback = [this](String const& filename, Vector<CodeComprehension::Declaration>&& declarations) { async_declarations_in_document(filename, move(declarations)); }; - m_autocomplete_engine->set_todo_entries_of_document_callback = [this](String const& filename, Vector<Cpp::Parser::TodoEntry>&& todo_entries) { + m_autocomplete_engine->set_todo_entries_of_document_callback = [this](String const& filename, Vector<CodeComprehension::TodoEntry>&& todo_entries) { async_todo_entries_in_document(filename, move(todo_entries)); }; } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp deleted file mode 100644 index 4abfdc7861..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "CppComprehensionEngine.h" -#include <AK/Assertions.h> -#include <AK/HashTable.h> -#include <AK/OwnPtr.h> -#include <AK/ScopeGuard.h> -#include <LibCore/DirIterator.h> -#include <LibCore/File.h> -#include <LibCpp/AST.h> -#include <LibCpp/Lexer.h> -#include <LibCpp/Parser.h> -#include <LibCpp/Preprocessor.h> -#include <LibRegex/Regex.h> -#include <Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h> - -namespace LanguageServers::Cpp { - -CppComprehensionEngine::CppComprehensionEngine(FileDB const& filedb) - : CodeComprehensionEngine(filedb, true) -{ -} - -CppComprehensionEngine::DocumentData const* CppComprehensionEngine::get_or_create_document_data(String const& file) -{ - auto absolute_path = filedb().to_absolute_path(file); - if (!m_documents.contains(absolute_path)) { - set_document_data(absolute_path, create_document_data_for(absolute_path)); - } - return get_document_data(absolute_path); -} - -CppComprehensionEngine::DocumentData const* CppComprehensionEngine::get_document_data(String const& file) const -{ - auto absolute_path = filedb().to_absolute_path(file); - auto document_data = m_documents.get(absolute_path); - if (!document_data.has_value()) - return nullptr; - return document_data.value(); -} - -OwnPtr<CppComprehensionEngine::DocumentData> CppComprehensionEngine::create_document_data_for(String const& file) -{ - if (m_unfinished_documents.contains(file)) { - return {}; - } - m_unfinished_documents.set(file); - ScopeGuard mark_finished([&file, this]() { m_unfinished_documents.remove(file); }); - auto document = filedb().get_or_create_from_filesystem(file); - if (!document) - return {}; - return create_document_data(document->text(), file); -} - -void CppComprehensionEngine::set_document_data(String const& file, OwnPtr<DocumentData>&& data) -{ - m_documents.set(filedb().to_absolute_path(file), move(data)); -} - -Vector<GUI::AutocompleteProvider::Entry> CppComprehensionEngine::get_suggestions(String const& file, const GUI::TextPosition& autocomplete_position) -{ - Cpp::Position position { autocomplete_position.line(), autocomplete_position.column() > 0 ? autocomplete_position.column() - 1 : 0 }; - - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "CppComprehensionEngine position {}:{}", position.line, position.column); - - auto const* document_ptr = get_or_create_document_data(file); - if (!document_ptr) - return {}; - - auto const& document = *document_ptr; - auto containing_token = document.parser().token_at(position); - - if (containing_token.has_value() && containing_token->type() == Token::Type::IncludePath) { - auto results = try_autocomplete_include(document, containing_token.value(), position); - if (results.has_value()) - return results.value(); - } - - auto node = document.parser().node_at(position); - if (!node) { - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", position.line, position.column); - return {}; - } - - if (node->parent() && node->parent()->parent()) - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "node: {}, parent: {}, grandparent: {}", node->class_name(), node->parent()->class_name(), node->parent()->parent()->class_name()); - - if (!node->parent()) - return {}; - - auto results = try_autocomplete_property(document, *node, containing_token); - if (results.has_value()) - return results.value(); - - results = try_autocomplete_name(document, *node, containing_token); - if (results.has_value()) - return results.value(); - return {}; -} - -Optional<Vector<GUI::AutocompleteProvider::Entry>> CppComprehensionEngine::try_autocomplete_name(DocumentData const& document, ASTNode const& node, Optional<Token> containing_token) const -{ - auto partial_text = String::empty(); - if (containing_token.has_value() && containing_token.value().type() != Token::Type::ColonColon) { - partial_text = containing_token.value().text(); - } - return autocomplete_name(document, node, partial_text); -} - -Optional<Vector<GUI::AutocompleteProvider::Entry>> CppComprehensionEngine::try_autocomplete_property(DocumentData const& document, ASTNode const& node, Optional<Token> containing_token) const -{ - if (!containing_token.has_value()) - return {}; - - if (!node.parent()->is_member_expression()) - return {}; - - auto const& parent = static_cast<MemberExpression const&>(*node.parent()); - - auto partial_text = String::empty(); - if (containing_token.value().type() != Token::Type::Dot) { - if (&node != parent.property()) - return {}; - partial_text = containing_token.value().text(); - } - - return autocomplete_property(document, parent, partial_text); -} - -Vector<GUI::AutocompleteProvider::Entry> CppComprehensionEngine::autocomplete_name(DocumentData const& document, ASTNode const& node, String const& partial_text) const -{ - auto reference_scope = scope_of_reference_to_symbol(node); - auto current_scope = scope_of_node(node); - - auto symbol_matches = [&](Symbol const& symbol) { - if (!is_symbol_available(symbol, current_scope, reference_scope)) { - return false; - } - - if (!symbol.name.name.starts_with(partial_text)) - return false; - - if (symbol.is_local) { - // If this symbol was declared below us in a function, it's not available to us. - bool is_unavailable = symbol.is_local && symbol.declaration->start().line > node.start().line; - if (is_unavailable) - return false; - } - - return true; - }; - - Vector<Symbol> matches; - - for_each_available_symbol(document, [&](Symbol const& symbol) { - if (symbol_matches(symbol)) { - matches.append(symbol); - } - return IterationDecision::Continue; - }); - - Vector<GUI::AutocompleteProvider::Entry> suggestions; - for (auto& symbol : matches) { - suggestions.append({ symbol.name.name, partial_text.length() }); - } - - if (reference_scope.is_empty()) { - for (auto& preprocessor_name : document.preprocessor().definitions().keys()) { - if (preprocessor_name.starts_with(partial_text)) { - suggestions.append({ preprocessor_name, partial_text.length() }); - } - } - } - - return suggestions; -} - -Vector<StringView> CppComprehensionEngine::scope_of_reference_to_symbol(ASTNode const& node) const -{ - Name const* name = nullptr; - if (node.is_name()) { - // FIXME It looks like this code path is never taken - name = reinterpret_cast<Name const*>(&node); - } else if (node.is_identifier()) { - auto* parent = node.parent(); - if (!(parent && parent->is_name())) - return {}; - name = reinterpret_cast<Name const*>(parent); - } else { - return {}; - } - - VERIFY(name->is_name()); - - Vector<StringView> scope_parts; - for (auto& scope_part : name->scope()) { - // If the target node is part of a scope reference, we want to end the scope chain before it. - if (&scope_part == &node) - break; - scope_parts.append(scope_part.name()); - } - return scope_parts; -} - -Vector<GUI::AutocompleteProvider::Entry> CppComprehensionEngine::autocomplete_property(DocumentData const& document, MemberExpression const& parent, const String partial_text) const -{ - VERIFY(parent.object()); - auto type = type_of(document, *parent.object()); - if (type.is_null()) { - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "Could not infer type of object"); - return {}; - } - - Vector<GUI::AutocompleteProvider::Entry> suggestions; - for (auto& prop : properties_of_type(document, type)) { - if (prop.name.name.starts_with(partial_text)) { - suggestions.append({ prop.name.name, partial_text.length() }); - } - } - return suggestions; -} - -bool CppComprehensionEngine::is_property(ASTNode const& node) const -{ - if (!node.parent()->is_member_expression()) - return false; - - auto& parent = verify_cast<MemberExpression>(*node.parent()); - return parent.property() == &node; -} - -String CppComprehensionEngine::type_of_property(DocumentData const& document, Identifier const& identifier) const -{ - auto& parent = verify_cast<MemberExpression>(*identifier.parent()); - VERIFY(parent.object()); - auto properties = properties_of_type(document, type_of(document, *parent.object())); - for (auto& prop : properties) { - if (prop.name.name != identifier.name()) - continue; - Type const* type { nullptr }; - if (prop.declaration->is_variable_declaration()) { - type = verify_cast<VariableDeclaration>(*prop.declaration).type(); - } - if (!type) - continue; - if (!type->is_named_type()) - continue; - - VERIFY(verify_cast<NamedType>(*type).name()); - if (verify_cast<NamedType>(*type).name()) - return verify_cast<NamedType>(*type).name()->full_name(); - return String::empty(); - } - return {}; -} - -String CppComprehensionEngine::type_of_variable(Identifier const& identifier) const -{ - ASTNode const* current = &identifier; - while (current) { - for (auto& decl : current->declarations()) { - if (decl.is_variable_or_parameter_declaration()) { - auto& var_or_param = verify_cast<VariableOrParameterDeclaration>(decl); - if (var_or_param.full_name() == identifier.name() && var_or_param.type()->is_named_type()) { - VERIFY(verify_cast<NamedType>(*var_or_param.type()).name()); - if (verify_cast<NamedType>(*var_or_param.type()).name()) - return verify_cast<NamedType>(*var_or_param.type()).name()->full_name(); - return String::empty(); - } - } - } - current = current->parent(); - } - return {}; -} - -String CppComprehensionEngine::type_of(DocumentData const& document, Expression const& expression) const -{ - if (expression.is_member_expression()) { - auto& member_expression = verify_cast<MemberExpression>(expression); - VERIFY(member_expression.property()); - if (member_expression.property()->is_identifier()) - return type_of_property(document, static_cast<Identifier const&>(*member_expression.property())); - return {}; - } - - Identifier const* identifier { nullptr }; - if (expression.is_name()) { - identifier = static_cast<Name const&>(expression).name(); - } else if (expression.is_identifier()) { - identifier = &static_cast<Identifier const&>(expression); - } else { - dbgln("expected identifier or name, got: {}", expression.class_name()); - VERIFY_NOT_REACHED(); // TODO - } - VERIFY(identifier); - if (is_property(*identifier)) - return type_of_property(document, *identifier); - - return type_of_variable(*identifier); -} - -Vector<CppComprehensionEngine::Symbol> CppComprehensionEngine::properties_of_type(DocumentData const& document, String const& type) const -{ - auto type_symbol = SymbolName::create(type); - auto decl = find_declaration_of(document, type_symbol); - if (!decl) { - dbgln("Couldn't find declaration of type: {}", type); - return {}; - } - - if (!decl->is_struct_or_class()) { - dbgln("Expected declaration of type: {} to be struct or class", type); - return {}; - } - - auto& struct_or_class = verify_cast<StructOrClassDeclaration>(*decl); - VERIFY(struct_or_class.full_name() == type_symbol.name); - - Vector<Symbol> properties; - for (auto& member : struct_or_class.members()) { - Vector<StringView> scope(type_symbol.scope); - scope.append(type_symbol.name); - // FIXME: We don't have to create the Symbol here, it should already exist in the 'm_symbol' table of some DocumentData we already parsed. - properties.append(Symbol::create(member.full_name(), scope, member, Symbol::IsLocal::No)); - } - return properties; -} - -CppComprehensionEngine::Symbol CppComprehensionEngine::Symbol::create(StringView name, Vector<StringView> const& scope, NonnullRefPtr<Declaration> declaration, IsLocal is_local) -{ - return { { name, scope }, move(declaration), is_local == IsLocal::Yes }; -} - -Vector<CppComprehensionEngine::Symbol> CppComprehensionEngine::get_child_symbols(ASTNode const& node) const -{ - return get_child_symbols(node, {}, Symbol::IsLocal::No); -} - -Vector<CppComprehensionEngine::Symbol> CppComprehensionEngine::get_child_symbols(ASTNode const& node, Vector<StringView> const& scope, Symbol::IsLocal is_local) const -{ - Vector<Symbol> symbols; - - for (auto& decl : node.declarations()) { - symbols.append(Symbol::create(decl.full_name(), scope, decl, is_local)); - - bool should_recurse = decl.is_namespace() || decl.is_struct_or_class() || decl.is_function(); - bool are_child_symbols_local = decl.is_function(); - - if (!should_recurse) - continue; - - auto new_scope = scope; - new_scope.append(decl.full_name()); - symbols.extend(get_child_symbols(decl, new_scope, are_child_symbols_local ? Symbol::IsLocal::Yes : is_local)); - } - - return symbols; -} - -String CppComprehensionEngine::document_path_from_include_path(StringView include_path) const -{ - static Regex<PosixExtended> library_include("<(.+)>"); - static Regex<PosixExtended> user_defined_include("\"(.+)\""); - - auto document_path_for_library_include = [&](StringView include_path) -> String { - RegexResult result; - if (!library_include.search(include_path, result)) - return {}; - - auto path = result.capture_group_matches.at(0).at(0).view.string_view(); - return String::formatted("/usr/include/{}", path); - }; - - auto document_path_for_user_defined_include = [&](StringView include_path) -> String { - RegexResult result; - if (!user_defined_include.search(include_path, result)) - return {}; - - return result.capture_group_matches.at(0).at(0).view.string_view(); - }; - - auto result = document_path_for_library_include(include_path); - if (result.is_null()) - result = document_path_for_user_defined_include(include_path); - - return result; -} - -void CppComprehensionEngine::on_edit(String const& file) -{ - set_document_data(file, create_document_data_for(file)); -} - -void CppComprehensionEngine::file_opened([[maybe_unused]] String const& file) -{ - get_or_create_document_data(file); -} - -Optional<GUI::AutocompleteProvider::ProjectLocation> CppComprehensionEngine::find_declaration_of(String const& filename, const GUI::TextPosition& identifier_position) -{ - auto const* document_ptr = get_or_create_document_data(filename); - if (!document_ptr) - return {}; - - auto const& document = *document_ptr; - auto decl = find_declaration_of(document, identifier_position); - if (decl) { - return GUI::AutocompleteProvider::ProjectLocation { decl->filename(), decl->start().line, decl->start().column }; - } - - return find_preprocessor_definition(document, identifier_position); -} - -RefPtr<Declaration> CppComprehensionEngine::find_declaration_of(DocumentData const& document, const GUI::TextPosition& identifier_position) -{ - auto node = document.parser().node_at(Cpp::Position { identifier_position.line(), identifier_position.column() }); - if (!node) { - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", identifier_position.line(), identifier_position.column()); - return {}; - } - return find_declaration_of(document, *node); -} - -Optional<GUI::AutocompleteProvider::ProjectLocation> CppComprehensionEngine::find_preprocessor_definition(DocumentData const& document, const GUI::TextPosition& text_position) -{ - Position cpp_position { text_position.line(), text_position.column() }; - auto substitution = find_preprocessor_substitution(document, cpp_position); - if (!substitution.has_value()) - return {}; - return GUI::AutocompleteProvider::ProjectLocation { substitution->defined_value.filename, substitution->defined_value.line, substitution->defined_value.column }; -} - -Optional<Cpp::Preprocessor::Substitution> CppComprehensionEngine::find_preprocessor_substitution(DocumentData const& document, Cpp::Position const& cpp_position) -{ - // Search for a replaced preprocessor token that intersects with text_position - for (auto& substitution : document.preprocessor().substitutions()) { - if (substitution.original_tokens.first().start() > cpp_position) - continue; - if (substitution.original_tokens.first().end() < cpp_position) - continue; - return substitution; - } - return {}; -} - -struct TargetDeclaration { - enum Type { - Variable, - Type, - Function, - Property, - Scope - } type; - String name; -}; - -static Optional<TargetDeclaration> get_target_declaration(ASTNode const& node, String name); -static Optional<TargetDeclaration> get_target_declaration(ASTNode const& node) -{ - if (node.is_identifier()) { - return get_target_declaration(node, static_cast<Identifier const&>(node).name()); - } - - if (node.is_declaration()) { - return get_target_declaration(node, verify_cast<Declaration>(node).full_name()); - } - - if (node.is_type() && node.parent() && node.parent()->is_declaration()) { - return get_target_declaration(*node.parent(), verify_cast<Declaration>(node.parent())->full_name()); - } - - dbgln("get_target_declaration: Invalid argument node of type: {}", node.class_name()); - return {}; -} - -static Optional<TargetDeclaration> get_target_declaration(ASTNode const& node, String name) -{ - if (node.parent() && node.parent()->is_name()) { - auto& name_node = *verify_cast<Name>(node.parent()); - if (&node != name_node.name()) { - // Node is part of scope reference chain - return TargetDeclaration { TargetDeclaration::Type::Scope, name }; - } - if (name_node.parent() && name_node.parent()->is_declaration()) { - auto declaration = verify_cast<Declaration>(name_node.parent()); - if (declaration->is_struct_or_class() || declaration->is_enum()) { - return TargetDeclaration { TargetDeclaration::Type::Type, name }; - } - if (declaration->is_function()) { - return TargetDeclaration { TargetDeclaration::Type::Function, name }; - } - } - } - - if ((node.parent() && node.parent()->is_function_call()) || (node.parent()->is_name() && node.parent()->parent() && node.parent()->parent()->is_function_call())) { - return TargetDeclaration { TargetDeclaration::Type::Function, name }; - } - - if ((node.parent() && node.parent()->is_type()) || (node.parent()->is_name() && node.parent()->parent() && node.parent()->parent()->is_type())) - return TargetDeclaration { TargetDeclaration::Type::Type, name }; - - if ((node.parent() && node.parent()->is_member_expression())) - return TargetDeclaration { TargetDeclaration::Type::Property, name }; - - return TargetDeclaration { TargetDeclaration::Type::Variable, name }; -} -RefPtr<Declaration> CppComprehensionEngine::find_declaration_of(DocumentData const& document_data, ASTNode const& node) const -{ - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "find_declaration_of: {} ({})", document_data.parser().text_of_node(node), node.class_name()); - - auto target_decl = get_target_declaration(node); - if (!target_decl.has_value()) - return {}; - - auto reference_scope = scope_of_reference_to_symbol(node); - auto current_scope = scope_of_node(node); - - auto symbol_matches = [&](Symbol const& symbol) { - bool match_function = target_decl.value().type == TargetDeclaration::Function && symbol.declaration->is_function(); - bool match_variable = target_decl.value().type == TargetDeclaration::Variable && symbol.declaration->is_variable_declaration(); - bool match_type = target_decl.value().type == TargetDeclaration::Type && (symbol.declaration->is_struct_or_class() || symbol.declaration->is_enum()); - bool match_property = target_decl.value().type == TargetDeclaration::Property && symbol.declaration->parent()->is_declaration() && verify_cast<Declaration>(symbol.declaration->parent())->is_struct_or_class(); - bool match_parameter = target_decl.value().type == TargetDeclaration::Variable && symbol.declaration->is_parameter(); - bool match_scope = target_decl.value().type == TargetDeclaration::Scope && (symbol.declaration->is_namespace() || symbol.declaration->is_struct_or_class()); - - if (match_property) { - // FIXME: This is not really correct, we also need to check that the type of the struct/class matches (not just the property name) - if (symbol.name.name == target_decl.value().name) { - return true; - } - } - - if (!is_symbol_available(symbol, current_scope, reference_scope)) { - return false; - } - - if (match_function || match_type || match_scope) { - if (symbol.name.name == target_decl->name) - return true; - } - - if (match_variable || match_parameter) { - // If this symbol was declared below us in a function, it's not available to us. - bool is_unavailable = symbol.is_local && symbol.declaration->start().line > node.start().line; - - if (!is_unavailable && (symbol.name.name == target_decl->name)) { - return true; - } - } - - return false; - }; - - Optional<Symbol> match; - - for_each_available_symbol(document_data, [&](Symbol const& symbol) { - if (symbol_matches(symbol)) { - match = symbol; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - - if (!match.has_value()) - return {}; - - return match->declaration; -} - -void CppComprehensionEngine::update_declared_symbols(DocumentData& document) -{ - for (auto& symbol : get_child_symbols(*document.parser().root_node())) { - document.m_symbols.set(symbol.name, move(symbol)); - } - - Vector<GUI::AutocompleteProvider::Declaration> declarations; - for (auto& symbol_entry : document.m_symbols) { - auto& symbol = symbol_entry.value; - declarations.append({ symbol.name.name, { document.filename(), symbol.declaration->start().line, symbol.declaration->start().column }, type_of_declaration(symbol.declaration), symbol.name.scope_as_string() }); - } - - for (auto& definition : document.preprocessor().definitions()) { - declarations.append({ definition.key, { document.filename(), definition.value.line, definition.value.column }, GUI::AutocompleteProvider::DeclarationType::PreprocessorDefinition, {} }); - } - set_declarations_of_document(document.filename(), move(declarations)); -} - -void CppComprehensionEngine::update_todo_entries(DocumentData& document) -{ - set_todo_entries_of_document(document.filename(), document.parser().get_todo_entries()); -} - -GUI::AutocompleteProvider::DeclarationType CppComprehensionEngine::type_of_declaration(Declaration const& decl) -{ - if (decl.is_struct()) - return GUI::AutocompleteProvider::DeclarationType::Struct; - if (decl.is_class()) - return GUI::AutocompleteProvider::DeclarationType::Class; - if (decl.is_function()) - return GUI::AutocompleteProvider::DeclarationType::Function; - if (decl.is_variable_declaration()) - return GUI::AutocompleteProvider::DeclarationType::Variable; - if (decl.is_namespace()) - return GUI::AutocompleteProvider::DeclarationType::Namespace; - if (decl.is_member()) - return GUI::AutocompleteProvider::DeclarationType::Member; - return GUI::AutocompleteProvider::DeclarationType::Variable; -} - -OwnPtr<CppComprehensionEngine::DocumentData> CppComprehensionEngine::create_document_data(String&& text, String const& filename) -{ - auto document_data = make<DocumentData>(); - document_data->m_filename = filename; - document_data->m_text = move(text); - document_data->m_preprocessor = make<Preprocessor>(document_data->m_filename, document_data->text()); - document_data->preprocessor().set_ignore_unsupported_keywords(true); - document_data->preprocessor().set_ignore_invalid_statements(true); - document_data->preprocessor().set_keep_include_statements(true); - - document_data->preprocessor().definitions_in_header_callback = [this](StringView include_path) -> Preprocessor::Definitions { - auto included_document = get_or_create_document_data(document_path_from_include_path(include_path)); - if (!included_document) - return {}; - - return included_document->preprocessor().definitions(); - }; - - auto tokens = document_data->preprocessor().process_and_lex(); - - for (auto include_path : document_data->preprocessor().included_paths()) { - auto include_fullpath = document_path_from_include_path(include_path); - auto included_document = get_or_create_document_data(include_fullpath); - if (!included_document) - continue; - - document_data->m_available_headers.set(include_fullpath); - - for (auto& header : included_document->m_available_headers) - document_data->m_available_headers.set(header); - } - - document_data->m_parser = make<Parser>(move(tokens), filename); - - auto root = document_data->parser().parse(); - - if constexpr (CPP_LANGUAGE_SERVER_DEBUG) - root->dump(); - - update_declared_symbols(*document_data); - update_todo_entries(*document_data); - - return document_data; -} - -Vector<StringView> CppComprehensionEngine::scope_of_node(ASTNode const& node) const -{ - - auto parent = node.parent(); - if (!parent) - return {}; - - auto parent_scope = scope_of_node(*parent); - - if (!parent->is_declaration()) - return parent_scope; - - auto& parent_decl = static_cast<Declaration&>(*parent); - - StringView containing_scope; - if (parent_decl.is_namespace()) - containing_scope = static_cast<NamespaceDeclaration&>(parent_decl).full_name(); - if (parent_decl.is_struct_or_class()) - containing_scope = static_cast<StructOrClassDeclaration&>(parent_decl).full_name(); - if (parent_decl.is_function()) - containing_scope = static_cast<FunctionDeclaration&>(parent_decl).full_name(); - - parent_scope.append(containing_scope); - return parent_scope; -} - -Optional<Vector<GUI::AutocompleteProvider::Entry>> CppComprehensionEngine::try_autocomplete_include(DocumentData const&, Token include_path_token, Cpp::Position const& cursor_position) const -{ - VERIFY(include_path_token.type() == Token::Type::IncludePath); - auto partial_include = include_path_token.text().trim_whitespace(); - - enum IncludeType { - Project, - System, - } include_type { Project }; - - String include_root; - bool already_has_suffix = false; - if (partial_include.starts_with("<")) { - include_root = "/usr/include/"; - include_type = System; - if (partial_include.ends_with(">")) { - already_has_suffix = true; - partial_include = partial_include.substring_view(0, partial_include.length() - 1).trim_whitespace(); - } - } else if (partial_include.starts_with("\"")) { - include_root = filedb().project_root(); - if (partial_include.length() > 1 && partial_include.ends_with("\"")) { - already_has_suffix = true; - partial_include = partial_include.substring_view(0, partial_include.length() - 1).trim_whitespace(); - } - } else - return {}; - - // The cursor is past the end of the <> or "", and so should not trigger autocomplete. - if (already_has_suffix && include_path_token.end() <= cursor_position) - return {}; - - auto last_slash = partial_include.find_last('/'); - auto include_dir = String::empty(); - auto partial_basename = partial_include.substring_view((last_slash.has_value() ? last_slash.value() : 0) + 1); - if (last_slash.has_value()) { - include_dir = partial_include.substring_view(1, last_slash.value()); - } - - auto full_dir = LexicalPath::join(include_root, include_dir).string(); - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "searching path: {}, partial_basename: {}", full_dir, partial_basename); - - Core::DirIterator it(full_dir, Core::DirIterator::Flags::SkipDots); - Vector<GUI::AutocompleteProvider::Entry> options; - - auto prefix = include_type == System ? "<" : "\""; - auto suffix = include_type == System ? ">" : "\""; - while (it.has_next()) { - auto path = it.next_path(); - - if (!path.starts_with(partial_basename)) - continue; - - if (Core::File::is_directory(LexicalPath::join(full_dir, path).string())) { - // FIXME: Don't dismiss the autocomplete when filling these suggestions. - auto completion = String::formatted("{}{}{}/", prefix, include_dir, path); - options.empend(completion, include_dir.length() + partial_basename.length() + 1, GUI::AutocompleteProvider::Language::Cpp, path, GUI::AutocompleteProvider::Entry::HideAutocompleteAfterApplying::No); - } else if (path.ends_with(".h")) { - // FIXME: Place the cursor after the trailing > or ", even if it was - // already typed. - auto completion = String::formatted("{}{}{}{}", prefix, include_dir, path, already_has_suffix ? "" : suffix); - options.empend(completion, include_dir.length() + partial_basename.length() + 1, GUI::AutocompleteProvider::Language::Cpp, path); - } - } - - return options; -} - -RefPtr<Declaration> CppComprehensionEngine::find_declaration_of(CppComprehensionEngine::DocumentData const& document, CppComprehensionEngine::SymbolName const& target_symbol_name) const -{ - RefPtr<Declaration> target_declaration; - for_each_available_symbol(document, [&](Symbol const& symbol) { - if (symbol.name == target_symbol_name) { - target_declaration = symbol.declaration; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - return target_declaration; -} - -String CppComprehensionEngine::SymbolName::scope_as_string() const -{ - if (scope.is_empty()) - return String::empty(); - - StringBuilder builder; - for (size_t i = 0; i < scope.size() - 1; ++i) { - builder.appendff("{}::", scope[i]); - } - builder.append(scope.last()); - return builder.to_string(); -} - -CppComprehensionEngine::SymbolName CppComprehensionEngine::SymbolName::create(StringView name, Vector<StringView>&& scope) -{ - return { name, move(scope) }; -} - -CppComprehensionEngine::SymbolName CppComprehensionEngine::SymbolName::create(StringView qualified_name) -{ - auto parts = qualified_name.split_view("::"); - VERIFY(!parts.is_empty()); - auto name = parts.take_last(); - return SymbolName::create(name, move(parts)); -} - -String CppComprehensionEngine::SymbolName::to_string() const -{ - if (scope.is_empty()) - return name; - return String::formatted("{}::{}", scope_as_string(), name); -} - -bool CppComprehensionEngine::is_symbol_available(Symbol const& symbol, Vector<StringView> const& current_scope, Vector<StringView> const& reference_scope) -{ - - if (!reference_scope.is_empty()) { - return reference_scope == symbol.name.scope; - } - - // FIXME: Take "using namespace ..." into consideration - - // Check if current_scope starts with symbol's scope - if (symbol.name.scope.size() > current_scope.size()) - return false; - - for (size_t i = 0; i < symbol.name.scope.size(); ++i) { - if (current_scope[i] != symbol.name.scope[i]) - return false; - } - - return true; -} - -Optional<CodeComprehensionEngine::FunctionParamsHint> CppComprehensionEngine::get_function_params_hint(String const& filename, const GUI::TextPosition& identifier_position) -{ - auto const* document_ptr = get_or_create_document_data(filename); - if (!document_ptr) - return {}; - - auto const& document = *document_ptr; - Cpp::Position cpp_position { identifier_position.line(), identifier_position.column() }; - auto node = document.parser().node_at(cpp_position); - if (!node) { - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", identifier_position.line(), identifier_position.column()); - return {}; - } - - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "node type: {}", node->class_name()); - - FunctionCall* call_node { nullptr }; - - if (node->is_function_call()) { - call_node = verify_cast<FunctionCall>(node.ptr()); - - auto token = document.parser().token_at(cpp_position); - - // If we're in a function call with 0 arguments - if (token.has_value() && (token->type() == Token::Type::LeftParen || token->type() == Token::Type::RightParen)) { - return get_function_params_hint(document, *call_node, call_node->arguments().is_empty() ? 0 : call_node->arguments().size() - 1); - } - } - - // Walk upwards in the AST to find a FunctionCall node - while (!call_node && node) { - auto parent_is_call = node->parent() && node->parent()->is_function_call(); - if (parent_is_call) { - call_node = verify_cast<FunctionCall>(node->parent()); - break; - } - node = node->parent(); - } - - if (!call_node) { - dbgln("did not find function call"); - return {}; - } - - Optional<size_t> invoked_arg_index; - for (size_t arg_index = 0; arg_index < call_node->arguments().size(); ++arg_index) { - if (&call_node->arguments()[arg_index] == node.ptr()) { - invoked_arg_index = arg_index; - break; - } - } - if (!invoked_arg_index.has_value()) { - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "could not find argument index, defaulting to the last argument"); - invoked_arg_index = call_node->arguments().is_empty() ? 0 : call_node->arguments().size() - 1; - } - - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "arg index: {}", invoked_arg_index.value()); - return get_function_params_hint(document, *call_node, invoked_arg_index.value()); -} - -Optional<CppComprehensionEngine::FunctionParamsHint> CppComprehensionEngine::get_function_params_hint( - DocumentData const& document, - FunctionCall& call_node, - size_t argument_index) -{ - Identifier const* callee = nullptr; - VERIFY(call_node.callee()); - if (call_node.callee()->is_identifier()) { - callee = verify_cast<Identifier>(call_node.callee()); - } else if (call_node.callee()->is_name()) { - callee = verify_cast<Name>(*call_node.callee()).name(); - } else if (call_node.callee()->is_member_expression()) { - auto& member_exp = verify_cast<MemberExpression>(*call_node.callee()); - VERIFY(member_exp.property()); - if (member_exp.property()->is_identifier()) { - callee = verify_cast<Identifier>(member_exp.property()); - } - } - - if (!callee) { - dbgln("unexpected node type for function call: {}", call_node.callee()->class_name()); - return {}; - } - VERIFY(callee); - - auto decl = find_declaration_of(document, *callee); - if (!decl) { - dbgln("func decl not found"); - return {}; - } - if (!decl->is_function()) { - dbgln("declaration is not a function"); - return {}; - } - - auto& func_decl = verify_cast<FunctionDeclaration>(*decl); - auto document_of_declaration = get_document_data(func_decl.filename()); - - FunctionParamsHint hint {}; - hint.current_index = argument_index; - for (auto& arg : func_decl.parameters()) { - Vector<StringView> tokens_text; - for (auto token : document_of_declaration->parser().tokens_in_range(arg.start(), arg.end())) { - tokens_text.append(token.text()); - } - hint.params.append(String::join(" ", tokens_text)); - } - - return hint; -} - -Vector<GUI::AutocompleteProvider::TokenInfo> CppComprehensionEngine::get_tokens_info(String const& filename) -{ - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "CppComprehensionEngine::get_tokens_info: {}", filename); - - auto const* document_ptr = get_or_create_document_data(filename); - if (!document_ptr) - return {}; - - auto const& document = *document_ptr; - - Vector<GUI::AutocompleteProvider::TokenInfo> tokens_info; - size_t i = 0; - for (auto const& token : document.preprocessor().unprocessed_tokens()) { - - tokens_info.append({ get_token_semantic_type(document, token), - token.start().line, token.start().column, token.end().line, token.end().column }); - ++i; - dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "{}: {}", token.text(), GUI::AutocompleteProvider::TokenInfo::type_to_string(tokens_info.last().type)); - } - return tokens_info; -} - -GUI::AutocompleteProvider::TokenInfo::SemanticType CppComprehensionEngine::get_token_semantic_type(DocumentData const& document, Token const& token) -{ - using GUI::AutocompleteProvider; - switch (token.type()) { - case Cpp::Token::Type::Identifier: - return get_semantic_type_for_identifier(document, token.start()); - case Cpp::Token::Type::Keyword: - return AutocompleteProvider::TokenInfo::SemanticType::Keyword; - case Cpp::Token::Type::KnownType: - return AutocompleteProvider::TokenInfo::SemanticType::Type; - case Cpp::Token::Type::DoubleQuotedString: - case Cpp::Token::Type::SingleQuotedString: - case Cpp::Token::Type::RawString: - return AutocompleteProvider::TokenInfo::SemanticType::String; - case Cpp::Token::Type::Integer: - case Cpp::Token::Type::Float: - return AutocompleteProvider::TokenInfo::SemanticType::Number; - case Cpp::Token::Type::IncludePath: - return AutocompleteProvider::TokenInfo::SemanticType::IncludePath; - case Cpp::Token::Type::EscapeSequence: - return AutocompleteProvider::TokenInfo::SemanticType::Keyword; - case Cpp::Token::Type::PreprocessorStatement: - case Cpp::Token::Type::IncludeStatement: - return AutocompleteProvider::TokenInfo::SemanticType::PreprocessorStatement; - case Cpp::Token::Type::Comment: - return AutocompleteProvider::TokenInfo::SemanticType::Comment; - default: - return AutocompleteProvider::TokenInfo::SemanticType::Unknown; - } -} - -GUI::AutocompleteProvider::TokenInfo::SemanticType CppComprehensionEngine::get_semantic_type_for_identifier(DocumentData const& document, Position position) -{ - if (find_preprocessor_substitution(document, position).has_value()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::PreprocessorMacro; - - auto decl = find_declaration_of(document, GUI::TextPosition { position.line, position.column }); - if (!decl) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Identifier; - - if (decl->is_function()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Function; - if (decl->is_parameter()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Parameter; - if (decl->is_variable_declaration()) { - if (decl->is_member()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Member; - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Variable; - } - if (decl->is_struct_or_class() || decl->is_enum()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::CustomType; - if (decl->is_namespace()) - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Namespace; - - return GUI::AutocompleteProvider::TokenInfo::SemanticType::Identifier; -} - -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h deleted file mode 100644 index 0473480d1d..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include <AK/Function.h> -#include <AK/String.h> -#include <AK/Vector.h> -#include <DevTools/HackStudio/AutoCompleteResponse.h> -#include <DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h> -#include <DevTools/HackStudio/LanguageServers/FileDB.h> -#include <LibCpp/AST.h> -#include <LibCpp/Parser.h> -#include <LibCpp/Preprocessor.h> -#include <LibGUI/TextPosition.h> - -namespace LanguageServers::Cpp { - -using namespace ::Cpp; - -class CppComprehensionEngine : public CodeComprehensionEngine { -public: - CppComprehensionEngine(FileDB const& filedb); - - virtual Vector<GUI::AutocompleteProvider::Entry> get_suggestions(String const& file, const GUI::TextPosition& autocomplete_position) override; - virtual void on_edit(String const& file) override; - virtual void file_opened([[maybe_unused]] String const& file) override; - virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(String const& filename, const GUI::TextPosition& identifier_position) override; - virtual Optional<FunctionParamsHint> get_function_params_hint(String const&, const GUI::TextPosition&) override; - virtual Vector<GUI::AutocompleteProvider::TokenInfo> get_tokens_info(String const& filename) override; - -private: - struct SymbolName { - StringView name; - Vector<StringView> scope; - - static SymbolName create(StringView, Vector<StringView>&&); - static SymbolName create(StringView); - String scope_as_string() const; - String to_string() const; - - bool operator==(SymbolName const&) const = default; - }; - - struct Symbol { - SymbolName name; - NonnullRefPtr<Declaration> declaration; - - // Local symbols are symbols that should not appear in a global symbol search. - // For example, a variable that is declared inside a function will have is_local = true. - bool is_local { false }; - - enum class IsLocal { - No, - Yes - }; - static Symbol create(StringView name, Vector<StringView> const& scope, NonnullRefPtr<Declaration>, IsLocal is_local); - }; - - friend Traits<SymbolName>; - - struct DocumentData { - String const& filename() const { return m_filename; } - String const& text() const { return m_text; } - Preprocessor const& preprocessor() const - { - VERIFY(m_preprocessor); - return *m_preprocessor; - } - Preprocessor& preprocessor() - { - VERIFY(m_preprocessor); - return *m_preprocessor; - } - Parser const& parser() const - { - VERIFY(m_parser); - return *m_parser; - } - Parser& parser() - { - VERIFY(m_parser); - return *m_parser; - } - - String m_filename; - String m_text; - OwnPtr<Preprocessor> m_preprocessor; - OwnPtr<Parser> m_parser; - - HashMap<SymbolName, Symbol> m_symbols; - HashTable<String> m_available_headers; - }; - - Vector<GUI::AutocompleteProvider::Entry> autocomplete_property(DocumentData const&, MemberExpression const&, const String partial_text) const; - Vector<GUI::AutocompleteProvider::Entry> autocomplete_name(DocumentData const&, ASTNode const&, String const& partial_text) const; - String type_of(DocumentData const&, Expression const&) const; - String type_of_property(DocumentData const&, Identifier const&) const; - String type_of_variable(Identifier const&) const; - bool is_property(ASTNode const&) const; - RefPtr<Declaration> find_declaration_of(DocumentData const&, ASTNode const&) const; - RefPtr<Declaration> find_declaration_of(DocumentData const&, SymbolName const&) const; - RefPtr<Declaration> find_declaration_of(DocumentData const&, const GUI::TextPosition& identifier_position); - - enum class RecurseIntoScopes { - No, - Yes - }; - - Vector<Symbol> properties_of_type(DocumentData const& document, String const& type) const; - Vector<Symbol> get_child_symbols(ASTNode const&) const; - Vector<Symbol> get_child_symbols(ASTNode const&, Vector<StringView> const& scope, Symbol::IsLocal) const; - - DocumentData const* get_document_data(String const& file) const; - DocumentData const* get_or_create_document_data(String const& file); - void set_document_data(String const& file, OwnPtr<DocumentData>&& data); - - OwnPtr<DocumentData> create_document_data_for(String const& file); - String document_path_from_include_path(StringView include_path) const; - void update_declared_symbols(DocumentData&); - void update_todo_entries(DocumentData&); - GUI::AutocompleteProvider::DeclarationType type_of_declaration(Declaration const&); - Vector<StringView> scope_of_node(ASTNode const&) const; - Vector<StringView> scope_of_reference_to_symbol(ASTNode const&) const; - - Optional<GUI::AutocompleteProvider::ProjectLocation> find_preprocessor_definition(DocumentData const&, const GUI::TextPosition&); - Optional<Cpp::Preprocessor::Substitution> find_preprocessor_substitution(DocumentData const&, Cpp::Position const&); - - OwnPtr<DocumentData> create_document_data(String&& text, String const& filename); - Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_property(DocumentData const&, ASTNode const&, Optional<Token> containing_token) const; - Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_name(DocumentData const&, ASTNode const&, Optional<Token> containing_token) const; - Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_include(DocumentData const&, Token include_path_token, Cpp::Position const& cursor_position) const; - static bool is_symbol_available(Symbol const&, Vector<StringView> const& current_scope, Vector<StringView> const& reference_scope); - Optional<FunctionParamsHint> get_function_params_hint(DocumentData const&, FunctionCall&, size_t argument_index); - - template<typename Func> - void for_each_available_symbol(DocumentData const&, Func) const; - - template<typename Func> - void for_each_included_document_recursive(DocumentData const&, Func) const; - - GUI::AutocompleteProvider::TokenInfo::SemanticType get_token_semantic_type(DocumentData const&, Token const&); - GUI::AutocompleteProvider::TokenInfo::SemanticType get_semantic_type_for_identifier(DocumentData const&, Position); - - HashMap<String, OwnPtr<DocumentData>> m_documents; - - // A document's path will be in this set if we're currently processing it. - // A document is added to this set when we start processing it (e.g because it was #included) and removed when we're done. - // We use this to prevent circular #includes from looping indefinitely. - HashTable<String> m_unfinished_documents; -}; - -template<typename Func> -void CppComprehensionEngine::for_each_available_symbol(DocumentData const& document, Func func) const -{ - for (auto& item : document.m_symbols) { - auto decision = func(item.value); - if (decision == IterationDecision::Break) - return; - } - - for_each_included_document_recursive(document, [&](DocumentData const& document) { - for (auto& item : document.m_symbols) { - auto decision = func(item.value); - if (decision == IterationDecision::Break) - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); -} - -template<typename Func> -void CppComprehensionEngine::for_each_included_document_recursive(DocumentData const& document, Func func) const -{ - for (auto& included_path : document.m_available_headers) { - auto* included_document = get_document_data(included_path); - if (!included_document) - continue; - auto decision = func(*included_document); - if (decision == IterationDecision::Break) - continue; - } -} -} - -namespace AK { - -template<> -struct Traits<LanguageServers::Cpp::CppComprehensionEngine::SymbolName> : public GenericTraits<LanguageServers::Cpp::CppComprehensionEngine::SymbolName> { - static unsigned hash(LanguageServers::Cpp::CppComprehensionEngine::SymbolName const& key) - { - unsigned hash = 0; - hash = pair_int_hash(hash, string_hash(key.name.characters_without_null_termination(), key.name.length())); - for (auto& scope_part : key.scope) { - hash = pair_int_hash(hash, string_hash(scope_part.characters_without_null_termination(), scope_part.length())); - } - return hash; - } -}; - -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.cpp deleted file mode 100644 index 14dbd87944..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Tests.h" -#include "../FileDB.h" -#include "CppComprehensionEngine.h" -#include <AK/LexicalPath.h> -#include <LibCore/File.h> - -using namespace LanguageServers; -using namespace LanguageServers::Cpp; - -static bool s_some_test_failed = false; - -#define I_TEST(name) \ - { \ - printf("Testing " #name "... "); \ - fflush(stdout); \ - } - -#define PASS \ - do { \ - printf("PASS\n"); \ - fflush(stdout); \ - return; \ - } while (0) - -#define FAIL(reason) \ - do { \ - printf("FAIL: " #reason "\n"); \ - s_some_test_failed = true; \ - return; \ - } while (0) - -constexpr char TESTS_ROOT_DIR[] = "/home/anon/Tests/cpp-tests/comprehension"; - -static void test_complete_local_args(); -static void test_complete_local_vars(); -static void test_complete_type(); -static void test_find_variable_definition(); -static void test_complete_includes(); -static void test_parameters_hint(); - -int run_tests() -{ - test_complete_local_args(); - test_complete_local_vars(); - test_complete_type(); - test_find_variable_definition(); - test_complete_includes(); - test_parameters_hint(); - return s_some_test_failed ? 1 : 0; -} - -static void add_file(FileDB& filedb, String const& name) -{ - auto file = Core::File::open(LexicalPath::join(TESTS_ROOT_DIR, name).string(), Core::OpenMode::ReadOnly); - VERIFY(!file.is_error()); - filedb.add(name, file.value()->fd()); -} - -void test_complete_local_args() -{ - I_TEST(Complete Local Args) - FileDB filedb; - add_file(filedb, "complete_local_args.cpp"); - CppComprehensionEngine engine(filedb); - auto suggestions = engine.get_suggestions("complete_local_args.cpp", { 2, 6 }); - if (suggestions.size() != 2) - FAIL(bad size); - - if (suggestions[0].completion == "argc" && suggestions[1].completion == "argv") - PASS; - - FAIL("wrong results"); -} - -void test_complete_local_vars() -{ - I_TEST(Complete Local Vars) - FileDB filedb; - add_file(filedb, "complete_local_vars.cpp"); - CppComprehensionEngine autocomplete(filedb); - auto suggestions = autocomplete.get_suggestions("complete_local_vars.cpp", { 3, 7 }); - if (suggestions.size() != 1) - FAIL(bad size); - - if (suggestions[0].completion == "myvar1") - PASS; - - FAIL("wrong results"); -} - -void test_complete_type() -{ - I_TEST(Complete Type) - FileDB filedb; - add_file(filedb, "complete_type.cpp"); - CppComprehensionEngine autocomplete(filedb); - auto suggestions = autocomplete.get_suggestions("complete_type.cpp", { 5, 7 }); - if (suggestions.size() != 1) - FAIL(bad size); - - if (suggestions[0].completion == "MyStruct") - PASS; - - FAIL("wrong results"); -} - -void test_find_variable_definition() -{ - I_TEST(Find Variable Declaration) - FileDB filedb; - add_file(filedb, "find_variable_declaration.cpp"); - CppComprehensionEngine engine(filedb); - auto position = engine.find_declaration_of("find_variable_declaration.cpp", { 2, 5 }); - if (!position.has_value()) - FAIL("declaration not found"); - - if (position.value().file == "find_variable_declaration.cpp" && position.value().line == 0 && position.value().column >= 19) - PASS; - FAIL("wrong declaration location"); -} - -void test_complete_includes() -{ - I_TEST(Complete Type) - FileDB filedb; - filedb.set_project_root(TESTS_ROOT_DIR); - add_file(filedb, "complete_includes.cpp"); - add_file(filedb, "sample_header.h"); - CppComprehensionEngine autocomplete(filedb); - - auto suggestions = autocomplete.get_suggestions("complete_includes.cpp", { 0, 22 }); - if (suggestions.size() != 1) - FAIL(project include - bad size); - - if (suggestions[0].completion != "\"sample_header.h\"") - FAIL("project include - wrong results"); - - suggestions = autocomplete.get_suggestions("complete_includes.cpp", { 1, 18 }); - if (suggestions.size() != 1) - FAIL(global include - bad size); - - if (suggestions[0].completion != "<sys/cdefs.h>") - FAIL("global include - wrong results"); - - PASS; -} - -void test_parameters_hint() -{ - I_TEST(Function Parameters hint) - FileDB filedb; - filedb.set_project_root(TESTS_ROOT_DIR); - add_file(filedb, "parameters_hint1.cpp"); - CppComprehensionEngine engine(filedb); - - auto result = engine.get_function_params_hint("parameters_hint1.cpp", { 4, 9 }); - if (!result.has_value()) - FAIL("failed to get parameters hint (1)"); - if (result->params != Vector<String> { "int x", "char y" } || result->current_index != 0) - FAIL("bad result (1)"); - - result = engine.get_function_params_hint("parameters_hint1.cpp", { 5, 15 }); - if (!result.has_value()) - FAIL("failed to get parameters hint (2)"); - if (result->params != Vector<String> { "int x", "char y" } || result->current_index != 1) - FAIL("bad result (2)"); - - result = engine.get_function_params_hint("parameters_hint1.cpp", { 6, 8 }); - if (!result.has_value()) - FAIL("failed to get parameters hint (3)"); - if (result->params != Vector<String> { "int x", "char y" } || result->current_index != 0) - FAIL("bad result (3)"); - - PASS; -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.h deleted file mode 100644 index 0c98313e9a..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -int run_tests(); diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_includes.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_includes.cpp deleted file mode 100644 index 3eb239f29f..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_includes.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "sample_heade -#include <sys/cdef - -void foo() {} - diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_args.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_args.cpp deleted file mode 100644 index 893bed6c54..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_args.cpp +++ /dev/null @@ -1,4 +0,0 @@ -int main(int argc, char** argv) -{ - ar -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_vars.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_vars.cpp deleted file mode 100644 index 02f53b5631..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_local_vars.cpp +++ /dev/null @@ -1,5 +0,0 @@ -int main(int argc, char** argv) -{ - int myvar1 = 3; - myv -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_type.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_type.cpp deleted file mode 100644 index a552ad8cbf..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/complete_type.cpp +++ /dev/null @@ -1,7 +0,0 @@ -struct MyStruct { - int x; -}; -void foo() -{ - MyS -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/find_variable_declaration.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/find_variable_declaration.cpp deleted file mode 100644 index 22f20d2f34..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/find_variable_declaration.cpp +++ /dev/null @@ -1,4 +0,0 @@ -int main(int argc, char** argv) -{ - argv = nullptr; -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/parameters_hint1.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/parameters_hint1.cpp deleted file mode 100644 index 43fc7c5e32..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/parameters_hint1.cpp +++ /dev/null @@ -1,8 +0,0 @@ -void foo(int x, char y); - -void bar() -{ - foo(); - foo(123, 'b'); - foo( -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/sample_header.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/sample_header.h deleted file mode 100644 index ab2154484e..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests/sample_header.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -int bar(); diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp index 4978a22eb9..b8511d0117 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp @@ -5,7 +5,6 @@ */ #include "ConnectionFromClient.h" -#include "Tests.h" #include <LibCore/ArgsParser.h> #include <LibCore/EventLoop.h> #include <LibCore/LocalServer.h> @@ -13,23 +12,7 @@ #include <LibIPC/SingleServer.h> #include <LibMain/Main.h> -static ErrorOr<int> mode_server(); - -ErrorOr<int> serenity_main(Main::Arguments arguments) -{ - bool tests = false; - - Core::ArgsParser parser; - parser.add_option(tests, "Run tests", "tests", 't'); - parser.parse(arguments); - - if (tests) - return run_tests(); - - return mode_server(); -} - -ErrorOr<int> mode_server() +ErrorOr<int> serenity_main(Main::Arguments) { Core::EventLoop event_loop; TRY(Core::System::pledge("stdio unix recvfd rpath")); diff --git a/Userland/DevTools/HackStudio/LanguageServers/FileDB.cpp b/Userland/DevTools/HackStudio/LanguageServers/FileDB.cpp index a45882afbe..f704407368 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/FileDB.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/FileDB.cpp @@ -12,7 +12,7 @@ namespace LanguageServers { -RefPtr<const GUI::TextDocument> FileDB::get(String const& filename) const +RefPtr<const GUI::TextDocument> FileDB::get_document(String const& filename) const { auto absolute_path = to_absolute_path(filename); auto document_optional = m_open_files.get(absolute_path); @@ -22,29 +22,25 @@ RefPtr<const GUI::TextDocument> FileDB::get(String const& filename) const return *document_optional.value(); } -RefPtr<GUI::TextDocument> FileDB::get(String const& filename) +RefPtr<GUI::TextDocument> FileDB::get_document(String const& filename) { - auto document = reinterpret_cast<FileDB const*>(this)->get(filename); + auto document = reinterpret_cast<FileDB const*>(this)->get_document(filename); if (document.is_null()) return nullptr; return adopt_ref(*const_cast<GUI::TextDocument*>(document.leak_ref())); } -RefPtr<const GUI::TextDocument> FileDB::get_or_create_from_filesystem(String const& filename) const +Optional<String> FileDB::get_or_read_from_filesystem(StringView filename) const { auto absolute_path = to_absolute_path(filename); - auto document = get(absolute_path); + auto document = get_document(absolute_path); if (document) - return document; - return create_from_filesystem(absolute_path); -} + return document->text(); -RefPtr<GUI::TextDocument> FileDB::get_or_create_from_filesystem(String const& filename) -{ - auto document = reinterpret_cast<FileDB const*>(this)->get_or_create_from_filesystem(filename); - if (document.is_null()) - return nullptr; - return adopt_ref(*const_cast<GUI::TextDocument*>(document.leak_ref())); + document = create_from_filesystem(absolute_path); + if (document) + return document->text(); + return {}; } bool FileDB::is_open(String const& filename) const @@ -123,7 +119,7 @@ RefPtr<GUI::TextDocument> FileDB::create_from_file(Core::File& file) const void FileDB::on_file_edit_insert_text(String const& filename, String const& inserted_text, size_t start_line, size_t start_column) { VERIFY(is_open(filename)); - auto document = get(filename); + auto document = get_document(filename); VERIFY(document); GUI::TextPosition start_position { start_line, start_column }; document->insert_at(start_position, inserted_text, &s_default_document_client); @@ -136,7 +132,7 @@ void FileDB::on_file_edit_remove_text(String const& filename, size_t start_line, // TODO: If file is not open - need to get its contents // Otherwise- somehow verify that respawned language server is synced with all file contents VERIFY(is_open(filename)); - auto document = get(filename); + auto document = get_document(filename); VERIFY(document); GUI::TextPosition start_position { start_line, start_column }; GUI::TextRange range { diff --git a/Userland/DevTools/HackStudio/LanguageServers/FileDB.h b/Userland/DevTools/HackStudio/LanguageServers/FileDB.h index c8d64b2182..b25bd0b7b5 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/FileDB.h +++ b/Userland/DevTools/HackStudio/LanguageServers/FileDB.h @@ -9,22 +9,22 @@ #include <AK/HashMap.h> #include <AK/NonnullRefPtr.h> #include <AK/String.h> +#include <LibCodeComprehension/FileDB.h> #include <LibGUI/TextDocument.h> namespace LanguageServers { -class FileDB final { +class FileDB final : public CodeComprehension::FileDB { public: - RefPtr<const GUI::TextDocument> get(String const& filename) const; - RefPtr<GUI::TextDocument> get(String const& filename); - RefPtr<const GUI::TextDocument> get_or_create_from_filesystem(String const& filename) const; - RefPtr<GUI::TextDocument> get_or_create_from_filesystem(String const& filename); + FileDB() = default; + virtual Optional<String> get_or_read_from_filesystem(StringView filename) const override; + + RefPtr<const GUI::TextDocument> get_document(String const& filename) const; + RefPtr<GUI::TextDocument> get_document(String const& filename); + bool add(String const& filename, int fd); bool add(String const& filename, String const& content); - void set_project_root(String const& root_path) { m_project_root = root_path; } - String const& project_root() const { return m_project_root; } - void on_file_edit_insert_text(String const& filename, String const& inserted_text, size_t start_line, size_t start_column); void on_file_edit_remove_text(String const& filename, size_t start_line, size_t start_column, size_t end_line, size_t end_column); String to_absolute_path(String const& filename) const; diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc index 97d72ee0b3..e4088ad3c4 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc @@ -1,9 +1,9 @@ endpoint LanguageClient { - auto_complete_suggestions(Vector<GUI::AutocompleteProvider::Entry> suggestions) =| - declaration_location(GUI::AutocompleteProvider::ProjectLocation location) =| - declarations_in_document(String filename, Vector<GUI::AutocompleteProvider::Declaration> declarations) =| - todo_entries_in_document(String filename, Vector<Cpp::Parser::TodoEntry> todo_entries) =| + auto_complete_suggestions(Vector<CodeComprehension::AutocompleteResultEntry> suggestions) =| + declaration_location(CodeComprehension::ProjectLocation location) =| + declarations_in_document(String filename, Vector<CodeComprehension::Declaration> declarations) =| + todo_entries_in_document(String filename, Vector<CodeComprehension::TodoEntry> todo_entries) =| parameters_hint_result(Vector<String> params, int current_index) =| - tokens_info_result(Vector<GUI::AutocompleteProvider::TokenInfo> tokens_info) =| + tokens_info_result(Vector<CodeComprehension::TokenInfo> tokens_info) =| } diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc index 7cdcd4e635..1f97dbc97d 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc @@ -7,9 +7,9 @@ endpoint LanguageServer file_edit_remove_text(String filename, i32 start_line, i32 start_column, i32 end_line, i32 end_column) =| set_file_content(String filename, String content) =| - auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation location) =| - find_declaration(GUI::AutocompleteProvider::ProjectLocation location) =| - get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation location) =| + auto_complete_suggestions(CodeComprehension::ProjectLocation location) =| + find_declaration(CodeComprehension::ProjectLocation location) =| + get_parameters_hint(CodeComprehension::ProjectLocation location) =| get_tokens_info(String filename) =| } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt index 246f7e42d4..3fac0d165e 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt @@ -4,7 +4,6 @@ serenity_component( ) set(SOURCES - ShellComprehensionEngine.cpp main.cpp ) @@ -16,4 +15,4 @@ serenity_bin(ShellLanguageServer) # We link with LibGUI because we use GUI::TextDocument to update # the content of files according to the edit actions we receive over IPC. -target_link_libraries(ShellLanguageServer LibIPC LibShell LibGUI LibLanguageServer LibMain) +target_link_libraries(ShellLanguageServer LibIPC LibShell LibGUI LibLanguageServer LibShellComprehension LibMain) diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ConnectionFromClient.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/ConnectionFromClient.h index 7e5ab2682d..58864ddd97 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/ConnectionFromClient.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/ConnectionFromClient.h @@ -6,8 +6,8 @@ #pragma once -#include "ShellComprehensionEngine.h" #include <DevTools/HackStudio/LanguageServers/ConnectionFromClient.h> +#include <LibCodeComprehension/Shell/ShellComprehensionEngine.h> #include <LibCpp/Parser.h> namespace LanguageServers::Shell { @@ -19,11 +19,11 @@ private: ConnectionFromClient(NonnullOwnPtr<Core::Stream::LocalSocket> socket) : LanguageServers::ConnectionFromClient(move(socket)) { - m_autocomplete_engine = make<ShellComprehensionEngine>(m_filedb); - m_autocomplete_engine->set_declarations_of_document_callback = [this](String const& filename, Vector<GUI::AutocompleteProvider::Declaration>&& declarations) { + m_autocomplete_engine = make<CodeComprehension::Shell::ShellComprehensionEngine>(m_filedb); + m_autocomplete_engine->set_declarations_of_document_callback = [this](String const& filename, Vector<CodeComprehension::Declaration>&& declarations) { async_declarations_in_document(filename, move(declarations)); }; - m_autocomplete_engine->set_todo_entries_of_document_callback = [this](String const& filename, Vector<Cpp::Parser::TodoEntry>&& todo_entries) { + m_autocomplete_engine->set_todo_entries_of_document_callback = [this](String const& filename, Vector<CodeComprehension::TodoEntry>&& todo_entries) { async_todo_entries_in_document(filename, move(todo_entries)); }; } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.cpp b/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.cpp deleted file mode 100644 index 090ceaaa3d..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ShellComprehensionEngine.h" -#include <AK/Assertions.h> -#include <AK/HashTable.h> -#include <LibRegex/Regex.h> -#include <Userland/DevTools/HackStudio/LanguageServers/ConnectionFromClient.h> - -namespace LanguageServers::Shell { - -RefPtr<::Shell::Shell> ShellComprehensionEngine::s_shell {}; - -ShellComprehensionEngine::ShellComprehensionEngine(FileDB const& filedb) - : CodeComprehensionEngine(filedb, true) -{ -} - -ShellComprehensionEngine::DocumentData const& ShellComprehensionEngine::get_or_create_document_data(String const& file) -{ - auto absolute_path = filedb().to_absolute_path(file); - if (!m_documents.contains(absolute_path)) { - set_document_data(absolute_path, create_document_data_for(absolute_path)); - } - return get_document_data(absolute_path); -} - -ShellComprehensionEngine::DocumentData const& ShellComprehensionEngine::get_document_data(String const& file) const -{ - auto absolute_path = filedb().to_absolute_path(file); - auto document_data = m_documents.get(absolute_path); - VERIFY(document_data.has_value()); - return *document_data.value(); -} - -OwnPtr<ShellComprehensionEngine::DocumentData> ShellComprehensionEngine::create_document_data_for(String const& file) -{ - auto document = filedb().get(file); - if (!document) - return {}; - auto content = document->text(); - auto document_data = make<DocumentData>(document->text(), file); - for (auto& path : document_data->sourced_paths()) - get_or_create_document_data(path); - - update_declared_symbols(*document_data); - return document_data; -} - -void ShellComprehensionEngine::set_document_data(String const& file, OwnPtr<DocumentData>&& data) -{ - m_documents.set(filedb().to_absolute_path(file), move(data)); -} - -ShellComprehensionEngine::DocumentData::DocumentData(String&& _text, String _filename) - : filename(move(_filename)) - , text(move(_text)) - , node(parse()) -{ -} - -Vector<String> const& ShellComprehensionEngine::DocumentData::sourced_paths() const -{ - if (all_sourced_paths.has_value()) - return all_sourced_paths.value(); - - struct : public ::Shell::AST::NodeVisitor { - void visit(const ::Shell::AST::CastToCommand* node) override - { - auto& inner = node->inner(); - if (inner->is_list()) { - if (auto* list = dynamic_cast<const ::Shell::AST::ListConcatenate*>(inner.ptr())) { - auto& entries = list->list(); - if (entries.size() == 2 && entries.first()->is_bareword() && static_ptr_cast<::Shell::AST::BarewordLiteral>(entries.first())->text() == "source") { - auto& filename = entries[1]; - if (filename->would_execute()) - return; - auto name_list = const_cast<::Shell::AST::Node*>(filename.ptr())->run(nullptr)->resolve_as_list(nullptr); - StringBuilder builder; - builder.join(" ", name_list); - sourced_files.set(builder.build()); - } - } - } - ::Shell::AST::NodeVisitor::visit(node); - } - - HashTable<String> sourced_files; - } visitor; - - node->visit(visitor); - - Vector<String> sourced_paths; - for (auto& entry : visitor.sourced_files) - sourced_paths.append(move(entry)); - - all_sourced_paths = move(sourced_paths); - return all_sourced_paths.value(); -} - -NonnullRefPtr<::Shell::AST::Node> ShellComprehensionEngine::DocumentData::parse() const -{ - ::Shell::Parser parser { text }; - if (auto node = parser.parse()) - return node.release_nonnull(); - - return ::Shell::AST::make_ref_counted<::Shell::AST::SyntaxError>(::Shell::AST::Position {}, "Unable to parse file"); -} - -size_t ShellComprehensionEngine::resolve(ShellComprehensionEngine::DocumentData const& document, const GUI::TextPosition& position) -{ - size_t offset = 0; - - if (position.line() > 0) { - auto first = true; - size_t line = 0; - for (auto& line_view : document.text.split_limit('\n', position.line() + 1, true)) { - if (line == position.line()) - break; - if (first) - first = false; - else - ++offset; // For the newline. - offset += line_view.length(); - ++line; - } - } - - offset += position.column() + 1; - return offset; -} - -Vector<GUI::AutocompleteProvider::Entry> ShellComprehensionEngine::get_suggestions(String const& file, const GUI::TextPosition& position) -{ - dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "ShellComprehensionEngine position {}:{}", position.line(), position.column()); - - auto const& document = get_or_create_document_data(file); - size_t offset_in_file = resolve(document, position); - - ::Shell::AST::HitTestResult hit_test = document.node->hit_test_position(offset_in_file); - if (!hit_test.matching_node) { - dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", position.line(), position.column()); - return {}; - } - - auto completions = const_cast<::Shell::AST::Node*>(document.node.ptr())->complete_for_editor(shell(), offset_in_file, hit_test); - Vector<GUI::AutocompleteProvider::Entry> entries; - for (auto& completion : completions) - entries.append({ completion.text_string, completion.input_offset }); - - return entries; -} - -void ShellComprehensionEngine::on_edit(String const& file) -{ - set_document_data(file, create_document_data_for(file)); -} - -void ShellComprehensionEngine::file_opened([[maybe_unused]] String const& file) -{ - set_document_data(file, create_document_data_for(file)); -} - -Optional<GUI::AutocompleteProvider::ProjectLocation> ShellComprehensionEngine::find_declaration_of(String const& filename, const GUI::TextPosition& identifier_position) -{ - dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "find_declaration_of({}, {}:{})", filename, identifier_position.line(), identifier_position.column()); - auto const& document = get_or_create_document_data(filename); - auto position = resolve(document, identifier_position); - auto result = document.node->hit_test_position(position); - if (!result.matching_node) { - dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", identifier_position.line(), identifier_position.column()); - return {}; - } - - if (!result.matching_node->is_bareword()) { - dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no bareword at position {}:{}", identifier_position.line(), identifier_position.column()); - return {}; - } - - auto name = static_ptr_cast<::Shell::AST::BarewordLiteral>(result.matching_node)->text(); - auto& declarations = all_declarations(); - for (auto& entry : declarations) { - for (auto& declaration : entry.value) { - if (declaration.name == name) - return declaration.position; - } - } - - return {}; -} - -void ShellComprehensionEngine::update_declared_symbols(DocumentData const& document) -{ - struct Visitor : public ::Shell::AST::NodeVisitor { - explicit Visitor(String const& filename) - : filename(filename) - { - } - - void visit(const ::Shell::AST::VariableDeclarations* node) override - { - for (auto& entry : node->variables()) { - auto literal = entry.name->leftmost_trivial_literal(); - if (!literal) - continue; - - String name; - if (literal->is_bareword()) - name = static_ptr_cast<::Shell::AST::BarewordLiteral>(literal)->text(); - - if (!name.is_empty()) { - dbgln("Found variable {}", name); - declarations.append({ move(name), { filename, entry.name->position().start_line.line_number, entry.name->position().start_line.line_column }, GUI::AutocompleteProvider::DeclarationType::Variable, {} }); - } - } - ::Shell::AST::NodeVisitor::visit(node); - } - - void visit(const ::Shell::AST::FunctionDeclaration* node) override - { - dbgln("Found function {}", node->name().name); - declarations.append({ node->name().name, { filename, node->position().start_line.line_number, node->position().start_line.line_column }, GUI::AutocompleteProvider::DeclarationType::Function, {} }); - } - - String const& filename; - Vector<GUI::AutocompleteProvider::Declaration> declarations; - } visitor { document.filename }; - - document.node->visit(visitor); - - set_declarations_of_document(document.filename, move(visitor.declarations)); -} -} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.h deleted file mode 100644 index 774a7be45a..0000000000 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/ShellComprehensionEngine.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include <DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h> -#include <Shell/Shell.h> - -namespace LanguageServers::Shell { - -class ShellComprehensionEngine : public CodeComprehensionEngine { -public: - ShellComprehensionEngine(FileDB const& filedb); - virtual Vector<GUI::AutocompleteProvider::Entry> get_suggestions(String const& file, const GUI::TextPosition& position) override; - virtual void on_edit(String const& file) override; - virtual void file_opened([[maybe_unused]] String const& file) override; - virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(String const& filename, const GUI::TextPosition& identifier_position) override; - -private: - struct DocumentData { - DocumentData(String&& text, String filename); - String filename; - String text; - NonnullRefPtr<::Shell::AST::Node> node; - - Vector<String> const& sourced_paths() const; - - private: - NonnullRefPtr<::Shell::AST::Node> parse() const; - - mutable Optional<Vector<String>> all_sourced_paths {}; - }; - - DocumentData const& get_document_data(String const& file) const; - DocumentData const& get_or_create_document_data(String const& file); - void set_document_data(String const& file, OwnPtr<DocumentData>&& data); - - OwnPtr<DocumentData> create_document_data_for(String const& file); - String document_path_from_include_path(StringView include_path) const; - void update_declared_symbols(DocumentData const&); - - static size_t resolve(ShellComprehensionEngine::DocumentData const& document, const GUI::TextPosition& position); - - ::Shell::Shell& shell() - { - if (s_shell) - return *s_shell; - s_shell = ::Shell::Shell::construct(); - return *s_shell; - } - - HashMap<String, OwnPtr<DocumentData>> m_documents; - static RefPtr<::Shell::Shell> s_shell; -}; -} |