| //===--- 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 <typename T> | |
| 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 == '%' && 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<std::string> 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<std::string> 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<std::string> 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> URIForFile::fromURI(StringRef Uri) { | |
| llvm::Expected<std::string> FilePath = parseFilePathFromURI(Uri); | |
| if (!FilePath) | |
| return FilePath.takeError(); | |
| return URIForFile(std::move(*FilePath), Uri.str()); | |
| } | |
| llvm::Expected<URIForFile> URIForFile::fromFile(StringRef AbsoluteFilepath, | |
| StringRef Scheme) { | |
| llvm::Expected<std::string> 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<StringRef> Str = Value.getAsString()) { | |
| llvm::Expected<URIForFile> 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<bool> 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<bool> 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<StringRef> 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); | |
| O.map("rootUri", Result.rootUri); | |
| O.map("rootPath", Result.rootPath); | |
| 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); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DidSaveTextDocumentParams | |
| //===----------------------------------------------------------------------===// | |
| bool llvm::lsp::fromJSON(const llvm::json::Value &Params, | |
| DidSaveTextDocumentParams &R, llvm::json::Path P) { | |
| llvm::json::ObjectMapper O(Params, P); | |
| return O && O.map("textDocument", R.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<TextDocumentContentChangeEvent> 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<int>(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<int>(Tag); | |
| } | |
| bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DiagnosticTag &Result, | |
| llvm::json::Path Path) { | |
| if (std::optional<int64_t> 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<int64_t> IntValue = Value.getAsInteger()) { | |
| if (*IntValue < static_cast<int>(CompletionItemKind::Text) || | |
| *IntValue > static_cast<int>(CompletionItemKind::TypeParameter)) | |
| return false; | |
| Result = static_cast<CompletionItemKind>(*IntValue); | |
| return true; | |
| } | |
| return false; | |
| } | |
| CompletionItemKind llvm::lsp::adjustKindToCapability( | |
| CompletionItemKind Kind, | |
| CompletionItemKindBitset &SupportedCompletionItemKinds) { | |
| size_t KindVal = static_cast<size_t>(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<int>(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<int>(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<CompletionTriggerKind>(TriggerKind); | |
| return true; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CompletionParams | |
| //===----------------------------------------------------------------------===// | |
| bool llvm::lsp::fromJSON(const llvm::json::Value &Value, | |
| CompletionParams &Result, llvm::json::Path Path) { | |
| if (!fromJSON(Value, static_cast<TextDocumentPositionParams &>(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); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShowMessageParams | |
| //===----------------------------------------------------------------------===// | |
| llvm::json::Value llvm::lsp::toJSON(const ShowMessageParams &Params) { | |
| auto Out = llvm::json::Object{ | |
| {"type", static_cast<int>(Params.type)}, | |
| {"message", Params.message}, | |
| }; | |
| if (Params.actions) | |
| Out["actions"] = *Params.actions; | |
| return Out; | |
| } | |
| llvm::json::Value llvm::lsp::toJSON(const MessageActionItem &Params) { | |
| return llvm::json::Object{{"title", Params.title}}; | |
| } |