simark updated this revision to Diff 138253. simark added a comment. Rebase
Non-trivial rebase on today's master. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D44408 Files: clangd/ClangdLSPServer.cpp clangd/ClangdLSPServer.h clangd/ClangdServer.cpp clangd/ClangdServer.h clangd/DraftStore.cpp clangd/DraftStore.h unittests/clangd/CMakeLists.txt unittests/clangd/ClangdTests.cpp unittests/clangd/CodeCompleteTests.cpp unittests/clangd/DraftStoreTests.cpp unittests/clangd/SyncAPI.cpp unittests/clangd/SyncAPI.h
Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -22,11 +22,13 @@ void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents); llvm::Expected<CompletionList> -runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, - clangd::CodeCompleteOptions Opts); +runCodeComplete(ClangdServer &Server, PathRef File, std::string Contents, + Position Pos, clangd::CodeCompleteOptions Opts); llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server, - PathRef File, Position Pos); + PathRef File, + std::string Contents, + Position Pos); llvm::Expected<std::vector<Location>> runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos); Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -68,17 +68,19 @@ } // namespace llvm::Expected<CompletionList> -runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, - clangd::CodeCompleteOptions Opts) { +runCodeComplete(ClangdServer &Server, PathRef File, std::string Contents, + Position Pos, clangd::CodeCompleteOptions Opts) { llvm::Optional<llvm::Expected<CompletionList>> Result; - Server.codeComplete(File, Pos, Opts, capture(Result)); + Server.codeComplete(File, std::move(Contents), Pos, Opts, capture(Result)); return std::move(*Result); } llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server, - PathRef File, Position Pos) { + PathRef File, + std::string Contents, + Position Pos) { llvm::Optional<llvm::Expected<SignatureHelp>> Result; - Server.signatureHelp(File, Pos, capture(Result)); + Server.signatureHelp(File, std::move(Contents), Pos, capture(Result)); return std::move(*Result); } Index: unittests/clangd/DraftStoreTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/DraftStoreTests.cpp @@ -0,0 +1,56 @@ +//===-- DraftStoreTests.cpp - Clangd unit tests -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DraftStore.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using namespace llvm; + +using ::testing::UnorderedElementsAre; + +/// Get the active drafts from \p DS as a vector. +static std::vector<PathRef> getActiveDrafts(const DraftStore &DS) { + std::vector<PathRef> ActiveDrafts; + + DS.forEachActiveDraft([&ActiveDrafts](PathRef File, StringRef Contents) { + ActiveDrafts.push_back(File); + }); + + return ActiveDrafts; +} + +TEST(DraftStoreTest, forEachActiveDraft) { + DraftStore DS; + + DS.updateDraft("/foo.cpp", "Foo"); + DS.updateDraft("/bar.cpp", "Bar"); + DS.updateDraft("/baz.cpp", "Baz"); + + std::vector<PathRef> Drafts = getActiveDrafts(DS); + EXPECT_THAT(Drafts, UnorderedElementsAre("/foo.cpp", "/bar.cpp", "/baz.cpp")); + + DS.removeDraft("/bar.cpp"); + + Drafts = getActiveDrafts(DS); + EXPECT_THAT(Drafts, UnorderedElementsAre("/foo.cpp", "/baz.cpp")); + + DS.removeDraft("/another.cpp"); + + Drafts = getActiveDrafts(DS); + EXPECT_THAT(Drafts, UnorderedElementsAre("/foo.cpp", "/baz.cpp")); +} + +} // namespace +} // namespace clangd +} // namespace clang Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -122,7 +122,7 @@ Annotations Test(Text); runAddDocument(Server, File, Test.code()); auto CompletionList = - cantFail(runCodeComplete(Server, File, Test.point(), Opts)); + cantFail(runCodeComplete(Server, File, Test.code(), Test.point(), Opts)); // Sanity-check that filterText is valid. EXPECT_THAT(CompletionList.items, Each(NameContainsFilter())); return CompletionList; @@ -537,16 +537,17 @@ auto I = memIndex({var("ns::index")}); Opts.Index = I.get(); - auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts)); + auto WithIndex = + cantFail(runCodeComplete(Server, File, Test.code(), Test.point(), Opts)); EXPECT_THAT(WithIndex.items, UnorderedElementsAre(Named("local"), Named("index"))); - auto ClassFromPreamble = - cantFail(runCodeComplete(Server, File, Test.point("2"), Opts)); + auto ClassFromPreamble = cantFail( + runCodeComplete(Server, File, Test.code(), Test.point("2"), Opts)); EXPECT_THAT(ClassFromPreamble.items, Contains(Named("member"))); Opts.Index = nullptr; auto WithoutIndex = - cantFail(runCodeComplete(Server, File, Test.point(), Opts)); + cantFail(runCodeComplete(Server, File, Test.code(), Test.point(), Opts)); EXPECT_THAT(WithoutIndex.items, UnorderedElementsAre(Named("local"), Named("preamble"))); } @@ -577,7 +578,8 @@ )cpp"); runAddDocument(Server, File, Test.code()); - auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + auto Results = + cantFail(runCodeComplete(Server, File, Test.code(), Test.point(), {})); // "XYZ" and "foo" are not included in the file being completed but are still // visible through the index. EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); @@ -616,7 +618,7 @@ auto File = testPath("foo.cpp"); Annotations Test(Text); runAddDocument(Server, File, Test.code()); - return cantFail(runSignatureHelp(Server, File, Test.point())); + return cantFail(runSignatureHelp(Server, File, Test.code(), Test.point())); } MATCHER_P(ParamsAre, P, "") { Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -245,13 +245,13 @@ EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); FS.Files[FooH] = ""; - Server.forceReparse(FooCpp); + Server.forceReparse(FooCpp, SourceContents); auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp); ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); FS.Files[FooH] = "int a;"; - Server.forceReparse(FooCpp); + Server.forceReparse(FooCpp, SourceContents); auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp); ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); @@ -375,7 +375,7 @@ runAddDocument(Server, FooCpp, SourceContents2); EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); // But forceReparse should reparse the file with proper flags. - Server.forceReparse(FooCpp); + Server.forceReparse(FooCpp, SourceContents2); ASSERT_TRUE(Server.blockUntilIdleForTest()); EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); // Subsequent addDocument calls should finish without errors too. @@ -413,7 +413,7 @@ runAddDocument(Server, FooCpp, SourceContents); EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); // But forceReparse should reparse the file with proper flags. - Server.forceReparse(FooCpp); + Server.forceReparse(FooCpp, SourceContents); ASSERT_TRUE(Server.blockUntilIdleForTest()); EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); // Subsequent addDocument call should finish without errors too. @@ -475,7 +475,8 @@ CDB.ExtraClangFlags.clear(); DiagConsumer.clear(); Server.removeDocument(BazCpp); - Server.reparseOpenedFiles(); + Server.forceReparse(FooCpp, FooSource.code()); + Server.forceReparse(BarCpp, BarSource.code()); ASSERT_TRUE(Server.blockUntilIdleForTest()); EXPECT_THAT(DiagConsumer.filesWithDiags(), @@ -530,25 +531,28 @@ ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); auto FooCpp = testPath("foo.cpp"); + StringRef SourceContents = "int main() {}"; // clang cannot create CompilerInvocation if we pass two files in the // CompileCommand. We pass the file in ExtraFlags once and CDB adds another // one in getCompileCommand(). CDB.ExtraClangFlags.push_back(FooCpp); // Clang can't parse command args in that case, but we shouldn't crash. - runAddDocument(Server, FooCpp, "int main() {}"); + runAddDocument(Server, FooCpp, SourceContents); EXPECT_EQ(runDumpAST(Server, FooCpp), "<no-ast>"); EXPECT_ERROR(runFindDefinitions(Server, FooCpp, Position())); EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position())); EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name")); // FIXME: codeComplete and signatureHelp should also return errors when they // can't parse the file. - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(), - clangd::CodeCompleteOptions())) - .items, - IsEmpty()); - auto SigHelp = runSignatureHelp(Server, FooCpp, Position()); + EXPECT_THAT( + cantFail(runCodeComplete(Server, FooCpp, SourceContents.str(), Position(), + clangd::CodeCompleteOptions())) + .items, + IsEmpty()); + auto SigHelp = + runSignatureHelp(Server, FooCpp, SourceContents.str(), Position()); ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error"; EXPECT_THAT(SigHelp->signatures, IsEmpty()); } @@ -564,19 +568,23 @@ // BlockingRequestInterval-request will be a blocking one. const unsigned BlockingRequestInterval = 40; - const auto SourceContentsWithoutErrors = R"cpp( -int a; -int b; -int c; -int d; -)cpp"; + auto GetSourceContents = [](bool WithErrors) { + const auto SourceContentsWithoutErrors = R"cpp( + int a; + int b; + int c; + int d; + )cpp"; - const auto SourceContentsWithErrors = R"cpp( -int a = x; -int b; -int c; -int d; -)cpp"; + const auto SourceContentsWithErrors = R"cpp( + int a = x; + int b; + int c; + int d; + )cpp"; + + return WithErrors ? SourceContentsWithErrors : SourceContentsWithoutErrors; + }; // Giving invalid line and column number should not crash ClangdServer, but // just to make sure we're sometimes hitting the bounds inside the file we @@ -687,8 +695,7 @@ auto AddDocument = [&](unsigned FileIndex) { bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen); Server.addDocument(FilePaths[FileIndex], - ShouldHaveErrors ? SourceContentsWithErrors - : SourceContentsWithoutErrors); + GetSourceContents(ShouldHaveErrors)); UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors); }; @@ -700,11 +707,14 @@ auto ForceReparseRequest = [&]() { unsigned FileIndex = FileIndexDist(RandGen); + const RequestStats &Stats = ReqStats[FileIndex]; + // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) + if (Stats.FileIsRemoved) AddDocument(FileIndex); - Server.forceReparse(FilePaths[FileIndex]); + Server.forceReparse(FilePaths[FileIndex], + GetSourceContents(Stats.LastContentsHadErrors)); UpdateStatsOnForceReparse(FileIndex); }; @@ -720,8 +730,10 @@ auto CodeCompletionRequest = [&]() { unsigned FileIndex = FileIndexDist(RandGen); + const RequestStats &Stats = ReqStats[FileIndex]; + // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) + if (Stats.FileIsRemoved) AddDocument(FileIndex); Position Pos; @@ -733,8 +745,9 @@ // requests as opposed to AddDocument/RemoveDocument, which are implicitly // cancelled by any subsequent AddDocument/RemoveDocument request to the // same file. - cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos, - clangd::CodeCompleteOptions())); + cantFail(runCodeComplete(Server, FilePaths[FileIndex], + GetSourceContents(Stats.LastContentsHadErrors), + Pos, clangd::CodeCompleteOptions())); }; auto FindDefinitionsRequest = [&]() { Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -15,6 +15,7 @@ CodeCompleteTests.cpp CodeCompletionStringsTests.cpp ContextTests.cpp + DraftStoreTests.cpp FileIndexTests.cpp FuzzyMatchTests.cpp HeadersTests.cpp Index: clangd/DraftStore.h =================================================================== --- clangd/DraftStore.h +++ clangd/DraftStore.h @@ -13,53 +13,36 @@ #include "Path.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringMap.h" -#include <cstdint> #include <mutex> #include <string> -#include <vector> namespace clang { namespace clangd { -/// Using unsigned int type here to avoid undefined behaviour on overflow. -typedef uint64_t DocVersion; - -/// Document draft with a version of this draft. -struct VersionedDraft { - DocVersion Version; - /// If the value of the field is None, draft is now deleted - llvm::Optional<std::string> Draft; -}; - /// A thread-safe container for files opened in a workspace, addressed by /// filenames. The contents are owned by the DraftStore. Versions are mantained /// for the all added documents, including removed ones. The document version is /// incremented on each update and removal of the document. class DraftStore { public: /// \return version and contents of the stored document. /// For untracked files, a (0, None) pair is returned. - VersionedDraft getDraft(PathRef File) const; - - /// \return List of names of active drafts in this store. Drafts that were - /// removed (which still have an entry in the Drafts map) are not returned by - /// this function. - std::vector<Path> getActiveFiles() const; + llvm::Optional<std::string> getDraft(PathRef File) const; - /// \return version of the tracked document. - /// For untracked files, 0 is returned. - DocVersion getVersion(PathRef File) const; + /// Call \p Callback for each active draft, while holding the store lock. + void forEachActiveDraft( + llvm::function_ref<void(PathRef, StringRef)> Callback) const; /// Replace contents of the draft for \p File with \p Contents. /// \return The new version of the draft for \p File. - DocVersion updateDraft(PathRef File, StringRef Contents); + void updateDraft(PathRef File, StringRef Contents); /// Remove the contents of the draft /// \return The new version of the draft for \p File. - DocVersion removeDraft(PathRef File); + void removeDraft(PathRef File); private: mutable std::mutex Mutex; - llvm::StringMap<VersionedDraft> Drafts; + llvm::StringMap<llvm::Optional<std::string>> Drafts; }; } // namespace clangd Index: clangd/DraftStore.cpp =================================================================== --- clangd/DraftStore.cpp +++ clangd/DraftStore.cpp @@ -12,49 +12,33 @@ using namespace clang; using namespace clang::clangd; -VersionedDraft DraftStore::getDraft(PathRef File) const { +llvm::Optional<std::string> DraftStore::getDraft(PathRef File) const { std::lock_guard<std::mutex> Lock(Mutex); auto It = Drafts.find(File); if (It == Drafts.end()) - return {0, llvm::None}; + return llvm::None; + return It->second; } -std::vector<Path> DraftStore::getActiveFiles() const { +void DraftStore::forEachActiveDraft( + llvm::function_ref<void(PathRef, StringRef)> Callback) const { std::lock_guard<std::mutex> Lock(Mutex); - std::vector<Path> ResultVector; for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++) - if (DraftIt->second.Draft) - ResultVector.push_back(DraftIt->getKey()); - - return ResultVector; -} - -DocVersion DraftStore::getVersion(PathRef File) const { - std::lock_guard<std::mutex> Lock(Mutex); - - auto It = Drafts.find(File); - if (It == Drafts.end()) - return 0; - return It->second.Version; + if (DraftIt->second) + Callback(DraftIt->getKey(), *DraftIt->second); } -DocVersion DraftStore::updateDraft(PathRef File, StringRef Contents) { +void DraftStore::updateDraft(PathRef File, StringRef Contents) { std::lock_guard<std::mutex> Lock(Mutex); - auto &Entry = Drafts[File]; - DocVersion NewVersion = ++Entry.Version; - Entry.Draft = Contents; - return NewVersion; + Drafts[File] = Contents; } -DocVersion DraftStore::removeDraft(PathRef File) { +void DraftStore::removeDraft(PathRef File) { std::lock_guard<std::mutex> Lock(Mutex); - auto &Entry = Drafts[File]; - DocVersion NewVersion = ++Entry.Version; - Entry.Draft = llvm::None; - return NewVersion; + Drafts[File] = llvm::None; } Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -117,45 +117,38 @@ /// \p File is already tracked. Also schedules parsing of the AST for it on a /// separate thread. When the parsing is complete, DiagConsumer passed in /// constructor will receive onDiagnosticsReady callback. - void addDocument(PathRef File, StringRef Contents, + void addDocument(PathRef File, std::string Contents, WantDiagnostics WD = WantDiagnostics::Auto); /// Remove \p File from list of tracked files, schedule a request to free /// resources associated with it. void removeDocument(PathRef File); - /// Force \p File to be reparsed using the latest contents. + /// Force \p File to be reparsed using the \p Contents as the contents. /// Will also check if CompileCommand, provided by GlobalCompilationDatabase /// for \p File has changed. If it has, will remove currently stored Preamble /// and AST and rebuild them from scratch. - void forceReparse(PathRef File); - - /// Calls forceReparse() on all currently opened files. - /// As a result, this method may be very expensive. - /// This method is normally called when the compilation database is changed. - void reparseOpenedFiles(); + void forceReparse(PathRef File, std::string Contents); /// Run code completion for \p File at \p Pos. /// Request is processed asynchronously. /// - /// The current draft for \p File will be used. If \p UsedFS is non-null, it - /// will be overwritten by vfs::FileSystem used for completion. + /// \p Contents is the current content of the file. If \p UsedFS is non-null, + /// it will be overwritten by vfs::FileSystem used for completion. /// /// This method should only be called for currently tracked files. However, it /// is safe to call removeDocument for \p File after this method returns, even /// while returned future is not yet ready. /// A version of `codeComplete` that runs \p Callback on the processing thread /// when codeComplete results become available. - void codeComplete(PathRef File, Position Pos, + void codeComplete(PathRef File, std::string Contents, Position Pos, const clangd::CodeCompleteOptions &Opts, Callback<CompletionList> CB); - /// Provide signature help for \p File at \p Pos. If \p OverridenContents is - /// not None, they will used only for signature help, i.e. no diagnostics - /// update will be scheduled and a draft for \p File will not be updated. If - /// If \p UsedFS is non-null, it will be overwritten by vfs::FileSystem used - /// for signature help. This method should only be called for tracked files. - void signatureHelp(PathRef File, Position Pos, Callback<SignatureHelp> CB); + /// Provide signature help for \p File at \p Pos. \p Contents is the current + /// content of the file. This method should only be called for tracked files. + void signatureHelp(PathRef File, std::string Contents, Position Pos, + Callback<SignatureHelp> CB); /// Get definition of symbol at a specified \p Line and \p Column in \p File. void findDefinitions(PathRef File, Position Pos, @@ -206,12 +199,6 @@ StringRef DeclaringHeader, StringRef InsertedHeader); - /// Gets current document contents for \p File. Returns None if \p File is not - /// currently tracked. - /// FIXME(ibiryukov): This function is here to allow offset-to-Position - /// conversions in outside code, maybe there's a way to get rid of it. - llvm::Optional<std::string> getDocument(PathRef File); - /// Only for testing purposes. /// Waits until all requests to worker thread are finished and dumps AST for /// \p File. \p File must be in the list of added documents. @@ -240,14 +227,26 @@ formatCode(llvm::StringRef Code, PathRef File, ArrayRef<tooling::Range> Ranges); - void scheduleReparseAndDiags(PathRef File, VersionedDraft Contents, + typedef uint64_t DocVersion; + + /// Document contents with a version of this content. + struct VersionedContents { + DocVersion Version; + std::string Contents; + }; + + void scheduleReparseAndDiags(PathRef File, VersionedContents Contents, WantDiagnostics WD, IntrusiveRefCntPtr<vfs::FileSystem> FS); CompileArgsCache CompileArgs; DiagnosticsConsumer &DiagConsumer; FileSystemProvider &FSProvider; - DraftStore DraftMgr; + + // For each file, an internal number representing the latest version we know + // about it. + llvm::StringMap<DocVersion> InternalVersion; + // The index used to look up symbols. This could be: // - null (all index functionality is optional) // - the dynamic index owned by ClangdServer (FileIdx) Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -115,51 +115,41 @@ this->RootPath = NewRootPath; } -void ClangdServer::addDocument(PathRef File, StringRef Contents, +void ClangdServer::addDocument(PathRef File, std::string Contents, WantDiagnostics WantDiags) { - DocVersion Version = DraftMgr.updateDraft(File, Contents); - scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()}, + DocVersion Version = ++InternalVersion[File]; + scheduleReparseAndDiags(File, VersionedContents{Version, std::move(Contents)}, WantDiags, FSProvider.getFileSystem()); } void ClangdServer::removeDocument(PathRef File) { - DraftMgr.removeDraft(File); + ++InternalVersion[File]; CompileArgs.invalidate(File); WorkScheduler.remove(File); } -void ClangdServer::forceReparse(PathRef File) { - auto FileContents = DraftMgr.getDraft(File); - assert(FileContents.Draft && - "forceReparse() was called for non-added document"); - +void ClangdServer::forceReparse(PathRef File, std::string Contents) { // forceReparse promises to request new compilation flags from CDB, so we - // remove any cahced flags. + // remove any cached flags. CompileArgs.invalidate(File); - scheduleReparseAndDiags(File, std::move(FileContents), WantDiagnostics::Yes, - FSProvider.getFileSystem()); + addDocument(File, std::move(Contents), WantDiagnostics::Yes); } -void ClangdServer::codeComplete(PathRef File, Position Pos, +void ClangdServer::codeComplete(PathRef File, std::string Contents, + Position Pos, const clangd::CodeCompleteOptions &Opts, Callback<CompletionList> CB) { // Copy completion options for passing them to async task handler. auto CodeCompleteOpts = Opts; if (!CodeCompleteOpts.Index) // Respect overridden index. CodeCompleteOpts.Index = Index; - VersionedDraft Latest = DraftMgr.getDraft(File); - if (!Latest.Draft) - return CB(llvm::make_error<llvm::StringError>( - "codeComplete called for non-added document", - llvm::errc::invalid_argument)); - // Copy PCHs to avoid accessing this->PCHs concurrently std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs; auto FS = FSProvider.getFileSystem(); auto Task = [PCHs, Pos, FS, CodeCompleteOpts]( - std::string Contents, Path File, Callback<CompletionList> CB, + Path File, std::string Contents, Callback<CompletionList> CB, llvm::Expected<InputsAndPreamble> IP) { assert(IP && "error when trying to read preamble for codeComplete"); auto PreambleData = IP->Preamble; @@ -175,20 +165,15 @@ WorkScheduler.runWithPreamble( "CodeComplete", File, - Bind(Task, std::move(*Latest.Draft), File.str(), std::move(CB))); + Bind(Task, File.str(), std::move(Contents), std::move(CB))); } -void ClangdServer::signatureHelp(PathRef File, Position Pos, - Callback<SignatureHelp> CB) { - VersionedDraft Latest = DraftMgr.getDraft(File); - if (!Latest.Draft) - return CB(llvm::make_error<llvm::StringError>( - "signatureHelp is called for non-added document", - llvm::errc::invalid_argument)); +void ClangdServer::signatureHelp(PathRef File, std::string Contents, + Position Pos, Callback<SignatureHelp> CB) { auto PCHs = this->PCHs; auto FS = FSProvider.getFileSystem(); - auto Action = [Pos, FS, PCHs](std::string Contents, Path File, + auto Action = [Pos, FS, PCHs](Path File, std::string Contents, Callback<SignatureHelp> CB, llvm::Expected<InputsAndPreamble> IP) { if (!IP) @@ -203,7 +188,7 @@ WorkScheduler.runWithPreamble( "SignatureHelp", File, - Bind(Action, std::move(*Latest.Draft), File.str(), std::move(CB))); + Bind(Action, File.str(), std::move(Contents), std::move(CB))); } llvm::Expected<tooling::Replacements> @@ -342,13 +327,6 @@ return formatReplacements(Code, *Replaces, *Style); } -llvm::Optional<std::string> ClangdServer::getDocument(PathRef File) { - auto Latest = DraftMgr.getDraft(File); - if (!Latest.Draft) - return llvm::None; - return std::move(*Latest.Draft); -} - void ClangdServer::dumpAST(PathRef File, UniqueFunction<void(std::string)> Callback) { auto Action = [](decltype(Callback) Callback, @@ -464,11 +442,6 @@ void ClangdServer::findDocumentHighlights( PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) { - auto FileContents = DraftMgr.getDraft(File); - if (!FileContents.Draft) - return CB(llvm::make_error<llvm::StringError>( - "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument)); auto FS = FSProvider.getFileSystem(); auto Action = [FS, Pos](Callback<std::vector<DocumentHighlight>> CB, @@ -482,12 +455,6 @@ } void ClangdServer::findHover(PathRef File, Position Pos, Callback<Hover> CB) { - Hover FinalHover; - auto FileContents = DraftMgr.getDraft(File); - if (!FileContents.Draft) - return CB(llvm::make_error<llvm::StringError>( - "findHover called on non-added file", llvm::errc::invalid_argument)); - auto FS = FSProvider.getFileSystem(); auto Action = [Pos, FS](Callback<Hover> CB, llvm::Expected<InputsAndAST> InpAST) { @@ -500,7 +467,7 @@ } void ClangdServer::scheduleReparseAndDiags( - PathRef File, VersionedDraft Contents, WantDiagnostics WantDiags, + PathRef File, VersionedContents Contents, WantDiagnostics WantDiags, IntrusiveRefCntPtr<vfs::FileSystem> FS) { tooling::CompileCommand Command = CompileArgs.getCompileCommand(File); @@ -525,15 +492,10 @@ WorkScheduler.update(File, ParseInputs{std::move(Command), std::move(FS), - std::move(*Contents.Draft)}, + std::move(Contents.Contents)}, WantDiags, std::move(Callback)); } -void ClangdServer::reparseOpenedFiles() { - for (const Path &FilePath : DraftMgr.getActiveFiles()) - forceReparse(FilePath); -} - void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { // FIXME: Do nothing for now. This will be used for indexing and potentially // invalidating other caches. Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -74,6 +74,17 @@ std::vector<Fix> getFixes(StringRef File, const clangd::Diagnostic &D); + /// Forces a reparse on all currently opened files. As a result, this method + /// may be very expensive. This method is normally called when the + // compilation database is changed. + void reparseOpenedFiles(); + + /// Gets current document contents for \p File. Returns None if \p File is not + /// currently tracked. + /// FIXME(ibiryukov): This function is here to allow offset-to-Position + /// conversions in outside code, maybe there's a way to get rid of it. + llvm::Optional<std::string> getDocument(PathRef File); + JSONOutput &Out; /// Used to indicate that the 'shutdown' request was received from the /// Language Server client. @@ -100,6 +111,9 @@ // the worker thread that may otherwise run an async callback on partially // destructed instance of ClangdLSPServer. ClangdServer Server; + + // Store of the current versions of the open documents. + DraftStore DraftMgr; }; } // namespace clangd Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -12,6 +12,7 @@ #include "JSONRPCDispatcher.h" #include "SourceCode.h" #include "URI.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" @@ -142,8 +143,12 @@ if (Params.metadata && !Params.metadata->extraFlags.empty()) CDB.setExtraFlagsForFile(Params.textDocument.uri.file(), std::move(Params.metadata->extraFlags)); - Server.addDocument(Params.textDocument.uri.file(), Params.textDocument.text, - WantDiagnostics::Yes); + + PathRef File = Params.textDocument.uri.file(); + std::string &Contents = Params.textDocument.text; + + DraftMgr.updateDraft(File, Contents); + Server.addDocument(File, std::move(Contents), WantDiagnostics::Yes); } void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) { @@ -154,9 +159,13 @@ if (Params.wantDiagnostics.hasValue()) WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes : WantDiagnostics::No; + + PathRef File = Params.textDocument.uri.file(); + std::string &Contents = Params.contentChanges[0].text; + // We only support full syncing right now. - Server.addDocument(Params.textDocument.uri.file(), - Params.contentChanges[0].text, WantDiags); + DraftMgr.updateDraft(File, Contents); + Server.addDocument(File, std::move(Contents), WantDiags); } void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) { @@ -188,7 +197,7 @@ } else if (Params.command == ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { auto &FileURI = Params.includeInsertion->textDocument.uri; - auto Code = Server.getDocument(FileURI.file()); + auto Code = getDocument(FileURI.file()); if (!Code) return replyError(ErrorCode::InvalidParams, ("command " + @@ -233,7 +242,7 @@ void ClangdLSPServer::onRename(RenameParams &Params) { Path File = Params.textDocument.uri.file(); - llvm::Optional<std::string> Code = Server.getDocument(File); + llvm::Optional<std::string> Code = getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, "onRename called for non-added file"); @@ -254,13 +263,15 @@ } void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) { - Server.removeDocument(Params.textDocument.uri.file()); + PathRef File = Params.textDocument.uri.file(); + DraftMgr.removeDraft(File); + Server.removeDocument(File); } void ClangdLSPServer::onDocumentOnTypeFormatting( DocumentOnTypeFormattingParams &Params) { auto File = Params.textDocument.uri.file(); - auto Code = Server.getDocument(File); + auto Code = getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, "onDocumentOnTypeFormatting called for non-added file"); @@ -276,7 +287,7 @@ void ClangdLSPServer::onDocumentRangeFormatting( DocumentRangeFormattingParams &Params) { auto File = Params.textDocument.uri.file(); - auto Code = Server.getDocument(File); + auto Code = getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, "onDocumentRangeFormatting called for non-added file"); @@ -291,7 +302,7 @@ void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) { auto File = Params.textDocument.uri.file(); - auto Code = Server.getDocument(File); + auto Code = getDocument(File); if (!Code) return replyError(ErrorCode::InvalidParams, "onDocumentFormatting called for non-added file"); @@ -307,7 +318,7 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. - auto Code = Server.getDocument(Params.textDocument.uri.file()); + auto Code = getDocument(Params.textDocument.uri.file()); if (!Code) return replyError(ErrorCode::InvalidParams, "onCodeAction called for non-added file"); @@ -329,7 +340,13 @@ } void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { - Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, + PathRef File = Params.textDocument.uri.file(); + + llvm::Optional<std::string> Contents = DraftMgr.getDraft(File); + // FIXME(sammccall): return error for consistency? + assert(Contents && "codeComplete called for non-added document"); + + Server.codeComplete(File, std::move(*Contents), Params.position, CCOpts, [](llvm::Expected<CompletionList> List) { if (!List) return replyError(ErrorCode::InvalidParams, @@ -339,14 +356,22 @@ } void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) { - Server.signatureHelp(Params.textDocument.uri.file(), Params.position, - [](llvm::Expected<SignatureHelp> SignatureHelp) { - if (!SignatureHelp) - return replyError( - ErrorCode::InvalidParams, - llvm::toString(SignatureHelp.takeError())); - reply(*SignatureHelp); - }); + PathRef File = Params.textDocument.uri.file(); + + auto Callback = [](llvm::Expected<SignatureHelp> SignatureHelp) { + if (!SignatureHelp) + return replyError(ErrorCode::InvalidParams, + llvm::toString(SignatureHelp.takeError())); + reply(*SignatureHelp); + }; + + llvm::Optional<std::string> Contents = DraftMgr.getDraft(File); + if (!Contents) + return Callback(llvm::make_error<llvm::StringError>( + "signatureHelp is called for non-added document", + llvm::errc::invalid_argument)); + + Server.signatureHelp(File, std::move(*Contents), Params.position, Callback); } void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) { @@ -397,7 +422,7 @@ // Compilation database change. if (Settings.compilationDatabasePath.hasValue()) { CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue()); - Server.reparseOpenedFiles(); + reparseOpenedFiles(); } } @@ -479,3 +504,17 @@ }}, }); } + +void ClangdLSPServer::reparseOpenedFiles() { + DraftMgr.forEachActiveDraft([this](StringRef File, StringRef Contents) { + Server.forceReparse(File, Contents); + }); +} + +llvm::Optional<std::string> ClangdLSPServer::getDocument(PathRef File) { + llvm::Optional<std::string> Contents = DraftMgr.getDraft(File); + if (!Contents) + return llvm::None; + + return std::move(*Contents); +}
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits