17 #include "llvm/ADT/ScopeExit.h"
18 #include "llvm/ADT/SmallVector.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/LineEditor/LineEditor.h"
21 #include "llvm/Support/CommandLine.h"
22 #include "llvm/Support/Signals.h"
28 llvm::cl::opt<std::string> IndexLocation(
29 llvm::cl::desc(
"<path to index file | remote:server.address>"),
30 llvm::cl::Positional);
32 llvm::cl::opt<std::string>
33 ExecCommand(
"c", llvm::cl::desc(
"Command to execute and then exit"));
35 llvm::cl::opt<std::string> ProjectRoot(
"project-root",
36 llvm::cl::desc(
"Path to the project"));
38 static constexpr
char Overview[] = R
"(
39 This is an **experimental** interactive tool to process user-provided search
40 queries over given symbol collection obtained via clangd-indexer. The
41 tool can be used to evaluate search quality of existing index implementations
42 and manually construct non-trivial test cases.
44 You can connect to remote index by passing remote:address to dexp. Example:
46 $ dexp remote:0.0.0.0:9000
48 Type use "help" request to get information about the details.
51 void reportTime(llvm::StringRef Name, llvm::function_ref<
void()> F) {
52 const auto TimerStart = std::chrono::high_resolution_clock::now();
54 const auto TimerStop = std::chrono::high_resolution_clock::now();
55 const auto Duration = std::chrono::duration_cast<std::chrono::milliseconds>(
56 TimerStop - TimerStart);
57 llvm::outs() << llvm::formatv(
"{0} took {1:ms+n}.\n", Name, Duration);
60 std::vector<SymbolID> getSymbolIDsFromIndex(llvm::StringRef QualifiedName,
61 const SymbolIndex *
Index) {
62 FuzzyFindRequest Request;
65 bool IsGlobalScope = QualifiedName.consume_front(
"::");
67 if (IsGlobalScope || !Names.first.empty())
68 Request.Scopes = {std::string(Names.first)};
72 Request.Scopes = {
""};
74 Request.Query = std::string(Names.second);
75 std::vector<SymbolID> SymIDs;
77 std::string SymQualifiedName = (Sym.Scope + Sym.Name).str();
78 if (QualifiedName == SymQualifiedName)
79 SymIDs.push_back(Sym.ID);
88 llvm::cl::opt<bool, false, llvm::cl::parser<bool>> Help{
89 "help", llvm::cl::desc(
"Display available options"),
90 llvm::cl::ValueDisallowed, llvm::cl::cat(llvm::cl::GeneralCategory)};
92 virtual void run() = 0;
98 virtual ~Command() =
default;
99 bool parseAndRun(llvm::ArrayRef<const char *> Argv,
const char *Overview,
100 const SymbolIndex &
Index) {
101 std::string ParseErrs;
102 llvm::raw_string_ostream
OS(ParseErrs);
103 bool Ok = llvm::cl::ParseCommandLineOptions(Argv.size(), Argv.data(),
106 auto Cleanup = llvm::make_scope_exit(llvm::cl::ResetCommandLineParser);
107 if (Help.getNumOccurrences() > 0) {
110 llvm::cl::PrintHelpMessage();
114 llvm::outs() <<
OS.str();
116 this->Index = &
Index;
117 reportTime(Argv[0], [&] { run(); });
132 class FuzzyFind :
public Command {
133 llvm::cl::opt<std::string> Query{
135 llvm::cl::Positional,
137 llvm::cl::desc(
"Query string to be fuzzy-matched"),
139 llvm::cl::opt<std::string> Scopes{
141 llvm::cl::desc(
"Allowed symbol scopes (comma-separated list)"),
143 llvm::cl::opt<unsigned> Limit{
146 llvm::cl::desc(
"Max results to display"),
149 void run()
override {
150 FuzzyFindRequest Request;
151 Request.Limit = Limit;
152 Request.Query = Query;
153 if (Scopes.getNumOccurrences() > 0) {
154 llvm::SmallVector<llvm::StringRef, 8> Scopes;
155 llvm::StringRef(this->Scopes).split(Scopes,
',');
156 Request.Scopes = {Scopes.begin(), Scopes.end()};
158 Request.AnyScope = Request.Scopes.empty();
160 static const auto *OutputFormat =
"{0,-4} | {1,-40} | {2,-25}\n";
161 llvm::outs() << llvm::formatv(OutputFormat,
"Rank",
"Symbol ID",
164 Index->fuzzyFind(Request, [&](
const Symbol &Sym) {
165 llvm::outs() << llvm::formatv(OutputFormat, Rank++, Sym.ID.str(),
166 Sym.Scope + Sym.Name);
171 class Lookup :
public Command {
172 llvm::cl::opt<std::string> ID{
174 llvm::cl::Positional,
175 llvm::cl::desc(
"Symbol ID to look up (hex)"),
177 llvm::cl::opt<std::string>
Name{
179 llvm::cl::desc(
"Qualified name to look up."),
182 void run()
override {
183 if (ID.getNumOccurrences() == 0 &&
Name.getNumOccurrences() == 0) {
185 <<
"Missing required argument: please provide id or -name.\n";
188 std::vector<SymbolID> IDs;
189 if (ID.getNumOccurrences()) {
197 IDs = getSymbolIDsFromIndex(Name,
Index);
200 LookupRequest Request;
201 Request.IDs.insert(IDs.begin(), IDs.end());
202 bool FoundSymbol =
false;
203 Index->lookup(Request, [&](
const Symbol &Sym) {
205 llvm::outs() <<
toYAML(Sym);
208 llvm::outs() <<
"not found\n";
212 class Refs :
public Command {
213 llvm::cl::opt<std::string> ID{
215 llvm::cl::Positional,
216 llvm::cl::desc(
"Symbol ID of the symbol being queried (hex)."),
218 llvm::cl::opt<std::string>
Name{
220 llvm::cl::desc(
"Qualified name of the symbol being queried."),
222 llvm::cl::opt<std::string> Filter{
224 llvm::cl::init(
".*"),
226 "Print all results from files matching this regular expression."),
229 void run()
override {
230 if (ID.getNumOccurrences() == 0 &&
Name.getNumOccurrences() == 0) {
232 <<
"Missing required argument: please provide id or -name.\n";
235 std::vector<SymbolID> IDs;
236 if (ID.getNumOccurrences()) {
244 IDs = getSymbolIDsFromIndex(Name,
Index);
245 if (IDs.size() > 1) {
246 llvm::outs() << llvm::formatv(
247 "The name {0} is ambiguous, found {1} different "
248 "symbols. Please use id flag to disambiguate.\n",
253 RefsRequest RefRequest;
254 RefRequest.IDs.insert(IDs.begin(), IDs.end());
255 llvm::Regex RegexFilter(Filter);
256 Index->refs(RefRequest, [&RegexFilter](
const Ref &R) {
259 llvm::outs() << U.takeError();
262 if (RegexFilter.match(U->body()))
263 llvm::outs() << R <<
"\n";
268 class Export :
public Command {
269 llvm::cl::opt<IndexFileFormat> Format{
271 llvm::cl::desc(
"Format of index export"),
274 "human-readable YAML format"),
278 llvm::cl::opt<std::string> OutputFile{
280 llvm::cl::Positional,
282 llvm::cl::desc(
"Output file for export"),
289 auto Buffer = llvm::MemoryBuffer::getFile(IndexLocation);
291 llvm::errs() << llvm::formatv(
"Can't open {0}", IndexLocation) <<
"\n";
304 llvm::raw_fd_ostream OutputStream(OutputFile, EC);
306 llvm::errs() << llvm::formatv(
"Can't open {0} for writing", OutputFile)
314 OutputStream << IndexOut;
323 {
"find",
"Search for symbols with fuzzyFind", std::make_unique<FuzzyFind>},
324 {
"lookup",
"Dump symbol details by ID or qualified name",
325 std::make_unique<Lookup>},
326 {
"refs",
"Find references by ID or qualified name", std::make_unique<Refs>},
327 {
"export",
"Export index", std::make_unique<Export>},
330 std::unique_ptr<SymbolIndex> openIndex(llvm::StringRef
Index) {
331 return Index.startswith(
"remote:")
339 std::replace(Request.begin(), Request.end(),
' ',
'\0');
340 llvm::SmallVector<llvm::StringRef, 8> Args;
341 llvm::StringRef(Request).split(Args,
'\0', -1,
345 if (Args.front() ==
"help") {
346 llvm::outs() <<
"dexp - Index explorer\nCommands:\n";
347 for (
const auto &C : CommandInfo)
348 llvm::outs() << llvm::formatv(
"{0,16} - {1}\n", C.Name, C.Description);
349 llvm::outs() <<
"Get detailed command help with e.g. `find -help`.\n";
352 llvm::SmallVector<const char *, 8> FakeArgv;
353 for (llvm::StringRef S : Args)
354 FakeArgv.push_back(S.data());
356 for (
const auto &Cmd : CommandInfo) {
357 if (Cmd.Name == Args.front())
358 return Cmd.Implementation()->parseAndRun(FakeArgv, Cmd.Description,
361 llvm::outs() <<
"Unknown command. Try 'help'.\n";
369 int main(
int argc,
const char *argv[]) {
372 llvm::cl::ParseCommandLineOptions(argc, argv, Overview);
373 llvm::cl::ResetCommandLineParser();
374 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
376 std::unique_ptr<SymbolIndex>
Index;
377 reportTime(llvm::StringRef(IndexLocation).startswith(
"remote:")
378 ?
"Remote index client creation"
380 [&]() {
Index = openIndex(IndexLocation); });
383 llvm::outs() <<
"Failed to open the index.\n";
387 if (!ExecCommand.empty())
388 return runCommand(ExecCommand, *
Index) ? 0 : 1;
390 llvm::LineEditor LE(
"dexp");
391 while (llvm::Optional<std::string> Request = LE.readLine())
392 runCommand(std::move(*Request), *
Index);