diff --git a/src/command_line.cc b/src/command_line.cc index affff753..df72c8fe 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -343,6 +343,8 @@ void LaunchStdinLoop(Config* config, case IpcId::CqueryFileInfo: case IpcId::CqueryFreshenIndex: case IpcId::CqueryInheritanceHierarchy: + case IpcId::CqueryCallHierarchyInitial: + case IpcId::CqueryCallHierarchyExpand: case IpcId::CqueryCallTreeInitial: case IpcId::CqueryCallTreeExpand: case IpcId::CqueryMemberHierarchyInitial: diff --git a/src/ipc.cc b/src/ipc.cc index 0856d384..bb78dd97 100644 --- a/src/ipc.cc +++ b/src/ipc.cc @@ -72,6 +72,10 @@ const char* IpcIdToString(IpcId id) { return "$cquery/freshenIndex"; case IpcId::CqueryInheritanceHierarchy: return "$cquery/inheritanceHierarchy"; + case IpcId::CqueryCallHierarchyInitial: + return "$cquery/callHierarchyInitial"; + case IpcId::CqueryCallHierarchyExpand: + return "$cquery/callHierarchyExpand"; case IpcId::CqueryCallTreeInitial: return "$cquery/callTreeInitial"; case IpcId::CqueryCallTreeExpand: diff --git a/src/ipc.h b/src/ipc.h index e902e0b8..dcae371a 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -49,6 +49,8 @@ enum class IpcId : int { CqueryFreshenIndex, // Messages used in tree views. CqueryInheritanceHierarchy, + CqueryCallHierarchyInitial, + CqueryCallHierarchyExpand, CqueryCallTreeInitial, CqueryCallTreeExpand, CqueryMemberHierarchyInitial, diff --git a/src/messages/cquery_call_hierarchy.cc b/src/messages/cquery_call_hierarchy.cc new file mode 100644 index 00000000..b3c6d189 --- /dev/null +++ b/src/messages/cquery_call_hierarchy.cc @@ -0,0 +1,210 @@ +#include "message_handler.h" +#include "query_utils.h" +#include "queue_manager.h" + +#include + +namespace { +struct Ipc_CqueryCallHierarchyInitial + : public RequestMessage { + const static IpcId kIpcId = IpcId::CqueryCallHierarchyInitial; + struct Params { + lsTextDocumentIdentifier textDocument; + lsPosition position; + bool callee = false; + int levels = 1; + }; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchyInitial::Params, + textDocument, + position, + callee, + levels); +MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchyInitial, id, params); +REGISTER_IPC_MESSAGE(Ipc_CqueryCallHierarchyInitial); + +enum class CallType { Direct = 0, Base = 1, Derived = 2, All = 1 | 2 }; +MAKE_REFLECT_TYPE_PROXY(CallType); + +struct Ipc_CqueryCallHierarchyExpand + : public RequestMessage { + const static IpcId kIpcId = IpcId::CqueryCallHierarchyExpand; + struct Params { + Maybe id; + // true: callee tree; false: caller tree + bool callee = false; + int levels = 1; + }; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchyExpand::Params, id, callee, levels); +MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchyExpand, id, params); +REGISTER_IPC_MESSAGE(Ipc_CqueryCallHierarchyExpand); + +struct Out_CqueryCallHierarchy : public lsOutMessage { + struct Entry { + QueryFuncId id; + std::string_view name; + lsLocation location; + CallType callType = CallType::Direct; + int numChildren; + // Empty if the |levels| limit is reached. + std::vector children; + }; + + lsRequestId id; + optional result; +}; +MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy::Entry, + id, + name, + location, + callType, + numChildren, + children); +MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy, jsonrpc, id, result); + +void Expand(MessageHandler* m, + Out_CqueryCallHierarchy::Entry* entry, + bool callee, + int levels) { + const QueryFunc& func = m->db->funcs[entry->id.id]; + const QueryFunc::Def* def = func.AnyDef(); + entry->numChildren = 0; + if (!def) + return; + if (def->spell) { + if (optional loc = + GetLsLocation(m->db, m->working_files, *def->spell)) + entry->location = *loc; + } + auto handle = [&](Use use, CallType call_type) { + QueryFunc& rel_func = m->db->GetFunc(use); + const QueryFunc::Def* rel_def = rel_func.AnyDef(); + if (!rel_def) + return; + if (optional loc = + GetLsLocation(m->db, m->working_files, use)) { + entry->numChildren++; + if (levels > 0) { + Out_CqueryCallHierarchy::Entry entry1; + entry1.id = QueryFuncId(use.id); + entry1.name = rel_def->ShortName(); + entry1.location = *loc; + entry1.callType = call_type; + Expand(m, &entry1, callee, levels - 1); + entry->children.push_back(std::move(entry1)); + } + } + }; + auto handle_uses = [&](const QueryFunc& func, CallType call_type) { + if (callee) { + if (const auto* def = func.AnyDef()) + for (SymbolRef ref : def->callees) + if (ref.kind == SymbolKind::Func) + handle(Use(ref.range, ref.id, ref.kind, ref.role, def->file), call_type); + } else { + for (Use use : func.uses) + if (use.kind == SymbolKind::Func) + handle(use, call_type); + } + }; + + std::unordered_set seen; + std::vector stack; + handle_uses(func, CallType::Direct); + + // Callers/callees of base functions. + stack.push_back(&func); + while (stack.size()) { + const QueryFunc& func1 = *stack.back(); + stack.pop_back(); + if (auto* def1 = func1.AnyDef()) { + EachDefinedEntity(m->db->funcs, def1->base, [&](QueryFunc& func2) { + if (seen.count(func2.usr)) { + seen.insert(func2.usr); + stack.push_back(&func2); + handle_uses(func2, CallType::Base); + } + }); + } + } + + // Callers/callees of derived functions. + stack.push_back(&func); + while (stack.size()) { + const QueryFunc& func1 = *stack.back(); + stack.pop_back(); + EachDefinedEntity(m->db->funcs, func1.derived, [&](QueryFunc& func2) { + if (seen.count(func2.usr)) { + seen.insert(func2.usr); + stack.push_back(&func2); + handle_uses(func2, CallType::Derived); + } + }); + } +} + +struct CqueryCallHierarchyInitialHandler + : BaseMessageHandler { + optional BuildInitial(QueryFuncId root_id, + bool callee, + int levels) { + const auto* def = db->funcs[root_id.id].AnyDef(); + if (!def) + return {}; + + Out_CqueryCallHierarchy::Entry entry; + entry.id = root_id; + entry.name = def->ShortName(); + Expand(this, &entry, callee, levels); + return entry; + } + + void Run(Ipc_CqueryCallHierarchyInitial* request) override { + QueryFile* file; + const auto& params = request->params; + if (!FindFileOrFail(db, project, request->id, + params.textDocument.uri.GetPath(), &file)) + return; + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + Out_CqueryCallHierarchy out; + out.id = request->id; + + for (SymbolRef sym : + FindSymbolsAtLocation(working_file, file, params.position)) { + if (sym.kind == SymbolKind::Func) { + out.result = + BuildInitial(QueryFuncId(sym.id), params.callee, params.levels); + break; + } + } + + QueueManager::WriteStdout(IpcId::CqueryCallHierarchyInitial, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryCallHierarchyInitialHandler); + +struct CqueryCallHierarchyExpandHandler + : BaseMessageHandler { + void Run(Ipc_CqueryCallHierarchyExpand* request) override { + const auto& params = request->params; + Out_CqueryCallHierarchy out; + out.id = request->id; + if (params.id) { + Out_CqueryCallHierarchy::Entry entry; + entry.id = *params.id; + // entry.name is empty and it is known by the client. + if (entry.id.id < db->funcs.size()) + Expand(this, &entry, params.callee, params.levels); + out.result = std::move(entry); + } + + QueueManager::WriteStdout(IpcId::CqueryCallHierarchyExpand, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryCallHierarchyExpandHandler); +} // namespace diff --git a/src/messages/cquery_member_hierarchy.cc b/src/messages/cquery_member_hierarchy.cc index 3b4302bf..858991b3 100644 --- a/src/messages/cquery_member_hierarchy.cc +++ b/src/messages/cquery_member_hierarchy.cc @@ -39,7 +39,10 @@ struct Out_CqueryMemberHierarchy struct Entry { QueryTypeId id; std::string_view name; + std::string_view field_name; lsLocation location; + // For unexpanded nodes, this is an upper bound because some entities may be + // undefined. If it is 0, there are no members. int numChildren; // Empty if the |levels| limit is reached. std::vector children; @@ -50,6 +53,7 @@ struct Out_CqueryMemberHierarchy MAKE_REFLECT_STRUCT(Out_CqueryMemberHierarchy::Entry, id, name, + field_name, location, numChildren, children); @@ -61,6 +65,7 @@ void Expand(MessageHandler* m, Out_CqueryMemberHierarchy::Entry* entry, int leve entry->numChildren = 0; return; } + entry->name = def->ShortName(); if (def->spell) { if (optional loc = GetLsLocation(m->db, m->working_files, *def->spell)) @@ -71,11 +76,12 @@ void Expand(MessageHandler* m, Out_CqueryMemberHierarchy::Entry* entry, int leve EachDefinedEntity(m->db->vars, def->vars, [&](QueryVar& var) { const QueryVar::Def* def1 = var.AnyDef(); Out_CqueryMemberHierarchy::Entry entry1; - entry1.name = def1->ShortName(); entry1.id = def1->type ? *def1->type : QueryTypeId(); + entry1.field_name = def1->ShortName(); Expand(m, &entry1, levels - 1); entry->children.push_back(std::move(entry1)); }); + entry->numChildren = int(entry->children.size()); } } @@ -89,7 +95,6 @@ struct CqueryMemberHierarchyInitialHandler Out_CqueryMemberHierarchy::Entry entry; entry.id = root_id; - entry.name = def->ShortName(); Expand(this, &entry, levels); return entry; }