From c54238f65cefbbf1751c8f124baef6a546773e6b Mon Sep 17 00:00:00 2001 From: Itamar Date: Sat, 15 May 2021 22:13:32 +0300 Subject: [PATCH] CppLanguageServer: Make autocomplete logic consider scopes When returning autocomplete suggestions, we now consider the scope of the name that is being completed. For example, when requested to complete an expression like 'MyNamespace::', we will only suggest things that are in the 'MyNamespace' namespace. This commit also has some general refactoring of the autocomplete logic. --- .../Cpp/ParserAutoComplete.cpp | 111 ++++++++++++++---- .../LanguageServers/Cpp/ParserAutoComplete.h | 5 +- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp index 1b02938386..ee6e5bef4b 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp @@ -63,32 +63,56 @@ Vector ParserAutoComplete::get_suggestions(con return {}; const auto& document = *document_ptr; + auto containing_token = document.parser().token_at(position); 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->is_identifier()) { - if (is_property(*node)) { - return autocomplete_property(document, (MemberExpression&)(*node->parent()), document.parser().text_of_node(*node)); - } + 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()); - return autocomplete_name(document, *node, document.parser().text_of_node(*node)); + if (!node->parent()) + return {}; + + auto results = autocomplete_property(document, *node, containing_token); + if (results.has_value()) + return results.value(); + + results = autocomplete_name(document, *node, containing_token); + if (results.has_value()) + return results.value(); + return {}; +} + +Optional> ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, Optional 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> ParserAutoComplete::autocomplete_property(const DocumentData& document, const ASTNode& node, Optional containing_token) const +{ + if (!containing_token.has_value()) + return {}; + + if (!node.parent()->is_member_expression()) + return {}; + + const auto& parent = static_cast(*node.parent()); + + auto partial_text = String::empty(); + if (containing_token.value().type() != Token::Type::Dot) { + if (&node != parent.m_property) + return {}; + partial_text = containing_token.value().text(); } - if (is_empty_property(document, *node, position)) { - VERIFY(node->parent()->is_member_expression()); - return autocomplete_property(document, (MemberExpression&)(*node->parent()), ""); - } - - String partial_text = String::empty(); - auto containing_token = document.parser().token_at(position); - if (containing_token.has_value()) { - partial_text = document.parser().text_of_token(containing_token.value()); - } - - return autocomplete_name(document, *node, partial_text.view()); + return autocomplete_property(document, parent, partial_text); } NonnullRefPtrVector ParserAutoComplete::get_available_declarations(const DocumentData& document, const ASTNode& node, RecurseIntoScopes recurse_into_scopes) const @@ -106,28 +130,38 @@ NonnullRefPtrVector ParserAutoComplete::get_available_declarations( Vector ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, const String& partial_text) const { + auto target_scope = scope_of_name_or_identifier(node); + auto available_declarations = get_available_declarations(document, node, RecurseIntoScopes::No); + Vector available_names; - auto add_name = [&available_names](auto& name) { + auto add_if_valid = [this, &available_names, &target_scope](auto& decl) { + auto name = decl.m_name; if (name.is_null() || name.is_empty()) return; + auto scope = scope_of_declaration(decl); + if (scope.is_null()) + scope = String::empty(); + if (scope != target_scope) + return; if (!available_names.contains_slow(name)) available_names.append(name); }; + for (auto& decl : available_declarations) { if (decl.filename() == node.filename() && decl.start().line > node.start().line) continue; if (decl.is_variable_or_parameter_declaration()) { - add_name(((Cpp::VariableOrParameterDeclaration&)decl).m_name); + add_if_valid(decl); } if (decl.is_struct_or_class()) { - add_name(((Cpp::StructOrClassDeclaration&)decl).m_name); + add_if_valid(decl); } if (decl.is_function()) { - add_name(((Cpp::FunctionDeclaration&)decl).m_name); + add_if_valid(decl); } if (decl.is_namespace()) { - add_name(((Cpp::NamespaceDeclaration&)decl).m_name); + add_if_valid(decl); } } @@ -138,15 +172,40 @@ Vector ParserAutoComplete::autocomplete_name(c } } - for (auto& preprocessor_name : document.parser().preprocessor_definitions().keys()) { - if (preprocessor_name.starts_with(partial_text)) { - suggestions.append({ preprocessor_name.to_string(), partial_text.length(), GUI::AutocompleteProvider::CompletionKind::PreprocessorDefinition }); + if (target_scope.is_empty()) { + for (auto& preprocessor_name : document.parser().preprocessor_definitions().keys()) { + if (preprocessor_name.starts_with(partial_text)) { + suggestions.append({ preprocessor_name.to_string(), partial_text.length(), GUI::AutocompleteProvider::CompletionKind::PreprocessorDefinition }); + } } } return suggestions; } +String ParserAutoComplete::scope_of_name_or_identifier(const ASTNode& node) const +{ + const Name* name = nullptr; + if (node.is_name()) { + name = reinterpret_cast(&node); + } else if (node.is_identifier()) { + auto* parent = node.parent(); + if (!(parent && parent->is_name())) + return {}; + name = reinterpret_cast(parent); + } else { + return String::empty(); + } + + VERIFY(name->is_name()); + + Vector scope_parts; + for (auto& scope_part : name->m_scope) { + scope_parts.append(scope_part.m_name); + } + return String::join("::", scope_parts); +} + Vector ParserAutoComplete::autocomplete_property(const DocumentData& document, const MemberExpression& parent, const String partial_text) const { auto type = type_of(document, *parent.m_object); @@ -507,7 +566,7 @@ OwnPtr ParserAutoComplete::create_document_dat return document_data; } -String ParserAutoComplete::scope_of_declaration(const Declaration& decl) +String ParserAutoComplete::scope_of_declaration(const Declaration& decl) const { auto parent = decl.parent(); if (!parent) diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h index c8742e0d51..c3b735d948 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h @@ -96,10 +96,13 @@ private: String document_path_from_include_path(const StringView& include_path) const; void update_declared_symbols(DocumentData&); GUI::AutocompleteProvider::DeclarationType type_of_declaration(const Declaration&); - String scope_of_declaration(const Declaration&); + String scope_of_declaration(const Declaration&) const; + String scope_of_name_or_identifier(const ASTNode& node) const; Optional find_preprocessor_definition(const DocumentData&, const GUI::TextPosition&); OwnPtr create_document_data(String&& text, const String& filename); + Optional> autocomplete_property(const DocumentData&, const ASTNode&, Optional containing_token) const; + Optional> autocomplete_name(const DocumentData&, const ASTNode&, Optional containing_token) const; HashMap> m_documents; };