diff --git a/Userland/DevTools/HackStudio/Editor.cpp b/Userland/DevTools/HackStudio/Editor.cpp index c16d2980ee..2ccc05ffd4 100644 --- a/Userland/DevTools/HackStudio/Editor.cpp +++ b/Userland/DevTools/HackStudio/Editor.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * 2018-2021, the SerenityOS developers * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -153,9 +154,7 @@ void Editor::show_documentation_tooltip_if_available(const String& hovered_token { auto it = man_paths().find(hovered_token); if (it == man_paths().end()) { -#if EDITOR_DEBUG - dbgln("no man path for {}", hovered_token); -#endif + dbgln_if(EDITOR_DEBUG, "no man path for {}", hovered_token); m_documentation_tooltip_window->hide(); return; } @@ -164,9 +163,7 @@ void Editor::show_documentation_tooltip_if_available(const String& hovered_token return; } -#if EDITOR_DEBUG - dbgln("opening {}", it->value); -#endif + dbgln(EDITOR_DEBUG, "opening {}", it->value); auto file = Core::File::construct(it->value); if (!file->open(Core::File::ReadOnly)) { dbgln("failed to open {}, {}", it->value, file->error_string()); @@ -236,9 +233,7 @@ void Editor::mousemove_event(GUI::MouseEvent& event) auto end_line_length = document().line(span.range.end().line()).length(); adjusted_range.end().set_column(min(end_line_length, adjusted_range.end().column() + 1)); auto hovered_span_text = document().text_in_range(adjusted_range); -#if EDITOR_DEBUG - dbgln("Hovering: {} \"{}\"", adjusted_range, hovered_span_text); -#endif + dbgln_if(EDITOR_DEBUG, "Hovering: {} \"{}\"", adjusted_range, hovered_span_text); if (is_clickable) { is_over_clickable = true; @@ -298,6 +293,10 @@ void Editor::mousedown_event(GUI::MouseEvent& event) on_navigatable_link_click(*span); return; } + if (highlighter->is_identifier(span->data)) { + on_identifier_click(*span); + return; + } } GUI::TextEditor::mousedown_event(event); @@ -325,9 +324,7 @@ static HashMap& include_paths() auto path = it.next_full_path(); if (!Core::File::is_directory(path)) { auto key = path.substring(base.length() + 1, path.length() - base.length() - 1); -#if EDITOR_DEBUG - dbgln("Adding header \"{}\" in path \"{}\"", key, path); -#endif + dbgln_if(EDITOR_DEBUG, "Adding header \"{}\" in path \"{}\"", key, path); paths.set(key, path); } else { handle_directory(base, path, handle_directory); @@ -349,9 +346,7 @@ void Editor::navigate_to_include_if_available(String path) { auto it = include_paths().find(path); if (it == include_paths().end()) { -#if EDITOR_DEBUG - dbgln("no header {} found.", path); -#endif + dbgln_if(EDITOR_DEBUG, "no header {} found.", path); return; } @@ -523,9 +518,23 @@ void Editor::on_navigatable_link_click(const GUI::TextDocumentSpan& span) adjusted_range.end().set_column(adjusted_range.end().column() + 1); auto span_text = document().text_in_range(adjusted_range); auto header_path = span_text.substring(1, span_text.length() - 2); -#if EDITOR_DEBUG - dbgln("Ctrl+click: {} \"{}\"", adjusted_range, header_path); -#endif + dbgln_if(EDITOR_DEBUG, "Ctrl+click: {} \"{}\"", adjusted_range, header_path); navigate_to_include_if_available(header_path); } + +void Editor::open_and_set_cursor(const String& file, size_t line, size_t column) +{ + if (code_document().file_path() != file) + on_open(file); + set_cursor(GUI::TextPosition { line, column }); +} + +void Editor::on_identifier_click(const GUI::TextDocumentSpan& span) +{ + m_language_client->on_declaration_found = [this](const String& file, size_t line, size_t column) { + open_and_set_cursor(file, line, column); + }; + m_language_client->search_declaration(code_document().file_path(), span.range.start().line(), span.range.start().column()); +} + } diff --git a/Userland/DevTools/HackStudio/Editor.h b/Userland/DevTools/HackStudio/Editor.h index 2d82357612..87ef310a44 100644 --- a/Userland/DevTools/HackStudio/Editor.h +++ b/Userland/DevTools/HackStudio/Editor.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * 2018-2021, the SerenityOS developers * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -77,6 +78,8 @@ private: void show_documentation_tooltip_if_available(const String&, const Gfx::IntPoint& screen_location); void navigate_to_include_if_available(String); void on_navigatable_link_click(const GUI::TextDocumentSpan&); + void on_identifier_click(const GUI::TextDocumentSpan&); + void open_and_set_cursor(const String& file, size_t line, size_t column); Gfx::IntRect breakpoint_icon_rect(size_t line_number) const; static const Gfx::Bitmap& breakpoint_icon_bitmap(); diff --git a/Userland/DevTools/HackStudio/LanguageClient.cpp b/Userland/DevTools/HackStudio/LanguageClient.cpp index 7c16305dc3..cc54bc5d71 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.cpp +++ b/Userland/DevTools/HackStudio/LanguageClient.cpp @@ -40,6 +40,14 @@ void ServerConnection::handle(const Messages::LanguageClient::AutoCompleteSugges } m_language_client->provide_autocomplete_suggestions(message.suggestions()); } +void ServerConnection::handle(const Messages::LanguageClient::DeclarationLocation& message) +{ + if (!m_language_client) { + dbgln("Language Server connection has no attached language client"); + return; + } + m_language_client->declaration_found(message.file_name(), message.line(), message.column()); +} void ServerConnection::die() { @@ -87,8 +95,6 @@ void LanguageClient::request_autocomplete(const String& path, size_t cursor_line void LanguageClient::provide_autocomplete_suggestions(const Vector& suggestions) { - if (!m_server_connection) - return; if (on_autocomplete_suggestions) on_autocomplete_suggestions(suggestions); @@ -147,4 +153,22 @@ void ServerConnection::remove_instance_for_project(const String& project_path) s_instances_for_projects.remove(key); } +void LanguageClient::search_declaration(const String& path, size_t line, size_t column) +{ + if (!m_server_connection) + return; + set_active_client(); + m_server_connection->post_message(Messages::LanguageServer::FindDeclaration(path, line, column)); +} + +void LanguageClient::declaration_found(const String& file, size_t line, size_t column) +{ + if (!on_declaration_found) { + dbgln("on_declaration_found callback is not set"); + return; + } + dbgln("calling on_declaration_found"); + on_declaration_found(file, line, column); +} + } diff --git a/Userland/DevTools/HackStudio/LanguageClient.h b/Userland/DevTools/HackStudio/LanguageClient.h index 2125219c9a..846e24f8fc 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.h +++ b/Userland/DevTools/HackStudio/LanguageClient.h @@ -90,6 +90,7 @@ public: protected: virtual void handle(const Messages::LanguageClient::AutoCompleteSuggestions&) override; + virtual void handle(const Messages::LanguageClient::DeclarationLocation&) override; String m_project_path; WeakPtr m_language_client; @@ -123,11 +124,14 @@ public: virtual void remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column); virtual void request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column); virtual void set_autocomplete_mode(const String& mode); + virtual void search_declaration(const String& path, size_t line, size_t column); void provide_autocomplete_suggestions(const Vector&); + void declaration_found(const String& file, size_t line, size_t column); void on_server_crash(); Function)> on_autocomplete_suggestions; + Function on_declaration_found; private: WeakPtr m_server_connection; diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoCompleteEngine.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoCompleteEngine.h index 2e61eb9101..28268d20cc 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoCompleteEngine.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoCompleteEngine.h @@ -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 find_declaration_of(const String&, const GUI::TextPosition&) { return {}; }; + protected: const FileDB& filedb() const { return m_filedb; } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp index a13c10fc12..9d11e92c4f 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp @@ -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(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)); +} + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h index 8118cbf675..0103dde6cf 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h @@ -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 m_autocomplete_engine; diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp index d8cd98178b..8257bc667c 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.cpp @@ -33,16 +33,6 @@ #include #include -// #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::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 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 ParserAutoComplete::get_suggestions(con return autocomplete_name(document, *node, partial_text.view()); } -Vector ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, const StringView& partial_text) const +NonnullRefPtrVector ParserAutoComplete::get_available_declarations(const DocumentData& document, const ASTNode& node) const { const Cpp::ASTNode* current = &node; - NonnullRefPtrVector available_declarations; + NonnullRefPtrVector 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 ParserAutoComplete::autocomplete_name(const DocumentData& document, const ASTNode& node, const StringView& partial_text) const +{ + auto available_declarations = get_available_declarations(document, node); Vector available_names; auto add_name = [&available_names](auto& name) { if (name.is_null() || name.is_empty()) @@ -173,7 +168,7 @@ Vector 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 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 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(node).m_name) + return decl; + } + if (node.is_type() && decl.is_struct_or_class()) { + if (((Cpp::StructOrClassDeclaration&)decl).m_name == static_cast(node).m_name) + return decl; + } + if (node.is_function_call() && decl.is_function()) { + if (((Cpp::FunctionDeclaration&)decl).m_name == static_cast(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 {}; +} + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h index b83495be37..71ca10c163 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ParserAutoComplete.h @@ -47,10 +47,11 @@ public: virtual Vector 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 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 find_declaration_of(const DocumentData&, const ASTNode&) const; + NonnullRefPtrVector get_available_declarations(const DocumentData&, const ASTNode&) const; struct PropertyInfo { StringView name; diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc index 31b5d5b2c5..9e85f8df9a 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc @@ -1,4 +1,5 @@ endpoint LanguageClient = 8002 { AutoCompleteSuggestions(Vector suggestions) =| + DeclarationLocation(String file_name, i32 line, i32 column) =| } diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc index 1eada48bf0..379ba03c30 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc @@ -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) =| + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h index cea922037e..a412fc8f4e 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h @@ -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 document_for(const String& file_name);