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