diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -637,8 +637,8 @@ Effect = T.takeError(); } assert(Effect.hasValue() && "Expected at least one selection"); - if (*Effect) { - // Tweaks don't apply clang-format, do that centrally here. + if (*Effect && (*Effect)->FormatEdits) { + // Format tweaks that require it centrally here. for (auto &It : (*Effect)->ApplyEdits) { Edit &E = It.second; format::FormatStyle Style = diff --git a/clang-tools-extra/clangd/refactor/Tweak.h b/clang-tools-extra/clangd/refactor/Tweak.h --- a/clang-tools-extra/clangd/refactor/Tweak.h +++ b/clang-tools-extra/clangd/refactor/Tweak.h @@ -78,6 +78,9 @@ /// A message to be displayed to the user. llvm::Optional ShowMessage; FileEdits ApplyEdits; + /// Whether the edits should be formatted before presenting to the client. + /// Note that it applies to all files. + bool FormatEdits = true; static Effect showMessage(StringRef S) { Effect E; diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -18,12 +18,14 @@ #include "TestTU.h" #include "TidyProvider.h" #include "URI.h" +#include "refactor/Tweak.h" #include "support/MemoryTree.h" #include "support/Path.h" #include "support/Threading.h" #include "clang/Config/config.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" @@ -31,6 +33,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" #include "llvm/Support/VirtualFileSystem.h" @@ -1259,6 +1262,60 @@ ASSERT_TRUE(MT.children().count("tuscheduler")); EXPECT_TRUE(MT.child("tuscheduler").children().count(FooCpp)); } + +TEST(ClangdServer, RespectsTweakFormatting) { + static constexpr const char *TweakID = "ModuleTweak"; + static constexpr const char *NewContents = "{not;\nformatted;}"; + + // Contributes a tweak that generates a non-formatted insertion and disables + // formatting. + struct TweakContributingModule final : public FeatureModule { + struct ModuleTweak final : public Tweak { + const char *id() const override { return TweakID; } + bool prepare(const Selection &Sel) override { return true; } + Expected apply(const Selection &Sel) override { + auto &SM = Sel.AST->getSourceManager(); + llvm::StringRef FilePath = SM.getFilename(Sel.Cursor); + tooling::Replacements Reps; + llvm::cantFail( + Reps.add(tooling::Replacement(FilePath, 0, 0, NewContents))); + auto E = llvm::cantFail(Effect::mainFileEdit(SM, std::move(Reps))); + E.FormatEdits = false; + return E; + } + std::string title() const override { return id(); } + llvm::StringLiteral kind() const override { + return llvm::StringLiteral(""); + }; + }; + + void contributeTweaks(std::vector> &Out) override { + Out.emplace_back(new ModuleTweak); + } + }; + + MockFS FS; + MockCompilationDatabase CDB; + auto Opts = ClangdServer::optsForTest(); + FeatureModuleSet Set; + Set.add(std::make_unique()); + Opts.FeatureModules = &Set; + ClangdServer Server(CDB, FS, Opts); + + auto FooCpp = testPath("foo.cpp"); + Server.addDocument(FooCpp, ""); + ASSERT_TRUE(Server.blockUntilIdleForTest()); + + // Ensure that disabled formatting is respected. + Notification N; + Server.applyTweak(FooCpp, {}, TweakID, [&](llvm::Expected E) { + ASSERT_TRUE(static_cast(E)); + EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()), + NewContents); + N.notify(); + }); + N.wait(); +} } // namespace } // namespace clangd } // namespace clang