Index: clangd/Function.h =================================================================== --- clangd/Function.h +++ clangd/Function.h @@ -16,6 +16,7 @@ #include "llvm/ADT/FunctionExtras.h" #include "llvm/Support/Error.h" +#include #include #include @@ -83,6 +84,68 @@ std::make_tuple(std::forward(F), std::forward(As)...)); } +/// An Event allows events of type T to be broadcast to listeners. +template +class Event { +public: + // A Listener is the callback through which events are delivered. + using Listener = std::function; + + // A subscription defines the scope of when a listener should receive events. + // After destroying the subscription, no more events are received. + class Subscription { + Event *Parent; + unsigned ListenerID; + + Subscription(Event *Parent, unsigned ListenerID) + : Parent(Parent), ListenerID(ListenerID) {} + friend Event; + + public: + Subscription(Subscription &&Other) { *this = std::move(Other); } + Subscription &operator=(Subscription &&Other) { + std::tie(Parent, ListenerID) = std::tie(Other.Parent, Other.ListenerID); + Other.Parent = nullptr; + return *this; + } + // Destroying a subscription may block if an event is being broadcast. + ~Subscription() { + if (!Parent) return; + std::lock_guard(Parent->ListenersMu); + llvm::erase_if(Parent->Listeners, + [&](const std::pair &P) { + return P.second == ListenerID; + }); + } + }; + + // Adds a listener that will observe all future events until the returned + // subscription is destroyed. + // May block if an event is currently being broadcast. + Subscription observe(Listener L) { + std::lock_guard Lock(ListenersMu); + Listeners.push_back({std::move(L), ++ListenerCount}); + return Subscription(this, ListenerCount);; + } + + // Synchronously sends an event to all registered listeners. + void broadcast(const T& V) { + std::lock_guard Lock(ListenersMu); + for (const auto &L : Listeners) + L.first(V); + } + + ~Event() { + std::lock_guard Lock(ListenersMu); + assert(Listeners.empty()); + } + +private: + std::mutex ListenersMu; + std::vector> Listeners; + unsigned ListenerCount = 0; +}; + } // namespace clangd } // namespace clang Index: clangd/GlobalCompilationDatabase.h =================================================================== --- clangd/GlobalCompilationDatabase.h +++ clangd/GlobalCompilationDatabase.h @@ -11,6 +11,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H #include "Path.h" +#include "Function.h" #include "llvm/ADT/StringMap.h" #include #include @@ -41,8 +42,15 @@ /// Clangd should treat the results as unreliable. virtual tooling::CompileCommand getFallbackCommand(PathRef File) const; - /// FIXME(ibiryukov): add facilities to track changes to compilation flags of - /// existing targets. + using CommandChanged = Event>; + /// The callback is notified when files may have new compile commands. + /// The argument is a list of full file paths. + CommandChanged::Subscription watch(CommandChanged::Listener L) { + return OnCommandChanged.observe(std::move(L)); + } + +protected: + mutable CommandChanged OnCommandChanged; }; /// Gets compile args from tooling::CompilationDatabases built for parent @@ -61,7 +69,8 @@ private: tooling::CompilationDatabase *getCDBForFile(PathRef File) const; - tooling::CompilationDatabase *getCDBInDirLocked(PathRef File) const; + std::pair + getCDBInDirLocked(PathRef File) const; mutable std::mutex Mutex; /// Caches compilation databases loaded from directories(keys are Index: clangd/GlobalCompilationDatabase.cpp =================================================================== --- clangd/GlobalCompilationDatabase.cpp +++ clangd/GlobalCompilationDatabase.cpp @@ -49,17 +49,17 @@ return None; } -tooling::CompilationDatabase * +std::pair DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { // FIXME(ibiryukov): Invalidate cached compilation databases on changes auto CachedIt = CompilationDatabases.find(Dir); if (CachedIt != CompilationDatabases.end()) - return CachedIt->second.get(); + return {CachedIt->second.get(), true}; std::string Error = ""; auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); auto Result = CDB.get(); CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB))); - return Result; + return {Result, false}; } tooling::CompilationDatabase * @@ -69,14 +69,20 @@ path::is_absolute(File, path::Style::windows)) && "path must be absolute"); + tooling::CompilationDatabase * CDB = nullptr; + bool Cached; std::lock_guard Lock(Mutex); - if (CompileCommandsDir) - return getCDBInDirLocked(*CompileCommandsDir); - for (auto Path = path::parent_path(File); !Path.empty(); - Path = path::parent_path(Path)) - if (auto CDB = getCDBInDirLocked(Path)) - return CDB; - return nullptr; + if (CompileCommandsDir) { + std::tie(CDB, Cached) = getCDBInDirLocked(*CompileCommandsDir); + } else { + for (auto Path = path::parent_path(File); !CDB && !Path.empty(); + Path = path::parent_path(Path)) { + std::tie(CDB, Cached) = getCDBInDirLocked(Path); + } + } + if (CDB && !Cached) + OnCommandChanged.broadcast(CDB->getAllFiles()); + return CDB; } Optional @@ -101,11 +107,14 @@ void OverlayCDB::setCompileCommand( PathRef File, llvm::Optional Cmd) { - std::unique_lock Lock(Mutex); - if (Cmd) - Commands[File] = std::move(*Cmd); - else - Commands.erase(File); + { + std::unique_lock Lock(Mutex); + if (Cmd) + Commands[File] = std::move(*Cmd); + else + Commands.erase(File); + } + OnCommandChanged.broadcast({File}); } } // namespace clangd Index: unittests/clangd/GlobalCompilationDatabaseTests.cpp =================================================================== --- unittests/clangd/GlobalCompilationDatabaseTests.cpp +++ unittests/clangd/GlobalCompilationDatabaseTests.cpp @@ -88,6 +88,16 @@ ElementsAre("clang", testPath("foo.cc"), "-DA=6")); } +TEST_F(OverlayCDBTest, Watch) { + OverlayCDB Inner(nullptr); + OverlayCDB Outer(&Inner); + + std::vector> Changes; + Outer.watch([&](const std::vector &ChangedFiles) { + Changes.push_back(ChangedFiles); + }); +} + } // namespace } // namespace clangd } // namespace clang