LutsenkoDanil created this revision. LutsenkoDanil added reviewers: sammccall, ilya-biryukov. Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, MaskRay, ioeric.
Patch changes behavior of preamble building. If headers was changed in editor, preamble will be built with draft versions of them. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D54077 Files: clangd/ClangdLSPServer.cpp clangd/ClangdLSPServer.h clangd/DraftStore.cpp clangd/DraftStore.h clangd/FS.h clangd/FSProvider.h unittests/clangd/DraftStoreTests.cpp
Index: unittests/clangd/DraftStoreTests.cpp =================================================================== --- unittests/clangd/DraftStoreTests.cpp +++ unittests/clangd/DraftStoreTests.cpp @@ -53,7 +53,7 @@ Expected<std::string> Result = DS.updateDraft(Path, {Event}); ASSERT_TRUE(!!Result); EXPECT_EQ(*Result, SrcAfter.code()); - EXPECT_EQ(*DS.getDraft(Path), SrcAfter.code()); + EXPECT_EQ(DS.getDraft(Path)->Content, SrcAfter.code()); } } @@ -83,7 +83,7 @@ ASSERT_TRUE(!!Result) << toString(Result.takeError()); EXPECT_EQ(*Result, FinalSrc.code()); - EXPECT_EQ(*DS.getDraft(Path), FinalSrc.code()); + EXPECT_EQ(DS.getDraft(Path)->Content, FinalSrc.code()); } TEST(DraftStoreIncrementalUpdateTest, Simple) { @@ -339,9 +339,9 @@ EXPECT_EQ(toString(Result.takeError()), "UTF-16 offset 100 is invalid for line 0"); - Optional<std::string> Contents = DS.getDraft(File); - EXPECT_TRUE(Contents); - EXPECT_EQ(*Contents, OriginalContents); + Optional<Draft> NewDraft = DS.getDraft(File); + EXPECT_TRUE(NewDraft); + EXPECT_EQ(NewDraft->Content, OriginalContents); } } // namespace Index: clangd/FSProvider.h =================================================================== --- clangd/FSProvider.h +++ clangd/FSProvider.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FSPROVIDER_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FSPROVIDER_H +#include "FS.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/Support/VirtualFileSystem.h" @@ -38,6 +39,20 @@ } }; +class DraftFileSystemProvider : public FileSystemProvider { + DraftStore &DS; + +public: + DraftFileSystemProvider(DraftStore &DS) : DS(DS) {} + + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> + getFileSystem() const override { + static llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> Result( + new DraftFileSystem(llvm::vfs::getRealFileSystem(), DS)); + return Result; + } +}; + } // namespace clangd } // namespace clang Index: clangd/FS.h =================================================================== --- clangd/FS.h +++ clangd/FS.h @@ -10,13 +10,84 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H +#include "DraftStore.h" +#include "Logger.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/VirtualFileSystem.h" namespace clang { namespace clangd { +class DraftFile : public llvm::vfs::File { + std::unique_ptr<File> F; + Draft DraftInfo; + +public: + DraftFile(std::unique_ptr<File> F, Draft D) + : F(std::move(F)), DraftInfo(std::move(D)) {} + + llvm::ErrorOr<std::string> getName() override { return F->getName(); } + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> + getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, + bool IsVolatile) override { + (void)IsVolatile; + (void)RequiresNullTerminator; + + if (FileSize < 0) { + FileSize = DraftInfo.Content.size(); + } + + return llvm::MemoryBuffer::getMemBufferCopy( + llvm::StringRef(DraftInfo.Content).substr(0, FileSize), Name); + } + + llvm::ErrorOr<llvm::vfs::Status> status() override { + auto RealStatus = F->status(); + + if (!RealStatus) { + return RealStatus; + } + + llvm::vfs::Status Result( + RealStatus->getName(), RealStatus->getUniqueID(), DraftInfo.ModifyTime, + RealStatus->getUser(), RealStatus->getGroup(), DraftInfo.Content.size(), + RealStatus->getType(), RealStatus->getPermissions()); + + Result.IsVFSMapped = RealStatus->IsVFSMapped; + return Result; + } + + std::error_code close() override { return F->close(); } +}; + +/// Wrapper around real FS. If file was changed in the DraftStrore, returns +/// changed content, instead of real +class DraftFileSystem : public llvm::vfs::ProxyFileSystem { + DraftStore &DS; + +public: + DraftFileSystem(IntrusiveRefCntPtr<FileSystem> FS, DraftStore &DS) + : llvm::vfs::ProxyFileSystem(FS), DS(DS) {} + + llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> + openFileForRead(const Twine &Path) override { + auto Result = getUnderlyingFS().openFileForRead(Path); + + if (!Result) { + return Result; + } + + if (auto D = DS.getDraft(Path.str())) { + return std::unique_ptr<llvm::vfs::File>( + new DraftFile(std::move(*Result), std::move(*D))); + } + + return Result; + } +}; + /// Records status information for files open()ed or stat()ed during preamble /// build (except for the main file), so we can avoid stat()s on the underlying /// FS when reusing the preamble. For example, code completion can re-stat files Index: clangd/DraftStore.h =================================================================== --- clangd/DraftStore.h +++ clangd/DraftStore.h @@ -14,21 +14,27 @@ #include "Protocol.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Support/Chrono.h" #include <mutex> #include <string> #include <vector> namespace clang { namespace clangd { +struct Draft { + std::string Content; + llvm::sys::TimePoint<> ModifyTime; +}; + /// A thread-safe container for files opened in a workspace, addressed by /// filenames. The contents are owned by the DraftStore. This class supports /// both whole and incremental updates of the documents. class DraftStore { public: /// \return Contents of the stored document. /// For untracked files, a llvm::None is returned. - llvm::Optional<std::string> getDraft(PathRef File) const; + llvm::Optional<Draft> getDraft(PathRef File) const; /// \return List of names of the drafts in this store. std::vector<Path> getActiveFiles() const; @@ -51,7 +57,7 @@ private: mutable std::mutex Mutex; - llvm::StringMap<std::string> Drafts; + llvm::StringMap<Draft> Drafts; }; } // namespace clangd Index: clangd/DraftStore.cpp =================================================================== --- clangd/DraftStore.cpp +++ clangd/DraftStore.cpp @@ -15,7 +15,7 @@ namespace clang { namespace clangd { -Optional<std::string> DraftStore::getDraft(PathRef File) const { +Optional<Draft> DraftStore::getDraft(PathRef File) const { std::lock_guard<std::mutex> Lock(Mutex); auto It = Drafts.find(File); @@ -38,7 +38,7 @@ void DraftStore::addDraft(PathRef File, StringRef Contents) { std::lock_guard<std::mutex> Lock(Mutex); - Drafts[File] = Contents; + Drafts[File] = { Contents, llvm::sys::TimePoint<>::clock::now() }; } Expected<std::string> @@ -53,7 +53,7 @@ llvm::errc::invalid_argument); } - std::string Contents = EntryIt->second; + std::string Contents = EntryIt->second.Content; for (const TextDocumentContentChangeEvent &Change : Changes) { if (!Change.range) { @@ -105,7 +105,7 @@ Contents = std::move(NewContents); } - EntryIt->second = Contents; + Drafts[File] = { Contents, llvm::sys::TimePoint<>::clock::now() }; return Contents; } Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -117,7 +117,7 @@ void call(StringRef Method, llvm::json::Value Params); void notify(StringRef Method, llvm::json::Value Params); - RealFileSystemProvider FSProvider; + DraftFileSystemProvider FSProvider; /// Options used for code completion clangd::CodeCompleteOptions CCOpts; /// Options used for diagnostics. Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -454,15 +454,15 @@ void ClangdLSPServer::onRename(const RenameParams &Params, Callback<WorkspaceEdit> Reply) { Path File = Params.textDocument.uri.file(); - Optional<std::string> Code = DraftMgr.getDraft(File); - if (!Code) + auto Draft = DraftMgr.getDraft(File); + if (!Draft) return Reply(make_error<LSPError>("onRename called for non-added file", ErrorCode::InvalidParams)); Server->rename( File, Params.position, Params.newName, Bind( - [File, Code, + [File, Draft, Params](decltype(Reply) Reply, Expected<std::vector<tooling::Replacement>> Replacements) { if (!Replacements) @@ -472,7 +472,7 @@ // Server Protocol. Fuse them into one big JSON array. std::vector<TextEdit> Edits; for (const auto &R : *Replacements) - Edits.push_back(replacementToEdit(*Code, R)); + Edits.push_back(replacementToEdit(Draft->Content, R)); WorkspaceEdit WE; WE.changes = {{Params.textDocument.uri.uri(), Edits}}; Reply(WE); @@ -491,49 +491,49 @@ const DocumentOnTypeFormattingParams &Params, Callback<std::vector<TextEdit>> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) + auto Draft = DraftMgr.getDraft(File); + if (!Draft) return Reply(make_error<LSPError>( "onDocumentOnTypeFormatting called for non-added file", ErrorCode::InvalidParams)); - auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position); + auto ReplacementsOrError = Server->formatOnType(Draft->Content, File, Params.position); if (ReplacementsOrError) - Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); + Reply(replacementsToEdits(Draft->Content, ReplacementsOrError.get())); else Reply(ReplacementsOrError.takeError()); } void ClangdLSPServer::onDocumentRangeFormatting( const DocumentRangeFormattingParams &Params, Callback<std::vector<TextEdit>> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) + auto Draft = DraftMgr.getDraft(File); + if (!Draft) return Reply(make_error<LSPError>( "onDocumentRangeFormatting called for non-added file", ErrorCode::InvalidParams)); - auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range); + auto ReplacementsOrError = Server->formatRange(Draft->Content, File, Params.range); if (ReplacementsOrError) - Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); + Reply(replacementsToEdits(Draft->Content, ReplacementsOrError.get())); else Reply(ReplacementsOrError.takeError()); } void ClangdLSPServer::onDocumentFormatting( const DocumentFormattingParams &Params, Callback<std::vector<TextEdit>> Reply) { auto File = Params.textDocument.uri.file(); - auto Code = DraftMgr.getDraft(File); - if (!Code) + auto Draft = DraftMgr.getDraft(File); + if (!Draft) return Reply( make_error<LSPError>("onDocumentFormatting called for non-added file", ErrorCode::InvalidParams)); - auto ReplacementsOrError = Server->formatFile(*Code, File); + auto ReplacementsOrError = Server->formatFile(Draft->Content, File); if (ReplacementsOrError) - Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); + Reply(replacementsToEdits(Draft->Content, ReplacementsOrError.get())); else Reply(ReplacementsOrError.takeError()); } @@ -690,7 +690,8 @@ Optional<Path> CompileCommandsDir, bool UseDirBasedCDB, const ClangdServer::Options &Opts) - : Transp(Transp), MsgHandler(new MessageHandler(*this)), CCOpts(CCOpts), + : Transp(Transp), MsgHandler(new MessageHandler(*this)), + FSProvider(DraftMgr), CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()), SupportedCompletionItemKinds(defaultCompletionItemKinds()), UseDirBasedCDB(UseDirBasedCDB), @@ -783,7 +784,7 @@ void ClangdLSPServer::reparseOpenedFiles() { for (const Path &FilePath : DraftMgr.getActiveFiles()) - Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath), + Server->addDocument(FilePath, DraftMgr.getDraft(FilePath)->Content, WantDiagnostics::Auto); }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits