diff options
author | Itamar <itamar8910@gmail.com> | 2021-07-03 11:55:54 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-07-04 17:50:33 +0200 |
commit | 32be65a8b4bf1a0b47551ee334a09befd0de2c96 (patch) | |
tree | 71882d3ad86415d9cd7c9ed56ce20bd9df80789a /Userland | |
parent | 232013c05bd024efc62f23cf99bafe4f0bef1333 (diff) | |
download | serenity-32be65a8b4bf1a0b47551ee334a09befd0de2c96.zip |
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.
Diffstat (limited to 'Userland')
9 files changed, 181 insertions, 2 deletions
diff --git a/Userland/DevTools/HackStudio/LanguageClient.cpp b/Userland/DevTools/HackStudio/LanguageClient.cpp index 6e47813609..745950170a 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.cpp +++ b/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); } +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() { 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 }); } +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 { 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); } +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) { s_instance_for_language.set(language_name, move(connection_wrapper)); diff --git a/Userland/DevTools/HackStudio/LanguageClient.h b/Userland/DevTools/HackStudio/LanguageClient.h index 004bbd2b5f..b99c16d64e 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.h +++ b/Userland/DevTools/HackStudio/LanguageClient.h @@ -48,6 +48,7 @@ protected: virtual void declaration_location(GUI::AutocompleteProvider::ProjectLocation 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 parameters_hint_result(Vector<String> const&, int index) override; void set_wrapper(ServerConnectionWrapper& wrapper) { m_wrapper = &wrapper; } 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 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 get_parameters_hint(const String& path, size_t line, size_t column); void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&) 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(const String&, size_t, size_t)> on_declaration_found; + Function<void(Vector<String> const&, size_t)> on_function_parameters_hint_result; private: ServerConnectionWrapper& m_connection_wrapper; diff --git a/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.cpp index 5c4d579f70..567268a994 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.cpp +++ b/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 }); } +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); +} + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.h index b227923dd3..1901743463 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.h +++ b/Userland/DevTools/HackStudio/LanguageServers/ClientConnection.h @@ -33,6 +33,7 @@ protected: virtual void set_file_content(String const&, String const&) override; virtual void auto_complete_suggestions(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; OwnPtr<CodeComprehensionEngine> m_autocomplete_engine; diff --git a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h b/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h index e7a6aabaf6..700bba4603 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h +++ b/Userland/DevTools/HackStudio/LanguageServers/CodeComprehensionEngine.h @@ -26,7 +26,13 @@ public: virtual void on_edit([[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: Function<void(const String&, Vector<GUI::AutocompleteProvider::Declaration>&&)> set_declarations_of_document_callback; diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp index 7eec6b927a..ec29ee1ae5 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp +++ b/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; if (node.is_name()) { + // FIXME It looks like this code path is never taken name = reinterpret_cast<const Name*>(&node); } else if (node.is_identifier()) { 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()); if (!node.is_identifier()) { dbgln("node is not an identifier, can't find declaration"); + return {}; } auto target_decl = get_target_declaration(node); @@ -726,4 +728,113 @@ bool CppComprehensionEngine::is_symbol_available(const Symbol& symbol, const Vec 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; +} + } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h index e77e276f5d..742688219f 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h @@ -29,6 +29,7 @@ public: virtual void on_edit(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<FunctionParamsHint> get_function_params_hint(const String&, const GUI::TextPosition&) override; private: 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_include(const DocumentData&, Token include_path_token); 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> void for_each_available_symbol(const DocumentData&, Func) const; @@ -171,7 +173,6 @@ void CppComprehensionEngine::for_each_included_document_recursive(const Document continue; } } - } namespace AK { diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc index 6400dd7b29..6760c13c12 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc @@ -4,4 +4,5 @@ endpoint LanguageClient declaration_location(GUI::AutocompleteProvider::ProjectLocation location) =| declarations_in_document(String filename, Vector<GUI::AutocompleteProvider::Declaration> declarations) =| todo_entries_in_document(String filename, Vector<Cpp::Parser::TodoEntry> todo_entries) =| + parameters_hint_result(Vector<String> params, int current_index) =| } diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc index 668ad0a6e9..ba6cac7196 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc @@ -9,4 +9,5 @@ endpoint LanguageServer auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation location) =| find_declaration(GUI::AutocompleteProvider::ProjectLocation location) =| + get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation location) =| } |