Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -27,12 +27,12 @@ JSONRPCDispatcher.cpp Logger.cpp Protocol.cpp - ProtocolHandlers.cpp Quality.cpp RIFF.cpp SourceCode.cpp Threading.cpp Trace.cpp + Transport.cpp TUScheduler.cpp URI.cpp XRefs.cpp Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -16,7 +16,7 @@ #include "GlobalCompilationDatabase.h" #include "Path.h" #include "Protocol.h" -#include "ProtocolHandlers.h" +#include "Transport.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/Optional.h" #include @@ -24,7 +24,6 @@ namespace clang { namespace clangd { -class JSONOutput; class SymbolIndex; /// This class exposes ClangdServer's capabilities via Language Server Protocol. @@ -32,54 +31,59 @@ /// JSONRPCDispatcher binds the implemented ProtocolCallbacks methods /// (e.g. onInitialize) to corresponding JSON-RPC methods ("initialize"). /// The server also supports $/cancelRequest (JSONRPCDispatcher provides this). -class ClangdLSPServer : private DiagnosticsConsumer, private ProtocolCallbacks { +class ClangdLSPServer : private DiagnosticsConsumer, private Transport::MessageHandler { public: /// If \p CompileCommandsDir has a value, compile_commands.json will be /// loaded only from \p CompileCommandsDir. Otherwise, clangd will look /// for compile_commands.json in all parent directories of each file. - ClangdLSPServer(JSONOutput &Out, const clangd::CodeCompleteOptions &CCOpts, + ClangdLSPServer(Transport &TransportLayer, + const clangd::CodeCompleteOptions &CCOpts, llvm::Optional CompileCommandsDir, bool ShouldUseInMemoryCDB, const ClangdServer::Options &Opts); - /// Run LSP server loop, receiving input for it from \p In. \p In must be + /// Run LSP server loop, receiving input from the TransportLater \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. /// /// \return Whether we received a 'shutdown' request before an 'exit' request. - bool run(std::FILE *In, - JSONStreamStyle InputStyle = JSONStreamStyle::Standard); + bool run(); + + bool notifyServer(llvm::StringRef Method, llvm::json::Value ) override; + bool callServer(llvm::StringRef Method, llvm::json::Value Params, + llvm::json::Value ID) override; + bool replyToServer(llvm::json::Value ID, + llvm::Expected Result) override; private: // Implement DiagnosticsConsumer. void onDiagnosticsReady(PathRef File, std::vector Diagnostics) override; - // Implement ProtocolCallbacks. - void onInitialize(InitializeParams &Params) override; - void onShutdown(ShutdownParams &Params) override; - void onExit(ExitParams &Params) override; - void onDocumentDidOpen(DidOpenTextDocumentParams &Params) override; - void onDocumentDidChange(DidChangeTextDocumentParams &Params) override; - void onDocumentDidClose(DidCloseTextDocumentParams &Params) override; - void - onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) override; - void - onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) override; - void onDocumentFormatting(DocumentFormattingParams &Params) override; - void onDocumentSymbol(DocumentSymbolParams &Params) override; - void onCodeAction(CodeActionParams &Params) override; - void onCompletion(TextDocumentPositionParams &Params) override; - void onSignatureHelp(TextDocumentPositionParams &Params) override; - void onGoToDefinition(TextDocumentPositionParams &Params) override; - void onReference(ReferenceParams &Params) override; - void onSwitchSourceHeader(TextDocumentIdentifier &Params) override; - void onDocumentHighlight(TextDocumentPositionParams &Params) override; - void onFileEvent(DidChangeWatchedFilesParams &Params) override; - void onCommand(ExecuteCommandParams &Params) override; - void onWorkspaceSymbol(WorkspaceSymbolParams &Params) override; - void onRename(RenameParams &Parames) override; - void onHover(TextDocumentPositionParams &Params) override; - void onChangeConfiguration(DidChangeConfigurationParams &Params) override; + // Implement LSP methods. + void onInitialize(InitializeParams &Params); + void onShutdown(ShutdownParams &Params); + void onExit(ExitParams &Params); + void onDocumentDidOpen(DidOpenTextDocumentParams &Params); + void onDocumentDidChange(DidChangeTextDocumentParams &Params); + void onDocumentDidClose(DidCloseTextDocumentParams &Params); + void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params); + void onDocumentRangeFormatting(DocumentRangeFormattingParams &Params); + void onDocumentFormatting(DocumentFormattingParams &Params); + void onDocumentSymbol(DocumentSymbolParams &Params); + void onCodeAction(CodeActionParams &Params); + void onCompletion(TextDocumentPositionParams &Params); + void onSignatureHelp(TextDocumentPositionParams &Params); + void onGoToDefinition(TextDocumentPositionParams &Params); + void onReference(ReferenceParams &Params); + void onSwitchSourceHeader(TextDocumentIdentifier &Params); + void onDocumentHighlight(TextDocumentPositionParams &Params); + void onFileEvent(DidChangeWatchedFilesParams &Params); + void onCommand(ExecuteCommandParams &Params); + void onWorkspaceSymbol(WorkspaceSymbolParams &Params); + void onRename(RenameParams &Parames); + void onHover(TextDocumentPositionParams &Params); + void onChangeConfiguration(DidChangeConfigurationParams &Params); + void onCancelRequest(CancelRequestParams &Params); std::vector getFixes(StringRef File, const clangd::Diagnostic &D); @@ -89,7 +93,7 @@ void reparseOpenedFiles(); void applyConfiguration(const ClangdConfigurationParamsChange &Settings); - JSONOutput &Out; + Transport &TransportLayer; /// Used to indicate that the 'shutdown' request was received from the /// Language Server client. bool ShutdownRequestReceived = false; @@ -176,6 +180,18 @@ // Set in construtor and destroyed when run() finishes. To ensure all worker // threads exit before run() returns. std::unique_ptr Server; + + // Tracking cancellations needs a mutex: handlers may finish on a different + // thread, and that's when we clean up entries in the map. + mutable std::mutex RequestCancelersMutex; + llvm::StringMap> RequestCancelers; + unsigned NextRequestCookie = 0; + Context cancelableRequestContext(const llvm::json::Value &ID); + void cancelRequest(const llvm::json::Value &ID); + + using Handler = std::function; + llvm::StringMap Handlers; + Handler UnknownHandler; }; } // namespace clangd } // namespace clang Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -15,8 +15,20 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" +// TODO looking for llvm::to_string() +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/SourceMgr.h" + using namespace clang::clangd; using namespace clang; using namespace llvm; @@ -78,8 +90,149 @@ return Defaults; } +// When tracing, we trace a request and attach the response in reply(). +// Because the Span isn't available, we find the current request using Context. +class RequestSpan { + RequestSpan(llvm::json::Object *Args) : Args(Args) {} + std::mutex Mu; + llvm::json::Object *Args; + static Key> RSKey; + +public: + // Return a context that's aware of the enclosing request, identified by Span. + static Context stash(const trace::Span &Span) { + return Context::current().derive( + RSKey, std::unique_ptr(new RequestSpan(Span.Args))); + } + + // If there's an enclosing request and the tracer is interested, calls \p F + // with a json::Object where request info can be added. + template static void attach(Func &&F) { + auto *RequestArgs = Context::current().get(RSKey); + if (!RequestArgs || !*RequestArgs || !(*RequestArgs)->Args) + return; + std::lock_guard Lock((*RequestArgs)->Mu); + F(*(*RequestArgs)->Args); + } +}; +Key> RequestSpan::RSKey; + +static Key CurrentTransportLayer; +static Key RequestID; + +const json::Value *getRequestId() { + return Context::current().get(RequestID); +} + +void reply(json::Value &&Result) { + auto ID = getRequestId(); + if (!ID) { + elog("Attempted to reply to a notification!"); + return; + } + RequestSpan::attach([&](json::Object &Args) { Args["Reply"] = Result; }); + log("--> reply({0})", *ID); + Context::current() + .getExisting(CurrentTransportLayer) + ->replyToClient(*ID, std::move(Result)); +} + +void replyError(ErrorCode Code, const llvm::StringRef &Message) { + elog("Error {0}: {1}", static_cast(Code), Message); + RequestSpan::attach([&](json::Object &Args) { + Args["Error"] = json::Object{{"code", static_cast(Code)}, + {"message", Message.str()}}; + }); +/* + ->writeMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"id", *ID}, + {"error", json::Object{{"code", static_cast(Code)}, + {"message", Message}}}, + }); +*/ + LSPError Error; + Error.Code = Code; + Error.Message = Message; + // TODO ID? + + if (auto ID = getRequestId()) { + log("--> reply({0}) error: {1}", *ID, Message); + Context::current() + .getExisting(CurrentTransportLayer) + ->replyToClient( + *ID, make_error(Error) + ); + } +} + +void replyError(Error E) { + handleAllErrors(std::move(E), + [](const CancelledError &TCE) { + replyError(ErrorCode::RequestCancelled, TCE.message()); + }, + [](const ErrorInfoBase &EIB) { + replyError(ErrorCode::InvalidParams, EIB.message()); + }); +} + +void call(StringRef Method, json::Value &&Params) { + RequestSpan::attach([&](json::Object &Args) { + Args["Call"] = json::Object{{"method", Method.str()}, {"params", Params}}; + }); + // FIXME: Generate/Increment IDs for every request so that we can get proper + // replies once we need to. + auto ID = 1; + log("--> {0}({1})", Method, ID); + Context::current() + .getExisting(CurrentTransportLayer) + ->callClient(Method, std::move(Params), ID); +} + } // namespace +bool ClangdLSPServer::callServer(llvm::StringRef Method, llvm::json::Value Params, + llvm::json::Value ID) { + auto I = Handlers.find(Method); + auto &Handler = I != Handlers.end() ? I->second : UnknownHandler; + + // Create a Context that contains request information. + WithContextValue WithTransportLayer(CurrentTransportLayer, &TransportLayer); + WithContextValue WithID(RequestID, ID); + + // Create a tracing Span covering the whole request lifetime. + trace::Span Tracer(Method); + SPAN_ATTACH(Tracer, "ID", ID); + SPAN_ATTACH(Tracer, "Params", Params); + + // Requests with IDs can be canceled by the client. Add cancellation context. + WithContext WithCancel(cancelableRequestContext(ID)); + + // Stash a reference to the span args, so later calls can add metadata. + WithContext WithRequestSpan(RequestSpan::stash(Tracer)); + Handler(std::move(Params)); + return true; +} + +bool ClangdLSPServer::notifyServer(llvm::StringRef Method, llvm::json::Value Params) { + auto I = Handlers.find(Method); + auto &Handler = I != Handlers.end() ? I->second : UnknownHandler; + + // Create a tracing Span covering the whole request lifetime. + trace::Span Tracer(Method); + SPAN_ATTACH(Tracer, "Params", Params); + + // Stash a reference to the span args, so later calls can add metadata. + WithContext WithRequestSpan(RequestSpan::stash(Tracer)); + Handler(std::move(Params)); + return true; +} + +bool ClangdLSPServer::replyToServer(llvm::json::Value ID, llvm::Expected Result) { + // TODO + llvm_unreachable("Unimplemented"); +} + void ClangdLSPServer::onInitialize(InitializeParams &Params) { if (Params.initializationOptions) applyConfiguration(*Params.initializationOptions); @@ -149,7 +302,8 @@ void ClangdLSPServer::onShutdown(ShutdownParams &Params) { // Do essentially nothing, just say we're ready to exit. ShutdownRequestReceived = true; - reply(nullptr); + ::reply(nullptr); + TransportLayer.shouldTerminateLoop = true; } void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; } @@ -199,7 +353,7 @@ // We don't need the response so id == 1 is OK. // Ideally, we would wait for the response and if there is no error, we // would reply success/failure to the original RPC. - call("workspace/applyEdit", Edit); + ::call("workspace/applyEdit", Edit); }; if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && Params.workspaceEdit) { @@ -212,13 +366,13 @@ // 6. The editor applies the changes (applyEdit), and sends us a reply (but // we ignore it) - reply("Fix applied."); + ::reply("Fix applied."); ApplyEdit(*Params.workspaceEdit); } else { // We should not get here because ExecuteCommandParams would not have // parsed in the first place and this handler should not be called. But if // more commands are added, this will be here has a safe guard. - replyError( + ::replyError( ErrorCode::InvalidParams, llvm::formatv("Unsupported command \"{0}\".", Params.command).str()); } @@ -229,12 +383,12 @@ Params.query, CCOpts.Limit, [this](llvm::Expected> Items) { if (!Items) - return replyError(ErrorCode::InternalError, + return ::replyError(ErrorCode::InternalError, llvm::toString(Items.takeError())); for (auto &Sym : *Items) Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); - reply(json::Array(*Items)); + ::reply(json::Array(*Items)); }); } @@ -242,7 +396,7 @@ Path File = Params.textDocument.uri.file(); llvm::Optional Code = DraftMgr.getDraft(File); if (!Code) - return replyError(ErrorCode::InvalidParams, + return ::replyError(ErrorCode::InvalidParams, "onRename called for non-added file"); Server->rename( @@ -250,7 +404,7 @@ [File, Code, Params](llvm::Expected> Replacements) { if (!Replacements) - return replyError(ErrorCode::InternalError, + return ::replyError(ErrorCode::InternalError, llvm::toString(Replacements.takeError())); // Turn the replacements into the format specified by the Language @@ -260,7 +414,7 @@ Edits.push_back(replacementToEdit(*Code, R)); WorkspaceEdit WE; WE.changes = {{Params.textDocument.uri.uri(), Edits}}; - reply(WE); + ::reply(WE); }); } @@ -276,14 +430,14 @@ auto File = Params.textDocument.uri.file(); auto Code = DraftMgr.getDraft(File); if (!Code) - return replyError(ErrorCode::InvalidParams, + return ::replyError(ErrorCode::InvalidParams, "onDocumentOnTypeFormatting called for non-added file"); auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position); if (ReplacementsOrError) - reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + ::reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); else - replyError(ErrorCode::UnknownErrorCode, + ::replyError(ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); } @@ -292,14 +446,14 @@ auto File = Params.textDocument.uri.file(); auto Code = DraftMgr.getDraft(File); if (!Code) - return replyError(ErrorCode::InvalidParams, + return ::replyError(ErrorCode::InvalidParams, "onDocumentRangeFormatting called for non-added file"); auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range); if (ReplacementsOrError) - reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + ::reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); else - replyError(ErrorCode::UnknownErrorCode, + ::replyError(ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); } @@ -307,14 +461,14 @@ auto File = Params.textDocument.uri.file(); auto Code = DraftMgr.getDraft(File); if (!Code) - return replyError(ErrorCode::InvalidParams, + return ::replyError(ErrorCode::InvalidParams, "onDocumentFormatting called for non-added file"); auto ReplacementsOrError = Server->formatFile(*Code, File); if (ReplacementsOrError) - reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + ::reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); else - replyError(ErrorCode::UnknownErrorCode, + ::replyError(ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); } @@ -323,11 +477,11 @@ Params.textDocument.uri.file(), [this](llvm::Expected> Items) { if (!Items) - return replyError(ErrorCode::InvalidParams, + return ::replyError(ErrorCode::InvalidParams, llvm::toString(Items.takeError())); for (auto &Sym : *Items) Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); - reply(json::Array(*Items)); + ::reply(json::Array(*Items)); }); } @@ -352,7 +506,7 @@ }); } } - reply(std::move(Commands)); + ::reply(std::move(Commands)); } void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { @@ -368,7 +522,7 @@ C.kind, SupportedCompletionItemKinds); LSPList.items.push_back(std::move(C)); } - return reply(std::move(LSPList)); + return ::reply(std::move(LSPList)); }); } @@ -379,7 +533,7 @@ return replyError( ErrorCode::InvalidParams, llvm::toString(SignatureHelp.takeError())); - reply(*SignatureHelp); + ::reply(*SignatureHelp); }); } @@ -390,13 +544,13 @@ return replyError( ErrorCode::InvalidParams, llvm::toString(Items.takeError())); - reply(json::Array(*Items)); + ::reply(json::Array(*Items)); }); } void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) { llvm::Optional Result = Server->switchSourceHeader(Params.uri.file()); - reply(Result ? URI::createFile(*Result).toString() : ""); + ::reply(Result ? URI::createFile(*Result).toString() : ""); } void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) { @@ -406,7 +560,7 @@ if (!Highlights) return replyError(ErrorCode::InternalError, llvm::toString(Highlights.takeError())); - reply(json::Array(*Highlights)); + ::reply(json::Array(*Highlights)); }); } @@ -419,7 +573,7 @@ return; } - reply(*H); + ::reply(*H); }); } @@ -462,38 +616,93 @@ Server->findReferences(Params.textDocument.uri.file(), Params.position, [](llvm::Expected> Locations) { if (!Locations) - return replyError( + return ::replyError( ErrorCode::InternalError, llvm::toString(Locations.takeError())); - reply(llvm::json::Array(*Locations)); + ::reply(llvm::json::Array(*Locations)); }); } -ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, +// TODO refactor +typedef std::function Handler; +template + void Register( + ClangdLSPServer& LSPServer, + llvm::StringMap& Handlers, + StringRef Method, + void (ClangdLSPServer::*Handler)(Param) + ) { + // Capture pointers by value, as the lambda will outlive this object. + assert(!Handlers.count(Method) && "Handler already registered!"); + Handlers[Method] = std::move( + [=, &LSPServer](const json::Value &RawParams) { + typename std::remove_reference::type P; + if (fromJSON(RawParams, P)) { + (LSPServer.*Handler)(P); + } else { + elog("Failed to decode {0} request.", Method); + } + } + ); + } + +ClangdLSPServer::ClangdLSPServer(Transport &TransportLayer, const clangd::CodeCompleteOptions &CCOpts, llvm::Optional CompileCommandsDir, bool ShouldUseInMemoryCDB, const ClangdServer::Options &Opts) - : Out(Out), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory() + : TransportLayer(TransportLayer), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory() : CompilationDB::makeDirectoryBased( std::move(CompileCommandsDir))), CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()), SupportedCompletionItemKinds(defaultCompletionItemKinds()), Server(new ClangdServer(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this, - Opts)) {} - -bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) { + Opts)), + UnknownHandler([](const json::Value &Params) { + ::replyError(ErrorCode::MethodNotFound, "method not found"); + }) +{ + Register(*this, Handlers, "initialize", &ClangdLSPServer::onInitialize); + Register(*this, Handlers, "shutdown", &ClangdLSPServer::onShutdown); + Register(*this, Handlers, "exit", &ClangdLSPServer::onExit); + Register(*this, Handlers, "textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen); + Register(*this, Handlers, "textDocument/didClose", &ClangdLSPServer::onDocumentDidClose); + Register(*this, Handlers, "textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); + Register(*this, Handlers, "textDocument/rangeFormatting", + &ClangdLSPServer::onDocumentRangeFormatting); + Register(*this, Handlers, "textDocument/onTypeFormatting", + &ClangdLSPServer::onDocumentOnTypeFormatting); + Register(*this, Handlers, "textDocument/formatting", &ClangdLSPServer::onDocumentFormatting); + Register(*this, Handlers, "textDocument/codeAction", &ClangdLSPServer::onCodeAction); + Register(*this, Handlers, "textDocument/completion", &ClangdLSPServer::onCompletion); + Register(*this, Handlers, "textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp); + Register(*this, Handlers, "textDocument/definition", &ClangdLSPServer::onGoToDefinition); + Register(*this, Handlers, "textDocument/references", &ClangdLSPServer::onReference); + Register(*this, Handlers, "textDocument/switchSourceHeader", + &ClangdLSPServer::onSwitchSourceHeader); + Register(*this, Handlers, "textDocument/rename", &ClangdLSPServer::onRename); + Register(*this, Handlers, "textDocument/hover", &ClangdLSPServer::onHover); + Register(*this, Handlers, "textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol); + Register(*this, Handlers, "workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); + Register(*this, Handlers, "workspace/executeCommand", &ClangdLSPServer::onCommand); + Register(*this, Handlers, "textDocument/documentHighlight", + &ClangdLSPServer::onDocumentHighlight); + Register(*this, Handlers, "workspace/didChangeConfiguration", + &ClangdLSPServer::onChangeConfiguration); + Register(*this, Handlers, "workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol); + Register(*this, Handlers, "$/cancelRequest", &ClangdLSPServer::onCancelRequest); +} + +bool ClangdLSPServer::run() { assert(!IsDone && "Run was called before"); assert(Server); - // Set up JSONRPCDispatcher. - JSONRPCDispatcher Dispatcher([](const json::Value &Params) { - replyError(ErrorCode::MethodNotFound, "method not found"); - }); - registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this); + bool Error = !TransportLayer.loop(*this); - // Run the Language Server loop. - runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone); + if (Error) { + // TODO XXX + elog("Transport error."); + } // 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. @@ -501,7 +710,7 @@ // Destroy ClangdServer to ensure all worker threads finish. Server.reset(); - return ShutdownRequestReceived; + return ShutdownRequestReceived && Error; } std::vector ClangdLSPServer::getFixes(StringRef File, @@ -519,6 +728,28 @@ return FixItsIter->second; } +void ClangdLSPServer::onCancelRequest(CancelRequestParams &Params) { + // TODO + /* + JSONRPCDispatcher::JSONRPCDispatcher(Handler UnknownHandler) + : UnknownHandler(std::move(UnknownHandler)) { + registerHandler("$/cancelRequest", [this](const json::Value &Params) { + if (auto *O = Params.getAsObject()) + if (auto *ID = O->get("id")) + return cancelRequest(*ID); + log("Bad cancellation request: {0}", Params); + }); + } + + void JSONRPCDispatcher::cancelRequest(const json::Value &ID) { + auto StrID = llvm::to_string(ID); + std::lock_guard Lock(RequestCancelersMutex); + auto It = RequestCancelers.find(StrID); + if (It != RequestCancelers.end()) + It->second.first(); // Invoke the canceler. + } + */ +} void ClangdLSPServer::onDiagnosticsReady(PathRef File, std::vector Diagnostics) { json::Array DiagnosticsJSON; @@ -562,15 +793,12 @@ } // Publish diagnostics. - Out.writeMessage(json::Object{ - {"jsonrpc", "2.0"}, - {"method", "textDocument/publishDiagnostics"}, - {"params", - json::Object{ - {"uri", URIForFile{File}}, - {"diagnostics", std::move(DiagnosticsJSON)}, - }}, - }); + TransportLayer.notifyClient( + "textDocument/publishDiagnostics", + json::Object{ + {"uri", URIForFile{File}}, + {"diagnostics", std::move(DiagnosticsJSON)}, + }); } void ClangdLSPServer::reparseOpenedFiles() { @@ -642,3 +870,21 @@ return *CachingCDB; return *CDB; } + +Context ClangdLSPServer::cancelableRequestContext(const json::Value &ID) { + auto Task = cancelableTask(); + auto StrID = llvm::to_string(ID); // JSON-serialize ID for map key. + auto Cookie = NextRequestCookie++; // No lock, only called on main thread. + { + std::lock_guard Lock(RequestCancelersMutex); + RequestCancelers[StrID] = {std::move(Task.second), Cookie}; + } + // When the request ends, we can clean up the entry we just added. + // The cookie lets us check that it hasn't been overwritten due to ID reuse. + return Task.first.derive(make_scope_exit([this, StrID, Cookie] { + std::lock_guard Lock(RequestCancelersMutex); + auto It = RequestCancelers.find(StrID); + if (It != RequestCancelers.end() && It->second.second == Cookie) + RequestCancelers.erase(It); + })); +} \ No newline at end of file Index: clangd/JSONRPCDispatcher.h =================================================================== --- clangd/JSONRPCDispatcher.h +++ clangd/JSONRPCDispatcher.h @@ -59,6 +59,7 @@ std::mutex StreamMutex; }; +/* /// Sends a successful reply. /// Current context must derive from JSONRPCDispatcher::Handler. void reply(llvm::json::Value &&Result); @@ -75,6 +76,7 @@ /// Sends a request to the client. /// Current context must derive from JSONRPCDispatcher::Handler. void call(llvm::StringRef Method, llvm::json::Value &&Params); +*/ /// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the /// registered Handler for the method received. @@ -112,26 +114,6 @@ llvm::StringMap Handlers; Handler UnknownHandler; }; - -/// Controls the way JSON-RPC messages are encoded (both input and output). -enum JSONStreamStyle { - /// Encoding per the LSP specification, with mandatory Content-Length header. - Standard, - /// Messages are delimited by a '---' line. Comment lines start with #. - Delimited -}; - -/// 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. -/// We use C-style FILE* for reading as std::istream has unclear interaction -/// with signals, which are sent by debuggers on some OSs. -void runLanguageServerLoop(std::FILE *In, JSONOutput &Out, - JSONStreamStyle InputStyle, - JSONRPCDispatcher &Dispatcher, bool &IsDone); } // namespace clangd } // namespace clang Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -9,7 +9,6 @@ #include "JSONRPCDispatcher.h" #include "Cancellation.h" -#include "ProtocolHandlers.h" #include "Trace.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" @@ -95,71 +94,6 @@ InputMirror->flush(); } -void clangd::reply(json::Value &&Result) { - auto ID = getRequestId(); - if (!ID) { - elog("Attempted to reply to a notification!"); - return; - } - RequestSpan::attach([&](json::Object &Args) { Args["Reply"] = Result; }); - log("--> reply({0})", *ID); - Context::current() - .getExisting(RequestOut) - ->writeMessage(json::Object{ - {"jsonrpc", "2.0"}, - {"id", *ID}, - {"result", std::move(Result)}, - }); -} - -void clangd::replyError(ErrorCode Code, const llvm::StringRef &Message) { - elog("Error {0}: {1}", static_cast(Code), Message); - RequestSpan::attach([&](json::Object &Args) { - Args["Error"] = json::Object{{"code", static_cast(Code)}, - {"message", Message.str()}}; - }); - - if (auto ID = getRequestId()) { - log("--> reply({0}) error: {1}", *ID, Message); - Context::current() - .getExisting(RequestOut) - ->writeMessage(json::Object{ - {"jsonrpc", "2.0"}, - {"id", *ID}, - {"error", json::Object{{"code", static_cast(Code)}, - {"message", Message}}}, - }); - } -} - -void clangd::replyError(Error E) { - handleAllErrors(std::move(E), - [](const CancelledError &TCE) { - replyError(ErrorCode::RequestCancelled, TCE.message()); - }, - [](const ErrorInfoBase &EIB) { - replyError(ErrorCode::InvalidParams, EIB.message()); - }); -} - -void clangd::call(StringRef Method, json::Value &&Params) { - RequestSpan::attach([&](json::Object &Args) { - Args["Call"] = json::Object{{"method", Method.str()}, {"params", Params}}; - }); - // FIXME: Generate/Increment IDs for every request so that we can get proper - // replies once we need to. - auto ID = 1; - log("--> {0}({1})", Method, ID); - Context::current() - .getExisting(RequestOut) - ->writeMessage(json::Object{ - {"jsonrpc", "2.0"}, - {"id", ID}, - {"method", Method}, - {"params", std::move(Params)}, - }); -} - JSONRPCDispatcher::JSONRPCDispatcher(Handler UnknownHandler) : UnknownHandler(std::move(UnknownHandler)) { registerHandler("$/cancelRequest", [this](const json::Value &Params) { @@ -388,42 +322,4 @@ llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON)); return std::move(JSON); } -} - -// The use of C-style std::FILE* IO deserves some explanation. -// Previously, std::istream was used. When a debugger attached on MacOS, the -// process received EINTR, the stream went bad, and clangd exited. -// A retry-on-EINTR loop around reads solved this problem, but caused clangd to -// sometimes hang rather than exit on other OSes. The interaction between -// istreams and signals isn't well-specified, so it's hard to get this right. -// The C APIs seem to be clearer in this respect. -void clangd::runLanguageServerLoop(std::FILE *In, JSONOutput &Out, - JSONStreamStyle InputStyle, - JSONRPCDispatcher &Dispatcher, - bool &IsDone) { - auto &ReadMessage = - (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage; - while (!IsDone && !feof(In)) { - if (ferror(In)) { - elog("IO error: {0}", llvm::sys::StrError()); - return; - } - if (auto JSON = ReadMessage(In, Out)) { - if (auto Doc = json::parse(*JSON)) { - // Log the formatted message. - vlog(Out.Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc); - // Finally, execute the action for this JSON message. - if (!Dispatcher.call(*Doc, Out)) - elog("JSON dispatch failed!"); - } else { - // Parse error. Log the raw message. - vlog("<<< {0}\n", *JSON); - elog("JSON parse error: {0}", llvm::toString(Doc.takeError())); - } - } - } -} - -const json::Value *clangd::getRequestId() { - return Context::current().get(RequestID); -} +} \ No newline at end of file Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -26,6 +26,7 @@ #include "URI.h" #include "llvm/ADT/Optional.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include #include @@ -48,6 +49,15 @@ // Defined by the protocol. RequestCancelled = -32800, }; +struct LSPError : public llvm::ErrorInfo { + ErrorCode Code; + std::string Message; + static char ID; + void log(llvm::raw_ostream& OS) const override { OS << Message; } + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } +}; struct URIForFile { URIForFile() = default; @@ -883,6 +893,13 @@ }; bool fromJSON(const llvm::json::Value &, RenameParams &); +struct CancelRequestParams { + /* TODO */ +}; + +// TODO +inline bool fromJSON(const llvm::json::Value &, CancelRequestParams &) { return true; } + enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; /// A document highlight is a range inside a text document which deserves Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -25,6 +25,8 @@ namespace clangd { using namespace llvm; +char LSPError::ID; + URIForFile::URIForFile(std::string AbsPath) { assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative"); File = std::move(AbsPath); Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ /dev/null @@ -1,67 +0,0 @@ -//===--- ProtocolHandlers.h - LSP callbacks ---------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// 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. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOLHANDLERS_H - -#include "JSONRPCDispatcher.h" -#include "Protocol.h" -#include "llvm/ADT/Twine.h" -#include "llvm/Support/raw_ostream.h" - -namespace clang { -namespace clangd { - -// The interface implemented by ClangLSPServer to handle incoming requests. -class ProtocolCallbacks { -public: - virtual ~ProtocolCallbacks() = default; - - virtual void onInitialize(InitializeParams &Params) = 0; - virtual void onShutdown(ShutdownParams &Params) = 0; - virtual void onExit(ExitParams &Params) = 0; - virtual void onDocumentDidOpen(DidOpenTextDocumentParams &Params) = 0; - virtual void onDocumentDidChange(DidChangeTextDocumentParams &Params) = 0; - virtual void onDocumentDidClose(DidCloseTextDocumentParams &Params) = 0; - virtual void onDocumentFormatting(DocumentFormattingParams &Params) = 0; - virtual void onDocumentSymbol(DocumentSymbolParams &Params) = 0; - virtual void - onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) = 0; - virtual void - onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) = 0; - virtual void onCodeAction(CodeActionParams &Params) = 0; - virtual void onCompletion(TextDocumentPositionParams &Params) = 0; - virtual void onSignatureHelp(TextDocumentPositionParams &Params) = 0; - virtual void onGoToDefinition(TextDocumentPositionParams &Params) = 0; - virtual void onReference(ReferenceParams &Params) = 0; - virtual void onSwitchSourceHeader(TextDocumentIdentifier &Params) = 0; - virtual void onFileEvent(DidChangeWatchedFilesParams &Params) = 0; - virtual void onCommand(ExecuteCommandParams &Params) = 0; - virtual void onWorkspaceSymbol(WorkspaceSymbolParams &Params) = 0; - virtual void onRename(RenameParams &Parames) = 0; - virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; - virtual void onHover(TextDocumentPositionParams &Params) = 0; - virtual void onChangeConfiguration(DidChangeConfigurationParams &Params) = 0; -}; - -void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, - ProtocolCallbacks &Callbacks); - -} // namespace clangd -} // namespace clang - -#endif Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ /dev/null @@ -1,79 +0,0 @@ -//===--- ProtocolHandlers.cpp - LSP callbacks -----------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "ProtocolHandlers.h" -#include "ClangdLSPServer.h" -#include "ClangdServer.h" -#include "DraftStore.h" -#include "Trace.h" - -using namespace clang; -using namespace clang::clangd; -using namespace llvm; - -namespace { - -// 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 fromJSON function. -struct HandlerRegisterer { - template - void operator()(StringRef Method, void (ProtocolCallbacks::*Handler)(Param)) { - // Capture pointers by value, as the lambda will outlive this object. - auto *Callbacks = this->Callbacks; - Dispatcher.registerHandler(Method, [=](const json::Value &RawParams) { - typename std::remove_reference::type P; - if (fromJSON(RawParams, P)) { - (Callbacks->*Handler)(P); - } else { - elog("Failed to decode {0} request.", Method); - } - }); - } - - JSONRPCDispatcher &Dispatcher; - ProtocolCallbacks *Callbacks; -}; - -} // namespace - -void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, - ProtocolCallbacks &Callbacks) { - HandlerRegisterer Register{Dispatcher, &Callbacks}; - - Register("initialize", &ProtocolCallbacks::onInitialize); - Register("shutdown", &ProtocolCallbacks::onShutdown); - Register("exit", &ProtocolCallbacks::onExit); - 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/signatureHelp", &ProtocolCallbacks::onSignatureHelp); - Register("textDocument/definition", &ProtocolCallbacks::onGoToDefinition); - Register("textDocument/references", &ProtocolCallbacks::onReference); - Register("textDocument/switchSourceHeader", - &ProtocolCallbacks::onSwitchSourceHeader); - Register("textDocument/rename", &ProtocolCallbacks::onRename); - Register("textDocument/hover", &ProtocolCallbacks::onHover); - Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol); - Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); - Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); - Register("textDocument/documentHighlight", - &ProtocolCallbacks::onDocumentHighlight); - Register("workspace/didChangeConfiguration", - &ProtocolCallbacks::onChangeConfiguration); - Register("workspace/symbol", &ProtocolCallbacks::onWorkspaceSymbol); -} Index: clangd/Transport.h =================================================================== --- /dev/null +++ clangd/Transport.h @@ -0,0 +1,94 @@ +//===--- Transport.h - sending and receiving LSP messages -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// The language server protocol is usually implemented by writing messages as +// JSON-RPC over the stdin/stdout of a subprocess. However other communications +// mechanisms are possible, such as XPC on mac (see xpc/ directory). +// +// The Transport interface allows the mechanism to be replaced, and the JSONRPC +// Transport is the standard implementation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H_ + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { + +// A transport is responsible for maintaining the connection to a client +// application, and reading/writing structured messages to it. +// +// Transports have limited thread safety requirements: +// - messages will not be sent concurrently +// - messages MAY be sent while loop() is reading, or its callback is active +class Transport { +public: + Transport() : shouldTerminateLoop(false) { } + virtual ~Transport() = default; + + // Called by Clangd to send messages to the client. + // (Because JSON and XPC are so similar, these are concrete and delegate to + // sendMessage. We could change this to support more diverse transports). + void notifyClient(llvm::StringRef Method, llvm::json::Value Params); + void callClient(llvm::StringRef Method, llvm::json::Value Params, + llvm::json::Value ID); + void replyToClient(llvm::json::Value ID, llvm::Expected Result); + + // Implemented by Clangd to handle incoming messages. (See loop() below). + class MessageHandler { + public: + // TODO was originally abstract + virtual ~MessageHandler() {} + virtual bool notifyServer(llvm::StringRef Method, llvm::json::Value ) = 0; + virtual bool callServer(llvm::StringRef Method, llvm::json::Value Params, + llvm::json::Value ID) = 0; + virtual bool replyToServer(llvm::json::Value ID, + llvm::Expected Result) = 0; + }; + // Called by Clangd to receive messages from the client. + // The transport should in turn invoke the handler to process messages. + // If handler returns true, the transport should immedately return success. + // Otherwise, it returns an error when the transport becomes unusable. + // (Because JSON and XPC are so similar, they share handleMessage()). + virtual bool loop(MessageHandler &) = 0; + + std::atomic shouldTerminateLoop; + +protected: + // Common implementation for notify(), call(), and reply(). + virtual void sendMessage(llvm::json::Value) = 0; + // Delegates to notify(), call(), and reply(). + bool handleMessage(llvm::json::Value, MessageHandler&); +}; + +// Controls the way JSON-RPC messages are encoded (both input and output). +enum JSONStreamStyle { + // Encoding per the LSP specification, with mandatory Content-Length header. + Standard, + // Messages are delimited by a '---' line. Comment lines start with #. + Delimited +}; + +// Returns a Transport that speaks JSON-RPC over a pair of streams. +// The input stream must be opened in binary mode. +std::unique_ptr +newJSONTransport(std::FILE *In, llvm::raw_ostream &Out, + JSONStreamStyle = JSONStreamStyle::Standard); + +} // namespace clangd +} // namespace clang + +#endif + + Index: clangd/Transport.cpp =================================================================== --- /dev/null +++ clangd/Transport.cpp @@ -0,0 +1,311 @@ +//===--- Transport.cpp - sending and receiving LSP messages -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// The JSON-RPC transport is implemented here. +// The alternative Mac-only XPC transport is in the xpc/ directory. +// +//===----------------------------------------------------------------------===// +#include "Transport.h" +#include "Logger.h" +#include "Protocol.h" +#include "llvm/Support/Errno.h" + +using namespace llvm; +namespace clang { +namespace clangd { + +void Transport::notifyClient(llvm::StringRef Method, llvm::json::Value Params) { + sendMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"method", Method}, + {"params", std::move(Params)}, + }); +} +void Transport::callClient(llvm::StringRef Method, llvm::json::Value Params, + llvm::json::Value ID) { + sendMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"id", std::move(ID)}, + {"method", Method}, + {"params", std::move(Params)}, + }); +} +void Transport::replyToClient(llvm::json::Value ID, + llvm::Expected Result) { + auto Message = json::Object{ + {"jsonrpc", "2.0"}, + {"id", std::move(ID)}, + }; + if (Result) + Message["result"] = std::move(*Result); + else { + ErrorCode Code = ErrorCode::UnknownErrorCode; + std::string Msg = + toString(handleErrors(Result.takeError(), [&](const LSPError &Err) { + Code = Err.Code; + return make_error(Err); // Recreate error for its message. + })); + Message["error"] = json::Object{ + {"message", Msg}, + {"code", static_cast(Code)}, + }; + } + sendMessage(std::move(Message)); +} + +namespace { + +// Tries to read a line up to and including \n. +// If failing, feof() or ferror() will be set. +static bool readLine(std::FILE *In, std::string &Out) { + static constexpr int BufSize = 1024; + size_t Size = 0; + Out.clear(); + for (;;) { + Out.resize(Size + BufSize); + // Handle EINTR which is sent when a debugger attaches on some platforms. + if (!llvm::sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In)) + return false; + clearerr(In); + // If the line contained null bytes, anything after it (including \n) will + // be ignored. Fortunately this is not a legal header or JSON. + size_t Read = std::strlen(&Out[Size]); + if (Read > 0 && Out[Size + Read - 1] == '\n') { + Out.resize(Size + Read); + return true; + } + Size += Read; + } +} + +// Returns None when: +// - ferror() or feof() are set. +// - Content-Length is missing or empty (protocol error) +static llvm::Optional readStandardMessage(std::FILE *In) { + // A Language Server Protocol message starts with a set of HTTP headers, + // delimited by \r\n, and terminated by an empty line (\r\n). + unsigned long long ContentLength = 0; + std::string Line; + while (true) { + if (feof(In) || ferror(In) || !readLine(In, Line)) + return llvm::None; + + llvm::StringRef LineRef(Line); + + // We allow comments in headers. Technically this isn't part + // of the LSP specification, but makes writing tests easier. + if (LineRef.startswith("#")) + continue; + + // Content-Length is a mandatory header, and the only one we handle. + if (LineRef.consume_front("Content-Length: ")) { + if (ContentLength != 0) { + elog("Warning: Duplicate Content-Length header received. " + "The previous value for this message ({0}) was ignored.", + ContentLength); + } + llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); + continue; + } else if (!LineRef.trim().empty()) { + // It's another header, ignore it. + continue; + } else { + // An empty line indicates the end of headers. + // Go ahead and read the JSON. + break; + } + } + + // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999" + if (ContentLength > 1 << 30) { // 1024M + elog("Refusing to read message with long Content-Length: {0}. " + "Expect protocol errors", + ContentLength); + return llvm::None; + } + if (ContentLength == 0) { + log("Warning: Missing Content-Length header, or zero-length message."); + return llvm::None; + } + + std::string JSON(ContentLength, '\0'); + for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) { + // Handle EINTR which is sent when a debugger attaches on some platforms. + Read = llvm::sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1, + ContentLength - Pos, In); + if (Read == 0) { + elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos, + ContentLength); + return llvm::None; + } + clearerr(In); // If we're done, the error was transient. If we're not done, + // either it was transient or we'll see it again on retry. + Pos += Read; + } + return std::move(JSON); +} + +// For lit tests we support a simplified syntax: +// - messages are delimited by '---' on a line by itself +// - lines starting with # are ignored. +// This is a testing path, so favor simplicity over performance here. +// When returning None, feof() or ferror() will be set. +llvm::Optional readDelimitedMessage(std::FILE *In) { + std::string JSON; + std::string Line; + while (readLine(In, Line)) { + auto LineRef = llvm::StringRef(Line).trim(); + if (LineRef.startswith("#")) // comment + continue; + + // found a delimiter + if (LineRef.rtrim() == "---") + break; + + JSON += Line; + } + + if (ferror(In)) { + elog("Input error while reading message!"); + return llvm::None; + } + return std::move(JSON); // Including at EOF +} + +class JSONTransport : public Transport { +public: + JSONTransport(std::FILE *In, llvm::raw_ostream &Out, JSONStreamStyle Style) + : In(In), Out(Out), Style(Style) {} + + FILE *In; + llvm::raw_ostream &Out; + JSONStreamStyle Style; + + bool loop(MessageHandler &Handler) override { + auto &ReadMessage = + (Style == Delimited) ? readDelimitedMessage : readStandardMessage; + while (!feof(In)) { + if (ferror(In)) + // TODO XXX return errorCodeToError(std::error_code(errno, std::system_category())); + return false; + if (auto JSON = ReadMessage(In)) { + if (auto Doc = json::parse(*JSON)) { + if (!handleMessage(std::move(*Doc), Handler)) + return false; + if (shouldTerminateLoop) + // TODO XXX + break; + } else { + // Parse error. Log the raw message. + vlog("<<< {0}\n", *JSON); + elog("JSON parse error: {0}", llvm::toString(Doc.takeError())); + // TODO XXX + return false; + } + } else { + // TODO XXX + return false; + } + } + return true; + } + +private: + void sendMessage(llvm::json::Value Message) override { + Out << llvm::formatv("{0:2}", Message); + Out.flush(); + } +}; + +} // namespace + +bool Transport::handleMessage(llvm::json::Value LSPMessage, MessageHandler& Handler) { +/* +TODO + + // Message must be an object with "jsonrpc":"2.0". + auto *Object = Message.getAsObject(); + if (!Object || Object->getString("jsonrpc") != Optional("2.0")) + return false; + + // ID may be any JSON value. If absent, this is a notification. + llvm::Optional ID; + if (auto *I = Object->get("id")) + ID = std::move(*I); + auto Method = Object->getString("method"); + logIncomingMessage(ID, Method, Object->getObject("error")); + if (!Method) // We only handle incoming requests, and ignore responses. + return false; + // Params should be given, use null if not. + json::Value Params = nullptr; + if (auto *P = Object->get("params")) + Params = std::move(*P); + +*/ + llvm::json::Object* LSPMessageObj = LSPMessage.getAsObject(); + if (!LSPMessageObj) { + // TODO error - Value is not an object + return false; + } + + llvm::Optional Method = [LSPMessageObj]() { + const llvm::json::Value *MethodValue = LSPMessageObj->get("method"); + if (MethodValue) { + return MethodValue->getAsString(); + } + return llvm::Optional{}; + }(); + + llvm::Optional ID = [LSPMessageObj]() { + const llvm::json::Value *MethodValue = LSPMessageObj->get("id"); + if (MethodValue) { + return MethodValue->getAsInteger(); + } + return llvm::Optional{}; + }(); + + if (Method && ID) { + const llvm::json::Value *Params = LSPMessageObj->get("params"); + if (!Params) { + // TODO error - params are missing + return false; + } + Handler.callServer(Method.getValue(), *Params, ID.getValue()); + return true; + } + if (!Method && ID) { + const llvm::json::Value *Result = LSPMessageObj->get("result"); + if (!Result) { + // TODO error - result is missing + return false; + } + Handler.replyToServer(ID.getValue(), *Result); + return true; + } + if (Method && !ID) { + const llvm::json::Value *Params = LSPMessageObj->get("params"); + if (!Params) { + // TODO error - params are missing + return false; + } + Handler.notifyServer(Method.getValue(), *Params); + return true; + } + + // TODO error - invalid JSON LSP message + return false; +} + +std::unique_ptr +newJSONTransport(std::FILE *In, llvm::raw_ostream &Out, JSONStreamStyle Style) { + return llvm::make_unique(In, Out, Style); +} + +} // namespace clangd +} // namespace clang Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -315,13 +315,20 @@ CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets; CCOpts.AllScopes = AllScopesCompletion; + // TODO + std::unique_ptr TransportLayer = newJSONTransport(stdin, llvm::outs(), InputStyle); + if (!TransportLayer.get()) { + // TODO + return 42; + } + // Initialize and run ClangdLSPServer. ClangdLSPServer LSPServer( - Out, CCOpts, CompileCommandsDirPath, + *TransportLayer.get(), CCOpts, CompileCommandsDirPath, /*ShouldUseInMemoryCDB=*/CompileArgsFrom == LSPCompileArgs, Opts); constexpr int NoShutdownRequestErrorCode = 1; llvm::set_thread_name("clangd.main"); // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); - return LSPServer.run(stdin, InputStyle) ? 0 : NoShutdownRequestErrorCode; + return LSPServer.run() ? 0 : NoShutdownRequestErrorCode; }