Index: clangd/CodeComplete.h =================================================================== --- clangd/CodeComplete.h +++ clangd/CodeComplete.h @@ -68,6 +68,11 @@ /// If more results are available, we set CompletionList.isIncomplete. size_t Limit = 0; + enum IncludeInsertion { + IWYU, + NeverInsert, + } InsertIncludes = IncludeInsertion::IWYU; + /// A visual indicator to prepend to the completion label to indicate whether /// completion result would trigger an #include insertion or not. struct IncludeInsertionIndicator { Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -190,7 +190,9 @@ // Returns a token identifying the overload set this is part of. // 0 indicates it's not part of any overload set. - size_t overloadSet() const { + size_t overloadSet(const CodeCompleteOptions &Opts) const { + if (!Opts.BundleOverloads) + return 0; llvm::SmallString<256> Scratch; if (IndexResult) { switch (IndexResult->SymInfo.Kind) { @@ -207,7 +209,7 @@ // This could break #include insertion. return llvm::hash_combine( (IndexResult->Scope + IndexResult->Name).toStringRef(Scratch), - headerToInsertIfAllowed().getValueOr("")); + headerToInsertIfAllowed(Opts).getValueOr("")); default: return 0; } @@ -222,12 +224,14 @@ D->printQualifiedName(OS); } return llvm::hash_combine(Scratch, - headerToInsertIfAllowed().getValueOr("")); + headerToInsertIfAllowed(Opts).getValueOr("")); } // The best header to include if include insertion is allowed. - llvm::Optional headerToInsertIfAllowed() const { - if (RankedIncludeHeaders.empty()) + llvm::Optional + headerToInsertIfAllowed(const CodeCompleteOptions &Opts) const { + if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert || + RankedIncludeHeaders.empty()) return None; if (SemaResult && SemaResult->Declaration) { // Avoid inserting new #include if the declaration is found in the current @@ -337,7 +341,7 @@ Includes.calculateIncludePath(*ResolvedDeclaring, *ResolvedInserted), Includes.shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted)); }; - bool ShouldInsert = C.headerToInsertIfAllowed().hasValue(); + bool ShouldInsert = C.headerToInsertIfAllowed(Opts).hasValue(); // Calculate include paths and edits for all possible headers. for (const auto &Inc : C.RankedIncludeHeaders) { if (auto ToInclude = Inserted(Inc)) { @@ -1169,7 +1173,7 @@ bool AllScopes = false; // Include-insertion and proximity scoring rely on the include structure. // This is available after Sema has run. - llvm::Optional Inserter; // Available during runWithSema. + llvm::Optional Inserter; // Optional during runWithSema. llvm::Optional FileProximity; // Initialized once Sema runs. /// Speculative request based on the cached request and the filter text before /// the cursor. @@ -1374,7 +1378,7 @@ if (C.IndexResult) C.RankedIncludeHeaders = getRankedIncludes(*C.IndexResult); C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult); - if (auto OverloadSet = Opts.BundleOverloads ? C.overloadSet() : 0) { + if (auto OverloadSet = C.overloadSet(Opts)) { auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size()); if (Ret.second) Bundles.emplace_back(); Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -6,8 +6,9 @@ // //===----------------------------------------------------------------------===// -#include "Features.inc" #include "ClangdLSPServer.h" +#include "CodeComplete.h" +#include "Features.inc" #include "Path.h" #include "Protocol.h" #include "Trace.h" @@ -154,6 +155,20 @@ "debug-origin", llvm::cl::desc("Show origins of completion items"), llvm::cl::init(CodeCompleteOptions().ShowOrigins), llvm::cl::Hidden); +static llvm::cl::opt HeaderInsertion( + "header-insertion", + llvm::cl::desc("Add #include directives when accepting code completions"), + llvm::cl::init(CodeCompleteOptions().InsertIncludes), + llvm::cl::values( + clEnumValN(CodeCompleteOptions::IWYU, "iwyu", + "Include what you use. " + "Insert the owning header for top-level symbols, unless the " + "header is already directly included or the symbol is " + "forward-declared."), + clEnumValN( + CodeCompleteOptions::NeverInsert, "never", + "Never insert #include directives as part of code completion"))); + static llvm::cl::opt HeaderInsertionDecorators( "header-insertion-decorators", llvm::cl::desc("Prepend a circular dot or space before the completion " @@ -430,6 +445,7 @@ CCOpts.Limit = LimitResults; CCOpts.BundleOverloads = CompletionStyle != Detailed; CCOpts.ShowOrigins = ShowOrigins; + CCOpts.InsertIncludes = HeaderInsertion; if (!HeaderInsertionDecorators) { CCOpts.IncludeIndicator.Insert.clear(); CCOpts.IncludeIndicator.NoInsert.clear(); Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -32,7 +32,6 @@ using ::llvm::Failed; using ::testing::AllOf; using ::testing::Contains; -using ::testing::Each; using ::testing::ElementsAre; using ::testing::Field; using ::testing::HasSubstr; @@ -556,6 +555,16 @@ {Sym}); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\"")))); + // Can be disabled via option. + CodeCompleteOptions NoInsertion; + NoInsertion.InsertIncludes = CodeCompleteOptions::NeverInsert; + Results = completions(Server, + R"cpp( + int main() { ns::^ } + )cpp", + {Sym}, NoInsertion); + EXPECT_THAT(Results.Completions, + ElementsAre(AllOf(Named("X"), Not(InsertInclude())))); // Duplicate based on inclusions in preamble. Results = completions(Server, R"cpp(