[clangd] Refactor ProtocolHandlers to decouple them from ClangdLSPServer

Summary:
A refactoring to decouple ProtocolHandlers and Language Server input parsing
loop from the ClangdLSPServer.
The input parsing was extracted from `main` to a function(runLanguageServerLoop).
ProtocolHandlers now provide an interface to handle various LSP methods,
this interface is used by ClangdLSPServer.
Methods for code formatting were moved from ProtocolHandlers to ClangdServer.
ClangdLSPServer now provides a cleaner interface that only runs Language Server
input loop.

Reviewers: bkramer, krasimir

Reviewed By: krasimir

Subscribers: cfe-commits, klimek

Tags: #clang-tools-extra

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

llvm-svn: 303173
This commit is contained in:
Ilya Biryukov 2017-05-16 14:40:30 +00:00
parent 23453c11ff
commit afb555473e
9 changed files with 569 additions and 427 deletions

View File

@ -9,10 +9,35 @@
#include "ClangdLSPServer.h"
#include "JSONRPCDispatcher.h"
#include "ProtocolHandlers.h"
using namespace clang::clangd;
using namespace clang;
namespace {
std::string
replacementsToEdits(StringRef Code,
const std::vector<tooling::Replacement> &Replacements) {
// Turn the replacements into the format specified by the Language Server
// Protocol. Fuse them into one big JSON array.
std::string Edits;
for (auto &R : Replacements) {
Range ReplacementRange = {
offsetToPosition(Code, R.getOffset()),
offsetToPosition(Code, R.getOffset() + R.getLength())};
TextEdit TE = {ReplacementRange, R.getReplacementText()};
Edits += TextEdit::unparse(TE);
Edits += ',';
}
if (!Edits.empty())
Edits.pop_back();
return Edits;
}
} // namespace
class ClangdLSPServer::LSPDiagnosticsConsumer : public DiagnosticsConsumer {
public:
LSPDiagnosticsConsumer(ClangdLSPServer &Server) : Server(Server) {}
@ -26,23 +51,170 @@ private:
ClangdLSPServer &Server;
};
class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks {
public:
LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {}
void onInitialize(StringRef ID, JSONOutput &Out) override;
void onShutdown(JSONOutput &Out) override;
void onDocumentDidOpen(DidOpenTextDocumentParams Params,
JSONOutput &Out) override;
void onDocumentDidChange(DidChangeTextDocumentParams Params,
JSONOutput &Out) override;
void onDocumentDidClose(DidCloseTextDocumentParams Params,
JSONOutput &Out) override;
void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
StringRef ID, JSONOutput &Out) override;
void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
StringRef ID, JSONOutput &Out) override;
void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
JSONOutput &Out) override;
void onCodeAction(CodeActionParams Params, StringRef ID,
JSONOutput &Out) override;
void onCompletion(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) override;
private:
ClangdLSPServer &LangServer;
};
void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
JSONOutput &Out) {
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID +
R"(,"result":{"capabilities":{
"textDocumentSync": 1,
"documentFormattingProvider": true,
"documentRangeFormattingProvider": true,
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
"codeActionProvider": true,
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
}}})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
LangServer.IsDone = true;
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
DidOpenTextDocumentParams Params, JSONOutput &Out) {
LangServer.Server.addDocument(Params.textDocument.uri.file,
Params.textDocument.text);
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
DidChangeTextDocumentParams Params, JSONOutput &Out) {
// We only support full syncing right now.
LangServer.Server.addDocument(Params.textDocument.uri.file,
Params.contentChanges[0].text);
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
DidCloseTextDocumentParams Params, JSONOutput &Out) {
LangServer.Server.removeDocument(Params.textDocument.uri.file);
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
auto File = Params.textDocument.uri.file;
std::string Code = LangServer.Server.getDocument(File);
std::string Edits = replacementsToEdits(
Code, LangServer.Server.formatOnType(File, Params.position));
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
auto File = Params.textDocument.uri.file;
std::string Code = LangServer.Server.getDocument(File);
std::string Edits = replacementsToEdits(
Code, LangServer.Server.formatRange(File, Params.range));
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
auto File = Params.textDocument.uri.file;
std::string Code = LangServer.Server.getDocument(File);
std::string Edits =
replacementsToEdits(Code, LangServer.Server.formatFile(File));
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
CodeActionParams Params, StringRef ID, JSONOutput &Out) {
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
std::string Code =
LangServer.Server.getDocument(Params.textDocument.uri.file);
std::string Commands;
for (Diagnostic &D : Params.context.diagnostics) {
std::vector<clang::tooling::Replacement> Fixes =
LangServer.getFixIts(Params.textDocument.uri.file, D);
std::string Edits = replacementsToEdits(Code, Fixes);
if (!Edits.empty())
Commands +=
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
R"('", "command": "clangd.applyFix", "arguments": [")" +
llvm::yaml::escape(Params.textDocument.uri.uri) +
R"(", [)" + Edits +
R"(]]},)";
}
if (!Commands.empty())
Commands.pop_back();
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(, "result": [)" + Commands +
R"(]})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
auto Items = LangServer.Server.codeComplete(
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character});
std::string Completions;
for (const auto &Item : Items) {
Completions += CompletionItem::unparse(Item);
Completions += ",";
}
if (!Completions.empty())
Completions.pop_back();
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Completions + R"(]})");
}
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::run(std::istream &In) {
assert(!IsDone && "Run was called before");
void ClangdLSPServer::closeDocument(StringRef File) {
Server.removeDocument(File);
}
// Set up JSONRPCDispatcher.
LSPProtocolCallbacks Callbacks(*this);
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
regiterCallbackHandlers(Dispatcher, Out, Callbacks);
std::vector<CompletionItem> ClangdLSPServer::codeComplete(PathRef File,
Position Pos) {
return Server.codeComplete(File, Pos);
// Run the Language Server loop.
runLanguageServerLoop(In, Out, Dispatcher, IsDone);
// Make sure IsDone is set to true after this method exits to ensure assertion
// at the start of the method fires if it's ever executed again.
IsDone = true;
}
std::vector<clang::tooling::Replacement>
@ -60,10 +232,6 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
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;

View File

@ -20,43 +20,25 @@ 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.
/// This class provides implementation of an LSP server, glueing the JSON
/// dispatch and ClangdServer together.
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);
/// Run LSP server loop, receiving input for it from \p In. \p In must be
/// opened in binary mode. Output will be written using Out variable passed to
/// class constructor. This method must not be executed more than once for
/// each instance of ClangdLSPServer.
void run(std::istream &In);
private:
class LSPProtocolCallbacks;
class LSPDiagnosticsConsumer;
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
/// 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.
@ -64,6 +46,10 @@ private:
std::vector<DiagWithFixIts> Diagnostics);
JSONOutput &Out;
/// Used to indicate that the 'shutdown' request was received from the
/// Language Server client.
/// It's used to break out of the LSP parsing loop.
bool IsDone = false;
std::mutex FixItsMutex;
typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>

View File

@ -7,10 +7,8 @@
//
//===----------------------------------------------------------------------===//
#include "JSONRPCDispatcher.h"
#include "ClangdLSPServer.h"
#include "Protocol.h"
#include "ProtocolHandlers.h"
#include "JSONRPCDispatcher.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Program.h"
@ -29,6 +27,7 @@ static llvm::cl::opt<bool>
int main(int argc, char *argv[]) {
llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
llvm::raw_ostream &Outs = llvm::outs();
llvm::raw_ostream &Logs = llvm::errs();
JSONOutput Out(Outs, Logs);
@ -36,89 +35,6 @@ int main(int argc, char *argv[]) {
// Change stdin to binary to not lose \r\n on windows.
llvm::sys::ChangeStdinToBinary();
// Set up a document store and intialize all the method handlers for JSONRPC
// dispatching.
ClangdLSPServer LSPServer(Out, RunSynchronously);
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
Dispatcher.registerHandler("initialize",
llvm::make_unique<InitializeHandler>(Out));
auto ShutdownPtr = llvm::make_unique<ShutdownHandler>(Out);
auto *ShutdownHandler = ShutdownPtr.get();
Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
Dispatcher.registerHandler(
"textDocument/didOpen",
llvm::make_unique<TextDocumentDidOpenHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/didClose",
llvm::make_unique<TextDocumentDidCloseHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/didChange",
llvm::make_unique<TextDocumentDidChangeHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/rangeFormatting",
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/onTypeFormatting",
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler(
"textDocument/formatting",
llvm::make_unique<TextDocumentFormattingHandler>(Out, LSPServer));
Dispatcher.registerHandler("textDocument/codeAction",
llvm::make_unique<CodeActionHandler>(Out, LSPServer));
Dispatcher.registerHandler("textDocument/completion",
llvm::make_unique<CompletionHandler>(Out, LSPServer));
while (std::cin.good()) {
// A Language Server Protocol message starts with a HTTP header, delimited
// by \r\n.
std::string Line;
std::getline(std::cin, Line);
if (!std::cin.good() && errno == EINTR) {
std::cin.clear();
continue;
}
// Skip empty lines.
llvm::StringRef LineRef(Line);
if (LineRef.trim().empty())
continue;
// We allow YAML-style comments. Technically this isn't part of the
// LSP specification, but makes writing tests easier.
if (LineRef.startswith("#"))
continue;
unsigned long long Len = 0;
// FIXME: Content-Type is a specified header, but does nothing.
// Content-Length is a mandatory header. It specifies the length of the
// following JSON.
if (LineRef.consume_front("Content-Length: "))
llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
// Check if the next line only contains \r\n. If not this is another header,
// which we ignore.
char NewlineBuf[2];
std::cin.read(NewlineBuf, 2);
if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
continue;
// Now read the JSON. Insert a trailing null byte as required by the YAML
// parser.
std::vector<char> JSON(Len + 1, '\0');
std::cin.read(JSON.data(), Len);
if (Len > 0) {
llvm::StringRef JSONRef(JSON.data(), Len);
// Log the message.
Out.log("<-- " + JSONRef + "\n");
// Finally, execute the action for this JSON message.
if (!Dispatcher.call(JSONRef))
Out.log("JSON dispatch failed!\n");
// If we're done, exit the loop.
if (ShutdownHandler->isDone())
break;
}
}
LSPServer.run(std::cin);
}

View File

@ -8,15 +8,54 @@
//===-------------------------------------------------------------------===//
#include "ClangdServer.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/FileSystem.h"
using namespace clang;
using namespace clang::clangd;
namespace {
std::vector<tooling::Replacement> formatCode(StringRef Code, StringRef Filename,
ArrayRef<tooling::Range> Ranges) {
// Call clang-format.
// FIXME: Don't ignore style.
format::FormatStyle Style = format::getLLVMStyle();
auto Result = format::reformat(Style, Code, Ranges, Filename);
return std::vector<tooling::Replacement>(Result.begin(), Result.end());
}
} // namespace
size_t clangd::positionToOffset(StringRef Code, Position P) {
size_t Offset = 0;
for (int I = 0; I != P.line; ++I) {
// FIXME: \r\n
// FIXME: UTF-8
size_t F = Code.find('\n', Offset);
if (F == StringRef::npos)
return 0; // FIXME: Is this reasonable?
Offset = F + 1;
}
return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
}
/// Turn an offset in Code into a [line, column] pair.
Position clangd::offsetToPosition(StringRef Code, size_t Offset) {
StringRef JustBefore = Code.substr(0, Offset);
// FIXME: \r\n
// FIXME: UTF-8
int Lines = JustBefore.count('\n');
int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
return {Lines, Cols};
}
WorkerRequest::WorkerRequest(WorkerRequestKind Kind, Path File,
DocVersion Version)
: Kind(Kind), File(File), Version(Version) {}
@ -117,6 +156,34 @@ std::vector<CompletionItem> ClangdServer::codeComplete(PathRef File,
});
return Result;
}
std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File,
Range Rng) {
std::string Code = getDocument(File);
size_t Begin = positionToOffset(Code, Rng.start);
size_t Len = positionToOffset(Code, Rng.end) - Begin;
return formatCode(Code, File, {tooling::Range(Begin, Len)});
}
std::vector<tooling::Replacement> ClangdServer::formatFile(PathRef File) {
// Format everything.
std::string Code = getDocument(File);
return formatCode(Code, File, {tooling::Range(0, Code.size())});
}
std::vector<tooling::Replacement> ClangdServer::formatOnType(PathRef File,
Position Pos) {
// Look for the previous opening brace from the character position and
// format starting from there.
std::string Code = getDocument(File);
size_t CursorPos = positionToOffset(Code, Pos);
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
if (PreviousLBracePos == StringRef::npos)
PreviousLBracePos = CursorPos;
size_t Len = 1 + CursorPos - PreviousLBracePos;
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
}
std::string ClangdServer::getDocument(PathRef File) {
auto draft = DraftMgr.getDraft(File);

View File

@ -34,6 +34,12 @@ class PCHContainerOperations;
namespace clangd {
/// Turn a [line, column] pair into an offset in Code.
size_t positionToOffset(StringRef Code, Position P);
/// Turn an offset in Code into a [line, column] pair.
Position offsetToPosition(StringRef Code, size_t Offset);
class DiagnosticsConsumer {
public:
virtual ~DiagnosticsConsumer() = default;
@ -100,7 +106,6 @@ public:
/// 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);
@ -108,11 +113,17 @@ public:
/// Run code completion for \p File at \p Pos.
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
/// Run formatting for \p Rng inside \p File.
std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
/// Run formatting for the whole \p File.
std::vector<tooling::Replacement> formatFile(PathRef File);
/// Run formatting after a character was typed at \p Pos in \p File.
std::vector<tooling::Replacement> formatOnType(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.
/// FIXME(ibiryukov): This function is here to allow offset-to-Position
/// conversions in outside code, maybe there's a way to get rid of it.
std::string getDocument(PathRef File);
private:

View File

@ -129,3 +129,61 @@ bool JSONRPCDispatcher::call(StringRef Content) const {
return true;
}
void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
JSONRPCDispatcher &Dispatcher,
bool &IsDone) {
while (In.good()) {
// A Language Server Protocol message starts with a HTTP header, delimited
// by \r\n.
std::string Line;
std::getline(In, Line);
if (!In.good() && errno == EINTR) {
In.clear();
continue;
}
// Skip empty lines.
llvm::StringRef LineRef(Line);
if (LineRef.trim().empty())
continue;
// We allow YAML-style comments. Technically this isn't part of the
// LSP specification, but makes writing tests easier.
if (LineRef.startswith("#"))
continue;
unsigned long long Len = 0;
// FIXME: Content-Type is a specified header, but does nothing.
// Content-Length is a mandatory header. It specifies the length of the
// following JSON.
if (LineRef.consume_front("Content-Length: "))
llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
// Check if the next line only contains \r\n. If not this is another header,
// which we ignore.
char NewlineBuf[2];
In.read(NewlineBuf, 2);
if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
continue;
// Now read the JSON. Insert a trailing null byte as required by the YAML
// parser.
std::vector<char> JSON(Len + 1, '\0');
In.read(JSON.data(), Len);
if (Len > 0) {
llvm::StringRef JSONRef(JSON.data(), Len);
// Log the message.
Out.log("<-- " + JSONRef + "\n");
// Finally, execute the action for this JSON message.
if (!Dispatcher.call(JSONRef))
Out.log("JSON dispatch failed!\n");
// If we're done, exit the loop.
if (IsDone)
break;
}
}
}

View File

@ -79,6 +79,15 @@ private:
std::unique_ptr<Handler> UnknownHandler;
};
/// Parses input queries from LSP client (coming from \p In) and runs call
/// method of \p Dispatcher for each query.
/// After handling each query checks if \p IsDone is set true and exits the loop
/// if it is.
/// Input stream(\p In) must be opened in binary mode to avoid preliminary
/// replacements of \r\n with \n.
void runLanguageServerLoop(std::istream &In, JSONOutput &Out,
JSONRPCDispatcher &Dispatcher, bool &IsDone);
} // namespace clangd
} // namespace clang

View File

@ -8,204 +8,215 @@
//===----------------------------------------------------------------------===//
#include "ProtocolHandlers.h"
#include "ClangdLSPServer.h"
#include "ClangdServer.h"
#include "DraftStore.h"
#include "clang/Format/Format.h"
#include "ClangdLSPServer.h"
using namespace clang;
using namespace clangd;
void TextDocumentDidOpenHandler::handleNotification(
llvm::yaml::MappingNode *Params) {
auto DOTDP = DidOpenTextDocumentParams::parse(Params);
if (!DOTDP) {
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
return;
}
AST.openDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
}
namespace {
void TextDocumentDidCloseHandler::handleNotification(
llvm::yaml::MappingNode *Params) {
auto DCTDP = DidCloseTextDocumentParams::parse(Params);
if (!DCTDP) {
Output.log("Failed to decode DidCloseTextDocumentParams!\n");
return;
struct InitializeHandler : Handler {
InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
Callbacks.onInitialize(ID, Output);
}
AST.closeDocument(DCTDP->textDocument.uri.file);
}
private:
ProtocolCallbacks &Callbacks;
};
void TextDocumentDidChangeHandler::handleNotification(
llvm::yaml::MappingNode *Params) {
auto DCTDP = DidChangeTextDocumentParams::parse(Params);
if (!DCTDP || DCTDP->contentChanges.size() != 1) {
Output.log("Failed to decode DidChangeTextDocumentParams!\n");
return;
}
// We only support full syncing right now.
AST.openDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
}
struct ShutdownHandler : Handler {
ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
/// Turn a [line, column] pair into an offset in Code.
static size_t positionToOffset(StringRef Code, Position P) {
size_t Offset = 0;
for (int I = 0; I != P.line; ++I) {
// FIXME: \r\n
// FIXME: UTF-8
size_t F = Code.find('\n', Offset);
if (F == StringRef::npos)
return 0; // FIXME: Is this reasonable?
Offset = F + 1;
}
return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
}
/// Turn an offset in Code into a [line, column] pair.
static Position offsetToPosition(StringRef Code, size_t Offset) {
StringRef JustBefore = Code.substr(0, Offset);
// FIXME: \r\n
// FIXME: UTF-8
int Lines = JustBefore.count('\n');
int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
return {Lines, Cols};
}
template <typename T>
static std::string replacementsToEdits(StringRef Code, const T &Replacements) {
// Turn the replacements into the format specified by the Language Server
// Protocol. Fuse them into one big JSON array.
std::string Edits;
for (auto &R : Replacements) {
Range ReplacementRange = {
offsetToPosition(Code, R.getOffset()),
offsetToPosition(Code, R.getOffset() + R.getLength())};
TextEdit TE = {ReplacementRange, R.getReplacementText()};
Edits += TextEdit::unparse(TE);
Edits += ',';
}
if (!Edits.empty())
Edits.pop_back();
return Edits;
}
static std::string formatCode(StringRef Code, StringRef Filename,
ArrayRef<tooling::Range> Ranges, StringRef ID) {
// Call clang-format.
// FIXME: Don't ignore style.
format::FormatStyle Style = format::getLLVMStyle();
tooling::Replacements Replacements =
format::reformat(Style, Code, Ranges, Filename);
std::string Edits = replacementsToEdits(Code, Replacements);
return R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})";
}
void TextDocumentRangeFormattingHandler::handleMethod(
llvm::yaml::MappingNode *Params, StringRef ID) {
auto DRFP = DocumentRangeFormattingParams::parse(Params);
if (!DRFP) {
Output.log("Failed to decode DocumentRangeFormattingParams!\n");
return;
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
Callbacks.onShutdown(Output);
}
std::string Code = AST.getDocument(DRFP->textDocument.uri.file);
private:
ProtocolCallbacks &Callbacks;
};
size_t Begin = positionToOffset(Code, DRFP->range.start);
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
struct TextDocumentDidOpenHandler : Handler {
TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
{clang::tooling::Range(Begin, Len)}, ID));
}
void TextDocumentOnTypeFormattingHandler::handleMethod(
llvm::yaml::MappingNode *Params, StringRef ID) {
auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
if (!DOTFP) {
Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
return;
}
// Look for the previous opening brace from the character position and format
// starting from there.
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)
PreviousLBracePos = CursorPos;
size_t Len = 1 + CursorPos - PreviousLBracePos;
writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
{clang::tooling::Range(PreviousLBracePos, Len)}, ID));
}
void TextDocumentFormattingHandler::handleMethod(
llvm::yaml::MappingNode *Params, StringRef ID) {
auto DFP = DocumentFormattingParams::parse(Params);
if (!DFP) {
Output.log("Failed to decode DocumentFormattingParams!\n");
return;
}
// Format everything.
std::string Code = AST.getDocument(DFP->textDocument.uri.file);
writeMessage(formatCode(Code, DFP->textDocument.uri.file,
{clang::tooling::Range(0, Code.size())}, ID));
}
void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
StringRef ID) {
auto CAP = CodeActionParams::parse(Params);
if (!CAP) {
Output.log("Failed to decode CodeActionParams!\n");
return;
}
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
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);
std::string Edits = replacementsToEdits(Code, Fixes);
if (!Edits.empty())
Commands +=
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
R"('", "command": "clangd.applyFix", "arguments": [")" +
llvm::yaml::escape(CAP->textDocument.uri.uri) +
R"(", [)" + Edits +
R"(]]},)";
}
if (!Commands.empty())
Commands.pop_back();
writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(, "result": [)" + Commands +
R"(]})");
}
void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
StringRef ID) {
auto TDPP = TextDocumentPositionParams::parse(Params);
if (!TDPP) {
Output.log("Failed to decode TextDocumentPositionParams!\n");
return;
}
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);
Completions += ",";
}
if (!Completions.empty())
Completions.pop_back();
writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Completions + R"(]})");
void handleNotification(llvm::yaml::MappingNode *Params) override {
auto DOTDP = DidOpenTextDocumentParams::parse(Params);
if (!DOTDP) {
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
return;
}
Callbacks.onDocumentDidOpen(*DOTDP, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct TextDocumentDidChangeHandler : Handler {
TextDocumentDidChangeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleNotification(llvm::yaml::MappingNode *Params) override {
auto DCTDP = DidChangeTextDocumentParams::parse(Params);
if (!DCTDP || DCTDP->contentChanges.size() != 1) {
Output.log("Failed to decode DidChangeTextDocumentParams!\n");
return;
}
Callbacks.onDocumentDidChange(*DCTDP, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct TextDocumentDidCloseHandler : Handler {
TextDocumentDidCloseHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleNotification(llvm::yaml::MappingNode *Params) override {
auto DCTDP = DidCloseTextDocumentParams::parse(Params);
if (!DCTDP) {
Output.log("Failed to decode DidCloseTextDocumentParams!\n");
return;
}
Callbacks.onDocumentDidClose(*DCTDP, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct TextDocumentOnTypeFormattingHandler : Handler {
TextDocumentOnTypeFormattingHandler(JSONOutput &Output,
ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
if (!DOTFP) {
Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
return;
}
Callbacks.onDocumentOnTypeFormatting(*DOTFP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct TextDocumentRangeFormattingHandler : Handler {
TextDocumentRangeFormattingHandler(JSONOutput &Output,
ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto DRFP = DocumentRangeFormattingParams::parse(Params);
if (!DRFP) {
Output.log("Failed to decode DocumentRangeFormattingParams!\n");
return;
}
Callbacks.onDocumentRangeFormatting(*DRFP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct TextDocumentFormattingHandler : Handler {
TextDocumentFormattingHandler(JSONOutput &Output,
ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto DFP = DocumentFormattingParams::parse(Params);
if (!DFP) {
Output.log("Failed to decode DocumentFormattingParams!\n");
return;
}
Callbacks.onDocumentFormatting(*DFP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct CodeActionHandler : Handler {
CodeActionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto CAP = CodeActionParams::parse(Params);
if (!CAP) {
Output.log("Failed to decode CodeActionParams!\n");
return;
}
Callbacks.onCodeAction(*CAP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
struct CompletionHandler : Handler {
CompletionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto TDPP = TextDocumentPositionParams::parse(Params);
if (!TDPP) {
Output.log("Failed to decode TextDocumentPositionParams!\n");
return;
}
Callbacks.onCompletion(*TDPP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
} // namespace
void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
JSONOutput &Out,
ProtocolCallbacks &Callbacks) {
Dispatcher.registerHandler(
"initialize", llvm::make_unique<InitializeHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"shutdown", llvm::make_unique<ShutdownHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/didOpen",
llvm::make_unique<TextDocumentDidOpenHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/didClose",
llvm::make_unique<TextDocumentDidCloseHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/didChange",
llvm::make_unique<TextDocumentDidChangeHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/rangeFormatting",
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/onTypeFormatting",
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/formatting",
llvm::make_unique<TextDocumentFormattingHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/codeAction",
llvm::make_unique<CodeActionHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/completion",
llvm::make_unique<CompletionHandler>(Out, Callbacks));
}

View File

@ -22,118 +22,34 @@
namespace clang {
namespace clangd {
class ClangdLSPServer;
class ClangdLSPServer;
struct InitializeHandler : Handler {
InitializeHandler(JSONOutput &Output) : Handler(Output) {}
class ProtocolCallbacks {
public:
virtual ~ProtocolCallbacks() = default;
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID +
R"(,"result":{"capabilities":{
"textDocumentSync": 1,
"documentFormattingProvider": true,
"documentRangeFormattingProvider": true,
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
"codeActionProvider": true,
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
}}})");
}
virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0;
virtual void onShutdown(JSONOutput &Out) = 0;
virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params,
JSONOutput &Out) = 0;
virtual void onDocumentDidChange(DidChangeTextDocumentParams Params,
JSONOutput &Out) = 0;
virtual void onDocumentDidClose(DidCloseTextDocumentParams Params,
JSONOutput &Out) = 0;
virtual void onDocumentFormatting(DocumentFormattingParams Params,
StringRef ID, JSONOutput &Out) = 0;
virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
StringRef ID, JSONOutput &Out) = 0;
virtual void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
StringRef ID, JSONOutput &Out) = 0;
virtual void onCodeAction(CodeActionParams Params, StringRef ID,
JSONOutput &Out) = 0;
virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) = 0;
};
struct ShutdownHandler : Handler {
ShutdownHandler(JSONOutput &Output) : Handler(Output) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
IsDone = true;
}
bool isDone() const { return IsDone; }
private:
bool IsDone = false;
};
struct TextDocumentDidOpenHandler : Handler {
TextDocumentDidOpenHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
ClangdLSPServer &AST;
};
struct TextDocumentDidChangeHandler : Handler {
TextDocumentDidChangeHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
ClangdLSPServer &AST;
};
struct TextDocumentDidCloseHandler : Handler {
TextDocumentDidCloseHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleNotification(llvm::yaml::MappingNode *Params) override;
private:
ClangdLSPServer &AST;
};
struct TextDocumentOnTypeFormattingHandler : Handler {
TextDocumentOnTypeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
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, ClangdLSPServer &AST)
: Handler(Output), AST(AST) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
private:
ClangdLSPServer &AST;
};
void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
ProtocolCallbacks &Callbacks);
} // namespace clangd
} // namespace clang