Index: clang-tools-extra/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -24,6 +24,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Regex.h" #include "llvm/Support/ScopedPrinter.h" namespace clang { @@ -395,9 +396,9 @@ {"completionProvider", llvm::json::Object{ {"resolveProvider", false}, - // We do extra checks for '>' and ':' in completion to only - // trigger on '->' and '::'. - {"triggerCharacters", {".", ">", ":"}}, + // We do extra checks for '>', ':' and '/' in completion to + // only trigger on '->', '::' and '#include .../'. + {"triggerCharacters", {".", ">", ":", "/"}}, }}, {"signatureHelpProvider", llvm::json::Object{ @@ -1056,7 +1057,7 @@ const CompletionParams &Params) const { llvm::StringRef Trigger = Params.context.triggerCharacter; if (Params.context.triggerKind != CompletionTriggerKind::TriggerCharacter || - (Trigger != ">" && Trigger != ":")) + (Trigger != ">" && Trigger != ":" && Trigger != "/")) return true; auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); @@ -1083,6 +1084,16 @@ return (*Code)[*Offset - 2] == '-'; // trigger only on '->'. if (Trigger == ":") return (*Code)[*Offset - 2] == ':'; // trigger only on '::'. + + if (Trigger == "/") { // trigger only on '#include.../' + auto StartOfLine = Code->rfind('\n', *Offset); + if (StartOfLine == llvm::StringRef::npos) { + StartOfLine = 0; + llvm::Regex IncPattern("^#[[:space:]]*include.*"); + return IncPattern.match( + llvm::StringRef(Code->data() + StartOfLine, *Offset - StartOfLine)); + } + } assert(false && "unhandled trigger character"); return true; } Index: clang-tools-extra/clangd/CodeComplete.h =================================================================== --- clang-tools-extra/clangd/CodeComplete.h +++ clang-tools-extra/clangd/CodeComplete.h @@ -115,6 +115,10 @@ /// Such completions can insert scope qualifiers. bool AllScopes = false; + /// Whether to include the '/', '"' and '>' endings when completing included + /// files. + bool IncludedFileEndings = true; + /// Whether to use the clang parser, or fallback to text-based completion /// (using identifiers in the current file and symbol indexes). enum CodeCompletionParse { Index: clang-tools-extra/clangd/CodeComplete.cpp =================================================================== --- clang-tools-extra/clangd/CodeComplete.cpp +++ clang-tools-extra/clangd/CodeComplete.cpp @@ -1694,6 +1694,7 @@ // Tell Sema not to deserialize the preamble to look for results. Result.LoadExternal = !Index; Result.IncludeFixIts = IncludeFixIts; + Result.IncludedFileEndings = IncludedFileEndings; return Result; } Index: clang-tools-extra/clangd/test/initialize-params.test =================================================================== --- clang-tools-extra/clangd/test/initialize-params.test +++ clang-tools-extra/clangd/test/initialize-params.test @@ -11,7 +11,8 @@ # CHECK-NEXT: "triggerCharacters": [ # CHECK-NEXT: ".", # CHECK-NEXT: ">", -# CHECK-NEXT: ":" +# CHECK-NEXT: ":", +# CHECK-NEXT: "/" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "declarationProvider": true, Index: clang-tools-extra/clangd/tool/ClangdMain.cpp =================================================================== --- clang-tools-extra/clangd/tool/ClangdMain.cpp +++ clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -285,6 +285,12 @@ "Specify a list of Tweaks to enable (only for clangd developers)."), llvm::cl::Hidden, llvm::cl::CommaSeparated); +static llvm::cl::opt CompleteIncludedFilesWithEndings( + "complete-included-files-with-endings", + llvm::cl::desc("Complete included files with endings, which are '/' for " + "folders, and '\"' (or '>') for files (default=true)"), + llvm::cl::init(true)); + namespace { /// \brief Supports a test URI scheme with relaxed constraints for lit tests. @@ -498,6 +504,7 @@ CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets; CCOpts.AllScopes = AllScopesCompletion; CCOpts.RunParser = CodeCompletionParse; + CCOpts.IncludedFileEndings = CompleteIncludedFilesWithEndings; RealFileSystemProvider FSProvider; // Initialize and run ClangdLSPServer. Index: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2285,6 +2285,26 @@ Has("bar.h\"", CompletionItemKind::File))); } +TEST(CompletionTest, IncludedCompletionWithoutEndings) { + MockFSProvider FS; + MockCompilationDatabase CDB; + std::string Subdir = testPath("sub"); + std::string SearchDirArg = (Twine("-I") + Subdir).str(); + CDB.ExtraClangFlags = {SearchDirArg.c_str()}; + std::string BarHeader = testPath("sub/bar.h"); + FS.Files[BarHeader] = ""; + IgnoreDiagnostics DiagConsumer; + ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); + clangd::CodeCompleteOptions CCOpts; + CCOpts.IncludedFileEndings = false; + auto Results = completions(Server, + R"cpp( + #include "^" + )cpp", + {}, CCOpts); + EXPECT_THAT(Results.Completions, AllOf(Has("sub"), Has("bar.h"))); +} + TEST(CompletionTest, NoCrashAtNonAlphaIncludeHeader) { auto Results = completions( R"cpp( Index: clang/include/clang/Driver/CC1Options.td =================================================================== --- clang/include/clang/Driver/CC1Options.td +++ clang/include/clang/Driver/CC1Options.td @@ -491,6 +491,8 @@ HelpText<"Include brief documentation comments in code-completion results.">; def code_completion_with_fixits : Flag<["-"], "code-completion-with-fixits">, HelpText<"Include code completion results which require small fix-its.">; +def no_code_completion_included_file_endings : Flag<["-"], "no-code-completion-included-file-endings">, + HelpText<"Do not include '/', '\"' and '>' endings when completing included files.">; def disable_free : Flag<["-"], "disable-free">, HelpText<"Disable freeing of memory on exit">; def discard_value_names : Flag<["-"], "discard-value-names">, Index: clang/include/clang/Sema/CodeCompleteConsumer.h =================================================================== --- clang/include/clang/Sema/CodeCompleteConsumer.h +++ clang/include/clang/Sema/CodeCompleteConsumer.h @@ -1101,6 +1101,12 @@ return CodeCompleteOpts.LoadExternal; } + /// Whether to include the '/', '"' and '>' endings when completing included + /// files. + bool includedFileEndings() const { + return CodeCompleteOpts.IncludedFileEndings; + } + /// Deregisters and destroys this code-completion consumer. virtual ~CodeCompleteConsumer(); Index: clang/include/clang/Sema/CodeCompleteOptions.h =================================================================== --- clang/include/clang/Sema/CodeCompleteOptions.h +++ clang/include/clang/Sema/CodeCompleteOptions.h @@ -43,10 +43,14 @@ /// on member access, etc. unsigned IncludeFixIts : 1; + /// Whether to include the '/', '"' and '>' endings when completing included + /// files. + unsigned IncludedFileEndings : 1; + CodeCompleteOptions() : IncludeMacros(0), IncludeCodePatterns(0), IncludeGlobals(1), - IncludeNamespaceLevelDecls(1), IncludeBriefComments(0), - LoadExternal(1), IncludeFixIts(0) {} + IncludeNamespaceLevelDecls(1), IncludeBriefComments(0), LoadExternal(1), + IncludeFixIts(0), IncludedFileEndings(1) {} }; } // namespace clang Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -1809,6 +1809,8 @@ = Args.hasArg(OPT_code_completion_brief_comments); Opts.CodeCompleteOpts.IncludeFixIts = Args.hasArg(OPT_code_completion_with_fixits); + Opts.CodeCompleteOpts.IncludedFileEndings + = !Args.hasArg(OPT_no_code_completion_included_file_endings); Opts.OverrideRecordLayoutsFile = Args.getLastArgValue(OPT_foverride_record_layout_EQ); Index: clang/lib/Sema/SemaCodeComplete.cpp =================================================================== --- clang/lib/Sema/SemaCodeComplete.cpp +++ clang/lib/Sema/SemaCodeComplete.cpp @@ -8655,7 +8655,8 @@ auto AddCompletion = [&](StringRef Filename, bool IsDirectory) { SmallString<64> TypedChunk = Filename; // Directory completion is up to the slash, e.g. ' : '"'); + if (CodeCompleter->includedFileEndings()) + TypedChunk.push_back(IsDirectory ? '/' : Angled ? '>' : '"'); auto R = SeenResults.insert(TypedChunk); if (R.second) { // New completion const char *InternedTyped = Results.getAllocator().CopyString(TypedChunk); Index: clang/test/CodeCompletion/included-files-endings.cpp =================================================================== --- /dev/null +++ clang/test/CodeCompletion/included-files-endings.cpp @@ -0,0 +1,24 @@ +// RUN: rm -rf %t && mkdir %t && cp %s %t/main.cc && mkdir %t/foo && mkdir %t/sys +// RUN: touch %t/foobar.h && touch %t/foo/bar.h && touch %t/sys/foosys.h + +// With endings. +#include "foobar.h" +// RUN: %clang -fsyntax-only -isystem %t/sys -Xclang -code-completion-at=%t/main.cc:5:13 %t/main.cc | FileCheck -check-prefix=CHECK-1 %s +// CHECK-1: foo/ +// CHECK-1: foobar.h" + +// System headers with endings. +#include +// RUN: %clang -fsyntax-only -isystem %t/sys -Xclang -code-completion-at=%t/main.cc:11:14 %t/main.cc | FileCheck -check-prefix=CHECK-2 %s +// CHECK-2: sys.h> + +// Without endings. +#include "foobar.h" +// RUN: %clang -fsyntax-only -isystem %t/sys -Xclang -code-completion-at=%t/main.cc:16:13 -Xclang -no-code-completion-included-file-endings %t/main.cc | FileCheck -check-prefix=CHECK-3 %s +// CHECK-3: foo +// CHECK-3: foobar.h + +// System headers without endings. +#include +// RUN: %clang -fsyntax-only -isystem %t/sys -Xclang -code-completion-at=%t/main.cc:22:14 -Xclang -no-code-completion-included-file-endings %t/main.cc | FileCheck -check-prefix=CHECK-4 %s +// CHECK-4: sys.h