Restored r303067 and fixed failing test.

Summary:
This commit restores r303067(reverted by r303094) and fixes the 'formatting.test'
failure.
The failure is due to destructors of `ClangdLSPServer`'s fields(`FixItsMap` and
`FixItsMutex`) being called before destructor of `Server`. It led to the worker
thread calling `consumeDiagnostics` after `FixItsMutex` and `FixItsMap`
destructors were called.
Also, clangd is now run with '-run-synchronously' flag in 'formatting.test'.

Reviewers: bkramer, krasimir

Reviewed By: krasimir

Subscribers: mgorny, cfe-commits, klimek

Differential Revision: https://reviews.llvm.org/D33233

llvm-svn: 303151
This commit is contained in:
Ilya Biryukov 2017-05-16 09:38:59 +00:00
parent f45f681609
commit 38d79774d0
21 changed files with 1224 additions and 759 deletions

View File

@ -1,440 +0,0 @@
//===--- ASTManager.cpp - Clang AST manager -------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ASTManager.h"
#include "JSONRPCDispatcher.h"
#include "Protocol.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Path.h"
#include <mutex>
#include <thread>
using namespace clang;
using namespace clangd;
void DocData::setAST(std::unique_ptr<ASTUnit> AST) {
this->AST = std::move(AST);
}
ASTUnit *DocData::getAST() const { return AST.get(); }
void DocData::cacheFixIts(DiagnosticToReplacementMap FixIts) {
this->FixIts = std::move(FixIts);
}
std::vector<clang::tooling::Replacement>
DocData::getFixIts(const clangd::Diagnostic &D) const {
auto it = FixIts.find(D);
if (it != FixIts.end())
return it->second;
return {};
}
ASTManagerRequest::ASTManagerRequest(ASTManagerRequestType Type,
std::string File,
DocVersion Version)
: Type(Type), File(File), Version(Version) {}
/// Retrieve a copy of the contents of every file in the store, for feeding into
/// ASTUnit.
static std::vector<ASTUnit::RemappedFile>
getRemappedFiles(const DocumentStore &Docs) {
// FIXME: Use VFS instead. This would allow us to get rid of the chdir below.
std::vector<ASTUnit::RemappedFile> RemappedFiles;
for (const auto &P : Docs.getAllDocuments()) {
StringRef FileName = P.first;
RemappedFiles.push_back(ASTUnit::RemappedFile(
FileName,
llvm::MemoryBuffer::getMemBufferCopy(P.second, FileName).release()));
}
return RemappedFiles;
}
/// Convert from clang diagnostic level to LSP severity.
static int getSeverity(DiagnosticsEngine::Level L) {
switch (L) {
case DiagnosticsEngine::Remark:
return 4;
case DiagnosticsEngine::Note:
return 3;
case DiagnosticsEngine::Warning:
return 2;
case DiagnosticsEngine::Fatal:
case DiagnosticsEngine::Error:
return 1;
case DiagnosticsEngine::Ignored:
return 0;
}
llvm_unreachable("Unknown diagnostic level!");
}
static CompletionItemKind getKind(CXCursorKind K) {
switch (K) {
case CXCursor_MacroInstantiation:
case CXCursor_MacroDefinition:
return CompletionItemKind::Text;
case CXCursor_CXXMethod:
return CompletionItemKind::Method;
case CXCursor_FunctionDecl:
case CXCursor_FunctionTemplate:
return CompletionItemKind::Function;
case CXCursor_Constructor:
case CXCursor_Destructor:
return CompletionItemKind::Constructor;
case CXCursor_FieldDecl:
return CompletionItemKind::Field;
case CXCursor_VarDecl:
case CXCursor_ParmDecl:
return CompletionItemKind::Variable;
case CXCursor_ClassDecl:
case CXCursor_StructDecl:
case CXCursor_UnionDecl:
case CXCursor_ClassTemplate:
case CXCursor_ClassTemplatePartialSpecialization:
return CompletionItemKind::Class;
case CXCursor_Namespace:
case CXCursor_NamespaceAlias:
case CXCursor_NamespaceRef:
return CompletionItemKind::Module;
case CXCursor_EnumConstantDecl:
return CompletionItemKind::Value;
case CXCursor_EnumDecl:
return CompletionItemKind::Enum;
case CXCursor_TypeAliasDecl:
case CXCursor_TypeAliasTemplateDecl:
case CXCursor_TypedefDecl:
case CXCursor_MemberRef:
case CXCursor_TypeRef:
return CompletionItemKind::Reference;
default:
return CompletionItemKind::Missing;
}
}
ASTManager::ASTManager(JSONOutput &Output, DocumentStore &Store,
bool RunSynchronously)
: Output(Output), Store(Store), RunSynchronously(RunSynchronously),
PCHs(std::make_shared<PCHContainerOperations>()),
ClangWorker([this]() { runWorker(); }) {}
void ASTManager::runWorker() {
while (true) {
ASTManagerRequest Request;
// Pick request from the queue
{
std::unique_lock<std::mutex> Lock(RequestLock);
// Wait for more requests.
ClangRequestCV.wait(Lock,
[this] { return !RequestQueue.empty() || Done; });
if (Done)
return;
assert(!RequestQueue.empty() && "RequestQueue was empty");
Request = std::move(RequestQueue.back());
RequestQueue.pop_back();
// Skip outdated requests
if (Request.Version != DocVersions.find(Request.File)->second) {
Output.log("Version for " + Twine(Request.File) +
" in request is outdated, skipping request\n");
continue;
}
} // unlock RequestLock
handleRequest(Request.Type, Request.File);
}
}
void ASTManager::queueOrRun(ASTManagerRequestType RequestType, StringRef File) {
if (RunSynchronously) {
handleRequest(RequestType, File);
return;
}
std::lock_guard<std::mutex> Guard(RequestLock);
// We increment the version of the added document immediately and schedule
// the requested operation to be run on a worker thread
DocVersion version = ++DocVersions[File];
RequestQueue.push_back(ASTManagerRequest(RequestType, File, version));
ClangRequestCV.notify_one();
}
void ASTManager::handleRequest(ASTManagerRequestType RequestType,
StringRef File) {
switch (RequestType) {
case ASTManagerRequestType::ParseAndPublishDiagnostics:
parseFileAndPublishDiagnostics(File);
break;
case ASTManagerRequestType::RemoveDocData: {
std::lock_guard<std::mutex> Lock(ClangObjectLock);
auto DocDataIt = DocDatas.find(File);
// We could get the remove request before parsing for the document is
// started, just do nothing in that case, parsing request will be discarded
// because it has a lower version value
if (DocDataIt == DocDatas.end())
return;
DocDatas.erase(DocDataIt);
break;
} // unlock ClangObjectLock
}
}
void ASTManager::parseFileAndPublishDiagnostics(StringRef File) {
std::unique_lock<std::mutex> ClangObjectLockGuard(ClangObjectLock);
auto &DocData = DocDatas[File];
ASTUnit *Unit = DocData.getAST();
if (!Unit) {
auto newAST = createASTUnitForFile(File, this->Store);
Unit = newAST.get();
DocData.setAST(std::move(newAST));
} else {
// Do a reparse if this wasn't the first parse.
// FIXME: This might have the wrong working directory if it changed in the
// meantime.
Unit->Reparse(PCHs, getRemappedFiles(this->Store));
}
if (!Unit)
return;
// Send the diagnotics to the editor.
// FIXME: If the diagnostic comes from a different file, do we want to
// show them all? Right now we drop everything not coming from the
// main file.
std::string Diagnostics;
DocData::DiagnosticToReplacementMap LocalFixIts; // Temporary storage
for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
DEnd = Unit->stored_diag_end();
D != DEnd; ++D) {
if (!D->getLocation().isValid() ||
!D->getLocation().getManager().isInMainFile(D->getLocation()))
continue;
Position P;
P.line = D->getLocation().getSpellingLineNumber() - 1;
P.character = D->getLocation().getSpellingColumnNumber();
Range R = {P, P};
Diagnostics +=
R"({"range":)" + Range::unparse(R) +
R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) +
R"(,"message":")" + llvm::yaml::escape(D->getMessage()) +
R"("},)";
// We convert to Replacements to become independent of the SourceManager.
clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()), D->getMessage()};
auto &FixItsForDiagnostic = LocalFixIts[Diag];
for (const FixItHint &Fix : D->getFixIts()) {
FixItsForDiagnostic.push_back(clang::tooling::Replacement(
Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert));
}
}
// Put FixIts into place.
DocData.cacheFixIts(std::move(LocalFixIts));
ClangObjectLockGuard.unlock();
// No accesses to clang objects are allowed after this point.
// Publish diagnostics.
if (!Diagnostics.empty())
Diagnostics.pop_back(); // Drop trailing comma.
Output.writeMessage(
R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
URI::fromFile(File).uri + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
}
ASTManager::~ASTManager() {
{
std::lock_guard<std::mutex> Guard(RequestLock);
// Wake up the clang worker thread, then exit.
Done = true;
ClangRequestCV.notify_one();
} // unlock DocDataLock
ClangWorker.join();
}
void ASTManager::onDocumentAdd(StringRef File) {
queueOrRun(ASTManagerRequestType::ParseAndPublishDiagnostics, File);
}
void ASTManager::onDocumentRemove(StringRef File) {
queueOrRun(ASTManagerRequestType::RemoveDocData, File);
}
tooling::CompilationDatabase *
ASTManager::getOrCreateCompilationDatabaseForFile(StringRef File) {
namespace path = llvm::sys::path;
assert((path::is_absolute(File, path::Style::posix) ||
path::is_absolute(File, path::Style::windows)) &&
"path must be absolute");
for (auto Path = path::parent_path(File); !Path.empty();
Path = path::parent_path(Path)) {
auto CachedIt = CompilationDatabases.find(Path);
if (CachedIt != CompilationDatabases.end())
return CachedIt->second.get();
std::string Error;
auto CDB = tooling::CompilationDatabase::loadFromDirectory(Path, Error);
if (!CDB) {
if (!Error.empty()) {
Output.log("Error when trying to load compilation database from " +
Twine(Path) + ": " + Twine(Error) + "\n");
}
continue;
}
// TODO(ibiryukov): Invalidate cached compilation databases on changes
auto result = CDB.get();
CompilationDatabases.insert(std::make_pair(Path, std::move(CDB)));
return result;
}
Output.log("Failed to find compilation database for " + Twine(File) + "\n");
return nullptr;
}
std::unique_ptr<clang::ASTUnit>
ASTManager::createASTUnitForFile(StringRef File, const DocumentStore &Docs) {
tooling::CompilationDatabase *CDB =
getOrCreateCompilationDatabaseForFile(File);
std::vector<tooling::CompileCommand> Commands;
if (CDB) {
Commands = CDB->getCompileCommands(File);
// chdir. This is thread hostile.
if (!Commands.empty())
llvm::sys::fs::set_current_path(Commands.front().Directory);
}
if (Commands.empty()) {
// Add a fake command line if we know nothing.
Commands.push_back(tooling::CompileCommand(
llvm::sys::path::parent_path(File), llvm::sys::path::filename(File),
{"clang", "-fsyntax-only", File.str()}, ""));
}
// Inject the resource dir.
// FIXME: Don't overwrite it if it's already there.
static int Dummy; // Just an address in this process.
std::string ResourceDir =
CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
Commands.front().CommandLine.push_back("-resource-dir=" + ResourceDir);
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions);
std::vector<const char *> ArgStrs;
for (const auto &S : Commands.front().CommandLine)
ArgStrs.push_back(S.c_str());
auto ArgP = &*ArgStrs.begin();
return std::unique_ptr<clang::ASTUnit>(ASTUnit::LoadFromCommandLine(
ArgP, ArgP + ArgStrs.size(), PCHs, Diags, ResourceDir,
/*OnlyLocalDecls=*/false, /*CaptureDiagnostics=*/true,
getRemappedFiles(Docs),
/*RemappedFilesKeepOriginalName=*/true,
/*PrecompilePreambleAfterNParses=*/1, /*TUKind=*/TU_Complete,
/*CacheCodeCompletionResults=*/true,
/*IncludeBriefCommentsInCodeCompletion=*/true,
/*AllowPCHWithCompilerErrors=*/true));
}
std::vector<clang::tooling::Replacement>
ASTManager::getFixIts(StringRef File, const clangd::Diagnostic &D) {
// TODO(ibiryukov): the FixIts should be available immediately
// even when parsing is being run on a worker thread
std::lock_guard<std::mutex> Guard(ClangObjectLock);
return DocDatas[File].getFixIts(D);
}
namespace {
class CompletionItemsCollector : public CodeCompleteConsumer {
std::vector<CompletionItem> *Items;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
public:
CompletionItemsCollector(std::vector<CompletionItem> *Items,
const CodeCompleteOptions &CodeCompleteOpts)
: CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
Items(Items),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override {
for (unsigned I = 0; I != NumResults; ++I) {
CodeCompletionResult &Result = Results[I];
CodeCompletionString *CCS = Result.CreateCodeCompletionString(
S, Context, *Allocator, CCTUInfo,
CodeCompleteOpts.IncludeBriefComments);
if (CCS) {
CompletionItem Item;
assert(CCS->getTypedText());
Item.label = CCS->getTypedText();
Item.kind = getKind(Result.CursorKind);
if (CCS->getBriefComment())
Item.documentation = CCS->getBriefComment();
Items->push_back(std::move(Item));
}
}
}
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
};
} // namespace
std::vector<CompletionItem>
ASTManager::codeComplete(StringRef File, unsigned Line, unsigned Column) {
CodeCompleteOptions CCO;
CCO.IncludeBriefComments = 1;
// This is where code completion stores dirty buffers. Need to free after
// completion.
SmallVector<const llvm::MemoryBuffer *, 4> OwnedBuffers;
SmallVector<StoredDiagnostic, 4> StoredDiagnostics;
IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine(
new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions));
std::vector<CompletionItem> Items;
CompletionItemsCollector Collector(&Items, CCO);
std::lock_guard<std::mutex> Guard(ClangObjectLock);
auto &DocData = DocDatas[File];
auto Unit = DocData.getAST();
if (!Unit) {
auto newAST = createASTUnitForFile(File, this->Store);
Unit = newAST.get();
DocData.setAST(std::move(newAST));
}
if (!Unit)
return {};
IntrusiveRefCntPtr<SourceManager> SourceMgr(
new SourceManager(*DiagEngine, Unit->getFileManager()));
// CodeComplete seems to require fresh LangOptions.
LangOptions LangOpts = Unit->getLangOpts();
// The language server protocol uses zero-based line and column numbers.
// The clang code completion uses one-based numbers.
Unit->CodeComplete(File, Line + 1, Column + 1, getRemappedFiles(this->Store),
CCO.IncludeMacros, CCO.IncludeCodePatterns,
CCO.IncludeBriefComments, Collector, PCHs, *DiagEngine,
LangOpts, *SourceMgr, Unit->getFileManager(),
StoredDiagnostics, OwnedBuffers);
for (const llvm::MemoryBuffer *Buffer : OwnedBuffers)
delete Buffer;
return Items;
}

View File

@ -1,162 +0,0 @@
//===--- ASTManager.h - Clang AST manager -----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMANAGER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_ASTMANAGER_H
#include "DocumentStore.h"
#include "JSONRPCDispatcher.h"
#include "Protocol.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include <condition_variable>
#include <deque>
#include <thread>
namespace clang {
class ASTUnit;
class DiagnosticsEngine;
class PCHContainerOperations;
namespace tooling {
class CompilationDatabase;
} // namespace tooling
namespace clangd {
/// Using 'unsigned' here to avoid undefined behaviour on overflow.
typedef unsigned DocVersion;
/// Stores ASTUnit and FixIts map for an opened document.
class DocData {
public:
typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
DiagnosticToReplacementMap;
public:
void setAST(std::unique_ptr<ASTUnit> AST);
ASTUnit *getAST() const;
void cacheFixIts(DiagnosticToReplacementMap FixIts);
std::vector<clang::tooling::Replacement>
getFixIts(const clangd::Diagnostic &D) const;
private:
std::unique_ptr<ASTUnit> AST;
DiagnosticToReplacementMap FixIts;
};
enum class ASTManagerRequestType { ParseAndPublishDiagnostics, RemoveDocData };
/// A request to the worker thread
class ASTManagerRequest {
public:
ASTManagerRequest() = default;
ASTManagerRequest(ASTManagerRequestType Type, std::string File,
DocVersion Version);
ASTManagerRequestType Type;
std::string File;
DocVersion Version;
};
class ASTManager : public DocumentStoreListener {
public:
ASTManager(JSONOutput &Output, DocumentStore &Store, bool RunSynchronously);
~ASTManager() override;
void onDocumentAdd(StringRef File) override;
void onDocumentRemove(StringRef File) override;
/// Get code completions at a specified \p Line and \p Column in \p File.
///
/// This function is thread-safe and returns completion items that own the
/// data they contain.
std::vector<CompletionItem> codeComplete(StringRef File, unsigned Line,
unsigned Column);
/// Get the fixes associated with a certain diagnostic in a specified file as
/// replacements.
///
/// This function is thread-safe. It returns a copy to avoid handing out
/// references to unguarded data.
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
DocumentStore &getStore() const { return Store; }
private:
JSONOutput &Output;
DocumentStore &Store;
// Set to true if requests should block instead of being processed
// asynchronously.
bool RunSynchronously;
/// Loads a compilation database for File. May return nullptr if it fails. The
/// database is cached for subsequent accesses.
clang::tooling::CompilationDatabase *
getOrCreateCompilationDatabaseForFile(StringRef File);
// Creates a new ASTUnit for the document at File.
// FIXME: This calls chdir internally, which is thread unsafe.
std::unique_ptr<clang::ASTUnit>
createASTUnitForFile(StringRef File, const DocumentStore &Docs);
/// If RunSynchronously is false, queues the request to be run on the worker
/// thread.
/// If RunSynchronously is true, runs the request handler immediately on the
/// main thread.
void queueOrRun(ASTManagerRequestType RequestType, StringRef File);
void runWorker();
void handleRequest(ASTManagerRequestType RequestType, StringRef File);
/// Parses files and publishes diagnostics.
/// This function is called on the worker thread in asynchronous mode and
/// on the main thread in synchronous mode.
void parseFileAndPublishDiagnostics(StringRef File);
/// Caches compilation databases loaded from directories(keys are directories).
llvm::StringMap<std::unique_ptr<clang::tooling::CompilationDatabase>>
CompilationDatabases;
/// Clang objects.
/// A map from filenames to DocData structures that store ASTUnit and Fixits for
/// the files. The ASTUnits are used for generating diagnostics and fix-it-s
/// asynchronously by the worker thread and synchronously for code completion.
llvm::StringMap<DocData> DocDatas;
std::shared_ptr<clang::PCHContainerOperations> PCHs;
/// A lock for access to the DocDatas, CompilationDatabases and PCHs.
std::mutex ClangObjectLock;
/// Stores latest versions of the tracked documents to discard outdated requests.
/// Guarded by RequestLock.
/// TODO(ibiryukov): the entries are neved deleted from this map.
llvm::StringMap<DocVersion> DocVersions;
/// A LIFO queue of requests. Note that requests are discarded if the `version`
/// field is not equal to the one stored inside DocVersions.
/// TODO(krasimir): code completion should always have priority over parsing
/// for diagnostics.
std::deque<ASTManagerRequest> RequestQueue;
/// Setting Done to true will make the worker thread terminate.
bool Done = false;
/// Condition variable to wake up the worker thread.
std::condition_variable ClangRequestCV;
/// Lock for accesses to RequestQueue, DocVersions and Done.
std::mutex RequestLock;
/// We run parsing on a separate thread. This thread looks into RequestQueue to
/// find requests to handle and terminates when Done is set to true.
std::thread ClangWorker;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -1,6 +1,11 @@
add_clang_executable(clangd
ASTManager.cpp
ClangdLSPServer.cpp
ClangdMain.cpp
ClangdServer.cpp
ClangdUnit.cpp
ClangdUnitStore.cpp
DraftStore.cpp
GlobalCompilationDatabase.cpp
JSONRPCDispatcher.cpp
Protocol.cpp
ProtocolHandlers.cpp

View File

@ -0,0 +1,100 @@
//===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#include "ClangdLSPServer.h"
#include "JSONRPCDispatcher.h"
using namespace clang::clangd;
using namespace clang;
class ClangdLSPServer::LSPDiagnosticsConsumer : public DiagnosticsConsumer {
public:
LSPDiagnosticsConsumer(ClangdLSPServer &Server) : Server(Server) {}
virtual void onDiagnosticsReady(PathRef File,
std::vector<DiagWithFixIts> Diagnostics) {
Server.consumeDiagnostics(File, Diagnostics);
}
private:
ClangdLSPServer &Server;
};
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
: Out(Out),
Server(llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(),
llvm::make_unique<LSPDiagnosticsConsumer>(*this),
RunSynchronously) {}
void ClangdLSPServer::openDocument(StringRef File, StringRef Contents) {
Server.addDocument(File, Contents);
}
void ClangdLSPServer::closeDocument(StringRef File) {
Server.removeDocument(File);
}
std::vector<CompletionItem> ClangdLSPServer::codeComplete(PathRef File,
Position Pos) {
return Server.codeComplete(File, Pos);
}
std::vector<clang::tooling::Replacement>
ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
std::lock_guard<std::mutex> Lock(FixItsMutex);
auto DiagToFixItsIter = FixItsMap.find(File);
if (DiagToFixItsIter == FixItsMap.end())
return {};
const auto &DiagToFixItsMap = DiagToFixItsIter->second;
auto FixItsIter = DiagToFixItsMap.find(D);
if (FixItsIter == DiagToFixItsMap.end())
return {};
return FixItsIter->second;
}
std::string ClangdLSPServer::getDocument(PathRef File) {
return Server.getDocument(File);
}
void ClangdLSPServer::consumeDiagnostics(
PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
std::string DiagnosticsJSON;
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
for (auto &DiagWithFixes : Diagnostics) {
auto Diag = DiagWithFixes.Diag;
DiagnosticsJSON +=
R"({"range":)" + Range::unparse(Diag.range) +
R"(,"severity":)" + std::to_string(Diag.severity) +
R"(,"message":")" + llvm::yaml::escape(Diag.message) +
R"("},)";
// We convert to Replacements to become independent of the SourceManager.
auto &FixItsForDiagnostic = LocalFixIts[Diag];
std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
std::back_inserter(FixItsForDiagnostic));
}
// Cache FixIts
{
// FIXME(ibiryukov): should be deleted when documents are removed
std::lock_guard<std::mutex> Lock(FixItsMutex);
FixItsMap[File] = LocalFixIts;
}
// Publish diagnostics.
if (!DiagnosticsJSON.empty())
DiagnosticsJSON.pop_back(); // Drop trailing comma.
Out.writeMessage(
R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
R"(]}})");
}

View File

@ -0,0 +1,82 @@
//===--- ClangdLSPServer.h - LSP server --------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H
#include "ClangdServer.h"
#include "Path.h"
#include "Protocol.h"
#include "clang/Tooling/Core/Replacement.h"
namespace clang {
namespace clangd {
class JSONOutput;
/// This class serves as an intermediate layer of LSP server implementation,
/// glueing the JSON LSP protocol layer and ClangdServer together. It doesn't
/// directly handle input from LSP client.
/// Most methods are synchronous and return their result directly, but
/// diagnostics are provided asynchronously when ready via
/// JSONOutput::writeMessage.
class ClangdLSPServer {
public:
ClangdLSPServer(JSONOutput &Out, bool RunSynchronously);
/// Update the document text for \p File with \p Contents, schedule update of
/// diagnostics. Out.writeMessage will called to push diagnostics to LSP
/// client asynchronously when they are ready.
void openDocument(PathRef File, StringRef Contents);
/// Stop tracking the document for \p File.
void closeDocument(PathRef File);
/// Run code completion synchronously.
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
/// Get the fixes associated with a certain diagnostic in a specified file as
/// replacements.
///
/// This function is thread-safe. It returns a copy to avoid handing out
/// references to unguarded data.
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
/// Get the current document contents stored for \p File.
/// FIXME(ibiryukov): This function is here to allow implementation of
/// formatCode from ProtocolHandlers.cpp. We should move formatCode to
/// ClangdServer class and remove this function from public interface.
std::string getDocument(PathRef File);
private:
class LSPDiagnosticsConsumer;
/// Function that will be called on a separate thread when diagnostics are
/// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches
/// corresponding fixits in the FixItsMap.
void consumeDiagnostics(PathRef File,
std::vector<DiagWithFixIts> Diagnostics);
JSONOutput &Out;
std::mutex FixItsMutex;
typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
DiagnosticToReplacementMap;
/// Caches FixIts per file and diagnostics
llvm::StringMap<DiagnosticToReplacementMap> FixItsMap;
// Server must be the last member of the class to allow its destructor to exit
// the worker thread that may otherwise run an async callback on partially
// destructed instance of ClangdLSPServer.
ClangdServer Server;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -7,15 +7,19 @@
//
//===----------------------------------------------------------------------===//
#include "ASTManager.h"
#include "DocumentStore.h"
#include "JSONRPCDispatcher.h"
#include "ClangdLSPServer.h"
#include "Protocol.h"
#include "ProtocolHandlers.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Program.h"
#include <iostream>
#include <memory>
#include <string>
using namespace clang;
using namespace clang::clangd;
static llvm::cl::opt<bool>
@ -34,9 +38,7 @@ int main(int argc, char *argv[]) {
// Set up a document store and intialize all the method handlers for JSONRPC
// dispatching.
DocumentStore Store;
ASTManager AST(Out, Store, RunSynchronously);
Store.addListener(&AST);
ClangdLSPServer LSPServer(Out, RunSynchronously);
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
Dispatcher.registerHandler("initialize",
llvm::make_unique<InitializeHandler>(Out));
@ -45,26 +47,26 @@ int main(int argc, char *argv[]) {
Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
Dispatcher.registerHandler(
"textDocument/didOpen",
llvm::make_unique<TextDocumentDidOpenHandler>(Out, Store));
llvm::make_unique<TextDocumentDidOpenHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/didClose",
llvm::make_unique<TextDocumentDidCloseHandler>(Out, Store));
llvm::make_unique<TextDocumentDidCloseHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/didChange",
llvm::make_unique<TextDocumentDidChangeHandler>(Out, Store));
llvm::make_unique<TextDocumentDidChangeHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/rangeFormatting",
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, Store));
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/onTypeFormatting",
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, Store));
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/formatting",
llvm::make_unique<TextDocumentFormattingHandler>(Out, Store));
llvm::make_unique<TextDocumentFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler("textDocument/codeAction",
llvm::make_unique<CodeActionHandler>(Out, AST));
llvm::make_unique<CodeActionHandler>(Out, LSPServer));
Dispatcher.registerHandler("textDocument/completion",
llvm::make_unique<CompletionHandler>(Out, AST));
llvm::make_unique<CompletionHandler>(Out, LSPServer));
while (std::cin.good()) {
// A Language Server Protocol message starts with a HTTP header, delimited

View File

@ -0,0 +1,149 @@
//===--- 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.
//
//===-------------------------------------------------------------------===//
#include "ClangdServer.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/FileSystem.h"
using namespace clang::clangd;
WorkerRequest::WorkerRequest(WorkerRequestKind Kind, Path File,
DocVersion Version)
: Kind(Kind), File(File), Version(Version) {}
ClangdScheduler::ClangdScheduler(ClangdServer &Server, bool RunSynchronously)
: RunSynchronously(RunSynchronously) {
if (RunSynchronously) {
// Don't start the worker thread if we're running synchronously
return;
}
// Initialize Worker in ctor body, rather than init list to avoid potentially
// using not-yet-initialized members
Worker = std::thread([&Server, this]() {
while (true) {
WorkerRequest Request;
// Pick request from the queue
{
std::unique_lock<std::mutex> Lock(Mutex);
// Wait for more requests.
RequestCV.wait(Lock, [this] { return !RequestQueue.empty() || Done; });
if (Done)
return;
assert(!RequestQueue.empty() && "RequestQueue was empty");
Request = std::move(RequestQueue.back());
RequestQueue.pop_back();
// Skip outdated requests
if (Request.Version != Server.DraftMgr.getVersion(Request.File)) {
// FIXME(ibiryukov): Logging
// Output.log("Version for " + Twine(Request.File) +
// " in request is outdated, skipping request\n");
continue;
}
} // unlock Mutex
Server.handleRequest(std::move(Request));
}
});
}
ClangdScheduler::~ClangdScheduler() {
if (RunSynchronously)
return; // no worker thread is running in that case
{
std::lock_guard<std::mutex> Lock(Mutex);
// Wake up the worker thread
Done = true;
RequestCV.notify_one();
} // unlock Mutex
Worker.join();
}
void ClangdScheduler::enqueue(ClangdServer &Server, WorkerRequest Request) {
if (RunSynchronously) {
Server.handleRequest(Request);
return;
}
std::lock_guard<std::mutex> Lock(Mutex);
RequestQueue.push_back(Request);
RequestCV.notify_one();
}
ClangdServer::ClangdServer(std::unique_ptr<GlobalCompilationDatabase> CDB,
std::unique_ptr<DiagnosticsConsumer> DiagConsumer,
bool RunSynchronously)
: CDB(std::move(CDB)), DiagConsumer(std::move(DiagConsumer)),
PCHs(std::make_shared<PCHContainerOperations>()),
WorkScheduler(*this, RunSynchronously) {}
void ClangdServer::addDocument(PathRef File, StringRef Contents) {
DocVersion NewVersion = DraftMgr.updateDraft(File, Contents);
WorkScheduler.enqueue(
*this, WorkerRequest(WorkerRequestKind::ParseAndPublishDiagnostics, File,
NewVersion));
}
void ClangdServer::removeDocument(PathRef File) {
auto NewVersion = DraftMgr.removeDraft(File);
WorkScheduler.enqueue(
*this, WorkerRequest(WorkerRequestKind::RemoveDocData, File, NewVersion));
}
std::vector<CompletionItem> ClangdServer::codeComplete(PathRef File,
Position Pos) {
auto FileContents = DraftMgr.getDraft(File);
assert(FileContents.Draft && "codeComplete is called for non-added document");
std::vector<CompletionItem> Result;
Units.runOnUnitWithoutReparse(
File, *FileContents.Draft, *CDB, PCHs, [&](ClangdUnit &Unit) {
Result = Unit.codeComplete(*FileContents.Draft, Pos);
});
return Result;
}
std::string ClangdServer::getDocument(PathRef File) {
auto draft = DraftMgr.getDraft(File);
assert(draft.Draft && "File is not tracked, cannot get contents");
return *draft.Draft;
}
void ClangdServer::handleRequest(WorkerRequest Request) {
switch (Request.Kind) {
case WorkerRequestKind::ParseAndPublishDiagnostics: {
auto FileContents = DraftMgr.getDraft(Request.File);
if (FileContents.Version != Request.Version)
return; // This request is outdated, do nothing
assert(FileContents.Draft &&
"No contents inside a file that was scheduled for reparse");
Units.runOnUnit(Request.File, *FileContents.Draft, *CDB, PCHs,
[&](ClangdUnit const &Unit) {
DiagConsumer->onDiagnosticsReady(
Request.File, Unit.getLocalDiagnostics());
});
break;
}
case WorkerRequestKind::RemoveDocData:
if (Request.Version != DraftMgr.getVersion(Request.File))
return; // This request is outdated, do nothing
Units.removeUnitIfPresent(Request.File);
break;
}
}

View File

@ -0,0 +1,138 @@
//===--- ClangdServer.h - 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.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
#include "ClangdUnitStore.h"
#include "DraftStore.h"
#include "GlobalCompilationDatabase.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
#include "ClangdUnit.h"
#include "Protocol.h"
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include <utility>
namespace clang {
class PCHContainerOperations;
namespace clangd {
class DiagnosticsConsumer {
public:
virtual ~DiagnosticsConsumer() = default;
/// Called by ClangdServer when \p Diagnostics for \p File are ready.
virtual void onDiagnosticsReady(PathRef File,
std::vector<DiagWithFixIts> Diagnostics) = 0;
};
enum class WorkerRequestKind { ParseAndPublishDiagnostics, RemoveDocData };
/// A request to the worker thread
class WorkerRequest {
public:
WorkerRequest() = default;
WorkerRequest(WorkerRequestKind Kind, Path File, DocVersion Version);
WorkerRequestKind Kind;
Path File;
DocVersion Version;
};
class ClangdServer;
/// Handles running WorkerRequests of ClangdServer on a separate threads.
/// Currently runs only one worker thread.
class ClangdScheduler {
public:
ClangdScheduler(ClangdServer &Server, bool RunSynchronously);
~ClangdScheduler();
/// Enqueue WorkerRequest to be run on a worker thread
void enqueue(ClangdServer &Server, WorkerRequest Request);
private:
bool RunSynchronously;
std::mutex Mutex;
/// We run some tasks on a separate thread(parsing, ClangdUnit cleanup).
/// This thread looks into RequestQueue to find requests to handle and
/// terminates when Done is set to true.
std::thread Worker;
/// Setting Done to true will make the worker thread terminate.
bool Done = false;
/// A LIFO queue of requests. Note that requests are discarded if the
/// `version` field is not equal to the one stored inside DraftStore.
/// FIXME(krasimir): code completion should always have priority over parsing
/// for diagnostics.
std::deque<WorkerRequest> RequestQueue;
/// Condition variable to wake up the worker thread.
std::condition_variable RequestCV;
};
/// Provides API to manage ASTs for a collection of C++ files and request
/// various language features(currently, only codeCompletion and async
/// diagnostics for tracked files).
class ClangdServer {
public:
ClangdServer(std::unique_ptr<GlobalCompilationDatabase> CDB,
std::unique_ptr<DiagnosticsConsumer> DiagConsumer,
bool RunSynchronously);
/// Add a \p File to the list of tracked C++ files or update the contents if
/// \p File is already tracked. Also schedules parsing of the AST for it on a
/// separate thread. When the parsing is complete, DiagConsumer passed in
/// constructor will receive onDiagnosticsReady callback.
void addDocument(PathRef File, StringRef Contents);
/// Remove \p File from list of tracked files, schedule a request to free
/// resources associated with it.
void removeDocument(PathRef File);
/// Run code completion for \p File at \p Pos.
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
/// Gets current document contents for \p File. \p File must point to a
/// currently tracked file.
/// FIXME(ibiryukov): This function is here to allow implementation of
/// formatCode from ProtocolHandlers.cpp. We should move formatCode to this
/// class and remove this function from public interface.
std::string getDocument(PathRef File);
private:
friend class ClangdScheduler;
/// This function is called on a worker thread.
void handleRequest(WorkerRequest Request);
std::unique_ptr<GlobalCompilationDatabase> CDB;
std::unique_ptr<DiagnosticsConsumer> DiagConsumer;
DraftStore DraftMgr;
ClangdUnitStore Units;
std::shared_ptr<PCHContainerOperations> PCHs;
// WorkScheduler has to be the last member, because its destructor has to be
// called before all other members to stop the worker thread that references
// ClangdServer
ClangdScheduler WorkScheduler;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -0,0 +1,224 @@
//===--- ClangdUnit.cpp -----------------------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#include "ClangdUnit.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
using namespace clang::clangd;
using namespace clang;
ClangdUnit::ClangdUnit(PathRef FileName, StringRef Contents,
std::shared_ptr<PCHContainerOperations> PCHs,
std::vector<tooling::CompileCommand> Commands)
: FileName(FileName), PCHs(PCHs) {
assert(!Commands.empty() && "No compile commands provided");
// Inject the resource dir.
// FIXME: Don't overwrite it if it's already there.
static int Dummy; // Just an address in this process.
std::string ResourceDir =
CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
Commands.front().CommandLine.push_back("-resource-dir=" + ResourceDir);
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions);
std::vector<const char *> ArgStrs;
for (const auto &S : Commands.front().CommandLine)
ArgStrs.push_back(S.c_str());
ASTUnit::RemappedFile RemappedSource(
FileName,
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release());
auto ArgP = &*ArgStrs.begin();
Unit = std::unique_ptr<ASTUnit>(ASTUnit::LoadFromCommandLine(
ArgP, ArgP + ArgStrs.size(), PCHs, Diags, ResourceDir,
/*OnlyLocalDecls=*/false, /*CaptureDiagnostics=*/true, RemappedSource,
/*RemappedFilesKeepOriginalName=*/true,
/*PrecompilePreambleAfterNParses=*/1, /*TUKind=*/TU_Complete,
/*CacheCodeCompletionResults=*/true,
/*IncludeBriefCommentsInCodeCompletion=*/true,
/*AllowPCHWithCompilerErrors=*/true));
}
void ClangdUnit::reparse(StringRef Contents) {
// Do a reparse if this wasn't the first parse.
// FIXME: This might have the wrong working directory if it changed in the
// meantime.
ASTUnit::RemappedFile RemappedSource(
FileName,
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release());
Unit->Reparse(PCHs, RemappedSource);
}
namespace {
CompletionItemKind getKind(CXCursorKind K) {
switch (K) {
case CXCursor_MacroInstantiation:
case CXCursor_MacroDefinition:
return CompletionItemKind::Text;
case CXCursor_CXXMethod:
return CompletionItemKind::Method;
case CXCursor_FunctionDecl:
case CXCursor_FunctionTemplate:
return CompletionItemKind::Function;
case CXCursor_Constructor:
case CXCursor_Destructor:
return CompletionItemKind::Constructor;
case CXCursor_FieldDecl:
return CompletionItemKind::Field;
case CXCursor_VarDecl:
case CXCursor_ParmDecl:
return CompletionItemKind::Variable;
case CXCursor_ClassDecl:
case CXCursor_StructDecl:
case CXCursor_UnionDecl:
case CXCursor_ClassTemplate:
case CXCursor_ClassTemplatePartialSpecialization:
return CompletionItemKind::Class;
case CXCursor_Namespace:
case CXCursor_NamespaceAlias:
case CXCursor_NamespaceRef:
return CompletionItemKind::Module;
case CXCursor_EnumConstantDecl:
return CompletionItemKind::Value;
case CXCursor_EnumDecl:
return CompletionItemKind::Enum;
case CXCursor_TypeAliasDecl:
case CXCursor_TypeAliasTemplateDecl:
case CXCursor_TypedefDecl:
case CXCursor_MemberRef:
case CXCursor_TypeRef:
return CompletionItemKind::Reference;
default:
return CompletionItemKind::Missing;
}
}
class CompletionItemsCollector : public CodeCompleteConsumer {
std::vector<CompletionItem> *Items;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
public:
CompletionItemsCollector(std::vector<CompletionItem> *Items,
const CodeCompleteOptions &CodeCompleteOpts)
: CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
Items(Items),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override {
for (unsigned I = 0; I != NumResults; ++I) {
CodeCompletionResult &Result = Results[I];
CodeCompletionString *CCS = Result.CreateCodeCompletionString(
S, Context, *Allocator, CCTUInfo,
CodeCompleteOpts.IncludeBriefComments);
if (CCS) {
CompletionItem Item;
assert(CCS->getTypedText());
Item.label = CCS->getTypedText();
Item.kind = getKind(Result.CursorKind);
if (CCS->getBriefComment())
Item.documentation = CCS->getBriefComment();
Items->push_back(std::move(Item));
}
}
}
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
};
} // namespace
std::vector<CompletionItem> ClangdUnit::codeComplete(StringRef Contents,
Position Pos) {
CodeCompleteOptions CCO;
CCO.IncludeBriefComments = 1;
// This is where code completion stores dirty buffers. Need to free after
// completion.
SmallVector<const llvm::MemoryBuffer *, 4> OwnedBuffers;
SmallVector<StoredDiagnostic, 4> StoredDiagnostics;
IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine(
new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions));
std::vector<CompletionItem> Items;
CompletionItemsCollector Collector(&Items, CCO);
ASTUnit::RemappedFile RemappedSource(
FileName,
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName).release());
IntrusiveRefCntPtr<SourceManager> SourceMgr(
new SourceManager(*DiagEngine, Unit->getFileManager()));
// CodeComplete seems to require fresh LangOptions.
LangOptions LangOpts = Unit->getLangOpts();
// The language server protocol uses zero-based line and column numbers.
// The clang code completion uses one-based numbers.
Unit->CodeComplete(FileName, Pos.line + 1, Pos.character + 1, RemappedSource,
CCO.IncludeMacros, CCO.IncludeCodePatterns,
CCO.IncludeBriefComments, Collector, PCHs, *DiagEngine,
LangOpts, *SourceMgr, Unit->getFileManager(),
StoredDiagnostics, OwnedBuffers);
for (const llvm::MemoryBuffer *Buffer : OwnedBuffers)
delete Buffer;
return Items;
}
namespace {
/// Convert from clang diagnostic level to LSP severity.
static int getSeverity(DiagnosticsEngine::Level L) {
switch (L) {
case DiagnosticsEngine::Remark:
return 4;
case DiagnosticsEngine::Note:
return 3;
case DiagnosticsEngine::Warning:
return 2;
case DiagnosticsEngine::Fatal:
case DiagnosticsEngine::Error:
return 1;
case DiagnosticsEngine::Ignored:
return 0;
}
llvm_unreachable("Unknown diagnostic level!");
}
} // namespace
std::vector<DiagWithFixIts> ClangdUnit::getLocalDiagnostics() const {
std::vector<DiagWithFixIts> Result;
for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
DEnd = Unit->stored_diag_end();
D != DEnd; ++D) {
if (!D->getLocation().isValid() ||
!D->getLocation().getManager().isInMainFile(D->getLocation()))
continue;
Position P;
P.line = D->getLocation().getSpellingLineNumber() - 1;
P.character = D->getLocation().getSpellingColumnNumber();
Range R = {P, P};
clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()), D->getMessage()};
llvm::SmallVector<tooling::Replacement, 1> FixItsForDiagnostic;
for (const FixItHint &Fix : D->getFixIts()) {
FixItsForDiagnostic.push_back(clang::tooling::Replacement(
Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert));
}
Result.push_back({Diag, std::move(FixItsForDiagnostic)});
}
return Result;
}

View File

@ -0,0 +1,63 @@
//===--- ClangdUnit.h -------------------------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNIT_H
#include "Protocol.h"
#include "Path.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/Core/Replacement.h"
#include <memory>
namespace clang {
class ASTUnit;
class PCHContainerOperations;
namespace tooling {
struct CompileCommand;
}
namespace clangd {
/// A diagnostic with its FixIts.
struct DiagWithFixIts {
clangd::Diagnostic Diag;
llvm::SmallVector<tooling::Replacement, 1> FixIts;
};
/// Stores parsed C++ AST and provides implementations of all operations clangd
/// would want to perform on parsed C++ files.
class ClangdUnit {
public:
ClangdUnit(PathRef FileName, StringRef Contents,
std::shared_ptr<PCHContainerOperations> PCHs,
std::vector<tooling::CompileCommand> Commands);
/// Reparse with new contents.
void reparse(StringRef Contents);
/// Get code completions at a specified \p Line and \p Column in \p File.
///
/// This function is thread-safe and returns completion items that own the
/// data they contain.
std::vector<CompletionItem> codeComplete(StringRef Contents, Position Pos);
/// Returns diagnostics and corresponding FixIts for each diagnostic that are
/// located in the current file.
std::vector<DiagWithFixIts> getLocalDiagnostics() const;
private:
Path FileName;
std::unique_ptr<ASTUnit> Unit;
std::shared_ptr<PCHContainerOperations> PCHs;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -0,0 +1,34 @@
//===--- ClangdUnitStore.cpp - A ClangdUnits container -----------*-C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ClangdUnitStore.h"
#include "llvm/Support/Path.h"
using namespace clang::clangd;
using namespace clang;
void ClangdUnitStore::removeUnitIfPresent(PathRef File) {
std::lock_guard<std::mutex> Lock(Mutex);
auto It = OpenedFiles.find(File);
if (It == OpenedFiles.end())
return;
OpenedFiles.erase(It);
}
std::vector<tooling::CompileCommand> ClangdUnitStore::getCompileCommands(GlobalCompilationDatabase &CDB, PathRef File) {
std::vector<tooling::CompileCommand> Commands = CDB.getCompileCommands(File);
if (Commands.empty()) {
// Add a fake command line if we know nothing.
Commands.push_back(tooling::CompileCommand(
llvm::sys::path::parent_path(File), llvm::sys::path::filename(File),
{"clang", "-fsyntax-only", File.str()}, ""));
}
return Commands;
}

View File

@ -0,0 +1,93 @@
//===--- ClangdUnitStore.h - A ClangdUnits container -------------*-C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNITSTORE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDUNITSTORE_H
#include <mutex>
#include "ClangdUnit.h"
#include "GlobalCompilationDatabase.h"
#include "Path.h"
#include "clang/Tooling/CompilationDatabase.h"
namespace clang {
namespace clangd {
/// Thread-safe collection of ASTs built for specific files. Provides
/// synchronized access to ASTs.
class ClangdUnitStore {
public:
/// Run specified \p Action on the ClangdUnit for \p File.
/// If the file is not present in ClangdUnitStore, a new ClangdUnit will be
/// created from the \p FileContents. If the file is already present in the
/// store, ClangdUnit::reparse will be called with the new contents before
/// running \p Action.
template <class Func>
void runOnUnit(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
std::shared_ptr<PCHContainerOperations> PCHs, Func Action) {
runOnUnitImpl(File, FileContents, CDB, PCHs, /*ReparseBeforeAction=*/true,
std::forward<Func>(Action));
}
/// Run specified \p Action on the ClangdUnit for \p File.
/// If the file is not present in ClangdUnitStore, a new ClangdUnit will be
/// created from the \p FileContents. If the file is already present in the
/// store, the \p Action will be run directly on it.
template <class Func>
void runOnUnitWithoutReparse(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
std::shared_ptr<PCHContainerOperations> PCHs,
Func Action) {
runOnUnitImpl(File, FileContents, CDB, PCHs, /*ReparseBeforeAction=*/false,
std::forward<Func>(Action));
}
/// Remove ClangdUnit for \p File, if any
void removeUnitIfPresent(PathRef File);
private:
/// Run specified \p Action on the ClangdUnit for \p File.
template <class Func>
void runOnUnitImpl(PathRef File, StringRef FileContents,
GlobalCompilationDatabase &CDB,
std::shared_ptr<PCHContainerOperations> PCHs,
bool ReparseBeforeAction, Func Action) {
std::lock_guard<std::mutex> Lock(Mutex);
auto Commands = getCompileCommands(CDB, File);
assert(!Commands.empty() &&
"getCompileCommands should add default command");
// chdir. This is thread hostile.
// FIXME(ibiryukov): get rid of this
llvm::sys::fs::set_current_path(Commands.front().Directory);
auto It = OpenedFiles.find(File);
if (It == OpenedFiles.end()) {
It = OpenedFiles
.insert(std::make_pair(
File, ClangdUnit(File, FileContents, PCHs, Commands)))
.first;
} else if (ReparseBeforeAction) {
It->second.reparse(FileContents);
}
return Action(It->second);
}
std::vector<tooling::CompileCommand>
getCompileCommands(GlobalCompilationDatabase &CDB, PathRef File);
std::mutex Mutex;
llvm::StringMap<ClangdUnit> OpenedFiles;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -1,86 +0,0 @@
//===--- DocumentStore.h - File contents container --------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/StringMap.h"
#include <mutex>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
class DocumentStore;
struct DocumentStoreListener {
virtual ~DocumentStoreListener() = default;
virtual void onDocumentAdd(StringRef File) {}
virtual void onDocumentRemove(StringRef File) {}
};
/// A container for files opened in a workspace, addressed by File. The contents
/// are owned by the DocumentStore.
class DocumentStore {
public:
/// Add a document to the store. Overwrites existing contents.
void addDocument(StringRef File, StringRef Text) {
{
std::lock_guard<std::mutex> Guard(DocsMutex);
Docs[File] = Text;
}
for (const auto &Listener : Listeners)
Listener->onDocumentAdd(File);
}
/// Delete a document from the store.
void removeDocument(StringRef File) {
{
std::lock_guard<std::mutex> Guard(DocsMutex);
Docs.erase(File);
}
for (const auto &Listener : Listeners)
Listener->onDocumentRemove(File);
}
/// Retrieve a document from the store. Empty string if it's unknown.
///
/// This function is thread-safe. It returns a copy to avoid handing out
/// references to unguarded data.
std::string getDocument(StringRef File) const {
// FIXME: This could be a reader lock.
std::lock_guard<std::mutex> Guard(DocsMutex);
return Docs.lookup(File);
}
/// Add a listener. Does not take ownership.
void addListener(DocumentStoreListener *DSL) { Listeners.push_back(DSL); }
/// Get name and constents of all documents in this store.
///
/// This function is thread-safe. It returns a copies to avoid handing out
/// references to unguarded data.
std::vector<std::pair<std::string, std::string>> getAllDocuments() const {
std::vector<std::pair<std::string, std::string>> AllDocs;
std::lock_guard<std::mutex> Guard(DocsMutex);
for (const auto &P : Docs)
AllDocs.emplace_back(P.first(), P.second);
return AllDocs;
}
private:
llvm::StringMap<std::string> Docs;
std::vector<DocumentStoreListener *> Listeners;
mutable std::mutex DocsMutex;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -0,0 +1,48 @@
//===--- DraftStore.cpp - File contents container ---------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "DraftStore.h"
using namespace clang::clangd;
VersionedDraft DraftStore::getDraft(PathRef File) const {
std::lock_guard<std::mutex> Lock(Mutex);
auto It = Drafts.find(File);
if (It == Drafts.end())
return {0, llvm::None};
return It->second;
}
DocVersion DraftStore::getVersion(PathRef File) const {
std::lock_guard<std::mutex> Lock(Mutex);
auto It = Drafts.find(File);
if (It == Drafts.end())
return 0;
return It->second.Version;
}
DocVersion DraftStore::updateDraft(PathRef File, StringRef Contents) {
std::lock_guard<std::mutex> Lock(Mutex);
auto &Entry = Drafts[File];
DocVersion NewVersion = ++Entry.Version;
Entry.Draft = Contents;
return NewVersion;
}
DocVersion DraftStore::removeDraft(PathRef File) {
std::lock_guard<std::mutex> Lock(Mutex);
auto &Entry = Drafts[File];
DocVersion NewVersion = ++Entry.Version;
Entry.Draft = llvm::None;
return NewVersion;
}

View File

@ -0,0 +1,61 @@
//===--- DraftStore.h - File contents container -----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DRAFTSTORE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DRAFTSTORE_H
#include "Path.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/StringMap.h"
#include <mutex>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
/// Using 'unsigned' here to avoid undefined behaviour on overflow.
typedef unsigned DocVersion;
/// Document draft with a version of this draft.
struct VersionedDraft {
DocVersion Version;
/// If the value of the field is None, draft is now deleted
llvm::Optional<std::string> Draft;
};
/// A thread-safe container for files opened in a workspace, addressed by
/// filenames. The contents are owned by the DraftStore. Versions are mantained
/// for the all added documents, including removed ones. The document version is
/// incremented on each update and removal of the document.
class DraftStore {
public:
/// \return version and contents of the stored document.
/// For untracked files, a (0, None) pair is returned.
VersionedDraft getDraft(PathRef File) const;
/// \return version of the tracked document.
/// For untracked files, 0 is returned.
DocVersion getVersion(PathRef File) const;
/// Replace contents of the draft for \p File with \p Contents.
/// \return The new version of the draft for \p File.
DocVersion updateDraft(PathRef File, StringRef Contents);
/// Remove the contents of the draft
/// \return The new version of the draft for \p File.
DocVersion removeDraft(PathRef File);
private:
mutable std::mutex Mutex;
llvm::StringMap<VersionedDraft> Drafts;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -0,0 +1,65 @@
//===--- GlobalCompilationDatabase.cpp --------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#include "GlobalCompilationDatabase.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
using namespace clang::clangd;
using namespace clang;
std::vector<tooling::CompileCommand>
DirectoryBasedGlobalCompilationDatabase::getCompileCommands(PathRef File) {
std::vector<tooling::CompileCommand> Commands;
auto CDB = getCompilationDatabase(File);
if (!CDB)
return {};
return CDB->getCompileCommands(File);
}
tooling::CompilationDatabase *
DirectoryBasedGlobalCompilationDatabase::getCompilationDatabase(PathRef File) {
std::lock_guard<std::mutex> Lock(Mutex);
namespace path = llvm::sys::path;
assert((path::is_absolute(File, path::Style::posix) ||
path::is_absolute(File, path::Style::windows)) &&
"path must be absolute");
for (auto Path = path::parent_path(File); !Path.empty();
Path = path::parent_path(Path)) {
auto CachedIt = CompilationDatabases.find(Path);
if (CachedIt != CompilationDatabases.end())
return CachedIt->second.get();
std::string Error;
auto CDB = tooling::CompilationDatabase::loadFromDirectory(Path, Error);
if (!CDB) {
if (!Error.empty()) {
// FIXME(ibiryukov): logging
// Output.log("Error when trying to load compilation database from " +
// Twine(Path) + ": " + Twine(Error) + "\n");
}
continue;
}
// FIXME(ibiryukov): Invalidate cached compilation databases on changes
auto result = CDB.get();
CompilationDatabases.insert(std::make_pair(Path, std::move(CDB)));
return result;
}
// FIXME(ibiryukov): logging
// Output.log("Failed to find compilation database for " + Twine(File) +
// "\n");
return nullptr;
}

View File

@ -0,0 +1,59 @@
//===--- GlobalCompilationDatabase.h ----------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
#include "Path.h"
#include "llvm/ADT/StringMap.h"
#include <memory>
#include <mutex>
namespace clang {
namespace tooling {
class CompilationDatabase;
struct CompileCommand;
} // namespace tooling
namespace clangd {
/// Provides compilation arguments used for building ClangdUnit.
class GlobalCompilationDatabase {
public:
virtual ~GlobalCompilationDatabase() = default;
virtual std::vector<tooling::CompileCommand>
getCompileCommands(PathRef File) = 0;
/// FIXME(ibiryukov): add facilities to track changes to compilation flags of
/// existing targets.
};
/// Gets compile args from tooling::CompilationDatabases built for parent
/// directories.
class DirectoryBasedGlobalCompilationDatabase
: public GlobalCompilationDatabase {
public:
std::vector<tooling::CompileCommand>
getCompileCommands(PathRef File) override;
private:
tooling::CompilationDatabase *getCompilationDatabase(PathRef File);
std::mutex Mutex;
/// Caches compilation databases loaded from directories(keys are
/// directories).
llvm::StringMap<std::unique_ptr<clang::tooling::CompilationDatabase>>
CompilationDatabases;
};
} // namespace clangd
} // namespace clang
#endif

View File

@ -0,0 +1,29 @@
//===--- Path.h - Helper typedefs --------------------------------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATH_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATH_H
#include "llvm/ADT/StringRef.h"
#include <string>
namespace clang {
namespace clangd {
/// A typedef to represent a file path. Used solely for more descriptive
/// signatures.
using Path = std::string;
/// A typedef to represent a ref to file path. Used solely for more descriptive
/// signatures.
using PathRef = llvm::StringRef;
} // namespace clangd
} // namespace clang
#endif

View File

@ -8,9 +8,10 @@
//===----------------------------------------------------------------------===//
#include "ProtocolHandlers.h"
#include "ASTManager.h"
#include "DocumentStore.h"
#include "ClangdServer.h"
#include "DraftStore.h"
#include "clang/Format/Format.h"
#include "ClangdLSPServer.h"
using namespace clang;
using namespace clangd;
@ -21,7 +22,7 @@ void TextDocumentDidOpenHandler::handleNotification(
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
return;
}
Store.addDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
AST.openDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
}
void TextDocumentDidCloseHandler::handleNotification(
@ -32,7 +33,7 @@ void TextDocumentDidCloseHandler::handleNotification(
return;
}
Store.removeDocument(DCTDP->textDocument.uri.file);
AST.closeDocument(DCTDP->textDocument.uri.file);
}
void TextDocumentDidChangeHandler::handleNotification(
@ -43,7 +44,7 @@ void TextDocumentDidChangeHandler::handleNotification(
return;
}
// We only support full syncing right now.
Store.addDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
AST.openDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
}
/// Turn a [line, column] pair into an offset in Code.
@ -110,7 +111,7 @@ void TextDocumentRangeFormattingHandler::handleMethod(
return;
}
std::string Code = Store.getDocument(DRFP->textDocument.uri.file);
std::string Code = AST.getDocument(DRFP->textDocument.uri.file);
size_t Begin = positionToOffset(Code, DRFP->range.start);
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
@ -129,7 +130,7 @@ void TextDocumentOnTypeFormattingHandler::handleMethod(
// Look for the previous opening brace from the character position and format
// starting from there.
std::string Code = Store.getDocument(DOTFP->textDocument.uri.file);
std::string Code = AST.getDocument(DOTFP->textDocument.uri.file);
size_t CursorPos = positionToOffset(Code, DOTFP->position);
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
if (PreviousLBracePos == StringRef::npos)
@ -149,7 +150,7 @@ void TextDocumentFormattingHandler::handleMethod(
}
// Format everything.
std::string Code = Store.getDocument(DFP->textDocument.uri.file);
std::string Code = AST.getDocument(DFP->textDocument.uri.file);
writeMessage(formatCode(Code, DFP->textDocument.uri.file,
{clang::tooling::Range(0, Code.size())}, ID));
}
@ -164,7 +165,7 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
std::string Code = AST.getStore().getDocument(CAP->textDocument.uri.file);
std::string Code = AST.getDocument(CAP->textDocument.uri.file);
std::string Commands;
for (Diagnostic &D : CAP->context.diagnostics) {
std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(CAP->textDocument.uri.file, D);
@ -195,8 +196,8 @@ void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
return;
}
auto Items = AST.codeComplete(TDPP->textDocument.uri.file, TDPP->position.line,
TDPP->position.character);
auto Items = AST.codeComplete(TDPP->textDocument.uri.file, Position{TDPP->position.line,
TDPP->position.character});
std::string Completions;
for (const auto &Item : Items) {
Completions += CompletionItem::unparse(Item);

View File

@ -22,8 +22,8 @@
namespace clang {
namespace clangd {
class ASTManager;
class DocumentStore;
class ClangdLSPServer;
class ClangdLSPServer;
struct InitializeHandler : Handler {
InitializeHandler(JSONOutput &Output) : Handler(Output) {}
@ -56,83 +56,83 @@ private:
};
struct TextDocumentDidOpenHandler : Handler {
TextDocumentDidOpenHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
TextDocumentDidOpenHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
DocumentStore &Store;
ClangdLSPServer &AST;
};
struct TextDocumentDidChangeHandler : Handler {
TextDocumentDidChangeHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
TextDocumentDidChangeHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
DocumentStore &Store;
ClangdLSPServer &AST;
};
struct TextDocumentDidCloseHandler : Handler {
TextDocumentDidCloseHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
TextDocumentDidCloseHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
DocumentStore &Store;
ClangdLSPServer &AST;
};
struct TextDocumentOnTypeFormattingHandler : Handler {
TextDocumentOnTypeFormattingHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
DocumentStore &Store;
};
struct TextDocumentRangeFormattingHandler : Handler {
TextDocumentRangeFormattingHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
DocumentStore &Store;
};
struct TextDocumentFormattingHandler : Handler {
TextDocumentFormattingHandler(JSONOutput &Output, DocumentStore &Store)
: Handler(Output), Store(Store) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
DocumentStore &Store;
};
struct CodeActionHandler : Handler {
CodeActionHandler(JSONOutput &Output, ASTManager &AST)
TextDocumentOnTypeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ASTManager &AST;
ClangdLSPServer &AST;
};
struct TextDocumentRangeFormattingHandler : Handler {
TextDocumentRangeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ClangdLSPServer &AST;
};
struct TextDocumentFormattingHandler : Handler {
TextDocumentFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ClangdLSPServer &AST;
};
struct CodeActionHandler : Handler {
CodeActionHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ClangdLSPServer &AST;
};
struct CompletionHandler : Handler {
CompletionHandler(JSONOutput &Output, ASTManager &AST)
CompletionHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ASTManager &AST;
ClangdLSPServer &AST;
};
} // namespace clangd

View File

@ -1,4 +1,4 @@
# RUN: clangd < %s | FileCheck %s
# RUN: clangd -run-synchronously < %s | FileCheck %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125