Add callHierarchy

This commit is contained in:
Fangrui Song 2022-11-03 00:20:22 -07:00
parent 74458915b3
commit 8a93950fb9
4 changed files with 157 additions and 1 deletions

View File

@ -28,6 +28,7 @@ REFLECT_STRUCT(TextDocumentContentChangeEvent, range, rangeLength, text);
REFLECT_STRUCT(TextDocumentDidChangeParam, textDocument, contentChanges); REFLECT_STRUCT(TextDocumentDidChangeParam, textDocument, contentChanges);
REFLECT_STRUCT(TextDocumentPositionParam, textDocument, position); REFLECT_STRUCT(TextDocumentPositionParam, textDocument, position);
REFLECT_STRUCT(RenameParam, textDocument, position, newName); REFLECT_STRUCT(RenameParam, textDocument, position, newName);
REFLECT_STRUCT(CallsParam, item);
// completion // completion
REFLECT_UNDERLYING(CompletionTriggerKind); REFLECT_UNDERLYING(CompletionTriggerKind);
@ -162,6 +163,8 @@ MessageHandler::MessageHandler() {
bind("$ccls/navigate", &MessageHandler::ccls_navigate); bind("$ccls/navigate", &MessageHandler::ccls_navigate);
bind("$ccls/reload", &MessageHandler::ccls_reload); bind("$ccls/reload", &MessageHandler::ccls_reload);
bind("$ccls/vars", &MessageHandler::ccls_vars); bind("$ccls/vars", &MessageHandler::ccls_vars);
bind("callHierarchy/incomingCalls", &MessageHandler::callHierarchy_incomingCalls);
bind("callHierarchy/outgoingCalls", &MessageHandler::callHierarchy_outgoingCalls);
bind("exit", &MessageHandler::exit); bind("exit", &MessageHandler::exit);
bind("initialize", &MessageHandler::initialize); bind("initialize", &MessageHandler::initialize);
bind("initialized", &MessageHandler::initialized); bind("initialized", &MessageHandler::initialized);
@ -183,6 +186,7 @@ MessageHandler::MessageHandler() {
bind("textDocument/hover", &MessageHandler::textDocument_hover); bind("textDocument/hover", &MessageHandler::textDocument_hover);
bind("textDocument/implementation", &MessageHandler::textDocument_implementation); bind("textDocument/implementation", &MessageHandler::textDocument_implementation);
bind("textDocument/onTypeFormatting", &MessageHandler::textDocument_onTypeFormatting); bind("textDocument/onTypeFormatting", &MessageHandler::textDocument_onTypeFormatting);
bind("textDocument/prepareCallHierarchy", &MessageHandler::textDocument_prepareCallHierarchy);
bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting); bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting);
bind("textDocument/references", &MessageHandler::textDocument_references); bind("textDocument/references", &MessageHandler::textDocument_references);
bind("textDocument/rename", &MessageHandler::textDocument_rename); bind("textDocument/rename", &MessageHandler::textDocument_rename);

View File

@ -58,6 +58,22 @@ struct WorkspaceEdit {
}; };
REFLECT_STRUCT(WorkspaceEdit, documentChanges); 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 // completion
enum class CompletionTriggerKind { enum class CompletionTriggerKind {
Invoked = 1, Invoked = 1,
@ -259,6 +275,8 @@ private:
void ccls_navigate(JsonReader &, ReplyOnce &); void ccls_navigate(JsonReader &, ReplyOnce &);
void ccls_reload(JsonReader &); void ccls_reload(JsonReader &);
void ccls_vars(JsonReader &, ReplyOnce &); void ccls_vars(JsonReader &, ReplyOnce &);
void callHierarchy_incomingCalls(CallsParam &param, ReplyOnce &);
void callHierarchy_outgoingCalls(CallsParam &param, ReplyOnce &);
void exit(EmptyParam &); void exit(EmptyParam &);
void initialize(JsonReader &, ReplyOnce &); void initialize(JsonReader &, ReplyOnce &);
void initialized(EmptyParam &); void initialized(EmptyParam &);
@ -281,6 +299,8 @@ private:
void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &); void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &, void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &,
ReplyOnce &); ReplyOnce &);
void textDocument_prepareCallHierarchy(TextDocumentPositionParam &,
ReplyOnce &);
void textDocument_rangeFormatting(DocumentRangeFormattingParam &, void textDocument_rangeFormatting(DocumentRangeFormattingParam &,
ReplyOnce &); ReplyOnce &);
void textDocument_references(JsonReader &, ReplyOnce &); void textDocument_references(JsonReader &, ReplyOnce &);

View File

@ -6,6 +6,7 @@
#include "pipeline.hh" #include "pipeline.hh"
#include "query.hh" #include "query.hh"
#include <map>
#include <unordered_set> #include <unordered_set>
namespace ccls { namespace ccls {
@ -60,6 +61,18 @@ struct Out_cclsCall {
REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren, REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren,
children); children);
struct Out_incomingCall {
CallHierarchyItem from;
std::vector<lsRange> fromRanges;
};
REFLECT_STRUCT(Out_incomingCall, from, fromRanges);
struct Out_outgoingCall {
CallHierarchyItem to;
std::vector<lsRange> fromRanges;
};
REFLECT_STRUCT(Out_outgoingCall, to, fromRanges);
bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee,
CallType call_type, bool qualified, int levels) { CallType call_type, bool qualified, int levels) {
const QueryFunc &func = m->db->getFunc(entry->usr); const QueryFunc &func = m->db->getFunc(entry->usr);
@ -205,4 +218,122 @@ void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) {
else else
reply(flattenHierarchy(result)); reply(flattenHierarchy(result));
} }
void MessageHandler::textDocument_prepareCallHierarchy(
TextDocumentPositionParam &param, ReplyOnce &reply) {
std::string path = param.textDocument.uri.getPath();
auto [file, wf] = findOrFail(path, reply);
if (!file)
return;
std::vector<CallHierarchyItem> 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<SymbolIdx, std::pair<int, std::vector<lsRange>>> &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 <typename Out>
static std::vector<Out> toCallResult(
DB *db,
const std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> &sym2ranges) {
std::vector<Out> 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 &param,
ReplyOnce &reply) {
Usr usr;
try {
usr = std::stoull(param.item.data);
} catch (...) {
return;
}
const QueryFunc &func = db->getFunc(usr);
std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> sym2ranges;
for (Use use : func.uses) {
const QueryFile &file = db->files[use.file_id];
Maybe<ExtentRef> 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<Out_incomingCall>(db, sym2ranges));
}
void MessageHandler::callHierarchy_outgoingCalls(CallsParam &param,
ReplyOnce &reply) {
Usr usr;
try {
usr = std::stoull(param.item.data);
} catch (...) {
return;
}
const QueryFunc &func = db->getFunc(usr);
std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> 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<Out_outgoingCall>(db, sym2ranges));
}
} // namespace ccls } // namespace ccls

View File

@ -88,6 +88,7 @@ struct ServerCap {
struct ExecuteCommandOptions { struct ExecuteCommandOptions {
std::vector<const char *> commands = {ccls_xref}; std::vector<const char *> commands = {ccls_xref};
} executeCommandProvider; } executeCommandProvider;
bool callHierarchyProvider = true;
Config::ServerCap::Workspace workspace; Config::ServerCap::Workspace workspace;
}; };
REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds); REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds);
@ -109,7 +110,7 @@ REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider,
documentRangeFormattingProvider, documentRangeFormattingProvider,
documentOnTypeFormattingProvider, renameProvider, documentOnTypeFormattingProvider, renameProvider,
documentLinkProvider, foldingRangeProvider, documentLinkProvider, foldingRangeProvider,
executeCommandProvider, workspace); executeCommandProvider, callHierarchyProvider, workspace);
struct DynamicReg { struct DynamicReg {
bool dynamicRegistration = false; bool dynamicRegistration = false;