//===--- Protocol.cpp - Language Server Protocol Implementation -----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file contains the serialization code for the LSP structs. // //===----------------------------------------------------------------------===// #include "llvm/Support/LSP/Protocol.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/JSON.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; using namespace llvm::lsp; // Helper that doesn't treat `null` and absent fields as failures. template static bool mapOptOrNull(const llvm::json::Value &Params, llvm::StringLiteral Prop, T &Out, llvm::json::Path Path) { const llvm::json::Object *O = Params.getAsObject(); assert(O); // Field is missing or null. auto *V = O->get(Prop); if (!V || V->getAsNull()) return true; return fromJSON(*V, Out, Path.field(Prop)); } //===----------------------------------------------------------------------===// // LSPError //===----------------------------------------------------------------------===// char LSPError::ID; //===----------------------------------------------------------------------===// // URIForFile //===----------------------------------------------------------------------===// static bool isWindowsPath(StringRef Path) { return Path.size() > 1 && llvm::isAlpha(Path[0]) && Path[1] == ':'; } static bool isNetworkPath(StringRef Path) { return Path.size() > 2 && Path[0] == Path[1] && llvm::sys::path::is_separator(Path[0]); } static bool shouldEscapeInURI(unsigned char C) { // Unreserved characters. if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || (C >= '0' && C <= '9')) return false; switch (C) { case '-': case '_': case '.': case '~': // '/' is only reserved when parsing. case '/': // ':' is only reserved for relative URI paths, which we doesn't produce. case ':': return false; } return true; } /// Encodes a string according to percent-encoding. /// - Unreserved characters are not escaped. /// - Reserved characters always escaped with exceptions like '/'. /// - All other characters are escaped. static void percentEncode(StringRef Content, std::string &Out) { for (unsigned char C : Content) { if (shouldEscapeInURI(C)) { Out.push_back('%'); Out.push_back(llvm::hexdigit(C / 16)); Out.push_back(llvm::hexdigit(C % 16)); } else { Out.push_back(C); } } } /// Decodes a string according to percent-encoding. static std::string percentDecode(StringRef Content) { std::string Result; for (auto I = Content.begin(), E = Content.end(); I != E; ++I) { if (*I != '%') { Result += *I; continue; } if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) && llvm::isHexDigit(*(I + 2))) { Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2))); I += 2; } else { Result.push_back(*I); } } return Result; } /// Return the set containing the supported URI schemes. static StringSet<> &getSupportedSchemes() { static StringSet<> Schemes({"file", "test"}); return Schemes; } /// Returns true if the given scheme is structurally valid, i.e. it does not /// contain any invalid scheme characters. This does not check that the scheme /// is actually supported. static bool isStructurallyValidScheme(StringRef Scheme) { if (Scheme.empty()) return false; if (!llvm::isAlpha(Scheme[0])) return false; return llvm::all_of(llvm::drop_begin(Scheme), [](char C) { return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-'; }); } static llvm::Expected uriFromAbsolutePath(StringRef AbsolutePath, StringRef Scheme) { std::string Body; StringRef Authority; StringRef Root = llvm::sys::path::root_name(AbsolutePath); if (isNetworkPath(Root)) { // Windows UNC paths e.g. \\server\share => file://server/share Authority = Root.drop_front(2); AbsolutePath.consume_front(Root); } else if (isWindowsPath(Root)) { // Windows paths e.g. X:\path => file:///X:/path Body = "/"; } Body += llvm::sys::path::convert_to_slash(AbsolutePath); std::string Uri = Scheme.str() + ":"; if (Authority.empty() && Body.empty()) return Uri; // If authority if empty, we only print body if it starts with "/"; otherwise, // the URI is invalid. if (!Authority.empty() || StringRef(Body).starts_with("/")) { Uri.append("//"); percentEncode(Authority, Uri); } percentEncode(Body, Uri); return Uri; } static llvm::Expected getAbsolutePath(StringRef Authority, StringRef Body) { if (!Body.starts_with("/")) return llvm::createStringError( llvm::inconvertibleErrorCode(), "File scheme: expect body to be an absolute path starting " "with '/': " + Body); SmallString<128> Path; if (!Authority.empty()) { // Windows UNC paths e.g. file://server/share => \\server\share ("//" + Authority).toVector(Path); } else if (isWindowsPath(Body.substr(1))) { // Windows paths e.g. file:///X:/path => X:\path Body.consume_front("/"); } Path.append(Body); llvm::sys::path::native(Path); return std::string(Path); } static llvm::Expected parseFilePathFromURI(StringRef OrigUri) { StringRef Uri = OrigUri; // Decode the scheme of the URI. size_t Pos = Uri.find(':'); if (Pos == StringRef::npos) return llvm::createStringError(llvm::inconvertibleErrorCode(), "Scheme must be provided in URI: " + OrigUri); StringRef SchemeStr = Uri.substr(0, Pos); std::string UriScheme = percentDecode(SchemeStr); if (!isStructurallyValidScheme(UriScheme)) return llvm::createStringError(llvm::inconvertibleErrorCode(), "Invalid scheme: " + SchemeStr + " (decoded: " + UriScheme + ")"); Uri = Uri.substr(Pos + 1); // Decode the authority of the URI. std::string UriAuthority; if (Uri.consume_front("//")) { Pos = Uri.find('/'); UriAuthority = percentDecode(Uri.substr(0, Pos)); Uri = Uri.substr(Pos); } // Decode the body of the URI. std::string UriBody = percentDecode(Uri); // Compute the absolute path for this uri. if (!getSupportedSchemes().contains(UriScheme)) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "unsupported URI scheme `" + UriScheme + "' for workspace files"); } return getAbsolutePath(UriAuthority, UriBody); } llvm::Expected URIForFile::fromURI(StringRef Uri) { llvm::Expected FilePath = parseFilePathFromURI(Uri); if (!FilePath) return FilePath.takeError(); return URIForFile(std::move(*FilePath), Uri.str()); } llvm::Expected URIForFile::fromFile(StringRef AbsoluteFilepath, StringRef Scheme) { llvm::Expected Uri = uriFromAbsolutePath(AbsoluteFilepath, Scheme); if (!Uri) return Uri.takeError(); return fromURI(*Uri); } StringRef URIForFile::scheme() const { return uri().split(':').first; } void URIForFile::registerSupportedScheme(StringRef Scheme) { getSupportedSchemes().insert(Scheme); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, URIForFile &Result, llvm::json::Path Path) { if (std::optional Str = Value.getAsString()) { llvm::Expected ExpectedUri = URIForFile::fromURI(*Str); if (!ExpectedUri) { Path.report("unresolvable URI"); consumeError(ExpectedUri.takeError()); return false; } Result = std::move(*ExpectedUri); return true; } return false; } llvm::json::Value llvm::lsp::toJSON(const URIForFile &Value) { return Value.uri(); } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const URIForFile &Value) { return Os << Value.uri(); } //===----------------------------------------------------------------------===// // ClientCapabilities //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, ClientCapabilities &Result, llvm::json::Path Path) { const llvm::json::Object *O = Value.getAsObject(); if (!O) { Path.report("expected object"); return false; } if (const llvm::json::Object *TextDocument = O->getObject("textDocument")) { if (const llvm::json::Object *DocumentSymbol = TextDocument->getObject("documentSymbol")) { if (std::optional HierarchicalSupport = DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport")) Result.hierarchicalDocumentSymbol = *HierarchicalSupport; } if (auto *CodeAction = TextDocument->getObject("codeAction")) { if (CodeAction->getObject("codeActionLiteralSupport")) Result.codeActionStructure = true; } } if (auto *Window = O->getObject("window")) { if (std::optional WorkDoneProgressSupport = Window->getBoolean("workDoneProgress")) Result.workDoneProgress = *WorkDoneProgressSupport; } return true; } //===----------------------------------------------------------------------===// // ClientInfo //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, ClientInfo &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); if (!O || !O.map("name", Result.name)) return false; // Don't fail if we can't parse version. O.map("version", Result.version); return true; } //===----------------------------------------------------------------------===// // InitializeParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TraceLevel &Result, llvm::json::Path Path) { if (std::optional Str = Value.getAsString()) { if (*Str == "off") { Result = TraceLevel::Off; return true; } if (*Str == "messages") { Result = TraceLevel::Messages; return true; } if (*Str == "verbose") { Result = TraceLevel::Verbose; return true; } } return false; } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, InitializeParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); if (!O) return false; // We deliberately don't fail if we can't parse individual fields. O.map("capabilities", Result.capabilities); O.map("trace", Result.trace); mapOptOrNull(Value, "clientInfo", Result.clientInfo, Path); return true; } //===----------------------------------------------------------------------===// // TextDocumentItem //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextDocumentItem &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("uri", Result.uri) && O.map("languageId", Result.languageId) && O.map("text", Result.text) && O.map("version", Result.version); } //===----------------------------------------------------------------------===// // TextDocumentIdentifier //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const TextDocumentIdentifier &Value) { return llvm::json::Object{{"uri", Value.uri}}; } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextDocumentIdentifier &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("uri", Result.uri); } //===----------------------------------------------------------------------===// // VersionedTextDocumentIdentifier //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const VersionedTextDocumentIdentifier &Value) { return llvm::json::Object{ {"uri", Value.uri}, {"version", Value.version}, }; } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, VersionedTextDocumentIdentifier &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("uri", Result.uri) && O.map("version", Result.version); } //===----------------------------------------------------------------------===// // Position //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Position &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("line", Result.line) && O.map("character", Result.character); } llvm::json::Value llvm::lsp::toJSON(const Position &Value) { return llvm::json::Object{ {"line", Value.line}, {"character", Value.character}, }; } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Position &Value) { return Os << Value.line << ':' << Value.character; } //===----------------------------------------------------------------------===// // Range //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Range &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("start", Result.start) && O.map("end", Result.end); } llvm::json::Value llvm::lsp::toJSON(const Range &Value) { return llvm::json::Object{ {"start", Value.start}, {"end", Value.end}, }; } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Range &Value) { return Os << Value.start << '-' << Value.end; } //===----------------------------------------------------------------------===// // Location //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Location &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("uri", Result.uri) && O.map("range", Result.range); } llvm::json::Value llvm::lsp::toJSON(const Location &Value) { return llvm::json::Object{ {"uri", Value.uri}, {"range", Value.range}, }; } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Location &Value) { return Os << Value.range << '@' << Value.uri; } //===----------------------------------------------------------------------===// // TextDocumentPositionParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextDocumentPositionParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument) && O.map("position", Result.position); } //===----------------------------------------------------------------------===// // ReferenceParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, ReferenceContext &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.mapOptional("includeDeclaration", Result.includeDeclaration); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, ReferenceParams &Result, llvm::json::Path Path) { TextDocumentPositionParams &Base = Result; llvm::json::ObjectMapper O(Value, Path); return fromJSON(Value, Base, Path) && O && O.mapOptional("context", Result.context); } //===----------------------------------------------------------------------===// // DidOpenTextDocumentParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DidOpenTextDocumentParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument); } //===----------------------------------------------------------------------===// // DidCloseTextDocumentParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DidCloseTextDocumentParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument); } //===----------------------------------------------------------------------===// // DidChangeTextDocumentParams //===----------------------------------------------------------------------===// LogicalResult TextDocumentContentChangeEvent::applyTo(std::string &Contents) const { // If there is no range, the full document changed. if (!range) { Contents = text; return success(); } // Try to map the replacement range to the content. llvm::SourceMgr TmpScrMgr; TmpScrMgr.AddNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(Contents), SMLoc()); SMRange RangeLoc = range->getAsSMRange(TmpScrMgr); if (!RangeLoc.isValid()) return failure(); Contents.replace(RangeLoc.Start.getPointer() - Contents.data(), RangeLoc.End.getPointer() - RangeLoc.Start.getPointer(), text); return success(); } LogicalResult TextDocumentContentChangeEvent::applyTo( ArrayRef Changes, std::string &Contents) { for (const auto &Change : Changes) if (failed(Change.applyTo(Contents))) return failure(); return success(); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextDocumentContentChangeEvent &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("range", Result.range) && O.map("rangeLength", Result.rangeLength) && O.map("text", Result.text); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DidChangeTextDocumentParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument) && O.map("contentChanges", Result.contentChanges); } //===----------------------------------------------------------------------===// // MarkupContent //===----------------------------------------------------------------------===// static llvm::StringRef toTextKind(MarkupKind Kind) { switch (Kind) { case MarkupKind::PlainText: return "plaintext"; case MarkupKind::Markdown: return "markdown"; } llvm_unreachable("Invalid MarkupKind"); } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, MarkupKind Kind) { return Os << toTextKind(Kind); } llvm::json::Value llvm::lsp::toJSON(const MarkupContent &Mc) { if (Mc.value.empty()) return nullptr; return llvm::json::Object{ {"kind", toTextKind(Mc.kind)}, {"value", Mc.value}, }; } //===----------------------------------------------------------------------===// // Hover //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const Hover &Hover) { llvm::json::Object Result{{"contents", toJSON(Hover.contents)}}; if (Hover.range) Result["range"] = toJSON(*Hover.range); return std::move(Result); } //===----------------------------------------------------------------------===// // DocumentSymbol //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const DocumentSymbol &Symbol) { llvm::json::Object Result{{"name", Symbol.name}, {"kind", static_cast(Symbol.kind)}, {"range", Symbol.range}, {"selectionRange", Symbol.selectionRange}}; if (!Symbol.detail.empty()) Result["detail"] = Symbol.detail; if (!Symbol.children.empty()) Result["children"] = Symbol.children; return std::move(Result); } //===----------------------------------------------------------------------===// // DocumentSymbolParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DocumentSymbolParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument); } //===----------------------------------------------------------------------===// // DiagnosticRelatedInformation //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DiagnosticRelatedInformation &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("location", Result.location) && O.map("message", Result.message); } llvm::json::Value llvm::lsp::toJSON(const DiagnosticRelatedInformation &Info) { return llvm::json::Object{ {"location", Info.location}, {"message", Info.message}, }; } //===----------------------------------------------------------------------===// // Diagnostic //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(DiagnosticTag Tag) { return static_cast(Tag); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DiagnosticTag &Result, llvm::json::Path Path) { if (std::optional I = Value.getAsInteger()) { Result = (DiagnosticTag)*I; return true; } return false; } llvm::json::Value llvm::lsp::toJSON(const Diagnostic &Diag) { llvm::json::Object Result{ {"range", Diag.range}, {"severity", (int)Diag.severity}, {"message", Diag.message}, }; if (Diag.category) Result["category"] = *Diag.category; if (!Diag.source.empty()) Result["source"] = Diag.source; if (Diag.relatedInformation) Result["relatedInformation"] = *Diag.relatedInformation; if (!Diag.tags.empty()) Result["tags"] = Diag.tags; return std::move(Result); } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Diagnostic &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); if (!O) return false; int Severity = 0; if (!mapOptOrNull(Value, "severity", Severity, Path)) return false; Result.severity = (DiagnosticSeverity)Severity; return O.map("range", Result.range) && O.map("message", Result.message) && mapOptOrNull(Value, "category", Result.category, Path) && mapOptOrNull(Value, "source", Result.source, Path) && mapOptOrNull(Value, "relatedInformation", Result.relatedInformation, Path) && mapOptOrNull(Value, "tags", Result.tags, Path); } //===----------------------------------------------------------------------===// // PublishDiagnosticsParams //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const PublishDiagnosticsParams &Params) { return llvm::json::Object{ {"uri", Params.uri}, {"diagnostics", Params.diagnostics}, {"version", Params.version}, }; } //===----------------------------------------------------------------------===// // TextEdit //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextEdit &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("range", Result.range) && O.map("newText", Result.newText); } llvm::json::Value llvm::lsp::toJSON(const TextEdit &Value) { return llvm::json::Object{ {"range", Value.range}, {"newText", Value.newText}, }; } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const TextEdit &Value) { Os << Value.range << " => \""; llvm::printEscapedString(Value.newText, Os); return Os << '"'; } //===----------------------------------------------------------------------===// // CompletionItemKind //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CompletionItemKind &Result, llvm::json::Path Path) { if (std::optional IntValue = Value.getAsInteger()) { if (*IntValue < static_cast(CompletionItemKind::Text) || *IntValue > static_cast(CompletionItemKind::TypeParameter)) return false; Result = static_cast(*IntValue); return true; } return false; } CompletionItemKind llvm::lsp::adjustKindToCapability( CompletionItemKind Kind, CompletionItemKindBitset &SupportedCompletionItemKinds) { size_t KindVal = static_cast(Kind); if (KindVal >= kCompletionItemKindMin && KindVal <= SupportedCompletionItemKinds.size() && SupportedCompletionItemKinds[KindVal]) return Kind; // Provide some fall backs for common kinds that are close enough. switch (Kind) { case CompletionItemKind::Folder: return CompletionItemKind::File; case CompletionItemKind::EnumMember: return CompletionItemKind::Enum; case CompletionItemKind::Struct: return CompletionItemKind::Class; default: return CompletionItemKind::Text; } } bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CompletionItemKindBitset &Result, llvm::json::Path Path) { if (const llvm::json::Array *ArrayValue = Value.getAsArray()) { for (size_t I = 0, E = ArrayValue->size(); I < E; ++I) { CompletionItemKind KindOut; if (fromJSON((*ArrayValue)[I], KindOut, Path.index(I))) Result.set(size_t(KindOut)); } return true; } return false; } //===----------------------------------------------------------------------===// // CompletionItem //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const CompletionItem &Value) { assert(!Value.label.empty() && "completion item label is required"); llvm::json::Object Result{{"label", Value.label}}; if (Value.kind != CompletionItemKind::Missing) Result["kind"] = static_cast(Value.kind); if (!Value.detail.empty()) Result["detail"] = Value.detail; if (Value.documentation) Result["documentation"] = Value.documentation; if (!Value.sortText.empty()) Result["sortText"] = Value.sortText; if (!Value.filterText.empty()) Result["filterText"] = Value.filterText; if (!Value.insertText.empty()) Result["insertText"] = Value.insertText; if (Value.insertTextFormat != InsertTextFormat::Missing) Result["insertTextFormat"] = static_cast(Value.insertTextFormat); if (Value.textEdit) Result["textEdit"] = *Value.textEdit; if (!Value.additionalTextEdits.empty()) { Result["additionalTextEdits"] = llvm::json::Array(Value.additionalTextEdits); } if (Value.deprecated) Result["deprecated"] = Value.deprecated; return std::move(Result); } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const CompletionItem &Value) { return Os << Value.label << " - " << toJSON(Value); } bool llvm::lsp::operator<(const CompletionItem &Lhs, const CompletionItem &Rhs) { return (Lhs.sortText.empty() ? Lhs.label : Lhs.sortText) < (Rhs.sortText.empty() ? Rhs.label : Rhs.sortText); } //===----------------------------------------------------------------------===// // CompletionList //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const CompletionList &Value) { return llvm::json::Object{ {"isIncomplete", Value.isIncomplete}, {"items", llvm::json::Array(Value.items)}, }; } //===----------------------------------------------------------------------===// // CompletionContext //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CompletionContext &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); int TriggerKind; if (!O || !O.map("triggerKind", TriggerKind) || !mapOptOrNull(Value, "triggerCharacter", Result.triggerCharacter, Path)) return false; Result.triggerKind = static_cast(TriggerKind); return true; } //===----------------------------------------------------------------------===// // CompletionParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CompletionParams &Result, llvm::json::Path Path) { if (!fromJSON(Value, static_cast(Result), Path)) return false; if (const llvm::json::Value *Context = Value.getAsObject()->get("context")) return fromJSON(*Context, Result.context, Path.field("context")); return true; } //===----------------------------------------------------------------------===// // ParameterInformation //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const ParameterInformation &Value) { assert((Value.labelOffsets || !Value.labelString.empty()) && "parameter information label is required"); llvm::json::Object Result; if (Value.labelOffsets) Result["label"] = llvm::json::Array( {Value.labelOffsets->first, Value.labelOffsets->second}); else Result["label"] = Value.labelString; if (!Value.documentation.empty()) Result["documentation"] = Value.documentation; return std::move(Result); } //===----------------------------------------------------------------------===// // SignatureInformation //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const SignatureInformation &Value) { assert(!Value.label.empty() && "signature information label is required"); llvm::json::Object Result{ {"label", Value.label}, {"parameters", llvm::json::Array(Value.parameters)}, }; if (!Value.documentation.empty()) Result["documentation"] = Value.documentation; return std::move(Result); } raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const SignatureInformation &Value) { return Os << Value.label << " - " << toJSON(Value); } //===----------------------------------------------------------------------===// // SignatureHelp //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const SignatureHelp &Value) { assert(Value.activeSignature >= 0 && "Unexpected negative value for number of active signatures."); assert(Value.activeParameter >= 0 && "Unexpected negative value for active parameter index"); return llvm::json::Object{ {"activeSignature", Value.activeSignature}, {"activeParameter", Value.activeParameter}, {"signatures", llvm::json::Array(Value.signatures)}, }; } //===----------------------------------------------------------------------===// // DocumentLinkParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DocumentLinkParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument); } //===----------------------------------------------------------------------===// // DocumentLink //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const DocumentLink &Value) { return llvm::json::Object{ {"range", Value.range}, {"target", Value.target}, }; } //===----------------------------------------------------------------------===// // InlayHintsParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, InlayHintsParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument) && O.map("range", Result.range); } //===----------------------------------------------------------------------===// // InlayHint //===----------------------------------------------------------------------===// llvm::json::Value llvm::lsp::toJSON(const InlayHint &Value) { return llvm::json::Object{{"position", Value.position}, {"kind", (int)Value.kind}, {"label", Value.label}, {"paddingLeft", Value.paddingLeft}, {"paddingRight", Value.paddingRight}}; } bool llvm::lsp::operator==(const InlayHint &Lhs, const InlayHint &Rhs) { return std::tie(Lhs.position, Lhs.kind, Lhs.label) == std::tie(Rhs.position, Rhs.kind, Rhs.label); } bool llvm::lsp::operator<(const InlayHint &Lhs, const InlayHint &Rhs) { return std::tie(Lhs.position, Lhs.kind, Lhs.label) < std::tie(Rhs.position, Rhs.kind, Rhs.label); } llvm::raw_ostream &llvm::lsp::operator<<(llvm::raw_ostream &Os, InlayHintKind Value) { switch (Value) { case InlayHintKind::Parameter: return Os << "parameter"; case InlayHintKind::Type: return Os << "type"; } llvm_unreachable("Unknown InlayHintKind"); } //===----------------------------------------------------------------------===// // CodeActionContext //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CodeActionContext &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); if (!O || !O.map("diagnostics", Result.diagnostics)) return false; O.map("only", Result.only); return true; } //===----------------------------------------------------------------------===// // CodeActionParams //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, CodeActionParams &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("textDocument", Result.textDocument) && O.map("range", Result.range) && O.map("context", Result.context); } //===----------------------------------------------------------------------===// // WorkspaceEdit //===----------------------------------------------------------------------===// bool llvm::lsp::fromJSON(const llvm::json::Value &Value, WorkspaceEdit &Result, llvm::json::Path Path) { llvm::json::ObjectMapper O(Value, Path); return O && O.map("changes", Result.changes); } llvm::json::Value llvm::lsp::toJSON(const WorkspaceEdit &Value) { llvm::json::Object FileChanges; for (auto &Change : Value.changes) FileChanges[Change.first] = llvm::json::Array(Change.second); return llvm::json::Object{{"changes", std::move(FileChanges)}}; } //===----------------------------------------------------------------------===// // CodeAction //===----------------------------------------------------------------------===// const llvm::StringLiteral CodeAction::kQuickFix = "quickfix"; const llvm::StringLiteral CodeAction::kRefactor = "refactor"; const llvm::StringLiteral CodeAction::kInfo = "info"; llvm::json::Value llvm::lsp::toJSON(const CodeAction &Value) { llvm::json::Object CodeAction{{"title", Value.title}}; if (Value.kind) CodeAction["kind"] = *Value.kind; if (Value.diagnostics) CodeAction["diagnostics"] = llvm::json::Array(*Value.diagnostics); if (Value.isPreferred) CodeAction["isPreferred"] = true; if (Value.edit) CodeAction["edit"] = *Value.edit; return std::move(CodeAction); }