Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -44,29 +44,23 @@ Tagged> 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 onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, - JSONOutput &Out) override; - void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, - JSONOutput &Out) override; + void onInitialize(Ctx &C, InitializeParams &Params) override; + void onShutdown(Ctx &C, NoParams &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 onGoToDefinition(Ctx &C, TextDocumentPositionParams &Params) override; + void onSwitchSourceHeader(Ctx &C, TextDocumentIdentifier &Params) override; std::vector getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -37,11 +37,9 @@ } // 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, @@ -49,69 +47,64 @@ "codeActionProvider": true, "completionProvider": {"resolveProvider": false, "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::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); @@ -131,16 +124,10 @@ } 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, @@ -154,14 +141,11 @@ } if (!Completions.empty()) Completions.pop_back(); - Out.writeMessage( - R"({"jsonrpc":"2.0","id":)" + ID.str() + - R"(,"result":[)" + Completions + R"(]})"); + C.reply("[" + Completions + "]"); } -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, @@ -175,23 +159,14 @@ } 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 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, @@ -205,7 +180,10 @@ assert(!IsDone && "Run was called before"); // Set up JSONRPCDispatcher. - JSONRPCDispatcher Dispatcher(llvm::make_unique(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. Index: clangd/JSONRPCDispatcher.h =================================================================== --- clangd/JSONRPCDispatcher.h +++ clangd/JSONRPCDispatcher.h @@ -12,6 +12,7 @@ #include "Logger.h" #include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/YAMLParser.h" #include @@ -31,6 +32,7 @@ void writeMessage(const Twine &Message); /// Write to the logging stream. + /// No newline is implicitly added. (TODO: we should fix this!) void log(const Twine &Message) override; private: @@ -40,45 +42,45 @@ std::mutex StreamMutex; }; -/// Callback for messages sent to the server, called by the JSONRPCDispatcher. -class Handler { +/// Context object passed to handlers to allow replies. +class RequestContext { public: - Handler(JSONOutput &Output) : Output(Output) {} - virtual ~Handler() = default; - - /// Called when the server receives a method call. This is supposed to return - /// a result on Outs. The default implementation returns an "unknown method" - /// error to the client and logs a warning. - virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID); - /// Called when the server receives a notification. No result should be - /// written to Outs. The default implemetation logs a warning. - virtual void handleNotification(llvm::yaml::MappingNode *Params); - -protected: - JSONOutput &Output; - - /// Helper to write a JSONRPC result to Output. - void writeMessage(const Twine &Message) { Output.writeMessage(Message); } + RequestContext(JSONOutput &Out, StringRef ID) : Out(Out), ID(ID) {} + + /// Sends a successful reply. Result should be well-formed JSON. + void reply(const Twine &Result); + /// Sends an error response to the client, and logs it. + void replyError(int code, const llvm::StringRef &Message); + /// Logs a message locally only. A newline will be added. + void log(const Twine &Message) { Out.log(Message + "\n"); } + +private: + JSONOutput &Out; + llvm::SmallString<64> ID; // Valid JSON, or empty for a notification. }; /// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the /// registered Handler for the method received. class JSONRPCDispatcher { public: + // A handler responds to requests for a particular method name. + using Handler = + std::function; + /// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown /// method is received. - JSONRPCDispatcher(std::unique_ptr UnknownHandler) + JSONRPCDispatcher(Handler UnknownHandler) : UnknownHandler(std::move(UnknownHandler)) {} /// Registers a Handler for the specified Method. - void registerHandler(StringRef Method, std::unique_ptr H); + void registerHandler(StringRef Method, Handler H); /// Parses a JSONRPC message and calls the Handler for it. - bool call(StringRef Content) const; + bool call(StringRef Content, JSONOutput &Out) const; private: - llvm::StringMap> Handlers; - std::unique_ptr UnknownHandler; + llvm::StringMap Handlers; + Handler UnknownHandler; }; /// Parses input queries from LSP client (coming from \p In) and runs call Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -37,38 +37,43 @@ Logs.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()) { + log("Attempted to reply to a notification!"); + 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) { + log("Error " + code + Message); + 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 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> &Handlers, +callHandler(const llvm::StringMap &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) { + RequestContext C(Out, Id ? Id->getRawValue() : ""); + 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(C, Params); } -bool JSONRPCDispatcher::call(StringRef Content) const { +bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const { llvm::SourceMgr SM; llvm::yaml::Stream YAMLStream(Content, SM); @@ -116,7 +121,7 @@ // 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(Value); - callHandler(Handlers, Method, Id, Params, UnknownHandler.get()); + callHandler(Handlers, Method, Id, Params, UnknownHandler, Out); return true; } else { return false; @@ -127,7 +132,7 @@ // leftovers. if (!Method) return false; - callHandler(Handlers, Method, Id, nullptr, UnknownHandler.get()); + callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out); return true; } @@ -202,7 +207,7 @@ 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. Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -166,6 +166,14 @@ Verbose = 2, }; +struct NoParams { + static llvm::Optional parse(llvm::yaml::MappingNode *Params, + Logger &Logger) { + return NoParams{}; + } +}; +using ShutdownParams = NoParams; + struct InitializeParams { /// The process Id of the parent process that started /// the server. Is null if the process has not been started by another Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -304,11 +304,17 @@ llvm::Optional InitializeParams::parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger) { + // If we don't understand the params, proceed with default parameters. + auto ParseFailure = [&] { + Logger.log("Failed to decode InitializeParams\n"); + return InitializeParams(); + }; InitializeParams Result; for (auto &NextKeyValue : *Params) { auto *KeyString = dyn_cast(NextKeyValue.getKey()); if (!KeyString) - return llvm::None; + return ParseFailure(); + ; llvm::SmallString<10> KeyStorage; StringRef KeyValue = KeyString->getValue(KeyStorage); @@ -321,10 +327,12 @@ auto *Value = dyn_cast_or_null(NextKeyValue.getValue()); if (!Value) - return llvm::None; + return ParseFailure(); + ; long long Val; if (llvm::getAsSignedInteger(Value->getValue(KeyStorage), 0, Val)) - return llvm::None; + return ParseFailure(); + ; Result.processId = Val; } else if (KeyValue == "rootPath") { Result.rootPath = Value->getValue(KeyStorage); Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -7,8 +7,11 @@ // //===----------------------------------------------------------------------===// // -// This file contains the actions performed when the server gets a specific -// request. +// ProtocolHandlers translates incoming JSON requests from JSONRPCDispatcher +// into method calls on ClangLSPServer. +// +// Currently it parses requests into objects, but the ClangLSPServer is +// responsible for producing JSON responses. We should move that here, too. // //===----------------------------------------------------------------------===// @@ -23,38 +26,34 @@ namespace clang { namespace clangd { +// The interface implemented by ClangLSPServer to handle incoming requests. class ProtocolCallbacks { public: + using Ctx = RequestContext; virtual ~ProtocolCallbacks() = default; - virtual void onInitialize(StringRef ID, InitializeParams IP, - 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; - virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, - JSONOutput &Out) = 0; - virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, - JSONOutput &Out) = 0; + virtual void onInitialize(Ctx &C, InitializeParams &Params) = 0; + virtual void onShutdown(Ctx &C, ShutdownParams &Params) = 0; + virtual void onDocumentDidOpen(Ctx &C, DidOpenTextDocumentParams &Params) = 0; + virtual void onDocumentDidChange(Ctx &C, + DidChangeTextDocumentParams &Params) = 0; + virtual void onDocumentDidClose(Ctx &C, + DidCloseTextDocumentParams &Params) = 0; + virtual void onDocumentFormatting(Ctx &C, + DocumentFormattingParams &Params) = 0; + virtual void + onDocumentOnTypeFormatting(Ctx &C, + DocumentOnTypeFormattingParams &Params) = 0; + virtual void + onDocumentRangeFormatting(Ctx &C, DocumentRangeFormattingParams &Params) = 0; + virtual void onCodeAction(Ctx &C, CodeActionParams &Params) = 0; + virtual void onCompletion(Ctx &C, TextDocumentPositionParams &Params) = 0; + virtual void onGoToDefinition(Ctx &C, TextDocumentPositionParams &Params) = 0; + virtual void onSwitchSourceHeader(Ctx &C, TextDocumentIdentifier &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, - ProtocolCallbacks &Callbacks); + ProtocolCallbacks &Callbacks); } // namespace clangd } // namespace clang Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -11,258 +11,59 @@ #include "ClangdLSPServer.h" #include "ClangdServer.h" #include "DraftStore.h" -using namespace clang; -using namespace clangd; +namespace clang { +namespace clangd { namespace { -struct InitializeHandler : Handler { - InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) - : Handler(Output), Callbacks(Callbacks) {} - - void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { - auto IP = InitializeParams::parse(Params, Output); - if (!IP) { - Output.log("Failed to decode InitializeParams!\n"); - IP = InitializeParams(); - } - - Callbacks.onInitialize(ID, *IP, Output); - } - -private: - ProtocolCallbacks &Callbacks; -}; - -struct ShutdownHandler : Handler { - ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) - : Handler(Output), Callbacks(Callbacks) {} - - void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { - Callbacks.onShutdown(Output); - } - -private: - ProtocolCallbacks &Callbacks; -}; - -struct TextDocumentDidOpenHandler : Handler { - TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) - : Handler(Output), Callbacks(Callbacks) {} - - void handleNotification(llvm::yaml::MappingNode *Params) override { - auto DOTDP = DidOpenTextDocumentParams::parse(Params, Output); - 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, Output); - if (!DCTDP || DCTDP->contentChanges.size() != 1) { - Output.log("Failed to decode DidChangeTextDocumentParams!\n"); - return; - } - - Callbacks.onDocumentDidChange(*DCTDP, Output); +// Helper for attaching ProtocolCallbacks methods to a JSONRPCDispatcher. +// Invoke like: Registerer("foo", &ProtocolCallbacks::onFoo) +// onFoo should be: void onFoo(Ctx &C, FooParams &Params) +// FooParams should have a static factory method: parse(yaml::MappingNode*). +struct HandlerRegisterer { + template + void operator()(StringRef method, + void (ProtocolCallbacks::*Handler)(RequestContext &, Param)) { + // Capture pointers by value, as the lambda will outlive this object. + auto *Out = this->Out; + auto *Callbacks = this->Callbacks; + Dispatcher.registerHandler( + method, [=](RequestContext &C, llvm::yaml::MappingNode *RawParams) { + if (auto P = std::decay::type::parse(RawParams, *Out)) { + (Callbacks->*Handler)(C, *P); + } else { + Out->log("Failed to decode " + method + " request"); + } + }); } -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, Output); - 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, Output); - 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, Output); - 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, Output); - 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, Output); - 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, Output); - if (!TDPP) { - Output.log("Failed to decode TextDocumentPositionParams!\n"); - return; - } - - Callbacks.onCompletion(*TDPP, ID, Output); - } - -private: - ProtocolCallbacks &Callbacks; -}; - -struct GotoDefinitionHandler : Handler { - GotoDefinitionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) - : Handler(Output), Callbacks(Callbacks) {} - - void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { - auto TDPP = TextDocumentPositionParams::parse(Params, Output); - if (!TDPP) { - Output.log("Failed to decode TextDocumentPositionParams!\n"); - return; - } - - Callbacks.onGoToDefinition(*TDPP, ID, Output); - } - -private: - ProtocolCallbacks &Callbacks; -}; - -struct SwitchSourceHeaderHandler : Handler { - SwitchSourceHeaderHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) - : Handler(Output), Callbacks(Callbacks) {} - - void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { - auto TDPP = TextDocumentIdentifier::parse(Params, Output); - if (!TDPP) - return; - - Callbacks.onSwitchSourceHeader(*TDPP, ID, Output); - } - -private: - ProtocolCallbacks &Callbacks; + JSONRPCDispatcher &Dispatcher; + JSONOutput *Out; + ProtocolCallbacks *Callbacks; }; } // namespace -void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, - JSONOutput &Out, - ProtocolCallbacks &Callbacks) { - Dispatcher.registerHandler( - "initialize", llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "shutdown", llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/didOpen", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/didClose", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/didChange", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/rangeFormatting", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/onTypeFormatting", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/formatting", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/codeAction", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/completion", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/definition", - llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler( - "textDocument/switchSourceHeader", - llvm::make_unique(Out, Callbacks)); +void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, + ProtocolCallbacks &Callbacks) { + HandlerRegisterer Register{Dispatcher, &Out, &Callbacks}; + + Register("initialize", &ProtocolCallbacks::onInitialize); + Register("shutdown", &ProtocolCallbacks::onShutdown); + Register("textDocument/didOpen", &ProtocolCallbacks::onDocumentDidOpen); + Register("textDocument/didClose", &ProtocolCallbacks::onDocumentDidClose); + Register("textDocument/didChange", &ProtocolCallbacks::onDocumentDidChange); + Register("textDocument/rangeFormatting", + &ProtocolCallbacks::onDocumentRangeFormatting); + Register("textDocument/onTypeFormatting", + &ProtocolCallbacks::onDocumentOnTypeFormatting); + Register("textDocument/formatting", &ProtocolCallbacks::onDocumentFormatting); + Register("textDocument/codeAction", &ProtocolCallbacks::onCodeAction); + Register("textDocument/completion", &ProtocolCallbacks::onCompletion); + Register("textDocument/definition", &ProtocolCallbacks::onGoToDefinition); + Register("textDocument/switchSourceHeader", + &ProtocolCallbacks::onSwitchSourceHeader); } + +} // namespace clangd +} // namespace clang Index: test/clangd/fixits.test =================================================================== --- test/clangd/fixits.test +++ test/clangd/fixits.test @@ -15,13 +15,13 @@ {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} # -# CHECK: {"jsonrpc":"2.0","id":2, "result": [{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}] +# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}] # Content-Length: 771 {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} # Make sure unused "code" and "source" fields ignored gracefully -# CHECK: {"jsonrpc":"2.0","id":2, "result": [{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}] +# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}] # Content-Length: 44