//===--- 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" #include "llvm/Support/Path.h" using namespace clang::clangd; URI URI::fromUri(llvm::StringRef uri) { URI Result; Result.uri = uri; uri.consume_front("file://"); // Also trim authority-less URIs uri.consume_front("file:"); // For Windows paths e.g. /X: if (uri.size() > 2 && uri[0] == '/' && uri[2] == ':') uri.consume_front("/"); // Make sure that file paths are in native separators Result.file = llvm::sys::path::convert_to_slash(uri); return Result; } URI URI::fromFile(llvm::StringRef file) { using namespace llvm::sys; URI Result; Result.file = file; Result.uri = "file://"; // For Windows paths e.g. X: if (file.size() > 1 && file[1] == ':') Result.uri += "/"; // Make sure that uri paths are with posix separators Result.uri += path::convert_to_slash(file, path::Style::posix); return Result; } URI URI::parse(llvm::yaml::ScalarNode *Param) { llvm::SmallString<10> Storage; return URI::fromUri(Param->getValue(Storage)); } std::string URI::unparse(const URI &U) { return "\"" + U.uri + "\""; } 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; if (KeyValue == "uri") { Result.uri = URI::parse(Value); } 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; } std::string Location::unparse(const Location &P) { std::string Result; llvm::raw_string_ostream(Result) << llvm::format( R"({"uri": %s, "range": %s})", URI::unparse(P.uri).c_str(), Range::unparse(P.range).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 = URI::parse(Value); } 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 Metadata::parse(llvm::yaml::MappingNode *Params) { Metadata 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 == "extraFlags") { auto *Seq = dyn_cast(Value); if (!Seq) return llvm::None; for (auto &Item : *Seq) { auto *Node = dyn_cast(&Item); if (!Node) return llvm::None; Result.extraFlags.push_back(Node->getValue(Storage)); } } } 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 if (KeyValue == "metadata") { auto Parsed = Metadata::parse(Value); if (!Parsed) return llvm::None; Result.metadata = std::move(*Parsed); } else { return llvm::None; } } return Result; } llvm::Optional DidCloseTextDocumentParams::parse(llvm::yaml::MappingNode *Params) { DidCloseTextDocumentParams 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(); 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 { 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 DocumentOnTypeFormattingParams::parse(llvm::yaml::MappingNode *Params) { DocumentOnTypeFormattingParams 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); if (KeyValue == "ch") { auto *ScalarValue = dyn_cast_or_null(NextKeyValue.getValue()); if (!ScalarValue) return llvm::None; llvm::SmallString<10> Storage; Result.ch = ScalarValue->getValue(Storage); continue; } auto *Value = dyn_cast_or_null(NextKeyValue.getValue()); if (!Value) return llvm::None; if (KeyValue == "textDocument") { auto Parsed = TextDocumentIdentifier::parse(Value); if (!Parsed) return llvm::None; Result.textDocument = std::move(*Parsed); } else if (KeyValue == "position") { auto Parsed = Position::parse(Value); if (!Parsed) return llvm::None; Result.position = 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; } llvm::Optional Diagnostic::parse(llvm::yaml::MappingNode *Params) { Diagnostic 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); llvm::SmallString<10> Storage; if (KeyValue == "range") { auto *Value = dyn_cast_or_null(NextKeyValue.getValue()); if (!Value) return llvm::None; auto Parsed = Range::parse(Value); if (!Parsed) return llvm::None; Result.range = std::move(*Parsed); } else if (KeyValue == "severity") { auto *Value = dyn_cast_or_null(NextKeyValue.getValue()); if (!Value) return llvm::None; long long Val; if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) return llvm::None; Result.severity = Val; } else if (KeyValue == "message") { auto *Value = dyn_cast_or_null(NextKeyValue.getValue()); if (!Value) return llvm::None; Result.message = Value->getValue(Storage); } else { return llvm::None; } } return Result; } llvm::Optional CodeActionContext::parse(llvm::yaml::MappingNode *Params) { CodeActionContext 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 == "diagnostics") { 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 = Diagnostic::parse(I); if (!Parsed) return llvm::None; Result.diagnostics.push_back(std::move(*Parsed)); } } else { return llvm::None; } } return Result; } llvm::Optional CodeActionParams::parse(llvm::yaml::MappingNode *Params) { CodeActionParams 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 == "context") { auto Parsed = CodeActionContext::parse(Value); if (!Parsed) return llvm::None; Result.context = std::move(*Parsed); } else { return llvm::None; } } return Result; } llvm::Optional TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params) { TextDocumentPositionParams 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) continue; 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 == "position") { auto Parsed = Position::parse(Value); if (!Parsed) return llvm::None; Result.position = std::move(*Parsed); } else { return llvm::None; } } return Result; } std::string CompletionItem::unparse(const CompletionItem &CI) { std::string Result = "{"; llvm::raw_string_ostream Os(Result); assert(!CI.label.empty() && "completion item label is required"); Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)"; if (CI.kind != CompletionItemKind::Missing) Os << R"("kind":)" << static_cast(CI.kind) << R"(,)"; if (!CI.detail.empty()) Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)"; if (!CI.documentation.empty()) Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation) << R"(",)"; if (!CI.sortText.empty()) Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)"; if (!CI.filterText.empty()) Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)"; if (!CI.insertText.empty()) Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)"; if (CI.insertTextFormat != InsertTextFormat::Missing) { Os << R"("insertTextFormat":")" << static_cast(CI.insertTextFormat) << R"(",)"; } if (CI.textEdit) Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ','; if (!CI.additionalTextEdits.empty()) { Os << R"("additionalTextEdits":[)"; for (const auto &Edit : CI.additionalTextEdits) Os << TextEdit::unparse(Edit) << ","; Os.flush(); // The list additionalTextEdits is guaranteed nonempty at this point. // Replace the trailing comma with right brace. Result.back() = ']'; } Os.flush(); // Label is required, so Result is guaranteed to have a trailing comma. Result.back() = '}'; return Result; }