1040 lines · cpp
1//===--- Protocol.cpp - Language Server Protocol Implementation -----------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file contains the serialization code for the LSP structs.10//11//===----------------------------------------------------------------------===//12 13#include "llvm/Support/LSP/Protocol.h"14#include "llvm/ADT/StringExtras.h"15#include "llvm/ADT/StringSet.h"16#include "llvm/Support/ErrorHandling.h"17#include "llvm/Support/JSON.h"18#include "llvm/Support/MemoryBuffer.h"19#include "llvm/Support/Path.h"20#include "llvm/Support/raw_ostream.h"21 22using namespace llvm;23using namespace llvm::lsp;24 25// Helper that doesn't treat `null` and absent fields as failures.26template <typename T>27static bool mapOptOrNull(const llvm::json::Value &Params,28 llvm::StringLiteral Prop, T &Out,29 llvm::json::Path Path) {30 const llvm::json::Object *O = Params.getAsObject();31 assert(O);32 33 // Field is missing or null.34 auto *V = O->get(Prop);35 if (!V || V->getAsNull())36 return true;37 return fromJSON(*V, Out, Path.field(Prop));38}39 40//===----------------------------------------------------------------------===//41// LSPError42//===----------------------------------------------------------------------===//43 44char LSPError::ID;45 46//===----------------------------------------------------------------------===//47// URIForFile48//===----------------------------------------------------------------------===//49 50static bool isWindowsPath(StringRef Path) {51 return Path.size() > 1 && llvm::isAlpha(Path[0]) && Path[1] == ':';52}53 54static bool isNetworkPath(StringRef Path) {55 return Path.size() > 2 && Path[0] == Path[1] &&56 llvm::sys::path::is_separator(Path[0]);57}58 59static bool shouldEscapeInURI(unsigned char C) {60 // Unreserved characters.61 if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||62 (C >= '0' && C <= '9'))63 return false;64 65 switch (C) {66 case '-':67 case '_':68 case '.':69 case '~':70 // '/' is only reserved when parsing.71 case '/':72 // ':' is only reserved for relative URI paths, which we doesn't produce.73 case ':':74 return false;75 }76 return true;77}78 79/// Encodes a string according to percent-encoding.80/// - Unreserved characters are not escaped.81/// - Reserved characters always escaped with exceptions like '/'.82/// - All other characters are escaped.83static void percentEncode(StringRef Content, std::string &Out) {84 for (unsigned char C : Content) {85 if (shouldEscapeInURI(C)) {86 Out.push_back('%');87 Out.push_back(llvm::hexdigit(C / 16));88 Out.push_back(llvm::hexdigit(C % 16));89 } else {90 Out.push_back(C);91 }92 }93}94 95/// Decodes a string according to percent-encoding.96static std::string percentDecode(StringRef Content) {97 std::string Result;98 for (auto I = Content.begin(), E = Content.end(); I != E; ++I) {99 if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) &&100 llvm::isHexDigit(*(I + 2))) {101 Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2)));102 I += 2;103 } else {104 Result.push_back(*I);105 }106 }107 return Result;108}109 110/// Return the set containing the supported URI schemes.111static StringSet<> &getSupportedSchemes() {112 static StringSet<> Schemes({"file", "test"});113 return Schemes;114}115 116/// Returns true if the given scheme is structurally valid, i.e. it does not117/// contain any invalid scheme characters. This does not check that the scheme118/// is actually supported.119static bool isStructurallyValidScheme(StringRef Scheme) {120 if (Scheme.empty())121 return false;122 if (!llvm::isAlpha(Scheme[0]))123 return false;124 return llvm::all_of(llvm::drop_begin(Scheme), [](char C) {125 return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-';126 });127}128 129static llvm::Expected<std::string> uriFromAbsolutePath(StringRef AbsolutePath,130 StringRef Scheme) {131 std::string Body;132 StringRef Authority;133 StringRef Root = llvm::sys::path::root_name(AbsolutePath);134 if (isNetworkPath(Root)) {135 // Windows UNC paths e.g. \\server\share => file://server/share136 Authority = Root.drop_front(2);137 AbsolutePath.consume_front(Root);138 } else if (isWindowsPath(Root)) {139 // Windows paths e.g. X:\path => file:///X:/path140 Body = "/";141 }142 Body += llvm::sys::path::convert_to_slash(AbsolutePath);143 144 std::string Uri = Scheme.str() + ":";145 if (Authority.empty() && Body.empty())146 return Uri;147 148 // If authority if empty, we only print body if it starts with "/"; otherwise,149 // the URI is invalid.150 if (!Authority.empty() || StringRef(Body).starts_with("/")) {151 Uri.append("//");152 percentEncode(Authority, Uri);153 }154 percentEncode(Body, Uri);155 return Uri;156}157 158static llvm::Expected<std::string> getAbsolutePath(StringRef Authority,159 StringRef Body) {160 if (!Body.starts_with("/"))161 return llvm::createStringError(162 llvm::inconvertibleErrorCode(),163 "File scheme: expect body to be an absolute path starting "164 "with '/': " +165 Body);166 SmallString<128> Path;167 if (!Authority.empty()) {168 // Windows UNC paths e.g. file://server/share => \\server\share169 ("//" + Authority).toVector(Path);170 } else if (isWindowsPath(Body.substr(1))) {171 // Windows paths e.g. file:///X:/path => X:\path172 Body.consume_front("/");173 }174 Path.append(Body);175 llvm::sys::path::native(Path);176 return std::string(Path);177}178 179static llvm::Expected<std::string> parseFilePathFromURI(StringRef OrigUri) {180 StringRef Uri = OrigUri;181 182 // Decode the scheme of the URI.183 size_t Pos = Uri.find(':');184 if (Pos == StringRef::npos)185 return llvm::createStringError(llvm::inconvertibleErrorCode(),186 "Scheme must be provided in URI: " +187 OrigUri);188 StringRef SchemeStr = Uri.substr(0, Pos);189 std::string UriScheme = percentDecode(SchemeStr);190 if (!isStructurallyValidScheme(UriScheme))191 return llvm::createStringError(llvm::inconvertibleErrorCode(),192 "Invalid scheme: " + SchemeStr +193 " (decoded: " + UriScheme + ")");194 Uri = Uri.substr(Pos + 1);195 196 // Decode the authority of the URI.197 std::string UriAuthority;198 if (Uri.consume_front("//")) {199 Pos = Uri.find('/');200 UriAuthority = percentDecode(Uri.substr(0, Pos));201 Uri = Uri.substr(Pos);202 }203 204 // Decode the body of the URI.205 std::string UriBody = percentDecode(Uri);206 207 // Compute the absolute path for this uri.208 if (!getSupportedSchemes().contains(UriScheme)) {209 return llvm::createStringError(llvm::inconvertibleErrorCode(),210 "unsupported URI scheme `" + UriScheme +211 "' for workspace files");212 }213 return getAbsolutePath(UriAuthority, UriBody);214}215 216llvm::Expected<URIForFile> URIForFile::fromURI(StringRef Uri) {217 llvm::Expected<std::string> FilePath = parseFilePathFromURI(Uri);218 if (!FilePath)219 return FilePath.takeError();220 return URIForFile(std::move(*FilePath), Uri.str());221}222 223llvm::Expected<URIForFile> URIForFile::fromFile(StringRef AbsoluteFilepath,224 StringRef Scheme) {225 llvm::Expected<std::string> Uri =226 uriFromAbsolutePath(AbsoluteFilepath, Scheme);227 if (!Uri)228 return Uri.takeError();229 return fromURI(*Uri);230}231 232StringRef URIForFile::scheme() const { return uri().split(':').first; }233 234void URIForFile::registerSupportedScheme(StringRef Scheme) {235 getSupportedSchemes().insert(Scheme);236}237 238bool llvm::lsp::fromJSON(const llvm::json::Value &Value, URIForFile &Result,239 llvm::json::Path Path) {240 if (std::optional<StringRef> Str = Value.getAsString()) {241 llvm::Expected<URIForFile> ExpectedUri = URIForFile::fromURI(*Str);242 if (!ExpectedUri) {243 Path.report("unresolvable URI");244 consumeError(ExpectedUri.takeError());245 return false;246 }247 Result = std::move(*ExpectedUri);248 return true;249 }250 return false;251}252 253llvm::json::Value llvm::lsp::toJSON(const URIForFile &Value) {254 return Value.uri();255}256 257raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const URIForFile &Value) {258 return Os << Value.uri();259}260 261//===----------------------------------------------------------------------===//262// ClientCapabilities263//===----------------------------------------------------------------------===//264 265bool llvm::lsp::fromJSON(const llvm::json::Value &Value,266 ClientCapabilities &Result, llvm::json::Path Path) {267 const llvm::json::Object *O = Value.getAsObject();268 if (!O) {269 Path.report("expected object");270 return false;271 }272 if (const llvm::json::Object *TextDocument = O->getObject("textDocument")) {273 if (const llvm::json::Object *DocumentSymbol =274 TextDocument->getObject("documentSymbol")) {275 if (std::optional<bool> HierarchicalSupport =276 DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))277 Result.hierarchicalDocumentSymbol = *HierarchicalSupport;278 }279 if (auto *CodeAction = TextDocument->getObject("codeAction")) {280 if (CodeAction->getObject("codeActionLiteralSupport"))281 Result.codeActionStructure = true;282 }283 }284 if (auto *Window = O->getObject("window")) {285 if (std::optional<bool> WorkDoneProgressSupport =286 Window->getBoolean("workDoneProgress"))287 Result.workDoneProgress = *WorkDoneProgressSupport;288 }289 return true;290}291 292//===----------------------------------------------------------------------===//293// ClientInfo294//===----------------------------------------------------------------------===//295 296bool llvm::lsp::fromJSON(const llvm::json::Value &Value, ClientInfo &Result,297 llvm::json::Path Path) {298 llvm::json::ObjectMapper O(Value, Path);299 if (!O || !O.map("name", Result.name))300 return false;301 302 // Don't fail if we can't parse version.303 O.map("version", Result.version);304 return true;305}306 307//===----------------------------------------------------------------------===//308// InitializeParams309//===----------------------------------------------------------------------===//310 311bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TraceLevel &Result,312 llvm::json::Path Path) {313 if (std::optional<StringRef> Str = Value.getAsString()) {314 if (*Str == "off") {315 Result = TraceLevel::Off;316 return true;317 }318 if (*Str == "messages") {319 Result = TraceLevel::Messages;320 return true;321 }322 if (*Str == "verbose") {323 Result = TraceLevel::Verbose;324 return true;325 }326 }327 return false;328}329 330bool llvm::lsp::fromJSON(const llvm::json::Value &Value,331 InitializeParams &Result, llvm::json::Path Path) {332 llvm::json::ObjectMapper O(Value, Path);333 if (!O)334 return false;335 // We deliberately don't fail if we can't parse individual fields.336 O.map("capabilities", Result.capabilities);337 O.map("trace", Result.trace);338 mapOptOrNull(Value, "clientInfo", Result.clientInfo, Path);339 340 return true;341}342 343//===----------------------------------------------------------------------===//344// TextDocumentItem345//===----------------------------------------------------------------------===//346 347bool llvm::lsp::fromJSON(const llvm::json::Value &Value,348 TextDocumentItem &Result, llvm::json::Path Path) {349 llvm::json::ObjectMapper O(Value, Path);350 return O && O.map("uri", Result.uri) &&351 O.map("languageId", Result.languageId) && O.map("text", Result.text) &&352 O.map("version", Result.version);353}354 355//===----------------------------------------------------------------------===//356// TextDocumentIdentifier357//===----------------------------------------------------------------------===//358 359llvm::json::Value llvm::lsp::toJSON(const TextDocumentIdentifier &Value) {360 return llvm::json::Object{{"uri", Value.uri}};361}362 363bool llvm::lsp::fromJSON(const llvm::json::Value &Value,364 TextDocumentIdentifier &Result,365 llvm::json::Path Path) {366 llvm::json::ObjectMapper O(Value, Path);367 return O && O.map("uri", Result.uri);368}369 370//===----------------------------------------------------------------------===//371// VersionedTextDocumentIdentifier372//===----------------------------------------------------------------------===//373 374llvm::json::Value375llvm::lsp::toJSON(const VersionedTextDocumentIdentifier &Value) {376 return llvm::json::Object{377 {"uri", Value.uri},378 {"version", Value.version},379 };380}381 382bool llvm::lsp::fromJSON(const llvm::json::Value &Value,383 VersionedTextDocumentIdentifier &Result,384 llvm::json::Path Path) {385 llvm::json::ObjectMapper O(Value, Path);386 return O && O.map("uri", Result.uri) && O.map("version", Result.version);387}388 389//===----------------------------------------------------------------------===//390// Position391//===----------------------------------------------------------------------===//392 393bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Position &Result,394 llvm::json::Path Path) {395 llvm::json::ObjectMapper O(Value, Path);396 return O && O.map("line", Result.line) &&397 O.map("character", Result.character);398}399 400llvm::json::Value llvm::lsp::toJSON(const Position &Value) {401 return llvm::json::Object{402 {"line", Value.line},403 {"character", Value.character},404 };405}406 407raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Position &Value) {408 return Os << Value.line << ':' << Value.character;409}410 411//===----------------------------------------------------------------------===//412// Range413//===----------------------------------------------------------------------===//414 415bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Range &Result,416 llvm::json::Path Path) {417 llvm::json::ObjectMapper O(Value, Path);418 return O && O.map("start", Result.start) && O.map("end", Result.end);419}420 421llvm::json::Value llvm::lsp::toJSON(const Range &Value) {422 return llvm::json::Object{423 {"start", Value.start},424 {"end", Value.end},425 };426}427 428raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Range &Value) {429 return Os << Value.start << '-' << Value.end;430}431 432//===----------------------------------------------------------------------===//433// Location434//===----------------------------------------------------------------------===//435 436bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Location &Result,437 llvm::json::Path Path) {438 llvm::json::ObjectMapper O(Value, Path);439 return O && O.map("uri", Result.uri) && O.map("range", Result.range);440}441 442llvm::json::Value llvm::lsp::toJSON(const Location &Value) {443 return llvm::json::Object{444 {"uri", Value.uri},445 {"range", Value.range},446 };447}448 449raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const Location &Value) {450 return Os << Value.range << '@' << Value.uri;451}452 453//===----------------------------------------------------------------------===//454// TextDocumentPositionParams455//===----------------------------------------------------------------------===//456 457bool llvm::lsp::fromJSON(const llvm::json::Value &Value,458 TextDocumentPositionParams &Result,459 llvm::json::Path Path) {460 llvm::json::ObjectMapper O(Value, Path);461 return O && O.map("textDocument", Result.textDocument) &&462 O.map("position", Result.position);463}464 465//===----------------------------------------------------------------------===//466// ReferenceParams467//===----------------------------------------------------------------------===//468 469bool llvm::lsp::fromJSON(const llvm::json::Value &Value,470 ReferenceContext &Result, llvm::json::Path Path) {471 llvm::json::ObjectMapper O(Value, Path);472 return O && O.mapOptional("includeDeclaration", Result.includeDeclaration);473}474 475bool llvm::lsp::fromJSON(const llvm::json::Value &Value,476 ReferenceParams &Result, llvm::json::Path Path) {477 TextDocumentPositionParams &Base = Result;478 llvm::json::ObjectMapper O(Value, Path);479 return fromJSON(Value, Base, Path) && O &&480 O.mapOptional("context", Result.context);481}482 483//===----------------------------------------------------------------------===//484// DidOpenTextDocumentParams485//===----------------------------------------------------------------------===//486 487bool llvm::lsp::fromJSON(const llvm::json::Value &Value,488 DidOpenTextDocumentParams &Result,489 llvm::json::Path Path) {490 llvm::json::ObjectMapper O(Value, Path);491 return O && O.map("textDocument", Result.textDocument);492}493 494//===----------------------------------------------------------------------===//495// DidCloseTextDocumentParams496//===----------------------------------------------------------------------===//497 498bool llvm::lsp::fromJSON(const llvm::json::Value &Value,499 DidCloseTextDocumentParams &Result,500 llvm::json::Path Path) {501 llvm::json::ObjectMapper O(Value, Path);502 return O && O.map("textDocument", Result.textDocument);503}504 505//===----------------------------------------------------------------------===//506// DidChangeTextDocumentParams507//===----------------------------------------------------------------------===//508 509LogicalResult510TextDocumentContentChangeEvent::applyTo(std::string &Contents) const {511 // If there is no range, the full document changed.512 if (!range) {513 Contents = text;514 return success();515 }516 517 // Try to map the replacement range to the content.518 llvm::SourceMgr TmpScrMgr;519 TmpScrMgr.AddNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(Contents),520 SMLoc());521 SMRange RangeLoc = range->getAsSMRange(TmpScrMgr);522 if (!RangeLoc.isValid())523 return failure();524 525 Contents.replace(RangeLoc.Start.getPointer() - Contents.data(),526 RangeLoc.End.getPointer() - RangeLoc.Start.getPointer(),527 text);528 return success();529}530 531LogicalResult TextDocumentContentChangeEvent::applyTo(532 ArrayRef<TextDocumentContentChangeEvent> Changes, std::string &Contents) {533 for (const auto &Change : Changes)534 if (failed(Change.applyTo(Contents)))535 return failure();536 return success();537}538 539bool llvm::lsp::fromJSON(const llvm::json::Value &Value,540 TextDocumentContentChangeEvent &Result,541 llvm::json::Path Path) {542 llvm::json::ObjectMapper O(Value, Path);543 return O && O.map("range", Result.range) &&544 O.map("rangeLength", Result.rangeLength) && O.map("text", Result.text);545}546 547bool llvm::lsp::fromJSON(const llvm::json::Value &Value,548 DidChangeTextDocumentParams &Result,549 llvm::json::Path Path) {550 llvm::json::ObjectMapper O(Value, Path);551 return O && O.map("textDocument", Result.textDocument) &&552 O.map("contentChanges", Result.contentChanges);553}554 555//===----------------------------------------------------------------------===//556// MarkupContent557//===----------------------------------------------------------------------===//558 559static llvm::StringRef toTextKind(MarkupKind Kind) {560 switch (Kind) {561 case MarkupKind::PlainText:562 return "plaintext";563 case MarkupKind::Markdown:564 return "markdown";565 }566 llvm_unreachable("Invalid MarkupKind");567}568 569raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, MarkupKind Kind) {570 return Os << toTextKind(Kind);571}572 573llvm::json::Value llvm::lsp::toJSON(const MarkupContent &Mc) {574 if (Mc.value.empty())575 return nullptr;576 577 return llvm::json::Object{578 {"kind", toTextKind(Mc.kind)},579 {"value", Mc.value},580 };581}582 583//===----------------------------------------------------------------------===//584// Hover585//===----------------------------------------------------------------------===//586 587llvm::json::Value llvm::lsp::toJSON(const Hover &Hover) {588 llvm::json::Object Result{{"contents", toJSON(Hover.contents)}};589 if (Hover.range)590 Result["range"] = toJSON(*Hover.range);591 return std::move(Result);592}593 594//===----------------------------------------------------------------------===//595// DocumentSymbol596//===----------------------------------------------------------------------===//597 598llvm::json::Value llvm::lsp::toJSON(const DocumentSymbol &Symbol) {599 llvm::json::Object Result{{"name", Symbol.name},600 {"kind", static_cast<int>(Symbol.kind)},601 {"range", Symbol.range},602 {"selectionRange", Symbol.selectionRange}};603 604 if (!Symbol.detail.empty())605 Result["detail"] = Symbol.detail;606 if (!Symbol.children.empty())607 Result["children"] = Symbol.children;608 return std::move(Result);609}610 611//===----------------------------------------------------------------------===//612// DocumentSymbolParams613//===----------------------------------------------------------------------===//614 615bool llvm::lsp::fromJSON(const llvm::json::Value &Value,616 DocumentSymbolParams &Result, llvm::json::Path Path) {617 llvm::json::ObjectMapper O(Value, Path);618 return O && O.map("textDocument", Result.textDocument);619}620 621//===----------------------------------------------------------------------===//622// DiagnosticRelatedInformation623//===----------------------------------------------------------------------===//624 625bool llvm::lsp::fromJSON(const llvm::json::Value &Value,626 DiagnosticRelatedInformation &Result,627 llvm::json::Path Path) {628 llvm::json::ObjectMapper O(Value, Path);629 return O && O.map("location", Result.location) &&630 O.map("message", Result.message);631}632 633llvm::json::Value llvm::lsp::toJSON(const DiagnosticRelatedInformation &Info) {634 return llvm::json::Object{635 {"location", Info.location},636 {"message", Info.message},637 };638}639 640//===----------------------------------------------------------------------===//641// Diagnostic642//===----------------------------------------------------------------------===//643 644llvm::json::Value llvm::lsp::toJSON(DiagnosticTag Tag) {645 return static_cast<int>(Tag);646}647 648bool llvm::lsp::fromJSON(const llvm::json::Value &Value, DiagnosticTag &Result,649 llvm::json::Path Path) {650 if (std::optional<int64_t> I = Value.getAsInteger()) {651 Result = (DiagnosticTag)*I;652 return true;653 }654 655 return false;656}657 658llvm::json::Value llvm::lsp::toJSON(const Diagnostic &Diag) {659 llvm::json::Object Result{660 {"range", Diag.range},661 {"severity", (int)Diag.severity},662 {"message", Diag.message},663 };664 if (Diag.category)665 Result["category"] = *Diag.category;666 if (!Diag.source.empty())667 Result["source"] = Diag.source;668 if (Diag.relatedInformation)669 Result["relatedInformation"] = *Diag.relatedInformation;670 if (!Diag.tags.empty())671 Result["tags"] = Diag.tags;672 return std::move(Result);673}674 675bool llvm::lsp::fromJSON(const llvm::json::Value &Value, Diagnostic &Result,676 llvm::json::Path Path) {677 llvm::json::ObjectMapper O(Value, Path);678 if (!O)679 return false;680 int Severity = 0;681 if (!mapOptOrNull(Value, "severity", Severity, Path))682 return false;683 Result.severity = (DiagnosticSeverity)Severity;684 685 return O.map("range", Result.range) && O.map("message", Result.message) &&686 mapOptOrNull(Value, "category", Result.category, Path) &&687 mapOptOrNull(Value, "source", Result.source, Path) &&688 mapOptOrNull(Value, "relatedInformation", Result.relatedInformation,689 Path) &&690 mapOptOrNull(Value, "tags", Result.tags, Path);691}692 693//===----------------------------------------------------------------------===//694// PublishDiagnosticsParams695//===----------------------------------------------------------------------===//696 697llvm::json::Value llvm::lsp::toJSON(const PublishDiagnosticsParams &Params) {698 return llvm::json::Object{699 {"uri", Params.uri},700 {"diagnostics", Params.diagnostics},701 {"version", Params.version},702 };703}704 705//===----------------------------------------------------------------------===//706// TextEdit707//===----------------------------------------------------------------------===//708 709bool llvm::lsp::fromJSON(const llvm::json::Value &Value, TextEdit &Result,710 llvm::json::Path Path) {711 llvm::json::ObjectMapper O(Value, Path);712 return O && O.map("range", Result.range) && O.map("newText", Result.newText);713}714 715llvm::json::Value llvm::lsp::toJSON(const TextEdit &Value) {716 return llvm::json::Object{717 {"range", Value.range},718 {"newText", Value.newText},719 };720}721 722raw_ostream &llvm::lsp::operator<<(raw_ostream &Os, const TextEdit &Value) {723 Os << Value.range << " => \"";724 llvm::printEscapedString(Value.newText, Os);725 return Os << '"';726}727 728//===----------------------------------------------------------------------===//729// CompletionItemKind730//===----------------------------------------------------------------------===//731 732bool llvm::lsp::fromJSON(const llvm::json::Value &Value,733 CompletionItemKind &Result, llvm::json::Path Path) {734 if (std::optional<int64_t> IntValue = Value.getAsInteger()) {735 if (*IntValue < static_cast<int>(CompletionItemKind::Text) ||736 *IntValue > static_cast<int>(CompletionItemKind::TypeParameter))737 return false;738 Result = static_cast<CompletionItemKind>(*IntValue);739 return true;740 }741 return false;742}743 744CompletionItemKind llvm::lsp::adjustKindToCapability(745 CompletionItemKind Kind,746 CompletionItemKindBitset &SupportedCompletionItemKinds) {747 size_t KindVal = static_cast<size_t>(Kind);748 if (KindVal >= kCompletionItemKindMin &&749 KindVal <= SupportedCompletionItemKinds.size() &&750 SupportedCompletionItemKinds[KindVal])751 return Kind;752 753 // Provide some fall backs for common kinds that are close enough.754 switch (Kind) {755 case CompletionItemKind::Folder:756 return CompletionItemKind::File;757 case CompletionItemKind::EnumMember:758 return CompletionItemKind::Enum;759 case CompletionItemKind::Struct:760 return CompletionItemKind::Class;761 default:762 return CompletionItemKind::Text;763 }764}765 766bool llvm::lsp::fromJSON(const llvm::json::Value &Value,767 CompletionItemKindBitset &Result,768 llvm::json::Path Path) {769 if (const llvm::json::Array *ArrayValue = Value.getAsArray()) {770 for (size_t I = 0, E = ArrayValue->size(); I < E; ++I) {771 CompletionItemKind KindOut;772 if (fromJSON((*ArrayValue)[I], KindOut, Path.index(I)))773 Result.set(size_t(KindOut));774 }775 return true;776 }777 return false;778}779 780//===----------------------------------------------------------------------===//781// CompletionItem782//===----------------------------------------------------------------------===//783 784llvm::json::Value llvm::lsp::toJSON(const CompletionItem &Value) {785 assert(!Value.label.empty() && "completion item label is required");786 llvm::json::Object Result{{"label", Value.label}};787 if (Value.kind != CompletionItemKind::Missing)788 Result["kind"] = static_cast<int>(Value.kind);789 if (!Value.detail.empty())790 Result["detail"] = Value.detail;791 if (Value.documentation)792 Result["documentation"] = Value.documentation;793 if (!Value.sortText.empty())794 Result["sortText"] = Value.sortText;795 if (!Value.filterText.empty())796 Result["filterText"] = Value.filterText;797 if (!Value.insertText.empty())798 Result["insertText"] = Value.insertText;799 if (Value.insertTextFormat != InsertTextFormat::Missing)800 Result["insertTextFormat"] = static_cast<int>(Value.insertTextFormat);801 if (Value.textEdit)802 Result["textEdit"] = *Value.textEdit;803 if (!Value.additionalTextEdits.empty()) {804 Result["additionalTextEdits"] =805 llvm::json::Array(Value.additionalTextEdits);806 }807 if (Value.deprecated)808 Result["deprecated"] = Value.deprecated;809 return std::move(Result);810}811 812raw_ostream &llvm::lsp::operator<<(raw_ostream &Os,813 const CompletionItem &Value) {814 return Os << Value.label << " - " << toJSON(Value);815}816 817bool llvm::lsp::operator<(const CompletionItem &Lhs,818 const CompletionItem &Rhs) {819 return (Lhs.sortText.empty() ? Lhs.label : Lhs.sortText) <820 (Rhs.sortText.empty() ? Rhs.label : Rhs.sortText);821}822 823//===----------------------------------------------------------------------===//824// CompletionList825//===----------------------------------------------------------------------===//826 827llvm::json::Value llvm::lsp::toJSON(const CompletionList &Value) {828 return llvm::json::Object{829 {"isIncomplete", Value.isIncomplete},830 {"items", llvm::json::Array(Value.items)},831 };832}833 834//===----------------------------------------------------------------------===//835// CompletionContext836//===----------------------------------------------------------------------===//837 838bool llvm::lsp::fromJSON(const llvm::json::Value &Value,839 CompletionContext &Result, llvm::json::Path Path) {840 llvm::json::ObjectMapper O(Value, Path);841 int TriggerKind;842 if (!O || !O.map("triggerKind", TriggerKind) ||843 !mapOptOrNull(Value, "triggerCharacter", Result.triggerCharacter, Path))844 return false;845 Result.triggerKind = static_cast<CompletionTriggerKind>(TriggerKind);846 return true;847}848 849//===----------------------------------------------------------------------===//850// CompletionParams851//===----------------------------------------------------------------------===//852 853bool llvm::lsp::fromJSON(const llvm::json::Value &Value,854 CompletionParams &Result, llvm::json::Path Path) {855 if (!fromJSON(Value, static_cast<TextDocumentPositionParams &>(Result), Path))856 return false;857 if (const llvm::json::Value *Context = Value.getAsObject()->get("context"))858 return fromJSON(*Context, Result.context, Path.field("context"));859 return true;860}861 862//===----------------------------------------------------------------------===//863// ParameterInformation864//===----------------------------------------------------------------------===//865 866llvm::json::Value llvm::lsp::toJSON(const ParameterInformation &Value) {867 assert((Value.labelOffsets || !Value.labelString.empty()) &&868 "parameter information label is required");869 llvm::json::Object Result;870 if (Value.labelOffsets)871 Result["label"] = llvm::json::Array(872 {Value.labelOffsets->first, Value.labelOffsets->second});873 else874 Result["label"] = Value.labelString;875 if (!Value.documentation.empty())876 Result["documentation"] = Value.documentation;877 return std::move(Result);878}879 880//===----------------------------------------------------------------------===//881// SignatureInformation882//===----------------------------------------------------------------------===//883 884llvm::json::Value llvm::lsp::toJSON(const SignatureInformation &Value) {885 assert(!Value.label.empty() && "signature information label is required");886 llvm::json::Object Result{887 {"label", Value.label},888 {"parameters", llvm::json::Array(Value.parameters)},889 };890 if (!Value.documentation.empty())891 Result["documentation"] = Value.documentation;892 return std::move(Result);893}894 895raw_ostream &llvm::lsp::operator<<(raw_ostream &Os,896 const SignatureInformation &Value) {897 return Os << Value.label << " - " << toJSON(Value);898}899 900//===----------------------------------------------------------------------===//901// SignatureHelp902//===----------------------------------------------------------------------===//903 904llvm::json::Value llvm::lsp::toJSON(const SignatureHelp &Value) {905 assert(Value.activeSignature >= 0 &&906 "Unexpected negative value for number of active signatures.");907 assert(Value.activeParameter >= 0 &&908 "Unexpected negative value for active parameter index");909 return llvm::json::Object{910 {"activeSignature", Value.activeSignature},911 {"activeParameter", Value.activeParameter},912 {"signatures", llvm::json::Array(Value.signatures)},913 };914}915 916//===----------------------------------------------------------------------===//917// DocumentLinkParams918//===----------------------------------------------------------------------===//919 920bool llvm::lsp::fromJSON(const llvm::json::Value &Value,921 DocumentLinkParams &Result, llvm::json::Path Path) {922 llvm::json::ObjectMapper O(Value, Path);923 return O && O.map("textDocument", Result.textDocument);924}925 926//===----------------------------------------------------------------------===//927// DocumentLink928//===----------------------------------------------------------------------===//929 930llvm::json::Value llvm::lsp::toJSON(const DocumentLink &Value) {931 return llvm::json::Object{932 {"range", Value.range},933 {"target", Value.target},934 };935}936 937//===----------------------------------------------------------------------===//938// InlayHintsParams939//===----------------------------------------------------------------------===//940 941bool llvm::lsp::fromJSON(const llvm::json::Value &Value,942 InlayHintsParams &Result, llvm::json::Path Path) {943 llvm::json::ObjectMapper O(Value, Path);944 return O && O.map("textDocument", Result.textDocument) &&945 O.map("range", Result.range);946}947 948//===----------------------------------------------------------------------===//949// InlayHint950//===----------------------------------------------------------------------===//951 952llvm::json::Value llvm::lsp::toJSON(const InlayHint &Value) {953 return llvm::json::Object{{"position", Value.position},954 {"kind", (int)Value.kind},955 {"label", Value.label},956 {"paddingLeft", Value.paddingLeft},957 {"paddingRight", Value.paddingRight}};958}959bool llvm::lsp::operator==(const InlayHint &Lhs, const InlayHint &Rhs) {960 return std::tie(Lhs.position, Lhs.kind, Lhs.label) ==961 std::tie(Rhs.position, Rhs.kind, Rhs.label);962}963bool llvm::lsp::operator<(const InlayHint &Lhs, const InlayHint &Rhs) {964 return std::tie(Lhs.position, Lhs.kind, Lhs.label) <965 std::tie(Rhs.position, Rhs.kind, Rhs.label);966}967 968llvm::raw_ostream &llvm::lsp::operator<<(llvm::raw_ostream &Os,969 InlayHintKind Value) {970 switch (Value) {971 case InlayHintKind::Parameter:972 return Os << "parameter";973 case InlayHintKind::Type:974 return Os << "type";975 }976 llvm_unreachable("Unknown InlayHintKind");977}978 979//===----------------------------------------------------------------------===//980// CodeActionContext981//===----------------------------------------------------------------------===//982 983bool llvm::lsp::fromJSON(const llvm::json::Value &Value,984 CodeActionContext &Result, llvm::json::Path Path) {985 llvm::json::ObjectMapper O(Value, Path);986 if (!O || !O.map("diagnostics", Result.diagnostics))987 return false;988 O.map("only", Result.only);989 return true;990}991 992//===----------------------------------------------------------------------===//993// CodeActionParams994//===----------------------------------------------------------------------===//995 996bool llvm::lsp::fromJSON(const llvm::json::Value &Value,997 CodeActionParams &Result, llvm::json::Path Path) {998 llvm::json::ObjectMapper O(Value, Path);999 return O && O.map("textDocument", Result.textDocument) &&1000 O.map("range", Result.range) && O.map("context", Result.context);1001}1002 1003//===----------------------------------------------------------------------===//1004// WorkspaceEdit1005//===----------------------------------------------------------------------===//1006 1007bool llvm::lsp::fromJSON(const llvm::json::Value &Value, WorkspaceEdit &Result,1008 llvm::json::Path Path) {1009 llvm::json::ObjectMapper O(Value, Path);1010 return O && O.map("changes", Result.changes);1011}1012 1013llvm::json::Value llvm::lsp::toJSON(const WorkspaceEdit &Value) {1014 llvm::json::Object FileChanges;1015 for (auto &Change : Value.changes)1016 FileChanges[Change.first] = llvm::json::Array(Change.second);1017 return llvm::json::Object{{"changes", std::move(FileChanges)}};1018}1019 1020//===----------------------------------------------------------------------===//1021// CodeAction1022//===----------------------------------------------------------------------===//1023 1024const llvm::StringLiteral CodeAction::kQuickFix = "quickfix";1025const llvm::StringLiteral CodeAction::kRefactor = "refactor";1026const llvm::StringLiteral CodeAction::kInfo = "info";1027 1028llvm::json::Value llvm::lsp::toJSON(const CodeAction &Value) {1029 llvm::json::Object CodeAction{{"title", Value.title}};1030 if (Value.kind)1031 CodeAction["kind"] = *Value.kind;1032 if (Value.diagnostics)1033 CodeAction["diagnostics"] = llvm::json::Array(*Value.diagnostics);1034 if (Value.isPreferred)1035 CodeAction["isPreferred"] = true;1036 if (Value.edit)1037 CodeAction["edit"] = *Value.edit;1038 return std::move(CodeAction);1039}1040