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/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/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 +#include +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(Outs, Logs)); + Dispatcher.registerHandler("initialize", + llvm::make_unique(Outs, Logs)); + Dispatcher.registerHandler("shutdown", + llvm::make_unique(Outs, Logs)); + Dispatcher.registerHandler( + "textDocument/didOpen", + llvm::make_unique(Outs, Logs, Store)); + // FIXME: Implement textDocument/didClose. + Dispatcher.registerHandler( + "textDocument/didChange", + llvm::make_unique(Outs, Logs, Store)); + Dispatcher.registerHandler( + "textDocument/rangeFormatting", + llvm::make_unique(Outs, Logs, Store)); + Dispatcher.registerHandler( + "textDocument/formatting", + llvm::make_unique(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 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/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 + +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 Docs; +}; + +} // namespace clangd +} // namespace clang + +#endif 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 UnknownHandler) + : UnknownHandler(std::move(UnknownHandler)) {} + + /// Registers a Handler for the specified Method. + void registerHandler(StringRef Method, std::unique_ptr H); + + /// Parses a JSONRPC message and calls the Handler for it. + bool call(StringRef Content) const; + +private: + llvm::StringMap> Handlers; + std::unique_ptr UnknownHandler; +}; + +} // 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 H) { + assert(!Handlers.count(Method) && "Handler already registered!"); + Handlers[Method] = std::move(H); +} + +static void +callHandler(const llvm::StringMap> &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(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(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(Value); + if (!Version || Version->getRawValue() != "\"2.0\"") + return false; + } else if (KeyValue == "method") { + Method = dyn_cast(Value); + } else if (KeyValue == "id") { + Id = dyn_cast(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(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/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 + +namespace clang { +namespace clangd { + +struct TextDocumentIdentifier { + /// The text document's URI. + std::string uri; + + static llvm::Optional + 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 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 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 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 + parse(llvm::yaml::MappingNode *Params); +}; + +struct DidOpenTextDocumentParams { + /// The document that was opened. + TextDocumentItem textDocument; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct TextDocumentContentChangeEvent { + /// The new text of the document. + std::string text; + + static llvm::Optional + 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 contentChanges; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +struct FormattingOptions { + /// Size of a tab in spaces. + int tabSize; + + /// Prefer spaces over tabs. + bool insertSpaces; + + static llvm::Optional + 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 + parse(llvm::yaml::MappingNode *Params); +}; + +struct DocumentFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The format options + FormattingOptions options; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + +} // namespace clangd +} // namespace clang + +#endif 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::parse(llvm::yaml::MappingNode *Params) { + TextDocumentIdentifier Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + Position Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + Range Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + TextDocumentItem Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + TextEdit Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(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(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(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::parse(llvm::yaml::MappingNode *Params) { + DidOpenTextDocumentParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + DidChangeTextDocumentParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(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(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(Value); + if (!Seq) + return llvm::None; + for (auto &Item : *Seq) { + auto *I = dyn_cast(&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::parse(llvm::yaml::MappingNode *Params) { + TextDocumentContentChangeEvent Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + FormattingOptions Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + DocumentRangeFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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::parse(llvm::yaml::MappingNode *Params) { + DocumentFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(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/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/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 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/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/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"}