Index: include/clang-c/Index.h =================================================================== --- include/clang-c/Index.h +++ include/clang-c/Index.h @@ -5606,9 +5606,14 @@ CXCompletionContext_NaturalLanguage = 1 << 21, /** + * #include file completions should be included in the results. + */ + CXCompletionContext_IncludedFile = 1 << 22, + + /** * The current context is unknown, so set all contexts. */ - CXCompletionContext_Unknown = ((1 << 22) - 1) + CXCompletionContext_Unknown = ((1 << 23) - 1) }; /** Index: include/clang/Lex/CodeCompletionHandler.h =================================================================== --- include/clang/Lex/CodeCompletionHandler.h +++ include/clang/Lex/CodeCompletionHandler.h @@ -14,6 +14,8 @@ #ifndef LLVM_CLANG_LEX_CODECOMPLETIONHANDLER_H #define LLVM_CLANG_LEX_CODECOMPLETIONHANDLER_H +#include "llvm/ADT/StringRef.h" + namespace clang { class IdentifierInfo; @@ -60,6 +62,11 @@ MacroInfo *MacroInfo, unsigned ArgumentIndex) { } + /// Callback invoked when performing code completion inside the filename + /// part of an #include directive. (Also #import, #include_next, etc). + /// \p Dir is the directory relative to the include path, e.g. "a" for CodeCompleteNaturalLanguage(); - FormTokenWithChars(Result, CurPtr-1, tok::unknown); + if (ParsingFilename) + codeCompleteIncludedFile(AfterQuote, CurPtr - 1, /*IsAngled=*/false); + else + PP->CodeCompleteNaturalLanguage(); + FormTokenWithChars(Result, CurPtr - 1, tok::unknown); cutOffLexing(); return true; } @@ -2043,9 +2047,8 @@ if (C == '\\') C = getAndAdvanceChar(CurPtr, Result); - if (C == '\n' || C == '\r' || // Newline. - (C == 0 && (CurPtr-1 == BufferEnd || // End of file. - isCodeCompletionPoint(CurPtr-1)))) { + if (C == '\n' || C == '\r' || // Newline. + (C == 0 && (CurPtr - 1 == BufferEnd))) { // End of file. // If the filename is unterminated, then it must just be a lone < // character. Return this as such. FormTokenWithChars(Result, AfterLessPos, tok::less); @@ -2053,6 +2056,12 @@ } if (C == 0) { + if (isCodeCompletionPoint(CurPtr - 1)) { + codeCompleteIncludedFile(AfterLessPos, CurPtr - 1, /*IsAngled=*/true); + cutOffLexing(); + FormTokenWithChars(Result, CurPtr - 1, tok::unknown); + return true; + } NulCharacter = CurPtr-1; } C = getAndAdvanceChar(CurPtr, Result); @@ -2069,6 +2078,34 @@ return true; } +void Lexer::codeCompleteIncludedFile(const char *PathStart, + const char *CompletionPoint, + bool IsAngled) { + // Completion only applies to the filename, after the last slash. + StringRef PartialPath(PathStart, CompletionPoint - PathStart); + auto Slash = PartialPath.find_last_of(LangOpts.MSVCCompat ? "/\\" : "/"); + StringRef Dir = + (Slash == StringRef::npos) ? "" : PartialPath.take_front(Slash); + const char *StartOfFilename = + (Slash == StringRef::npos) ? PathStart : PathStart + Slash + 1; + // Code completion filter range is the filename only, up to completion point. + PP->setCodeCompletionIdentifierInfo(&PP->getIdentifierTable().get( + StringRef(StartOfFilename, CompletionPoint - StartOfFilename))); + // We should replace the characters up to the closing quote, if any. + while (CompletionPoint < BufferEnd) { + char Next = *(CompletionPoint + 1); + if (Next == 0 || Next == '\r' || Next == '\n') + break; + ++CompletionPoint; + if (Next == (IsAngled ? '>' : '"')) + break; + } + PP->setCodeCompletionTokenRange( + FileLoc.getLocWithOffset(StartOfFilename - BufferStart), + FileLoc.getLocWithOffset(CompletionPoint - BufferStart)); + PP->CodeCompleteIncludedFile(Dir, IsAngled); +} + /// LexCharConstant - Lex the remainder of a character constant, after having /// lexed either ' or L' or u8' or u' or U'. bool Lexer::LexCharConstant(Token &Result, const char *CurPtr, Index: lib/Lex/Preprocessor.cpp =================================================================== --- lib/Lex/Preprocessor.cpp +++ lib/Lex/Preprocessor.cpp @@ -445,6 +445,13 @@ return false; } +void Preprocessor::CodeCompleteIncludedFile(llvm::StringRef Dir, + bool IsAngled) { + if (CodeComplete) + CodeComplete->CodeCompleteIncludedFile(Dir, IsAngled); + setCodeCompletionReached(); +} + void Preprocessor::CodeCompleteNaturalLanguage() { if (CodeComplete) CodeComplete->CodeCompleteNaturalLanguage(); Index: lib/Parse/Parser.cpp =================================================================== --- lib/Parse/Parser.cpp +++ lib/Parse/Parser.cpp @@ -1967,6 +1967,10 @@ ArgumentIndex); } +void Parser::CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled) { + Actions.CodeCompleteIncludedFile(Dir, IsAngled); +} + void Parser::CodeCompleteNaturalLanguage() { Actions.CodeCompleteNaturalLanguage(); } Index: lib/Sema/CodeCompleteConsumer.cpp =================================================================== --- lib/Sema/CodeCompleteConsumer.cpp +++ lib/Sema/CodeCompleteConsumer.cpp @@ -80,6 +80,7 @@ case CCC_ObjCClassMessage: case CCC_ObjCInterfaceName: case CCC_ObjCCategoryName: + case CCC_IncludedFile: return false; } @@ -155,6 +156,8 @@ return "ObjCInterfaceName"; case CCKind::CCC_ObjCCategoryName: return "ObjCCategoryName"; + case CCKind::CCC_IncludedFile: + return "IncludedFile"; case CCKind::CCC_Recovery: return "Recovery"; } @@ -522,7 +525,8 @@ case CodeCompletionResult::RK_Macro: return !Result.Macro->getName().startswith(Filter); case CodeCompletionResult::RK_Pattern: - return !StringRef(Result.Pattern->getAsString()).startswith(Filter); + return !(Result.Pattern->getTypedText() && + StringRef(Result.Pattern->getTypedText()).startswith(Filter)); } llvm_unreachable("Unknown code completion result Kind."); } Index: lib/Sema/SemaCodeComplete.cpp =================================================================== --- lib/Sema/SemaCodeComplete.cpp +++ lib/Sema/SemaCodeComplete.cpp @@ -32,6 +32,8 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Path.h" #include #include #include @@ -7994,6 +7996,115 @@ // for the expanded tokens. } +// This handles completion inside an #include filename, e.g. #include NativeRelDir = StringRef(RelDir); + llvm::sys::path::native(NativeRelDir); + auto FS = getSourceManager().getFileManager().getVirtualFileSystem(); + + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), + CodeCompletionContext::CCC_IncludedFile); + llvm::DenseSet SeenResults; // To deduplicate results. + + // Helper: adds one file or directory completion result. + auto AddCompletion = [&](StringRef Filename, bool IsDirectory) { + SmallString<64> TypedChunk = Filename; + // Directory completion is up to the slash, e.g. ' : '"'); + auto R = SeenResults.insert(TypedChunk); + if (R.second) { // New completion + const char *InternedTyped = Results.getAllocator().CopyString(TypedChunk); + *R.first = InternedTyped; // Avoid dangling StringRef. + CodeCompletionBuilder Builder(CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo()); + Builder.AddTypedTextChunk(InternedTyped); + // The result is a "Pattern", which is pretty opaque. + // We may want to include the real filename to allow smart ranking. + Results.AddResult(CodeCompletionResult(Builder.TakeString())); + } + }; + + // Helper: scans IncludeDir for nice files, and adds results for each. + auto AddFilesFromIncludeDir = [&](StringRef IncludeDir, bool IsSystem) { + llvm::SmallString<128> Dir = IncludeDir; + if (!NativeRelDir.empty()) + llvm::sys::path::append(Dir, NativeRelDir); + + std::error_code EC; + unsigned Count = 0; + for (auto It = FS->dir_begin(Dir, EC); + !EC && It != vfs::directory_iterator(); It.increment(EC)) { + if (++Count == 2500) // If we happen to hit a huge directory, + break; // bail out early so we're not too slow. + StringRef Filename = llvm::sys::path::filename(It->path()); + switch (It->type()) { + case llvm::sys::fs::file_type::directory_file: + AddCompletion(Filename, /*IsDirectory=*/true); + break; + case llvm::sys::fs::file_type::regular_file: + // Only files that really look like headers. (Except in system dirs). + if (!IsSystem) { + // Header extensions from Types.def, which we can't depend on here. + if (!(Filename.endswith_lower(".h") || + Filename.endswith_lower(".hh") || + Filename.endswith_lower(".hpp") || + Filename.endswith_lower(".inc"))) + break; + } + AddCompletion(Filename, /*IsDirectory=*/false); + break; + default: + break; + } + } + }; + + // Helper: adds results relative to IncludeDir, if possible. + auto AddFilesFromDirLookup = [&](const DirectoryLookup &IncludeDir, + bool IsSystem) { + llvm::SmallString<128> Dir; + switch (IncludeDir.getLookupType()) { + case DirectoryLookup::LT_HeaderMap: + // header maps are not (currently) enumerable. + break; + case DirectoryLookup::LT_NormalDir: + AddFilesFromIncludeDir(IncludeDir.getDir()->getName(), IsSystem); + break; + case DirectoryLookup::LT_Framework: + AddFilesFromIncludeDir(IncludeDir.getFrameworkDir()->getName(), IsSystem); + break; + } + }; + + // Finally with all our helpers, we can scan the include path. + // Do this in standard order so deduplication keeps the right file. + // (In case we decide to add more details to the results later). + const auto &S = PP.getHeaderSearchInfo(); + using llvm::make_range; + if (!Angled) { + // The current directory is on the include path for "quoted" includes. + auto *CurFile = PP.getCurrentFileLexer()->getFileEntry(); + if (CurFile && CurFile->getDir()) + AddFilesFromIncludeDir(CurFile->getDir()->getName(), false); + for (const auto &D : make_range(S.quoted_dir_begin(), S.quoted_dir_end())) + AddFilesFromDirLookup(D, false); + } + for (const auto &D : make_range(S.angled_dir_begin(), S.angled_dir_end())) + AddFilesFromDirLookup(D, true); + for (const auto &D : make_range(S.system_dir_begin(), S.system_dir_end())) + AddFilesFromDirLookup(D, true); + + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); +} + void Sema::CodeCompleteNaturalLanguage() { HandleCodeCompleteResults(this, CodeCompleter, CodeCompletionContext::CCC_NaturalLanguage, Index: test/CodeCompletion/included-files.cpp =================================================================== --- test/CodeCompletion/included-files.cpp +++ test/CodeCompletion/included-files.cpp @@ -0,0 +1,29 @@ +// RUN: rm -rf %t && mkdir %t && cp %s %t/main.cc && mkdir %t/a +// RUN: touch %t/foo.h && touch %t/foo.cc && touch %t/a/foosys %t/a/foosys.h + +// Quoted string shows header-ish files from CWD, and all from system. +#include "foo.h" +// RUN: %clang -fsyntax-only -isystem %t/a -Xclang -code-completion-at=%t/main.cc:5:13 %t/main.cc | FileCheck -check-prefix=CHECK-1 %s +// CHECK-1-NOT: foo.cc" +// CHECK-1: foo.h" +// CHECK-1: foosys" + +// Quoted string with dir shows header-ish files in that subdir. +#include "a/foosys" +// RUN: %clang -fsyntax-only -isystem %t/a -Xclang -code-completion-at=%t/main.cc:12:13 %t/main.cc | FileCheck -check-prefix=CHECK-2 %s +// CHECK-2-NOT: foo.h" +// CHECK-2: foosys.h" +// CHECK-2-NOT: foosys" + +// Angled string showes all files, but only in system dirs. +#include +// RUN: %clang -fsyntax-only -isystem %t/a -Xclang -code-completion-at=%t/main.cc:19:13 %t/main.cc | FileCheck -check-prefix=CHECK-3 %s +// CHECK-3-NOT: foo.cc> +// CHECK-3-NOT: foo.h> +// CHECK-3: foosys> + +// Backslash handling. +#include "a\foosys" +// RUN: %clang -fsyntax-only -isystem %t/a -Xclang -code-completion-at=%t/main.cc:26:13 %t/main.cc -fms-compatibility | FileCheck -check-prefix=CHECK-4 %s +// CHECK-4: foosys.h" + Index: tools/libclang/CIndexCodeCompletion.cpp =================================================================== --- tools/libclang/CIndexCodeCompletion.cpp +++ tools/libclang/CIndexCodeCompletion.cpp @@ -499,6 +499,10 @@ contexts = CXCompletionContext_NaturalLanguage; break; } + case CodeCompletionContext::CCC_IncludedFile: { + contexts = CXCompletionContext_IncludedFile; + break; + } case CodeCompletionContext::CCC_SelectorName: { contexts = CXCompletionContext_ObjCSelectorName; break;