[clang-tools-extra] r294291 - Add a prototype for clangd

Benjamin Kramer via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 7 02:28:20 PST 2017


Author: d0k
Date: Tue Feb  7 04:28:20 2017
New Revision: 294291

URL: http://llvm.org/viewvc/llvm-project?rev=294291&view=rev
Log:
Add a prototype for clangd

clangd is a language server protocol implementation based on clang. It's
supposed to provide editor integration while not suffering from the
confined ABI of libclang.

This implementation is limited to the bare minimum functionality of
doing (whole-document) formatting and rangeFormatting. The JSON parsing
is based on LLVM's YAMLParser but yet most of the code of clangd is
currently dealing with JSON serialization and deserialization.

This was only tested with VS Code so far, mileage with other LSP clients
may vary.

Differential Revision: https://reviews.llvm.org/D29451

Added:
    clang-tools-extra/trunk/clangd/
    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/clangd/
    clang-tools-extra/trunk/test/clangd/formatting.test   (with props)
Modified:
    clang-tools-extra/trunk/CMakeLists.txt
    clang-tools-extra/trunk/test/CMakeLists.txt

Modified: clang-tools-extra/trunk/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/CMakeLists.txt?rev=294291&r1=294290&r2=294291&view=diff
==============================================================================
--- clang-tools-extra/trunk/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/CMakeLists.txt Tue Feb  7 04:28:20 2017
@@ -10,6 +10,7 @@ endif()
 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)

Added: clang-tools-extra/trunk/clangd/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/CMakeLists.txt?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/CMakeLists.txt (added)
+++ clang-tools-extra/trunk/clangd/CMakeLists.txt Tue Feb  7 04:28:20 2017
@@ -0,0 +1,12 @@
+add_clang_executable(clangd
+  ClangDMain.cpp
+  JSONRPCDispatcher.cpp
+  Protocol.cpp
+  ProtocolHandlers.cpp
+  )
+
+target_link_libraries(clangd
+  clangBasic
+  clangFormat
+  LLVMSupport
+  )

Added: clang-tools-extra/trunk/clangd/ClangDMain.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangDMain.cpp?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangDMain.cpp (added)
+++ clang-tools-extra/trunk/clangd/ClangDMain.cpp Tue Feb  7 04:28:20 2017
@@ -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";
+    }
+  }
+}

Added: clang-tools-extra/trunk/clangd/DocumentStore.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/DocumentStore.h?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/DocumentStore.h (added)
+++ clang-tools-extra/trunk/clangd/DocumentStore.h Tue Feb  7 04:28:20 2017
@@ -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

Added: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp (added)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp Tue Feb  7 04:28:20 2017
@@ -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;
+}

Added: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h (added)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h Tue Feb  7 04:28:20 2017
@@ -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

Added: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (added)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Tue Feb  7 04:28:20 2017
@@ -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;
+}

Added: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (added)
+++ clang-tools-extra/trunk/clangd/Protocol.h Tue Feb  7 04:28:20 2017
@@ -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

Added: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (added)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Tue Feb  7 04:28:20 2017
@@ -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));
+}

Added: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.h?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h (added)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h Tue Feb  7 04:28:20 2017
@@ -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

Modified: clang-tools-extra/trunk/test/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/CMakeLists.txt?rev=294291&r1=294290&r2=294291&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/test/CMakeLists.txt Tue Feb  7 04:28:20 2017
@@ -43,6 +43,7 @@ set(CLANG_TOOLS_TEST_DEPS
   # Individual tools we test.
   clang-apply-replacements
   clang-change-namespace
+  clangd
   clang-include-fixer
   clang-move
   clang-query

Added: clang-tools-extra/trunk/test/clangd/formatting.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/formatting.test?rev=294291&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clangd/formatting.test (added)
+++ clang-tools-extra/trunk/test/clangd/formatting.test Tue Feb  7 04:28:20 2017
@@ -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"}

Propchange: clang-tools-extra/trunk/test/clangd/formatting.test
------------------------------------------------------------------------------
    svn:eol-style = CRLF




More information about the cfe-commits mailing list