diff --git a/src/message_handler.cc b/src/message_handler.cc index 498a72de..09ee6cbf 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -28,6 +28,7 @@ REFLECT_STRUCT(TextDocumentContentChangeEvent, range, rangeLength, text); REFLECT_STRUCT(TextDocumentDidChangeParam, textDocument, contentChanges); REFLECT_STRUCT(TextDocumentPositionParam, textDocument, position); REFLECT_STRUCT(RenameParam, textDocument, position, newName); +REFLECT_STRUCT(CallsParam, item); // completion REFLECT_UNDERLYING(CompletionTriggerKind); @@ -162,6 +163,8 @@ MessageHandler::MessageHandler() { bind("$ccls/navigate", &MessageHandler::ccls_navigate); bind("$ccls/reload", &MessageHandler::ccls_reload); bind("$ccls/vars", &MessageHandler::ccls_vars); + bind("callHierarchy/incomingCalls", &MessageHandler::callHierarchy_incomingCalls); + bind("callHierarchy/outgoingCalls", &MessageHandler::callHierarchy_outgoingCalls); bind("exit", &MessageHandler::exit); bind("initialize", &MessageHandler::initialize); bind("initialized", &MessageHandler::initialized); @@ -183,6 +186,7 @@ MessageHandler::MessageHandler() { bind("textDocument/hover", &MessageHandler::textDocument_hover); bind("textDocument/implementation", &MessageHandler::textDocument_implementation); bind("textDocument/onTypeFormatting", &MessageHandler::textDocument_onTypeFormatting); + bind("textDocument/prepareCallHierarchy", &MessageHandler::textDocument_prepareCallHierarchy); bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting); bind("textDocument/references", &MessageHandler::textDocument_references); bind("textDocument/rename", &MessageHandler::textDocument_rename); diff --git a/src/message_handler.hh b/src/message_handler.hh index 7718e346..a5833fee 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -58,6 +58,22 @@ struct WorkspaceEdit { }; REFLECT_STRUCT(WorkspaceEdit, documentChanges); +struct CallHierarchyItem { + std::string name; + SymbolKind kind; + std::string detail; + DocumentUri uri; + lsRange range; + lsRange selectionRange; + std::string data; +}; +REFLECT_STRUCT(CallHierarchyItem, name, kind, detail, uri, range, + selectionRange, data); + +struct CallsParam { + CallHierarchyItem item; +}; + // completion enum class CompletionTriggerKind { Invoked = 1, @@ -259,6 +275,8 @@ private: void ccls_navigate(JsonReader &, ReplyOnce &); void ccls_reload(JsonReader &); void ccls_vars(JsonReader &, ReplyOnce &); + void callHierarchy_incomingCalls(CallsParam ¶m, ReplyOnce &); + void callHierarchy_outgoingCalls(CallsParam ¶m, ReplyOnce &); void exit(EmptyParam &); void initialize(JsonReader &, ReplyOnce &); void initialized(EmptyParam &); @@ -281,6 +299,8 @@ private: void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &); void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &, ReplyOnce &); + void textDocument_prepareCallHierarchy(TextDocumentPositionParam &, + ReplyOnce &); void textDocument_rangeFormatting(DocumentRangeFormattingParam &, ReplyOnce &); void textDocument_references(JsonReader &, ReplyOnce &); diff --git a/src/messages/ccls_call.cc b/src/messages/ccls_call.cc index 5fd3021e..3cb2850e 100644 --- a/src/messages/ccls_call.cc +++ b/src/messages/ccls_call.cc @@ -6,6 +6,7 @@ #include "pipeline.hh" #include "query.hh" +#include #include namespace ccls { @@ -60,6 +61,18 @@ struct Out_cclsCall { REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren, children); +struct Out_incomingCall { + CallHierarchyItem from; + std::vector fromRanges; +}; +REFLECT_STRUCT(Out_incomingCall, from, fromRanges); + +struct Out_outgoingCall { + CallHierarchyItem to; + std::vector fromRanges; +}; +REFLECT_STRUCT(Out_outgoingCall, to, fromRanges); + bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_type, bool qualified, int levels) { const QueryFunc &func = m->db->getFunc(entry->usr); @@ -205,4 +218,122 @@ void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) { else reply(flattenHierarchy(result)); } + +void MessageHandler::textDocument_prepareCallHierarchy( + TextDocumentPositionParam ¶m, ReplyOnce &reply) { + std::string path = param.textDocument.uri.getPath(); + auto [file, wf] = findOrFail(path, reply); + if (!file) + return; + + std::vector result; + for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) { + if (sym.kind != Kind::Func) + continue; + const auto *def = db->getFunc(sym.usr).anyDef(); + if (!def) + continue; + auto r = getLsRange(wf, sym.range); + if (!r) + continue; + CallHierarchyItem &item = result.emplace_back(); + item.name = def->name(false); + item.kind = def->kind; + item.detail = def->name(true); + item.uri = DocumentUri::fromPath(path); + item.range = item.selectionRange = *r; + item.data = std::to_string(sym.usr); + } + reply(result); +} + +static lsRange toLsRange(Range r) { + return {{r.start.line, r.start.column}, {r.end.line, r.end.column}}; +} + +static void +add(std::map>> &sym2ranges, + SymbolRef sym, int file_id) { + auto [it, inserted] = sym2ranges.try_emplace(SymbolIdx{sym.usr, sym.kind}); + if (inserted) + it->second.first = file_id; + if (it->second.first == file_id) + it->second.second.push_back(toLsRange(sym.range)); +} + +template +static std::vector toCallResult( + DB *db, + const std::map>> &sym2ranges) { + std::vector result; + for (auto &[sym, ranges] : sym2ranges) { + CallHierarchyItem item; + item.uri = getLsDocumentUri(db, ranges.first); + auto r = ranges.second[0]; + item.range = {{uint16_t(r.start.line), int16_t(r.start.character)}, + {uint16_t(r.end.line), int16_t(r.end.character)}}; + item.selectionRange = item.range; + switch (sym.kind) { + default: + continue; + case Kind::Func: { + auto idx = db->func_usr[sym.usr]; + const QueryFunc &func = db->funcs[idx]; + const QueryFunc::Def *def = func.anyDef(); + if (!def) + continue; + item.name = def->name(false); + item.kind = def->kind; + item.detail = def->name(true); + item.data = std::to_string(sym.usr); + } + } + + result.push_back({std::move(item), std::move(ranges.second)}); + } + return result; +} + +void MessageHandler::callHierarchy_incomingCalls(CallsParam ¶m, + ReplyOnce &reply) { + Usr usr; + try { + usr = std::stoull(param.item.data); + } catch (...) { + return; + } + const QueryFunc &func = db->getFunc(usr); + std::map>> sym2ranges; + for (Use use : func.uses) { + const QueryFile &file = db->files[use.file_id]; + Maybe best; + for (auto [sym, refcnt] : file.symbol2refcnt) + if (refcnt > 0 && sym.extent.valid() && sym.kind == Kind::Func && + sym.extent.start <= use.range.start && + use.range.end <= sym.extent.end && + (!best || best->extent.start < sym.extent.start)) + best = sym; + if (best) + add(sym2ranges, *best, use.file_id); + } + reply(toCallResult(db, sym2ranges)); +} + +void MessageHandler::callHierarchy_outgoingCalls(CallsParam ¶m, + ReplyOnce &reply) { + Usr usr; + try { + usr = std::stoull(param.item.data); + } catch (...) { + return; + } + const QueryFunc &func = db->getFunc(usr); + std::map>> sym2ranges; + if (const auto *def = func.anyDef()) + for (SymbolRef sym : def->callees) + if (sym.kind == Kind::Func) { + add(sym2ranges, sym, def->file_id); + } + reply(toCallResult(db, sym2ranges)); +} } // namespace ccls diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index ea07006d..e7a71d54 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -88,6 +88,7 @@ struct ServerCap { struct ExecuteCommandOptions { std::vector commands = {ccls_xref}; } executeCommandProvider; + bool callHierarchyProvider = true; Config::ServerCap::Workspace workspace; }; REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds); @@ -109,7 +110,7 @@ REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider, documentRangeFormattingProvider, documentOnTypeFormattingProvider, renameProvider, documentLinkProvider, foldingRangeProvider, - executeCommandProvider, workspace); + executeCommandProvider, callHierarchyProvider, workspace); struct DynamicReg { bool dynamicRegistration = false;