jkorous created this revision.
jkorous added a reviewer: sammccall.
Herald added subscribers: cfe-commits, kadircet, jfb, arphaman, dexonsmith, 
MaskRay, ioeric, ilya-biryukov, mgorny.

Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D53290

Files:
  clangd/CMakeLists.txt
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/JSONRPCDispatcher.cpp
  clangd/JSONRPCDispatcher.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/ProtocolHandlers.cpp
  clangd/ProtocolHandlers.h
  clangd/Transport.cpp
  clangd/Transport.h
  clangd/tool/ClangdMain.cpp

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<Transport> 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;
 }
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<llvm::json::Value> 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<llvm::json::Value> 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<bool> 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<Transport>
+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<llvm::json::Value> 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<LSPError>(Err); // Recreate error for its message.
+        }));
+    Message["error"] = json::Object{
+        {"message", Msg},
+        {"code", static_cast<int>(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<std::string> 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<std::string> 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<StringRef>("2.0"))
+    return false;
+
+  // ID may be any JSON value. If absent, this is a notification.
+  llvm::Optional<json::Value> 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<llvm::StringRef> Method = [LSPMessageObj]() {
+    const llvm::json::Value *MethodValue = LSPMessageObj->get("method");
+    if (MethodValue) {
+      return MethodValue->getAsString();
+    }
+    return llvm::Optional<llvm::StringRef>{};
+  }();
+
+  llvm::Optional<int64_t> ID = [LSPMessageObj]() {
+    const llvm::json::Value *MethodValue = LSPMessageObj->get("id");
+    if (MethodValue) {
+      return MethodValue->getAsInteger();
+    }
+    return llvm::Optional<int64_t>{};
+  }();
+
+  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<Transport>
+newJSONTransport(std::FILE *In, llvm::raw_ostream &Out, JSONStreamStyle Style) {
+  return llvm::make_unique<JSONTransport>(In, Out, Style);
+}
+
+} // namespace clangd
+} // namespace clang
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 <typename Param>
-  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<Param>::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/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 <bitset>
 #include <string>
@@ -48,6 +49,15 @@
   // Defined by the protocol.
   RequestCancelled = -32800,
 };
+struct LSPError : public llvm::ErrorInfo<LSPError> {
+  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/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<Handler> 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<int>(Code), Message);
-  RequestSpan::attach([&](json::Object &Args) {
-    Args["Error"] = json::Object{{"code", static_cast<int>(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<int>(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/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -16,70 +16,74 @@
 #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 <memory>
 
 namespace clang {
 namespace clangd {
 
-class JSONOutput;
 class SymbolIndex;
 
 /// This class exposes ClangdServer's capabilities via Language Server Protocol.
 ///
 /// 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<Path> 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<llvm::json::Value> Result) override;
 
 private:
   // Implement DiagnosticsConsumer.
   void onDiagnosticsReady(PathRef File, std::vector<Diag> 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<Fix> 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<ClangdServer> 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<std::pair<Canceler, unsigned>> RequestCancelers;
+  unsigned NextRequestCookie = 0;
+  Context cancelableRequestContext(const llvm::json::Value &ID);
+  void cancelRequest(const llvm::json::Value &ID);
+
+  using Handler = std::function<void(const llvm::json::Value &)>;
+  llvm::StringMap<Handler> 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<std::unique_ptr<RequestSpan>> 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<RequestSpan>(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 <typename Func> static void attach(Func &&F) {
+    auto *RequestArgs = Context::current().get(RSKey);
+    if (!RequestArgs || !*RequestArgs || !(*RequestArgs)->Args)
+      return;
+    std::lock_guard<std::mutex> Lock((*RequestArgs)->Mu);
+    F(*(*RequestArgs)->Args);
+  }
+};
+Key<std::unique_ptr<RequestSpan>> RequestSpan::RSKey;
+
+static Key<Transport *> CurrentTransportLayer;
+static Key<json::Value> 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<int>(Code), Message);
+  RequestSpan::attach([&](json::Object &Args) {
+    Args["Error"] = json::Object{{"code", static_cast<int>(Code)},
+                                 {"message", Message.str()}};
+  });
+/*
+        ->writeMessage(json::Object{
+            {"jsonrpc", "2.0"},
+            {"id", *ID},
+            {"error", json::Object{{"code", static_cast<int>(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<LSPError>(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<llvm::json::Value> 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,28 +383,28 @@
       Params.query, CCOpts.Limit,
       [this](llvm::Expected<std::vector<SymbolInformation>> 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));
       });
 }
 
 void ClangdLSPServer::onRename(RenameParams &Params) {
   Path File = Params.textDocument.uri.file();
   llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
   if (!Code)
-    return replyError(ErrorCode::InvalidParams,
+    return ::replyError(ErrorCode::InvalidParams,
                       "onRename called for non-added file");
 
   Server->rename(
       File, Params.position, Params.newName,
       [File, Code,
        Params](llvm::Expected<std::vector<tooling::Replacement>> 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,58 +430,58 @@
   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()));
 }
 
 void ClangdLSPServer::onDocumentRangeFormatting(
     DocumentRangeFormattingParams &Params) {
   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()));
 }
 
 void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
   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()));
 }
 
 void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) {
   Server->documentSymbols(
       Params.textDocument.uri.file(),
       [this](llvm::Expected<std::vector<SymbolInformation>> 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<Path> 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,46 +616,101 @@
   Server->findReferences(Params.textDocument.uri.file(), Params.position,
                          [](llvm::Expected<std::vector<Location>> 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<void(const llvm::json::Value &)> Handler;
+template <typename Param> 
+  void Register(
+    ClangdLSPServer& LSPServer,
+    llvm::StringMap<Handler>& 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<Param>::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<Path> 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.
   IsDone = true;
   // Destroy ClangdServer to ensure all worker threads finish.
   Server.reset();
 
-  return ShutdownRequestReceived;
+  return ShutdownRequestReceived && Error;
 }
 
 std::vector<Fix> 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<std::mutex> Lock(RequestCancelersMutex);
+    auto It = RequestCancelers.find(StrID);
+    if (It != RequestCancelers.end())
+      It->second.first(); // Invoke the canceler.
+  }
+  */
+}
 void ClangdLSPServer::onDiagnosticsReady(PathRef File,
                                          std::vector<Diag> 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<std::mutex> 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<std::mutex> 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/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
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to