From 97832f2a738a610ac277333a6e5a08db8934d8ba Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Wed, 24 May 2017 00:17:29 -0700 Subject: [PATCH] Add call tree and full type hierarchy using upcoming vscode tree API --- README.md | 1 + src/command_line.cc | 182 ++++++++++++++++++++++++++++++++++++-- src/ipc.cc | 7 +- src/ipc.h | 5 +- src/language_server_api.h | 49 ++++++++++ 5 files changed, 236 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 48162660..523263aa 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ be productive with cquery. Here's a list of implemented features: * code actions (clang FixIts) * darken/fade code disabled by preprocessor * #include auto-complete and quick-jump (goto definition, document links) + * explorable call tree, expandable type hierarchy (requires vscode insiders) # Setup - build cquery, install extension, setup project diff --git a/src/command_line.cc b/src/command_line.cc index e8669a8f..e2c4516b 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -719,7 +719,109 @@ std::vector FindSymbolsAtLocation(WorkingFile* working_file, QueryFil return symbols; } +NonElidedVector BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root) { + optional& root_type = db->types[root.id]; + if (!root_type) + return {}; + NonElidedVector parent_entries; + parent_entries.reserve(root_type->def.parents.size()); + + for (QueryTypeId parent_id : root_type->def.parents) { + optional& parent_type = db->types[parent_id.id]; + if (!parent_type) + continue; + + Out_CqueryTypeHierarchyTree::TypeEntry parent_entry; + parent_entry.name = parent_type->def.detailed_name; + if (parent_type->def.definition_spelling) + parent_entry.location = GetLsLocation(db, working_files, *parent_type->def.definition_spelling); + parent_entry.children = BuildParentTypeHierarchy(db, working_files, parent_id); + + parent_entries.push_back(parent_entry); + } + + return parent_entries; +} + + +optional BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id) { + optional& root_type = db->types[root_id.id]; + if (!root_type) + return nullopt; + + Out_CqueryTypeHierarchyTree::TypeEntry entry; + + // Name and location. + entry.name = root_type->def.detailed_name; + if (root_type->def.definition_spelling) + entry.location = GetLsLocation(db, working_files, *root_type->def.definition_spelling); + + entry.children.reserve(root_type->derived.size()); + + // Base types. + Out_CqueryTypeHierarchyTree::TypeEntry base; + base.name = "[[Base]]"; + base.location = entry.location; + base.children = BuildParentTypeHierarchy(db, working_files, root_id); + if (!base.children.empty()) + entry.children.push_back(base); + + // Add derived. + for (QueryTypeId derived : root_type->derived) { + auto derived_entry = BuildTypeHierarchy(db, working_files, derived); + if (derived_entry) + entry.children.push_back(*derived_entry); + } + + return entry; +} + +NonElidedVector BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { + optional& root_func = db->funcs[root.id]; + if (!root_func) + return {}; + if (!root_func->def.definition_spelling) + return {}; + optional def_loc = GetLsLocation(db, working_files, *root_func->def.definition_spelling); + if (!def_loc) + return {}; + + Out_CqueryCallTree::CallEntry entry; + entry.name = root_func->def.short_name; + entry.usr = root_func->def.usr; + entry.location = *def_loc; + entry.hasCallers = !root_func->callers.empty(); + NonElidedVector result; + result.push_back(entry); + return result; +} + +NonElidedVector BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { + optional& root_func = db->funcs[root.id]; + if (!root_func) + return {}; + + NonElidedVector result; + result.reserve(root_func->callers.size()); + for (QueryFuncRef caller : root_func->callers) { + optional& call_func = db->funcs[caller.id.id]; + if (!call_func) + continue; + optional call_location = GetLsLocation(db, working_files, caller.loc); + if (!call_location) + continue; + + Out_CqueryCallTree::CallEntry call_entry; + call_entry.name = call_func->def.short_name; + call_entry.usr = call_func->def.usr; + call_entry.location = *call_location; + call_entry.hasCallers = !call_func->callers.empty(); + result.push_back(call_entry); + } + + return result; +} void PublishInactiveLines(WorkingFile* working_file, const std::vector& inactive) { Out_CquerySetInactiveRegion out; @@ -882,6 +984,9 @@ void RegisterMessageTypes() { MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); @@ -902,8 +1007,6 @@ void RegisterMessageTypes() { - - } // namespace @@ -1537,6 +1640,70 @@ bool QueryDbMainLoop( break; } + case IpcId::CqueryTypeHierarchyTree: { + auto msg = static_cast(message.get()); + + QueryFile* file = FindFile(db, msg->params.textDocument.uri.GetPath()); + if (!file) { + std::cerr << "Unable to find file " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + WorkingFile* working_file = working_files->GetFileByFilename(file->def.path); + + Out_CqueryTypeHierarchyTree response; + response.id = msg->id; + + for (const SymbolRef& ref : FindSymbolsAtLocation(working_file, file, msg->params.position)) { + if (ref.idx.kind == SymbolKind::Type) { + response.result = BuildTypeHierarchy(db, working_files, QueryTypeId(ref.idx.idx)); + break; + } + } + + ipc->SendOutMessageToClient(IpcId::CqueryTypeHierarchyTree, response); + break; + } + + case IpcId::CqueryCallTreeInitial: { + auto msg = static_cast(message.get()); + + QueryFile* file = FindFile(db, msg->params.textDocument.uri.GetPath()); + if (!file) { + std::cerr << "Unable to find file " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + WorkingFile* working_file = working_files->GetFileByFilename(file->def.path); + + Out_CqueryCallTree response; + response.id = msg->id; + + for (const SymbolRef& ref : FindSymbolsAtLocation(working_file, file, msg->params.position)) { + if (ref.idx.kind == SymbolKind::Func) { + response.result = BuildInitialCallTree(db, working_files, QueryFuncId(ref.idx.idx)); + break; + } + } + + response.Write(std::cerr); + ipc->SendOutMessageToClient(IpcId::CqueryCallTreeInitial, response); + break; + } + + case IpcId::CqueryCallTreeExpand: { + auto msg = static_cast(message.get()); + + Out_CqueryCallTree response; + response.id = msg->id; + + auto func_id = db->usr_to_func.find(msg->params.usr); + if (func_id != db->usr_to_func.end()) + response.result = BuildExpandCallTree(db, working_files, func_id->second); + + response.Write(std::cerr); + ipc->SendOutMessageToClient(IpcId::CqueryCallTreeExpand, response); + break; + } + case IpcId::CqueryVars: { auto msg = static_cast(message.get()); @@ -1557,7 +1724,7 @@ bool QueryDbMainLoop( response.result = GetLsLocations(db, working_files, locations); } } - ipc->SendOutMessageToClient(IpcId::TextDocumentReferences, response); + ipc->SendOutMessageToClient(IpcId::CqueryVars, response); break; } @@ -1581,7 +1748,7 @@ bool QueryDbMainLoop( response.result = GetLsLocations(db, working_files, locations); } } - ipc->SendOutMessageToClient(IpcId::TextDocumentReferences, response); + ipc->SendOutMessageToClient(IpcId::CqueryCallers, response); break; } @@ -1614,7 +1781,7 @@ bool QueryDbMainLoop( response.result.push_back(*ls_loc); } } - ipc->SendOutMessageToClient(IpcId::TextDocumentReferences, response); + ipc->SendOutMessageToClient(IpcId::CqueryBase, response); break; } @@ -1644,7 +1811,7 @@ bool QueryDbMainLoop( response.result = GetLsLocations(db, working_files, locations); } } - ipc->SendOutMessageToClient(IpcId::TextDocumentReferences, response); + ipc->SendOutMessageToClient(IpcId::CqueryDerived, response); break; } @@ -2588,6 +2755,9 @@ void LanguageServerStdinLoop(Config* config, std::unordered_map* r case IpcId::TextDocumentCodeLens: case IpcId::WorkspaceSymbol: case IpcId::CqueryFreshenIndex: + case IpcId::CqueryTypeHierarchyTree: + case IpcId::CqueryCallTreeInitial: + case IpcId::CqueryCallTreeExpand: case IpcId::CqueryVars: case IpcId::CqueryCallers: case IpcId::CqueryBase: diff --git a/src/ipc.cc b/src/ipc.cc index 42776dee..ff10e5bf 100644 --- a/src/ipc.cc +++ b/src/ipc.cc @@ -54,7 +54,12 @@ const char* IpcIdToString(IpcId id) { case IpcId::CqueryFreshenIndex: return "$cquery/freshenIndex"; - + case IpcId::CqueryTypeHierarchyTree: + return "$cquery/typeHierarchyTree"; + case IpcId::CqueryCallTreeInitial: + return "$cquery/callTreeInitial"; + case IpcId::CqueryCallTreeExpand: + return "$cquery/callTreeExpand"; case IpcId::CqueryVars: return "$cquery/vars"; case IpcId::CqueryCallers: diff --git a/src/ipc.h b/src/ipc.h index 8aa1d5bd..b8a9cd99 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -35,7 +35,10 @@ enum class IpcId : int { // Custom messages CqueryFreshenIndex, - + // Messages used in tree views. + CqueryTypeHierarchyTree, + CqueryCallTreeInitial, + CqueryCallTreeExpand, // These are like DocumentReferences but show different types of data. CqueryVars, // Show all variables of a type. CqueryCallers, // Show all callers of a function. diff --git a/src/language_server_api.h b/src/language_server_api.h index f8f296e2..c7b423bc 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -1587,6 +1587,55 @@ struct Ipc_CqueryFreshenIndex : public IpcMessage { }; MAKE_REFLECT_STRUCT(Ipc_CqueryFreshenIndex, id); +// Type Hierarchy Tree +struct Ipc_CqueryTypeHierarchyTree : public IpcMessage { + const static IpcId kIpcId = IpcId::CqueryTypeHierarchyTree; + lsRequestId id; + lsTextDocumentPositionParams params; +}; +MAKE_REFLECT_STRUCT(Ipc_CqueryTypeHierarchyTree, id, params); +struct Out_CqueryTypeHierarchyTree : public lsOutMessage { + struct TypeEntry { + std::string name; + optional location; + NonElidedVector children; + }; + lsRequestId id; + optional result; +}; +MAKE_REFLECT_STRUCT(Out_CqueryTypeHierarchyTree::TypeEntry, name, location, children); +MAKE_REFLECT_STRUCT(Out_CqueryTypeHierarchyTree, jsonrpc, id, result); + +// Call Tree +struct Ipc_CqueryCallTreeInitial : public IpcMessage { + const static IpcId kIpcId = IpcId::CqueryCallTreeInitial; + lsRequestId id; + lsTextDocumentPositionParams params; +}; +MAKE_REFLECT_STRUCT(Ipc_CqueryCallTreeInitial, id, params); +struct Ipc_CqueryCallTreeExpand : public IpcMessage { + struct Params { + std::string usr; + }; + const static IpcId kIpcId = IpcId::CqueryCallTreeExpand; + lsRequestId id; + Params params; +}; +MAKE_REFLECT_STRUCT(Ipc_CqueryCallTreeExpand::Params, usr); +MAKE_REFLECT_STRUCT(Ipc_CqueryCallTreeExpand, id, params); +struct Out_CqueryCallTree : public lsOutMessage { + struct CallEntry { + std::string name; + std::string usr; + lsLocation location; + bool hasCallers = true; + }; + + lsRequestId id; + NonElidedVector result; +}; +MAKE_REFLECT_STRUCT(Out_CqueryCallTree::CallEntry, name, usr, location, hasCallers); +MAKE_REFLECT_STRUCT(Out_CqueryCallTree, jsonrpc, id, result); // Vars, Callers, Derived, GotoParent struct Ipc_CqueryVars : public IpcMessage {