ilya-biryukov updated this revision to Diff 99129.
ilya-biryukov marked 3 inline comments as done.
ilya-biryukov added a comment.

Addressed review comments


https://reviews.llvm.org/D33201

Files:
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/ClangdMain.cpp
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/JSONRPCDispatcher.cpp
  clangd/JSONRPCDispatcher.h
  clangd/ProtocolHandlers.cpp
  clangd/ProtocolHandlers.h

Index: clangd/ProtocolHandlers.h
===================================================================
--- clangd/ProtocolHandlers.h
+++ clangd/ProtocolHandlers.h
@@ -22,118 +22,34 @@
 
 namespace clang {
 namespace clangd {
-class ClangdLSPServer;
-class ClangdLSPServer;
 
-struct InitializeHandler : Handler {
-  InitializeHandler(JSONOutput &Output) : Handler(Output) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
-    writeMessage(
-        R"({"jsonrpc":"2.0","id":)" + ID +
-        R"(,"result":{"capabilities":{
-          "textDocumentSync": 1,
-          "documentFormattingProvider": true,
-          "documentRangeFormattingProvider": true,
-          "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-          "codeActionProvider": true,
-          "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
-        }}})");
-  }
-};
-
-struct ShutdownHandler : Handler {
-  ShutdownHandler(JSONOutput &Output) : Handler(Output) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
-    IsDone = true;
-  }
-
-  bool isDone() const { return IsDone; }
-
-private:
-  bool IsDone = false;
-};
-
-struct TextDocumentDidOpenHandler : Handler {
-  TextDocumentDidOpenHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
-  ClangdLSPServer *
+class ProtocolCallbacks {
+public:
+  virtual ~ProtocolCallbacks() = default;
+
+  virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0;
+  virtual void onShutdown(JSONOutput &Out) = 0;
+  virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params,
+                                 JSONOutput &Out) = 0;
+  virtual void onDocumentDidChange(DidChangeTextDocumentParams Params,
+                                   JSONOutput &Out) = 0;
+
+  virtual void onDocumentDidClose(DidCloseTextDocumentParams Params,
+                                  JSONOutput &Out) = 0;
+  virtual void onDocumentFormatting(DocumentFormattingParams Params,
+                                    StringRef ID, JSONOutput &Out) = 0;
+  virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
+                                          StringRef ID, JSONOutput &Out) = 0;
+  virtual void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
+                                         StringRef ID, JSONOutput &Out) = 0;
+  virtual void onCodeAction(CodeActionParams Params, StringRef ID,
+                            JSONOutput &Out) = 0;
+  virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID,
+                            JSONOutput &Out) = 0;
 };
 
-struct TextDocumentDidChangeHandler : Handler {
-  TextDocumentDidChangeHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct TextDocumentDidCloseHandler : Handler {
-  TextDocumentDidCloseHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct TextDocumentOnTypeFormattingHandler : Handler {
-  TextDocumentOnTypeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct TextDocumentRangeFormattingHandler : Handler {
-  TextDocumentRangeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct TextDocumentFormattingHandler : Handler {
-  TextDocumentFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct CodeActionHandler : Handler {
-  CodeActionHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
-  ClangdLSPServer *
-};
-
-struct CompletionHandler : Handler {
-  CompletionHandler(JSONOutput &Output, ClangdLSPServer &AST)
-      : Handler(Output), AST(AST) {}
-
-  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
- private:
-  ClangdLSPServer *
-};
+void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
+                             ProtocolCallbacks &Callbacks);
 
 } // namespace clangd
 } // namespace clang
Index: clangd/ProtocolHandlers.cpp
===================================================================
--- clangd/ProtocolHandlers.cpp
+++ clangd/ProtocolHandlers.cpp
@@ -8,204 +8,215 @@
 //===----------------------------------------------------------------------===//
 
 #include "ProtocolHandlers.h"
+#include "ClangdLSPServer.h"
 #include "ClangdServer.h"
 #include "DraftStore.h"
-#include "clang/Format/Format.h"
-#include "ClangdLSPServer.h"
 using namespace clang;
 using namespace clangd;
 
-void TextDocumentDidOpenHandler::handleNotification(
-    llvm::yaml::MappingNode *Params) {
-  auto DOTDP = DidOpenTextDocumentParams::parse(Params);
-  if (!DOTDP) {
-    Output.log("Failed to decode DidOpenTextDocumentParams!\n");
-    return;
-  }
-  AST.openDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
-}
-
-void TextDocumentDidCloseHandler::handleNotification(
-    llvm::yaml::MappingNode *Params) {
-  auto DCTDP = DidCloseTextDocumentParams::parse(Params);
-  if (!DCTDP) {
-    Output.log("Failed to decode DidCloseTextDocumentParams!\n");
-    return;
-  }
-
-  AST.closeDocument(DCTDP->textDocument.uri.file);
-}
-
-void TextDocumentDidChangeHandler::handleNotification(
-    llvm::yaml::MappingNode *Params) {
-  auto DCTDP = DidChangeTextDocumentParams::parse(Params);
-  if (!DCTDP || DCTDP->contentChanges.size() != 1) {
-    Output.log("Failed to decode DidChangeTextDocumentParams!\n");
-    return;
-  }
-  // We only support full syncing right now.
-  AST.openDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
-}
-
-/// Turn a [line, column] pair into an offset in Code.
-static size_t positionToOffset(StringRef Code, Position P) {
-  size_t Offset = 0;
-  for (int I = 0; I != P.line; ++I) {
-    // FIXME: \r\n
-    // FIXME: UTF-8
-    size_t F = Code.find('\n', Offset);
-    if (F == StringRef::npos)
-      return 0; // FIXME: Is this reasonable?
-    Offset = F + 1;
-  }
-  return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
-}
-
-/// Turn an offset in Code into a [line, column] pair.
-static Position offsetToPosition(StringRef Code, size_t Offset) {
-  StringRef JustBefore = Code.substr(0, Offset);
-  // FIXME: \r\n
-  // FIXME: UTF-8
-  int Lines = JustBefore.count('\n');
-  int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
-  return {Lines, Cols};
-}
-
-template <typename T>
-static std::string replacementsToEdits(StringRef Code, const T &Replacements) {
-  // Turn the replacements into the format specified by the Language Server
-  // Protocol. Fuse them into one big JSON array.
-  std::string Edits;
-  for (auto &R : Replacements) {
-    Range ReplacementRange = {
-        offsetToPosition(Code, R.getOffset()),
-        offsetToPosition(Code, R.getOffset() + R.getLength())};
-    TextEdit TE = {ReplacementRange, R.getReplacementText()};
-    Edits += TextEdit::unparse(TE);
-    Edits += ',';
-  }
-  if (!Edits.empty())
-    Edits.pop_back();
-
-  return Edits;
-}
-
-static std::string formatCode(StringRef Code, StringRef Filename,
-                              ArrayRef<tooling::Range> Ranges, StringRef ID) {
-  // Call clang-format.
-  // FIXME: Don't ignore style.
-  format::FormatStyle Style = format::getLLVMStyle();
-  tooling::Replacements Replacements =
-      format::reformat(Style, Code, Ranges, Filename);
-
-  std::string Edits = replacementsToEdits(Code, Replacements);
-  return R"({"jsonrpc":"2.0","id":)" + ID.str() +
-         R"(,"result":[)" + Edits + R"(]})";
-}
-
-void TextDocumentRangeFormattingHandler::handleMethod(
-    llvm::yaml::MappingNode *Params, StringRef ID) {
-  auto DRFP = DocumentRangeFormattingParams::parse(Params);
-  if (!DRFP) {
-    Output.log("Failed to decode DocumentRangeFormattingParams!\n");
-    return;
-  }
-
-  std::string Code = AST.getDocument(DRFP->textDocument.uri.file);
-
-  size_t Begin = positionToOffset(Code, DRFP->range.start);
-  size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
-
-  writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
-                          {clang::tooling::Range(Begin, Len)}, ID));
-}
-
-void TextDocumentOnTypeFormattingHandler::handleMethod(
-    llvm::yaml::MappingNode *Params, StringRef ID) {
-  auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
-  if (!DOTFP) {
-    Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
-    return;
-  }
-
-  // Look for the previous opening brace from the character position and format
-  // starting from there.
-  std::string Code = AST.getDocument(DOTFP->textDocument.uri.file);
-  size_t CursorPos = positionToOffset(Code, DOTFP->position);
-  size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
-  if (PreviousLBracePos == StringRef::npos)
-    PreviousLBracePos = CursorPos;
-  size_t Len = 1 + CursorPos - PreviousLBracePos;
-
-  writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
-                          {clang::tooling::Range(PreviousLBracePos, Len)}, ID));
-}
-
-void TextDocumentFormattingHandler::handleMethod(
-    llvm::yaml::MappingNode *Params, StringRef ID) {
-  auto DFP = DocumentFormattingParams::parse(Params);
-  if (!DFP) {
-    Output.log("Failed to decode DocumentFormattingParams!\n");
-    return;
-  }
-
-  // Format everything.
-  std::string Code = AST.getDocument(DFP->textDocument.uri.file);
-  writeMessage(formatCode(Code, DFP->textDocument.uri.file,
-                          {clang::tooling::Range(0, Code.size())}, ID));
-}
-
-void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
-                                     StringRef ID) {
-  auto CAP = CodeActionParams::parse(Params);
-  if (!CAP) {
-    Output.log("Failed to decode CodeActionParams!\n");
-    return;
-  }
-
-  // We provide a code action for each diagnostic at the requested location
-  // which has FixIts available.
-  std::string Code = AST.getDocument(CAP->textDocument.uri.file);
-  std::string Commands;
-  for (Diagnostic &D : CAP->context.diagnostics) {
-    std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(CAP->textDocument.uri.file, D);
-    std::string Edits = replacementsToEdits(Code, Fixes);
-
-    if (!Edits.empty())
-      Commands +=
-          R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
-          R"('", "command": "clangd.applyFix", "arguments": [")" +
-          llvm::yaml::escape(CAP->textDocument.uri.uri) +
-          R"(", [)" + Edits +
-          R"(]]},)";
-  }
-  if (!Commands.empty())
-    Commands.pop_back();
-
-  writeMessage(
-      R"({"jsonrpc":"2.0","id":)" + ID.str() +
-      R"(, "result": [)" + Commands +
-      R"(]})");
-}
+namespace {
 
-void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
-                                     StringRef ID) {
-  auto TDPP = TextDocumentPositionParams::parse(Params);
-  if (!TDPP) {
-    Output.log("Failed to decode TextDocumentPositionParams!\n");
-    return;
-  }
-
-  auto Items = AST.codeComplete(TDPP->textDocument.uri.file, Position{TDPP->position.line,
-          TDPP->position.character});
-  std::string Completions;
-  for (const auto &Item : Items) {
-    Completions += CompletionItem::unparse(Item);
-    Completions += ",";
-  }
-  if (!Completions.empty())
-    Completions.pop_back();
-  writeMessage(
-      R"({"jsonrpc":"2.0","id":)" + ID.str() +
-      R"(,"result":[)" + Completions + R"(]})");
+struct InitializeHandler : Handler {
+  InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    Callbacks.onInitialize(ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct ShutdownHandler : Handler {
+  ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    Callbacks.onShutdown(Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentDidOpenHandler : Handler {
+  TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleNotification(llvm::yaml::MappingNode *Params) override {
+    auto DOTDP = DidOpenTextDocumentParams::parse(Params);
+    if (!DOTDP) {
+      Output.log("Failed to decode DidOpenTextDocumentParams!\n");
+      return;
+    }
+    Callbacks.onDocumentDidOpen(*DOTDP, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentDidChangeHandler : Handler {
+  TextDocumentDidChangeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleNotification(llvm::yaml::MappingNode *Params) override {
+    auto DCTDP = DidChangeTextDocumentParams::parse(Params);
+    if (!DCTDP || DCTDP->contentChanges.size() != 1) {
+      Output.log("Failed to decode DidChangeTextDocumentParams!\n");
+      return;
+    }
+
+    Callbacks.onDocumentDidChange(*DCTDP, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentDidCloseHandler : Handler {
+  TextDocumentDidCloseHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleNotification(llvm::yaml::MappingNode *Params) override {
+    auto DCTDP = DidCloseTextDocumentParams::parse(Params);
+    if (!DCTDP) {
+      Output.log("Failed to decode DidCloseTextDocumentParams!\n");
+      return;
+    }
+
+    Callbacks.onDocumentDidClose(*DCTDP, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentOnTypeFormattingHandler : Handler {
+  TextDocumentOnTypeFormattingHandler(JSONOutput &Output,
+                                      ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
+    if (!DOTFP) {
+      Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
+      return;
+    }
+
+    Callbacks.onDocumentOnTypeFormatting(*DOTFP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentRangeFormattingHandler : Handler {
+  TextDocumentRangeFormattingHandler(JSONOutput &Output,
+                                     ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto DRFP = DocumentRangeFormattingParams::parse(Params);
+    if (!DRFP) {
+      Output.log("Failed to decode DocumentRangeFormattingParams!\n");
+      return;
+    }
+
+    Callbacks.onDocumentRangeFormatting(*DRFP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentFormattingHandler : Handler {
+  TextDocumentFormattingHandler(JSONOutput &Output,
+                                ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto DFP = DocumentFormattingParams::parse(Params);
+    if (!DFP) {
+      Output.log("Failed to decode DocumentFormattingParams!\n");
+      return;
+    }
+
+    Callbacks.onDocumentFormatting(*DFP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct CodeActionHandler : Handler {
+  CodeActionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto CAP = CodeActionParams::parse(Params);
+    if (!CAP) {
+      Output.log("Failed to decode CodeActionParams!\n");
+      return;
+    }
+
+    Callbacks.onCodeAction(*CAP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+struct CompletionHandler : Handler {
+  CompletionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto TDPP = TextDocumentPositionParams::parse(Params);
+    if (!TDPP) {
+      Output.log("Failed to decode TextDocumentPositionParams!\n");
+      return;
+    }
+
+    Callbacks.onCompletion(*TDPP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
+} // namespace
+
+void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
+                                     JSONOutput &Out,
+                                     ProtocolCallbacks &Callbacks) {
+  Dispatcher.registerHandler(
+      "initialize", llvm::make_unique<InitializeHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "shutdown", llvm::make_unique<ShutdownHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/didOpen",
+      llvm::make_unique<TextDocumentDidOpenHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/didClose",
+      llvm::make_unique<TextDocumentDidCloseHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/didChange",
+      llvm::make_unique<TextDocumentDidChangeHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/rangeFormatting",
+      llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/onTypeFormatting",
+      llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/formatting",
+      llvm::make_unique<TextDocumentFormattingHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/codeAction",
+      llvm::make_unique<CodeActionHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/completion",
+      llvm::make_unique<CompletionHandler>(Out, Callbacks));
 }
Index: clangd/JSONRPCDispatcher.h
===================================================================
--- clangd/JSONRPCDispatcher.h
+++ clangd/JSONRPCDispatcher.h
@@ -79,6 +79,15 @@
   std::unique_ptr<Handler> UnknownHandler;
 };
 
+/// Parses input queries from LSP client (coming from \p In) and runs call
+/// method of \p Dispatcher for each query.
+/// After handling each query checks if \p IsDone is set true and exits the loop
+/// if it is.
+/// Input stream(\p In) must be opened in binary mode to avoid preliminary
+/// replacements of \r\n with \n.
+void runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+                           JSONRPCDispatcher &Dispatcher, bool &IsDone);
+
 } // namespace clangd
 } // namespace clang
 
Index: clangd/JSONRPCDispatcher.cpp
===================================================================
--- clangd/JSONRPCDispatcher.cpp
+++ clangd/JSONRPCDispatcher.cpp
@@ -129,3 +129,61 @@
 
   return true;
 }
+
+void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+                                   JSONRPCDispatcher &Dispatcher,
+                                   bool &IsDone) {
+  while (In.good()) {
+    // A Language Server Protocol message starts with a HTTP header, delimited
+    // by \r\n.
+    std::string Line;
+    std::getline(In, Line);
+    if (!In.good() && errno == EINTR) {
+      In.clear();
+      continue;
+    }
+
+    // Skip empty lines.
+    llvm::StringRef LineRef(Line);
+    if (LineRef.trim().empty())
+      continue;
+
+    // We allow YAML-style comments. Technically this isn't part of the
+    // LSP specification, but makes writing tests easier.
+    if (LineRef.startswith("#"))
+      continue;
+
+    unsigned long long Len = 0;
+    // FIXME: Content-Type is a specified header, but does nothing.
+    // Content-Length is a mandatory header. It specifies the length of the
+    // following JSON.
+    if (LineRef.consume_front("Content-Length: "))
+      llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
+
+    // Check if the next line only contains \r\n. If not this is another header,
+    // which we ignore.
+    char NewlineBuf[2];
+    In.read(NewlineBuf, 2);
+    if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
+      continue;
+
+    // Now read the JSON. Insert a trailing null byte as required by the YAML
+    // parser.
+    std::vector<char> JSON(Len + 1, '\0');
+    In.read(JSON.data(), Len);
+
+    if (Len > 0) {
+      llvm::StringRef JSONRef(JSON.data(), Len);
+      // Log the message.
+      Out.log("<-- " + JSONRef + "\n");
+
+      // Finally, execute the action for this JSON message.
+      if (!Dispatcher.call(JSONRef))
+        Out.log("JSON dispatch failed!\n");
+
+      // If we're done, exit the loop.
+      if (IsDone)
+        break;
+    }
+  }
+}
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -34,6 +34,12 @@
 
 namespace clangd {
 
+/// Turn a [line, column] pair into an offset in Code.
+size_t positionToOffset(StringRef Code, Position P);
+
+/// Turn an offset in Code into a [line, column] pair.
+Position offsetToPosition(StringRef Code, size_t Offset);
+
 class DiagnosticsConsumer {
 public:
   virtual ~DiagnosticsConsumer() = default;
@@ -100,19 +106,24 @@
   /// separate thread. When the parsing is complete, DiagConsumer passed in
   /// constructor will receive onDiagnosticsReady callback.
   void addDocument(PathRef File, StringRef Contents);
-
   /// Remove \p File from list of tracked files, schedule a request to free
   /// resources associated with it.
   void removeDocument(PathRef File);
 
   /// Run code completion for \p File at \p Pos.
   std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
 
+  /// Run formatting for \p Rng inside \p File.
+  std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
+  /// Run formatting for the whole \p File.
+  std::vector<tooling::Replacement> formatFile(PathRef File);
+  /// Run formatting after a character was typed at \p Pos in \p File.
+  std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos);
+
   /// Gets current document contents for \p File. \p File must point to a
   /// currently tracked file.
-  /// FIXME(ibiryukov): This function is here to allow implementation of
-  /// formatCode from ProtocolHandlers.cpp. We should move formatCode to this
-  /// class and remove this function from public interface.
+  /// FIXME(ibiryukov): This function is here to allow offset-to-Position
+  /// conversions in outside code, maybe there's a way to get rid of it.
   std::string getDocument(PathRef File);
 
 private:
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -8,15 +8,54 @@
 //===-------------------------------------------------------------------===//
 
 #include "ClangdServer.h"
+#include "clang/Format/Format.h"
 #include "clang/Frontend/ASTUnit.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Tooling/CompilationDatabase.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/Support/FileSystem.h"
 
 using namespace clang;
 using namespace clang::clangd;
 
+namespace {
+
+std::vector<tooling::Replacement> formatCode(StringRef Code, StringRef Filename,
+                                             ArrayRef<tooling::Range> Ranges) {
+  // Call clang-format.
+  // FIXME: Don't ignore style.
+  format::FormatStyle Style = format::getLLVMStyle();
+  auto Result = format::reformat(Style, Code, Ranges, Filename);
+
+  return std::vector<tooling::Replacement>(Result.begin(), Result.end());
+}
+
+} // namespace
+
+size_t clangd::positionToOffset(StringRef Code, Position P) {
+  size_t Offset = 0;
+  for (int I = 0; I != P.line; ++I) {
+    // FIXME: \r\n
+    // FIXME: UTF-8
+    size_t F = Code.find('\n', Offset);
+    if (F == StringRef::npos)
+      return 0; // FIXME: Is this reasonable?
+    Offset = F + 1;
+  }
+  return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
+}
+
+/// Turn an offset in Code into a [line, column] pair.
+Position clangd::offsetToPosition(StringRef Code, size_t Offset) {
+  StringRef JustBefore = Code.substr(0, Offset);
+  // FIXME: \r\n
+  // FIXME: UTF-8
+  int Lines = JustBefore.count('\n');
+  int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
+  return {Lines, Cols};
+}
+
 WorkerRequest::WorkerRequest(WorkerRequestKind Kind, Path File,
                              DocVersion Version)
     : Kind(Kind), File(File), Version(Version) {}
@@ -117,6 +156,34 @@
       });
   return Result;
 }
+std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File,
+                                                            Range Rng) {
+  std::string Code = getDocument(File);
+
+  size_t Begin = positionToOffset(Code, Rng.start);
+  size_t Len = positionToOffset(Code, Rng.end) - Begin;
+  return formatCode(Code, File, {tooling::Range(Begin, Len)});
+}
+
+std::vector<tooling::Replacement> ClangdServer::formatFile(PathRef File) {
+  // Format everything.
+  std::string Code = getDocument(File);
+  return formatCode(Code, File, {tooling::Range(0, Code.size())});
+}
+
+std::vector<tooling::Replacement> ClangdServer::formatOnType(PathRef File,
+                                                             Position Pos) {
+  // Look for the previous opening brace from the character position and
+  // format starting from there.
+  std::string Code = getDocument(File);
+  size_t CursorPos = positionToOffset(Code, Pos);
+  size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
+  if (PreviousLBracePos == StringRef::npos)
+    PreviousLBracePos = CursorPos;
+  size_t Len = 1 + CursorPos - PreviousLBracePos;
+
+  return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
+}
 
 std::string ClangdServer::getDocument(PathRef File) {
   auto draft = DraftMgr.getDraft(File);
Index: clangd/ClangdMain.cpp
===================================================================
--- clangd/ClangdMain.cpp
+++ clangd/ClangdMain.cpp
@@ -7,10 +7,8 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "JSONRPCDispatcher.h"
 #include "ClangdLSPServer.h"
-#include "Protocol.h"
-#include "ProtocolHandlers.h"
+#include "JSONRPCDispatcher.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Program.h"
@@ -29,96 +27,14 @@
 
 int main(int argc, char *argv[]) {
   llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
+
   llvm::raw_ostream &Outs = llvm::outs();
   llvm::raw_ostream &Logs = llvm::errs();
   JSONOutput Out(Outs, Logs);
 
   // Change stdin to binary to not lose \r\n on windows.
   llvm::sys::ChangeStdinToBinary();
 
-  // Set up a document store and intialize all the method handlers for JSONRPC
-  // dispatching.
   ClangdLSPServer LSPServer(Out, RunSynchronously);
-  JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
-  Dispatcher.registerHandler("initialize",
-                             llvm::make_unique<InitializeHandler>(Out));
-  auto ShutdownPtr = llvm::make_unique<ShutdownHandler>(Out);
-  auto *ShutdownHandler = ShutdownPtr.get();
-  Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
-  Dispatcher.registerHandler(
-      "textDocument/didOpen",
-      llvm::make_unique<TextDocumentDidOpenHandler>(Out, LSPServer));
-  Dispatcher.registerHandler(
-      "textDocument/didClose",
-      llvm::make_unique<TextDocumentDidCloseHandler>(Out, LSPServer));
-  Dispatcher.registerHandler(
-      "textDocument/didChange",
-      llvm::make_unique<TextDocumentDidChangeHandler>(Out, LSPServer));
-  Dispatcher.registerHandler(
-      "textDocument/rangeFormatting",
-      llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, LSPServer));
-  Dispatcher.registerHandler(
-      "textDocument/onTypeFormatting",
-      llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, LSPServer));
-  Dispatcher.registerHandler(
-      "textDocument/formatting",
-      llvm::make_unique<TextDocumentFormattingHandler>(Out, LSPServer));
-  Dispatcher.registerHandler("textDocument/codeAction",
-                             llvm::make_unique<CodeActionHandler>(Out, LSPServer));
-  Dispatcher.registerHandler("textDocument/completion",
-                             llvm::make_unique<CompletionHandler>(Out, LSPServer));
-
-  while (std::cin.good()) {
-    // A Language Server Protocol message starts with a HTTP header, delimited
-    // by \r\n.
-    std::string Line;
-    std::getline(std::cin, Line);
-    if (!std::cin.good() && errno == EINTR) {
-      std::cin.clear();
-      continue;
-    }
-
-    // Skip empty lines.
-    llvm::StringRef LineRef(Line);
-    if (LineRef.trim().empty())
-      continue;
-
-    // We allow YAML-style comments. Technically this isn't part of the
-    // LSP specification, but makes writing tests easier.
-    if (LineRef.startswith("#"))
-      continue;
-
-    unsigned long long Len = 0;
-    // FIXME: Content-Type is a specified header, but does nothing.
-    // Content-Length is a mandatory header. It specifies the length of the
-    // following JSON.
-    if (LineRef.consume_front("Content-Length: "))
-      llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
-
-    // Check if the next line only contains \r\n. If not this is another header,
-    // which we ignore.
-    char NewlineBuf[2];
-    std::cin.read(NewlineBuf, 2);
-    if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
-      continue;
-
-    // Now read the JSON. Insert a trailing null byte as required by the YAML
-    // parser.
-    std::vector<char> JSON(Len + 1, '\0');
-    std::cin.read(JSON.data(), Len);
-
-    if (Len > 0) {
-      llvm::StringRef JSONRef(JSON.data(), Len);
-      // Log the message.
-      Out.log("<-- " + JSONRef + "\n");
-
-      // Finally, execute the action for this JSON message.
-      if (!Dispatcher.call(JSONRef))
-        Out.log("JSON dispatch failed!\n");
-
-      // If we're done, exit the loop.
-      if (ShutdownHandler->isDone())
-        break;
-    }
-  }
+  LSPServer.run(std::cin);
 }
Index: clangd/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -20,50 +20,36 @@
 
 class JSONOutput;
 
-/// This class serves as an intermediate layer of LSP server implementation,
-/// glueing the JSON LSP protocol layer and ClangdServer together. It doesn't
-/// directly handle input from LSP client.
-/// Most methods are synchronous and return their result directly, but
-/// diagnostics are provided asynchronously when ready via
-/// JSONOutput::writeMessage.
+/// This class provides implementation of an LSP server, glueing the JSON
+/// dispatch and ClangdServer together.
 class ClangdLSPServer {
 public:
   ClangdLSPServer(JSONOutput &Out, bool RunSynchronously);
 
-  /// Update the document text for \p File with \p Contents, schedule update of
-  /// diagnostics. Out.writeMessage will called to push diagnostics to LSP
-  /// client asynchronously when they are ready.
-  void openDocument(PathRef File, StringRef Contents);
-  /// Stop tracking the document for \p File.
-  void closeDocument(PathRef File);
-
-  /// Run code completion synchronously.
-  std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
-
-  /// Get the fixes associated with a certain diagnostic in a specified file as
-  /// replacements.
-  ///
-  /// This function is thread-safe. It returns a copy to avoid handing out
-  /// references to unguarded data.
-  std::vector<clang::tooling::Replacement>
-  getFixIts(StringRef File, const clangd::Diagnostic &D);
-
-  /// Get the current document contents stored for \p File.
-  /// FIXME(ibiryukov): This function is here to allow implementation of
-  /// formatCode from ProtocolHandlers.cpp. We should move formatCode to
-  /// ClangdServer class and remove this function from public interface.
-  std::string getDocument(PathRef File);
+  /// Run LSP server loop, receiving input for it from \p In. \p In must be
+  /// opened in binary mode. Output will be written using Out variable passed to
+  /// class constructor. This method must not be executed more than once for
+  /// each instance of ClangdLSPServer.
+  void run(std::istream &In);
 
 private:
+  class LSPProtocolCallbacks;
   class LSPDiagnosticsConsumer;
 
+  std::vector<clang::tooling::Replacement>
+  getFixIts(StringRef File, const clangd::Diagnostic &D);
+
   /// Function that will be called on a separate thread when diagnostics are
   /// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches
   /// corresponding fixits in the FixItsMap.
   void consumeDiagnostics(PathRef File,
                           std::vector<DiagWithFixIts> Diagnostics);
 
   JSONOutput &Out;
+  /// Used to indicate that the 'shutdown' request was received from the
+  /// Language Server client.
+  /// It's used to break out of the LSP parsing loop.
+  bool IsDone = false;
 
   std::mutex FixItsMutex;
   typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -9,10 +9,35 @@
 
 #include "ClangdLSPServer.h"
 #include "JSONRPCDispatcher.h"
+#include "ProtocolHandlers.h"
 
 using namespace clang::clangd;
 using namespace clang;
 
+namespace {
+
+std::string
+replacementsToEdits(StringRef Code,
+                    const std::vector<tooling::Replacement> &Replacements) {
+  // Turn the replacements into the format specified by the Language Server
+  // Protocol. Fuse them into one big JSON array.
+  std::string Edits;
+  for (auto &R : Replacements) {
+    Range ReplacementRange = {
+        offsetToPosition(Code, R.getOffset()),
+        offsetToPosition(Code, R.getOffset() + R.getLength())};
+    TextEdit TE = {ReplacementRange, R.getReplacementText()};
+    Edits += TextEdit::unparse(TE);
+    Edits += ',';
+  }
+  if (!Edits.empty())
+    Edits.pop_back();
+
+  return Edits;
+}
+
+} // namespace
+
 class ClangdLSPServer::LSPDiagnosticsConsumer : public DiagnosticsConsumer {
 public:
   LSPDiagnosticsConsumer(ClangdLSPServer &Server) : Server(Server) {}
@@ -26,23 +51,170 @@
   ClangdLSPServer &Server;
 };
 
+class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks {
+public:
+  LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {}
+
+  void onInitialize(StringRef ID, JSONOutput &Out) override;
+  void onShutdown(JSONOutput &Out) override;
+  void onDocumentDidOpen(DidOpenTextDocumentParams Params,
+                         JSONOutput &Out) override;
+  void onDocumentDidChange(DidChangeTextDocumentParams Params,
+                           JSONOutput &Out) override;
+  void onDocumentDidClose(DidCloseTextDocumentParams Params,
+                          JSONOutput &Out) override;
+  void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
+                                  StringRef ID, JSONOutput &Out) override;
+  void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
+                                 StringRef ID, JSONOutput &Out) override;
+  void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
+                            JSONOutput &Out) override;
+  void onCodeAction(CodeActionParams Params, StringRef ID,
+                    JSONOutput &Out) override;
+  void onCompletion(TextDocumentPositionParams Params, StringRef ID,
+                    JSONOutput &Out) override;
+
+private:
+  ClangdLSPServer &LangServer;
+};
+
+void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
+                                                         JSONOutput &Out) {
+  Out.writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID +
+      R"(,"result":{"capabilities":{
+          "textDocumentSync": 1,
+          "documentFormattingProvider": true,
+          "documentRangeFormattingProvider": true,
+          "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
+          "codeActionProvider": true,
+          "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
+        }}})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
+  LangServer.IsDone = true;
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
+    DidOpenTextDocumentParams Params, JSONOutput &Out) {
+  LangServer.Server.addDocument(Params.textDocument.uri.file,
+                                Params.textDocument.text);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
+    DidChangeTextDocumentParams Params, JSONOutput &Out) {
+  // We only support full syncing right now.
+  LangServer.Server.addDocument(Params.textDocument.uri.file,
+                                Params.contentChanges[0].text);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
+    DidCloseTextDocumentParams Params, JSONOutput &Out) {
+  LangServer.Server.removeDocument(Params.textDocument.uri.file);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
+    DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
+  auto File = Params.textDocument.uri.file;
+  std::string Code = LangServer.Server.getDocument(File);
+  std::string Edits = replacementsToEdits(
+      Code, LangServer.Server.formatOnType(File, Params.position));
+
+  Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+                   R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
+    DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
+  auto File = Params.textDocument.uri.file;
+  std::string Code = LangServer.Server.getDocument(File);
+  std::string Edits = replacementsToEdits(
+      Code, LangServer.Server.formatRange(File, Params.range));
+
+  Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+                   R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
+    DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
+  auto File = Params.textDocument.uri.file;
+  std::string Code = LangServer.Server.getDocument(File);
+  std::string Edits =
+      replacementsToEdits(Code, LangServer.Server.formatFile(File));
+
+  Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+                   R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
+    CodeActionParams Params, StringRef ID, JSONOutput &Out) {
+  // We provide a code action for each diagnostic at the requested location
+  // which has FixIts available.
+  std::string Code =
+      LangServer.Server.getDocument(Params.textDocument.uri.file);
+  std::string Commands;
+  for (Diagnostic &D : Params.context.diagnostics) {
+    std::vector<clang::tooling::Replacement> Fixes =
+        LangServer.getFixIts(Params.textDocument.uri.file, D);
+    std::string Edits = replacementsToEdits(Code, Fixes);
+
+    if (!Edits.empty())
+      Commands +=
+          R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
+          R"('", "command": "clangd.applyFix", "arguments": [")" +
+          llvm::yaml::escape(Params.textDocument.uri.uri) +
+          R"(", [)" + Edits +
+          R"(]]},)";
+  }
+  if (!Commands.empty())
+    Commands.pop_back();
+
+  Out.writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID.str() +
+      R"(, "result": [)" + Commands +
+      R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
+    TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
+
+  auto Items = LangServer.Server.codeComplete(
+      Params.textDocument.uri.file,
+      Position{Params.position.line, Params.position.character});
+
+  std::string Completions;
+  for (const auto &Item : Items) {
+    Completions += CompletionItem::unparse(Item);
+    Completions += ",";
+  }
+  if (!Completions.empty())
+    Completions.pop_back();
+  Out.writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID.str() +
+      R"(,"result":[)" + Completions + R"(]})");
+}
+
 ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
     : Out(Out),
       Server(llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(),
              llvm::make_unique<LSPDiagnosticsConsumer>(*this),
              RunSynchronously) {}
 
-void ClangdLSPServer::openDocument(StringRef File, StringRef Contents) {
-  Server.addDocument(File, Contents);
-}
-
-void ClangdLSPServer::closeDocument(StringRef File) {
-  Server.removeDocument(File);
-}
+void ClangdLSPServer::run(std::istream &In) {
+  assert(!IsDone && "Run was called before");
 
-std::vector<CompletionItem> ClangdLSPServer::codeComplete(PathRef File,
-                                                          Position Pos) {
-  return Server.codeComplete(File, Pos);
+  // Set up JSONRPCDispatcher.
+  LSPProtocolCallbacks Callbacks(*this);
+  JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
+  regiterCallbackHandlers(Dispatcher, Out, Callbacks);
+
+  // Run the Language Server loop.
+  runLanguageServerLoop(In, Out, Dispatcher, IsDone);
+
+  // Make sure IsDone is set to true after this method exits to ensure assertion
+  // at the start of the method fires if it's ever executed again.
+  IsDone = true;
 }
 
 std::vector<clang::tooling::Replacement>
@@ -60,10 +232,6 @@
   return FixItsIter->second;
 }
 
-std::string ClangdLSPServer::getDocument(PathRef File) {
-  return Server.getDocument(File);
-}
-
 void ClangdLSPServer::consumeDiagnostics(
     PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
   std::string DiagnosticsJSON;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to