1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 08:27:46 +00:00

LanguageServers/Cpp: Add 'FindDeclaration' capability

The C++ LanguageServer can now find the matching declaration for
variable names, function calls, struct/class types and properties.

When clicking on one of the above with Ctrl pressed, HackStudio will
ask the language server to find a matching declaration, and navigate
to the result in the Editor. :^)
This commit is contained in:
Itamar 2021-02-20 12:27:39 +02:00 committed by Andreas Kling
parent d3ff82ba80
commit 5bc82c0185
12 changed files with 150 additions and 38 deletions

View file

@ -41,6 +41,13 @@ public:
virtual void on_edit([[maybe_unused]] const String& file) {};
virtual void file_opened([[maybe_unused]] const String& file) {};
struct ProjectPosition {
String file;
size_t line;
size_t column;
};
virtual Optional<ProjectPosition> find_declaration_of(const String&, const GUI::TextPosition&) { return {}; };
protected:
const FileDB& filedb() const { return m_filedb; }

View file

@ -128,7 +128,7 @@ void ClientConnection::handle(const Messages::LanguageServer::SetFileContent& me
void ClientConnection::handle(const Messages::LanguageServer::SetAutoCompleteMode& message)
{
#ifdef DEBUG_CPP_LANGUAGE_SERVER
#ifdef CPP_LANGUAGE_SERVER_DEBUG
dbgln("SetAutoCompleteMode: {}", message.mode());
#endif
if (message.mode() == "Parser")
@ -137,4 +137,23 @@ void ClientConnection::handle(const Messages::LanguageServer::SetAutoCompleteMod
m_autocomplete_engine = make<LexerAutoComplete>(m_filedb);
}
void ClientConnection::handle(const Messages::LanguageServer::FindDeclaration& message)
{
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "FindDeclaration: {} {}:{}", message.file_name(), message.line(), message.column());
auto document = m_filedb.get(message.file_name());
if (!document) {
dbgln("file {} has not been opened", message.file_name());
return;
}
GUI::TextPosition identifier_position = { (size_t)message.line(), (size_t)message.column() };
auto location = m_autocomplete_engine->find_declaration_of(message.file_name(), identifier_position);
if (!location.has_value()) {
dbgln("could not find declaration");
return;
}
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "declaration location: {} {}:{}", location.value().file, location.value().line, location.value().column);
post_message(Messages::LanguageClient::DeclarationLocation(location.value().file, location.value().line, location.value().column));
}
}

View file

@ -57,6 +57,7 @@ private:
virtual void handle(const Messages::LanguageServer::SetFileContent&) override;
virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override;
virtual void handle(const Messages::LanguageServer::SetAutoCompleteMode&) override;
virtual void handle(const Messages::LanguageServer::FindDeclaration&) override;
FileDB m_filedb;
OwnPtr<AutoCompleteEngine> m_autocomplete_engine;

View file

@ -33,16 +33,6 @@
#include <LibCpp/Preprocessor.h>
#include <LibRegex/Regex.h>
// #define AUTOCOMPLETE_VERBOSE
#ifdef AUTOCOMPLETE_VERBOSE
# define VERBOSE(fmt, ...) dbgln(fmt, ##__VA_ARGS__)
#else
# define VERBOSE(fmt, ...) \
do { \
} while (0)
#endif
namespace LanguageServers::Cpp {
ParserAutoComplete::ParserAutoComplete(const FileDB& filedb)
@ -77,7 +67,7 @@ OwnPtr<ParserAutoComplete::DocumentData> ParserAutoComplete::create_document_dat
for (auto& path : document_data->preprocessor.included_paths()) {
get_or_create_document_data(document_path_from_include_path(path));
}
#ifdef DEBUG_AUTOCOMPLETE
#ifdef CPP_LANGUAGE_SERVER_DEBUG
root->dump(0);
#endif
return move(document_data);
@ -99,12 +89,12 @@ Vector<GUI::AutocompleteProvider::Entry> ParserAutoComplete::get_suggestions(con
{
Cpp::Position position { autocomplete_position.line(), autocomplete_position.column() > 0 ? autocomplete_position.column() - 1 : 0 };
VERBOSE("ParserAutoComplete position {}:{}", position.line, position.column);
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "ParserAutoComplete position {}:{}", position.line, position.column);
const auto& document = get_or_create_document_data(file);
auto node = document.parser.node_at(position);
if (!node) {
VERBOSE("no node at position {}:{}", position.line, position.column);
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", position.line, position.column);
return {};
}
@ -130,17 +120,22 @@ Vector<GUI::AutocompleteProvider::Entry> ParserAutoComplete::get_suggestions(con
return autocomplete_name(document, *node, partial_text.view());
}
Vector<GUI::AutocompleteProvider::Entry> ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, const StringView& partial_text) const
NonnullRefPtrVector<Declaration> ParserAutoComplete::get_available_declarations(const DocumentData& document, const ASTNode& node) const
{
const Cpp::ASTNode* current = &node;
NonnullRefPtrVector<Cpp::Declaration> available_declarations;
NonnullRefPtrVector<Declaration> available_declarations;
while (current) {
available_declarations.append(current->declarations());
current = current->parent();
}
available_declarations.append(get_declarations_in_outer_scope_including_headers(document));
return available_declarations;
}
Vector<GUI::AutocompleteProvider::Entry> ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, const StringView& partial_text) const
{
auto available_declarations = get_available_declarations(document, node);
Vector<StringView> available_names;
auto add_name = [&available_names](auto& name) {
if (name.is_null() || name.is_empty())
@ -173,7 +168,7 @@ Vector<GUI::AutocompleteProvider::Entry> ParserAutoComplete::autocomplete_proper
{
auto type = type_of(document, *parent.m_object);
if (type.is_null()) {
VERBOSE("Could not infer type of object");
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "Could not infer type of object");
return {};
}
@ -321,4 +316,47 @@ void ParserAutoComplete::file_opened([[maybe_unused]] const String& file)
set_document_data(file, create_document_data_for(file));
}
Optional<AutoCompleteEngine::ProjectPosition> ParserAutoComplete::find_declaration_of(const String& file_name, const GUI::TextPosition& identifier_position)
{
const auto& document = get_or_create_document_data(file_name);
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 {};
}
auto decl = find_declaration_of(document, *node);
if (!decl)
return {};
return ProjectPosition { decl->filename(), decl->start().line, decl->start().column };
}
RefPtr<Declaration> ParserAutoComplete::find_declaration_of(const DocumentData& document_data, const ASTNode& node) const
{
dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "find_declaration_of: {}", document_data.parser.text_of_node(node));
auto declarations = get_available_declarations(document_data, node);
for (auto& decl : declarations) {
if (node.is_identifier() && decl.is_variable_or_parameter_declaration()) {
if (((Cpp::VariableOrParameterDeclaration&)decl).m_name == static_cast<const Identifier&>(node).m_name)
return decl;
}
if (node.is_type() && decl.is_struct_or_class()) {
if (((Cpp::StructOrClassDeclaration&)decl).m_name == static_cast<const Type&>(node).m_name)
return decl;
}
if (node.is_function_call() && decl.is_function()) {
if (((Cpp::FunctionDeclaration&)decl).m_name == static_cast<const FunctionCall&>(node).m_name)
return decl;
}
if (is_property(node) && decl.is_struct_or_class()) {
for (auto& member : ((Cpp::StructOrClassDeclaration&)decl).m_members) {
ASSERT(node.is_identifier());
if (member.m_name == ((const Identifier&)node).m_name)
return member;
}
}
}
return {};
}
}

View file

@ -47,10 +47,11 @@ public:
virtual Vector<GUI::AutocompleteProvider::Entry> get_suggestions(const String& file, const GUI::TextPosition& autocomplete_position) override;
virtual void on_edit(const String& file) override;
virtual void file_opened([[maybe_unused]] const String& file) override;
virtual Optional<ProjectPosition> find_declaration_of(const String& file_name, const GUI::TextPosition& identifier_position) override;
private:
struct DocumentData {
DocumentData(String&& text);
DocumentData(String&& text, const String& filename);
String text;
Preprocessor preprocessor;
Parser parser;
@ -63,6 +64,8 @@ private:
String type_of_variable(const Identifier&) const;
bool is_property(const ASTNode&) const;
bool is_empty_property(const DocumentData&, const ASTNode&, const Position& autocomplete_position) const;
RefPtr<Declaration> find_declaration_of(const DocumentData&, const ASTNode&) const;
NonnullRefPtrVector<Declaration> get_available_declarations(const DocumentData&, const ASTNode&) const;
struct PropertyInfo {
StringView name;

View file

@ -1,4 +1,5 @@
endpoint LanguageClient = 8002
{
AutoCompleteSuggestions(Vector<GUI::AutocompleteProvider::Entry> suggestions) =|
DeclarationLocation(String file_name, i32 line, i32 column) =|
}

View file

@ -9,4 +9,6 @@ endpoint LanguageServer = 8001
AutoCompleteSuggestions(String file_name, i32 cursor_line, i32 cursor_column) =|
SetAutoCompleteMode(String mode) =|
FindDeclaration(String file_name, i32 line, i32 column) =|
}

View file

@ -57,6 +57,7 @@ private:
virtual void handle(const Messages::LanguageServer::SetFileContent&) override;
virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override;
virtual void handle(const Messages::LanguageServer::SetAutoCompleteMode&) override { }
virtual void handle(const Messages::LanguageServer::FindDeclaration&) override {};
RefPtr<GUI::TextDocument> document_for(const String& file_name);