From bf98dc56fb15f4d47c6f9faf245dc1d84b5907a2 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sun, 5 Mar 2017 11:48:05 -0800 Subject: [PATCH] wip --- command_line.cc | 872 ++++++++++++++++++--------------- compilation_database_loader.cc | 34 +- indexer.cpp | 36 +- ipc.cc | 4 +- ipc.h | 9 +- language_server_api.h | 32 +- old.cc | 175 +++++++ platform.h | 4 +- platform_win.cc | 19 + query.cc | 40 +- task.cc | 4 +- test.cc | 6 +- utils.cc | 14 +- utils.h | 3 +- 14 files changed, 795 insertions(+), 457 deletions(-) create mode 100644 old.cc diff --git a/command_line.cc b/command_line.cc index 118a0451..3d07f3d9 100644 --- a/command_line.cc +++ b/command_line.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include "compilation_database_loader.h" #include "indexer.h" @@ -9,6 +10,8 @@ #include "query.h" #include "language_server_api.h" +#include "third_party/tiny-process-library/process.hpp" + #include #include @@ -17,40 +20,6 @@ #include #endif -bool ParsePreferredSymbolLocation(const std::string& content, PreferredSymbolLocation* obj) { -#define PARSE_AS(name, string) \ - if (content == #string) { \ - *obj = name; \ - return true; \ - } - - PARSE_AS(PreferredSymbolLocation::Declaration, "declaration"); - PARSE_AS(PreferredSymbolLocation::Definition, "definition"); - - return false; -#undef PARSE_AS -} - -bool ParseCommand(const std::string& content, Command* obj) { -#define PARSE_AS(name, string) \ - if (content == #string) { \ - *obj = name; \ - return true; \ - } - - PARSE_AS(Command::Callees, "callees"); - PARSE_AS(Command::Callers, "callers"); - PARSE_AS(Command::FindAllUsages, "find-all-usages"); - PARSE_AS(Command::FindInterestingUsages, "find-interesting-usages"); - PARSE_AS(Command::GotoReferenced, "goto-referenced"); - PARSE_AS(Command::Hierarchy, "hierarchy"); - PARSE_AS(Command::Outline, "outline"); - PARSE_AS(Command::Search, "search"); - - return false; -#undef PARSE_AS -} - std::unordered_map ParseOptions(int argc, char** argv) { std::unordered_map output; @@ -82,118 +51,7 @@ bool HasOption(const std::unordered_map& options, cons return options.find(option) != options.end(); } -/* -// Connects to a running --project-directory instance. Forks -// and creates it if not running. -// -// Implements language server spec. -indexer.exe --language-server - -// Holds the runtime db that the --language-server instance -// runs queries against. -indexer.exe --project-directory /work2/chrome/src - -// Created from the --project-directory (server) instance -indexer.exe --index-file /work2/chrome/src/chrome/foo.cc - -// Configuration data is read from a JSON file. -{ - "max_threads": 40, - "cache_directory": "/work/indexer_cache/" - -} -*/ - - -struct IpcMessage_IsAlive : public BaseIpcMessage { - static IpcMessageId id; -}; - -IpcMessageId IpcMessage_IsAlive::id = "IsAlive"; - - - - - - - - - - - - - -struct IpcMessage_DocumentSymbolsRequest : public BaseIpcMessage { - std::string document; - - // BaseIpcMessage: - static IpcMessageId id; - void Serialize(Writer& writer) override { - writer.String(document.c_str()); - } - void Deserialize(Reader& reader) override { - document = reader.GetString(); - } -}; -IpcMessageId IpcMessage_DocumentSymbolsRequest::id = "IpcMessage_DocumentSymbolsRequest"; - -struct IpcMessage_DocumentSymbolsResponse : public BaseIpcMessage { - std::vector symbols; - - // BaseIpcMessage: - static IpcMessageId id; -}; -IpcMessageId IpcMessage_DocumentSymbolsResponse::id = "IpcMessage_DocumentSymbolsResponse"; - - - - - -void QueryDbMain() { - IpcServer ipc("languageserver"); - - while (true) { - std::vector> messages = ipc.TakeMessages(); - - for (auto& message : messages) { - std::cout << "Processing message " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; - - if (message->runtime_id() == IpcMessage_IsAlive::id) { - IpcMessage_IsAlive response; - ipc.SendToClient(0, &response); // todo: make non-blocking - break; - } - else { - std::cerr << "Unhandled IPC message with kind " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; - exit(1); - break; - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } -} - -void EmitReferences(IpcClient& ipc) { - -} - -// Separate thread whose only job is to read from stdin and -// dispatch read commands to the actual indexer program. This -// cannot be done on the main thread because reading from std::cin -// blocks. -void LanguageServerStdinToServerDispatcher(IpcClient& ipc) { - while (true) { - std::string input; - std::cin >> input; - - } -} - - -void ParseRpc(const std::string& method, const rapidjson::GenericValue>& params) { -} std::unique_ptr ParseMessage() { int content_length = -1; @@ -216,7 +74,11 @@ std::unique_ptr ParseMessage() { break; } - assert(content_length >= 0); + // bad input that is not a message. + if (content_length < 0) { + std::cerr << "parsing command failed (no Content-Length header)" << std::endl; + return nullptr; + } std::string content; content.reserve(content_length); @@ -231,41 +93,322 @@ std::unique_ptr ParseMessage() { assert(!document.HasParseError()); return language_server_api::MessageRegistry::instance()->Parse(document); - - /* - std::string id; - if (document["id"].IsString()) - id = document["id"].GetString(); - else - id = std::to_string(document["id"].GetInt()); - std::string method = document["method"].GetString(); - auto& params = document["params"]; - - - // Send initialize response. - { - std::string content = - R"foo({ - "jsonrpc": "2.0", - "id": 0, - "result": { - "capabilities": { - "documentSymbolProvider": true - } - } - })foo"; - std::cout << "Content-Length: " << content.size(); - std::cout << (char)13 << char(10) << char(13) << char(10); - std::cout << content; - } - */ } -// Main loop for the language server. |ipc| is connected to -// a server. -void LanguageServerLoop(IpcClient* ipc) { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct IpcMessage_Quit : public BaseIpcMessage { + static IpcMessageId kId; +}; +IpcMessageId IpcMessage_Quit::kId = "Quit"; + + +struct IpcMessage_IsAlive : public BaseIpcMessage { + static IpcMessageId kId; +}; +IpcMessageId IpcMessage_IsAlive::kId = "IsAlive"; + + + + + +struct IpcMessage_OpenProject : public BaseIpcMessage { + static IpcMessageId kId; + + std::string project_path; + + // BaseIpcMessage: + void Serialize(Writer& writer) override { + writer.String(project_path.c_str(), project_path.size()); + } + void Deserialize(Reader& reader) override { + project_path = reader.GetString(); + } +}; +IpcMessageId IpcMessage_OpenProject::kId = "OpenProject"; + + + + + + + + +struct IpcMessage_DocumentSymbolsRequest : public BaseIpcMessage { + language_server_api::RequestId id; + std::string document; + + // BaseIpcMessage: + static IpcMessageId kId; + void Serialize(Writer& writer) override { + using namespace language_server_api; + auto& value = *this; + + writer.StartObject(); + SERIALIZE_MEMBER(id); + SERIALIZE_MEMBER(document); + writer.EndObject(); + } + void Deserialize(Reader& reader) override { + using namespace language_server_api; + auto& value = *this; + + DESERIALIZE_MEMBER(id); + DESERIALIZE_MEMBER(document); + } +}; +IpcMessageId IpcMessage_DocumentSymbolsRequest::kId = "IpcMessage_DocumentSymbolsRequest"; + +struct IpcMessage_DocumentSymbolsResponse : public BaseIpcMessage { + language_server_api::RequestId id; + std::vector symbols; + + // BaseIpcMessage: + static IpcMessageId kId; + void Serialize(Writer& writer) override { + using namespace language_server_api; + auto& value = *this; + + writer.StartObject(); + SERIALIZE_MEMBER(id); + SERIALIZE_MEMBER(symbols); + writer.EndObject(); + } + void Deserialize(Reader& reader) override { + using namespace language_server_api; + auto& value = *this; + + DESERIALIZE_MEMBER(id); + DESERIALIZE_MEMBER(symbols); + } +}; +IpcMessageId IpcMessage_DocumentSymbolsResponse::kId = "IpcMessage_DocumentSymbolsResponse"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +void QueryDbMainLoop(IpcServer* ipc, QueryableDatabase* db) { + using namespace language_server_api; + + std::vector> messages = ipc->TakeMessages(); + + for (auto& message : messages) { + std::cerr << "Processing message " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; + + if (IpcMessage_Quit::kId == message->runtime_id()) { + break; + } + + else if (IpcMessage_IsAlive::kId == message->runtime_id()) { + IpcMessage_IsAlive response; + ipc->SendToClient(0, &response); // todo: make non-blocking + } + + else if (IpcMessage_OpenProject::kId == message->runtime_id()) { + IpcMessage_OpenProject* msg = static_cast(message.get()); + std::string path = msg->project_path; + + + std::vector entries = LoadCompilationEntriesFromDirectory(path); + for (int i = 0; i < entries.size(); ++i) { + const CompilationEntry& entry = entries[i]; + std::string filepath = path + "/" + entry.filename; + std::cerr << "[" << i << "/" << (entries.size() - 1) << "] Parsing file " << filepath << std::endl; + IndexedFile file = Parse(filepath, entry.args); + IndexUpdate update(file); + db->ApplyIndexUpdate(&update); + } + std::cerr << "Done" << std::endl; + } + + + else if (IpcMessage_DocumentSymbolsRequest::kId == message->runtime_id()) { + auto msg = static_cast(message.get()); + + IpcMessage_DocumentSymbolsResponse response; + response.id = msg->id; + + std::cerr << "Wanted file " << msg->document << std::endl; + for (auto& file : db->files) { + std::cerr << " - Have file " << file.file_id << std::endl; + + // TODO: make sure we normalize ids! + // TODO: hashmap lookup. + if (file.file_id == msg->document) { + std::cerr << "Found file" << std::endl; + + + for (UsrRef ref : file.outline) { + SymbolIdx symbol = db->usr_to_symbol[ref.usr]; + + SymbolInformation info; + info.location.range.start.line = ref.loc.line - 1; // TODO: cleanup indexer to negate by 1. + info.location.range.start.character = ref.loc.column - 1; // TODO: cleanup indexer to negate by 1. + // TODO: store range information. + info.location.range.end.line = info.location.range.start.line; + info.location.range.end.character = info.location.range.start.character; + + // TODO: cleanup namespace/naming so there is only one SymbolKind. + switch (symbol.kind) { + case ::SymbolKind::Type: + { + QueryableTypeDef& def = db->types[symbol.idx]; + info.name = def.def.qualified_name; + info.kind = language_server_api::SymbolKind::Class; + break; + } + case ::SymbolKind::Func: + { + QueryableFuncDef& def = db->funcs[symbol.idx]; + info.name = def.def.qualified_name; + if (def.def.declaring_type.has_value()) { + info.kind = language_server_api::SymbolKind::Method; + Usr declaring = def.def.declaring_type.value(); + info.containerName = db->types[db->usr_to_symbol[declaring].idx].def.qualified_name; + } + else { + info.kind = language_server_api::SymbolKind::Function; + } + break; + } + case ::SymbolKind::Var: + { + QueryableVarDef& def = db->vars[symbol.idx]; + info.name = def.def.qualified_name; + info.kind = language_server_api::SymbolKind::Variable; + break; + } + }; + + // TODO + //info.containerName = "fooey"; + + response.symbols.push_back(info); + + } + break; + } + } + + + + + ipc->SendToClient(0, &response); + } + + + else { + std::cerr << "Unhandled IPC message with kind " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; + exit(1); + } + } +} + + + + + + + + + + + + + +// TODO: global lock on stderr output. + + + + + + + + + + + + + + + + + + +// Separate thread whose only job is to read from stdin and +// dispatch read commands to the actual indexer program. This +// cannot be done on the main thread because reading from std::cin +// blocks. +// +// |ipc| is connected to a server. +void LanguageServerStdinLoop(IpcClient* ipc) { using namespace language_server_api; while (true) { @@ -279,8 +422,15 @@ void LanguageServerLoop(IpcClient* ipc) { switch (message->method_id) { case MethodId::Initialize: { - // TODO: response should take id as input. - // TODO: message should not have top-level id. + auto request = static_cast(message.get()); + if (request->params.rootUri) { + std::string project_path = request->params.rootUri->GetPath(); + std::cerr << "Initialize in directory " << project_path << std::endl; + IpcMessage_OpenProject open_project; + open_project.project_path = project_path; + ipc->SendToServer(&open_project); + } + auto response = Out_InitializeResponse(); response.id = message->id.value(); response.result.capabilities.documentSymbolProvider = true; @@ -290,157 +440,169 @@ void LanguageServerLoop(IpcClient* ipc) { case MethodId::TextDocumentDocumentSymbol: { - auto response = Out_DocumentSymbolResponse(); - response.id = message->id.value(); + // TODO: response should take id as input. + // TODO: message should not have top-level id. + auto request = static_cast(message.get()); - for (int i = 0; i < 2500; ++i) { - SymbolInformation info; - info.containerName = "fooContainer"; - info.kind = language_server_api::SymbolKind::Field; - info.location.range.start.line = 5; - info.location.range.end.character = 20; - info.location.range.end.line = 5; - info.location.range.end.character = 25; - info.name = "Foobar"; - response.result.push_back(info); - } - - response.Send(); + IpcMessage_DocumentSymbolsRequest ipc_request; + ipc_request.id = request->id.value(); + ipc_request.document = request->params.textDocument.uri.GetPath(); + std::cerr << "Request textDocument=" << ipc_request.document << std::endl; + ipc->SendToServer(&ipc_request); break; } } } } +void LanguageServerMainLoop(IpcClient* ipc) { + using namespace language_server_api; + + std::vector> messages = ipc->TakeMessages(); + for (auto& message : messages) { + if (IpcMessage_Quit::kId == message->runtime_id()) { + exit(0); + } + else if (IpcMessage_DocumentSymbolsResponse::kId == message->runtime_id()) { + auto msg = static_cast(message.get()); -void LanguageServerMain() { - IpcClient ipc("languageserver", 0); + auto response = Out_DocumentSymbolResponse(); + response.id = msg->id; + response.result = msg->symbols; + response.Send(); + std::cerr << "Send symbol response to client (" << response.result.size() << " symbols)" << std::endl; + } + + else { + std::cerr << "Unhandled IPC message with kind " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; + exit(1); + } + } +} + +void LanguageServerMain(std::string process_name) { + IpcClient client_ipc("languageserver", 0); // Discard any left-over messages from previous runs. - ipc.TakeMessages(); + client_ipc.TakeMessages(); // Emit an alive check. Sleep so the server has time to respond. IpcMessage_IsAlive check_alive; - ipc.SendToServer(&check_alive); + client_ipc.SendToServer(&check_alive); // TODO: Tune this value or make it configurable. std::this_thread::sleep_for(std::chrono::milliseconds(20)); // Check if we got an IsAlive message back. - std::vector> messages = ipc.TakeMessages(); + std::vector> messages = client_ipc.TakeMessages(); bool has_server = false; for (auto& message : messages) { - if (message->runtime_id() == IpcMessage_IsAlive::id) { + if (message->runtime_id() == IpcMessage_IsAlive::kId) { has_server = true; break; } } // No server is running. Start it. - //if (!has_server) { - // std::cerr << "Unable to detect running indexer server" << std::endl; - // exit(1); - //} +#if false + if (!has_server) { + if (process_name.empty()) + return; - std::thread stdio_reader(&LanguageServerLoop, &ipc); + Process p(process_name + " --querydb", "", + /*stdout*/[](const char* bytes, size_t n) { + for (int i = 0; i < n; ++i) + std::cerr << bytes[i]; + }, + /*stderr*/[](const char* bytes, size_t n) { + for (int i = 0; i < n; ++i) + std::cerr << bytes[i]; +}, +/*open_stdin*/false); + std::this_thread::sleep_for(std::chrono::seconds(1)); + // Pass empty process name so we only try to start the querydb once. + LanguageServerMain(""); + return; +} +#endif - //std::cout << "Found indexer server" << std::endl; - //LanguageServerLoop(ipc); - - // TODO: This is used for debugging, so we can attach to the client. - - - //std::cout << "garbagelkadklasldk" << std::endl; - - bool should_break = true; - while (should_break) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // for debugging attach //std::this_thread::sleep_for(std::chrono::seconds(4)); - //std::cout.flush(); - /* - language_server_api::ShowMessageOutNotification show; - show.type = language_server_api::MessageType::Info; - show.message = "hello"; - show.Send(); - */ + std::thread stdio_reader(&LanguageServerStdinLoop, &client_ipc); + + + // No server. Run it in-process. + if (!has_server) { + + QueryableDatabase db; + IpcServer server_ipc("languageserver"); + + while (true) { + QueryDbMainLoop(&server_ipc, &db); + LanguageServerMainLoop(&client_ipc); + // TODO: use a condition variable. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + } + + else { + while (true) { + LanguageServerMainLoop(&client_ipc); + // TODO: use a condition variable. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + } } -#if false - -struct IpcMessage_IsAlive : public BaseIpcMessage { - IpcMessage_IsAlive(); - - // BaseIpcMessage: - void Serialize(Writer& writer) override; - void Deserialize(Reader& reader) override; -}; - -struct IpcMessage_ImportIndex : public BaseIpcMessage { - std::string path; - - IpcMessage_ImportIndex(); - - // BaseMessage: - void Serialize(Writer& writer) override; - void Deserialize(Reader& reader) override; -}; - -struct IpcMessage_CreateIndex : public BaseIpcMessage { - std::string path; - std::vector args; - - IpcMessage_CreateIndex(); - - // BaseMessage: - void Serialize(Writer& writer) override; - void Deserialize(Reader& reader) override; -}; -IpcMessage_IsAlive::IpcMessage_IsAlive() { - kind = JsonMessage::Kind::IsAlive; -} -void IpcMessage_IsAlive::Serialize(Writer& writer) {} -void IpcMessage_IsAlive::Deserialize(Reader& reader) {} -IpcMessage_ImportIndex::IpcMessage_ImportIndex() { - kind = JsonMessage::Kind::ImportIndex; -} -void IpcMessage_ImportIndex::Serialize(Writer& writer) { - writer.StartObject(); - ::Serialize(writer, "path", path); - writer.EndObject(); -} -void IpcMessage_ImportIndex::Deserialize(Reader& reader) { - ::Deserialize(reader, "path", path); -} -IpcMessage_CreateIndex::IpcMessage_CreateIndex() { - kind = JsonMessage::Kind::CreateIndex; -} -void IpcMessage_CreateIndex::Serialize(Writer& writer) { - writer.StartObject(); - ::Serialize(writer, "path", path); - ::Serialize(writer, "args", args); - writer.EndObject(); -} -void IpcMessage_CreateIndex::Deserialize(Reader& reader) { - ::Deserialize(reader, "path", path); - ::Deserialize(reader, "args", args); -} -#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + int main(int argc, char** argv) { // We need to write to stdout in binary mode because in Windows, writing @@ -451,9 +613,13 @@ int main(int argc, char** argv) { _setmode(_fileno(stdin), O_BINARY); #endif - std::cerr << "Starting language server" << std::endl; + + + IpcRegistry::instance()->Register(); IpcRegistry::instance()->Register(); + IpcRegistry::instance()->Register(); + IpcRegistry::instance()->Register(); IpcRegistry::instance()->Register(); @@ -462,128 +628,34 @@ int main(int argc, char** argv) { language_server_api::MessageRegistry::instance()->Register(); language_server_api::MessageRegistry::instance()->Register(); + + + + + std::unordered_map options = ParseOptions(argc, argv); if (HasOption(options, "--language-server")) { - LanguageServerMain(); + std::cerr << "Running language server" << std::endl; + LanguageServerMain(argv[0]); return 0; } - if (HasOption(options, "--querydb")) { - QueryDbMain(); - return 0; - } - - - LanguageServerMain(); - return 0; - - if (argc == 1 || options.find("--help") != options.end()) { - std::cout << R"help(clang-indexer help: - - General: - --help Print this help information. - --help-commands - Print all available query commands. - --project Path to compile_commands.json. Needed for the server, and - optionally by clients if there are multiple servers running. - --print-config - Emit all configuration data this executable is using. - - - Server: - --server If present, this binary will run in server mode. The binary - will not return until killed or an exit is requested. The - server computes and caches an index of the entire program - which is then queried by short-lived client processes. A - client is created by running this binary with a --command - flag. - --cache-dir Directory to cache the index and other useful information. If - a previous cache is present, the database will try to reuse - it. If this flag is not present, the database will be - in-memory only. - --threads Number of threads to use for indexing and querying tasks. - This value is optional; a good estimate is computed by - default. - - - Client: - --command Execute a query command against the index. See - --command-help for a listing of valid commands and a - description of what they do. Presence of this flag indicates - that the indexer is in client mode; this flag is mutually - exclusive with --server. - --location Location of the query. Some commands require only a file, - other require a line and column as well. Format is - filename[:line:column]. For example, "foobar.cc" and - "foobar.cc:1:10" are valid inputs. - --preferred-symbol-location - When looking up symbols, try to return either the - 'declaration' or the 'definition'. Defaults to 'definition'. -)help"; - exit(0); - } - - if (HasOption(options, "--help-commands")) { - std::cout << R"(Available commands: - - callees: - callers: - Emit all functions (with location) that this function calls ("callees") or - that call this function ("callers"). Requires a location. - - find-all-usages: - Emit every usage of the given symbol. This is intended to support a rename - refactoring. This output contains many uninteresting usages of symbols; - prefer find-interesting-usges. Requires a location. - - find-interesting-usages: - Emit only usages of the given symbol which are semantically interesting. - Requires a location. - - goto-referenced: - Find an associated reference (either definition or declaration) for the - given symbol. Requires a location. - - hierarchy: - List the type hierarchy (ie, inherited and derived members) for the given - method or type. Requires a location. - - outline: - Emit a file outline, listing all of the symbols in the file. - - search: - Search for a symbol by name. -)"; - exit(0); - } - - if (HasOption(options, "--project")) { - std::vector entries = LoadCompilationEntriesFromDirectory(options["--project"]); - - - for (const CompilationEntry& entry : entries) { - std::cout << "Parsing " << entry.filename << std::endl; - QueryableDatabase db; - IndexedFile file = Parse(entry.filename, entry.args); - - IndexUpdate update(file); - db.ApplyIndexUpdate(&update); - //std::cout << db.ToString() << std::endl << std::endl; + else if (HasOption(options, "--querydb")) { + std::cerr << "Running querydb" << std::endl; + QueryableDatabase db; + IpcServer ipc("languageserver"); + while (true) { + QueryDbMainLoop(&ipc, &db); + // TODO: use a condition variable. + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - - std::cin.get(); - exit(0); + return 0; + } + else { + std::cerr << "Running language server" << std::endl; + LanguageServerMain(argv[0]); + return 0; } - if (HasOption(options, "--command")) { - Command command; - if (!ParseCommand(options["--command"], &command)) - Fail("Unknown command \"" + options["--command"] + "\"; see --help-commands"); - - - } - - std::cout << "Invalid arguments. Try --help."; - exit(1); - return 0; + return 1; } diff --git a/compilation_database_loader.cc b/compilation_database_loader.cc index 09b8d84c..8c6adabf 100644 --- a/compilation_database_loader.cc +++ b/compilation_database_loader.cc @@ -5,12 +5,42 @@ #include "libclangmm/Utility.h" +#include "utils.h" + +// See http://stackoverflow.com/a/2072890 +bool EndsWith(const std::string& value, const std::string& ending) { + if (ending.size() > value.size()) + return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +std::vector LoadFromDirectoryListing(const std::string& project_directory) { + std::vector result; + + std::vector files = GetFilesInFolder(project_directory, false /*add_folder_to_path*/); + + for (const std::string& file : files) { + if (EndsWith(file, ".cc") || EndsWith(file, ".cpp") || + EndsWith(file, ".c") || EndsWith(file, ".h") || + EndsWith(file, ".hpp")) { + + CompilationEntry entry; + entry.directory = "."; + entry.filename = file; + entry.args = {}; + result.push_back(entry); + } + } + + return result; +} + std::vector LoadCompilationEntriesFromDirectory(const std::string& project_directory) { CXCompilationDatabase_Error cx_db_load_error; CXCompilationDatabase cx_db = clang_CompilationDatabase_fromDirectory(project_directory.c_str(), &cx_db_load_error); if (cx_db_load_error == CXCompilationDatabase_CanNotLoadDatabase) { - std::cerr << "[FATAL]: Unable to load compile_commands.json located at \"" << project_directory << "\""; - exit(1); + std::cerr << "Unable to load compile_commands.json located at \"" << project_directory << "\"; using directory listing instead." << std::endl; + return LoadFromDirectoryListing(project_directory); } CXCompileCommands cx_commands = clang_CompilationDatabase_getAllCompileCommands(cx_db); diff --git a/indexer.cpp b/indexer.cpp index 7ab433da..10050765 100644 --- a/indexer.cpp +++ b/indexer.cpp @@ -87,7 +87,7 @@ std::string IndexedFile::ToString() { IndexedTypeDef::IndexedTypeDef(TypeId id, const std::string& usr) : id(id), def(usr) { assert(usr.size() > 0); - //std::cout << "Creating type with usr " << usr << std::endl; + //std::cerr << "Creating type with usr " << usr << std::endl; } void IndexedTypeDef::AddUsage(Location loc, bool insert_if_not_present) { @@ -196,8 +196,8 @@ CXIdxClientContainer startedTranslationUnit(CXClientData client_data, void *rese clang::VisiterResult DumpVisitor(clang::Cursor cursor, clang::Cursor parent, int* level) { for (int i = 0; i < *level; ++i) - std::cout << " "; - std::cout << clang::ToString(cursor.get_kind()) << " " << cursor.get_spelling() << std::endl; + std::cerr << " "; + std::cerr << clang::ToString(cursor.get_kind()) << " " << cursor.get_spelling() << std::endl; *level += 1; cursor.VisitChildren(&DumpVisitor, level); @@ -432,6 +432,8 @@ optional ResolveDeclToType(IndexedFile* db, clang::Cursor decl_cursor, // The second TypeRef is an uninteresting usage. bool process_last_type_ref = true; if (IsTypeDefinition(semantic_container) && !IsTypeDefinition(lexical_container)) { + //if (!decl_cursor.is_definition()) + // decl_cursor = decl_cursor.get_definition(); assert(decl_cursor.is_definition()); process_last_type_ref = false; } @@ -713,15 +715,15 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { } default: - std::cout << "!! Unhandled indexDeclaration: " << clang::Cursor(decl->cursor).ToString() << " at " << db->id_cache.Resolve(decl->loc, false /*interesting*/).ToString() << std::endl; - std::cout << " entityInfo->kind = " << decl->entityInfo->kind << std::endl; - std::cout << " entityInfo->USR = " << decl->entityInfo->USR << std::endl; + std::cerr << "!! Unhandled indexDeclaration: " << clang::Cursor(decl->cursor).ToString() << " at " << db->id_cache.Resolve(decl->loc, false /*interesting*/).ToString() << std::endl; + std::cerr << " entityInfo->kind = " << decl->entityInfo->kind << std::endl; + std::cerr << " entityInfo->USR = " << decl->entityInfo->USR << std::endl; if (decl->declAsContainer) - std::cout << " declAsContainer = " << clang::Cursor(decl->declAsContainer->cursor).ToString() << std::endl; + std::cerr << " declAsContainer = " << clang::Cursor(decl->declAsContainer->cursor).ToString() << std::endl; if (decl->semanticContainer) - std::cout << " semanticContainer = " << clang::Cursor(decl->semanticContainer->cursor).ToString() << std::endl; + std::cerr << " semanticContainer = " << clang::Cursor(decl->semanticContainer->cursor).ToString() << std::endl; if (decl->lexicalContainer) - std::cout << " lexicalContainer = " << clang::Cursor(decl->lexicalContainer->cursor).get_usr() << std::endl; + std::cerr << " lexicalContainer = " << clang::Cursor(decl->lexicalContainer->cursor).get_usr() << std::endl; break; } } @@ -849,18 +851,18 @@ void indexEntityReference(CXClientData client_data, const CXIdxEntityRefInfo* re } default: - std::cout << "!! Unhandled indexEntityReference: " << cursor.ToString() << " at " << db->id_cache.Resolve(ref->loc, false /*interesting*/).ToString() << std::endl; - std::cout << " ref->referencedEntity->kind = " << ref->referencedEntity->kind << std::endl; + std::cerr << "!! Unhandled indexEntityReference: " << cursor.ToString() << " at " << db->id_cache.Resolve(ref->loc, false /*interesting*/).ToString() << std::endl; + std::cerr << " ref->referencedEntity->kind = " << ref->referencedEntity->kind << std::endl; if (ref->parentEntity) - std::cout << " ref->parentEntity->kind = " << ref->parentEntity->kind << std::endl; - std::cout << " ref->loc = " << db->id_cache.Resolve(ref->loc, false /*interesting*/).ToString() << std::endl; - std::cout << " ref->kind = " << ref->kind << std::endl; + std::cerr << " ref->parentEntity->kind = " << ref->parentEntity->kind << std::endl; + std::cerr << " ref->loc = " << db->id_cache.Resolve(ref->loc, false /*interesting*/).ToString() << std::endl; + std::cerr << " ref->kind = " << ref->kind << std::endl; if (ref->parentEntity) - std::cout << " parentEntity = " << clang::Cursor(ref->parentEntity->cursor).ToString() << std::endl; + std::cerr << " parentEntity = " << clang::Cursor(ref->parentEntity->cursor).ToString() << std::endl; if (ref->referencedEntity) - std::cout << " referencedEntity = " << clang::Cursor(ref->referencedEntity->cursor).ToString() << std::endl; + std::cerr << " referencedEntity = " << clang::Cursor(ref->referencedEntity->cursor).ToString() << std::endl; if (ref->container) - std::cout << " container = " << clang::Cursor(ref->container->cursor).ToString() << std::endl; + std::cerr << " container = " << clang::Cursor(ref->container->cursor).ToString() << std::endl; break; } } diff --git a/ipc.cc b/ipc.cc index 88265021..10bc662e 100644 --- a/ipc.cc +++ b/ipc.cc @@ -52,7 +52,7 @@ void IpcDirectionalChannel::PushMessage(BaseIpcMessageElided* message) { writer.SetIndent(' ', 2); message->Serialize(writer); - //std::cout << "Sending message with id " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; + //std::cerr << "Sending message with id " << message->runtime_id() << " (hash " << message->hashed_runtime_id() << ")" << std::endl; size_t payload_size = strlen(output.GetString()); assert(payload_size < shmem_size && "Increase shared memory size, payload will never fit"); @@ -62,7 +62,7 @@ void IpcDirectionalChannel::PushMessage(BaseIpcMessageElided* message) { while (true) { if (!first) { if (!did_log) { - std::cout << "[info]: shmem full, waiting" << std::endl; // TODO: remove + std::cerr << "[info]: shmem full, waiting" << std::endl; // TODO: remove did_log = true; } std::this_thread::sleep_for(std::chrono::milliseconds(16)); diff --git a/ipc.h b/ipc.h index bad1c9ce..d68197ae 100644 --- a/ipc.h +++ b/ipc.h @@ -15,6 +15,7 @@ using Writer = rapidjson::PrettyWriter; using Reader = rapidjson::Document; +// TODO: We need to add support for payloads larger than the maximum shared memory buffer size. // Messages are funky objects. They contain potentially variable amounts of // data and are passed between processes. This means that they need to be @@ -42,12 +43,12 @@ struct BaseIpcMessageElided { // Usage: // // class IpcMessage_Foo : public BaseIpcMessage { -// static IpcMessageId id; +// static IpcMessageId kId; // // // BaseIpcMessage: // ... // } -// IpcMessageId IpcMessage_Foo::id = "Foo"; +// IpcMessageId IpcMessage_Foo::kId = "Foo"; // // // main() { @@ -102,7 +103,7 @@ void IpcRegistry::Register() { hash_to_id = MakeUnique>(); } - IpcMessageId id = T::id; + IpcMessageId id = T::kId; int hash = std::hash()(id); auto it = allocators->find(hash); @@ -163,6 +164,8 @@ struct IpcClient { void SendToServer(BaseIpcMessageElided* message); std::vector> TakeMessages(); + IpcDirectionalChannel* client() { return &client_; } + private: IpcDirectionalChannel server_; IpcDirectionalChannel client_; diff --git a/language_server_api.h b/language_server_api.h index f764a8a8..cd824c6c 100644 --- a/language_server_api.h +++ b/language_server_api.h @@ -512,15 +512,33 @@ namespace language_server_api { // Keep all types in the language_server_api namespace in sync with language server spec. // TODO struct DocumentUri { - std::string path; + std::string raw_uri; + + std::string GetPath() { + // TODO: make this not a hack. + std::string result = raw_uri; + + size_t index = result.find("%3A"); + if (index != -1) { + result.replace(result.begin() + index, result.begin() + index + 3, ":"); + } + + index = result.find("file://"); + if (index != -1) { + result.replace(result.begin() + index, result.begin() + index + 8, ""); + } + + std::replace(result.begin(), result.end(), '\\', '/'); + return result; + } }; void Serialize(Writer& writer, const DocumentUri& value) { - Serialize(writer, value.path); + Serialize(writer, value.raw_uri); } void Deserialize(const Reader& reader, DocumentUri& value) { - Deserialize(reader, value.path); + Deserialize(reader, value.raw_uri); } @@ -1344,7 +1362,7 @@ namespace language_server_api { struct In_DocumentSymbolRequest : public InRequestMessage { const static MethodId kMethod = MethodId::TextDocumentDocumentSymbol; - + DocumentSymbolParams params; In_DocumentSymbolRequest(optional id, const Reader& reader) @@ -1449,9 +1467,9 @@ namespace language_server_api { }; -#undef SERIALIZE_MEMBER -#undef SERIALIZE_MEMBER2 -#undef DESERIALIZE_MEMBER +//#undef SERIALIZE_MEMBER +//#undef SERIALIZE_MEMBER2 +//#undef DESERIALIZE_MEMBER diff --git a/old.cc b/old.cc new file mode 100644 index 00000000..1d3d8d8e --- /dev/null +++ b/old.cc @@ -0,0 +1,175 @@ +#if false + + +/* + +// Connects to a running --project-directory instance. Forks +// and creates it if not running. +// +// Implements language server spec. +indexer.exe --language-server + +// Holds the runtime db that the --language-server instance +// runs queries against. +indexer.exe --project-directory /work2/chrome/src + +// Created from the --project-directory (server) instance +indexer.exe --index-file /work2/chrome/src/chrome/foo.cc + +// Configuration data is read from a JSON file. +{ +"max_threads": 40, +"cache_directory": "/work/indexer_cache/" + +} +*/ + + +bool ParsePreferredSymbolLocation(const std::string& content, PreferredSymbolLocation* obj) { +#define PARSE_AS(name, string) \ + if (content == #string) { \ + *obj = name; \ + return true; \ + } + + PARSE_AS(PreferredSymbolLocation::Declaration, "declaration"); + PARSE_AS(PreferredSymbolLocation::Definition, "definition"); + + return false; +#undef PARSE_AS +} + +bool ParseCommand(const std::string& content, Command* obj) { +#define PARSE_AS(name, string) \ + if (content == #string) { \ + *obj = name; \ + return true; \ + } + + PARSE_AS(Command::Callees, "callees"); + PARSE_AS(Command::Callers, "callers"); + PARSE_AS(Command::FindAllUsages, "find-all-usages"); + PARSE_AS(Command::FindInterestingUsages, "find-interesting-usages"); + PARSE_AS(Command::GotoReferenced, "goto-referenced"); + PARSE_AS(Command::Hierarchy, "hierarchy"); + PARSE_AS(Command::Outline, "outline"); + PARSE_AS(Command::Search, "search"); + + return false; +#undef PARSE_AS +} + + + +int main(int argc, char** argv) { + if (argc == 1 || options.find("--help") != options.end()) { + std::cout << R"help(clang-indexer help: + + General: + --help Print this help information. + --help-commands + Print all available query commands. + --project Path to compile_commands.json. Needed for the server, and + optionally by clients if there are multiple servers running. + --print-config + Emit all configuration data this executable is using. + + + Server: + --server If present, this binary will run in server mode. The binary + will not return until killed or an exit is requested. The + server computes and caches an index of the entire program + which is then queried by short-lived client processes. A + client is created by running this binary with a --command + flag. + --cache-dir Directory to cache the index and other useful information. If + a previous cache is present, the database will try to reuse + it. If this flag is not present, the database will be + in-memory only. + --threads Number of threads to use for indexing and querying tasks. + This value is optional; a good estimate is computed by + default. + + + Client: + --command Execute a query command against the index. See + --command-help for a listing of valid commands and a + description of what they do. Presence of this flag indicates + that the indexer is in client mode; this flag is mutually + exclusive with --server. + --location Location of the query. Some commands require only a file, + other require a line and column as well. Format is + filename[:line:column]. For example, "foobar.cc" and + "foobar.cc:1:10" are valid inputs. + --preferred-symbol-location + When looking up symbols, try to return either the + 'declaration' or the 'definition'. Defaults to 'definition'. +)help"; + exit(0); + } + + if (HasOption(options, "--help-commands")) { + std::cout << R"(Available commands: + + callees: + callers: + Emit all functions (with location) that this function calls ("callees") or + that call this function ("callers"). Requires a location. + + find-all-usages: + Emit every usage of the given symbol. This is intended to support a rename + refactoring. This output contains many uninteresting usages of symbols; + prefer find-interesting-usges. Requires a location. + + find-interesting-usages: + Emit only usages of the given symbol which are semantically interesting. + Requires a location. + + goto-referenced: + Find an associated reference (either definition or declaration) for the + given symbol. Requires a location. + + hierarchy: + List the type hierarchy (ie, inherited and derived members) for the given + method or type. Requires a location. + + outline: + Emit a file outline, listing all of the symbols in the file. + + search: + Search for a symbol by name. +)"; + exit(0); + } + + if (HasOption(options, "--project")) { + std::vector entries = LoadCompilationEntriesFromDirectory(options["--project"]); + + + for (const CompilationEntry& entry : entries) { + std::cout << "Parsing " << entry.filename << std::endl; + QueryableDatabase db; + IndexedFile file = Parse(entry.filename, entry.args); + + IndexUpdate update(file); + db.ApplyIndexUpdate(&update); + //std::cout << db.ToString() << std::endl << std::endl; + } + + std::cin.get(); + exit(0); + } + + if (HasOption(options, "--command")) { + Command command; + if (!ParseCommand(options["--command"], &command)) + Fail("Unknown command \"" + options["--command"] + "\"; see --help-commands"); + + + } + + std::cout << "Invalid arguments. Try --help."; + exit(1); + return 0; +} +#endif \ No newline at end of file diff --git a/platform.h b/platform.h index f8bc4187..0202395e 100644 --- a/platform.h +++ b/platform.h @@ -16,8 +16,10 @@ struct PlatformSharedMemory { char* shared_start; }; -const int shmem_size = 1024; // number of chars/bytes (256kb) +const int shmem_size = 1024 * 256; // number of chars/bytes (256kb) std::unique_ptr CreatePlatformMutex(const std::string& name); std::unique_ptr CreatePlatformScopedMutexLock(PlatformMutex* mutex); std::unique_ptr CreatePlatformSharedMemory(const std::string& name); + +std::string GetWorkingDirectory(); \ No newline at end of file diff --git a/platform_win.cc b/platform_win.cc index 25e57104..65707531 100644 --- a/platform_win.cc +++ b/platform_win.cc @@ -71,3 +71,22 @@ std::unique_ptr CreatePlatformScopedMutexLock(PlatformM std::unique_ptr CreatePlatformSharedMemory(const std::string& name) { return MakeUnique(name); } + +// See http://stackoverflow.com/a/19535628 +std::string GetWorkingDirectory() { + char result[MAX_PATH]; + return std::string(result, GetModuleFileName(NULL, result, MAX_PATH)); +} + +/* +// linux +#include +#include +#include + +std::string getexepath() { + char result[ PATH_MAX ]; + ssize_t count = readlink( "/proc/self/exe", result, PATH_MAX ); + return std::string( result, (count > 0) ? count : 0 ); +} +*/ \ No newline at end of file diff --git a/query.cc b/query.cc index e9141136..aba9c925 100644 --- a/query.cc +++ b/query.cc @@ -74,9 +74,11 @@ std::vector MapIdToUsr(const IdCache& id_cache, const std::ve } QueryableTypeDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const TypeDefDefinitionData<>& def) { QueryableTypeDef::DefUpdate result(def.usr); - if (result.definition) + result.short_name = def.short_name; + result.qualified_name = def.qualified_name; + if (def.definition) result.definition = MapIdToUsr(id_cache, def.definition.value()); - if (result.alias_of) + if (def.alias_of) result.alias_of = MapIdToUsr(id_cache, def.alias_of.value()); result.parents = MapIdToUsr(id_cache, def.parents); result.types = MapIdToUsr(id_cache, def.types); @@ -86,11 +88,13 @@ QueryableTypeDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const TypeDefDef } QueryableFuncDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const FuncDefDefinitionData<>& def) { QueryableFuncDef::DefUpdate result(def.usr); - if (result.definition) + result.short_name = def.short_name; + result.qualified_name = def.qualified_name; + if (def.definition) result.definition = MapIdToUsr(id_cache, def.definition.value()); - if (result.declaring_type) + if (def.declaring_type) result.declaring_type = MapIdToUsr(id_cache, def.declaring_type.value()); - if (result.base) + if (def.base) result.base = MapIdToUsr(id_cache, def.base.value()); result.locals = MapIdToUsr(id_cache, def.locals); result.callees = MapIdToUsr(id_cache, def.callees); @@ -98,13 +102,15 @@ QueryableFuncDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const FuncDefDef } QueryableVarDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const VarDefDefinitionData<>& def) { QueryableVarDef::DefUpdate result(def.usr); - if (result.declaration) + result.short_name = def.short_name; + result.qualified_name = def.qualified_name; + if (def.declaration) result.declaration = MapIdToUsr(id_cache, def.declaration.value()); - if (result.definition) + if (def.definition) result.definition = MapIdToUsr(id_cache, def.definition.value()); - if (result.variable_type) + if (def.variable_type) result.variable_type = MapIdToUsr(id_cache, def.variable_type.value()); - if (result.declaring_type) + if (def.declaring_type) result.declaring_type = MapIdToUsr(id_cache, def.declaring_type.value()); return result; } @@ -112,8 +118,10 @@ QueryableVarDef::DefUpdate MapIdToUsr(const IdCache& id_cache, const VarDefDefin QueryableFile::QueryableFile(const IndexedFile& indexed) : file_id(indexed.path) { - auto add_outline = [this, &indexed](Usr usr, Location location) { - outline.push_back(UsrRef(usr, MapIdToUsr(indexed.id_cache, location))); + FileId local_file_id = indexed.id_cache.file_path_to_file_id.find(indexed.path)->second; + auto add_outline = [this, &indexed, local_file_id](Usr usr, Location location) { + if (location.file_id() == local_file_id) + outline.push_back(UsrRef(usr, MapIdToUsr(indexed.id_cache, location))); }; for (const IndexedTypeDef& def : indexed.types) { @@ -357,7 +365,7 @@ IndexUpdate::IndexUpdate(IndexedFile& previous_file, IndexedFile& current_file) previous, current, \ &removed, &added); \ if (did_add) {\ - std::cout << "Adding mergeable update on " << current_def->def.short_name << " (" << current_def->def.usr << ") for field " << #index_name << std::endl; \ + std::cerr << "Adding mergeable update on " << current_def->def.short_name << " (" << current_def->def.usr << ") for field " << #index_name << std::endl; \ query_name.push_back(MergeableUpdate(current_def->def.usr, removed, added)); \ } \ } @@ -376,7 +384,7 @@ IndexUpdate::IndexUpdate(IndexedFile& previous_file, IndexedFile& current_file) current_queryable_file.outline, &removed, &added); if (did_add) { - std::cout << "Adding mergeable update on outline (" << current_file.path << ")" << std::endl; + std::cerr << "Adding mergeable update on outline (" << current_file.path << ")" << std::endl; files_outline.push_back(MergeableUpdate(current_file.path, removed, added)); } } while (false); // do while false instead of just {} to appease Visual Studio code formatter. @@ -617,11 +625,11 @@ void QueryableDatabase::ApplyIndexUpdate(IndexUpdate* update) { int main233(int argc, char** argv) { IndexedFile indexed_file_a = Parse("full_tests/index_delta/a_v0.cc", {}); - std::cout << indexed_file_a.ToString() << std::endl; + std::cerr << indexed_file_a.ToString() << std::endl; - std::cout << std::endl; + std::cerr << std::endl; IndexedFile indexed_file_b = Parse("full_tests/index_delta/a_v1.cc", {}); - std::cout << indexed_file_b.ToString() << std::endl; + std::cerr << indexed_file_b.ToString() << std::endl; // TODO: We don't need to do ID remapping when computting a diff. Well, we need to do it for the IndexUpdate. IndexUpdate import(indexed_file_a); /* diff --git a/task.cc b/task.cc index 4c962fad..1094119f 100644 --- a/task.cc +++ b/task.cc @@ -183,11 +183,11 @@ static void ThreadMain(int id, Config* config, TaskManager* tm) { assert(false); break; case Task::Kind::Exit: - std::cout << id << ": Exiting" << std::endl; + std::cerr << id << ": Exiting" << std::endl; return; } - std::cout << id << ": waking" << std::endl; + std::cerr << id << ": waking" << std::endl; } } diff --git a/test.cc b/test.cc index 65fd422b..04de9b7a 100644 --- a/test.cc +++ b/test.cc @@ -1,6 +1,7 @@ #include "indexer.h" #include "serializer.h" #include "utils.h" +#include "platform.h" void Write(const std::vector& strs) { for (const std::string& str : strs) @@ -81,7 +82,7 @@ void VerifySerializeToFrom(IndexedFile* file) { } } -int main333(int argc, char** argv) { +int main222(int argc, char** argv) { // TODO: Assert that we need to be on clang >= 3.9.1 /* @@ -92,8 +93,9 @@ int main333(int argc, char** argv) { return 0; */ - for (std::string path : GetFilesInFolder("tests")) { + for (std::string path : GetFilesInFolder("tests", true /*add_folder_to_path*/)) { //if (path != "tests/usage/type_usage_declare_field.cc") continue; + path = "C:/Users/jacob/Desktop/superindex/indexer/" + path; // Parse expected output from the test, parse it into JSON document. std::string expected_output; diff --git a/utils.cc b/utils.cc index 304a9ce3..903b4318 100644 --- a/utils.cc +++ b/utils.cc @@ -5,7 +5,7 @@ #include "tinydir.h" -std::vector GetFilesInFolder(std::string folder) { +std::vector GetFilesInFolder(std::string folder, bool add_folder_to_path) { std::vector result; tinydir_dir dir; @@ -21,10 +21,16 @@ std::vector GetFilesInFolder(std::string folder) { goto bail; } - std::string full_path = folder + "/" + file.name; + std::string full_path; + if (add_folder_to_path) + full_path = folder + "/"; + full_path += file.name; if (file.is_dir) { - if (strcmp(file.name, ".") != 0 && strcmp(file.name, "..") != 0) { - for (std::string nested_file : GetFilesInFolder(full_path)) + // Ignore all dot directories. + // Note that we must always ignore the '.' and '..' directories, otherwise + // this will loop infinitely. + if (file.name[0] != '.') { + for (std::string nested_file : GetFilesInFolder(full_path, true /*add_folder_to_path*/)) result.push_back(nested_file); } } diff --git a/utils.h b/utils.h index ac2645b8..90a0042b 100644 --- a/utils.h +++ b/utils.h @@ -4,7 +4,8 @@ #include #include -std::vector GetFilesInFolder(std::string folder); +// Finds all files in the given folder. This is recursive. +std::vector GetFilesInFolder(std::string folder, bool add_folder_to_path); std::vector ReadLines(std::string filename); void ParseTestExpectation(std::string filename, std::string* expected_output);