From f8c8eca8ee95817d7393574e8eb202274e4d66ad Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 21 Dec 2018 01:05:23 -0800 Subject: [PATCH] Extend .ccls * Add %h for C header files (the suffix .h is considered a C header, not a C++ header) * Add %hpp for C++ header files * If .ccls exists, it provides full command line for files not specified by compile_commands.json (before, compile_commands.json was ignored) * If the first line of .ccls is %compile_commands.json, it appends flags to compile_commands.json "arguments", instead of overriding. Files not specified by compile_commands.json will not be added to folder.entries, but their command line can be inferred from other files. Also fix `#include <` completion of -I flags for clang < 8 --- src/include_complete.cc | 119 +++--- src/include_complete.hh | 5 - src/message_handler.hh | 2 +- src/messages/textDocument_completion.cc | 15 +- src/pipeline.cc | 4 +- src/project.cc | 467 +++++++++++++----------- src/project.hh | 13 +- 7 files changed, 318 insertions(+), 307 deletions(-) diff --git a/src/include_complete.cc b/src/include_complete.cc index 6090233a..83ac6750 100644 --- a/src/include_complete.cc +++ b/src/include_complete.cc @@ -48,40 +48,26 @@ size_t TrimCommonPathPrefix(const std::string &result, return 0; } -// Returns true iff angle brackets should be used. -bool TrimPath(Project *project, std::string &path) { +int TrimPath(Project *project, std::string &path) { size_t pos = 0; - bool angle = false; - for (auto &[root, folder] : project->root2folder) { - size_t pos1 = 0; - for (auto &search : folder.angle_search_list) - pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); - if (pos1 > pos) { - pos = pos1; - angle = true; - } - - pos1 = TrimCommonPathPrefix(path, root); - for (auto &search : folder.quote_search_list) - pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); - if (pos1 > pos) { - pos = pos1; - angle = false; - } - } + int kind = 0; + for (auto &[root, folder] : project->root2folder) + for (auto &[search, search_dir_kind] : folder.search_dir2kind) + if (int t = TrimCommonPathPrefix(path, search); t > pos) + pos = t, kind = search_dir_kind; path = path.substr(pos); - return angle; + return kind; } CompletionItem BuildCompletionItem(const std::string &path, - bool use_angle_brackets) { + int kind) { CompletionItem item; item.label = ElideLongPath(path); item.detail = path; // the include path, used in de-duplicating item.textEdit.newText = path; item.insertTextFormat = InsertTextFormat::PlainText; - item.use_angle_brackets_ = use_angle_brackets; item.kind = CompletionItemKind::File; + item.quote_kind_ = kind; item.priority_ = 0; return item; } @@ -113,14 +99,41 @@ void IncludeComplete::Rescan() { is_scanning = true; std::thread([this]() { set_thread_name("include"); - std::unordered_set angle_set, quote_set; for (auto &[root, folder] : project_->root2folder) { - for (const std::string &search : folder.angle_search_list) - if (angle_set.insert(search).second) - InsertIncludesFromDirectory(search, true); - for (const std::string &search : folder.quote_search_list) - if (quote_set.insert(search).second) - InsertIncludesFromDirectory(search, false); + for (auto &search_kind : folder.search_dir2kind) { + const std::string &search = search_kind.first; + int kind = search_kind.second; + assert(search.back() == '/'); + if (match_ && !match_->Matches(search)) + return; + bool include_cpp = search.find("include/c++") != std::string::npos; + + std::vector results; + GetFilesInFolder(search, true /*recursive*/, + false /*add_folder_to_path*/, + [&](const std::string &path) { + bool ok = include_cpp; + for (StringRef suffix : + g_config->completion.include.suffixWhitelist) + if (StringRef(path).endswith(suffix)) + ok = true; + if (!ok) + return; + if (match_ && !match_->Matches(search + path)) + return; + + CompletionCandidate candidate; + candidate.absolute_path = search + path; + candidate.completion_item = + BuildCompletionItem(path, kind); + results.push_back(candidate); + }); + + std::lock_guard lock(completion_items_mutex); + for (CompletionCandidate &result : results) + InsertCompletionItem(result.absolute_path, + std::move(result.completion_item)); + } } is_scanning = false; @@ -130,7 +143,7 @@ void IncludeComplete::Rescan() { void IncludeComplete::InsertCompletionItem(const std::string &absolute_path, CompletionItem &&item) { - if (inserted_paths.insert({item.detail, inserted_paths.size()}).second) { + if (inserted_paths.try_emplace(item.detail, inserted_paths.size()).second) { completion_items.push_back(item); // insert if not found or with shorter include path auto it = absolute_path_to_completion_item.find(absolute_path); @@ -139,12 +152,6 @@ void IncludeComplete::InsertCompletionItem(const std::string &absolute_path, absolute_path_to_completion_item[absolute_path] = completion_items.size() - 1; } - } else { - CompletionItem &inserted_item = - completion_items[inserted_paths[item.detail]]; - // Update |use_angle_brackets_|, prefer quotes. - if (!item.use_angle_brackets_) - inserted_item.use_angle_brackets_ = false; } } @@ -159,8 +166,8 @@ void IncludeComplete::AddFile(const std::string &path) { return; std::string trimmed_path = path; - bool use_angle_brackets = TrimPath(project_, trimmed_path); - CompletionItem item = BuildCompletionItem(trimmed_path, use_angle_brackets); + int kind = TrimPath(project_, trimmed_path); + CompletionItem item = BuildCompletionItem(trimmed_path, kind); std::unique_lock lock(completion_items_mutex, std::defer_lock); if (is_scanning) @@ -168,40 +175,6 @@ void IncludeComplete::AddFile(const std::string &path) { InsertCompletionItem(path, std::move(item)); } -void IncludeComplete::InsertIncludesFromDirectory(std::string directory, - bool use_angle_brackets) { - directory = NormalizePath(directory); - EnsureEndsInSlash(directory); - if (match_ && !match_->Matches(directory)) - return; - bool include_cpp = directory.find("include/c++") != std::string::npos; - - std::vector results; - GetFilesInFolder( - directory, true /*recursive*/, false /*add_folder_to_path*/, - [&](const std::string &path) { - bool ok = include_cpp; - for (StringRef suffix : g_config->completion.include.suffixWhitelist) - if (StringRef(path).endswith(suffix)) - ok = true; - if (!ok) - return; - if (match_ && !match_->Matches(directory + path)) - return; - - CompletionCandidate candidate; - candidate.absolute_path = directory + path; - candidate.completion_item = - BuildCompletionItem(path, use_angle_brackets); - results.push_back(candidate); - }); - - std::lock_guard lock(completion_items_mutex); - for (CompletionCandidate &result : results) - InsertCompletionItem(result.absolute_path, - std::move(result.completion_item)); -} - std::optional IncludeComplete::FindCompletionItemForAbsolutePath( const std::string &absolute_path) { diff --git a/src/include_complete.hh b/src/include_complete.hh index b9d02bae..6be2505c 100644 --- a/src/include_complete.hh +++ b/src/include_complete.hh @@ -22,11 +22,6 @@ struct IncludeComplete { // Ensures the one-off file is inside |completion_items|. void AddFile(const std::string &absolute_path); - // Scans the given directory and inserts all includes from this. This is a - // blocking function and should be run off the querydb thread. - void InsertIncludesFromDirectory(std::string directory, - bool use_angle_brackets); - std::optional FindCompletionItemForAbsolutePath(const std::string &absolute_path); diff --git a/src/message_handler.hh b/src/message_handler.hh index 8ccde4a5..7063fe1c 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -127,7 +127,7 @@ struct CompletionItem { std::vector parameters_; int score_; unsigned priority_; - bool use_angle_brackets_ = false; + int quote_kind_ = 0; }; // formatting diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 843a985c..49132467 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -49,9 +49,10 @@ REFLECT_STRUCT(CompletionList, isIncomplete, items); #if LLVM_VERSION_MAJOR < 8 void DecorateIncludePaths(const std::smatch &match, - std::vector *items) { + std::vector *items, + char quote) { std::string spaces_after_include = " "; - if (match[3].compare("include") == 0 && match[5].length()) + if (match[3].compare("include") == 0 && quote != '\0') spaces_after_include = match[4].str(); std::string prefix = @@ -60,8 +61,7 @@ void DecorateIncludePaths(const std::smatch &match, for (CompletionItem &item : *items) { char quote0, quote1; - if (match[5].compare("<") == 0 || - (match[5].length() == 0 && item.use_angle_brackets_)) + if (quote != '"') quote0 = '<', quote1 = '>'; else quote0 = quote1 = '"'; @@ -492,21 +492,22 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line); if (preprocess.ok && preprocess.keyword.compare("include") == 0) { CompletionList result; + char quote = std::string(preprocess.match[5])[0]; { std::unique_lock lock( include_complete->completion_items_mutex, std::defer_lock); if (include_complete->is_scanning) lock.lock(); - std::string quote = preprocess.match[5]; for (auto &item : include_complete->completion_items) - if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\"")) + if (quote == '\0' || (item.quote_kind_ & 1 && quote == '"') || + (item.quote_kind_ & 2 && quote == '<')) result.items.push_back(item); } begin_pos.character = 0; end_pos.character = (int)buffer_line.size(); FilterCandidates(result, preprocess.pattern, begin_pos, end_pos, buffer_line); - DecorateIncludePaths(preprocess.match, &result.items); + DecorateIncludePaths(preprocess.match, &result.items, quote); reply(result); return; } diff --git a/src/pipeline.cc b/src/pipeline.cc index abfbe08f..002e4bca 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -281,7 +281,7 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, on_indexed->PushBack(std::move(update), request.mode != IndexMode::NonInteractive); if (entry.id >= 0) { - std::lock_guard lock2(project->mutex_); + std::lock_guard lock2(project->mtx); project->root2folder[entry.root].path2entry_index[path] = entry.id; } } @@ -351,7 +351,7 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, vfs->state[path].loaded = true; } if (entry.id >= 0) { - std::lock_guard lock(project->mutex_); + std::lock_guard lock(project->mtx); auto &folder = project->root2folder[entry.root]; for (auto &dep : curr->dependencies) folder.path2entry_index[dep.first.val().str()] = entry.id; diff --git a/src/project.cc b/src/project.cc index 1302c74c..7f6c5c61 100644 --- a/src/project.cc +++ b/src/project.cc @@ -55,15 +55,6 @@ std::pair lookupExtension(std::string_view filename) { namespace { -enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand }; - -struct ProjectConfig { - std::unordered_set quote_dirs; - std::unordered_set angle_dirs; - std::string root; - ProjectMode mode = ProjectMode::CompileCommandsJson; -}; - enum OptionClass { EqOrJoinOrSep, EqOrSep, @@ -72,30 +63,33 @@ enum OptionClass { }; struct ProjectProcessor { - ProjectConfig *config; + Project::Folder &folder; std::unordered_set command_set; StringSet<> excludeArgs; - ProjectProcessor(ProjectConfig *config) : config(config) { + ProjectProcessor(Project::Folder &folder) : folder(folder) { for (auto &arg : g_config->clang.excludeArgs) excludeArgs.insert(arg); } + // Expand %c %cpp ... in .ccls void Process(Project::Entry &entry) { - const std::string base_name = sys::path::filename(entry.filename); - - // Expand %c %cpp %clang - std::vector args; - args.reserve(entry.args.size() + g_config->clang.extraArgs.size() + 1); - const LanguageId lang = lookupExtension(entry.filename).first; - for (const char *arg : entry.args) { + std::vector args(entry.args.begin(), + entry.args.begin() + entry.compdb_size); + auto [lang, header] = lookupExtension(entry.filename); + for (int i = entry.compdb_size; i < entry.args.size(); i++) { + const char *arg = entry.args[i]; StringRef A(arg); if (A[0] == '%') { bool ok = false; for (;;) { if (A.consume_front("%c ")) ok |= lang == LanguageId::C; + else if (A.consume_front("%h ")) + ok |= lang == LanguageId::C && header; else if (A.consume_front("%cpp ")) ok |= lang == LanguageId::Cpp; + else if (A.consume_front("%hpp ")) + ok |= lang == LanguageId::Cpp && header; else if (A.consume_front("%objective-c ")) ok |= lang == LanguageId::ObjC; else if (A.consume_front("%objective-cpp ")) @@ -109,14 +103,16 @@ struct ProjectProcessor { args.push_back(arg); } } - if (args.empty()) - return; - for (const std::string &arg : g_config->clang.extraArgs) - args.push_back(Intern(arg)); + entry.args = args; + GetSearchDirs(entry); + } + void GetSearchDirs(Project::Entry &entry) { +#if LLVM_VERSION_MAJOR < 8 + const std::string base_name = sys::path::filename(entry.filename); size_t hash = std::hash{}(entry.directory); bool OPT_o = false; - for (auto &arg : args) { + for (auto &arg : entry.args) { bool last_o = OPT_o; OPT_o = false; if (arg[0] == '-') { @@ -134,12 +130,14 @@ struct ProjectProcessor { } hash_combine(hash, std::hash{}(arg)); } - args.push_back(Intern("-working-directory=" + entry.directory)); - entry.args = args; -#if LLVM_VERSION_MAJOR < 8 - args.push_back("-fsyntax-only"); if (!command_set.insert(hash).second) return; + auto args = entry.args; + args.push_back("-fsyntax-only"); + for (const std::string &arg : g_config->clang.extraArgs) + args.push_back(Intern(arg)); + args.push_back(Intern("-working-directory=" + entry.directory)); + args.push_back(Intern("-resource-dir=" + g_config->clang.resourceDir)); // a weird C++ deduction guide heap-use-after-free causes libclang to crash. IgnoringDiagConsumer DiagC; @@ -174,16 +172,16 @@ struct ProjectProcessor { for (auto &E : HeaderOpts.UserEntries) { std::string path = NormalizePath(ResolveIfRelative(entry.directory, E.Path)); + EnsureEndsInSlash(path); switch (E.Group) { default: - config->angle_dirs.insert(path); + folder.search_dir2kind[path] |= 2; break; case frontend::Quoted: - config->quote_dirs.insert(path); + folder.search_dir2kind[path] |= 1; break; case frontend::Angled: - config->angle_dirs.insert(path); - config->quote_dirs.insert(path); + folder.search_dir2kind[path] |= 3; break; } } @@ -205,23 +203,47 @@ ReadCompilerArgumentsFromFile(const std::string &path) { return args; } -std::vector LoadFromDirectoryListing(ProjectConfig *config) { - std::vector result; - config->mode = ProjectMode::DotCcls; +bool AppendToCDB(const std::vector &args) { + return args.size() && StringRef("%compile_commands.json") == args[0]; +} - std::unordered_map> folder_args; +std::vector GetFallback(const std::string path) { + std::vector argv{"clang"}; + if (sys::path::extension(path) == ".h") + argv.push_back("-xobjective-c++-header"); + argv.push_back(Intern(path)); + return argv; +} + +void LoadDirectoryListing(ProjectProcessor &proc, const std::string &root, + const StringSet<> &Seen) { + Project::Folder &folder = proc.folder; std::vector files; - const std::string &root = config->root; - GetFilesInFolder(root, true /*recursive*/, - true /*add_folder_to_path*/, - [&folder_args, &files](const std::string &path) { + auto GetDotCcls = [&root, &folder](std::string cur) { + while (!(cur = sys::path::parent_path(cur)).empty()) { + auto it = folder.dot_ccls.find(cur); + if (it != folder.dot_ccls.end()) + return it->second; + std::string normalized = NormalizePath(cur); + // Break if outside of the project root. + if (normalized.size() <= root.size() || + normalized.compare(0, root.size(), root) != 0) + break; + } + return folder.dot_ccls[root]; + }; + + GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/, + [&folder, &files, &Seen](const std::string &path) { std::pair lang = lookupExtension(path); if (lang.first != LanguageId::Unknown && !lang.second) { - files.push_back(path); + if (!Seen.count(path)) + files.push_back(path); } else if (sys::path::filename(path) == ".ccls") { std::vector args = ReadCompilerArgumentsFromFile(path); - folder_args.emplace(sys::path::parent_path(path), args); + folder.dot_ccls.emplace(sys::path::parent_path(path), + args); std::string l; for (size_t i = 0; i < args.size(); i++) { if (i) @@ -232,131 +254,28 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { } }); - auto GetCompilerArgumentForFile = [&root, - &folder_args](std::string cur) { - while (!(cur = sys::path::parent_path(cur)).empty()) { - auto it = folder_args.find(cur); - if (it != folder_args.end()) - return it->second; - std::string normalized = NormalizePath(cur); - // Break if outside of the project root. - if (normalized.size() <= root.size() || - normalized.compare(0, root.size(), root) != 0) - break; + // If the first line of .ccls is %compile_commands.json, append extra flags. + for (auto &e : folder.entries) + if (const auto &args = GetDotCcls(e.filename); AppendToCDB(args)) { + if (args.size()) + e.args.insert(e.args.end(), args.begin() + 1, args.end()); + proc.Process(e); } - return folder_args[root]; - }; - - ProjectProcessor proc(config); - for (const std::string &file : files) { - Project::Entry e; - e.root = config->root; - e.directory = config->root; - e.filename = file; - e.args = GetCompilerArgumentForFile(file); - if (e.args.empty()) - e.args.push_back("%clang"); - e.args.push_back(Intern(e.filename)); - proc.Process(e); - result.push_back(e); - } - - return result; -} - -std::vector -LoadEntriesFromDirectory(ProjectConfig *project, - const std::string &opt_compdb_dir) { - // If there is a .ccls file always load using directory listing. - SmallString<256> Path, CclsPath; - sys::path::append(CclsPath, project->root, ".ccls"); - if (sys::fs::exists(CclsPath)) - return LoadFromDirectoryListing(project); - - // If |compilationDatabaseCommand| is specified, execute it to get the compdb. - std::string comp_db_dir; - if (g_config->compilationDatabaseCommand.empty()) { - project->mode = ProjectMode::CompileCommandsJson; - // Try to load compile_commands.json, but fallback to a project listing. - comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir; - sys::path::append(Path, comp_db_dir, "compile_commands.json"); - } else { - project->mode = ProjectMode::ExternalCommand; -#ifdef _WIN32 - // TODO -#else - char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; - if (!mkdtemp(tmpdir)) - return {}; - comp_db_dir = tmpdir; - sys::path::append(Path, comp_db_dir, "compile_commands.json"); - rapidjson::StringBuffer input; - rapidjson::Writer writer(input); - JsonWriter json_writer(&writer); - Reflect(json_writer, *g_config); - std::string contents = GetExternalCommandOutput( - std::vector{g_config->compilationDatabaseCommand, - project->root}, - input.GetString()); - FILE *fout = fopen(Path.c_str(), "wb"); - fwrite(contents.c_str(), contents.size(), 1, fout); - fclose(fout); -#endif - } - - std::string err_msg; - std::unique_ptr CDB = - tooling::CompilationDatabase::loadFromDirectory(comp_db_dir, err_msg); - if (!g_config->compilationDatabaseCommand.empty()) { -#ifdef _WIN32 - // TODO -#else - unlink(Path.c_str()); - rmdir(comp_db_dir.c_str()); -#endif - } - if (!CDB) { - LOG_S(WARNING) << "no .ccls or compile_commands.json . Consider adding one"; - return LoadFromDirectoryListing(project); - } - - LOG_S(INFO) << "loaded " << Path.c_str(); - - StringSet<> Seen; - std::vector result; - ProjectProcessor proc(project); - for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { - static bool once; - Project::Entry entry; - entry.root = project->root; - DoPathMapping(entry.root); - entry.directory = NormalizePath(Cmd.Directory); - DoPathMapping(entry.directory); - entry.filename = - NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); - DoPathMapping(entry.filename); - std::vector args = std::move(Cmd.CommandLine); - entry.args.reserve(args.size()); - for (std::string &arg : args) { - DoPathMapping(arg); - entry.args.push_back(Intern(arg)); + // Set flags for files not in compile_commands.json + for (const std::string &file : files) + if (const auto &args = GetDotCcls(file); !AppendToCDB(args)) { + Project::Entry e; + e.root = e.directory = root; + e.filename = file; + if (args.empty()) { + e.args = GetFallback(e.filename); + } else { + e.args = args; + e.args.push_back(Intern(e.filename)); + } + proc.Process(e); + folder.entries.push_back(e); } - - // Work around relative --sysroot= as it isn't affected by - // -working-directory=. chdir is thread hostile but this function runs - // before indexers do actual work and it works when there is only one - // workspace folder. - if (!once) { - once = true; - llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory( - entry.directory); - } - proc.Process(entry); - - if (Seen.insert(entry.filename).second) - result.push_back(entry); - } - return result; } // Computes a score based on how well |a| and |b| match. This is used for @@ -368,7 +287,7 @@ int ComputeGuessScore(std::string_view a, std::string_view b) { size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin(); size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin(); int score = 10 * i + j; - if (i + j < b.size()) + if (i + j < a.size()) score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') + std::count(b.begin() + i, b.end() - j, '/')); return score; @@ -376,29 +295,115 @@ int ComputeGuessScore(std::string_view a, std::string_view b) { } // namespace +void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { + SmallString<256> Path, CDBDir; + folder.entries.clear(); + if (g_config->compilationDatabaseCommand.empty()) { + CDBDir = root; + if (g_config->compilationDatabaseDirectory.size()) + sys::path::append(CDBDir, g_config->compilationDatabaseDirectory); + sys::path::append(Path, CDBDir, "compile_commands.json"); + } else { + // If `compilationDatabaseCommand` is specified, execute it to get the + // compdb. +#ifdef _WIN32 + // TODO +#else + char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; + if (!mkdtemp(tmpdir)) + return; + CDBDir = tmpdir; + sys::path::append(Path, CDBDir, "compile_commands.json"); + rapidjson::StringBuffer input; + rapidjson::Writer writer(input); + JsonWriter json_writer(&writer); + Reflect(json_writer, *g_config); + std::string contents = GetExternalCommandOutput( + std::vector{g_config->compilationDatabaseCommand, root}, + input.GetString()); + FILE *fout = fopen(Path.c_str(), "wb"); + fwrite(contents.c_str(), contents.size(), 1, fout); + fclose(fout); +#endif + } + + std::string err_msg; + std::unique_ptr CDB = + tooling::CompilationDatabase::loadFromDirectory(CDBDir, err_msg); + if (!g_config->compilationDatabaseCommand.empty()) { +#ifdef _WIN32 + // TODO +#else + unlink(Path.c_str()); + rmdir(CDBDir.c_str()); +#endif + } + + ProjectProcessor proc(folder); + StringSet<> Seen; + std::vector result; + if (CDB) { + LOG_S(INFO) << "loaded " << Path.c_str(); + for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { + static bool once; + Project::Entry entry; + entry.root = root; + DoPathMapping(entry.root); + entry.directory = NormalizePath(Cmd.Directory); + DoPathMapping(entry.directory); + entry.filename = + NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); + DoPathMapping(entry.filename); + std::vector args = std::move(Cmd.CommandLine); + entry.args.reserve(args.size()); + for (std::string &arg : args) { + DoPathMapping(arg); + if (!proc.excludeArgs.count(arg)) + entry.args.push_back(Intern(arg)); + } + entry.compdb_size = entry.args.size(); + + // Work around relative --sysroot= as it isn't affected by + // -working-directory=. chdir is thread hostile but this function runs + // before indexers do actual work and it works when there is only one + // workspace folder. + if (!once) { + once = true; + llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory( + entry.directory); + } + proc.GetSearchDirs(entry); + + if (Seen.insert(entry.filename).second) + folder.entries.push_back(entry); + } + } + + // Use directory listing if .ccls exists or compile_commands.json does not + // exist. + Path.clear(); + sys::path::append(Path, root, ".ccls"); + if (sys::fs::exists(Path)) + LoadDirectoryListing(proc, root, Seen); + std::vector extra_args; + for (const std::string &arg : g_config->clang.extraArgs) + extra_args.push_back(Intern(arg)); + for (auto &e : folder.entries) { + e.args.insert(e.args.end(), extra_args.begin(), extra_args.end()); + e.args.push_back(Intern("-working-directory=" + e.directory)); + } +} + void Project::Load(const std::string &root) { assert(root.back() == '/'); - ProjectConfig project; - project.root = root; + std::lock_guard lock(mtx); Folder &folder = root2folder[root]; - folder.entries = LoadEntriesFromDirectory( - &project, g_config->compilationDatabaseDirectory); - folder.quote_search_list.assign(project.quote_dirs.begin(), - project.quote_dirs.end()); - folder.angle_search_list.assign(project.angle_dirs.begin(), - project.angle_dirs.end()); - for (std::string &path : folder.angle_search_list) { - EnsureEndsInSlash(path); - LOG_S(INFO) << "angle search: " << path; - } - for (std::string &path : folder.quote_search_list) { - EnsureEndsInSlash(path); - LOG_S(INFO) << "quote search: " << path; - } + LoadDirectory(root, folder); + for (auto &[path, kind] : folder.search_dir2kind) + LOG_S(INFO) << "search directory: " << path << ' ' << " \"< "[kind]; // Setup project entries. - std::lock_guard lock(mutex_); folder.path2entry_index.reserve(folder.entries.size()); for (size_t i = 0; i < folder.entries.size(); ++i) { folder.entries[i].id = i; @@ -408,7 +413,9 @@ void Project::Load(const std::string &root) { Project::Entry Project::FindEntry(const std::string &path, bool can_be_inferred) { - std::lock_guard lock(mutex_); + Project::Folder *best_folder = nullptr; + const Entry *best = nullptr; + std::lock_guard lock(mtx); for (auto &[root, folder] : root2folder) { auto it = folder.path2entry_index.find(path); if (it != folder.path2entry_index.end()) { @@ -418,46 +425,80 @@ Project::Entry Project::FindEntry(const std::string &path, } } - Project::Entry result; - const Entry *best_entry = nullptr; - int best_score = INT_MIN; - for (auto &[root, folder] : root2folder) { - for (const Entry &entry : folder.entries) { - int score = ComputeGuessScore(path, entry.filename); - if (score > best_score) { - best_score = score; - best_entry = &entry; - } - } + std::string dir; + const std::vector *extra = nullptr; + Project::Entry ret; + for (auto &[root, folder] : root2folder) if (StringRef(path).startswith(root)) - result.root = root; + for (auto &[dir1, args] : folder.dot_ccls) + if (StringRef(path).startswith(dir1)) { + if (AppendToCDB(args)) { + dir = dir1; + extra = &args; + goto out; + } + ret.root = ret.directory = root; + ret.filename = path; + if (args.empty()) { + ret.args = GetFallback(path); + } else { + ret.args = args; + ret.args.push_back(Intern(path)); + } + ProjectProcessor(folder).Process(ret); + for (const std::string &arg : g_config->clang.extraArgs) + ret.args.push_back(Intern(arg)); + ret.args.push_back(Intern("-working-directory=" + ret.directory)); + return ret; + } +out: + + if (!best) { + int best_score = INT_MIN; + for (auto &[root, folder] : root2folder) { + if (dir.size() && !StringRef(path).startswith(dir)) + continue; + for (const Entry &e : folder.entries) + if (e.compdb_size) { + int score = ComputeGuessScore(path, e.filename); + if (score > best_score) { + best_score = score; + best = &e; + best_folder = &folder; + } + } + } } - if (result.root.empty()) - result.root = g_config->fallbackFolder; - result.is_inferred = true; - result.filename = path; - if (!best_entry) { - result.args.push_back("%clang"); - result.args.push_back(Intern(path)); + ret.is_inferred = true; + ret.filename = path; + if (!best) { + if (ret.root.empty()) + ret.root = g_config->fallbackFolder; + ret.directory = ret.root; + ret.args = GetFallback(path); } else { - result.args = best_entry->args; - - // |best_entry| probably has its own path in the arguments. We need to remap - // that path to the new filename. - std::string best_entry_base_name = - sys::path::filename(best_entry->filename); - for (const char *&arg : result.args) { + ret.root = best->root; + ret.directory = best->directory; + ret.args = best->args; + std::string base_name = sys::path::filename(best->filename); + for (const char *&arg : ret.args) { try { - if (arg == best_entry->filename || - sys::path::filename(arg) == best_entry_base_name) + if (arg == best->filename || sys::path::filename(arg) == base_name) arg = Intern(path); } catch (...) { } } + ret.args.resize(best->compdb_size); + if (extra && extra->size()) + ret.args.insert(ret.args.end(), extra->begin() + 1, extra->end()); + ProjectProcessor(*best_folder).Process(ret); + for (const std::string &arg : g_config->clang.extraArgs) + ret.args.push_back(Intern(arg)); + ret.args.push_back(Intern("-working-directory=" + ret.directory)); } - return result; + return ret; } void Project::Index(WorkingFiles *wfiles, RequestId id) { @@ -465,7 +506,7 @@ void Project::Index(WorkingFiles *wfiles, RequestId id) { GroupMatch match(gi.whitelist, gi.blacklist), match_i(gi.initialWhitelist, gi.initialBlacklist); { - std::lock_guard lock(mutex_); + std::lock_guard lock(mtx); for (auto &[root, folder] : root2folder) { int i = 0; for (const Project::Entry &entry : folder.entries) { diff --git a/src/project.hh b/src/project.hh index 1b14afaf..0434f402 100644 --- a/src/project.hh +++ b/src/project.hh @@ -25,20 +25,20 @@ struct Project { std::vector args; // If true, this entry is inferred and was not read from disk. bool is_inferred = false; + // 0 unless coming from a compile_commands.json entry. + int compdb_size = 0; int id = -1; }; struct Folder { std::string name; - // Include directories for <> headers - std::vector angle_search_list; - // Include directories for "" headers - std::vector quote_search_list; + std::unordered_map search_dir2kind; std::vector entries; std::unordered_map path2entry_index; + std::unordered_map> dot_ccls; }; - std::mutex mutex_; + std::mutex mtx; std::unordered_map root2folder; // Loads a project for the given |directory|. @@ -51,7 +51,8 @@ struct Project { // will affect flags in their subtrees (relative paths are relative to the // project root, not subdirectories). For compile_commands.json, its entries // are indexed. - void Load(const std::string &root_directory); + void Load(const std::string &root); + void LoadDirectory(const std::string &root, Folder &folder); // Lookup the CompilationEntry for |filename|. If no entry was found this // will infer one based on existing project structure.