sammccall created this revision.
sammccall added a reviewer: ilya-biryukov.
Herald added a subscriber: cfe-commits.

The dir component ("somedir" in #include <somedir/fo...>) is considered fixed.
We append "foo" to each directory on the include path, and then list its files.

Completions are of the forms:
 <somedir/foo.h>
 ^-text--^^-TT-^  (TypedText)
and
 <somedir/foodir/
 ^-text--^^-TT--^

The filter is set to the filename part ("fo"), so fuzzy matching can be
applied to the filename only.

No fancy scoring/priorities are set, and no information is added to
CodeCompleteResult to make smart scoring possible. Could be in future.

Note directory iteration is currently stat-happy on unix but 
https://reviews.llvm.org/D51921 will fix.


Repository:
  rC Clang

https://reviews.llvm.org/D52076

Files:
  include/clang-c/Index.h
  include/clang/Lex/CodeCompletionHandler.h
  include/clang/Lex/Preprocessor.h
  include/clang/Parse/Parser.h
  include/clang/Sema/CodeCompleteConsumer.h
  include/clang/Sema/Sema.h
  lib/Frontend/ASTUnit.cpp
  lib/Lex/Lexer.cpp
  lib/Lex/Preprocessor.cpp
  lib/Parse/Parser.cpp
  lib/Sema/CodeCompleteConsumer.cpp
  lib/Sema/SemaCodeComplete.cpp
  test/CodeCompletion/included-files.cpp
  tools/libclang/CIndexCodeCompletion.cpp

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;
Index: test/CodeCompletion/included-files.cpp
===================================================================
--- /dev/null
+++ test/CodeCompletion/included-files.cpp
@@ -0,0 +1,25 @@
+// 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: "a/foosys"
+// CHECK-2: "a/foosys.h"
+// CHECK-2-NOT: "a/foosys"
+// CHECK-2-NOT: "foo.h"
+// CHECK-2-NOT: "foosys"
+
+// Angled string showes all files, but only in system dirs.
+#include <foosys>
+// RUN: %clang -fsyntax-only -isystem %t/a -Xclang -code-completion-at=%t/main.cc:21:13 %t/main.cc | FileCheck -check-prefix=CHECK-3 %s
+// CHECK-3-NOT: <foo.cc>
+// CHECK-3-NOT: <foo.h>
+// CHECK-3: <foosys>
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 <list>
 #include <map>
 #include <vector>
@@ -7994,6 +7996,115 @@
   // 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 RelDir, bool Angled) {
+  // We need the native slashes for the actual file system interactions.
+  const char *InternedPrefix = RelDir.empty()
+                                   ? (Angled ? "<" : "\"")
+                                   : CodeCompleter->getAllocator().CopyString(
+                                         (Angled ? "<" : "\"") + RelDir + "/");
+  SmallString<128> NativeRelDir = 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.AddTextChunk(InternedPrefix);
+      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 == 1000) // 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->getName());
+      switch (It->getType()) {
+      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) {
+          if (!(Filename.endswith(".h") || Filename.endswith(".hh") ||
+                Filename.endswith(".H") || Filename.endswith(".hpp") ||
+                Filename.endswith(".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: 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/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/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/Lex/Lexer.cpp
===================================================================
--- lib/Lex/Lexer.cpp
+++ lib/Lex/Lexer.cpp
@@ -1896,6 +1896,7 @@
 /// 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,25 @@
 
     if (C == 0) {
       if (isCodeCompletionPoint(CurPtr-1)) {
-        PP->CodeCompleteNaturalLanguage();
-        FormTokenWithChars(Result, CurPtr-1, tok::unknown);
+        if (ParsingFilename) {
+          // Completion only applies to the filename, after the last slash.
+          StringRef PartialPath(AfterQuote, CurPtr - 1 - AfterQuote);
+          auto Slash = PartialPath.rfind('/');
+          StringRef Dir =
+              (Slash == StringRef::npos) ? "" : PartialPath.take_front(Slash);
+          const char *StartOfFilename =
+              (Slash == StringRef::npos) ? AfterQuote : AfterQuote + Slash + 1;
+          // Code completion range is the filename only.
+          PP->setCodeCompletionIdentifierInfo(&PP->getIdentifierTable().get(
+              StringRef(StartOfFilename, CurPtr - 1 - StartOfFilename)));
+          PP->setCodeCompletionTokenRange(
+              FileLoc.getLocWithOffset(BufferPtr - BufferStart),
+              FileLoc.getLocWithOffset(CurPtr - 1 - BufferStart));
+          PP->CodeCompleteIncludedFile(Dir, /*IsAngled=*/false);
+        } else {
+          PP->CodeCompleteNaturalLanguage();
+        }
+        FormTokenWithChars(Result, CurPtr - 1, tok::unknown);
         cutOffLexing();
         return true;
       }
@@ -2043,16 +2061,36 @@
     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);
       return true;
     }
 
     if (C == 0) {
+      if (isCodeCompletionPoint(CurPtr - 1)) {
+        // Completion only applies to the filename, after the last slash.
+        StringRef PartialPath(AfterLessPos, CurPtr - 1 - AfterLessPos);
+        auto Slash = PartialPath.rfind('/');
+        StringRef Dir =
+            (Slash == StringRef::npos) ? "" : PartialPath.take_front(Slash);
+        const char *StartOfFilename = (Slash == StringRef::npos)
+                                          ? AfterLessPos
+                                          : AfterLessPos + Slash + 1;
+        // Code completion filter range is the filename only.
+        PP->setCodeCompletionIdentifierInfo(&PP->getIdentifierTable().get(
+            StringRef(StartOfFilename, CurPtr - 1 - StartOfFilename)));
+        PP->setCodeCompletionTokenRange(
+            FileLoc.getLocWithOffset(BufferPtr - BufferStart),
+            FileLoc.getLocWithOffset(CurPtr - 1 - BufferStart));
+        PP->CodeCompleteIncludedFile(Dir, /*IsAngled=*/true);
+
+        cutOffLexing();
+        FormTokenWithChars(Result, CurPtr - 1, tok::unknown);
+        return true;
+      }
       NulCharacter = CurPtr-1;
     }
     C = getAndAdvanceChar(CurPtr, Result);
Index: lib/Frontend/ASTUnit.cpp
===================================================================
--- lib/Frontend/ASTUnit.cpp
+++ lib/Frontend/ASTUnit.cpp
@@ -1976,6 +1976,7 @@
   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;
Index: include/clang/Sema/Sema.h
===================================================================
--- include/clang/Sema/Sema.h
+++ include/clang/Sema/Sema.h
@@ -10345,6 +10345,7 @@
                                              IdentifierInfo *Macro,
                                              MacroInfo *MacroInfo,
                                              unsigned Argument);
+  void CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled);
   void CodeCompleteNaturalLanguage();
   void CodeCompleteAvailabilityPlatformName();
   void GatherGlobalCodeCompletions(CodeCompletionAllocator &Allocator,
Index: include/clang/Sema/CodeCompleteConsumer.h
===================================================================
--- include/clang/Sema/CodeCompleteConsumer.h
+++ include/clang/Sema/CodeCompleteConsumer.h
@@ -323,6 +323,9 @@
     /// Code completion where an Objective-C category name is expected.
     CCC_ObjCCategoryName,
 
+    /// Code completion inside the filename part of a #include directive.
+    CCC_IncludedFile,
+
     /// An unknown context, in which we are recovering from a parsing
     /// error and don't know which completions we should give.
     CCC_Recovery
Index: include/clang/Parse/Parser.h
===================================================================
--- include/clang/Parse/Parser.h
+++ include/clang/Parse/Parser.h
@@ -2969,6 +2969,7 @@
   void CodeCompletePreprocessorExpression() override;
   void CodeCompleteMacroArgument(IdentifierInfo *Macro, MacroInfo *MacroInfo,
                                  unsigned ArgumentIndex) override;
+  void CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled) override;
   void CodeCompleteNaturalLanguage() override;
 };
 
Index: include/clang/Lex/Preprocessor.h
===================================================================
--- include/clang/Lex/Preprocessor.h
+++ include/clang/Lex/Preprocessor.h
@@ -1128,6 +1128,10 @@
     CodeComplete = nullptr;
   }
 
+  /// Hook used by the lexer to invoke the "included file" code
+  /// completion point.
+  void CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled);
+
   /// Hook used by the lexer to invoke the "natural language" code
   /// completion point.
   void CodeCompleteNaturalLanguage();
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 <a/b.
+  virtual void CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled) {}
+
   /// Callback invoked when performing code completion in a part of the
   /// file where we expect natural language, e.g., a comment, string, or
   /// \#error directive.
Index: include/clang-c/Index.h
===================================================================
--- include/clang-c/Index.h
+++ include/clang-c/Index.h
@@ -5605,10 +5605,15 @@
    */
   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)
 };
 
 /**
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to