diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -66,6 +66,7 @@ FindTarget.cpp FileDistance.cpp Format.cpp + FormatProvider.cpp FS.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/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 "Module.h" @@ -118,6 +119,8 @@ /// checks will be disabled. TidyProviderRef ClangTidyProvider; + FormatProviderRef ClangFormatProvider; + /// Clangd's workspace root. Relevant for "workspace" operations not bound /// to a particular file. /// FIXME: If not set, should use the current working directory. @@ -367,6 +370,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; + // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex) llvm::StringMap> CachedCompletionFuzzyFindRequestByFile; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -12,6 +12,7 @@ #include "DumpAST.h" #include "FindSymbols.h" #include "Format.h" +#include "FormatProvider.h" #include "HeaderSourceSwitch.h" #include "Headers.h" #include "ParsedAST.h" @@ -111,6 +112,7 @@ Opts.UpdateDebounce = DebouncePolicy::fixed(/*zero*/ {}); Opts.StorePreamblesInMemory = true; Opts.AsyncThreadsCount = 4; // Consistent! + Opts.ClangFormatProvider = formatFallbackProvider; return Opts; } @@ -130,6 +132,7 @@ : Modules(Opts.Modules), CDB(CDB), TFS(TFS), DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr), ClangTidyProvider(Opts.ClangTidyProvider), + ClangFormatProvider(Opts.ClangFormatProvider), WorkspaceRoot(Opts.WorkspaceRoot), // Pass a callback into `WorkScheduler` to extract symbols from a newly // parsed file and rebuild the file index synchronously each time an AST @@ -193,6 +196,7 @@ Inputs.Opts = std::move(Opts); Inputs.Index = Index; Inputs.ClangTidyProvider = ClangTidyProvider; + Inputs.ClangFormatProvider = ClangFormatProvider; bool NewFile = WorkScheduler.update(File, Inputs, WantDiags); // If we loaded Foo.h, we want to make sure Foo.cpp is indexed. if (NewFile && BackgroundIdx) @@ -308,6 +312,8 @@ ParseInputs ParseInput{IP->Command, &TFS, IP->Contents.str()}; ParseInput.Index = Index; + ParseInput.ClangFormatProvider = ClangFormatProvider; + CodeCompleteOpts.MainFileSignals = IP->Signals; // 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. @@ -387,11 +393,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 Result; for (const tooling::Replacement &R : @@ -453,12 +456,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)); @@ -559,9 +561,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 { @@ -610,15 +611,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))); }; @@ -644,9 +645,8 @@ this](llvm::Expected 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), diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/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" @@ -1313,13 +1314,16 @@ /// set and contains a cached request. llvm::Optional 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"); @@ -1341,9 +1345,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); @@ -1352,7 +1355,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) @@ -1431,12 +1434,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 IdentifierResults; for (const auto &IDAndCount : Identifiers) { RawIdentifier ID; @@ -1454,7 +1457,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 ::. @@ -1855,7 +1858,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) diff --git a/clang-tools-extra/clangd/Compiler.h b/clang-tools-extra/clangd/Compiler.h --- a/clang-tools-extra/clangd/Compiler.h +++ b/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" @@ -54,6 +55,7 @@ const SymbolIndex *Index = nullptr; ParseOptions Opts = ParseOptions(); TidyProviderRef ClangTidyProvider = {}; + FormatProviderRef ClangFormatProvider = {}; }; /// Builds compiler invocation that could be used to build AST or preamble. diff --git a/clang-tools-extra/clangd/FormatProvider.h b/clang-tools-extra/clangd/FormatProvider.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/FormatProvider.h @@ -0,0 +1,36 @@ +//===--- 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( + PathRef, llvm::StringRef) const>; +using FormatProviderRef = + llvm::function_ref( + PathRef, llvm::StringRef)>; + +/// A Fallback style provider mainly designed for tests, just returns the LLVM +/// style for the detected langauge. +std::shared_ptr +formatFallbackProvider(PathRef Filename, llvm::StringRef Contents); + +FormatProvider getClangFormatProvider(ThreadsafeFS &TFS, + StringRef FallbackStyle); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_FORMATPROVIDER_H diff --git a/clang-tools-extra/clangd/FormatProvider.cpp b/clang-tools-extra/clangd/FormatProvider.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/FormatProvider.cpp @@ -0,0 +1,288 @@ +//===--- 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 "support/Path.h" +#include "support/ThreadsafeFS.h" +#include "clang/Format/Format.h" +#include "llvm/Support/DJB.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/MemoryBufferRef.h" +#include + +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 Style; + const LanguageKind Lang; + + // Super hacky non secure way to hold a pseudorandom ID for each version of + // the file. It gets incremented when the file config changes. + mutable unsigned ID; + // If this was inherited from a parent, this tracks the ID of the parent. + mutable unsigned ParentID = 0; + // If this config inherits from a parent, we need to cache the unparsed + // contents due to how clang-format configs apply atop eachother. + mutable std::string FileContents; + + bool update(const ThreadsafeFS &TFS, + std::chrono::steady_clock::time_point FreshTime) const { + read( + TFS, FreshTime, + [this](llvm::Optional Data) { + // This code is guarded by FileCache::Mu so no need to try(and fail) + // to get the lock again. + // Clear any stale config if one exists. + Style.reset(); + + FileContents.clear(); + ParentID = 0; + if (!Data || Data->empty()) + return; + vlog("Format: Parsing config file {0}", path()); + format::FormatStyle NewStyle = format::getLLVMStyle(Lang); + if (auto EC = format::parseConfiguration( + llvm::MemoryBufferRef(*Data, path()), &NewStyle, + /*AllowUnknownOptions=*/true)) { + elog("clang-format style parsing error: {0}", EC.message()); + return; + } + if (NewStyle.InheritsParentConfig) { + // If we are inheriting, Theres no need to store the value as its + // meaningless, instead we cache the unparsed contents and use that + // later to parse the configuration atop a parent configuration. + FileContents = Data->str(); + return; + } + ++ID; + Style = std::make_shared(std::move(NewStyle)); + }, + []() {}); + return Style != nullptr || !FileContents.empty(); + } + +public: + ClangFormatCache(PathRef Path, LanguageKind Lang) + : FileCache(Path), Lang(Lang), ID(llvm::djbHash(Path)) {} + + bool hasValue(const ThreadsafeFS &TFS, + std::chrono::steady_clock::time_point FreshTime) const { + return update(TFS, FreshTime); + } + + bool needsParent() const { return !FileContents.empty(); } + + /// Applies the config stored in here on top of the config in \p Parent. + void rebaseFromParent(const ClangFormatCache &Parent) const { + assert(Parent.Style != nullptr); + assert(&Parent != this); + std::shared_ptr PStyle; + unsigned int PId; + { + // Lock the parent while we fetch its version and contents. + std::lock_guard ParentGuard(Parent.Mu); + PStyle = Parent.Style; + PId = Parent.ID; + } + std::lock_guard Guard(this->Mu); + assert(!FileContents.empty()); + // This is already based on the parent + if (Parent.ID == ParentID) { + assert(Style != nullptr); + return; + } + Style = std::make_shared(*PStyle); + if (auto EC = format::parseConfiguration( + llvm::MemoryBufferRef(FileContents, path()), &*Style, + /*AllowUnknownOptions=*/true)) + llvm_unreachable("File contents should be valid"); + Style->InheritsParentConfig = false; + ParentID = Parent.ID; + ++ID; + } + + std::shared_ptr getCachedValue() const { + std::lock_guard Guard(this->Mu); + assert(Style); + return Style; + } +}; + +// Clang-Format checks for ".clang-format" then "_clang-format" in each +// directory, so this wraps those 2 caches into one. +class ClangFormatDirectoryCache { +private: + SmallString<256> appendDir(StringRef Dir, StringRef Tail) { + SmallString<256> Res(Dir); + llvm::sys::path::append(Res, Tail); + return Res; + } + ClangFormatCache Items[2]; + +public: + explicit ClangFormatDirectoryCache(StringRef Dir, LanguageKind Lang) + : Items{{appendDir(Dir, ".clang-format"), Lang}, + {appendDir(Dir, "._clang-format"), Lang}} {} + + const ClangFormatCache * + getActiveCache(const ThreadsafeFS &TFS, + std::chrono::steady_clock::time_point FreshTime) const { + for (const ClangFormatCache &Item : Items) { + if (Item.hasValue(TFS, FreshTime)) + return &Item; + } + return nullptr; + } +}; + +// Bundles format file caches along with a fallback style, should no format file +// be found. +class CacheMapAndFallback { +public: + CacheMapAndFallback(StringRef FallbackStyle, LanguageKind Lang) + : Fallback(getFallbackStyle(FallbackStyle, Lang)), Lang(Lang) {} + + ClangFormatDirectoryCache &getAncestorCache(PathRef Ancestor) { + return Map.try_emplace(Ancestor, Ancestor, Lang).first->getValue(); + } + + std::shared_ptr getFallbackStyle() const { + return Fallback; + } + +private: + static std::shared_ptr + getFallbackStyle(StringRef FallbackStyle, LanguageKind Lang) { + auto Result = std::make_shared(format::getNoStyle()); + if (!format::getPredefinedStyle(FallbackStyle, Lang, &*Result)) { + StringRef PrettyStyle; + switch (Lang) { + case LanguageKind::LK_Cpp: + PrettyStyle = "C/C++"; + break; + case LanguageKind::LK_ObjC: + PrettyStyle = "Objective-C/Objective-C++"; + break; + default: + llvm_unreachable("Unsupported language kind"); + } + + elog("error: Couldn't get fallback style for '{0}', using LLVM Style", + PrettyStyle); + *Result = format::getLLVMStyle(Lang); + } + return Result; + } + llvm::StringMap Map; + const std::shared_ptr Fallback; + const LanguageKind Lang; +}; + +class ClangFormatConfigTree { + const ThreadsafeFS &FS; + std::chrono::steady_clock::duration MaxStaleness; + mutable std::mutex Mu; + + CacheMapAndFallback CPPConfig, ObjCConfig; + + CacheMapAndFallback &getMapForStyle(LanguageKind Lang) { + switch (Lang) { + case LanguageKind::LK_Cpp: + return CPPConfig; + case LanguageKind::LK_ObjC: + return ObjCConfig; + default: + llvm_unreachable("Unsupported language kind"); + } + } + +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 get(PathRef AbsPath, + LanguageKind Lang) { + auto &MapFallback = getMapForStyle(Lang); + + 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 Caches; + { + std::lock_guard Lock(Mu); + for (auto I = path::rbegin(Parent), E = path::rend(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)); + Caches.push_back(&MapFallback.getAncestorCache(Ancestor)); + } + } + std::chrono::steady_clock::time_point FreshTime = + std::chrono::steady_clock::now() - MaxStaleness; + llvm::SmallVector Stack; + for (const ClangFormatDirectoryCache *CachePair : Caches) { + if (const auto *Cache = CachePair->getActiveCache(FS, FreshTime)) { + Stack.push_back(Cache); + if (!Cache->needsParent()) + break; + } + } + if (Stack.empty() || Stack.back()->needsParent()) + return MapFallback.getFallbackStyle(); + for (auto I = Stack.rbegin(), E = std::prev(Stack.rend()); I != E; ++I) + (*std::next(I))->rebaseFromParent(**I); + return Stack.front()->getCachedValue(); + } +}; + +} // namespace + +FormatProvider getClangFormatProvider(ThreadsafeFS &TFS, + StringRef FallbackStyle) { + return [Tree = std::make_unique(TFS, FallbackStyle)]( + PathRef Filename, StringRef Contents) { + LanguageKind Lang = format::guessLanguage(Filename, Contents); + return Tree->get(Filename, Lang); + }; +} + +std::shared_ptr +formatFallbackProvider(PathRef Filename, llvm::StringRef Contents) { + LanguageKind Lang = format::guessLanguage(Filename, Contents); + if (Lang == LanguageKind::LK_Cpp) { + static const auto Result = std::make_shared( + format::getLLVMStyle(LanguageKind::LK_Cpp)); + return Result; + } + if (Lang == LanguageKind::LK_ObjC) { + static const auto Result = std::make_shared( + format::getLLVMStyle(LanguageKind::LK_ObjC)); + return Result; + } + llvm_unreachable("Unsupported language kind"); +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -115,7 +115,7 @@ /// Get the hover information when hovering at \p Pos. llvm::Optional getHover(ParsedAST &AST, Position Pos, - format::FormatStyle Style, + const format::FormatStyle &Style, const SymbolIndex *Index); } // namespace clangd diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -844,7 +844,7 @@ } // namespace llvm::Optional getHover(ParsedAST &AST, Position Pos, - format::FormatStyle Style, + const format::FormatStyle &Style, const SymbolIndex *Index) { PrintingPolicy PP = getPrintingPolicy(AST.getASTContext().getPrintingPolicy()); diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -359,9 +359,9 @@ llvm::Optional FixIncludes; auto BuildDir = VFS->getCurrentWorkingDirectory(); if (Inputs.Index && !BuildDir.getError()) { - auto Style = getFormatStyleForFile(Filename, Inputs.Contents, *Inputs.TFS); + auto Style = Inputs.ClangFormatProvider(Filename, Inputs.Contents); auto Inserter = std::make_shared( - Filename, Inputs.Contents, Style, BuildDir.get(), + Filename, Inputs.Contents, *Style, BuildDir.get(), &Clang->getPreprocessor().getHeaderSearchInfo()); if (Preamble) { for (const auto &Inc : Preamble->Includes.MainFileIncludes) diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -161,15 +161,6 @@ llvm::Optional 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 cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces, diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/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 cleanupAndFormat(StringRef Code, const tooling::Replacements &Replaces, const format::FormatStyle &Style) { diff --git a/clang-tools-extra/clangd/support/FileCache.h b/clang-tools-extra/clangd/support/FileCache.h --- a/clang-tools-extra/clangd/support/FileCache.h +++ b/clang-tools-extra/clangd/support/FileCache.h @@ -67,12 +67,14 @@ std::string Path; // Members are mutable so read() can present a const interface. // (It is threadsafe and approximates read-through to TFS). - mutable std::mutex Mu; // Time when the cache was known valid (reflected disk state). mutable std::chrono::steady_clock::time_point ValidTime; // Filesystem metadata corresponding to the currently cached data. mutable llvm::sys::TimePoint<> ModifiedTime; mutable uint64_t Size; + +protected: + mutable std::mutex Mu; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp --- a/clang-tools-extra/clangd/tool/Check.cpp +++ b/clang-tools-extra/clangd/tool/Check.cpp @@ -76,7 +76,7 @@ // from buildInvocation ParseInputs Inputs; std::unique_ptr Invocation; - format::FormatStyle Style; + std::shared_ptr Style; // from buildAST std::shared_ptr Preamble; llvm::Optional AST; @@ -135,6 +135,7 @@ return false; } } + Inputs.ClangFormatProvider = Opts.ClangFormatProvider; log("Parsing command..."); Invocation = buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args); @@ -148,7 +149,7 @@ // FIXME: Check that resource-dir/built-in-headers exist? - Style = getFormatStyleForFile(File, Inputs.Contents, TFS); + Style = Inputs.ClangFormatProvider(File, Inputs.Contents); return true; } @@ -216,7 +217,7 @@ unsigned Definitions = locateSymbolAt(*AST, Pos, &Index).size(); vlog(" definition: {0}", Definitions); - auto Hover = getHover(*AST, Pos, Style, &Index); + auto Hover = getHover(*AST, Pos, *Style, &Index); vlog(" hover: {0}", Hover.hasValue()); // FIXME: it'd be nice to include code completion, but it's too slow. diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/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" @@ -824,6 +825,9 @@ ClangTidyOptProvider = combine(std::move(Providers)); Opts.ClangTidyProvider = ClangTidyOptProvider; } + FormatProvider ClangFormatOptProvider = + getClangFormatProvider(TFS, FallbackStyle); + Opts.ClangFormatProvider = ClangFormatOptProvider; Opts.QueryDriverGlobs = std::move(QueryDriverGlobs); Opts.TweakFilter = [&](const Tweak &T) { if (T.hidden() && !HiddenFeatures) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -11,6 +11,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" #include "Compiler.h" +#include "FormatProvider.h" #include "Matchers.h" #include "Protocol.h" #include "Quality.h" @@ -154,6 +155,7 @@ MockFS FS; Annotations Test(Text); ParseInputs ParseInput{tooling::CompileCommand(), &FS, Test.code().str()}; + ParseInput.ClangFormatProvider = formatFallbackProvider; return codeComplete(FilePath, Test.point(), /*Preamble=*/nullptr, ParseInput, Opts); } diff --git a/clang-tools-extra/clangd/unittests/FormatTests.cpp b/clang-tools-extra/clangd/unittests/FormatTests.cpp --- a/clang-tools-extra/clangd/unittests/FormatTests.cpp +++ b/clang-tools-extra/clangd/unittests/FormatTests.cpp @@ -303,6 +303,64 @@ )cpp"); } +TEST(FormatProvider, NestedDirectories) { + MockFS FS; + FS.Files[testPath("project/.clang-format")] = R"yaml( + BasedOnStyle: llvm +)yaml"; + FS.Files[testPath("project/test/.clang-format")] = R"yaml( + BasedOnStyle: llvm + ColumnLimit: 0 +)yaml"; + FS.Files[testPath("project/test/sub1/.clang-format")] = R"yaml( + BasedOnStyle: InheritParentConfig + UseTab: Always +)yaml"; + FS.Files[testPath("project/test/sub1/sub2/.clang-format")] = R"yaml( + BasedOnStyle: InheritParentConfig + AccessModifierOffset: -1 +)yaml"; + + auto Provider = getClangFormatProvider(FS, "google"); + auto LLVMStyle = format::getLLVMStyle(format::FormatStyle::LK_Cpp); + + EXPECT_EQ(*Provider(testPath("project/File.cpp"), ""), LLVMStyle); + + LLVMStyle.ColumnLimit = 0U; + EXPECT_EQ(*Provider(testPath("project/test/File.cpp"), ""), LLVMStyle); + + LLVMStyle.UseTab = format::FormatStyle::UT_Always; + EXPECT_EQ(*Provider(testPath("project/test/sub1/File.cpp"), ""), LLVMStyle); + + auto DefAccess = LLVMStyle.AccessModifierOffset; + LLVMStyle.AccessModifierOffset = -1; + EXPECT_EQ(*Provider(testPath("project/test/sub1/sub2/File.cpp"), ""), + LLVMStyle); + LLVMStyle.AccessModifierOffset = DefAccess; + + // FS reads can be stale by 5 seconds, so wait to force re polling when we + // have updated the files. + std::this_thread::sleep_for(std::chrono::seconds(6)); + + FS.Files[testPath("project/test/sub1/.clang-format")] = R"yaml( + BasedOnStyle: InheritParentConfig + UseTab: ForContinuationAndIndentation +)yaml"; + ++FS.Timestamps[testPath("project/test/sub1/.clang-format")]; + + LLVMStyle.UseTab = format::FormatStyle::UT_ForContinuationAndIndentation; + EXPECT_EQ(*Provider(testPath("project/test/sub1/File.cpp"), ""), LLVMStyle); + + LLVMStyle.AccessModifierOffset = -1; + EXPECT_EQ(*Provider(testPath("project/test/sub1/sub2/File.cpp"), ""), + LLVMStyle); + + // This shouldn't find a config file and instead fallback to using google + // style. + EXPECT_EQ(*Provider(testPath("File.cpp"), ""), + format::getGoogleStyle(format::FormatStyle::LK_Cpp)); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TestTU.h b/clang-tools-extra/clangd/unittests/TestTU.h --- a/clang-tools-extra/clangd/unittests/TestTU.h +++ b/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 ExtraArgs; TidyProvider ClangTidyProvider = {}; + + mutable FormatProvider ClangFormatProvider = formatFallbackProvider; // Index to use when building AST. const SymbolIndex *ExternalIndex = nullptr; diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/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; return Inputs; }