From cbd36aeedb5f1ac0f3ec9900e12490120ad86411 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Thu, 20 Dec 2018 20:53:50 -0800 Subject: [PATCH] Handle file deletion and register workspace/didChangeWatchedFiles * In the "initialized" callback, send client/registerCapability with DidChangeWatchedFilesRegistrationOptions * In workspace/didChangeWatchedFiles callback, call pipeline::Index * In pipeline::Index, add a `deleted` status --- src/indexer.cc | 8 +-- src/indexer.hh | 4 +- src/message_handler.cc | 1 + src/message_handler.hh | 1 + src/messages/initialize.cc | 29 ++++++++ src/messages/textDocument_did.cc | 6 +- src/messages/workspace.cc | 24 +++++-- src/pipeline.cc | 113 +++++++++++++++++++------------ src/pipeline.hh | 10 ++- src/project.cc | 25 ++++--- src/query.cc | 3 +- src/sema_manager.cc | 11 ++- src/serializer.cc | 6 +- 13 files changed, 160 insertions(+), 81 deletions(-) diff --git a/src/indexer.cc b/src/indexer.cc index 584dd20a..83c8cbc2 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -82,8 +82,7 @@ struct IndexParam { if (!vfs.Stamp(path, it->second.mtime, 1)) return; - it->second.db = std::make_unique(File.getUniqueID(), path, - it->second.content); + it->second.db = std::make_unique(path, it->second.content); } } @@ -1186,9 +1185,8 @@ public: const int IndexFile::kMajorVersion = 19; const int IndexFile::kMinorVersion = 1; -IndexFile::IndexFile(llvm::sys::fs::UniqueID UniqueID, const std::string &path, - const std::string &contents) - : UniqueID(UniqueID), path(path), file_contents(contents) {} +IndexFile::IndexFile(const std::string &path, const std::string &contents) + : path(path), file_contents(contents) {} IndexFunc &IndexFile::ToFunc(Usr usr) { auto [it, inserted] = usr2func.try_emplace(usr); diff --git a/src/indexer.hh b/src/indexer.hh index 88598510..191f8074 100644 --- a/src/indexer.hh +++ b/src/indexer.hh @@ -290,7 +290,6 @@ struct IndexFile { // files accepted by newer ccls. static const int kMinorVersion; - llvm::sys::fs::UniqueID UniqueID; std::string path; std::vector args; // This is unfortunately time_t as used by clang::FileEntry @@ -320,8 +319,7 @@ struct IndexFile { // File contents at the time of index. Not serialized. std::string file_contents; - IndexFile(llvm::sys::fs::UniqueID UniqueID, const std::string &path, - const std::string &contents); + IndexFile(const std::string &path, const std::string &contents); IndexFunc &ToFunc(Usr usr); IndexType &ToType(Usr usr); diff --git a/src/message_handler.cc b/src/message_handler.cc index 99e8197c..b88f777a 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -181,6 +181,7 @@ MessageHandler::MessageHandler() { Bind("$ccls/vars", &MessageHandler::ccls_vars); Bind("exit", &MessageHandler::exit); Bind("initialize", &MessageHandler::initialize); + Bind("initialized", &MessageHandler::initialized); Bind("shutdown", &MessageHandler::shutdown); Bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction); Bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens); diff --git a/src/message_handler.hh b/src/message_handler.hh index 7063fe1c..b0a3747c 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -250,6 +250,7 @@ private: void ccls_vars(JsonReader &, ReplyOnce &); void exit(EmptyParam &); void initialize(JsonReader &, ReplyOnce &); + void initialized(EmptyParam &); void shutdown(EmptyParam &, ReplyOnce &); void textDocument_codeAction(CodeActionParam &, ReplyOnce &); void textDocument_codeLens(TextDocumentParam &, ReplyOnce &); diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 10db5208..bbafa9d2 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -42,6 +42,8 @@ namespace { enum class TextDocumentSyncKind { None = 0, Full = 1, Incremental = 2 }; REFLECT_UNDERLYING(TextDocumentSyncKind) +bool didChangeWatchedFiles; + struct ServerCap { struct SaveOptions { bool includeText = false; @@ -239,6 +241,24 @@ struct InitializeResult { }; REFLECT_STRUCT(InitializeResult, capabilities); +struct FileSystemWatcher { + std::string globPattern = "**/*"; +}; +struct DidChangeWatchedFilesRegistration { + std::string id = "didChangeWatchedFiles"; + std::string method = "workspace/didChangeWatchedFiles"; + struct Option { + std::vector watchers = {{}}; + } registerOptions; +}; +struct RegistrationParam { + std::vector registrations = {{}}; +}; +REFLECT_STRUCT(FileSystemWatcher, globPattern); +REFLECT_STRUCT(DidChangeWatchedFilesRegistration::Option, watchers); +REFLECT_STRUCT(DidChangeWatchedFilesRegistration, id, method, registerOptions); +REFLECT_STRUCT(RegistrationParam, registrations); + void *Indexer(void *arg_) { MessageHandler *h; int idx; @@ -298,6 +318,8 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { capabilities.textDocument.definition.linkSupport; g_config->client.snippetSupport &= capabilities.textDocument.completion.completionItem.snippetSupport; + didChangeWatchedFiles = + capabilities.workspace.didChangeWatchedFiles.dynamicRegistration; // Ensure there is a resource directory. if (g_config->clang.resourceDir.empty()) @@ -370,6 +392,13 @@ void StandaloneInitialize(MessageHandler &handler, const std::string &root) { Initialize(&handler, param, reply); } +void MessageHandler::initialized(EmptyParam &) { + if (didChangeWatchedFiles) { + RegistrationParam param; + pipeline::Request("client/registerCapability", param); + } +} + void MessageHandler::shutdown(EmptyParam &, ReplyOnce &reply) { reply(JsonNull{}); } diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc index 2dc91629..b00b3c7d 100644 --- a/src/messages/textDocument_did.cc +++ b/src/messages/textDocument_did.cc @@ -25,7 +25,7 @@ void MessageHandler::textDocument_didChange(TextDocumentDidChangeParam ¶m) { std::string path = param.textDocument.uri.GetPath(); wfiles->OnChange(param); if (g_config->index.onChange) - pipeline::Index(path, {}, IndexMode::OnChange); + pipeline::Index(path, {}, IndexMode::OnChange, true); manager->OnView(path); if (g_config->diagnostics.onChange >= 0) manager->ScheduleDiag(path, g_config->diagnostics.onChange); @@ -56,14 +56,14 @@ void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { std::pair lang = lookupExtension(path); if ((lang.first != LanguageId::Unknown && !lang.second) || !pipeline::pending_index_requests) - pipeline::Index(path, {}, IndexMode::Normal); + pipeline::Index(path, {}, IndexMode::Normal, false); manager->OnView(path); } void MessageHandler::textDocument_didSave(TextDocumentParam ¶m) { const std::string &path = param.textDocument.uri.GetPath(); - pipeline::Index(path, {}, IndexMode::Normal); + pipeline::Index(path, {}, IndexMode::Normal, false); manager->OnSave(path); } } // namespace ccls diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index 5b049958..5435d98f 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -23,11 +23,13 @@ limitations under the License. #include #include +#include #include #include #include #include +using namespace llvm; namespace ccls { REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); @@ -44,20 +46,30 @@ void MessageHandler::workspace_didChangeWatchedFiles( DidChangeWatchedFilesParam ¶m) { for (auto &event : param.changes) { std::string path = event.uri.GetPath(); + if ((g_config->cacheDirectory.size() && + StringRef(path).startswith(g_config->cacheDirectory)) || + lookupExtension(path).first == LanguageId::Unknown) + return; + for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur)) + if (cur[0] == '.') + return; + IndexMode mode = wfiles->GetFile(path) ? IndexMode::Normal : IndexMode::NonInteractive; switch (event.type) { case FileChangeType::Created: case FileChangeType::Changed: { - pipeline::Index(path, {}, mode); - if (mode == IndexMode::Normal) - manager->OnSave(path); - else - manager->OnClose(path); + pipeline::Index(path, {}, mode, true); + if (event.type == FileChangeType::Changed) { + if (mode == IndexMode::Normal) + manager->OnSave(path); + else + manager->OnClose(path); + } break; } case FileChangeType::Deleted: - pipeline::Index(path, {}, mode); + pipeline::Index(path, {}, mode, false); manager->OnClose(path); break; } diff --git a/src/pipeline.cc b/src/pipeline.cc index f298595a..8785619f 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -77,16 +77,16 @@ void StandaloneInitialize(MessageHandler &, const std::string &root); namespace pipeline { std::atomic quit; -std::atomic loaded_ts = ATOMIC_VAR_INIT(0), - pending_index_requests = ATOMIC_VAR_INIT(0); +std::atomic loaded_ts{0}, pending_index_requests{0}, request_id{0}; int64_t tick = 0; namespace { -struct Index_Request { +struct IndexRequest { std::string path; std::vector args; IndexMode mode; + bool must_exist = false; RequestId id; int64_t ts = tick++; }; @@ -99,7 +99,7 @@ MultiQueueWaiter *main_waiter; MultiQueueWaiter *indexer_waiter; MultiQueueWaiter *stdout_waiter; ThreadedQueue *on_request; -ThreadedQueue *index_request; +ThreadedQueue *index_request; ThreadedQueue *on_indexed; ThreadedQueue *for_stdout; @@ -184,7 +184,7 @@ std::mutex &GetFileMutex(const std::string &path) { bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *project, VFS *vfs, const GroupMatch &matcher) { - std::optional opt_request = index_request->TryPopFront(); + std::optional opt_request = index_request->TryPopFront(); if (!opt_request) return false; auto &request = *opt_request; @@ -206,23 +206,33 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, return false; } - Project::Entry entry = project->FindEntry(request.path, true); + // must_exist is currently unused. + Project::Entry entry = project->FindEntry(request.path, false); + if (request.must_exist && entry.filename.empty()) + return true; if (request.args.size()) entry.args = request.args; std::string path_to_index = entry.filename; std::unique_ptr prev; + bool deleted = false; + int reparse = 0; std::optional write_time = LastWriteTime(path_to_index); - if (!write_time) - return true; - int reparse = vfs->Stamp(path_to_index, *write_time, 0); - if (request.path != path_to_index) { - std::optional mtime1 = LastWriteTime(request.path); - if (!mtime1) - return true; - if (vfs->Stamp(request.path, *mtime1, 0)) - reparse = 2; + if (!write_time) { + deleted = true; + } else { + reparse = vfs->Stamp(path_to_index, *write_time, 0); + if (request.path != path_to_index) { + std::optional mtime1 = LastWriteTime(request.path); + if (!mtime1) + deleted = true; + else if (vfs->Stamp(request.path, *mtime1, 0)) + reparse = 2; + } } + if (deleted) + reparse = 2; + if (g_config->index.onChange) { reparse = 2; std::lock_guard lock(vfs->mutex); @@ -306,27 +316,34 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, for (auto &arg : entry.args) (line += ' ') += arg; } - LOG_S(INFO) << "parse " << path_to_index << line; + LOG_S(INFO) << (deleted ? "delete " : "parse ") << path_to_index << line; } - std::vector> remapped; - if (g_config->index.onChange) { - std::string content = wfiles->GetContent(path_to_index); - if (content.size()) - remapped.emplace_back(path_to_index, content); - } - bool ok; - auto indexes = idx::Index(completion, wfiles, vfs, entry.directory, - path_to_index, entry.args, remapped, ok); - - if (!ok) { - if (request.id.Valid()) { - ResponseError err; - err.code = ErrorCode::InternalError; - err.message = "failed to index " + path_to_index; - pipeline::ReplyError(request.id, err); + std::vector> indexes; + if (deleted) { + indexes.push_back(std::make_unique(request.path, "")); + if (request.path != path_to_index) + indexes.push_back(std::make_unique(path_to_index, "")); + } else { + std::vector> remapped; + if (g_config->index.onChange) { + std::string content = wfiles->GetContent(path_to_index); + if (content.size()) + remapped.emplace_back(path_to_index, content); + } + bool ok; + indexes = idx::Index(completion, wfiles, vfs, entry.directory, + path_to_index, entry.args, remapped, ok); + + if (!ok) { + if (request.id.Valid()) { + ResponseError err; + err.code = ErrorCode::InternalError; + err.message = "failed to index " + path_to_index; + pipeline::ReplyError(request.id, err); + } + return true; } - return true; } for (std::unique_ptr &curr : indexes) { @@ -336,8 +353,9 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, continue; } - LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev - << ")"; + if (!deleted) + LOG_IF_S(INFO, loud) << "store index for " << path + << " (delta: " << !!prev << ")"; { std::lock_guard lock(GetFileMutex(path)); if (vfs->Loaded(path)) @@ -351,9 +369,14 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, 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)); + if (deleted) { + (void)sys::fs::remove(cache_path); + (void)sys::fs::remove(AppendSerializationFormat(cache_path)); + } else { + WriteToFile(cache_path, curr->file_contents); + WriteToFile(AppendSerializationFormat(cache_path), + Serialize(g_config->cacheFormat, *curr)); + } } on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()), request.mode != IndexMode::NonInteractive); @@ -404,7 +427,7 @@ void Init() { on_indexed = new ThreadedQueue(main_waiter); indexer_waiter = new MultiQueueWaiter; - index_request = new ThreadedQueue(indexer_waiter); + index_request = new ThreadedQueue(indexer_waiter); stdout_waiter = new MultiQueueWaiter; for_stdout = new ThreadedQueue(stdout_waiter); @@ -637,9 +660,10 @@ void Standalone(const std::string &root) { } void Index(const std::string &path, const std::vector &args, - IndexMode mode, RequestId id) { + IndexMode mode, bool must_exist, RequestId id) { pending_index_requests++; - index_request->PushBack({path, args, mode, id}, mode != IndexMode::NonInteractive); + index_request->PushBack({path, args, mode, must_exist, id}, + mode != IndexMode::NonInteractive); } std::optional LoadIndexedContent(const std::string &path) { @@ -653,7 +677,8 @@ std::optional LoadIndexedContent(const std::string &path) { return ReadContent(GetCachePath(path)); } -void Notify(const char *method, const std::function &fn) { +void NotifyOrRequest(const char *method, bool request, + const std::function &fn) { rapidjson::StringBuffer output; rapidjson::Writer w(output); w.StartObject(); @@ -661,6 +686,10 @@ void Notify(const char *method, const std::function &fn) { w.String("2.0"); w.Key("method"); w.String(method); + if (request) { + w.Key("id"); + w.Int64(request_id.fetch_add(1, std::memory_order_relaxed)); + } w.Key("params"); JsonWriter writer(&w); fn(writer); diff --git a/src/pipeline.hh b/src/pipeline.hh index da6b44bc..c61c6dc2 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -51,13 +51,17 @@ void MainLoop(); void Standalone(const std::string &root); void Index(const std::string &path, const std::vector &args, - IndexMode mode, RequestId id = {}); + IndexMode mode, bool must_exist, RequestId id = {}); std::optional LoadIndexedContent(const std::string& path); -void Notify(const char *method, const std::function &fn); +void NotifyOrRequest(const char *method, bool request, + const std::function &fn); template void Notify(const char *method, T &result) { - Notify(method, [&](JsonWriter &w) { Reflect(w, result); }); + NotifyOrRequest(method, false, [&](JsonWriter &w) { Reflect(w, result); }); +} +template void Request(const char *method, T &result) { + NotifyOrRequest(method, true, [&](JsonWriter &w) { Reflect(w, result); }); } void Reply(RequestId id, const std::function &fn); diff --git a/src/project.cc b/src/project.cc index 050e645c..3df96feb 100644 --- a/src/project.cc +++ b/src/project.cc @@ -424,7 +424,7 @@ void Project::Load(const std::string &root) { } Project::Entry Project::FindEntry(const std::string &path, - bool can_be_inferred) { + bool must_exist) { Project::Folder *best_folder = nullptr; const Entry *best = nullptr; std::lock_guard lock(mtx); @@ -432,11 +432,12 @@ Project::Entry Project::FindEntry(const std::string &path, auto it = folder.path2entry_index.find(path); if (it != folder.path2entry_index.end()) { Project::Entry &entry = folder.entries[it->second]; - if (can_be_inferred || entry.filename == path) + if (!must_exist || entry.filename == path) return entry; } } + bool exists = false; std::string dir; const std::vector *extra = nullptr; Project::Entry ret; @@ -444,11 +445,12 @@ Project::Entry Project::FindEntry(const std::string &path, if (StringRef(path).startswith(root)) for (auto &[dir1, args] : folder.dot_ccls) if (StringRef(path).startswith(dir1)) { - if (AppendToCDB(args)) { - dir = dir1; - extra = &args; + dir = dir1; + extra = &args; + if (AppendToCDB(args)) goto out; - } + exists = true; + ret.root = ret.directory = root; ret.filename = path; if (args.empty()) { @@ -464,6 +466,8 @@ Project::Entry Project::FindEntry(const std::string &path, return ret; } out: + if (must_exist && !exists) + return ret; if (!best) { int best_score = INT_MIN; @@ -526,9 +530,10 @@ void Project::Index(WorkingFiles *wfiles, RequestId id) { if (match.Matches(entry.filename, &reason) && match_i.Matches(entry.filename, &reason)) { bool interactive = wfiles->GetFile(entry.filename) != nullptr; - pipeline::Index( - entry.filename, entry.args, - interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); + pipeline::Index(entry.filename, entry.args, + interactive ? IndexMode::Normal + : IndexMode::NonInteractive, + false, id); } else { LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason << "; skip " << entry.filename; @@ -541,6 +546,6 @@ void Project::Index(WorkingFiles *wfiles, RequestId id) { pipeline::loaded_ts = pipeline::tick; // Dummy request to indicate that project is loaded and // trigger refreshing semantic highlight for all working files. - pipeline::Index("", {}, IndexMode::NonInteractive); + pipeline::Index("", {}, IndexMode::NonInteractive, false); } } // namespace ccls diff --git a/src/query.cc b/src/query.cc index aa305a3e..6a2d1b5b 100644 --- a/src/query.cc +++ b/src/query.cc @@ -83,8 +83,7 @@ bool TryReplaceDef(llvm::SmallVectorImpl &def_list, Q &&def) { IndexUpdate IndexUpdate::CreateDelta(IndexFile *previous, IndexFile *current) { IndexUpdate r; - static IndexFile empty(llvm::sys::fs::UniqueID(0, 0), current->path, - ""); + static IndexFile empty(current->path, ""); if (previous) r.prev_lid2path = std::move(previous->lid2path); else diff --git a/src/sema_manager.cc b/src/sema_manager.cc index 3d969b36..89388ee0 100644 --- a/src/sema_manager.cc +++ b/src/sema_manager.cc @@ -302,7 +302,7 @@ public: std::unique_ptr BuildCompilerInstance( Session &session, std::unique_ptr CI, IntrusiveRefCntPtr FS, DiagnosticConsumer &DC, - const PreambleData *preamble, const std::string &path, + const PreambleData *preamble, const std::string &main, std::unique_ptr &Buf) { if (preamble) { #if LLVM_VERSION_MAJOR >= 7 @@ -311,7 +311,7 @@ std::unique_ptr BuildCompilerInstance( preamble->Preamble.AddImplicitPreamble(*CI, FS, Buf.get()); #endif } else { - CI->getPreprocessorOpts().addRemappedFile(path, Buf.get()); + CI->getPreprocessorOpts().addRemappedFile(main, Buf.get()); } auto Clang = std::make_unique(session.PCH); @@ -328,6 +328,11 @@ std::unique_ptr BuildCompilerInstance( Clang->createFileManager(); Clang->setSourceManager(new SourceManager(Clang->getDiagnostics(), Clang->getFileManager(), true)); + auto &IS = Clang->getFrontendOpts().Inputs; + if (IS.size()) { + assert(IS[0].isFile()); + IS[0] = FrontendInputFile(main, IS[0].getKind(), IS[0].isSystem()); + } return Clang; } @@ -702,7 +707,7 @@ SemaManager::EnsureSession(const std::string &path, bool *created) { std::shared_ptr session = sessions.Get(path); if (!session) { session = std::make_shared( - project_->FindEntry(path, false), wfiles, PCH); + project_->FindEntry(path, true), wfiles, PCH); std::string line; if (LOG_V_ENABLED(1)) { line = "\n "; diff --git a/src/serializer.cc b/src/serializer.cc index c754f83a..0dec4710 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -482,8 +482,7 @@ Deserialize(SerializeFormat format, const std::string &path, if (major != IndexFile::kMajorVersion || minor != IndexFile::kMinorVersion) throw std::invalid_argument("Invalid version"); - file = std::make_unique(sys::fs::UniqueID(0, 0), path, - file_content); + file = std::make_unique(path, file_content); ReflectFile(reader, *file); } catch (std::invalid_argument &e) { LOG_S(INFO) << "failed to deserialize '" << path << "': " << e.what(); @@ -506,8 +505,7 @@ Deserialize(SerializeFormat format, const std::string &path, if (reader.HasParseError()) return nullptr; - file = std::make_unique(sys::fs::UniqueID(0, 0), path, - file_content); + file = std::make_unique(path, file_content); JsonReader json_reader{&reader}; try { ReflectFile(json_reader, *file);