ccls/src/messages/ccls_call.cc

245 lines
7.6 KiB
C++
Raw Normal View History

2018-08-21 05:27:52 +00:00
/* Copyright 2017-2018 ccls Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "hierarchy.hh"
#include "message_handler.h"
2018-05-28 00:50:02 +00:00
#include "pipeline.hh"
#include "query_utils.h"
#include <unordered_set>
using namespace ccls;
namespace {
MethodType kMethodType = "$ccls/call";
2018-03-20 02:51:42 +00:00
enum class CallType : uint8_t {
Direct = 0,
Base = 1,
Derived = 2,
All = 1 | 2
};
MAKE_REFLECT_TYPE_PROXY(CallType);
bool operator&(CallType lhs, CallType rhs) {
return uint8_t(lhs) & uint8_t(rhs);
}
struct In_cclsCall : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
struct Params {
// If id is specified, expand a node; otherwise textDocument+position should
// be specified for building the root and |levels| of nodes below.
lsTextDocumentIdentifier textDocument;
lsPosition position;
Usr usr;
std::string id;
// true: callee tree (functions called by this function); false: caller tree
// (where this function is called)
bool callee = false;
// Base: include base functions; All: include both base and derived
// functions.
CallType callType = CallType::All;
bool qualified = true;
int levels = 1;
bool hierarchy = false;
};
Params params;
};
MAKE_REFLECT_STRUCT(In_cclsCall::Params, textDocument, position, id, callee,
callType, qualified, levels, hierarchy);
MAKE_REFLECT_STRUCT(In_cclsCall, id, params);
REGISTER_IN_MESSAGE(In_cclsCall);
struct Out_cclsCall {
Usr usr;
std::string id;
std::string_view name;
lsLocation location;
CallType callType = CallType::Direct;
int numChildren;
// Empty if the |levels| limit is reached.
std::vector<Out_cclsCall> children;
bool operator==(const Out_cclsCall &o) const {
return location == o.location;
}
bool operator<(const Out_cclsCall &o) const { return location < o.location; }
};
MAKE_REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren,
children);
bool Expand(MessageHandler *m, Out_cclsCall *entry, bool callee,
2018-08-09 17:08:14 +00:00
CallType call_type, bool qualified, int levels) {
const QueryFunc &func = m->db->Func(entry->usr);
const QueryFunc::Def *def = func.AnyDef();
entry->numChildren = 0;
if (!def)
2018-02-26 03:10:02 +00:00
return false;
auto handle = [&](SymbolRef sym, int file_id, CallType call_type1) {
entry->numChildren++;
if (levels > 0) {
Out_cclsCall entry1;
entry1.id = std::to_string(sym.usr);
entry1.usr = sym.usr;
if (auto loc = GetLsLocation(m->db, m->working_files,
Use{{sym.range, sym.role}, file_id}))
2018-03-01 01:53:43 +00:00
entry1.location = *loc;
entry1.callType = call_type1;
if (Expand(m, &entry1, callee, call_type, qualified, levels - 1))
2018-02-26 03:10:02 +00:00
entry->children.push_back(std::move(entry1));
}
};
2018-08-09 17:08:14 +00:00
auto handle_uses = [&](const QueryFunc &func, CallType call_type) {
if (callee) {
2018-08-09 17:08:14 +00:00
if (const auto *def = func.AnyDef())
for (SymbolRef sym : def->callees)
if (sym.kind == SymbolKind::Func)
handle(sym, def->file_id, call_type);
} else {
for (Use use : func.uses) {
const QueryFile &file1 = m->db->files[use.file_id];
Maybe<ExtentRef> best;
for (auto [sym, refcnt] : file1.symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() &&
sym.kind == SymbolKind::Func &&
sym.extent.start <= use.range.start &&
use.range.end <= sym.extent.end &&
(!best || best->extent.start < sym.extent.start))
best = sym;
if (best)
handle(*best, use.file_id, call_type);
}
}
};
std::unordered_set<Usr> seen;
seen.insert(func.usr);
2018-08-09 17:08:14 +00:00
std::vector<const QueryFunc *> stack;
entry->name = def->Name(qualified);
handle_uses(func, CallType::Direct);
// Callers/callees of base functions.
if (call_type & CallType::Base) {
stack.push_back(&func);
while (stack.size()) {
2018-08-09 17:08:14 +00:00
const QueryFunc &func1 = *stack.back();
stack.pop_back();
2018-08-09 17:08:14 +00:00
if (auto *def1 = func1.AnyDef()) {
EachDefinedFunc(m->db, def1->bases, [&](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.
if (call_type & CallType::Derived) {
stack.push_back(&func);
while (stack.size()) {
2018-08-09 17:08:14 +00:00
const QueryFunc &func1 = *stack.back();
stack.pop_back();
2018-08-09 17:08:14 +00:00
EachDefinedFunc(m->db, func1.derived, [&](QueryFunc &func2) {
if (!seen.count(func2.usr)) {
seen.insert(func2.usr);
stack.push_back(&func2);
handle_uses(func2, CallType::Derived);
}
});
}
}
std::sort(entry->children.begin(), entry->children.end());
entry->children.erase(
std::unique(entry->children.begin(), entry->children.end()),
entry->children.end());
2018-02-26 03:10:02 +00:00
return true;
}
struct Handler_cclsCall : BaseMessageHandler<In_cclsCall> {
MethodType GetMethodType() const override { return kMethodType; }
std::optional<Out_cclsCall> BuildInitial(Usr root_usr, bool callee,
CallType call_type, bool qualified,
int levels) {
2018-08-09 17:08:14 +00:00
const auto *def = db->Func(root_usr).AnyDef();
if (!def)
return {};
Out_cclsCall entry;
entry.id = std::to_string(root_usr);
entry.usr = root_usr;
2018-02-26 03:10:02 +00:00
entry.callType = CallType::Direct;
2018-03-01 01:53:43 +00:00
if (def->spell) {
2018-03-31 03:16:33 +00:00
if (std::optional<lsLocation> loc =
2018-03-20 02:51:42 +00:00
GetLsLocation(db, working_files, *def->spell))
2018-03-01 01:53:43 +00:00
entry.location = *loc;
}
Expand(this, &entry, callee, call_type, qualified, levels);
return entry;
}
void Run(In_cclsCall *request) override {
2018-08-09 17:08:14 +00:00
auto &params = request->params;
std::optional<Out_cclsCall> result;
if (params.id.size()) {
try {
params.usr = std::stoull(params.id);
} catch (...) {
return;
}
result.emplace();
result->id = std::to_string(params.usr);
result->usr = params.usr;
result->callType = CallType::Direct;
if (db->HasFunc(params.usr))
Expand(this, &*result, params.callee, params.callType, params.qualified,
params.levels);
} else {
2018-08-09 17:08:14 +00:00
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
2018-08-09 17:08:14 +00:00
WorkingFile *working_file =
working_files->GetFileByFilename(file->def->path);
for (SymbolRef sym :
2018-03-20 02:51:42 +00:00
FindSymbolsAtLocation(working_file, file, params.position)) {
if (sym.kind == SymbolKind::Func) {
result = BuildInitial(sym.usr, params.callee, params.callType,
params.qualified, params.levels);
break;
}
}
}
if (params.hierarchy)
pipeline::Reply(request->id, result);
else {
auto out = FlattenHierarchy(result);
pipeline::Reply(request->id, out);
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsCall);
2018-08-09 17:08:14 +00:00
} // namespace