1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:18:11 +00:00

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.
This commit is contained in:
Itamar 2021-05-15 22:13:32 +03:00 committed by Andreas Kling
parent 0e51aea781
commit c54238f65c
2 changed files with 89 additions and 27 deletions

View file

@ -63,32 +63,56 @@ Vector<GUI::AutocompleteProvider::Entry> 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<Vector<GUI::AutocompleteProvider::Entry>> ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& 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>> ParserAutoComplete::autocomplete_property(const DocumentData& document, const ASTNode& node, Optional<Token> containing_token) const
{
if (!containing_token.has_value())
return {};
if (!node.parent()->is_member_expression())
return {};
const auto& parent = static_cast<const MemberExpression&>(*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<Declaration> ParserAutoComplete::get_available_declarations(const DocumentData& document, const ASTNode& node, RecurseIntoScopes recurse_into_scopes) const
@ -106,28 +130,38 @@ NonnullRefPtrVector<Declaration> ParserAutoComplete::get_available_declarations(
Vector<GUI::AutocompleteProvider::Entry> 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<StringView> 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<GUI::AutocompleteProvider::Entry> 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<const Name*>(&node);
} else if (node.is_identifier()) {
auto* parent = node.parent();
if (!(parent && parent->is_name()))
return {};
name = reinterpret_cast<const Name*>(parent);
} else {
return String::empty();
}
VERIFY(name->is_name());
Vector<StringView> scope_parts;
for (auto& scope_part : name->m_scope) {
scope_parts.append(scope_part.m_name);
}
return String::join("::", scope_parts);
}
Vector<GUI::AutocompleteProvider::Entry> 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::DocumentData> 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)