Bertik23 a3a25996b1
[LLVM][MLIR] Move LSP server support library from MLIR into LLVM (#157885)
This is a second PR on this patch (first #155572), that fixes the
linking problem for `flang-aarch64-dylib` test.

The SupportLSP library was made a component library.

---

This PR moves the generic Language Server Protocol (LSP) server support
code that was copied from clangd into MLIR, into the LLVM tree so it can
be reused by multiple subprojects.

Centralizing the generic LSP support in LLVM lowers the barrier to
building new LSP servers across the LLVM ecosystem and avoids each
subproject maintaining its own copy.

The code originated in clangd and was copied into MLIR for its LSP
server. MLIR had this code seperate to be reused by all of their LSP
server. This PR relocates the MLIR copy into LLVM as a shared component
into LLVM/Support. If this is not a suitable place, please suggest a
better one.

A follow up to this move could be deduplication with the original clangd
implementation and converge on a single shared LSP support library used
by clangd, MLIR, and future servers.
What changes

mlir/include/mlir/Tools/lsp-server-support/{Logging, Protocol,
Transport}.h moved to llvm/include/llvm/Support/LSP
mlir/lib/Tools/lsp-server-support/{Logging, Protocol, Transport}.cpp
moved to llvm/lib/Support/LSP

and their namespace was changed from mlir to llvm

I ran clang-tidy --fix and clang-format on the whole moved files (last
two commits), as they are basically new files and should hold up to the
code style used by LLVM.

MLIR LSP servers where updated to include these files from their new
location and account for the namespace change.

This PR is made as part of the LLVM IR LSP project
([RFC](https://discourse.llvm.org/t/rfc-ir-visualization-with-vs-code-extension-using-an-lsp-server/87773))
2025-09-11 17:17:52 +00:00

1044 lines
36 KiB
C++

//===--- 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 != '%') {
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<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);
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<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);
}