bkramer updated this revision to Diff 87226.
bkramer marked 8 inline comments as done.
bkramer added a comment.
Address review comments. Make test actually run (missing cmake file)
https://reviews.llvm.org/D29451
Files:
CMakeLists.txt
clangd/CMakeLists.txt
clangd/ClangDMain.cpp
clangd/DocumentStore.h
clangd/JSONRPCDispatcher.cpp
clangd/JSONRPCDispatcher.h
clangd/Protocol.cpp
clangd/Protocol.h
clangd/ProtocolHandlers.cpp
clangd/ProtocolHandlers.h
test/CMakeLists.txt
test/clangd/formatting.test
Index: test/clangd/formatting.test
===================================================================
--- /dev/null
+++ 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: test/CMakeLists.txt
===================================================================
--- test/CMakeLists.txt
+++ 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: clangd/ProtocolHandlers.h
===================================================================
--- /dev/null
+++ 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: clangd/ProtocolHandlers.cpp
===================================================================
--- /dev/null
+++ 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: clangd/Protocol.h
===================================================================
--- /dev/null
+++ 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: clangd/Protocol.cpp
===================================================================
--- /dev/null
+++ 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: clangd/JSONRPCDispatcher.h
===================================================================
--- /dev/null
+++ clangd/JSONRPCDispatcher.h
@@ -0,0 +1,67 @@
+//===--- 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: clangd/JSONRPCDispatcher.cpp
===================================================================
--- /dev/null
+++ clangd/JSONRPCDispatcher.cpp
@@ -0,0 +1,125 @@
+//===--- 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: clangd/DocumentStore.h
===================================================================
--- /dev/null
+++ 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
Index: clangd/ClangDMain.cpp
===================================================================
--- /dev/null
+++ 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))
+ 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: clangd/CMakeLists.txt
===================================================================
--- /dev/null
+++ 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: CMakeLists.txt
===================================================================
--- CMakeLists.txt
+++ 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)
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits