This revision was automatically updated to reflect the committed changes.
Closed by commit rL294291: Add a prototype for clangd (authored by d0k).

Changed prior to commit:
  https://reviews.llvm.org/D29451?vs=87226&id=87383#toc

Repository:
  rL LLVM

https://reviews.llvm.org/D29451

Files:
  clang-tools-extra/trunk/CMakeLists.txt
  clang-tools-extra/trunk/clangd/CMakeLists.txt
  clang-tools-extra/trunk/clangd/ClangDMain.cpp
  clang-tools-extra/trunk/clangd/DocumentStore.h
  clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
  clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
  clang-tools-extra/trunk/clangd/Protocol.cpp
  clang-tools-extra/trunk/clangd/Protocol.h
  clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
  clang-tools-extra/trunk/clangd/ProtocolHandlers.h
  clang-tools-extra/trunk/test/CMakeLists.txt
  clang-tools-extra/trunk/test/clangd/formatting.test

Index: clang-tools-extra/trunk/test/clangd/formatting.test
===================================================================
--- clang-tools-extra/trunk/test/clangd/formatting.test
+++ clang-tools-extra/trunk/test/clangd/formatting.test
@@ -0,0 +1,53 @@
+# RUN: sed -e '/^#/d' %s | clangd | FileCheck %s
+# It is absolutely vital that this file has CRLF line endings.
+#
+Content-Length: 125
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+# CHECK: Content-Length: 191
+# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
+# CHECK:   "textDocumentSync": 1,
+# CHECK:   "documentFormattingProvider": true,
+# CHECK:   "documentRangeFormattingProvider": true
+# CHECK: }}}
+#
+Content-Length: 193
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int foo ( int x ) {\n    x = x+1;\n    return x;\n    }"}}}
+#
+#
+Content-Length: 233
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
+# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 0, "character": 19}, "end": {"line": 1, "character": 4}}, "newText": "\n  "},{"range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 9}}, "newText": " "},{"range": {"start": {"line": 1, "character": 10}, "end": {"line": 1, "character": 10}}, "newText": " "},{"range": {"start": {"line": 1, "character": 12}, "end": {"line": 2, "character": 4}}, "newText": "\n  "}]}
+#
+#
+Content-Length: 197
+
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n  x = x + 1;\n  return x;\n    }"}]}}
+#
+#
+Content-Length: 233
+
+{"jsonrpc":"2.0","id":2,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":2},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
+# CHECK: {"jsonrpc":"2.0","id":2,"result":[]}
+#
+Content-Length: 153
+
+{"jsonrpc":"2.0","id":3,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
+# CHECK: {"jsonrpc":"2.0","id":3,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""},{"range": {"start": {"line": 2, "character": 11}, "end": {"line": 3, "character": 4}}, "newText": "\n"}]}
+#
+#
+Content-Length: 190
+
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n  x = x + 1;\n  return x;\n}"}]}}
+#
+#
+Content-Length: 153
+
+{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
+# CHECK: {"jsonrpc":"2.0","id":4,"result":[]}
+#
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
Index: clang-tools-extra/trunk/test/CMakeLists.txt
===================================================================
--- clang-tools-extra/trunk/test/CMakeLists.txt
+++ clang-tools-extra/trunk/test/CMakeLists.txt
@@ -43,6 +43,7 @@
   # Individual tools we test.
   clang-apply-replacements
   clang-change-namespace
+  clangd
   clang-include-fixer
   clang-move
   clang-query
Index: clang-tools-extra/trunk/CMakeLists.txt
===================================================================
--- clang-tools-extra/trunk/CMakeLists.txt
+++ clang-tools-extra/trunk/CMakeLists.txt
@@ -10,6 +10,7 @@
 add_subdirectory(change-namespace)
 add_subdirectory(clang-query)
 add_subdirectory(clang-move)
+add_subdirectory(clangd)
 add_subdirectory(include-fixer)
 add_subdirectory(pp-trace)
 add_subdirectory(tool-template)
Index: clang-tools-extra/trunk/clangd/ClangDMain.cpp
===================================================================
--- clang-tools-extra/trunk/clangd/ClangDMain.cpp
+++ clang-tools-extra/trunk/clangd/ClangDMain.cpp
@@ -0,0 +1,86 @@
+//===--- ClangDMain.cpp - clangd server loop ------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "DocumentStore.h"
+#include "JSONRPCDispatcher.h"
+#include "ProtocolHandlers.h"
+#include "llvm/Support/FileSystem.h"
+#include <iostream>
+#include <string>
+using namespace clang::clangd;
+
+int main(int argc, char *argv[]) {
+  llvm::raw_ostream &Outs = llvm::outs();
+  llvm::raw_ostream &Logs = llvm::errs();
+
+  // Set up a document store and intialize all the method handlers for JSONRPC
+  // dispatching.
+  DocumentStore Store;
+  JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Outs, Logs));
+  Dispatcher.registerHandler("initialize",
+                             llvm::make_unique<InitializeHandler>(Outs, Logs));
+  Dispatcher.registerHandler("shutdown",
+                             llvm::make_unique<ShutdownHandler>(Outs, Logs));
+  Dispatcher.registerHandler(
+      "textDocument/didOpen",
+      llvm::make_unique<TextDocumentDidOpenHandler>(Outs, Logs, Store));
+  // FIXME: Implement textDocument/didClose.
+  Dispatcher.registerHandler(
+      "textDocument/didChange",
+      llvm::make_unique<TextDocumentDidChangeHandler>(Outs, Logs, Store));
+  Dispatcher.registerHandler(
+      "textDocument/rangeFormatting",
+      llvm::make_unique<TextDocumentRangeFormattingHandler>(Outs, Logs, Store));
+  Dispatcher.registerHandler(
+      "textDocument/formatting",
+      llvm::make_unique<TextDocumentFormattingHandler>(Outs, Logs, Store));
+
+  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);
+
+    // Skip empty lines.
+    llvm::StringRef LineRef(Line);
+    if (LineRef.trim().empty())
+      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);
+    std::cin.read(JSON.data(), Len);
+
+    if (Len > 0) {
+      // Log the message.
+      Logs << "<-- ";
+      Logs.write(JSON.data(), JSON.size());
+      Logs << '\n';
+      Logs.flush();
+
+      // Finally, execute the action for this JSON message.
+      if (!Dispatcher.call(llvm::StringRef(JSON.data(), JSON.size() - 1)))
+        Logs << "JSON dispatch failed!\n";
+    }
+  }
+}
Index: clang-tools-extra/trunk/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/trunk/clangd/CMakeLists.txt
+++ clang-tools-extra/trunk/clangd/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_clang_executable(clangd
+  ClangDMain.cpp
+  JSONRPCDispatcher.cpp
+  Protocol.cpp
+  ProtocolHandlers.cpp
+  )
+
+target_link_libraries(clangd
+  clangBasic
+  clangFormat
+  LLVMSupport
+  )
Index: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
===================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
@@ -0,0 +1,66 @@
+//===--- JSONRPCDispatcher.h - Main JSON parser entry point -----*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
+
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/YAMLParser.h"
+
+namespace clang {
+namespace clangd {
+
+/// Callback for messages sent to the server, called by the JSONRPCDispatcher.
+class Handler {
+public:
+  Handler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
+      : Outs(Outs), Logs(Logs) {}
+  virtual ~Handler() = default;
+
+  /// Called when the server receives a method call. This is supposed to return
+  /// a result on Outs. The default implementation returns an "unknown method"
+  /// error to the client and logs a warning.
+  virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID);
+  /// Called when the server receives a notification. No result should be
+  /// written to Outs. The default implemetation logs a warning.
+  virtual void handleNotification(llvm::yaml::MappingNode *Params);
+
+protected:
+  llvm::raw_ostream &Outs;
+  llvm::raw_ostream &Logs;
+
+  /// Helper to write a JSONRPC result to Outs.
+  void writeMessage(const Twine &Message);
+};
+
+/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the
+/// registered Handler for the method received.
+class JSONRPCDispatcher {
+public:
+  /// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown
+  /// method is received.
+  JSONRPCDispatcher(std::unique_ptr<Handler> UnknownHandler)
+      : UnknownHandler(std::move(UnknownHandler)) {}
+
+  /// Registers a Handler for the specified Method.
+  void registerHandler(StringRef Method, std::unique_ptr<Handler> H);
+
+  /// Parses a JSONRPC message and calls the Handler for it.
+  bool call(StringRef Content) const;
+
+private:
+  llvm::StringMap<std::unique_ptr<Handler>> Handlers;
+  std::unique_ptr<Handler> UnknownHandler;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
===================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h
@@ -0,0 +1,100 @@
+//===--- 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.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the actions performed when the server gets a specific
+// request.
+//
+//===----------------------------------------------------------------------===//
+
+#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 {
+class DocumentStore;
+
+struct InitializeHandler : Handler {
+  InitializeHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
+      : Handler(Outs, Logs) {}
+
+  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
+        }}})");
+  }
+};
+
+struct ShutdownHandler : Handler {
+  ShutdownHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
+      : Handler(Outs, Logs) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    // FIXME: Calling exit is rude, can we communicate to main somehow?
+    exit(0);
+  }
+};
+
+struct TextDocumentDidOpenHandler : Handler {
+  TextDocumentDidOpenHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
+                             DocumentStore &Store)
+      : Handler(Outs, Logs), Store(Store) {}
+
+  void handleNotification(llvm::yaml::MappingNode *Params) override;
+
+private:
+  DocumentStore &Store;
+};
+
+struct TextDocumentDidChangeHandler : Handler {
+  TextDocumentDidChangeHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
+                               DocumentStore &Store)
+      : Handler(Outs, Logs), Store(Store) {}
+
+  void handleNotification(llvm::yaml::MappingNode *Params) override;
+
+private:
+  DocumentStore &Store;
+};
+
+struct TextDocumentRangeFormattingHandler : Handler {
+  TextDocumentRangeFormattingHandler(llvm::raw_ostream &Outs,
+                                     llvm::raw_ostream &Logs,
+                                     DocumentStore &Store)
+      : Handler(Outs, Logs), Store(Store) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
+
+private:
+  DocumentStore &Store;
+};
+
+struct TextDocumentFormattingHandler : Handler {
+  TextDocumentFormattingHandler(llvm::raw_ostream &Outs,
+                                llvm::raw_ostream &Logs, DocumentStore &Store)
+      : Handler(Outs, Logs), Store(Store) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
+
+private:
+  DocumentStore &Store;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/trunk/clangd/Protocol.h
===================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h
+++ clang-tools-extra/trunk/clangd/Protocol.h
@@ -0,0 +1,160 @@
+//===--- Protocol.h - Language Server Protocol Implementation ---*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains structs based on the LSP specification at
+// https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md
+//
+// This is not meant to be a complete implementation, new interfaces are added
+// when they're needed.
+//
+// Each struct has a parse and unparse function, that converts back and forth
+// between the struct and a JSON representation.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
+
+#include "llvm/ADT/Optional.h"
+#include "llvm/Support/YAMLParser.h"
+#include <string>
+
+namespace clang {
+namespace clangd {
+
+struct TextDocumentIdentifier {
+  /// The text document's URI.
+  std::string uri;
+
+  static llvm::Optional<TextDocumentIdentifier>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct Position {
+  /// Line position in a document (zero-based).
+  int line;
+
+  /// Character offset on a line in a document (zero-based).
+  int character;
+
+  static llvm::Optional<Position> parse(llvm::yaml::MappingNode *Params);
+  static std::string unparse(const Position &P);
+};
+
+struct Range {
+  /// The range's start position.
+  Position start;
+
+  /// The range's end position.
+  Position end;
+
+  static llvm::Optional<Range> parse(llvm::yaml::MappingNode *Params);
+  static std::string unparse(const Range &P);
+};
+
+struct TextEdit {
+  /// The range of the text document to be manipulated. To insert
+  /// text into a document create a range where start === end.
+  Range range;
+
+  /// The string to be inserted. For delete operations use an
+  /// empty string.
+  std::string newText;
+
+  static llvm::Optional<TextEdit> parse(llvm::yaml::MappingNode *Params);
+  static std::string unparse(const TextEdit &P);
+};
+
+struct TextDocumentItem {
+  /// The text document's URI.
+  std::string uri;
+
+  /// The text document's language identifier.
+  std::string languageId;
+
+  /// The version number of this document (it will strictly increase after each
+  int version;
+
+  /// The content of the opened text document.
+  std::string text;
+
+  static llvm::Optional<TextDocumentItem>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct DidOpenTextDocumentParams {
+  /// The document that was opened.
+  TextDocumentItem textDocument;
+
+  static llvm::Optional<DidOpenTextDocumentParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct TextDocumentContentChangeEvent {
+  /// The new text of the document.
+  std::string text;
+
+  static llvm::Optional<TextDocumentContentChangeEvent>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct DidChangeTextDocumentParams {
+  /// The document that did change. The version number points
+  /// to the version after all provided content changes have
+  /// been applied.
+  TextDocumentIdentifier textDocument;
+
+  /// The actual content changes.
+  std::vector<TextDocumentContentChangeEvent> contentChanges;
+
+  static llvm::Optional<DidChangeTextDocumentParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct FormattingOptions {
+  /// Size of a tab in spaces.
+  int tabSize;
+
+  /// Prefer spaces over tabs.
+  bool insertSpaces;
+
+  static llvm::Optional<FormattingOptions>
+  parse(llvm::yaml::MappingNode *Params);
+  static std::string unparse(const FormattingOptions &P);
+};
+
+struct DocumentRangeFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+
+  /// The range to format
+  Range range;
+
+  /// The format options
+  FormattingOptions options;
+
+  static llvm::Optional<DocumentRangeFormattingParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct DocumentFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+
+  /// The format options
+  FormattingOptions options;
+
+  static llvm::Optional<DocumentFormattingParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
===================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
@@ -0,0 +1,124 @@
+//===--- JSONRPCDispatcher.cpp - Main JSON parser entry point -------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONRPCDispatcher.h"
+#include "ProtocolHandlers.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/YAMLParser.h"
+using namespace clang;
+using namespace clangd;
+
+void Handler::writeMessage(const Twine &Message) {
+  llvm::SmallString<128> Storage;
+  StringRef M = Message.toStringRef(Storage);
+
+  // Log without headers.
+  Logs << "--> " << M << '\n';
+  Logs.flush();
+
+  // Emit message with header.
+  Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M;
+  Outs.flush();
+}
+
+void Handler::handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) {
+  Logs << "Method ignored.\n";
+  // Return that this method is unsupported.
+  writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID +
+      R"(,"error":{"code":-32601}})");
+}
+
+void Handler::handleNotification(llvm::yaml::MappingNode *Params) {
+  Logs << "Notification ignored.\n";
+}
+
+void JSONRPCDispatcher::registerHandler(StringRef Method,
+                                        std::unique_ptr<Handler> H) {
+  assert(!Handlers.count(Method) && "Handler already registered!");
+  Handlers[Method] = std::move(H);
+}
+
+static void
+callHandler(const llvm::StringMap<std::unique_ptr<Handler>> &Handlers,
+            llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
+            llvm::yaml::MappingNode *Params, Handler *UnknownHandler) {
+  llvm::SmallString<10> MethodStorage;
+  auto I = Handlers.find(Method->getValue(MethodStorage));
+  auto *Handler = I != Handlers.end() ? I->second.get() : UnknownHandler;
+  if (Id)
+    Handler->handleMethod(Params, Id->getRawValue());
+  else
+    Handler->handleNotification(Params);
+}
+
+bool JSONRPCDispatcher::call(StringRef Content) const {
+  llvm::SourceMgr SM;
+  llvm::yaml::Stream YAMLStream(Content, SM);
+
+  auto Doc = YAMLStream.begin();
+  if (Doc == YAMLStream.end())
+    return false;
+
+  auto *Root = Doc->getRoot();
+  if (!Root)
+    return false;
+
+  auto *Object = dyn_cast<llvm::yaml::MappingNode>(Root);
+  if (!Object)
+    return false;
+
+  llvm::yaml::ScalarNode *Version = nullptr;
+  llvm::yaml::ScalarNode *Method = nullptr;
+  llvm::yaml::MappingNode *Params = nullptr;
+  llvm::yaml::ScalarNode *Id = nullptr;
+  for (auto &NextKeyValue : *Object) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return false;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    llvm::yaml::Node *Value = NextKeyValue.getValue();
+    if (!Value)
+      return false;
+
+    if (KeyValue == "jsonrpc") {
+      // This should be "2.0". Always.
+      Version = dyn_cast<llvm::yaml::ScalarNode>(Value);
+      if (!Version || Version->getRawValue() != "\"2.0\"")
+        return false;
+    } else if (KeyValue == "method") {
+      Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
+    } else if (KeyValue == "id") {
+      Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
+    } else if (KeyValue == "params") {
+      if (!Method)
+        return false;
+      // We have to interleave the call of the function here, otherwise the
+      // YAMLParser will die because it can't go backwards. This is unfortunate
+      // because it will break clients that put the id after params. A possible
+      // fix would be to split the parsing and execution phases.
+      Params = dyn_cast<llvm::yaml::MappingNode>(Value);
+      callHandler(Handlers, Method, Id, Params, UnknownHandler.get());
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // In case there was a request with no params, call the handler on the
+  // leftovers.
+  if (!Method)
+    return false;
+  callHandler(Handlers, Method, Id, nullptr, UnknownHandler.get());
+
+  return true;
+}
Index: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
===================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
@@ -0,0 +1,116 @@
+//===--- 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 "DocumentStore.h"
+#include "clang/Format/Format.h"
+using namespace clang;
+using namespace clangd;
+
+void TextDocumentDidOpenHandler::handleNotification(
+    llvm::yaml::MappingNode *Params) {
+  auto DOTDP = DidOpenTextDocumentParams::parse(Params);
+  if (!DOTDP) {
+    Logs << "Failed to decode DidOpenTextDocumentParams!\n";
+    return;
+  }
+  Store.addDocument(DOTDP->textDocument.uri, DOTDP->textDocument.text);
+}
+
+void TextDocumentDidChangeHandler::handleNotification(
+    llvm::yaml::MappingNode *Params) {
+  auto DCTDP = DidChangeTextDocumentParams::parse(Params);
+  if (!DCTDP || DCTDP->contentChanges.size() != 1) {
+    Logs << "Failed to decode DidChangeTextDocumentParams!\n";
+    return;
+  }
+  // We only support full syncing right now.
+  Store.addDocument(DCTDP->textDocument.uri, 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};
+}
+
+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);
+
+  // Now 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 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) {
+    Logs << "Failed to decode DocumentRangeFormattingParams!\n";
+    return;
+  }
+
+  StringRef Code = Store.getDocument(DRFP->textDocument.uri);
+
+  size_t Begin = positionToOffset(Code, DRFP->range.start);
+  size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
+
+  writeMessage(formatCode(Code, DRFP->textDocument.uri,
+                          {clang::tooling::Range(Begin, Len)}, ID));
+}
+
+void TextDocumentFormattingHandler::handleMethod(
+    llvm::yaml::MappingNode *Params, StringRef ID) {
+  auto DFP = DocumentFormattingParams::parse(Params);
+  if (!DFP) {
+    Logs << "Failed to decode DocumentFormattingParams!\n";
+    return;
+  }
+
+  // Format everything.
+  StringRef Code = Store.getDocument(DFP->textDocument.uri);
+  writeMessage(formatCode(Code, DFP->textDocument.uri,
+                          {clang::tooling::Range(0, Code.size())}, ID));
+}
Index: clang-tools-extra/trunk/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp
+++ clang-tools-extra/trunk/clangd/Protocol.cpp
@@ -0,0 +1,412 @@
+//===--- Protocol.cpp - Language Server Protocol Implementation -----------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the serialization code for the LSP structs.
+// FIXME: This is extremely repetetive and ugly. Is there a better way?
+//
+//===----------------------------------------------------------------------===//
+
+#include "Protocol.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/raw_ostream.h"
+using namespace clang::clangd;
+
+llvm::Optional<TextDocumentIdentifier>
+TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) {
+  TextDocumentIdentifier Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "uri") {
+      Result.uri = Value->getValue(Storage);
+    } else if (KeyValue == "version") {
+      // FIXME: parse version, but only for VersionedTextDocumentIdentifiers.
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<Position> Position::parse(llvm::yaml::MappingNode *Params) {
+  Position Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "line") {
+      long long Val;
+      if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val))
+        return llvm::None;
+      Result.line = Val;
+    } else if (KeyValue == "character") {
+      long long Val;
+      if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val))
+        return llvm::None;
+      Result.character = Val;
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+std::string Position::unparse(const Position &P) {
+  std::string Result;
+  llvm::raw_string_ostream(Result)
+      << llvm::format(R"({"line": %d, "character": %d})", P.line, P.character);
+  return Result;
+}
+
+llvm::Optional<Range> Range::parse(llvm::yaml::MappingNode *Params) {
+  Range Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "start") {
+      auto Parsed = Position::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.start = std::move(*Parsed);
+    } else if (KeyValue == "end") {
+      auto Parsed = Position::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.end = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+std::string Range::unparse(const Range &P) {
+  std::string Result;
+  llvm::raw_string_ostream(Result) << llvm::format(
+      R"({"start": %s, "end": %s})", Position::unparse(P.start).c_str(),
+      Position::unparse(P.end).c_str());
+  return Result;
+}
+
+llvm::Optional<TextDocumentItem>
+TextDocumentItem::parse(llvm::yaml::MappingNode *Params) {
+  TextDocumentItem Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "uri") {
+      Result.uri = Value->getValue(Storage);
+    } else if (KeyValue == "languageId") {
+      Result.languageId = Value->getValue(Storage);
+    } else if (KeyValue == "version") {
+      long long Val;
+      if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val))
+        return llvm::None;
+      Result.version = Val;
+    } else if (KeyValue == "text") {
+      Result.text = Value->getValue(Storage);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<TextEdit> TextEdit::parse(llvm::yaml::MappingNode *Params) {
+  TextEdit Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value = NextKeyValue.getValue();
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "range") {
+      auto *Map = dyn_cast<llvm::yaml::MappingNode>(Value);
+      if (!Map)
+        return llvm::None;
+      auto Parsed = Range::parse(Map);
+      if (!Parsed)
+        return llvm::None;
+      Result.range = std::move(*Parsed);
+    } else if (KeyValue == "newText") {
+      auto *Node = dyn_cast<llvm::yaml::ScalarNode>(Value);
+      if (!Node)
+        return llvm::None;
+      Result.newText = Node->getValue(Storage);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+std::string TextEdit::unparse(const TextEdit &P) {
+  std::string Result;
+  llvm::raw_string_ostream(Result) << llvm::format(
+      R"({"range": %s, "newText": "%s"})", Range::unparse(P.range).c_str(),
+      llvm::yaml::escape(P.newText).c_str());
+  return Result;
+}
+
+llvm::Optional<DidOpenTextDocumentParams>
+DidOpenTextDocumentParams::parse(llvm::yaml::MappingNode *Params) {
+  DidOpenTextDocumentParams Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto Parsed = TextDocumentItem::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<DidChangeTextDocumentParams>
+DidChangeTextDocumentParams::parse(llvm::yaml::MappingNode *Params) {
+  DidChangeTextDocumentParams Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value = NextKeyValue.getValue();
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto *Map = dyn_cast<llvm::yaml::MappingNode>(Value);
+      if (!Map)
+        return llvm::None;
+      auto Parsed = TextDocumentIdentifier::parse(Map);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else if (KeyValue == "contentChanges") {
+      auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value);
+      if (!Seq)
+        return llvm::None;
+      for (auto &Item : *Seq) {
+        auto *I = dyn_cast<llvm::yaml::MappingNode>(&Item);
+        if (!I)
+          return llvm::None;
+        auto Parsed = TextDocumentContentChangeEvent::parse(I);
+        if (!Parsed)
+          return llvm::None;
+        Result.contentChanges.push_back(std::move(*Parsed));
+      }
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<TextDocumentContentChangeEvent>
+TextDocumentContentChangeEvent::parse(llvm::yaml::MappingNode *Params) {
+  TextDocumentContentChangeEvent Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "text") {
+      Result.text = Value->getValue(Storage);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<FormattingOptions>
+FormattingOptions::parse(llvm::yaml::MappingNode *Params) {
+  FormattingOptions Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "tabSize") {
+      long long Val;
+      if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val))
+        return llvm::None;
+      Result.tabSize = Val;
+    } else if (KeyValue == "insertSpaces") {
+      long long Val;
+      StringRef Str = Value->getValue(Storage);
+      if (llvm::getAsSignedInteger(Str, 0, Val)) {
+        if (Str == "true")
+          Val = 1;
+        else if (Str == "false")
+          Val = 0;
+        else
+          return llvm::None;
+      }
+      Result.insertSpaces = Val;
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+std::string FormattingOptions::unparse(const FormattingOptions &P) {
+  std::string Result;
+  llvm::raw_string_ostream(Result) << llvm::format(
+      R"({"tabSize": %d, "insertSpaces": %d})", P.tabSize, P.insertSpaces);
+  return Result;
+}
+
+llvm::Optional<DocumentRangeFormattingParams>
+DocumentRangeFormattingParams::parse(llvm::yaml::MappingNode *Params) {
+  DocumentRangeFormattingParams Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto Parsed = TextDocumentIdentifier::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else if (KeyValue == "range") {
+      auto Parsed = Range::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.range = std::move(*Parsed);
+    } else if (KeyValue == "options") {
+      auto Parsed = FormattingOptions::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.options = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<DocumentFormattingParams>
+DocumentFormattingParams::parse(llvm::yaml::MappingNode *Params) {
+  DocumentFormattingParams Result;
+  for (auto &NextKeyValue : *Params) {
+    auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+    if (!KeyString)
+      return llvm::None;
+
+    llvm::SmallString<10> KeyStorage;
+    StringRef KeyValue = KeyString->getValue(KeyStorage);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto Parsed = TextDocumentIdentifier::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else if (KeyValue == "options") {
+      auto Parsed = FormattingOptions::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.options = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
Index: clang-tools-extra/trunk/clangd/DocumentStore.h
===================================================================
--- clang-tools-extra/trunk/clangd/DocumentStore.h
+++ clang-tools-extra/trunk/clangd/DocumentStore.h
@@ -0,0 +1,38 @@
+//===--- DocumentStore.h - File contents container --------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DOCUMENTSTORE_H
+
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringMap.h"
+#include <string>
+
+namespace clang {
+namespace clangd {
+
+/// A container for files opened in a workspace, addressed by URI. The contents
+/// are owned by the DocumentStore.
+class DocumentStore {
+public:
+  /// Add a document to the store. Overwrites existing contents.
+  void addDocument(StringRef Uri, StringRef Text) { Docs[Uri] = Text; }
+  /// Delete a document from the store.
+  void removeDocument(StringRef Uri) { Docs.erase(Uri); }
+  /// Retrieve a document from the store. Empty string if it's unknown.
+  StringRef getDocument(StringRef Uri) const { return Docs.lookup(Uri); }
+
+private:
+  llvm::StringMap<std::string> Docs;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to