Skip to content

Commit

Permalink
[clangd] less boilerplate in RPC dispatch
Browse files Browse the repository at this point in the history
Summary:
Make the ProtocolHandlers glue between JSONRPCDispatcher and
ClangdLSPServer generic.
Eliminate small differences between methods, de-emphasize the unimportant
distinction between notifications and methods.

ClangdLSPServer is no longer responsible for producing a complete
JSON-RPC response, just the JSON of the result object. (In future, we
should move that JSON serialization out, too).
Handler methods now take a context object that we may hang more
functionality off in the future.

Added documentation to ProtocolHandlers.

Reviewers: ilya-biryukov, bkramer

Reviewed By: ilya-biryukov

Subscribers: cfe-commits

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

llvm-svn: 315577
  • Loading branch information
sam-mccall committed Oct 12, 2017
1 parent 4d93120 commit 8a5dded
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 457 deletions.
110 changes: 43 additions & 67 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Expand Up @@ -37,11 +37,9 @@ replacementsToEdits(StringRef Code,

} // namespace

void ClangdLSPServer::onInitialize(StringRef ID, InitializeParams IP,
JSONOutput &Out) {
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID +
R"(,"result":{"capabilities":{
void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
C.reply(
R"({"capabilities":{
"textDocumentSync": 1,
"documentFormattingProvider": true,
"documentRangeFormattingProvider": true,
Expand All @@ -50,73 +48,68 @@ void ClangdLSPServer::onInitialize(StringRef ID, InitializeParams IP,
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
"signatureHelpProvider": {"triggerCharacters": ["(",","]},
"definitionProvider": true
}}})");
if (IP.rootUri && !IP.rootUri->file.empty())
Server.setRootPath(IP.rootUri->file);
else if (IP.rootPath && !IP.rootPath->empty())
Server.setRootPath(*IP.rootPath);
}})");
if (Params.rootUri && !Params.rootUri->file.empty())
Server.setRootPath(Params.rootUri->file);
else if (Params.rootPath && !Params.rootPath->empty())
Server.setRootPath(*Params.rootPath);
}

void ClangdLSPServer::onShutdown(JSONOutput &Out) { IsDone = true; }
void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
IsDone = true;
}

void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams Params,
JSONOutput &Out) {
void ClangdLSPServer::onDocumentDidOpen(Ctx C,
DidOpenTextDocumentParams &Params) {
if (Params.metadata && !Params.metadata->extraFlags.empty())
CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
std::move(Params.metadata->extraFlags));
Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
}

void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams Params,
JSONOutput &Out) {
void ClangdLSPServer::onDocumentDidChange(Ctx C,
DidChangeTextDocumentParams &Params) {
// We only support full syncing right now.
Server.addDocument(Params.textDocument.uri.file,
Params.contentChanges[0].text);
}

void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Server.onFileEvent(Params);
}

void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams Params,
JSONOutput &Out) {
void ClangdLSPServer::onDocumentDidClose(Ctx C,
DidCloseTextDocumentParams &Params) {
Server.removeDocument(Params.textDocument.uri.file);
}

void ClangdLSPServer::onDocumentOnTypeFormatting(
DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
Ctx C, DocumentOnTypeFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
std::string Edits =
replacementsToEdits(Code, Server.formatOnType(File, Params.position));

Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
C.reply("[" + Edits + "]");
}

void ClangdLSPServer::onDocumentRangeFormatting(
DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
Ctx C, DocumentRangeFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
std::string Edits =
replacementsToEdits(Code, Server.formatRange(File, Params.range));

Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
C.reply("[" + Edits + "]");
}

void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams Params,
StringRef ID, JSONOutput &Out) {
void ClangdLSPServer::onDocumentFormatting(Ctx C,
DocumentFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
std::string Edits = replacementsToEdits(Code, Server.formatFile(File));

Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Edits + R"(]})");
C.reply("[" + Edits + "]");
}

void ClangdLSPServer::onCodeAction(CodeActionParams Params, StringRef ID,
JSONOutput &Out) {
void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
std::string Code = Server.getDocument(Params.textDocument.uri.file);
Expand All @@ -136,16 +129,10 @@ void ClangdLSPServer::onCodeAction(CodeActionParams Params, StringRef ID,
}
if (!Commands.empty())
Commands.pop_back();

Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(, "result": [)" + Commands +
R"(]})");
C.reply("[" + Commands + "]");
}

void ClangdLSPServer::onCompletion(TextDocumentPositionParams Params,
StringRef ID, JSONOutput &Out) {

void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
auto Items = Server
.codeComplete(Params.textDocument.uri.file,
Position{Params.position.line,
Expand All @@ -162,26 +149,21 @@ void ClangdLSPServer::onCompletion(TextDocumentPositionParams Params,
}
if (!Completions.empty())
Completions.pop_back();
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Completions + R"(]})");
C.reply("[" + Completions + "]");
}

void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams Params,
StringRef ID, JSONOutput &Out) {
const auto SigHelp = SignatureHelp::unparse(
void ClangdLSPServer::onSignatureHelp(Ctx C,
TextDocumentPositionParams &Params) {
C.reply(SignatureHelp::unparse(
Server
.signatureHelp(
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character})
.Value);
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + R"(,"result":)" +
SigHelp + "}");
.Value));
}

void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams Params,
StringRef ID, JSONOutput &Out) {

void ClangdLSPServer::onGoToDefinition(Ctx C,
TextDocumentPositionParams &Params) {
auto Items = Server
.findDefinitions(Params.textDocument.uri.file,
Position{Params.position.line,
Expand All @@ -195,23 +177,14 @@ void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams Params,
}
if (!Locations.empty())
Locations.pop_back();
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Locations + R"(]})");
C.reply("[" + Locations + "]");
}

void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier Params,
StringRef ID, JSONOutput &Out) {
void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
TextDocumentIdentifier &Params) {
llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
std::string ResultUri;
if (Result)
ResultUri = URI::unparse(URI::fromFile(*Result));
else
ResultUri = "\"\"";

Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":)" + ResultUri + R"(})");
C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
}

ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Expand All @@ -226,7 +199,10 @@ void ClangdLSPServer::run(std::istream &In) {
assert(!IsDone && "Run was called before");

// Set up JSONRPCDispatcher.
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
JSONRPCDispatcher Dispatcher(
[](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
Ctx.replyError(-32601, "method not found");
});
registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);

// Run the Language Server loop.
Expand Down
44 changes: 18 additions & 26 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Expand Up @@ -48,32 +48,24 @@ class ClangdLSPServer : private DiagnosticsConsumer, private ProtocolCallbacks {
Tagged<std::vector<DiagWithFixIts>> Diagnostics) override;

// Implement ProtocolCallbacks.
void onInitialize(StringRef ID, InitializeParams IP,
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;
void onSignatureHelp(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) override;
void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) override;
void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
JSONOutput &Out) override;
void onFileEvent(const DidChangeWatchedFilesParams &Params) override;
void onInitialize(Ctx C, InitializeParams &Params) override;
void onShutdown(Ctx C, ShutdownParams &Params) override;
void onDocumentDidOpen(Ctx C, DidOpenTextDocumentParams &Params) override;
void onDocumentDidChange(Ctx C, DidChangeTextDocumentParams &Params) override;
void onDocumentDidClose(Ctx C, DidCloseTextDocumentParams &Params) override;
void
onDocumentOnTypeFormatting(Ctx C,
DocumentOnTypeFormattingParams &Params) override;
void
onDocumentRangeFormatting(Ctx C,
DocumentRangeFormattingParams &Params) override;
void onDocumentFormatting(Ctx C, DocumentFormattingParams &Params) override;
void onCodeAction(Ctx C, CodeActionParams &Params) override;
void onCompletion(Ctx C, TextDocumentPositionParams &Params) override;
void onSignatureHelp(Ctx C, TextDocumentPositionParams &Params) override;
void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override;
void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override;
void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override;

std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
Expand Down
48 changes: 26 additions & 22 deletions clang-tools-extra/clangd/JSONRPCDispatcher.cpp
Expand Up @@ -45,38 +45,42 @@ void JSONOutput::mirrorInput(const Twine &Message) {
InputMirror->flush();
}

void Handler::handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) {
Output.log("Method ignored.\n");
// Return that this method is unsupported.
writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID +
R"(,"error":{"code":-32601,"message":"method not found"}})");
void RequestContext::reply(const llvm::Twine &Result) {
if (ID.empty()) {
Out.log("Attempted to reply to a notification!\n");
return;
}
Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
R"(,"result":)" + Result + "}");
}

void Handler::handleNotification(llvm::yaml::MappingNode *Params) {
Output.log("Notification ignored.\n");
void RequestContext::replyError(int code, const llvm::StringRef &Message) {
Out.log("Error " + llvm::Twine(code) + ": " + Message + "\n");
if (!ID.empty()) {
Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
R"(,"error":{"code":)" + llvm::Twine(code) +
R"(,"message":")" + llvm::yaml::escape(Message) +
R"("}})");
}
}

void JSONRPCDispatcher::registerHandler(StringRef Method,
std::unique_ptr<Handler> H) {
void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
assert(!Handlers.count(Method) && "Handler already registered!");
Handlers[Method] = std::move(H);
}

static void
callHandler(const llvm::StringMap<std::unique_ptr<Handler>> &Handlers,
callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
llvm::yaml::MappingNode *Params, Handler *UnknownHandler) {
llvm::SmallString<10> MethodStorage;
llvm::yaml::MappingNode *Params,
const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) {
llvm::SmallString<64> MethodStorage;
auto I = Handlers.find(Method->getValue(MethodStorage));
auto *Handler = I != Handlers.end() ? I->second.get() : UnknownHandler;
if (Id)
Handler->handleMethod(Params, Id->getRawValue());
else
Handler->handleNotification(Params);
auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
Handler(RequestContext(Out, Id ? Id->getRawValue() : ""), Params);
}

bool JSONRPCDispatcher::call(StringRef Content) const {
bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
llvm::SourceMgr SM;
llvm::yaml::Stream YAMLStream(Content, SM);

Expand Down Expand Up @@ -124,7 +128,7 @@ bool JSONRPCDispatcher::call(StringRef Content) const {
// because it will break clients that put the id after params. A possible
// fix would be to split the parsing and execution phases.
Params = dyn_cast<llvm::yaml::MappingNode>(Value);
callHandler(Handlers, Method, Id, Params, UnknownHandler.get());
callHandler(Handlers, Method, Id, Params, UnknownHandler, Out);
return true;
} else {
return false;
Expand All @@ -135,7 +139,7 @@ bool JSONRPCDispatcher::call(StringRef Content) const {
// leftovers.
if (!Method)
return false;
callHandler(Handlers, Method, Id, nullptr, UnknownHandler.get());
callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out);

return true;
}
Expand Down Expand Up @@ -215,7 +219,7 @@ void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
Out.log("<-- " + JSONRef + "\n");

// Finally, execute the action for this JSON message.
if (!Dispatcher.call(JSONRef))
if (!Dispatcher.call(JSONRef, Out))
Out.log("JSON dispatch failed!\n");

// If we're done, exit the loop.
Expand Down

0 comments on commit 8a5dded

Please sign in to comment.