Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -150,8 +150,15 @@ /// Force \p File to be reparsed using the latest contents. void forceReparse(PathRef File); - /// Run code completion for \p File at \p Pos. - Tagged> codeComplete(PathRef File, Position Pos); + /// Run code completion for \p File at \p Pos. If \p OverridenContents is not + /// None, they will used only for code completion, i.e. no diagnostics update + /// will be scheduled and a draft for \p File will not be updated. + /// If \p OverridenContents is None, contents of the current draft for \p File + /// will be used. + /// This method should only be called for currently tracked files. + Tagged> + codeComplete(PathRef File, Position Pos, + llvm::Optional OverridenContents = llvm::None); /// Run formatting for \p Rng inside \p File. std::vector formatRange(PathRef File, Range Rng); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -184,17 +184,27 @@ addDocument(File, getDocument(File)); } -Tagged> ClangdServer::codeComplete(PathRef File, - Position Pos) { - auto FileContents = DraftMgr.getDraft(File); - assert(FileContents.Draft && "codeComplete is called for non-added document"); +Tagged> +ClangdServer::codeComplete(PathRef File, Position Pos, + llvm::Optional OverridenContents) { + if (!OverridenContents) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "codeComplete is called for non-added document"); + + OverridenContents = *FileContents.Draft; + } std::vector Result; auto TaggedFS = FSProvider->getTaggedFileSystem(); - Units.runOnUnitWithoutReparse( - File, *FileContents.Draft, *CDB, PCHs, TaggedFS.Value, [&](ClangdUnit &Unit) { - Result = Unit.codeComplete(*FileContents.Draft, Pos, TaggedFS.Value); - }); + // It would be nice to use runOnUnitWithoutReparse here, but we can't + // guarantee the correctness of code completion cache here if we don't do the + // reparse. + Units.runOnUnit(File, *OverridenContents, *CDB, PCHs, TaggedFS.Value, + [&](ClangdUnit &Unit) { + Result = Unit.codeComplete(*OverridenContents, Pos, + TaggedFS.Value); + }); return make_tagged(std::move(Result), TaggedFS.Tag); } Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -398,5 +398,69 @@ EXPECT_EQ(Server.codeComplete(FooCpp, Position{0, 0}).Tag, FS->Tag); } +class ClangdCompletionTest : public ClangdVFSTest { +protected: + bool ContainsItem(std::vector const &Items, StringRef Name) { + for (const auto &Item : Items) { + if (Item.insertText == Name) + return true; + } + return false; + } +}; + +TEST_F(ClangdCompletionTest, CheckContentsOverride) { + MockFSProvider *FS; + + ClangdServer Server(llvm::make_unique(), + llvm::make_unique(), + getAndMove(llvm::make_unique(), FS), + /*RunSynchronously=*/false); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + const auto SourceContents = R"cpp( +int aba; +int b = ; +)cpp"; + + const auto OverridenSourceContents = R"cpp( +int cbc; +int b = ; +)cpp"; + // Complete after '=' sign. We need to be careful to keep the SourceContents' + // size the same. + // We complete on the 3rd line (2nd in zero-based numbering), because raw + // string literal of the SourceContents starts with a newline(it's easy to + // miss). + Position CompletePos = {2, 8}; + FS->Files[FooCpp] = SourceContents; + + Server.addDocument(FooCpp, SourceContents); + + { + auto CodeCompletionResults1 = + Server.codeComplete(FooCpp, CompletePos, None).Value; + EXPECT_TRUE(ContainsItem(CodeCompletionResults1, "aba")); + EXPECT_FALSE(ContainsItem(CodeCompletionResults1, "cbc")); + } + + { + auto CodeCompletionResultsOverriden = + Server + .codeComplete(FooCpp, CompletePos, + StringRef(OverridenSourceContents)) + .Value; + EXPECT_TRUE(ContainsItem(CodeCompletionResultsOverriden, "cbc")); + EXPECT_FALSE(ContainsItem(CodeCompletionResultsOverriden, "aba")); + } + + { + auto CodeCompletionResults2 = + Server.codeComplete(FooCpp, CompletePos, None).Value; + EXPECT_TRUE(ContainsItem(CodeCompletionResults2, "aba")); + EXPECT_FALSE(ContainsItem(CodeCompletionResults2, "cbc")); + } +} + } // namespace clangd } // namespace clang