richard9404 created this revision.
richard9404 added reviewers: sammccall, ilya-biryukov.
Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous.
Herald added a project: clang.

Header completion in clang always apppends endings to the results, i.e. `/` for 
folders and `"` (or `>`) for files. But a language-neutral client (like many 
vim lsp plugins) does not know a subsequent completion is expected, while the 
auto-inserted `/` prevents the user from triggering one by manually typing a 
`/`. And `"`/`>` might collide with auto pair insertion in some clients.

  #include "f"
            ^ completion triggered here
         => folder/  <- client is not aware that another completion is expected
         => file.h"  <- '"' collides with the ending '"' inserted by the client

This commit adds an option to suppress the endings, and adds `/` as a 
completion trigger in clangd, which makes header completion more similar to 
object member completion. The downside is that clangd will not be able to 
distinguish folders from files when setting the completion item kinds.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D64391

Files:
  clang-tools-extra/clangd/ClangdLSPServer.cpp
  clang-tools-extra/clangd/CodeComplete.cpp
  clang-tools-extra/clangd/CodeComplete.h
  clang-tools-extra/clangd/test/initialize-params.test
  clang-tools-extra/clangd/tool/ClangdMain.cpp
  clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
  clang/include/clang/Driver/CC1Options.td
  clang/include/clang/Sema/CodeCompleteConsumer.h
  clang/include/clang/Sema/CodeCompleteOptions.h
  clang/lib/Frontend/CompilerInvocation.cpp
  clang/lib/Sema/SemaCodeComplete.cpp
  clang/test/CodeCompletion/included-files-endings.cpp

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 <foosys.h>
+// 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 <foosys.h>
+// 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
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. <sys/
-    TypedChunk.push_back(IsDirectory ? '/' : Angled ? '>' : '"');
+    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/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/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/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/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-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-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<bool> 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/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/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/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;
 }
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to