brintos

brintos / llvm-project-archived public Read only

0
0
Text · 45.8 KiB · ac1e9aa Raw
1193 lines · cpp
1//===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===-------------------------------------------------------------------===//8 9#include "ClangdServer.h"10#include "CodeComplete.h"11#include "Config.h"12#include "Diagnostics.h"13#include "DumpAST.h"14#include "FindSymbols.h"15#include "Format.h"16#include "HeaderSourceSwitch.h"17#include "InlayHints.h"18#include "ParsedAST.h"19#include "Preamble.h"20#include "Protocol.h"21#include "SemanticHighlighting.h"22#include "SemanticSelection.h"23#include "SourceCode.h"24#include "TUScheduler.h"25#include "XRefs.h"26#include "clang-include-cleaner/Record.h"27#include "index/FileIndex.h"28#include "index/Merge.h"29#include "index/StdLib.h"30#include "refactor/Rename.h"31#include "refactor/Tweak.h"32#include "support/Cancellation.h"33#include "support/Context.h"34#include "support/Logger.h"35#include "support/MemoryTree.h"36#include "support/ThreadsafeFS.h"37#include "support/Trace.h"38#include "clang/Basic/Stack.h"39#include "clang/Format/Format.h"40#include "clang/Lex/Preprocessor.h"41#include "clang/Tooling/CompilationDatabase.h"42#include "clang/Tooling/Core/Replacement.h"43#include "llvm/ADT/ArrayRef.h"44#include "llvm/ADT/STLExtras.h"45#include "llvm/ADT/StringRef.h"46#include "llvm/Support/Error.h"47#include "llvm/Support/Path.h"48#include "llvm/Support/raw_ostream.h"49#include <algorithm>50#include <chrono>51#include <future>52#include <memory>53#include <mutex>54#include <optional>55#include <string>56#include <type_traits>57#include <utility>58#include <vector>59 60namespace clang {61namespace clangd {62namespace {63 64// Tracks number of times a tweak has been offered.65static constexpr trace::Metric TweakAvailable(66    "tweak_available", trace::Metric::Counter, "tweak_id");67 68// Update the FileIndex with new ASTs and plumb the diagnostics responses.69struct UpdateIndexCallbacks : public ParsingCallbacks {70  UpdateIndexCallbacks(FileIndex *FIndex,71                       ClangdServer::Callbacks *ServerCallbacks,72                       const ThreadsafeFS &TFS, AsyncTaskRunner *Tasks,73                       bool CollectInactiveRegions)74      : FIndex(FIndex), ServerCallbacks(ServerCallbacks), TFS(TFS),75        Stdlib{std::make_shared<StdLibSet>()}, Tasks(Tasks),76        CollectInactiveRegions(CollectInactiveRegions) {}77 78  void onPreambleAST(79      PathRef Path, llvm::StringRef Version, CapturedASTCtx ASTCtx,80      std::shared_ptr<const include_cleaner::PragmaIncludes> PI) override {81 82    if (!FIndex)83      return;84 85    auto &PP = ASTCtx.getPreprocessor();86    auto &CI = ASTCtx.getCompilerInvocation();87    if (auto Loc = Stdlib->add(CI.getLangOpts(), PP.getHeaderSearchInfo()))88      indexStdlib(CI, std::move(*Loc));89 90    // FIndex outlives the UpdateIndexCallbacks.91    auto Task = [FIndex(FIndex), Path(Path.str()), Version(Version.str()),92                 ASTCtx(std::move(ASTCtx)), PI(std::move(PI))]() mutable {93      trace::Span Tracer("PreambleIndexing");94      FIndex->updatePreamble(Path, Version, ASTCtx.getASTContext(),95                             ASTCtx.getPreprocessor(), *PI);96    };97 98    if (Tasks) {99      Tasks->runAsync("Preamble indexing for:" + Path + Version,100                      std::move(Task));101    } else102      Task();103  }104 105  void indexStdlib(const CompilerInvocation &CI, StdLibLocation Loc) {106    // This task is owned by Tasks, which outlives the TUScheduler and107    // therefore the UpdateIndexCallbacks.108    // We must be careful that the references we capture outlive TUScheduler.109    auto Task = [LO(CI.getLangOpts()), Loc(std::move(Loc)),110                 CI(std::make_unique<CompilerInvocation>(CI)),111                 // External values that outlive ClangdServer112                 TFS(&TFS),113                 // Index outlives TUScheduler (declared first)114                 FIndex(FIndex),115                 // shared_ptr extends lifetime116                 Stdlib(Stdlib),117                 // We have some FS implementations that rely on information in118                 // the context.119                 Ctx(Context::current().clone())]() mutable {120      // Make sure we install the context into current thread.121      WithContext C(std::move(Ctx));122      clang::noteBottomOfStack();123      IndexFileIn IF;124      IF.Symbols = indexStandardLibrary(std::move(CI), Loc, *TFS);125      if (Stdlib->isBest(LO))126        FIndex->updatePreamble(std::move(IF));127    };128    if (Tasks)129      // This doesn't have a semaphore to enforce -j, but it's rare.130      Tasks->runAsync("IndexStdlib", std::move(Task));131    else132      Task();133  }134 135  void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override {136    if (FIndex)137      FIndex->updateMain(Path, AST);138 139    if (ServerCallbacks)140      Publish([&]() {141        ServerCallbacks->onDiagnosticsReady(Path, AST.version(),142                                            AST.getDiagnostics());143        if (CollectInactiveRegions) {144          ServerCallbacks->onInactiveRegionsReady(Path,145                                                  getInactiveRegions(AST));146        }147      });148  }149 150  void onFailedAST(PathRef Path, llvm::StringRef Version,151                   std::vector<Diag> Diags, PublishFn Publish) override {152    if (ServerCallbacks)153      Publish(154          [&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); });155  }156 157  void onFileUpdated(PathRef File, const TUStatus &Status) override {158    if (ServerCallbacks)159      ServerCallbacks->onFileUpdated(File, Status);160  }161 162  void onPreamblePublished(PathRef File) override {163    if (ServerCallbacks)164      ServerCallbacks->onSemanticsMaybeChanged(File);165  }166 167private:168  FileIndex *FIndex;169  ClangdServer::Callbacks *ServerCallbacks;170  const ThreadsafeFS &TFS;171  std::shared_ptr<StdLibSet> Stdlib;172  AsyncTaskRunner *Tasks;173  bool CollectInactiveRegions;174};175 176class DraftStoreFS : public ThreadsafeFS {177public:178  DraftStoreFS(const ThreadsafeFS &Base, const DraftStore &Drafts)179      : Base(Base), DirtyFiles(Drafts) {}180 181private:182  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {183    auto OFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(184        Base.view(std::nullopt));185    OFS->pushOverlay(DirtyFiles.asVFS());186    return OFS;187  }188 189  const ThreadsafeFS &Base;190  const DraftStore &DirtyFiles;191};192 193} // namespace194 195ClangdServer::Options ClangdServer::optsForTest() {196  ClangdServer::Options Opts;197  Opts.UpdateDebounce = DebouncePolicy::fixed(/*zero*/ {});198  Opts.StorePreamblesInMemory = true;199  Opts.AsyncThreadsCount = 4; // Consistent!200  return Opts;201}202 203ClangdServer::Options::operator TUScheduler::Options() const {204  TUScheduler::Options Opts;205  Opts.AsyncThreadsCount = AsyncThreadsCount;206  Opts.RetentionPolicy = RetentionPolicy;207  Opts.StorePreamblesInMemory = StorePreamblesInMemory;208  Opts.UpdateDebounce = UpdateDebounce;209  Opts.ContextProvider = ContextProvider;210  Opts.PreambleThrottler = PreambleThrottler;211  return Opts;212}213 214ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,215                           const ThreadsafeFS &TFS, const Options &Opts,216                           Callbacks *Callbacks)217    : FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),218      DynamicIdx(Opts.BuildDynamicSymbolIndex219                     ? new FileIndex(Opts.EnableOutgoingCalls)220                     : nullptr),221      ModulesManager(Opts.ModulesManager),222      ClangTidyProvider(Opts.ClangTidyProvider),223      UseDirtyHeaders(Opts.UseDirtyHeaders),224      LineFoldingOnly(Opts.LineFoldingOnly),225      PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),226      ImportInsertions(Opts.ImportInsertions),227      PublishInactiveRegions(Opts.PublishInactiveRegions),228      WorkspaceRoot(Opts.WorkspaceRoot),229      Transient(Opts.ImplicitCancellation ? TUScheduler::InvalidateOnUpdate230                                          : TUScheduler::NoInvalidation),231      DirtyFS(std::make_unique<DraftStoreFS>(TFS, DraftMgr)) {232  if (Opts.AsyncThreadsCount != 0)233    IndexTasks.emplace();234  // Pass a callback into `WorkScheduler` to extract symbols from a newly235  // parsed file and rebuild the file index synchronously each time an AST236  // is parsed.237  WorkScheduler.emplace(CDB, TUScheduler::Options(Opts),238                        std::make_unique<UpdateIndexCallbacks>(239                            DynamicIdx.get(), Callbacks, TFS,240                            IndexTasks ? &*IndexTasks : nullptr,241                            PublishInactiveRegions));242  // Adds an index to the stack, at higher priority than existing indexes.243  auto AddIndex = [&](SymbolIndex *Idx) {244    if (this->Index != nullptr) {245      MergedIdx.push_back(std::make_unique<MergedIndex>(Idx, this->Index));246      this->Index = MergedIdx.back().get();247    } else {248      this->Index = Idx;249    }250  };251  if (Opts.StaticIndex)252    AddIndex(Opts.StaticIndex);253  if (Opts.BackgroundIndex) {254    BackgroundIndex::Options BGOpts;255    BGOpts.ThreadPoolSize = std::max(Opts.AsyncThreadsCount, 1u);256    BGOpts.OnProgress = [Callbacks](BackgroundQueue::Stats S) {257      if (Callbacks)258        Callbacks->onBackgroundIndexProgress(S);259    };260    BGOpts.ContextProvider = Opts.ContextProvider;261    BGOpts.SupportContainedRefs = Opts.EnableOutgoingCalls;262    BackgroundIdx = std::make_unique<BackgroundIndex>(263        TFS, CDB,264        BackgroundIndexStorage::createDiskBackedStorageFactory(265            [&CDB](llvm::StringRef File) { return CDB.getProjectInfo(File); }),266        std::move(BGOpts));267    AddIndex(BackgroundIdx.get());268  }269  if (DynamicIdx)270    AddIndex(DynamicIdx.get());271 272  if (Opts.FeatureModules) {273    FeatureModule::Facilities F{274        *this->WorkScheduler,275        this->Index,276        this->TFS,277    };278    for (auto &Mod : *Opts.FeatureModules)279      Mod.initialize(F);280  }281}282 283ClangdServer::~ClangdServer() {284  // Destroying TUScheduler first shuts down request threads that might285  // otherwise access members concurrently.286  // (Nobody can be using TUScheduler because we're on the main thread).287  WorkScheduler.reset();288  // Now requests have stopped, we can shut down feature modules.289  if (FeatureModules) {290    for (auto &Mod : *FeatureModules)291      Mod.stop();292    for (auto &Mod : *FeatureModules)293      Mod.blockUntilIdle(Deadline::infinity());294  }295}296 297void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,298                               llvm::StringRef Version,299                               WantDiagnostics WantDiags, bool ForceRebuild) {300  std::string ActualVersion = DraftMgr.addDraft(File, Version, Contents);301  ParseOptions Opts;302  Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;303  Opts.ImportInsertions = ImportInsertions;304 305  // Compile command is set asynchronously during update, as it can be slow.306  ParseInputs Inputs;307  Inputs.TFS = &getHeaderFS();308  Inputs.Contents = std::string(Contents);309  Inputs.Version = std::move(ActualVersion);310  Inputs.ForceRebuild = ForceRebuild;311  Inputs.Opts = std::move(Opts);312  Inputs.Index = Index;313  Inputs.ClangTidyProvider = ClangTidyProvider;314  Inputs.FeatureModules = FeatureModules;315  Inputs.ModulesManager = ModulesManager;316  bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);317  // If we loaded Foo.h, we want to make sure Foo.cpp is indexed.318  if (NewFile && BackgroundIdx)319    BackgroundIdx->boostRelated(File);320}321 322void ClangdServer::reparseOpenFilesIfNeeded(323    llvm::function_ref<bool(llvm::StringRef File)> Filter) {324  // Reparse only opened files that were modified.325  for (const Path &FilePath : DraftMgr.getActiveFiles())326    if (Filter(FilePath))327      if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?328        addDocument(FilePath, *Draft->Contents, Draft->Version,329                    WantDiagnostics::Auto);330}331 332std::shared_ptr<const std::string> ClangdServer::getDraft(PathRef File) const {333  auto Draft = DraftMgr.getDraft(File);334  if (!Draft)335    return nullptr;336  return std::move(Draft->Contents);337}338 339std::function<Context(PathRef)>340ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,341                                              Callbacks *Publish) {342  if (!Provider)343    return [](llvm::StringRef) { return Context::current().clone(); };344 345  struct Impl {346    const config::Provider *Provider;347    ClangdServer::Callbacks *Publish;348    std::mutex PublishMu;349 350    Impl(const config::Provider *Provider, ClangdServer::Callbacks *Publish)351        : Provider(Provider), Publish(Publish) {}352 353    Context operator()(llvm::StringRef File) {354      config::Params Params;355      // Don't reread config files excessively often.356      // FIXME: when we see a config file change event, use the event timestamp?357      Params.FreshTime =358          std::chrono::steady_clock::now() - std::chrono::seconds(5);359      llvm::SmallString<256> PosixPath;360      if (!File.empty()) {361        assert(llvm::sys::path::is_absolute(File));362        llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);363        Params.Path = PosixPath.str();364      }365 366      llvm::StringMap<std::vector<Diag>> ReportableDiagnostics;367      Config C = Provider->getConfig(Params, [&](const llvm::SMDiagnostic &D) {368        // Create the map entry even for note diagnostics we don't report.369        // This means that when the file is parsed with no warnings, we370        // publish an empty set of diagnostics, clearing any the client has.371        handleDiagnostic(D, !Publish || D.getFilename().empty()372                                ? nullptr373                                : &ReportableDiagnostics[D.getFilename()]);374      });375      // Blindly publish diagnostics for the (unopened) parsed config files.376      // We must avoid reporting diagnostics for *the same file* concurrently.377      // Source diags are published elsewhere, but those are different files.378      if (!ReportableDiagnostics.empty()) {379        std::lock_guard<std::mutex> Lock(PublishMu);380        for (auto &Entry : ReportableDiagnostics)381          Publish->onDiagnosticsReady(Entry.first(), /*Version=*/"",382                                      Entry.second);383      }384      return Context::current().derive(Config::Key, std::move(C));385    }386 387    void handleDiagnostic(const llvm::SMDiagnostic &D,388                          std::vector<Diag> *ClientDiagnostics) {389      switch (D.getKind()) {390      case llvm::SourceMgr::DK_Error:391        elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),392             D.getColumnNo(), D.getMessage());393        break;394      case llvm::SourceMgr::DK_Warning:395        log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),396            D.getLineNo(), D.getColumnNo(), D.getMessage());397        break;398      case llvm::SourceMgr::DK_Note:399      case llvm::SourceMgr::DK_Remark:400        vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),401             D.getColumnNo(), D.getMessage());402        ClientDiagnostics = nullptr; // Don't emit notes as LSP diagnostics.403        break;404      }405      if (ClientDiagnostics)406        ClientDiagnostics->push_back(toDiag(D, Diag::ClangdConfig));407    }408  };409 410  // Copyable wrapper.411  return [I(std::make_shared<Impl>(Provider, Publish))](llvm::StringRef Path) {412    return (*I)(Path);413  };414}415 416void ClangdServer::removeDocument(PathRef File) {417  DraftMgr.removeDraft(File);418  WorkScheduler->remove(File);419}420 421void ClangdServer::codeComplete(PathRef File, Position Pos,422                                const clangd::CodeCompleteOptions &Opts,423                                Callback<CodeCompleteResult> CB) {424  // Copy completion options for passing them to async task handler.425  auto CodeCompleteOpts = Opts;426  if (!CodeCompleteOpts.Index) // Respect overridden index.427    CodeCompleteOpts.Index = Index;428 429  auto Task = [Pos, CodeCompleteOpts, File = File.str(), CB = std::move(CB),430               this](llvm::Expected<InputsAndPreamble> IP) mutable {431    if (!IP)432      return CB(IP.takeError());433    if (auto Reason = isCancelled())434      return CB(llvm::make_error<CancelledError>(Reason));435 436    std::optional<SpeculativeFuzzyFind> SpecFuzzyFind;437    if (!IP->Preamble) {438      // No speculation in Fallback mode, as it's supposed to be much faster439      // without compiling.440      vlog("Build for file {0} is not ready. Enter fallback mode.", File);441    } else if (CodeCompleteOpts.Index) {442      SpecFuzzyFind.emplace();443      {444        std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);445        SpecFuzzyFind->CachedReq = CachedCompletionFuzzyFindRequestByFile[File];446      }447    }448    ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};449    // FIXME: Add traling new line if there is none at eof, workaround a crash,450    // see https://github.com/clangd/clangd/issues/332451    if (!IP->Contents.ends_with("\n"))452      ParseInput.Contents.append("\n");453    ParseInput.Index = Index;454 455    CodeCompleteOpts.MainFileSignals = IP->Signals;456    CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;457    CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;458    CodeCompleteOpts.InsertIncludes =459        Config::current().Completion.HeaderInsertion;460    CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;461    // FIXME(ibiryukov): even if Preamble is non-null, we may want to check462    // both the old and the new version in case only one of them matches.463    CodeCompleteResult Result = clangd::codeComplete(464        File, Pos, IP->Preamble, ParseInput, CodeCompleteOpts,465        SpecFuzzyFind ? &*SpecFuzzyFind : nullptr);466    // We don't want `codeComplete` to wait for the async call if it doesn't use467    // the result (e.g. non-index completion, speculation fails), so that `CB`468    // is called as soon as results are available.469    {470      clang::clangd::trace::Span Tracer("Completion results callback");471      CB(std::move(Result));472    }473    if (!SpecFuzzyFind)474      return;475    if (SpecFuzzyFind->NewReq) {476      std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);477      CachedCompletionFuzzyFindRequestByFile[File] = *SpecFuzzyFind->NewReq;478    }479    // Explicitly block until async task completes, this is fine as we've480    // already provided reply to the client and running as a preamble task481    // (i.e. won't block other preamble tasks).482    if (SpecFuzzyFind->Result.valid())483      SpecFuzzyFind->Result.wait();484  };485 486  // We use a potentially-stale preamble because latency is critical here.487  WorkScheduler->runWithPreamble(488      "CodeComplete", File,489      (Opts.RunParser == CodeCompleteOptions::AlwaysParse)490          ? TUScheduler::Stale491          : TUScheduler::StaleOrAbsent,492      std::move(Task));493}494 495void ClangdServer::signatureHelp(PathRef File, Position Pos,496                                 MarkupKind DocumentationFormat,497                                 Callback<SignatureHelp> CB) {498 499  auto Action = [Pos, File = File.str(), CB = std::move(CB),500                 DocumentationFormat,501                 this](llvm::Expected<InputsAndPreamble> IP) mutable {502    if (!IP)503      return CB(IP.takeError());504 505    const auto *PreambleData = IP->Preamble;506    if (!PreambleData)507      return CB(error("Failed to parse includes"));508 509    ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};510    // FIXME: Add traling new line if there is none at eof, workaround a crash,511    // see https://github.com/clangd/clangd/issues/332512    if (!IP->Contents.ends_with("\n"))513      ParseInput.Contents.append("\n");514    ParseInput.Index = Index;515    CB(clangd::signatureHelp(File, Pos, *PreambleData, ParseInput,516                             DocumentationFormat));517  };518 519  // Unlike code completion, we wait for a preamble here.520  WorkScheduler->runWithPreamble("SignatureHelp", File, TUScheduler::Stale,521                                 std::move(Action));522}523 524void ClangdServer::formatFile(PathRef File, const std::vector<Range> &Rngs,525                              Callback<tooling::Replacements> CB) {526  auto Code = getDraft(File);527  if (!Code)528    return CB(llvm::make_error<LSPError>("trying to format non-added document",529                                         ErrorCode::InvalidParams));530  std::vector<tooling::Range> RequestedRanges;531  if (!Rngs.empty()) {532    RequestedRanges.reserve(Rngs.size());533    for (const auto &Rng : Rngs) {534      llvm::Expected<size_t> Begin = positionToOffset(*Code, Rng.start);535      if (!Begin)536        return CB(Begin.takeError());537      llvm::Expected<size_t> End = positionToOffset(*Code, Rng.end);538      if (!End)539        return CB(End.takeError());540      RequestedRanges.emplace_back(*Begin, *End - *Begin);541    }542  } else {543    RequestedRanges = {tooling::Range(0, Code->size())};544  }545 546  // Call clang-format.547  auto Action = [File = File.str(), Code = std::move(*Code),548                 Ranges = std::move(RequestedRanges), CB = std::move(CB),549                 this]() mutable {550    format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS, true);551    tooling::Replacements IncludeReplaces =552        format::sortIncludes(Style, Code, Ranges, File);553    auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);554    if (!Changed)555      return CB(Changed.takeError());556 557    CB(IncludeReplaces.merge(format::reformat(558        Style, *Changed,559        tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),560        File)));561  };562  WorkScheduler->runQuick("Format", File, std::move(Action));563}564 565void ClangdServer::formatOnType(PathRef File, Position Pos,566                                StringRef TriggerText,567                                Callback<std::vector<TextEdit>> CB) {568  auto Code = getDraft(File);569  if (!Code)570    return CB(llvm::make_error<LSPError>("trying to format non-added document",571                                         ErrorCode::InvalidParams));572  llvm::Expected<size_t> CursorPos = positionToOffset(*Code, Pos);573  if (!CursorPos)574    return CB(CursorPos.takeError());575  auto Action = [File = File.str(), Code = std::move(*Code),576                 TriggerText = TriggerText.str(), CursorPos = *CursorPos,577                 CB = std::move(CB), this]() mutable {578    auto Style = getFormatStyleForFile(File, Code, TFS, false);579    std::vector<TextEdit> Result;580    for (const tooling::Replacement &R :581         formatIncremental(Code, CursorPos, TriggerText, Style))582      Result.push_back(replacementToEdit(Code, R));583    return CB(Result);584  };585  WorkScheduler->runQuick("FormatOnType", File, std::move(Action));586}587 588void ClangdServer::prepareRename(PathRef File, Position Pos,589                                 std::optional<std::string> NewName,590                                 const RenameOptions &RenameOpts,591                                 Callback<RenameResult> CB) {592  auto Action = [Pos, File = File.str(), CB = std::move(CB),593                 NewName = std::move(NewName),594                 RenameOpts](llvm::Expected<InputsAndAST> InpAST) mutable {595    if (!InpAST)596      return CB(InpAST.takeError());597    // prepareRename is latency-sensitive: we don't query the index, as we598    // only need main-file references599    auto Results =600        clangd::rename({Pos, NewName.value_or("__clangd_rename_placeholder"),601                        InpAST->AST, File, /*FS=*/nullptr,602                        /*Index=*/nullptr, RenameOpts});603    if (!Results) {604      // LSP says to return null on failure, but that will result in a generic605      // failure message. If we send an LSP error response, clients can surface606      // the message to users (VSCode does).607      return CB(Results.takeError());608    }609    return CB(*Results);610  };611  WorkScheduler->runWithAST("PrepareRename", File, std::move(Action));612}613 614void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,615                          const RenameOptions &Opts,616                          Callback<RenameResult> CB) {617  auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts,618                 CB = std::move(CB),619                 this](llvm::Expected<InputsAndAST> InpAST) mutable {620    // Tracks number of files edited per invocation.621    static constexpr trace::Metric RenameFiles("rename_files",622                                               trace::Metric::Distribution);623    if (!InpAST)624      return CB(InpAST.takeError());625    auto R = clangd::rename({Pos, NewName, InpAST->AST, File,626                             DirtyFS->view(std::nullopt), Index, Opts});627    if (!R)628      return CB(R.takeError());629 630    if (Opts.WantFormat) {631      auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,632                                         *InpAST->Inputs.TFS, false);633      llvm::Error Err = llvm::Error::success();634      for (auto &E : R->GlobalChanges)635        Err =636            llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));637 638      if (Err)639        return CB(std::move(Err));640    }641    RenameFiles.record(R->GlobalChanges.size());642    return CB(*R);643  };644  WorkScheduler->runWithAST("Rename", File, std::move(Action));645}646 647namespace {648// May generate several candidate selections, due to SelectionTree ambiguity.649// vector of pointers because GCC doesn't like non-copyable Selection.650llvm::Expected<std::vector<std::unique_ptr<Tweak::Selection>>>651tweakSelection(const Range &Sel, const InputsAndAST &AST,652               llvm::vfs::FileSystem *FS) {653  auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start);654  if (!Begin)655    return Begin.takeError();656  auto End = positionToOffset(AST.Inputs.Contents, Sel.end);657  if (!End)658    return End.takeError();659  std::vector<std::unique_ptr<Tweak::Selection>> Result;660  SelectionTree::createEach(661      AST.AST.getASTContext(), AST.AST.getTokens(), *Begin, *End,662      [&](SelectionTree T) {663        Result.push_back(std::make_unique<Tweak::Selection>(664            AST.Inputs.Index, AST.AST, *Begin, *End, std::move(T), FS));665        return false;666      });667  assert(!Result.empty() && "Expected at least one SelectionTree");668  return std::move(Result);669}670 671// Some fixes may perform local renaming, we want to convert those to clangd672// rename commands, such that we can leverage the index for more accurate673// results.674std::optional<ClangdServer::CodeActionResult::Rename>675tryConvertToRename(const Diag *Diag, const Fix &Fix) {676  bool IsClangTidyRename = Diag->Source == Diag::ClangTidy &&677                           Diag->Name == "readability-identifier-naming" &&678                           !Fix.Edits.empty();679  if (IsClangTidyRename && Diag->InsideMainFile) {680    ClangdServer::CodeActionResult::Rename R;681    R.NewName = Fix.Edits.front().newText;682    R.FixMessage = Fix.Message;683    R.Diag = {Diag->Range, Diag->Message};684    return R;685  }686 687  return std::nullopt;688}689 690} // namespace691 692void ClangdServer::codeAction(const CodeActionInputs &Params,693                              Callback<CodeActionResult> CB) {694  auto Action = [Params, CB = std::move(CB),695                 FeatureModules(this->FeatureModules)](696                    Expected<InputsAndAST> InpAST) mutable {697    if (!InpAST)698      return CB(InpAST.takeError());699    auto KindAllowed =700        [Only(Params.RequestedActionKinds)](llvm::StringRef Kind) {701          if (Only.empty())702            return true;703          return llvm::any_of(Only, [&](llvm::StringRef Base) {704            return Kind.consume_front(Base) &&705                   (Kind.empty() || Kind.starts_with("."));706          });707        };708 709    CodeActionResult Result;710    Result.Version = InpAST->AST.version().str();711    if (KindAllowed(CodeAction::QUICKFIX_KIND)) {712      auto FindMatchedDiag = [&InpAST](const DiagRef &DR) -> const Diag * {713        for (const auto &Diag : InpAST->AST.getDiagnostics())714          if (Diag.Range == DR.Range && Diag.Message == DR.Message)715            return &Diag;716        return nullptr;717      };718      for (const auto &DiagRef : Params.Diagnostics) {719        if (const auto *Diag = FindMatchedDiag(DiagRef))720          for (const auto &Fix : Diag->Fixes) {721            if (auto Rename = tryConvertToRename(Diag, Fix)) {722              Result.Renames.emplace_back(std::move(*Rename));723            } else {724              Result.QuickFixes.push_back({DiagRef, Fix});725            }726          }727      }728    }729 730    // Collect Tweaks731    auto Selections = tweakSelection(Params.Selection, *InpAST, /*FS=*/nullptr);732    if (!Selections)733      return CB(Selections.takeError());734    // Don't allow a tweak to fire more than once across ambiguous selections.735    llvm::DenseSet<llvm::StringRef> PreparedTweaks;736    auto DeduplicatingFilter = [&](const Tweak &T) {737      return KindAllowed(T.kind()) && Params.TweakFilter(T) &&738             !PreparedTweaks.count(T.id());739    };740    for (const auto &Sel : *Selections) {741      for (auto &T : prepareTweaks(*Sel, DeduplicatingFilter, FeatureModules)) {742        Result.TweakRefs.push_back(TweakRef{T->id(), T->title(), T->kind()});743        PreparedTweaks.insert(T->id());744        TweakAvailable.record(1, T->id());745      }746    }747    CB(std::move(Result));748  };749 750  WorkScheduler->runWithAST("codeAction", Params.File, std::move(Action),751                            Transient);752}753 754void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,755                              Callback<Tweak::Effect> CB) {756  // Tracks number of times a tweak has been attempted.757  static constexpr trace::Metric TweakAttempt(758      "tweak_attempt", trace::Metric::Counter, "tweak_id");759  // Tracks number of times a tweak has failed to produce edits.760  static constexpr trace::Metric TweakFailed(761      "tweak_failed", trace::Metric::Counter, "tweak_id");762  TweakAttempt.record(1, TweakID);763  auto Action = [File = File.str(), Sel, TweakID = TweakID.str(),764                 CB = std::move(CB),765                 this](Expected<InputsAndAST> InpAST) mutable {766    if (!InpAST)767      return CB(InpAST.takeError());768    auto FS = DirtyFS->view(std::nullopt);769    auto Selections = tweakSelection(Sel, *InpAST, FS.get());770    if (!Selections)771      return CB(Selections.takeError());772    std::optional<llvm::Expected<Tweak::Effect>> Effect;773    // Try each selection, take the first one that prepare()s.774    // If they all fail, Effect will hold get the last error.775    for (const auto &Selection : *Selections) {776      auto T = prepareTweak(TweakID, *Selection, FeatureModules);777      if (T) {778        Effect = (*T)->apply(*Selection);779        break;780      }781      Effect = T.takeError();782    }783    assert(Effect && "Expected at least one selection");784    if (*Effect && (*Effect)->FormatEdits) {785      // Format tweaks that require it centrally here.786      for (auto &It : (*Effect)->ApplyEdits) {787        Edit &E = It.second;788        format::FormatStyle Style =789            getFormatStyleForFile(File, E.InitialCode, TFS, false);790        if (llvm::Error Err = reformatEdit(E, Style))791          elog("Failed to format {0}: {1}", It.first(), std::move(Err));792      }793    } else {794      TweakFailed.record(1, TweakID);795    }796    return CB(std::move(*Effect));797  };798  WorkScheduler->runWithAST("ApplyTweak", File, std::move(Action));799}800 801void ClangdServer::locateSymbolAt(PathRef File, Position Pos,802                                  Callback<std::vector<LocatedSymbol>> CB) {803  auto Action = [Pos, CB = std::move(CB),804                 this](llvm::Expected<InputsAndAST> InpAST) mutable {805    if (!InpAST)806      return CB(InpAST.takeError());807    CB(clangd::locateSymbolAt(InpAST->AST, Pos, Index));808  };809 810  WorkScheduler->runWithAST("Definitions", File, std::move(Action));811}812 813void ClangdServer::switchSourceHeader(814    PathRef Path, Callback<std::optional<clangd::Path>> CB) {815  // We want to return the result as fast as possible, strategy is:816  //  1) use the file-only heuristic, it requires some IO but it is much817  //     faster than building AST, but it only works when .h/.cc files are in818  //     the same directory.819  //  2) if 1) fails, we use the AST&Index approach, it is slower but supports820  //     different code layout.821  if (auto CorrespondingFile =822          getCorrespondingHeaderOrSource(Path, TFS.view(std::nullopt)))823    return CB(std::move(CorrespondingFile));824  auto Action = [Path = Path.str(), CB = std::move(CB),825                 this](llvm::Expected<InputsAndAST> InpAST) mutable {826    if (!InpAST)827      return CB(InpAST.takeError());828    CB(getCorrespondingHeaderOrSource(Path, InpAST->AST, Index));829  };830  WorkScheduler->runWithAST("SwitchHeaderSource", Path, std::move(Action));831}832 833void ClangdServer::findDocumentHighlights(834    PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {835  auto Action =836      [Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {837        if (!InpAST)838          return CB(InpAST.takeError());839        CB(clangd::findDocumentHighlights(InpAST->AST, Pos));840      };841 842  WorkScheduler->runWithAST("Highlights", File, std::move(Action), Transient);843}844 845void ClangdServer::findHover(PathRef File, Position Pos,846                             Callback<std::optional<HoverInfo>> CB) {847  auto Action = [File = File.str(), Pos, CB = std::move(CB),848                 this](llvm::Expected<InputsAndAST> InpAST) mutable {849    if (!InpAST)850      return CB(InpAST.takeError());851    format::FormatStyle Style = getFormatStyleForFile(852        File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS, false);853    CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));854  };855 856  WorkScheduler->runWithAST("Hover", File, std::move(Action), Transient);857}858 859void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,860                                 TypeHierarchyDirection Direction,861                                 Callback<std::vector<TypeHierarchyItem>> CB) {862  auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),863                 this](Expected<InputsAndAST> InpAST) mutable {864    if (!InpAST)865      return CB(InpAST.takeError());866    CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction, Index,867                                File));868  };869 870  WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action));871}872 873void ClangdServer::superTypes(874    const TypeHierarchyItem &Item,875    Callback<std::optional<std::vector<TypeHierarchyItem>>> CB) {876  WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",877                     [=, CB = std::move(CB)]() mutable {878                       CB(clangd::superTypes(Item, Index));879                     });880}881 882void ClangdServer::subTypes(const TypeHierarchyItem &Item,883                            Callback<std::vector<TypeHierarchyItem>> CB) {884  WorkScheduler->run(885      "typeHierarchy/subTypes", /*Path=*/"",886      [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });887}888 889void ClangdServer::resolveTypeHierarchy(890    TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,891    Callback<std::optional<TypeHierarchyItem>> CB) {892  WorkScheduler->run(893      "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {894        clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);895        CB(Item);896      });897}898 899void ClangdServer::prepareCallHierarchy(900    PathRef File, Position Pos, Callback<std::vector<CallHierarchyItem>> CB) {901  auto Action = [File = File.str(), Pos,902                 CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {903    if (!InpAST)904      return CB(InpAST.takeError());905    CB(clangd::prepareCallHierarchy(InpAST->AST, Pos, File));906  };907  WorkScheduler->runWithAST("CallHierarchy", File, std::move(Action));908}909 910void ClangdServer::incomingCalls(911    const CallHierarchyItem &Item,912    Callback<std::vector<CallHierarchyIncomingCall>> CB) {913  WorkScheduler->run("Incoming Calls", "",914                     [CB = std::move(CB), Item, this]() mutable {915                       CB(clangd::incomingCalls(Item, Index));916                     });917}918 919void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,920                              Callback<std::vector<InlayHint>> CB) {921  auto Action = [RestrictRange(std::move(RestrictRange)),922                 CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {923    if (!InpAST)924      return CB(InpAST.takeError());925    CB(clangd::inlayHints(InpAST->AST, std::move(RestrictRange)));926  };927  WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);928}929 930void ClangdServer::outgoingCalls(931    const CallHierarchyItem &Item,932    Callback<std::vector<CallHierarchyOutgoingCall>> CB) {933  WorkScheduler->run("Outgoing Calls", "",934                     [CB = std::move(CB), Item, this]() mutable {935                       CB(clangd::outgoingCalls(Item, Index));936                     });937}938 939void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {940  // FIXME: Do nothing for now. This will be used for indexing and potentially941  // invalidating other caches.942}943 944void ClangdServer::workspaceSymbols(945    llvm::StringRef Query, int Limit,946    Callback<std::vector<SymbolInformation>> CB) {947  WorkScheduler->run(948      "getWorkspaceSymbols", /*Path=*/"",949      [Query = Query.str(), Limit, CB = std::move(CB), this]() mutable {950        CB(clangd::getWorkspaceSymbols(Query, Limit, Index,951                                       WorkspaceRoot.value_or("")));952      });953}954 955void ClangdServer::documentSymbols(llvm::StringRef File,956                                   Callback<std::vector<DocumentSymbol>> CB) {957  auto Action =958      [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {959        if (!InpAST)960          return CB(InpAST.takeError());961        CB(clangd::getDocumentSymbols(InpAST->AST));962      };963  WorkScheduler->runWithAST("DocumentSymbols", File, std::move(Action),964                            Transient);965}966 967void ClangdServer::foldingRanges(llvm::StringRef File,968                                 Callback<std::vector<FoldingRange>> CB) {969  auto Code = getDraft(File);970  if (!Code)971    return CB(llvm::make_error<LSPError>(972        "trying to compute folding ranges for non-added document",973        ErrorCode::InvalidParams));974  auto Action = [LineFoldingOnly = LineFoldingOnly, CB = std::move(CB),975                 Code = std::move(*Code)]() mutable {976    CB(clangd::getFoldingRanges(Code, LineFoldingOnly));977  };978  // We want to make sure folding ranges are always available for all the open979  // files, hence prefer runQuick to not wait for operations on other files.980  WorkScheduler->runQuick("FoldingRanges", File, std::move(Action));981}982 983void ClangdServer::findType(llvm::StringRef File, Position Pos,984                            Callback<std::vector<LocatedSymbol>> CB) {985  auto Action = [Pos, CB = std::move(CB),986                 this](llvm::Expected<InputsAndAST> InpAST) mutable {987    if (!InpAST)988      return CB(InpAST.takeError());989    CB(clangd::findType(InpAST->AST, Pos, Index));990  };991  WorkScheduler->runWithAST("FindType", File, std::move(Action));992}993 994void ClangdServer::findImplementations(995    PathRef File, Position Pos, Callback<std::vector<LocatedSymbol>> CB) {996  auto Action = [Pos, CB = std::move(CB),997                 this](llvm::Expected<InputsAndAST> InpAST) mutable {998    if (!InpAST)999      return CB(InpAST.takeError());1000    CB(clangd::findImplementations(InpAST->AST, Pos, Index));1001  };1002 1003  WorkScheduler->runWithAST("Implementations", File, std::move(Action));1004}1005 1006void ClangdServer::findReferences(PathRef File, Position Pos, uint32_t Limit,1007                                  bool AddContainer,1008                                  Callback<ReferencesResult> CB) {1009  auto Action = [Pos, Limit, AddContainer, CB = std::move(CB),1010                 this](llvm::Expected<InputsAndAST> InpAST) mutable {1011    if (!InpAST)1012      return CB(InpAST.takeError());1013    CB(clangd::findReferences(InpAST->AST, Pos, Limit, Index, AddContainer));1014  };1015 1016  WorkScheduler->runWithAST("References", File, std::move(Action));1017}1018 1019void ClangdServer::symbolInfo(PathRef File, Position Pos,1020                              Callback<std::vector<SymbolDetails>> CB) {1021  auto Action =1022      [Pos, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1023        if (!InpAST)1024          return CB(InpAST.takeError());1025        CB(clangd::getSymbolInfo(InpAST->AST, Pos));1026      };1027 1028  WorkScheduler->runWithAST("SymbolInfo", File, std::move(Action));1029}1030 1031void ClangdServer::semanticRanges(PathRef File,1032                                  const std::vector<Position> &Positions,1033                                  Callback<std::vector<SelectionRange>> CB) {1034  auto Action = [Positions, CB = std::move(CB)](1035                    llvm::Expected<InputsAndAST> InpAST) mutable {1036    if (!InpAST)1037      return CB(InpAST.takeError());1038    std::vector<SelectionRange> Result;1039    for (const auto &Pos : Positions) {1040      if (auto Range = clangd::getSemanticRanges(InpAST->AST, Pos))1041        Result.push_back(std::move(*Range));1042      else1043        return CB(Range.takeError());1044    }1045    CB(std::move(Result));1046  };1047  WorkScheduler->runWithAST("SemanticRanges", File, std::move(Action));1048}1049 1050void ClangdServer::documentLinks(PathRef File,1051                                 Callback<std::vector<DocumentLink>> CB) {1052  auto Action =1053      [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1054        if (!InpAST)1055          return CB(InpAST.takeError());1056        CB(clangd::getDocumentLinks(InpAST->AST));1057      };1058  WorkScheduler->runWithAST("DocumentLinks", File, std::move(Action),1059                            Transient);1060}1061 1062void ClangdServer::semanticHighlights(1063    PathRef File, Callback<std::vector<HighlightingToken>> CB) {1064 1065  auto Action = [CB = std::move(CB),1066                 PublishInactiveRegions = PublishInactiveRegions](1067                    llvm::Expected<InputsAndAST> InpAST) mutable {1068    if (!InpAST)1069      return CB(InpAST.takeError());1070    // Include inactive regions in semantic highlighting tokens only if the1071    // client doesn't support a dedicated protocol for being informed about1072    // them.1073    CB(clangd::getSemanticHighlightings(InpAST->AST, !PublishInactiveRegions));1074  };1075  WorkScheduler->runWithAST("SemanticHighlights", File, std::move(Action),1076                            Transient);1077}1078 1079void ClangdServer::getAST(PathRef File, std::optional<Range> R,1080                          Callback<std::optional<ASTNode>> CB) {1081  auto Action =1082      [R, CB(std::move(CB))](llvm::Expected<InputsAndAST> Inputs) mutable {1083        if (!Inputs)1084          return CB(Inputs.takeError());1085        if (!R) {1086          // It's safe to pass in the TU, as dumpAST() does not1087          // deserialize the preamble.1088          auto Node = DynTypedNode::create(1089              *Inputs->AST.getASTContext().getTranslationUnitDecl());1090          return CB(dumpAST(Node, Inputs->AST.getTokens(),1091                            Inputs->AST.getASTContext()));1092        }1093        unsigned Start, End;1094        if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->start))1095          Start = *Offset;1096        else1097          return CB(Offset.takeError());1098        if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->end))1099          End = *Offset;1100        else1101          return CB(Offset.takeError());1102        bool Success = SelectionTree::createEach(1103            Inputs->AST.getASTContext(), Inputs->AST.getTokens(), Start, End,1104            [&](SelectionTree T) {1105              if (const SelectionTree::Node *N = T.commonAncestor()) {1106                CB(dumpAST(N->ASTNode, Inputs->AST.getTokens(),1107                           Inputs->AST.getASTContext()));1108                return true;1109              }1110              return false;1111            });1112        if (!Success)1113          CB(std::nullopt);1114      };1115  WorkScheduler->runWithAST("GetAST", File, std::move(Action));1116}1117 1118void ClangdServer::customAction(PathRef File, llvm::StringRef Name,1119                                Callback<InputsAndAST> Action) {1120  WorkScheduler->runWithAST(Name, File, std::move(Action));1121}1122 1123void ClangdServer::diagnostics(PathRef File, Callback<std::vector<Diag>> CB) {1124  auto Action =1125      [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {1126        if (!InpAST)1127          return CB(InpAST.takeError());1128        return CB(InpAST->AST.getDiagnostics());1129      };1130 1131  WorkScheduler->runWithAST("Diagnostics", File, std::move(Action));1132}1133 1134llvm::StringMap<TUScheduler::FileStats> ClangdServer::fileStats() const {1135  return WorkScheduler->fileStats();1136}1137 1138[[nodiscard]] bool1139ClangdServer::blockUntilIdleForTest(std::optional<double> TimeoutSeconds) {1140  // Order is important here: we don't want to block on A and then B,1141  // if B might schedule work on A.1142 1143#if defined(__has_feature) &&                                                  \1144    (__has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer) || \1145     __has_feature(memory_sanitizer) || __has_feature(thread_sanitizer))1146  if (TimeoutSeconds.has_value())1147    (*TimeoutSeconds) *= 10;1148#endif1149 1150  // Nothing else can schedule work on TUScheduler, because it's not threadsafe1151  // and we're blocking the main thread.1152  if (!WorkScheduler->blockUntilIdle(timeoutSeconds(TimeoutSeconds)))1153    return false;1154  // TUScheduler is the only thing that starts background indexing work.1155  if (IndexTasks && !IndexTasks->wait(timeoutSeconds(TimeoutSeconds)))1156    return false;1157 1158  // Unfortunately we don't have strict topological order between the rest of1159  // the components. E.g. CDB broadcast triggers backrgound indexing.1160  // This queries the CDB which may discover new work if disk has changed.1161  //1162  // So try each one a few times in a loop.1163  // If there are no tricky interactions then all after the first are no-ops.1164  // Then on the last iteration, verify they're idle without waiting.1165  //1166  // There's a small chance they're juggling work and we didn't catch them :-(1167  for (std::optional<double> Timeout :1168       {TimeoutSeconds, TimeoutSeconds, std::optional<double>(0)}) {1169    if (!CDB.blockUntilIdle(timeoutSeconds(Timeout)))1170      return false;1171    if (BackgroundIdx && !BackgroundIdx->blockUntilIdleForTest(Timeout))1172      return false;1173    if (FeatureModules && llvm::any_of(*FeatureModules, [&](FeatureModule &M) {1174          return !M.blockUntilIdle(timeoutSeconds(Timeout));1175        }))1176      return false;1177  }1178 1179  assert(WorkScheduler->blockUntilIdle(Deadline::zero()) &&1180         "Something scheduled work while we're blocking the main thread!");1181  return true;1182}1183 1184void ClangdServer::profile(MemoryTree &MT) const {1185  if (DynamicIdx)1186    DynamicIdx->profile(MT.child("dynamic_index"));1187  if (BackgroundIdx)1188    BackgroundIdx->profile(MT.child("background_index"));1189  WorkScheduler->profile(MT.child("tuscheduler"));1190}1191} // namespace clangd1192} // namespace clang1193