Преглед на файлове

CppLanguageServer: Add "get_parameters_hint" capability

Given a call site, the C++ language server can now return the declared
parameters of the called function, as well as the index of the
parameter that the cursor is currently at.
Itamar преди 4 години
родител
ревизия
32be65a8b4

+ 28 - 0
Userland/DevTools/HackStudio/LanguageClient.cpp

@@ -33,6 +33,17 @@ void ServerConnection::declaration_location(const GUI::AutocompleteProvider::Pro
     m_current_language_client->declaration_found(location.file, location.line, location.column);
     m_current_language_client->declaration_found(location.file, location.line, location.column);
 }
 }
 
 
+void ServerConnection::parameters_hint_result(Vector<String> const& params, int argument_index)
+{
+    if (!m_current_language_client) {
+        dbgln("Language Server connection has no attached language client");
+        return;
+    }
+
+    VERIFY(argument_index >= 0);
+    m_current_language_client->parameters_hint_result(params, static_cast<size_t>(argument_index));
+}
+
 void ServerConnection::die()
 void ServerConnection::die()
 {
 {
     VERIFY(m_wrapper);
     VERIFY(m_wrapper);
@@ -112,6 +123,14 @@ void LanguageClient::search_declaration(const String& path, size_t line, size_t
     m_connection_wrapper.connection()->async_find_declaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
     m_connection_wrapper.connection()->async_find_declaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
 }
 }
 
 
+void LanguageClient::get_parameters_hint(const String& path, size_t line, size_t column)
+{
+    if (!m_connection_wrapper.connection())
+        return;
+    set_active_client();
+    m_connection_wrapper.connection()->async_get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
+}
+
 void LanguageClient::declaration_found(const String& file, size_t line, size_t column) const
 void LanguageClient::declaration_found(const String& file, size_t line, size_t column) const
 {
 {
     if (!on_declaration_found) {
     if (!on_declaration_found) {
@@ -121,6 +140,15 @@ void LanguageClient::declaration_found(const String& file, size_t line, size_t c
     on_declaration_found(file, line, column);
     on_declaration_found(file, line, column);
 }
 }
 
 
+void LanguageClient::parameters_hint_result(Vector<String> const& params, size_t argument_index) const
+{
+    if (!on_function_parameters_hint_result) {
+        dbgln("on_function_parameters_hint_result callback is not set");
+        return;
+    }
+    on_function_parameters_hint_result(params, argument_index);
+}
+
 void ServerConnectionInstances::set_instance_for_language(const String& language_name, NonnullOwnPtr<ServerConnectionWrapper>&& connection_wrapper)
 void ServerConnectionInstances::set_instance_for_language(const String& language_name, NonnullOwnPtr<ServerConnectionWrapper>&& connection_wrapper)
 {
 {
     s_instance_for_language.set(language_name, move(connection_wrapper));
     s_instance_for_language.set(language_name, move(connection_wrapper));

+ 5 - 0
Userland/DevTools/HackStudio/LanguageClient.h

@@ -48,6 +48,7 @@ protected:
     virtual void declaration_location(GUI::AutocompleteProvider::ProjectLocation const&) override;
     virtual void declaration_location(GUI::AutocompleteProvider::ProjectLocation const&) override;
     virtual void declarations_in_document(String const&, Vector<GUI::AutocompleteProvider::Declaration> const&) override;
     virtual void declarations_in_document(String const&, Vector<GUI::AutocompleteProvider::Declaration> const&) override;
     virtual void todo_entries_in_document(String const&, Vector<Cpp::Parser::TodoEntry> const&) override;
     virtual void todo_entries_in_document(String const&, Vector<Cpp::Parser::TodoEntry> const&) override;
+    virtual void parameters_hint_result(Vector<String> const&, int index) override;
     void set_wrapper(ServerConnectionWrapper& wrapper) { m_wrapper = &wrapper; }
     void set_wrapper(ServerConnectionWrapper& wrapper) { m_wrapper = &wrapper; }
 
 
     String m_project_path;
     String m_project_path;
@@ -129,12 +130,16 @@ 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 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 request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column);
     virtual void search_declaration(const String& path, size_t line, size_t column);
     virtual void search_declaration(const String& path, size_t line, size_t column);
+    virtual void get_parameters_hint(const String& path, size_t line, size_t column);
 
 
     void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&) const;
     void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&) const;
     void declaration_found(const String& file, size_t line, size_t column) const;
     void declaration_found(const String& file, size_t line, size_t column) const;
+    void parameters_hint_result(Vector<String> const& params, size_t argument_index) const;
 
 
+    // Callbacks that get called when the result of a language server query is ready
     Function<void(Vector<GUI::AutocompleteProvider::Entry>)> on_autocomplete_suggestions;
     Function<void(Vector<GUI::AutocompleteProvider::Entry>)> on_autocomplete_suggestions;
     Function<void(const String&, size_t, size_t)> on_declaration_found;
     Function<void(const String&, size_t, size_t)> on_declaration_found;
+    Function<void(Vector<String> const&, size_t)> on_function_parameters_hint_result;
 
 
 private:
 private:
     ServerConnectionWrapper& m_connection_wrapper;
     ServerConnectionWrapper& m_connection_wrapper;

+ 25 - 0
Userland/DevTools/HackStudio/LanguageServers/ClientConnection.cpp

@@ -118,4 +118,29 @@ void ClientConnection::find_declaration(GUI::AutocompleteProvider::ProjectLocati
     async_declaration_location(GUI::AutocompleteProvider::ProjectLocation { 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 });
 }
 }
 
 
+void ClientConnection::get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation const& location)
+{
+    dbgln_if(LANGUAGE_SERVER_DEBUG, "GetFunctionParams: {} {}:{}", location.file, location.line, location.column);
+    auto document = m_filedb.get(location.file);
+    if (!document) {
+        dbgln("file {} has not been opened", location.file);
+        return;
+    }
+
+    GUI::TextPosition identifier_position = { (size_t)location.line, (size_t)location.column };
+    auto params = m_autocomplete_engine->get_function_params_hint(location.file, identifier_position);
+    if (!params.has_value()) {
+        dbgln("could not get parameters hint");
+        return;
+    }
+
+    dbgln_if(LANGUAGE_SERVER_DEBUG, "parameters hint:");
+    for (auto& param : params->params) {
+        dbgln_if(LANGUAGE_SERVER_DEBUG, "{}", param);
+    }
+    dbgln_if(LANGUAGE_SERVER_DEBUG, "Parameter index: {}", params->current_index);
+
+    async_parameters_hint_result(params->params, params->current_index);
+}
+
 }
 }

+ 1 - 0
Userland/DevTools/HackStudio/LanguageServers/ClientConnection.h

@@ -33,6 +33,7 @@ protected:
     virtual void set_file_content(String const&, String const&) override;
     virtual void set_file_content(String const&, String const&) override;
     virtual void auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation const&) override;
     virtual void auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation const&) override;
     virtual void find_declaration(GUI::AutocompleteProvider::ProjectLocation const&) override;
     virtual void find_declaration(GUI::AutocompleteProvider::ProjectLocation const&) override;
+    virtual void get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation const&) override;
 
 
     FileDB m_filedb;
     FileDB m_filedb;
     OwnPtr<CodeComprehensionEngine> m_autocomplete_engine;
     OwnPtr<CodeComprehensionEngine> m_autocomplete_engine;

+ 7 - 1
Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h

@@ -26,7 +26,13 @@ public:
     virtual void on_edit([[maybe_unused]] const String& file) {};
     virtual void on_edit([[maybe_unused]] const String& file) {};
     virtual void file_opened([[maybe_unused]] const String& file) {};
     virtual void file_opened([[maybe_unused]] const String& file) {};
 
 
-    virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(const String&, const GUI::TextPosition&) { return {}; };
+    virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(const String&, const GUI::TextPosition&) { return {}; }
+
+    struct FunctionParamsHint {
+        Vector<String> params;
+        size_t current_index { 0 };
+    };
+    virtual Optional<FunctionParamsHint> get_function_params_hint(const String&, const GUI::TextPosition&) { return {}; }
 
 
 public:
 public:
     Function<void(const String&, Vector<GUI::AutocompleteProvider::Declaration>&&)> set_declarations_of_document_callback;
     Function<void(const String&, Vector<GUI::AutocompleteProvider::Declaration>&&)> set_declarations_of_document_callback;

+ 111 - 0
Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp

@@ -177,6 +177,7 @@ Vector<StringView> CppComprehensionEngine::scope_of_reference_to_symbol(const AS
 {
 {
     const Name* name = nullptr;
     const Name* name = nullptr;
     if (node.is_name()) {
     if (node.is_name()) {
+        // FIXME It looks like this code path is never taken
         name = reinterpret_cast<const Name*>(&node);
         name = reinterpret_cast<const Name*>(&node);
     } else if (node.is_identifier()) {
     } else if (node.is_identifier()) {
         auto* parent = node.parent();
         auto* parent = node.parent();
@@ -454,6 +455,7 @@ RefPtr<Declaration> CppComprehensionEngine::find_declaration_of(const DocumentDa
     dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "find_declaration_of: {} ({})", document_data.parser().text_of_node(node), node.class_name());
     dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "find_declaration_of: {} ({})", document_data.parser().text_of_node(node), node.class_name());
     if (!node.is_identifier()) {
     if (!node.is_identifier()) {
         dbgln("node is not an identifier, can't find declaration");
         dbgln("node is not an identifier, can't find declaration");
+        return {};
     }
     }
 
 
     auto target_decl = get_target_declaration(node);
     auto target_decl = get_target_declaration(node);
@@ -726,4 +728,113 @@ bool CppComprehensionEngine::is_symbol_available(const Symbol& symbol, const Vec
     return true;
     return true;
 }
 }
 
 
+Optional<CodeComprehensionEngine::FunctionParamsHint> CppComprehensionEngine::get_function_params_hint(const String& filename, const GUI::TextPosition& identifier_position)
+{
+    const auto* document_ptr = get_or_create_document_data(filename);
+    if (!document_ptr)
+        return {};
+
+    const auto& 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 = ((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->m_arguments.is_empty() ? 0 : call_node->m_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 = (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->m_arguments.size(); ++arg_index) {
+        if (&call_node->m_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->m_arguments.is_empty() ? 0 : call_node->m_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* callee = nullptr;
+    if (call_node.m_callee->is_identifier()) {
+        callee = (Identifier*)call_node.m_callee.ptr();
+    } else if (call_node.m_callee->is_name()) {
+        callee = ((Name&)*call_node.m_callee).m_name.ptr();
+    } else if (call_node.m_callee->is_member_expression()) {
+        auto& member_exp = ((MemberExpression&)*call_node.m_callee);
+        if (member_exp.m_property->is_identifier()) {
+            callee = (Identifier*)member_exp.m_property.ptr();
+        }
+    }
+
+    if (!callee) {
+        dbgln("unexpected node type for function call: {}", call_node.m_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 = (FunctionDeclaration&)*decl;
+    auto document_of_declaration = get_document_data(func_decl.filename());
+
+    FunctionParamsHint hint {};
+    hint.current_index = argument_index;
+    for (auto& arg : func_decl.m_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;
+}
+
 }
 }

+ 2 - 1
Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h

@@ -29,6 +29,7 @@ public:
     virtual void on_edit(const String& file) override;
     virtual void on_edit(const String& file) override;
     virtual void file_opened([[maybe_unused]] const String& file) override;
     virtual void file_opened([[maybe_unused]] const String& file) override;
     virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(const String& filename, const GUI::TextPosition& identifier_position) override;
     virtual Optional<GUI::AutocompleteProvider::ProjectLocation> find_declaration_of(const String& filename, const GUI::TextPosition& identifier_position) override;
+    virtual Optional<FunctionParamsHint> get_function_params_hint(const String&, const GUI::TextPosition&) override;
 
 
 private:
 private:
     struct SymbolName {
     struct SymbolName {
@@ -130,6 +131,7 @@ private:
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_name(const DocumentData&, const ASTNode&, Optional<Token> containing_token) const;
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_name(const DocumentData&, const ASTNode&, Optional<Token> containing_token) const;
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_include(const DocumentData&, Token include_path_token);
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_include(const DocumentData&, Token include_path_token);
     static bool is_symbol_available(const Symbol&, const Vector<StringView>& current_scope, const Vector<StringView>& reference_scope);
     static bool is_symbol_available(const Symbol&, const Vector<StringView>& current_scope, const Vector<StringView>& reference_scope);
+    Optional<FunctionParamsHint> get_function_params_hint(DocumentData const&, FunctionCall&, size_t argument_index);
 
 
     template<typename Func>
     template<typename Func>
     void for_each_available_symbol(const DocumentData&, Func) const;
     void for_each_available_symbol(const DocumentData&, Func) const;
@@ -171,7 +173,6 @@ void CppComprehensionEngine::for_each_included_document_recursive(const Document
             continue;
             continue;
     }
     }
 }
 }
-
 }
 }
 
 
 namespace AK {
 namespace AK {

+ 1 - 0
Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc

@@ -4,4 +4,5 @@ endpoint LanguageClient
     declaration_location(GUI::AutocompleteProvider::ProjectLocation location) =|
     declaration_location(GUI::AutocompleteProvider::ProjectLocation location) =|
     declarations_in_document(String filename, Vector<GUI::AutocompleteProvider::Declaration> declarations) =|
     declarations_in_document(String filename, Vector<GUI::AutocompleteProvider::Declaration> declarations) =|
     todo_entries_in_document(String filename, Vector<Cpp::Parser::TodoEntry> todo_entries) =|
     todo_entries_in_document(String filename, Vector<Cpp::Parser::TodoEntry> todo_entries) =|
+    parameters_hint_result(Vector<String> params, int current_index) =|
 }
 }

+ 1 - 0
Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc

@@ -9,4 +9,5 @@ endpoint LanguageServer
 
 
     auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation location) =|
     auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation location) =|
     find_declaration(GUI::AutocompleteProvider::ProjectLocation location) =|
     find_declaration(GUI::AutocompleteProvider::ProjectLocation location) =|
+    get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation location) =|
 }
 }