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 | |
28 | using namespace mlir; |
29 | using namespace mlir::lsp; |
30 | |
31 | // Helper that doesn't treat `null` and absent fields as failures. |
32 | template <typename T> |
33 | static bool mapOptOrNull(const llvm::json::Value ¶ms, |
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 | |
50 | char LSPError::ID; |
51 | |
52 | //===----------------------------------------------------------------------===// |
53 | // URIForFile |
54 | //===----------------------------------------------------------------------===// |
55 | |
56 | static bool isWindowsPath(StringRef path) { |
57 | return path.size() > 1 && llvm::isAlpha(C: path[0]) && path[1] == ':'; |
58 | } |
59 | |
60 | static bool isNetworkPath(StringRef path) { |
61 | return path.size() > 2 && path[0] == path[1] && |
62 | llvm::sys::path::is_separator(value: path[0]); |
63 | } |
64 | |
65 | static 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. |
89 | static 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. |
102 | static 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. |
121 | static 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. |
129 | static 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 | |
139 | static 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 | |
168 | static 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 | |
189 | static 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 | |
226 | llvm::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 | |
233 | llvm::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 | |
242 | StringRef URIForFile::scheme() const { return uri().split(Separator: ':').first; } |
243 | |
244 | void URIForFile::registerSupportedScheme(StringRef scheme) { |
245 | getSupportedSchemes().insert(key: scheme); |
246 | } |
247 | |
248 | bool 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 | |
263 | llvm::json::Value mlir::lsp::toJSON(const URIForFile &value) { |
264 | return value.uri(); |
265 | } |
266 | |
267 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const URIForFile &value) { |
268 | return os << value.uri(); |
269 | } |
270 | |
271 | //===----------------------------------------------------------------------===// |
272 | // ClientCapabilities |
273 | //===----------------------------------------------------------------------===// |
274 | |
275 | bool 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 | |
301 | bool 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 | |
316 | bool 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 | |
335 | bool 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 | |
352 | bool 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 | |
364 | llvm::json::Value mlir::lsp::toJSON(const TextDocumentIdentifier &value) { |
365 | return llvm::json::Object{{.K: "uri" , .V: value.uri}}; |
366 | } |
367 | |
368 | bool 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 | |
379 | llvm::json::Value |
380 | mlir::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 | |
387 | bool 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 | |
398 | bool 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 | |
405 | llvm::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 | |
412 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const Position &value) { |
413 | return os << value.line << ':' << value.character; |
414 | } |
415 | |
416 | //===----------------------------------------------------------------------===// |
417 | // Range |
418 | //===----------------------------------------------------------------------===// |
419 | |
420 | bool 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 | |
426 | llvm::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 | |
433 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const Range &value) { |
434 | return os << value.start << '-' << value.end; |
435 | } |
436 | |
437 | //===----------------------------------------------------------------------===// |
438 | // Location |
439 | //===----------------------------------------------------------------------===// |
440 | |
441 | bool 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 | |
447 | llvm::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 | |
454 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const Location &value) { |
455 | return os << value.range << '@' << value.uri; |
456 | } |
457 | |
458 | //===----------------------------------------------------------------------===// |
459 | // TextDocumentPositionParams |
460 | //===----------------------------------------------------------------------===// |
461 | |
462 | bool 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 | |
474 | bool 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 | |
480 | bool 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 | |
492 | bool 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 | |
503 | bool 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 | |
514 | LogicalResult |
515 | TextDocumentContentChangeEvent::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 | |
536 | LogicalResult 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 | |
544 | bool 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 | |
552 | bool 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 | |
564 | static 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 | |
574 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, MarkupKind kind) { |
575 | return os << toTextKind(kind); |
576 | } |
577 | |
578 | llvm::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 | |
592 | llvm::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 | |
603 | llvm::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 | |
620 | bool 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 | |
630 | bool 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 | |
638 | llvm::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 | |
649 | llvm::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 | |
664 | bool 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 | |
685 | llvm::json::Value mlir::lsp::toJSON(const PublishDiagnosticsParams ¶ms) { |
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 | |
697 | bool 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 | |
703 | llvm::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 | |
710 | raw_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 | |
720 | bool 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 | |
732 | CompletionItemKind 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 | |
754 | bool 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 | |
772 | llvm::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 | |
800 | raw_ostream &mlir::lsp::operator<<(raw_ostream &os, |
801 | const CompletionItem &value) { |
802 | return os << value.label << " - " << toJSON(value); |
803 | } |
804 | |
805 | bool 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 | |
815 | llvm::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 | |
826 | bool 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 | |
841 | bool 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 | |
854 | llvm::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 | |
872 | llvm::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 | |
883 | raw_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 | |
892 | llvm::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 | |
908 | bool 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 | |
918 | llvm::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 | |
929 | bool 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 | |
940 | llvm::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 | } |
947 | bool 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 | } |
951 | bool 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 | |
956 | llvm::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 | |
971 | bool 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 | |
984 | bool 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 | |
995 | bool 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 | |
1001 | llvm::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 | |
1012 | const llvm::StringLiteral CodeAction::kQuickFix = "quickfix" ; |
1013 | const llvm::StringLiteral CodeAction::kRefactor = "refactor" ; |
1014 | const llvm::StringLiteral CodeAction::kInfo = "info" ; |
1015 | |
1016 | llvm::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 | |