diff options
Diffstat (limited to 'clangd/ClangdServer.cpp')
-rw-r--r-- | clangd/ClangdServer.cpp | 218 |
1 files changed, 148 insertions, 70 deletions
diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp index 53a27bdb..374ffc0b 100644 --- a/clangd/ClangdServer.cpp +++ b/clangd/ClangdServer.cpp @@ -1,26 +1,30 @@ //===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 "ClangdServer.h" +#include "ClangdUnit.h" #include "CodeComplete.h" #include "FindSymbols.h" #include "Headers.h" +#include "Protocol.h" #include "SourceCode.h" +#include "TUScheduler.h" #include "Trace.h" -#include "XRefs.h" +#include "index/CanonicalIncludes.h" #include "index/FileIndex.h" #include "index/Merge.h" +#include "refactor/Tweak.h" #include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" #include "llvm/ADT/ArrayRef.h" @@ -28,19 +32,29 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <future> +#include <memory> #include <mutex> namespace clang { namespace clangd { namespace { -std::string getStandardResourceDir() { - static int Dummy; // Just an address in this process. - return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); +// Expand a DiagnosticError to make it print-friendly (print the detailed +// message, rather than "clang diagnostic"). +llvm::Error expandDiagnostics(llvm::Error Err, DiagnosticsEngine &DE) { + if (auto Diag = DiagnosticError::take(Err)) { + llvm::cantFail(std::move(Err)); + SmallVector<char, 128> DiagMessage; + Diag->second.EmitToString(DE, DiagMessage); + return llvm::make_error<llvm::StringError>(DiagMessage, + llvm::inconvertibleErrorCode()); + } + return Err; } class RefactoringResultCollector final @@ -48,9 +62,6 @@ class RefactoringResultCollector final public: void handleError(llvm::Error Err) override { assert(!Result.hasValue()); - // FIXME: figure out a way to return better message for DiagnosticError. - // clangd uses llvm::toString to convert the Err to string, however, for - // DiagnosticError, only "clang diagnostic" will be generated. Result = std::move(Err); } @@ -71,9 +82,10 @@ struct UpdateIndexCallbacks : public ParsingCallbacks { : FIndex(FIndex), DiagConsumer(DiagConsumer) {} void onPreambleAST(PathRef Path, ASTContext &Ctx, - std::shared_ptr<clang::Preprocessor> PP) override { + std::shared_ptr<clang::Preprocessor> PP, + const CanonicalIncludes &CanonIncludes) override { if (FIndex) - FIndex->updatePreamble(Path, Ctx, std::move(PP)); + FIndex->updatePreamble(Path, Ctx, std::move(PP), CanonIncludes); } void onMainAST(PathRef Path, ParsedAST &AST) override { @@ -107,20 +119,19 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, const FileSystemProvider &FSProvider, DiagnosticsConsumer &DiagConsumer, const Options &Opts) - : CDB(CDB), FSProvider(FSProvider), - ResourceDir(Opts.ResourceDir ? *Opts.ResourceDir - : getStandardResourceDir()), + : FSProvider(FSProvider), DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex(Opts.HeavyweightDynamicSymbolIndex) : nullptr), + ClangTidyOptProvider(Opts.ClangTidyOptProvider), + SuggestMissingIncludes(Opts.SuggestMissingIncludes), WorkspaceRoot(Opts.WorkspaceRoot), - PCHs(std::make_shared<PCHContainerOperations>()), // Pass a callback into `WorkScheduler` to extract symbols from a newly // parsed file and rebuild the file index synchronously each time an AST // is parsed. // FIXME(ioeric): this can be slow and we may be able to index on less // critical paths. - WorkScheduler(Opts.AsyncThreadsCount, Opts.StorePreamblesInMemory, + WorkScheduler(CDB, Opts.AsyncThreadsCount, Opts.StorePreamblesInMemory, llvm::make_unique<UpdateIndexCallbacks>(DynamicIdx.get(), DiagConsumer), Opts.UpdateDebounce, Opts.RetentionPolicy) { @@ -137,7 +148,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, AddIndex(Opts.StaticIndex); if (Opts.BackgroundIndex) { BackgroundIdx = llvm::make_unique<BackgroundIndex>( - Context::current().clone(), ResourceDir, FSProvider, CDB, + Context::current().clone(), FSProvider, CDB, BackgroundIndexStorage::createDiskBackedStorageFactory(), Opts.BackgroundIndexRebuildPeriodMs); AddIndex(BackgroundIdx.get()); @@ -148,14 +159,19 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, WantDiagnostics WantDiags) { - // FIXME: some build systems like Bazel will take time to preparing - // environment to build the file, it would be nice if we could emit a - // "PreparingBuild" status to inform users, it is non-trivial given the - // current implementation. - WorkScheduler.update(File, - ParseInputs{getCompileCommand(File), - FSProvider.getFileSystem(), Contents.str()}, - WantDiags); + ParseOptions Opts; + Opts.ClangTidyOpts = tidy::ClangTidyOptions::getDefaults(); + if (ClangTidyOptProvider) + Opts.ClangTidyOpts = ClangTidyOptProvider->getOptions(File); + Opts.SuggestMissingIncludes = SuggestMissingIncludes; + + // Compile command is set asynchronously during update, as it can be slow. + ParseInputs Inputs; + Inputs.FS = FSProvider.getFileSystem(); + Inputs.Contents = Contents; + Inputs.Opts = std::move(Opts); + Inputs.Index = Index; + WorkScheduler.update(File, Inputs, WantDiags); } void ClangdServer::removeDocument(PathRef File) { WorkScheduler.remove(File); } @@ -168,11 +184,8 @@ void ClangdServer::codeComplete(PathRef File, Position Pos, if (!CodeCompleteOpts.Index) // Respect overridden index. CodeCompleteOpts.Index = Index; - // Copy PCHs to avoid accessing this->PCHs concurrently - std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs; auto FS = FSProvider.getFileSystem(); - - auto Task = [PCHs, Pos, FS, CodeCompleteOpts, + auto Task = [Pos, FS, CodeCompleteOpts, this](Path File, Callback<CodeCompleteResult> CB, llvm::Expected<InputsAndPreamble> IP) { if (!IP) @@ -181,18 +194,25 @@ void ClangdServer::codeComplete(PathRef File, Position Pos, return CB(llvm::make_error<CancelledError>()); llvm::Optional<SpeculativeFuzzyFind> SpecFuzzyFind; - if (CodeCompleteOpts.Index && CodeCompleteOpts.SpeculativeIndexRequest) { - SpecFuzzyFind.emplace(); - { - std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex); - SpecFuzzyFind->CachedReq = CachedCompletionFuzzyFindRequestByFile[File]; + if (!IP->Preamble) { + // No speculation in Fallback mode, as it's supposed to be much faster + // without compiling. + vlog("Build for file {0} is not ready. Enter fallback mode.", File); + } else { + if (CodeCompleteOpts.Index && CodeCompleteOpts.SpeculativeIndexRequest) { + SpecFuzzyFind.emplace(); + { + std::lock_guard<std::mutex> Lock( + CachedCompletionFuzzyFindRequestMutex); + SpecFuzzyFind->CachedReq = + CachedCompletionFuzzyFindRequestByFile[File]; + } } } - // 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. CodeCompleteResult Result = clangd::codeComplete( - File, IP->Command, IP->Preamble, IP->Contents, Pos, FS, PCHs, + File, IP->Command, IP->Preamble, IP->Contents, Pos, FS, CodeCompleteOpts, SpecFuzzyFind ? SpecFuzzyFind.getPointer() : nullptr); { clang::clangd::trace::Span Tracer("Completion results callback"); @@ -210,24 +230,25 @@ void ClangdServer::codeComplete(PathRef File, Position Pos, }; // We use a potentially-stale preamble because latency is critical here. - WorkScheduler.runWithPreamble("CodeComplete", File, TUScheduler::Stale, + WorkScheduler.runWithPreamble("CodeComplete", File, + Opts.AllowFallback ? TUScheduler::StaleOrAbsent + : TUScheduler::Stale, Bind(Task, File.str(), std::move(CB))); } void ClangdServer::signatureHelp(PathRef File, Position Pos, Callback<SignatureHelp> CB) { - auto PCHs = this->PCHs; auto FS = FSProvider.getFileSystem(); auto *Index = this->Index; - auto Action = [Pos, FS, PCHs, Index](Path File, Callback<SignatureHelp> CB, - llvm::Expected<InputsAndPreamble> IP) { + auto Action = [Pos, FS, Index](Path File, Callback<SignatureHelp> CB, + llvm::Expected<InputsAndPreamble> IP) { if (!IP) return CB(IP.takeError()); auto PreambleData = IP->Preamble; CB(clangd::signatureHelp(File, IP->Command, PreambleData, IP->Contents, Pos, - FS, PCHs, Index)); + FS, Index)); }; // Unlike code completion, we wait for an up-to-date preamble here. @@ -272,9 +293,9 @@ ClangdServer::formatOnType(llvm::StringRef Code, PathRef File, Position Pos) { } void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, - Callback<std::vector<tooling::Replacement>> CB) { + Callback<std::vector<TextEdit>> CB) { auto Action = [Pos](Path File, std::string NewName, - Callback<std::vector<tooling::Replacement>> CB, + Callback<std::vector<TextEdit>> CB, llvm::Expected<InputsAndAST> InpAST) { if (!InpAST) return CB(InpAST.takeError()); @@ -290,15 +311,17 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, auto Rename = clang::tooling::RenameOccurrences::initiate( Context, SourceRange(SourceLocationBeg), NewName); if (!Rename) - return CB(Rename.takeError()); + return CB(expandDiagnostics(Rename.takeError(), + AST.getASTContext().getDiagnostics())); Rename->invoke(ResultCollector, Context); assert(ResultCollector.Result.hasValue()); if (!ResultCollector.Result.getValue()) - return CB(ResultCollector.Result->takeError()); + return CB(expandDiagnostics(ResultCollector.Result->takeError(), + AST.getASTContext().getDiagnostics())); - std::vector<tooling::Replacement> Replacements; + std::vector<TextEdit> Replacements; for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { tooling::Replacements ChangeReps = Change.getReplacements(); for (const auto &Rep : ChangeReps) { @@ -312,7 +335,8 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, // * rename globally in project // * rename in open files if (Rep.getFilePath() == File) - Replacements.push_back(Rep); + Replacements.push_back( + replacementToEdit(InpAST->Inputs.Contents, Rep)); } } return CB(std::move(Replacements)); @@ -322,6 +346,63 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, "Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB))); } +static llvm::Expected<Tweak::Selection> +tweakSelection(const Range &Sel, const InputsAndAST &AST) { + auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start); + if (!Begin) + return Begin.takeError(); + auto End = positionToOffset(AST.Inputs.Contents, Sel.end); + if (!End) + return End.takeError(); + return Tweak::Selection(AST.AST, *Begin, *End); +} + +void ClangdServer::enumerateTweaks(PathRef File, Range Sel, + Callback<std::vector<TweakRef>> CB) { + auto Action = [Sel](decltype(CB) CB, std::string File, + Expected<InputsAndAST> InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + auto Selection = tweakSelection(Sel, *InpAST); + if (!Selection) + return CB(Selection.takeError()); + std::vector<TweakRef> Res; + for (auto &T : prepareTweaks(*Selection)) + Res.push_back({T->id(), T->title()}); + CB(std::move(Res)); + }; + + WorkScheduler.runWithAST("EnumerateTweaks", File, + Bind(Action, std::move(CB), File.str())); +} + +void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID, + Callback<tooling::Replacements> CB) { + auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID, + Expected<InputsAndAST> InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + auto Selection = tweakSelection(Sel, *InpAST); + if (!Selection) + return CB(Selection.takeError()); + auto A = prepareTweak(TweakID, *Selection); + if (!A) + return CB(A.takeError()); + auto RawReplacements = (*A)->apply(*Selection); + if (!RawReplacements) + return CB(RawReplacements.takeError()); + // FIXME: this function has I/O operations (find .clang-format file), figure + // out a way to cache the format style. + auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents, + InpAST->Inputs.FS.get()); + return CB( + cleanupAndFormat(InpAST->Inputs.Contents, *RawReplacements, Style)); + }; + WorkScheduler.runWithAST( + "ApplyTweak", File, + Bind(Action, std::move(CB), File.str(), TweakID.str())); +} + void ClangdServer::dumpAST(PathRef File, llvm::unique_function<void(std::string)> Callback) { auto Action = [](decltype(Callback) Callback, @@ -342,13 +423,13 @@ void ClangdServer::dumpAST(PathRef File, WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback))); } -void ClangdServer::findDefinitions(PathRef File, Position Pos, - Callback<std::vector<Location>> CB) { - auto Action = [Pos, this](Callback<std::vector<Location>> CB, +void ClangdServer::locateSymbolAt(PathRef File, Position Pos, + Callback<std::vector<LocatedSymbol>> CB) { + auto Action = [Pos, this](decltype(CB) CB, llvm::Expected<InputsAndAST> InpAST) { if (!InpAST) return CB(InpAST.takeError()); - CB(clangd::findDefinitions(InpAST->AST, Pos, Index)); + CB(clangd::locateSymbolAt(InpAST->AST, Pos, Index)); }; WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB))); @@ -415,20 +496,16 @@ llvm::Expected<tooling::Replacements> ClangdServer::formatCode(llvm::StringRef Code, PathRef File, llvm::ArrayRef<tooling::Range> Ranges) { // Call clang-format. - auto FS = FSProvider.getFileSystem(); - auto Style = format::getStyle(format::DefaultFormatStyle, File, - format::DefaultFallbackStyle, Code, FS.get()); - if (!Style) - return Style.takeError(); - + format::FormatStyle Style = + getFormatStyleForFile(File, Code, FSProvider.getFileSystem().get()); tooling::Replacements IncludeReplaces = - format::sortIncludes(*Style, Code, Ranges, File); + format::sortIncludes(Style, Code, Ranges, File); auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); if (!Changed) return Changed.takeError(); return IncludeReplaces.merge(format::reformat( - Style.get(), *Changed, + Style, *Changed, tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges), File)); } @@ -457,16 +534,17 @@ void ClangdServer::findHover(PathRef File, Position Pos, WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } -tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { - trace::Span Span("GetCompileCommand"); - llvm::Optional<tooling::CompileCommand> C = CDB.getCompileCommand(File); - if (!C) // FIXME: Suppress diagnostics? Let the user know? - C = CDB.getFallbackCommand(File); +void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, + TypeHierarchyDirection Direction, + Callback<Optional<TypeHierarchyItem>> CB) { + auto Action = [Pos, Resolve, Direction](decltype(CB) CB, + Expected<InputsAndAST> InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction)); + }; - // Inject the resource dir. - // FIXME: Don't overwrite it if it's already there. - C->CommandLine.push_back("-resource-dir=" + ResourceDir); - return std::move(*C); + WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB))); } void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { |