summaryrefslogtreecommitdiffstats
path: root/clangd/ClangdServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/ClangdServer.cpp')
-rw-r--r--clangd/ClangdServer.cpp218
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) {