From fc55589ed3597cb0c26348df135549838bd51864 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sat, 6 May 2017 22:36:29 -0700 Subject: [PATCH] Infer arguments for files not in the project. --- src/command_line.cc | 34 ++++++++-------- src/project.cc | 96 ++++++++++++++++++++++++++++++++++++++++----- src/project.h | 10 ++--- 3 files changed, 108 insertions(+), 32 deletions(-) diff --git a/src/command_line.cc b/src/command_line.cc index 81bf489b..6f73aba6 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -749,12 +749,11 @@ struct Index_DoIndex { Freshen, }; - Index_DoIndex(Type type, const std::string& path, const optional>& args) - : type(type), path(path), args(args) {} + Index_DoIndex(Type type, const Project::Entry& entry) + : type(type), entry(entry) {} Type type; - std::string path; - optional> args; + Project::Entry entry; }; struct Index_DoIdMap { @@ -955,16 +954,14 @@ bool ImportCachedIndex(IndexerConfig* config, void ParseFile(IndexerConfig* config, FileConsumer::SharedState* file_consumer_shared, Index_DoIdMapQueue* queue_do_id_map, - const std::string& tu_or_dep_path, - const optional>& args) { + const Project::Entry& entry) { Timer time; - std::unique_ptr cache_for_args = LoadCachedIndex(config, tu_or_dep_path); + std::unique_ptr cache_for_args = LoadCachedIndex(config, entry.filename); + std::string tu_path = cache_for_args ? cache_for_args->import_file : entry.filename; + const std::vector& tu_args = entry.args; - std::string tu_path = cache_for_args ? cache_for_args->import_file : tu_or_dep_path; - // TODO: Replace checking cache for arguments by guessing arguments on via directory structure. That will also work better for new files. - const std::vector& tu_args = args ? *args : cache_for_args ? cache_for_args->args : kEmptyArgs; std::vector> indexes = Parse( config, file_consumer_shared, tu_path, tu_args); @@ -1056,7 +1053,7 @@ bool IndexMain_DoIndex(IndexerConfig* config, // This assumes index_request->path is a cc or translation unit file (ie, // it is in compile_commands.json). - bool needs_reparse = ImportCachedIndex(config, file_consumer_shared, queue_do_id_map, index_request->path); + bool needs_reparse = ImportCachedIndex(config, file_consumer_shared, queue_do_id_map, index_request->entry.filename); // If the file has been updated, we need to reparse it. if (needs_reparse) { @@ -1072,8 +1069,8 @@ bool IndexMain_DoIndex(IndexerConfig* config, case Index_DoIndex::Type::Parse: { // index_request->path can be a cc/tu or a dependency path. - file_consumer_shared->Reset(index_request->path); - ParseFile(config, file_consumer_shared, queue_do_id_map, index_request->path, index_request->args); + file_consumer_shared->Reset(index_request->entry.filename); + ParseFile(config, file_consumer_shared, queue_do_id_map, index_request->entry); break; } @@ -1081,9 +1078,9 @@ bool IndexMain_DoIndex(IndexerConfig* config, // This assumes index_request->path is a cc or translation unit file (ie, // it is in compile_commands.json). - bool needs_reparse = ResetStaleFiles(config, file_consumer_shared, index_request->path); + bool needs_reparse = ResetStaleFiles(config, file_consumer_shared, index_request->entry.filename); if (needs_reparse) - ParseFile(config, file_consumer_shared, queue_do_id_map, index_request->path, index_request->args); + ParseFile(config, file_consumer_shared, queue_do_id_map, index_request->entry); break; } } @@ -1291,7 +1288,7 @@ bool QueryDbMainLoop( << "] Dispatching index request for file " << entry.filename << std::endl; - queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::ImportThenParse, entry.filename, entry.args)); + queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::ImportThenParse, entry)); }); } @@ -1342,7 +1339,7 @@ bool QueryDbMainLoop( std::cerr << "[" << i << "/" << (project->entries.size() - 1) << "] Dispatching index request for file " << entry.filename << std::endl; - queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::Freshen, entry.filename, entry.args)); + queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::Freshen, entry)); }); break; } @@ -1381,7 +1378,8 @@ bool QueryDbMainLoop( std::string path = msg->params.textDocument.uri.GetPath(); // Send an index update request. - queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::Parse, path, project->FindArgsForFile(path))); + // TODO: Make sure we don't mess up header files when guessing. + queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::Parse, project->FindCompilationEntryForFile(path))); // Copy current buffer content so it can be applied when index request is done. WorkingFile* working_file = working_files->GetFileByFilename(path); diff --git a/src/project.cc b/src/project.cc index 83a51a4f..de1b80eb 100644 --- a/src/project.cc +++ b/src/project.cc @@ -7,6 +7,7 @@ #include "utils.h" #include +#include #include #include @@ -44,7 +45,7 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry(const std::vector LoadCompilationEntriesFromDirectory(const std::vecto return result; } + +// Computes a score based on how well |a| and |b| match. This is used for +// argument guessing. +int ComputeGuessScore(const std::string& a, const std::string& b) { + int score = 0; + int i = 0; + + for (i = 0; i < a.length() && i < b.length(); ++i) { + if (a[i] != b[i]) + break; + ++score; + } + + for (int j = i; j < a.length(); ++j) { + if (a[j] == '/') + --score; + } + for (int j = i; j < b.length(); ++j) { + if (b[j] == '/') + --score; + } + + return score; +} + } // namespace void Project::Load(const std::vector& extra_flags, const std::string& directory) { @@ -200,18 +226,29 @@ void Project::Load(const std::vector& extra_flags, const std::strin absolute_path_to_entry_index_[entries[i].filename] = i; } -optional Project::FindCompilationEntryForFile(const std::string& filename) { +Project::Entry Project::FindCompilationEntryForFile(const std::string& filename) { auto it = absolute_path_to_entry_index_.find(filename); if (it != absolute_path_to_entry_index_.end()) return entries[it->second]; - return nullopt; -} -optional> Project::FindArgsForFile(const std::string& filename) { - auto entry = FindCompilationEntryForFile(filename); - if (!entry) - return nullopt; - return entry->args; + // We couldn't find the file. Try to infer it. + // TODO: Cache inferred file in a separate array (using a lock or similar) + Entry* best_entry = nullptr; + int best_score = 0; + for (Entry& entry : entries) { + int score = ComputeGuessScore(filename, entry.filename); + if (score > best_score) { + best_score = score; + best_entry = &entry; + } + } + + Project::Entry result; + result.is_inferred = true; + result.filename = filename; + if (best_entry) + result.args = best_entry->args; + return result; } void Project::ForAllFilteredFiles(IndexerConfig* config, std::function action) { @@ -260,3 +297,44 @@ void Project::ForAllFilteredFiles(IndexerConfig* config, std::function entry = p.FindCompilationEntryForFile("/a/b/c/d/new.cc"); + REQUIRE(entry.has_value()); + REQUIRE(entry->args == std::vector{ "arg1" }); + } + + // Guess at same directory level, when there are child directories. + { + optional entry = p.FindCompilationEntryForFile("/a/b/c/new.cc"); + REQUIRE(entry.has_value()); + REQUIRE(entry->args == std::vector{ "arg2" }); + } + + // Guess at new directory (use the closest parent directory). + { + optional entry = p.FindCompilationEntryForFile("/a/b/c/new/new.cc"); + REQUIRE(entry.has_value()); + REQUIRE(entry->args == std::vector{ "arg2" }); + } +} + +TEST_SUITE_END(); \ No newline at end of file diff --git a/src/project.h b/src/project.h index cabee6f8..ef3861f7 100644 --- a/src/project.h +++ b/src/project.h @@ -17,6 +17,8 @@ struct Project { struct Entry { std::string filename; std::vector args; + // If true, this entry is inferred and was not read from disk. + bool is_inferred = false; }; std::vector entries; @@ -30,11 +32,9 @@ struct Project { // specified in a clang_args file located inside of |directory|. void Load(const std::vector& extra_flags, const std::string& directory); - // Lookup the CompilationEntry for |filename|. - optional FindCompilationEntryForFile(const std::string& filename); - - // Helper that uses FindCompilationEntryForFile. - optional> FindArgsForFile(const std::string& filename); + // Lookup the CompilationEntry for |filename|. If no entry was found this + // will infer one based on existing project structure. + Entry FindCompilationEntryForFile(const std::string& filename); void ForAllFilteredFiles(IndexerConfig* config, std::function action); };