njames93 created this revision.
njames93 added reviewers: sammccall, kadircet, hokein.
Herald added subscribers: usaxena95, arphaman, mgorny.
njames93 requested review of this revision.
Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov.
Herald added a project: clang.

I'm not super happy with this implementation right now, this is more a proof of 
concept, get it working kind of thing.
This does mimick the pattern that clang-format appears to use for grabbing its 
configuration albeit this goes straight to looking for config files in the 
directory rather than using the DefaultFormatStyle.

Although this is a large change. alot of modifications relate to the Tests 
being updated to this interface, I'd like to figure out how to minimise the 
changes and streamline the tests, but I'm not entirely sure the best way to go 
about that.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D93800

Files:
  clang-tools-extra/clangd/CMakeLists.txt
  clang-tools-extra/clangd/ClangdServer.cpp
  clang-tools-extra/clangd/ClangdServer.h
  clang-tools-extra/clangd/CodeComplete.cpp
  clang-tools-extra/clangd/Compiler.h
  clang-tools-extra/clangd/FormatProvider.cpp
  clang-tools-extra/clangd/FormatProvider.h
  clang-tools-extra/clangd/Hover.cpp
  clang-tools-extra/clangd/Hover.h
  clang-tools-extra/clangd/ParsedAST.cpp
  clang-tools-extra/clangd/SourceCode.cpp
  clang-tools-extra/clangd/SourceCode.h
  clang-tools-extra/clangd/tool/Check.cpp
  clang-tools-extra/clangd/tool/ClangdMain.cpp
  clang-tools-extra/clangd/unittests/ClangdTests.cpp
  clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
  clang-tools-extra/clangd/unittests/HoverTests.cpp
  clang-tools-extra/clangd/unittests/PreambleTests.cpp
  clang-tools-extra/clangd/unittests/TestTU.cpp
  clang-tools-extra/clangd/unittests/TestTU.h

Index: clang-tools-extra/clangd/unittests/TestTU.h
===================================================================
--- clang-tools-extra/clangd/unittests/TestTU.h
+++ clang-tools-extra/clangd/unittests/TestTU.h
@@ -19,6 +19,7 @@
 
 #include "../TidyProvider.h"
 #include "Compiler.h"
+#include "FormatProvider.h"
 #include "ParsedAST.h"
 #include "TestFS.h"
 #include "index/Index.h"
@@ -60,6 +61,8 @@
   std::vector<std::string> ExtraArgs;
 
   TidyProvider ClangTidyProvider = {};
+
+  mutable FormatProvider ClangFormatProvider = {};
   // Index to use when building AST.
   const SymbolIndex *ExternalIndex = nullptr;
 
Index: clang-tools-extra/clangd/unittests/TestTU.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/TestTU.cpp
+++ clang-tools-extra/clangd/unittests/TestTU.cpp
@@ -9,6 +9,7 @@
 #include "TestTU.h"
 #include "Compiler.h"
 #include "Diagnostics.h"
+#include "FormatProvider.h"
 #include "TestFS.h"
 #include "index/FileIndex.h"
 #include "index/MemIndex.h"
@@ -61,6 +62,9 @@
   Inputs.Opts = ParseOptions();
   if (ClangTidyProvider)
     Inputs.ClangTidyProvider = ClangTidyProvider;
+  ClangFormatProvider =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  Inputs.ClangFormatProvider = ClangFormatProvider;
   Inputs.Index = ExternalIndex;
   if (Inputs.Index)
     Inputs.Opts.SuggestMissingIncludes = true;
Index: clang-tools-extra/clangd/unittests/PreambleTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/PreambleTests.cpp
+++ clang-tools-extra/clangd/unittests/PreambleTests.cpp
@@ -393,9 +393,9 @@
     auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());
     ASSERT_TRUE(FoundMacro);
     EXPECT_THAT(FoundMacro->Name, "FOO");
-    auto HI =
-        getHover(*AST, offsetToPosition(Modified.code(), Modified.point()),
-                 format::getLLVMStyle(), nullptr);
+    auto HI = getHover(
+        *AST, offsetToPosition(Modified.code(), Modified.point()),
+        std::make_shared<format::FormatStyle>(format::getLLVMStyle()), nullptr);
     ASSERT_TRUE(HI);
     EXPECT_THAT(HI->Definition, testing::IsEmpty());
   }
@@ -409,7 +409,9 @@
     auto AST = createPatchedAST(Baseline, Modified.code());
     ASSERT_TRUE(AST);
 
-    auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr);
+    auto HI = getHover(
+        *AST, Modified.point(),
+        std::make_shared<format::FormatStyle>(format::getLLVMStyle()), nullptr);
     ASSERT_TRUE(HI);
     EXPECT_THAT(HI->Definition, "#define BAR");
   }
Index: clang-tools-extra/clangd/unittests/HoverTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -876,7 +876,9 @@
     TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
     auto AST = TU.build();
 
-    auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+    auto H = getHover(
+        AST, T.point(),
+        std::make_shared<format::FormatStyle>(format::getLLVMStyle()), nullptr);
     ASSERT_TRUE(H);
     HoverInfo Expected;
     Expected.SymRange = T.range();
@@ -972,7 +974,9 @@
     TestTU TU = TestTU::withCode(T.code());
     TU.ExtraArgs.push_back("-std=c++17");
     auto AST = TU.build();
-    auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+    auto H = getHover(
+        AST, T.point(),
+        std::make_shared<format::FormatStyle>(format::getLLVMStyle()), nullptr);
     ASSERT_TRUE(H);
     EXPECT_EQ(H->CallPassType->PassBy, Test.PassBy);
     EXPECT_EQ(H->CallPassType->Converted, Test.Converted);
@@ -1024,7 +1028,9 @@
     TestTU TU = TestTU::withCode(T.code());
     TU.ExtraArgs.push_back("-std=c++17");
     auto AST = TU.build();
-    auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+    auto H = getHover(
+        AST, T.point(),
+        std::make_shared<format::FormatStyle>(format::getLLVMStyle()), nullptr);
     ASSERT_FALSE(H);
   }
 }
@@ -2284,7 +2290,10 @@
     TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
     auto AST = TU.build();
 
-    auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
+    auto H =
+        getHover(AST, T.point(),
+                 std::make_shared<format::FormatStyle>(format::getLLVMStyle()),
+                 Index.get());
     ASSERT_TRUE(H);
     HoverInfo Expected;
     Expected.SymRange = T.range();
@@ -2326,7 +2335,9 @@
       MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
 
   for (const auto &P : T.points()) {
-    auto H = getHover(AST, P, format::getLLVMStyle(), Index.get());
+    auto H = getHover(
+        AST, P, std::make_shared<format::FormatStyle>(format::getLLVMStyle()),
+        Index.get());
     ASSERT_TRUE(H);
     EXPECT_EQ(H->Documentation, IndexSym.Documentation);
   }
@@ -2351,7 +2362,9 @@
   TestTU TU = TestTU::withCode(T.code());
   auto AST = TU.build();
   for (const auto &P : T.points()) {
-    auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
+    auto H = getHover(
+        AST, P, std::make_shared<format::FormatStyle>(format::getLLVMStyle()),
+        nullptr);
     ASSERT_TRUE(H);
     EXPECT_EQ(H->Documentation, "doc");
   }
@@ -2375,7 +2388,9 @@
   auto AST = TU.build();
   for (auto Comment : {"doc1", "doc2", "doc3"}) {
     for (const auto &P : T.points(Comment)) {
-      auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
+      auto H = getHover(
+          AST, P, std::make_shared<format::FormatStyle>(format::getLLVMStyle()),
+          nullptr);
       ASSERT_TRUE(H);
       EXPECT_EQ(H->Documentation, Comment);
     }
Index: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -10,6 +10,7 @@
 #include "ClangdServer.h"
 #include "CodeComplete.h"
 #include "Compiler.h"
+#include "FormatProvider.h"
 #include "Matchers.h"
 #include "Protocol.h"
 #include "Quality.h"
@@ -140,7 +141,7 @@
 CodeCompleteResult completionsNoCompile(llvm::StringRef Text,
                                         std::vector<Symbol> IndexSymbols = {},
                                         clangd::CodeCompleteOptions Opts = {},
-                                        PathRef FilePath = "foo.cpp") {
+                                        PathRef FilePath = "/foo.cpp") {
   std::unique_ptr<SymbolIndex> OverrideIndex;
   if (!IndexSymbols.empty()) {
     assert(!Opts.Index && "both Index and IndexSymbols given!");
@@ -149,8 +150,11 @@
   }
 
   MockFS FS;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
   Annotations Test(Text);
   ParseInputs ParseInput{tooling::CompileCommand(), &FS, Test.code().str()};
+  ParseInput.ClangFormatProvider = Format;
   return codeComplete(FilePath, Test.point(), /*Preamble=*/nullptr, ParseInput,
                       Opts);
 }
@@ -839,6 +843,9 @@
   MockCompilationDatabase CDB;
   ClangdServer::Options Opts = ClangdServer::optsForTest();
   Opts.BuildDynamicSymbolIndex = true;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  Opts.ClangFormatProvider = Format;
   ClangdServer Server(CDB, FS, Opts);
 
   FS.Files[testPath("foo_header.h")] = R"cpp(
@@ -874,6 +881,9 @@
   MockCompilationDatabase CDB;
   auto Opts = ClangdServer::optsForTest();
   Opts.BuildDynamicSymbolIndex = true;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  Opts.ClangFormatProvider = Format;
   ClangdServer Server(CDB, FS, Opts);
 
   FS.Files[testPath("foo.h")] = R"cpp(
@@ -935,6 +945,9 @@
 
   auto Opts = ClangdServer::optsForTest();
   Opts.BuildDynamicSymbolIndex = true;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  Opts.ClangFormatProvider = Format;
 
   ClangdServer Server(CDB, FS, Opts);
 
@@ -1667,7 +1680,11 @@
   FS.Files[FooCpp] = "";
 
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ServerOpts.ClangFormatProvider = Format;
+  ClangdServer Server(CDB, FS, ServerOpts);
 
   Annotations Source(R"cpp(
     #include "foo.h"
@@ -1755,7 +1772,11 @@
   MockFS FS;
   FS.Files[FooCpp] = "// empty file";
 
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ServerOpts.ClangFormatProvider = Format;
+  ClangdServer Server(CDB, FS, ServerOpts);
   // Run completion outside the file range.
   Position Pos;
   Pos.line = 100;
@@ -2161,6 +2182,9 @@
   MockCompilationDatabase CDB;
   ClangdServer::Options Opts = ClangdServer::optsForTest();
   Opts.BuildDynamicSymbolIndex = true;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  Opts.ClangFormatProvider = Format;
   ClangdServer Server(CDB, FS, Opts);
 
   FS.Files[testPath("foo.h")] = R"cpp(
@@ -2331,7 +2355,11 @@
 TEST(CompletionTest, EnableSpeculativeIndexRequest) {
   MockFS FS;
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest());
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ServerOpts.ClangFormatProvider = Format;
+  ClangdServer Server(CDB, FS, ServerOpts);
 
   auto File = testPath("foo.cpp");
   Annotations Test(R"cpp(
Index: clang-tools-extra/clangd/unittests/ClangdTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/ClangdTests.cpp
+++ clang-tools-extra/clangd/unittests/ClangdTests.cpp
@@ -11,6 +11,7 @@
 #include "ClangdServer.h"
 #include "CodeComplete.h"
 #include "ConfigFragment.h"
+#include "FormatProvider.h"
 #include "GlobalCompilationDatabase.h"
 #include "Matchers.h"
 #include "SyncAPI.h"
@@ -616,8 +617,12 @@
   MockFS FS;
   ErrorCheckingCallbacks DiagConsumer;
   MockCompilationDatabase CDB;
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
 
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto FooCpp = testPath("foo.cpp");
   // clang cannot create CompilerInvocation if we pass two files in the
@@ -731,7 +736,12 @@
   TestDiagConsumer DiagConsumer;
   {
     MockCompilationDatabase CDB;
-    ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+    FormatProvider Format =
+        getClangFormatProvider(FS, format::DefaultFallbackStyle);
+    ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+    ServerOpts.ClangFormatProvider = Format;
+
+    ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
     // Prepare some random distributions for the test.
     std::random_device RandGen;
@@ -926,7 +936,12 @@
   MockFS FS;
   ErrorCheckingCallbacks DiagConsumer;
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
+
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto Path = testPath("foo.cpp");
   std::string Code = R"cpp(
@@ -955,7 +970,12 @@
   MockFS FS;
   ErrorCheckingCallbacks DiagConsumer;
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
+
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto SourcePath = testPath("source/foo.cpp");
   auto HeaderPath = testPath("headers/foo.h");
@@ -1032,7 +1052,12 @@
   StatRecordingFS FS(CountStats);
   ErrorCheckingCallbacks DiagConsumer;
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
+
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto SourcePath = testPath("foo.cpp");
   auto HeaderPath = testPath("foo.h");
@@ -1061,7 +1086,12 @@
   MockFS FS;
   ErrorCheckingCallbacks DiagConsumer;
   MockCompilationDatabase CDB;
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
+
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto FooCpp = testPath("foo.cpp");
   Annotations Code(R"cpp(
@@ -1132,7 +1162,12 @@
 
   Notification CanReturnCommand;
   DelayedCompilationDatabase CDB(CanReturnCommand);
-  ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer);
+  FormatProvider Format =
+      getClangFormatProvider(FS, format::DefaultFallbackStyle);
+  ClangdServer::Options ServerOpts = ClangdServer::optsForTest();
+  ServerOpts.ClangFormatProvider = Format;
+
+  ClangdServer Server(CDB, FS, ServerOpts, &DiagConsumer);
 
   auto FooCpp = testPath("foo.cpp");
   Annotations Code(R"cpp(
Index: clang-tools-extra/clangd/tool/ClangdMain.cpp
===================================================================
--- clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -9,6 +9,7 @@
 #include "ClangdLSPServer.h"
 #include "CodeComplete.h"
 #include "Features.inc"
+#include "FormatProvider.h"
 #include "PathMapping.h"
 #include "Protocol.h"
 #include "TidyProvider.h"
@@ -882,6 +883,9 @@
     ClangTidyOptProvider = combine(std::move(Providers));
     Opts.ClangTidyProvider = ClangTidyOptProvider;
   }
+  FormatProvider ClangFormatOptProvider =
+      getClangFormatProvider(TFS, FallbackStyle);
+  Opts.ClangFormatProvider = ClangFormatOptProvider;
   Opts.AsyncPreambleBuilds = AsyncPreamble;
   Opts.SuggestMissingIncludes = SuggestMissingIncludes;
   Opts.QueryDriverGlobs = std::move(QueryDriverGlobs);
Index: clang-tools-extra/clangd/tool/Check.cpp
===================================================================
--- clang-tools-extra/clangd/tool/Check.cpp
+++ clang-tools-extra/clangd/tool/Check.cpp
@@ -76,7 +76,7 @@
   // from buildInvocation
   ParseInputs Inputs;
   std::unique_ptr<CompilerInvocation> Invocation;
-  format::FormatStyle Style;
+  std::shared_ptr<const format::FormatStyle> Style;
   // from buildAST
   std::shared_ptr<const PreambleData> Preamble;
   llvm::Optional<ParsedAST> AST;
@@ -134,6 +134,7 @@
         return false;
       }
     }
+    Inputs.ClangFormatProvider = Opts.ClangFormatProvider;
     log("Parsing command...");
     Invocation =
         buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);
@@ -147,7 +148,7 @@
 
     // FIXME: Check that resource-dir/built-in-headers exist?
 
-    Style = getFormatStyleForFile(File, Inputs.Contents, TFS);
+    Style = Inputs.ClangFormatProvider(File, Inputs.Contents);
 
     return true;
   }
Index: clang-tools-extra/clangd/SourceCode.h
===================================================================
--- clang-tools-extra/clangd/SourceCode.h
+++ clang-tools-extra/clangd/SourceCode.h
@@ -161,15 +161,6 @@
 llvm::Optional<std::string> getCanonicalPath(const FileEntry *F,
                                              const SourceManager &SourceMgr);
 
-/// Choose the clang-format style we should apply to a certain file.
-/// This will usually use FS to look for .clang-format directories.
-/// FIXME: should we be caching the .clang-format file search?
-/// This uses format::DefaultFormatStyle and format::DefaultFallbackStyle,
-/// though the latter may have been overridden in main()!
-format::FormatStyle getFormatStyleForFile(llvm::StringRef File,
-                                          llvm::StringRef Content,
-                                          const ThreadsafeFS &TFS);
-
 /// Cleanup and format the given replacements.
 llvm::Expected<tooling::Replacements>
 cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces,
Index: clang-tools-extra/clangd/SourceCode.cpp
===================================================================
--- clang-tools-extra/clangd/SourceCode.cpp
+++ clang-tools-extra/clangd/SourceCode.cpp
@@ -570,20 +570,6 @@
   return digest(Content);
 }
 
-format::FormatStyle getFormatStyleForFile(llvm::StringRef File,
-                                          llvm::StringRef Content,
-                                          const ThreadsafeFS &TFS) {
-  auto Style = format::getStyle(format::DefaultFormatStyle, File,
-                                format::DefaultFallbackStyle, Content,
-                                TFS.view(/*CWD=*/llvm::None).get());
-  if (!Style) {
-    log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.", File,
-        Style.takeError());
-    return format::getLLVMStyle();
-  }
-  return *Style;
-}
-
 llvm::Expected<tooling::Replacements>
 cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces,
                  const format::FormatStyle &Style) {
Index: clang-tools-extra/clangd/ParsedAST.cpp
===================================================================
--- clang-tools-extra/clangd/ParsedAST.cpp
+++ clang-tools-extra/clangd/ParsedAST.cpp
@@ -353,9 +353,9 @@
   auto BuildDir = VFS->getCurrentWorkingDirectory();
   if (Inputs.Opts.SuggestMissingIncludes && Inputs.Index &&
       !BuildDir.getError()) {
-    auto Style = getFormatStyleForFile(Filename, Inputs.Contents, *Inputs.TFS);
+    auto Style = Inputs.ClangFormatProvider(Filename, Inputs.Contents);
     auto Inserter = std::make_shared<IncludeInserter>(
-        Filename, Inputs.Contents, Style, BuildDir.get(),
+        Filename, Inputs.Contents, *Style, BuildDir.get(),
         &Clang->getPreprocessor().getHeaderSearchInfo());
     if (Preamble) {
       for (const auto &Inc : Preamble->Includes.MainFileIncludes)
Index: clang-tools-extra/clangd/Hover.h
===================================================================
--- clang-tools-extra/clangd/Hover.h
+++ clang-tools-extra/clangd/Hover.h
@@ -114,9 +114,10 @@
 }
 
 /// Get the hover information when hovering at \p Pos.
-llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
-                                   format::FormatStyle Style,
-                                   const SymbolIndex *Index);
+llvm::Optional<HoverInfo>
+getHover(ParsedAST &AST, Position Pos,
+         std::shared_ptr<const format::FormatStyle> Style,
+         const SymbolIndex *Index);
 
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/Hover.cpp
===================================================================
--- clang-tools-extra/clangd/Hover.cpp
+++ clang-tools-extra/clangd/Hover.cpp
@@ -828,9 +828,10 @@
 
 } // namespace
 
-llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
-                                   format::FormatStyle Style,
-                                   const SymbolIndex *Index) {
+llvm::Optional<HoverInfo>
+getHover(ParsedAST &AST, Position Pos,
+         std::shared_ptr<const format::FormatStyle> Style,
+         const SymbolIndex *Index) {
   PrintingPolicy PP =
       getPrintingPolicy(AST.getASTContext().getPrintingPolicy());
   const SourceManager &SM = AST.getSourceManager();
@@ -909,7 +910,7 @@
     return llvm::None;
 
   auto Replacements = format::reformat(
-      Style, HI->Definition, tooling::Range(0, HI->Definition.size()));
+      *Style, HI->Definition, tooling::Range(0, HI->Definition.size()));
   if (auto Formatted =
           tooling::applyAllReplacements(HI->Definition, Replacements))
     HI->Definition = *Formatted;
Index: clang-tools-extra/clangd/FormatProvider.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/FormatProvider.h
@@ -0,0 +1,31 @@
+//===--- FormatProvider.h -----------------------------------*- C++-*------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATPROVIDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATPROVIDER_H
+
+#include "support/Path.h"
+#include "support/ThreadsafeFS.h"
+#include "clang/Format/Format.h"
+#include "llvm/ADT/FunctionExtras.h"
+
+namespace clang {
+namespace clangd {
+using FormatProvider =
+    llvm::unique_function<std::shared_ptr<const format::FormatStyle>(
+        PathRef, llvm::StringRef) const>;
+using FormatProviderRef =
+    llvm::function_ref<std::shared_ptr<const format::FormatStyle>(
+        PathRef, llvm::StringRef)>;
+
+FormatProvider getClangFormatProvider(ThreadsafeFS &TFS,
+                                      StringRef FallbackStyle);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATPROVIDER_H
Index: clang-tools-extra/clangd/FormatProvider.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/FormatProvider.cpp
@@ -0,0 +1,187 @@
+//===--- FormatProvider.cpp ---------------------------------*- C++-*------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "FormatProvider.h"
+#include "support/FileCache.h"
+#include "support/Logger.h"
+#include "clang/Format/Format.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MemoryBufferRef.h"
+#include <memory>
+#include <tuple>
+#include <utility>
+
+namespace clang {
+namespace clangd {
+
+namespace {
+// Access to config from a clang format configuration file, caching IO and
+// parsing.
+using LanguageKind = format::FormatStyle::LanguageKind;
+class ClangFormatCache : private FileCache {
+  mutable std::shared_ptr<format::FormatStyle> Value;
+  const LanguageKind Lang;
+
+public:
+  ClangFormatCache(PathRef Path, LanguageKind Lang)
+      : FileCache(Path), Lang(Lang) {}
+
+  std::shared_ptr<const format::FormatStyle>
+  get(const ThreadsafeFS &TFS,
+      std::chrono::steady_clock::time_point FreshTime) const {
+    std::shared_ptr<const format::FormatStyle> Result;
+    read(
+        TFS, FreshTime,
+        [this](llvm::Optional<llvm::StringRef> Data) {
+          // Clear any stale config if one exists.
+          Value.reset();
+          if (!Data || !Data->empty())
+            return;
+          format::FormatStyle Style = format::getLLVMStyle(Lang);
+          if (auto EC = format::parseConfiguration(
+                  llvm::MemoryBufferRef(*Data, path()), &Style,
+                  /*AllowUnknownOptions=*/true))
+            elog("clang-format style parsing error: {0}", EC.message());
+          else
+            Value = std::make_shared<format::FormatStyle>(std::move(Style));
+        },
+        [&]() { Result = Value; });
+    return Result;
+  }
+};
+
+// Clang-Format checks for ".clang-format" then "_clang-format" in each
+// directory, so this wraps those 2 caches into one.
+struct ClangFormatCachePair {
+private:
+  SmallString<256> appendDir(StringRef Dir, StringRef Tail) {
+    SmallString<256> Res(Dir);
+    llvm::sys::path::append(Res, Tail);
+    return Res;
+  }
+
+public:
+  ClangFormatCache Dot;
+  ClangFormatCache Underscore;
+
+  explicit ClangFormatCachePair(StringRef Dir, LanguageKind Lang)
+      : Dot(appendDir(Dir, ".clang-format"), Lang),
+        Underscore(appendDir(Dir, "_clang-format"), Lang) {}
+};
+
+class ClangFormatConfigTree {
+  struct CacheMapAndFallback {
+  private:
+    static std::shared_ptr<format::FormatStyle> getFallback(StringRef Fallback,
+                                                            LanguageKind Lang) {
+      auto Result = std::make_shared<format::FormatStyle>(format::getNoStyle());
+      if (!format::getPredefinedStyle(Fallback, Lang, &*Result)) {
+        StringRef PrettyStyle;
+        if (Lang == LanguageKind::LK_Cpp)
+          PrettyStyle = "C/C++";
+        else if (Lang == LanguageKind::LK_ObjC)
+          PrettyStyle = "Objective-C/Objective-C++";
+        else
+          llvm_unreachable("Unsupported language kind");
+        elog("error: Couldn't get fallback style for '{0}', using LLVM Style",
+             PrettyStyle);
+        *Result = format::getLLVMStyle(Lang);
+      }
+      return Result;
+    }
+
+  public:
+    CacheMapAndFallback(StringRef FallbackStyle, LanguageKind Lang)
+        : Fallback(getFallback(FallbackStyle, Lang)) {}
+
+    llvm::StringMap<ClangFormatCachePair> Map;
+    const std::shared_ptr<const format::FormatStyle> Fallback;
+  };
+
+  const ThreadsafeFS &FS;
+  std::chrono::steady_clock::duration MaxStaleness;
+  mutable std::mutex Mu;
+
+  CacheMapAndFallback CPPConfig, ObjCConfig;
+
+public:
+  ClangFormatConfigTree(const ThreadsafeFS &FS, StringRef FallbackStyle)
+      : FS(FS), MaxStaleness(std::chrono::seconds(5)),
+        CPPConfig(FallbackStyle, LanguageKind::LK_Cpp),
+        ObjCConfig(FallbackStyle, LanguageKind::LK_Cpp) {}
+
+  std::shared_ptr<const format::FormatStyle> get(PathRef AbsPath,
+                                                 LanguageKind Lang) {
+    CacheMapAndFallback *MapFallback;
+
+    switch (Lang) {
+    case LanguageKind::LK_Cpp:
+      MapFallback = &CPPConfig;
+      break;
+    case LanguageKind::LK_ObjC:
+      MapFallback = &ObjCConfig;
+      break;
+    default:
+      llvm_unreachable("Unsupported language kind");
+    }
+    namespace path = llvm::sys::path;
+    assert(path::is_absolute(AbsPath));
+
+    // Compute absolute paths to all ancestors (substrings of P.Path).
+    // Ensure cache entries for each ancestor exist in the map.
+    llvm::StringRef Parent = path::parent_path(AbsPath);
+    llvm::SmallVector<ClangFormatCachePair *> Caches;
+    {
+      std::lock_guard<std::mutex> Lock(Mu);
+      for (auto I = path::begin(Parent), E = path::end(Parent); I != E; ++I) {
+        assert(I->end() >= Parent.begin() && I->end() <= Parent.end() &&
+               "Canonical path components should be substrings");
+        llvm::StringRef Ancestor(Parent.begin(), I->end() - Parent.begin());
+#ifdef _WIN32
+        // C:\ is an ancestor, but skip its (relative!) parent C:.
+        if (Ancestor.size() == 2 && Ancestor.back() == ':')
+          continue;
+#endif
+        assert(path::is_absolute(Ancestor));
+
+        auto It = MapFallback->Map.find(Ancestor);
+
+        // Assemble the actual config file path only if needed.
+        if (It == MapFallback->Map.end()) {
+          It = MapFallback->Map.try_emplace(Ancestor, Ancestor, Lang).first;
+        }
+        Caches.push_back(&It->second);
+      }
+    }
+    // Finally query each individual file.
+    // This will take a (per-file) lock for each file that actually exists.
+    std::chrono::steady_clock::time_point FreshTime =
+        std::chrono::steady_clock::now() - MaxStaleness;
+    for (const ClangFormatCachePair *Cache : Caches) {
+      // Check the .clang-format file.
+      if (auto Config = Cache->Dot.get(FS, FreshTime))
+        return Config;
+      // Check the _clang-format file.
+      if (auto Config = Cache->Underscore.get(FS, FreshTime))
+        return Config;
+    }
+    return MapFallback->Fallback;
+  }
+};
+
+} // namespace
+FormatProvider getClangFormatProvider(ThreadsafeFS &TFS,
+                                      StringRef FallbackStyle) {
+  return [Tree = std::make_unique<ClangFormatConfigTree>(TFS, FallbackStyle)](
+             PathRef Filename, StringRef Contents) {
+    LanguageKind Lang = format::guessLanguage(Filename, Contents);
+    return Tree->get(Filename, Lang);
+  };
+}
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/Compiler.h
===================================================================
--- clang-tools-extra/clangd/Compiler.h
+++ clang-tools-extra/clangd/Compiler.h
@@ -15,6 +15,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H
 
+#include "FormatProvider.h"
 #include "GlobalCompilationDatabase.h"
 #include "TidyProvider.h"
 #include "index/Index.h"
@@ -56,6 +57,7 @@
   const SymbolIndex *Index = nullptr;
   ParseOptions Opts = ParseOptions();
   TidyProviderRef ClangTidyProvider = {};
+  FormatProviderRef ClangFormatProvider = {};
 };
 
 /// Builds compiler invocation that could be used to build AST or preamble.
Index: clang-tools-extra/clangd/CodeComplete.cpp
===================================================================
--- clang-tools-extra/clangd/CodeComplete.cpp
+++ clang-tools-extra/clangd/CodeComplete.cpp
@@ -24,6 +24,7 @@
 #include "Diagnostics.h"
 #include "ExpectedTypes.h"
 #include "FileDistance.h"
+#include "FormatProvider.h"
 #include "FuzzyMatch.h"
 #include "Headers.h"
 #include "Hover.h"
@@ -1271,13 +1272,16 @@
   /// set and contains a cached request.
   llvm::Optional<FuzzyFindRequest> SpecReq;
 
+  FormatProviderRef FormatProvider;
+
 public:
   // A CodeCompleteFlow object is only useful for calling run() exactly once.
   CodeCompleteFlow(PathRef FileName, const IncludeStructure &Includes,
                    SpeculativeFuzzyFind *SpecFuzzyFind,
-                   const CodeCompleteOptions &Opts)
+                   const CodeCompleteOptions &Opts,
+                   FormatProviderRef FormatProvider)
       : FileName(FileName), Includes(Includes), SpecFuzzyFind(SpecFuzzyFind),
-        Opts(Opts) {}
+        Opts(Opts), FormatProvider(FormatProvider) {}
 
   CodeCompleteResult run(const SemaCompleteInput &SemaCCInput) && {
     trace::Span Tracer("CodeCompleteFlow");
@@ -1299,9 +1303,8 @@
       assert(Recorder && "Recorder is not set");
       CCContextKind = Recorder->CCContext.getKind();
       IsUsingDeclaration = Recorder->CCContext.isUsingDeclaration();
-      auto Style = getFormatStyleForFile(SemaCCInput.FileName,
-                                         SemaCCInput.ParseInput.Contents,
-                                         *SemaCCInput.ParseInput.TFS);
+      auto Style =
+          FormatProvider(SemaCCInput.FileName, SemaCCInput.ParseInput.Contents);
       const auto NextToken = Lexer::findNextToken(
           Recorder->CCSema->getPreprocessor().getCodeCompletionLoc(),
           Recorder->CCSema->getSourceManager(), Recorder->CCSema->LangOpts);
@@ -1310,7 +1313,7 @@
       // If preprocessor was run, inclusions from preprocessor callback should
       // already be added to Includes.
       Inserter.emplace(
-          SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, Style,
+          SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, *Style,
           SemaCCInput.ParseInput.CompileCommand.Directory,
           &Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
       for (const auto &Inc : Includes.MainFileIncludes)
@@ -1389,12 +1392,12 @@
     ProxSources[FileName].Cost = 0;
     FileProximity.emplace(ProxSources);
 
-    auto Style = getFormatStyleForFile(FileName, Content, TFS);
+    auto Style = FormatProvider(FileName, Content);
     // This will only insert verbatim headers.
-    Inserter.emplace(FileName, Content, Style,
+    Inserter.emplace(FileName, Content, *Style,
                      /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
 
-    auto Identifiers = collectIdentifiers(Content, Style);
+    auto Identifiers = collectIdentifiers(Content, *Style);
     std::vector<RawIdentifier> IdentifierResults;
     for (const auto &IDAndCount : Identifiers) {
       RawIdentifier ID;
@@ -1412,7 +1415,7 @@
     //  - all-scopes query if no qualifier was typed (and it's allowed).
     SpecifiedScope Scopes;
     Scopes.AccessibleScopes = visibleNamespaces(
-        Content.take_front(Offset), format::getFormattingLangOpts(Style));
+        Content.take_front(Offset), format::getFormattingLangOpts(*Style));
     for (std::string &S : Scopes.AccessibleScopes)
       if (!S.empty())
         S.append("::"); // visibleNamespaces doesn't include trailing ::.
@@ -1811,7 +1814,7 @@
   }
   auto Flow = CodeCompleteFlow(
       FileName, Preamble ? Preamble->Includes : IncludeStructure(),
-      SpecFuzzyFind, Opts);
+      SpecFuzzyFind, Opts, ParseInput.ClangFormatProvider);
   return (!Preamble || Opts.RunParser == CodeCompleteOptions::NeverParse)
              ? std::move(Flow).runWithoutSema(ParseInput.Contents, *Offset,
                                               *ParseInput.TFS)
Index: clang-tools-extra/clangd/ClangdServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdServer.h
+++ clang-tools-extra/clangd/ClangdServer.h
@@ -12,6 +12,7 @@
 #include "../clang-tidy/ClangTidyOptions.h"
 #include "CodeComplete.h"
 #include "ConfigProvider.h"
+#include "FormatProvider.h"
 #include "GlobalCompilationDatabase.h"
 #include "Hover.h"
 #include "Protocol.h"
@@ -118,6 +119,8 @@
     /// checks will be disabled.
     TidyProviderRef ClangTidyProvider;
 
+    FormatProviderRef ClangFormatProvider;
+
     /// If true, force -frecovery-ast flag.
     /// If false, respect the value in clang.
     bool BuildRecoveryAST = false;
@@ -383,6 +386,9 @@
   // When set, provides clang-tidy options for a specific file.
   TidyProviderRef ClangTidyProvider;
 
+  // When set, provides clang-format options for a specific file.
+  FormatProviderRef ClangFormatProvider;
+
   // If this is true, suggest include insertion fixes for diagnostic errors that
   // can be caused by missing includes (e.g. member access in incomplete type).
   bool SuggestMissingIncludes = false;
Index: clang-tools-extra/clangd/ClangdServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdServer.cpp
+++ clang-tools-extra/clangd/ClangdServer.cpp
@@ -145,6 +145,7 @@
                                      Opts.CollectMainFileRefs)
                      : nullptr),
       ClangTidyProvider(Opts.ClangTidyProvider),
+      ClangFormatProvider(Opts.ClangFormatProvider),
       SuggestMissingIncludes(Opts.SuggestMissingIncludes),
       BuildRecoveryAST(Opts.BuildRecoveryAST),
       PreserveRecoveryASTType(Opts.PreserveRecoveryASTType),
@@ -213,6 +214,7 @@
   Inputs.Opts = std::move(Opts);
   Inputs.Index = Index;
   Inputs.ClangTidyProvider = ClangTidyProvider;
+  Inputs.ClangFormatProvider = ClangFormatProvider;
   Inputs.Opts.BuildRecoveryAST = BuildRecoveryAST;
   Inputs.Opts.PreserveRecoveryASTType = PreserveRecoveryASTType;
   bool NewFile = WorkScheduler.update(File, Inputs, WantDiags);
@@ -258,6 +260,7 @@
     ParseInput.Index = Index;
     ParseInput.Opts.BuildRecoveryAST = BuildRecoveryAST;
     ParseInput.Opts.PreserveRecoveryASTType = PreserveRecoveryASTType;
+    ParseInput.ClangFormatProvider = ClangFormatProvider;
 
     // FIXME(ibiryukov): even if Preamble is non-null, we may want to check
     // both the old and the new version in case only one of them matches.
@@ -339,11 +342,8 @@
   auto Action = [File = File.str(), Code = Code.str(),
                  TriggerText = TriggerText.str(), CursorPos = *CursorPos,
                  CB = std::move(CB), this]() mutable {
-    auto Style = format::getStyle(format::DefaultFormatStyle, File,
-                                  format::DefaultFallbackStyle, Code,
-                                  TFS.view(/*CWD=*/llvm::None).get());
-    if (!Style)
-      return CB(Style.takeError());
+    auto Style = ClangFormatProvider(File, Code);
+    assert(Style && "Provider should always return a valid style");
 
     std::vector<TextEdit> Result;
     for (const tooling::Replacement &R :
@@ -410,12 +410,11 @@
       return CB(R.takeError());
 
     if (Opts.WantFormat) {
-      auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
-                                         *InpAST->Inputs.TFS);
+      auto Style = ClangFormatProvider(File, InpAST->Inputs.Contents);
       llvm::Error Err = llvm::Error::success();
       for (auto &E : R->GlobalChanges)
-        Err =
-            llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));
+        Err = llvm::joinErrors(reformatEdit(E.getValue(), *Style),
+                               std::move(Err));
 
       if (Err)
         return CB(std::move(Err));
@@ -516,9 +515,8 @@
       // Tweaks don't apply clang-format, do that centrally here.
       for (auto &It : (*Effect)->ApplyEdits) {
         Edit &E = It.second;
-        format::FormatStyle Style =
-            getFormatStyleForFile(File, E.InitialCode, TFS);
-        if (llvm::Error Err = reformatEdit(E, Style))
+        auto Style = ClangFormatProvider(File, E.InitialCode);
+        if (llvm::Error Err = reformatEdit(E, *Style))
           elog("Failed to format {0}: {1}", It.first(), std::move(Err));
       }
     } else {
@@ -567,15 +565,15 @@
   // Call clang-format.
   auto Action = [File = File.str(), Code = Code.str(), Ranges = Ranges.vec(),
                  CB = std::move(CB), this]() mutable {
-    format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS);
+    auto Style = ClangFormatProvider(File, Code);
     tooling::Replacements IncludeReplaces =
-        format::sortIncludes(Style, Code, Ranges, File);
+        format::sortIncludes(*Style, Code, Ranges, File);
     auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
     if (!Changed)
       return CB(Changed.takeError());
 
     CB(IncludeReplaces.merge(format::reformat(
-        Style, *Changed,
+        *Style, *Changed,
         tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
         File)));
   };
@@ -601,9 +599,8 @@
                  this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());
-    format::FormatStyle Style = getFormatStyleForFile(
-        File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS);
-    CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));
+    auto Style = ClangFormatProvider(File, InpAST->Inputs.Contents);
+    CB(clangd::getHover(InpAST->AST, Pos, Style, Index));
   };
 
   WorkScheduler.runWithAST("Hover", File, std::move(Action),
Index: clang-tools-extra/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/CMakeLists.txt
+++ clang-tools-extra/clangd/CMakeLists.txt
@@ -65,6 +65,7 @@
   FindTarget.cpp
   FileDistance.cpp
   Format.cpp
+  FormatProvider.cpp
   FS.cpp
   FuzzyMatch.cpp
   GlobalCompilationDatabase.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to