diff --git a/src/clang_complete.cc b/src/clang_complete.cc index a5456c53..9f9b671f 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -573,7 +573,6 @@ void CompletionMain(ClangCompleteManager *completion_manager) { BuildCompilerInvocation(session->file.args, session->FS); if (!CI) continue; - CI->getDiagnosticOpts().IgnoreWarnings = true; clang::CodeCompleteOptions CCOpts; CCOpts.IncludeBriefComments = true; #if LLVM_VERSION_MAJOR >= 7 @@ -612,8 +611,6 @@ void DiagnosticMain(ClangCompleteManager *manager) { // Fetching the completion request blocks until we have a request. ClangCompleteManager::DiagnosticRequest request = manager->diagnostic_request_.Dequeue(); - if (!g_config->diagnostics.onChange) - continue; std::string path = request.document.uri.GetPath(); std::shared_ptr session = manager->TryGetSession( @@ -623,7 +620,8 @@ void DiagnosticMain(ClangCompleteManager *manager) { BuildCompilerInvocation(session->file.args, session->FS); if (!CI) continue; - CI->getLangOpts()->SpellChecking = true; + CI->getDiagnosticOpts().IgnoreWarnings = false; + CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking; StoreDiags DC; WorkingFiles::Snapshot snapshot = manager->working_files_->AsSnapshot({StripFileType(path)}); @@ -680,6 +678,7 @@ void CompletionSession::BuildPreamble(CompilerInvocation &CI) { auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0); if (OldP && OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get())) return; + CI.getDiagnosticOpts().IgnoreWarnings = false; CI.getFrontendOpts().SkipFunctionBodies = true; CI.getLangOpts()->CommentOpts.ParseAllComments = true; #if LLVM_VERSION_MAJOR >= 7 diff --git a/src/clang_tu.cc b/src/clang_tu.cc index be2f1679..d4646705 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -75,6 +75,7 @@ BuildCompilerInvocation(const std::vector &args, std::unique_ptr CI = createInvocationFromCommandLine(cargs, Diags, VFS); if (CI) { + CI->getDiagnosticOpts().IgnoreWarnings = true; CI->getFrontendOpts().DisableFree = false; CI->getLangOpts()->SpellChecking = false; } diff --git a/src/config.h b/src/config.h index 79e7ee75..ae234f72 100644 --- a/src/config.h +++ b/src/config.h @@ -21,14 +21,7 @@ limitations under the License. /* The language client plugin needs to send initialization options in the -`initialize` request to the ccls language server. The only required option is -`cacheDirectory`, which is where index files will be stored. - - { - "initializationOptions": { - "cacheDirectory": "/tmp/ccls" - } - } +`initialize` request to the ccls language server. If necessary, the command line option --init can be used to override initialization options specified by the client. For example, in shell syntax: @@ -47,6 +40,7 @@ struct Config { std::string compilationDatabaseDirectory; // Cache directory for indexed files, either absolute or relative to the // project root. + // If empty, cache will be stored in memory. std::string cacheDirectory = ".ccls-cache"; // Cache serialization format. // @@ -54,12 +48,14 @@ struct Config { // printed with jq. // // "binary" uses a compact binary serialization format. - // It is not schema-aware and you need to re-index whenever a struct + // It is not schema-aware and you need to re-index whenever an internal struct // member has changed. SerializeFormat cacheFormat = SerializeFormat::Binary; struct Clang { // Arguments that should be excluded, e.g. ["-fopenmp", "-Wall"] + // + // e.g. If your project is built by GCC and has an option thag clang does not understand. std::vector excludeArgs; // Additional arguments to pass to clang. diff --git a/src/file_consumer.h b/src/file_consumer.h index b0292084..d2c75894 100644 --- a/src/file_consumer.h +++ b/src/file_consumer.h @@ -45,6 +45,7 @@ struct VFS { int64_t timestamp; int owner; int stage; + bool loaded = false; }; mutable std::unordered_map state; mutable std::mutex mutex; diff --git a/src/indexer.cc b/src/indexer.cc index 177d4be5..567dcf50 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -51,7 +51,7 @@ struct IndexParam { std::unordered_map SeenUniqueID; std::unordered_map UID2multi; std::unordered_map file_contents; - std::unordered_map file2write_time; + std::unordered_map file2mtime; struct DeclInfo { Usr usr; std::string short_name; @@ -71,13 +71,7 @@ struct IndexParam { if (inserted) { std::string file_name = FileName(File); it->second = file_name; - - // Set modification time. - std::optional write_time = LastWriteTime(file_name); - LOG_IF_S(ERROR, !write_time) - << "failed to fetch write time for " << file_name; - if (write_time) - file2write_time[file_name] = *write_time; + file2mtime[file_name] = File.getModificationTime(); } } @@ -1307,13 +1301,13 @@ Index(VFS *vfs, const std::string &opt_wdir, const std::string &file, Uniquify(it.second.uses); // Update file contents and modification time. - entry->last_write_time = param.file2write_time[entry->path]; + entry->mtime = param.file2mtime[entry->path]; // Update dependencies for the file. Do not include the file in its own // dependency set. for (auto &[_, path] : param.SeenUniqueID) if (path != entry->path && path != entry->import_file) - entry->dependencies[path] = param.file2write_time[path]; + entry->dependencies[path] = param.file2mtime[path]; } return result; diff --git a/src/indexer.h b/src/indexer.h index dc4fe774..797b0096 100644 --- a/src/indexer.h +++ b/src/indexer.h @@ -247,7 +247,8 @@ struct IndexFile { llvm::sys::fs::UniqueID UniqueID; std::string path; std::vector args; - int64_t last_write_time = 0; + // This is unfortunately time_t as used by clang::FileEntry + int64_t mtime = 0; LanguageId language = LanguageId::C; // uid2lid_and_path is used to generate lid2path, but not serialized. diff --git a/src/messages/ccls_freshenIndex.cc b/src/messages/ccls_freshenIndex.cc index edf23b4d..a49e0243 100644 --- a/src/messages/ccls_freshenIndex.cc +++ b/src/messages/ccls_freshenIndex.cc @@ -69,7 +69,8 @@ struct Handler_CclsFreshenIndex : BaseMessageHandler { q.pop(); need_index.insert(file->def->path); - std::optional write_time = LastWriteTime(file->def->path); + std::optional write_time = + pipeline::LastWriteTime(file->def->path); if (!write_time) continue; { diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index cfbca19e..fdbfa349 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -438,10 +438,7 @@ struct Handler_Initialize : BaseMessageHandler { Reflect(json_writer, *g_config); LOG_S(INFO) << "initializationOptions: " << output.GetString(); - if (g_config->cacheDirectory.empty()) { - LOG_S(ERROR) << "cacheDirectory cannot be empty."; - exit(1); - } else { + if (g_config->cacheDirectory.size()) { g_config->cacheDirectory = NormalizePath(g_config->cacheDirectory); EnsureEndsInSlash(g_config->cacheDirectory); } @@ -470,12 +467,14 @@ struct Handler_Initialize : BaseMessageHandler { // Set project root. EnsureEndsInSlash(project_path); g_config->projectRoot = project_path; - // Create two cache directories for files inside and outside of the - // project. - sys::fs::create_directories(g_config->cacheDirectory + - EscapeFileName(g_config->projectRoot)); - sys::fs::create_directories(g_config->cacheDirectory + '@' + - EscapeFileName(g_config->projectRoot)); + if (g_config->cacheDirectory.size()) { + // Create two cache directories for files inside and outside of the + // project. + sys::fs::create_directories(g_config->cacheDirectory + + EscapeFileName(g_config->projectRoot)); + sys::fs::create_directories(g_config->cacheDirectory + '@' + + EscapeFileName(g_config->projectRoot)); + } diag_pub->Init(); idx::Init(); diff --git a/src/messages/textDocument_didChange.cc b/src/messages/textDocument_didChange.cc index 1704282d..f010ae04 100644 --- a/src/messages/textDocument_didChange.cc +++ b/src/messages/textDocument_didChange.cc @@ -36,15 +36,17 @@ struct Handler_TextDocumentDidChange MethodType GetMethodType() const override { return kMethodType; } void Run(In_TextDocumentDidChange *request) override { - std::string path = request->params.textDocument.uri.GetPath(); - working_files->OnChange(request->params); + const auto ¶ms = request->params; + std::string path = params.textDocument.uri.GetPath(); + working_files->OnChange(params); if (g_config->index.onChange) { Project::Entry entry = project->FindCompilationEntryForFile(path); pipeline::Index(entry.filename, entry.args, IndexMode::OnChange); } - clang_complete->NotifyEdit(path); - clang_complete->DiagnosticsUpdate( - request->params.textDocument.AsTextDocumentIdentifier()); + clang_complete->NotifyView(path); + if (g_config->diagnostics.onChange) + clang_complete->DiagnosticsUpdate( + params.textDocument.AsTextDocumentIdentifier()); } }; REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidChange); diff --git a/src/messages/textDocument_didOpen.cc b/src/messages/textDocument_didOpen.cc index b88d3b94..2dc25c01 100644 --- a/src/messages/textDocument_didOpen.cc +++ b/src/messages/textDocument_didOpen.cc @@ -53,7 +53,7 @@ struct Handler_TextDocumentDidOpen WorkingFile *working_file = working_files->OnOpen(params.textDocument); if (std::optional cached_file_contents = - pipeline::LoadCachedFileContents(path)) + pipeline::LoadIndexedContent(path)) working_file->SetIndexContent(*cached_file_contents); QueryFile *file = nullptr; diff --git a/src/pipeline.cc b/src/pipeline.cc index c9f5c370..c973f564 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -95,12 +95,18 @@ ThreadedQueue *index_request; ThreadedQueue *on_indexed; ThreadedQueue *for_stdout; +struct InMemoryIndexFile { + std::string content; + IndexFile index; +}; +std::unordered_map g_index; + bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, const std::vector &args, const std::optional &from) { { std::lock_guard lock(vfs->mutex); - if (prev->last_write_time < vfs->state[path].timestamp) { + if (prev->mtime < vfs->state[path].timestamp) { LOG_S(INFO) << "timestamp changed for " << path << (from ? " (via " + *from + ")" : std::string()); return true; @@ -140,6 +146,13 @@ std::string GetCachePath(const std::string &source_file) { } std::unique_ptr RawCacheLoad(const std::string &path) { + if (g_config->cacheDirectory.empty()) { + auto it = g_index.find(path); + if (it == g_index.end()) + return nullptr; + return std::make_unique(it->second.index); + } + std::string cache_path = GetCachePath(path); std::optional file_content = ReadContent(cache_path); std::optional serialized_indexed_content = @@ -267,7 +280,18 @@ bool Indexer_Parse(DiagnosticsPublisher *diag_pub, WorkingFiles *working_files, for (std::unique_ptr &curr : indexes) { std::string path = curr->path; - if (!(vfs->Stamp(path, curr->last_write_time) || path == path_to_index)) + bool do_update = path == path_to_index, loaded; + { + std::lock_guard lock(vfs->mutex); + VFS::State &st = vfs->state[path]; + if (st.timestamp < curr->mtime) { + st.timestamp = curr->mtime; + do_update = true; + } + loaded = st.loaded; + st.loaded = true; + } + if (!do_update) continue; if (std::string reason; !matcher.IsMatch(path, &reason)) { LOG_IF_S(INFO, loud) << "skip emitting and storing index of " << path << " for " @@ -275,15 +299,22 @@ bool Indexer_Parse(DiagnosticsPublisher *diag_pub, WorkingFiles *working_files, continue; } - LOG_IF_S(INFO, loud) << "emit index for " << path; - prev = RawCacheLoad(path); + prev.reset(); + if (loaded) + prev = RawCacheLoad(path); - // Write current index to disk if requested. - { + // Store current index. + LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev + << ")"; + if (g_config->cacheDirectory.empty()) { + auto it = g_index.insert_or_assign( + path, InMemoryIndexFile{curr->file_contents, *curr}); + std::string().swap(it.first->second.index.file_contents); + } else { std::string cache_path = GetCachePath(path); WriteToFile(cache_path, curr->file_contents); WriteToFile(AppendSerializationFormat(cache_path), - Serialize(g_config->cacheFormat, *curr)); + Serialize(g_config->cacheFormat, *curr)); } vfs->Reset(path); @@ -295,8 +326,6 @@ bool Indexer_Parse(DiagnosticsPublisher *diag_pub, WorkingFiles *working_files, // Build delta update. IndexUpdate update = IndexUpdate::CreateDelta(prev.get(), curr.get()); - LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev - << ")"; on_indexed->PushBack(std::move(update), request.mode != IndexMode::NonInteractive); @@ -510,7 +539,20 @@ void Index(const std::string &path, const std::vector &args, index_request->PushBack({path, args, mode, id}, mode != IndexMode::NonInteractive); } -std::optional LoadCachedFileContents(const std::string &path) { +std::optional LastWriteTime(const std::string &path) { + sys::fs::file_status Status; + if (sys::fs::status(path, Status)) + return {}; + return sys::toTimeT(Status.getLastModificationTime()); +} + +std::optional LoadIndexedContent(const std::string &path) { + if (g_config->cacheDirectory.empty()) { + auto it = g_index.find(path); + if (it == g_index.end()) + return {}; + return it->second.content; + } return ReadContent(GetCachePath(path)); } diff --git a/src/pipeline.hh b/src/pipeline.hh index 3ad96636..3c49285c 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -46,7 +46,8 @@ void MainLoop(); void Index(const std::string &path, const std::vector &args, IndexMode mode, lsRequestId id = {}); -std::optional LoadCachedFileContents(const std::string& path); -void WriteStdout(MethodType method, lsBaseOutMessage& response); -} -} +std::optional LastWriteTime(const std::string &path); +std::optional LoadIndexedContent(const std::string& path); +void WriteStdout(MethodType method, lsBaseOutMessage &response); +} // namespace pipeline +} // namespace ccls diff --git a/src/serializer.cc b/src/serializer.cc index 487650b0..41c10d9a 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -309,7 +309,7 @@ bool ReflectMemberStart(Writer &visitor, IndexFile &value) { template void Reflect(TVisitor &visitor, IndexFile &value) { REFLECT_MEMBER_START(); if (!gTestOutputMode) { - REFLECT_MEMBER(last_write_time); + REFLECT_MEMBER(mtime); REFLECT_MEMBER(language); REFLECT_MEMBER(lid2path); REFLECT_MEMBER(import_file); diff --git a/src/utils.cc b/src/utils.cc index 36a7e0e4..df5b98c8 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -15,13 +15,13 @@ limitations under the License. #include "utils.h" -#include "filesystem.hh" -using namespace llvm; #include "log.hh" #include "platform.h" #include +#include "llvm/ADT/StringRef.h" + #include #include #include @@ -29,7 +29,6 @@ using namespace llvm; #include #include #include -using namespace std::placeholders; void TrimInPlace(std::string &s) { auto f = [](char c) { return !isspace(c); }; @@ -69,7 +68,8 @@ bool StartsWith(std::string_view s, std::string_view prefix) { } bool EndsWithAny(std::string_view s, const std::vector &ss) { - return std::any_of(ss.begin(), ss.end(), std::bind(EndsWith, s, _1)); + return std::any_of(ss.begin(), ss.end(), + std::bind(EndsWith, s, std::placeholders::_1)); } bool FindAnyPartial(const std::string &value, @@ -147,13 +147,6 @@ void WriteToFile(const std::string &filename, const std::string &content) { fclose(f); } -std::optional LastWriteTime(const std::string &filename) { - sys::fs::file_status Status; - if (sys::fs::status(filename, Status)) - return {}; - return Status.getLastModificationTime().time_since_epoch().count(); -} - // Find discontinous |search| in |content|. // Return |found| and the count of skipped chars before found. int ReverseSubseqMatch(std::string_view pat, std::string_view text, diff --git a/src/utils.h b/src/utils.h index 973243fd..40352e1a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -75,7 +75,6 @@ std::string EscapeFileName(std::string path); std::optional ReadContent(const std::string &filename); void WriteToFile(const std::string &filename, const std::string &content); -std::optional LastWriteTime(const std::string &filename); int ReverseSubseqMatch(std::string_view pat, std::string_view text, int case_sensitivity);