diff options
Diffstat (limited to 'clang/lib')
-rw-r--r-- | clang/lib/Frontend/ASTUnit.cpp | 1 | ||||
-rw-r--r-- | clang/lib/Lex/Lexer.cpp | 47 | ||||
-rw-r--r-- | clang/lib/Lex/Preprocessor.cpp | 7 | ||||
-rw-r--r-- | clang/lib/Parse/Parser.cpp | 4 | ||||
-rw-r--r-- | clang/lib/Sema/CodeCompleteConsumer.cpp | 6 | ||||
-rw-r--r-- | clang/lib/Sema/SemaCodeComplete.cpp | 111 |
6 files changed, 170 insertions, 6 deletions
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp index b91870c291c..d72fc92cc91 100644 --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -1976,6 +1976,7 @@ static void CalculateHiddenNames(const CodeCompletionContext &Context, case CodeCompletionContext::CCC_ObjCInstanceMessage: case CodeCompletionContext::CCC_ObjCClassMessage: case CodeCompletionContext::CCC_ObjCCategoryName: + case CodeCompletionContext::CCC_IncludedFile: // We're looking for nothing, or we're looking for names that cannot // be hidden. return; diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 6a69bb4974a..974414c1e45 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -1896,6 +1896,7 @@ const char *Lexer::LexUDSuffix(Token &Result, const char *CurPtr, /// either " or L" or u8" or u" or U". bool Lexer::LexStringLiteral(Token &Result, const char *CurPtr, tok::TokenKind Kind) { + const char *AfterQuote = CurPtr; // Does this string contain the \0 character? const char *NulCharacter = nullptr; @@ -1924,8 +1925,11 @@ bool Lexer::LexStringLiteral(Token &Result, const char *CurPtr, if (C == 0) { if (isCodeCompletionPoint(CurPtr-1)) { - PP->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 @@ bool Lexer::LexAngledStringLiteral(Token &Result, const char *CurPtr) { 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 @@ bool Lexer::LexAngledStringLiteral(Token &Result, const char *CurPtr) { } 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 @@ bool Lexer::LexAngledStringLiteral(Token &Result, const char *CurPtr) { 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, diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index dcff51ad61b..48b3571ab27 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -445,6 +445,13 @@ bool Preprocessor::SetCodeCompletionPoint(const FileEntry *File, return false; } +void Preprocessor::CodeCompleteIncludedFile(llvm::StringRef Dir, + bool IsAngled) { + if (CodeComplete) + CodeComplete->CodeCompleteIncludedFile(Dir, IsAngled); + setCodeCompletionReached(); +} + void Preprocessor::CodeCompleteNaturalLanguage() { if (CodeComplete) CodeComplete->CodeCompleteNaturalLanguage(); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 17366291213..34ed96fb7a1 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -1967,6 +1967,10 @@ void Parser::CodeCompleteMacroArgument(IdentifierInfo *Macro, ArgumentIndex); } +void Parser::CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled) { + Actions.CodeCompleteIncludedFile(Dir, IsAngled); +} + void Parser::CodeCompleteNaturalLanguage() { Actions.CodeCompleteNaturalLanguage(); } diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp index 226257dd395..754169fe5f1 100644 --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -80,6 +80,7 @@ bool CodeCompletionContext::wantConstructorResults() const { case CCC_ObjCClassMessage: case CCC_ObjCInterfaceName: case CCC_ObjCCategoryName: + case CCC_IncludedFile: return false; } @@ -155,6 +156,8 @@ StringRef clang::getCompletionKindString(CodeCompletionContext::Kind Kind) { return "ObjCInterfaceName"; case CCKind::CCC_ObjCCategoryName: return "ObjCCategoryName"; + case CCKind::CCC_IncludedFile: + return "IncludedFile"; case CCKind::CCC_Recovery: return "Recovery"; } @@ -522,7 +525,8 @@ bool PrintingCodeCompleteConsumer::isResultFilteredOut(StringRef Filter, 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."); } diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 4af36db2e56..94dd75bfe99 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/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 <list> #include <map> #include <vector> @@ -7994,6 +7996,115 @@ void Sema::CodeCompletePreprocessorMacroArgument(Scope *S, // for the expanded tokens. } +// This handles completion inside an #include filename, e.g. #include <foo/ba +// We look for the directory "foo" under each directory on the include path, +// list its files, and reassemble the appropriate #include. +void Sema::CodeCompleteIncludedFile(llvm::StringRef Dir, bool Angled) { + // RelDir should use /, but unescaped \ is possible on windows! + // Our completions will normalize to / for simplicity, this case is rare. + std::string RelDir = llvm::sys::path::convert_to_slash(Dir); + // We need the native slashes for the actual file system interactions. + SmallString<128> 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<StringRef> 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. <sys/ + TypedChunk.push_back(IsDirectory ? '/' : Angled ? '>' : '"'); + 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, |