Skip to content

Commit

Permalink
CppLanguageServer: Add "get_parameters_hint" capability
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
itamar8910 authored and awesomekling committed Jul 4, 2021
1 parent 232013c commit 32be65a
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 2 deletions.
28 changes: 28 additions & 0 deletions Userland/DevTools/HackStudio/LanguageClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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));
Expand Down
5 changes: 5 additions & 0 deletions Userland/DevTools/HackStudio/LanguageClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ServerConnection
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;
Expand Down Expand Up @@ -129,12 +130,16 @@ class LanguageClient : public Weakable<LanguageClient> {
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;
Expand Down
25 changes: 25 additions & 0 deletions Userland/DevTools/HackStudio/LanguageServers/ClientConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ClientConnection : public IPC::ClientConnection<LanguageClientEndpoint, La
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ class CodeComprehensionEngine {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class CppComprehensionEngine : public CodeComprehensionEngine {
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 {
Expand Down Expand Up @@ -130,6 +131,7 @@ class CppComprehensionEngine : public CodeComprehensionEngine {
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;
Expand Down Expand Up @@ -171,7 +173,6 @@ void CppComprehensionEngine::for_each_included_document_recursive(const Document
continue;
}
}

}

namespace AK {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =|
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) =|
}

0 comments on commit 32be65a

Please sign in to comment.