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

source code of mlir/lib/Tools/lsp-server-support/Protocol.cpp