MLIR  19.0.0git
Protocol.cpp
Go to the documentation of this file.
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 
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 &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(prop);
41  if (!v || v->getAsNull())
42  return true;
43  return fromJSON(*v, out, path.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(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(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('%');
93  out.push_back(llvm::hexdigit(c / 16));
94  out.push_back(llvm::hexdigit(c % 16));
95  } else {
96  out.push_back(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(*(i + 1)) &&
110  llvm::isHexDigit(*(i + 2))) {
111  result.push_back(llvm::hexFromNibbles(*(i + 1), *(i + 2)));
112  i += 2;
113  } else {
114  result.push_back(*i);
115  }
116  }
117  return result;
118 }
119 
120 /// Return the set containing the supported URI schemes.
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(scheme[0]))
133  return false;
134  return llvm::all_of(llvm::drop_begin(scheme), [](char c) {
135  return llvm::isAlnum(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(absolutePath);
144  if (isNetworkPath(root)) {
145  // Windows UNC paths e.g. \\server\share => file://server/share
146  authority = root.drop_front(2);
147  absolutePath.consume_front(root);
148  } else if (isWindowsPath(root)) {
149  // Windows paths e.g. X:\path => file:///X:/path
150  body = "/";
151  }
152  body += llvm::sys::path::convert_to_slash(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("/")) {
161  uri.append("//");
162  percentEncode(authority, uri);
163  }
164  percentEncode(body, uri);
165  return uri;
166 }
167 
168 static llvm::Expected<std::string> getAbsolutePath(StringRef authority,
169  StringRef body) {
170  if (!body.starts_with("/"))
171  return llvm::createStringError(
172  llvm::inconvertibleErrorCode(),
173  "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(path);
180  } else if (isWindowsPath(body.substr(1))) {
181  // Windows paths e.g. file:///X:/path => X:\path
182  body.consume_front("/");
183  }
184  path.append(body);
185  llvm::sys::path::native(path);
186  return std::string(path);
187 }
188 
190  StringRef uri = origUri;
191 
192  // Decode the scheme of the URI.
193  size_t pos = uri.find(':');
194  if (pos == StringRef::npos)
195  return llvm::createStringError(llvm::inconvertibleErrorCode(),
196  "Scheme must be provided in URI: " +
197  origUri);
198  StringRef schemeStr = uri.substr(0, pos);
199  std::string uriScheme = percentDecode(schemeStr);
200  if (!isStructurallyValidScheme(uriScheme))
201  return llvm::createStringError(llvm::inconvertibleErrorCode(),
202  "Invalid scheme: " + schemeStr +
203  " (decoded: " + uriScheme + ")");
204  uri = uri.substr(pos + 1);
205 
206  // Decode the authority of the URI.
207  std::string uriAuthority;
208  if (uri.consume_front("//")) {
209  pos = uri.find('/');
210  uriAuthority = percentDecode(uri.substr(0, pos));
211  uri = uri.substr(pos);
212  }
213 
214  // Decode the body of the URI.
215  std::string uriBody = percentDecode(uri);
216 
217  // Compute the absolute path for this uri.
218  if (!getSupportedSchemes().contains(uriScheme)) {
219  return llvm::createStringError(llvm::inconvertibleErrorCode(),
220  "unsupported URI scheme `" + uriScheme +
221  "' for workspace files");
222  }
223  return getAbsolutePath(uriAuthority, uriBody);
224 }
225 
228  if (!filePath)
229  return filePath.takeError();
230  return URIForFile(std::move(*filePath), uri.str());
231 }
232 
234  StringRef scheme) {
236  uriFromAbsolutePath(absoluteFilepath, scheme);
237  if (!uri)
238  return uri.takeError();
239  return fromURI(*uri);
240 }
241 
242 StringRef URIForFile::scheme() const { return uri().split(':').first; }
243 
244 void URIForFile::registerSupportedScheme(StringRef scheme) {
245  getSupportedSchemes().insert(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()) {
252  if (!expectedURI) {
253  path.report("unresolvable URI");
254  consumeError(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("expected object");
280  return false;
281  }
282  if (const llvm::json::Object *textDocument = o->getObject("textDocument")) {
283  if (const llvm::json::Object *documentSymbol =
284  textDocument->getObject("documentSymbol")) {
285  if (std::optional<bool> hierarchicalSupport =
286  documentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
287  result.hierarchicalDocumentSymbol = *hierarchicalSupport;
288  }
289  if (auto *codeAction = textDocument->getObject("codeAction")) {
290  if (codeAction->getObject("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("name", result.name))
305  return false;
306 
307  // Don't fail if we can't parse version.
308  o.map("version", 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("capabilities", result.capabilities);
342  o.map("trace", result.trace);
343  mapOptOrNull(value, "clientInfo", 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("uri", result.uri) &&
356  o.map("languageId", result.languageId) && o.map("text", result.text) &&
357  o.map("version", result.version);
358 }
359 
360 //===----------------------------------------------------------------------===//
361 // TextDocumentIdentifier
362 //===----------------------------------------------------------------------===//
363 
364 llvm::json::Value mlir::lsp::toJSON(const TextDocumentIdentifier &value) {
365  return llvm::json::Object{{"uri", 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("uri", result.uri);
373 }
374 
375 //===----------------------------------------------------------------------===//
376 // VersionedTextDocumentIdentifier
377 //===----------------------------------------------------------------------===//
378 
379 llvm::json::Value
381  return llvm::json::Object{
382  {"uri", value.uri},
383  {"version", value.version},
384  };
385 }
386 
387 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
389  llvm::json::Path path) {
390  llvm::json::ObjectMapper o(value, path);
391  return o && o.map("uri", result.uri) && o.map("version", 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("line", result.line) &&
402  o.map("character", result.character);
403 }
404 
405 llvm::json::Value mlir::lsp::toJSON(const Position &value) {
406  return llvm::json::Object{
407  {"line", value.line},
408  {"character", 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("start", result.start) && o.map("end", result.end);
424 }
425 
426 llvm::json::Value mlir::lsp::toJSON(const Range &value) {
427  return llvm::json::Object{
428  {"start", value.start},
429  {"end", 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("uri", result.uri) && o.map("range", result.range);
445 }
446 
447 llvm::json::Value mlir::lsp::toJSON(const Location &value) {
448  return llvm::json::Object{
449  {"uri", value.uri},
450  {"range", 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,
464  llvm::json::Path path) {
465  llvm::json::ObjectMapper o(value, path);
466  return o && o.map("textDocument", result.textDocument) &&
467  o.map("position", 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("includeDeclaration", 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, base, path) && o &&
485  o.mapOptional("context", result.context);
486 }
487 
488 //===----------------------------------------------------------------------===//
489 // DidOpenTextDocumentParams
490 //===----------------------------------------------------------------------===//
491 
492 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
494  llvm::json::Path path) {
495  llvm::json::ObjectMapper o(value, path);
496  return o && o.map("textDocument", result.textDocument);
497 }
498 
499 //===----------------------------------------------------------------------===//
500 // DidCloseTextDocumentParams
501 //===----------------------------------------------------------------------===//
502 
503 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
505  llvm::json::Path path) {
506  llvm::json::ObjectMapper o(value, path);
507  return o && o.map("textDocument", result.textDocument);
508 }
509 
510 //===----------------------------------------------------------------------===//
511 // DidChangeTextDocumentParams
512 //===----------------------------------------------------------------------===//
513 
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(llvm::MemoryBuffer::getMemBuffer(contents),
525  SMLoc());
526  SMRange rangeLoc = range->getAsSMRange(tmpScrMgr);
527  if (!rangeLoc.isValid())
528  return failure();
529 
530  contents.replace(rangeLoc.Start.getPointer() - contents.data(),
531  rangeLoc.End.getPointer() - rangeLoc.Start.getPointer(),
532  text);
533  return success();
534 }
535 
537  ArrayRef<TextDocumentContentChangeEvent> changes, std::string &contents) {
538  for (const auto &change : changes)
539  if (failed(change.applyTo(contents)))
540  return failure();
541  return success();
542 }
543 
544 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
546  llvm::json::Path path) {
547  llvm::json::ObjectMapper o(value, path);
548  return o && o.map("range", result.range) &&
549  o.map("rangeLength", result.rangeLength) && o.map("text", result.text);
550 }
551 
552 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
554  llvm::json::Path path) {
555  llvm::json::ObjectMapper o(value, path);
556  return o && o.map("textDocument", result.textDocument) &&
557  o.map("contentChanges", result.contentChanges);
558 }
559 
560 //===----------------------------------------------------------------------===//
561 // MarkupContent
562 //===----------------------------------------------------------------------===//
563 
564 static llvm::StringRef toTextKind(MarkupKind kind) {
565  switch (kind) {
567  return "plaintext";
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  {"kind", toTextKind(mc.kind)},
584  {"value", 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{{"contents", toJSON(hover.contents)}};
594  if (hover.range)
595  result["range"] = toJSON(*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{{"name", symbol.name},
605  {"kind", static_cast<int>(symbol.kind)},
606  {"range", symbol.range},
607  {"selectionRange", 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("textDocument", result.textDocument);
624 }
625 
626 //===----------------------------------------------------------------------===//
627 // DiagnosticRelatedInformation
628 //===----------------------------------------------------------------------===//
629 
630 bool mlir::lsp::fromJSON(const llvm::json::Value &value,
632  llvm::json::Path path) {
633  llvm::json::ObjectMapper o(value, path);
634  return o && o.map("location", result.location) &&
635  o.map("message", result.message);
636 }
637 
638 llvm::json::Value mlir::lsp::toJSON(const DiagnosticRelatedInformation &info) {
639  return llvm::json::Object{
640  {"location", info.location},
641  {"message", 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  {"range", diag.range},
652  {"severity", (int)diag.severity},
653  {"message", 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(value, "severity", severity, path))
671  return false;
672  result.severity = (DiagnosticSeverity)severity;
673 
674  return o.map("range", result.range) && o.map("message", result.message) &&
675  mapOptOrNull(value, "category", result.category, path) &&
676  mapOptOrNull(value, "source", result.source, path) &&
677  mapOptOrNull(value, "relatedInformation", result.relatedInformation,
678  path);
679 }
680 
681 //===----------------------------------------------------------------------===//
682 // PublishDiagnosticsParams
683 //===----------------------------------------------------------------------===//
684 
685 llvm::json::Value mlir::lsp::toJSON(const PublishDiagnosticsParams &params) {
686  return llvm::json::Object{
687  {"uri", params.uri},
688  {"diagnostics", params.diagnostics},
689  {"version", 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("range", result.range) && o.map("newText", result.newText);
701 }
702 
703 llvm::json::Value mlir::lsp::toJSON(const TextEdit &value) {
704  return llvm::json::Object{
705  {"range", value.range},
706  {"newText", value.newText},
707  };
708 }
709 
710 raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const TextEdit &value) {
711  os << value.range << " => \"";
712  llvm::printEscapedString(value.newText, 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 
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) {
749  default:
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((*arrayValue)[i], kindOut, path.index(i)))
761  result.set(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{{"label", 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;
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 
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  {"isIncomplete", value.isIncomplete},
818  {"items", 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("triggerKind", triggerKind) ||
831  !mapOptOrNull(value, "triggerCharacter", 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, static_cast<TextDocumentPositionParams &>(result), path))
844  return false;
845  if (const llvm::json::Value *context = value.getAsObject()->get("context"))
846  return fromJSON(*context, result.context, path.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  {"label", value.label},
876  {"parameters", 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  {"activeSignature", value.activeSignature},
899  {"activeParameter", value.activeParameter},
900  {"signatures", 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("textDocument", 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  {"range", value.range},
921  {"target", 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("textDocument", result.textDocument) &&
933  o.map("range", result.range);
934 }
935 
936 //===----------------------------------------------------------------------===//
937 // InlayHint
938 //===----------------------------------------------------------------------===//
939 
940 llvm::json::Value mlir::lsp::toJSON(const InlayHint &value) {
941  return llvm::json::Object{{"position", value.position},
942  {"kind", (int)value.kind},
943  {"label", value.label},
944  {"paddingLeft", value.paddingLeft},
945  {"paddingRight", value.paddingRight}};
946 }
947 bool mlir::lsp::operator==(const InlayHint &lhs, const InlayHint &rhs) {
948  return std::tie(lhs.position, lhs.kind, lhs.label) ==
949  std::tie(rhs.position, rhs.kind, rhs.label);
950 }
951 bool mlir::lsp::operator<(const InlayHint &lhs, const InlayHint &rhs) {
952  return std::tie(lhs.position, lhs.kind, lhs.label) <
953  std::tie(rhs.position, rhs.kind, rhs.label);
954 }
955 
956 llvm::raw_ostream &mlir::lsp::operator<<(llvm::raw_ostream &os,
957  InlayHintKind value) {
958  switch (value) {
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("diagnostics", result.diagnostics))
975  return false;
976  o.map("only", 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("textDocument", result.textDocument) &&
988  o.map("range", result.range) && o.map("context", 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("changes", 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{{"changes", 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{{"title", 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 }
static std::string diag(const llvm::Value &value)
static char ID
Definition: Protocol.h:82
URI in "file" scheme for a file.
Definition: Protocol.h:100
StringRef uri() const
Returns the original uri of the file.
Definition: Protocol.h:116
static void registerSupportedScheme(StringRef scheme)
Register a supported URI scheme.
Definition: Protocol.cpp:244
static llvm::Expected< URIForFile > fromFile(StringRef absoluteFilepath, StringRef scheme="file")
Try to build a URIForFile from the given absolute file path and optional scheme.
Definition: Protocol.cpp:233
static llvm::Expected< URIForFile > fromURI(StringRef uri)
Try to build a URIForFile from the given URI string.
Definition: Protocol.cpp:226
StringRef scheme() const
Return the scheme of the uri.
Definition: Protocol.cpp:242
static llvm::Expected< std::string > uriFromAbsolutePath(StringRef absolutePath, StringRef scheme)
Definition: Protocol.cpp:139
static bool isNetworkPath(StringRef path)
Definition: Protocol.cpp:60
static llvm::Expected< std::string > getAbsolutePath(StringRef authority, StringRef body)
Definition: Protocol.cpp:168
static llvm::StringRef toTextKind(MarkupKind kind)
Definition: Protocol.cpp:564
static std::string percentDecode(StringRef content)
Decodes a string according to percent-encoding.
Definition: Protocol.cpp:102
static void percentEncode(StringRef content, std::string &out)
Encodes a string according to percent-encoding.
Definition: Protocol.cpp:89
static bool isStructurallyValidScheme(StringRef scheme)
Returns true if the given scheme is structurally valid, i.e.
Definition: Protocol.cpp:129
static StringSet & getSupportedSchemes()
Return the set containing the supported URI schemes.
Definition: Protocol.cpp:121
static bool isWindowsPath(StringRef path)
Definition: Protocol.cpp:56
static bool mapOptOrNull(const llvm::json::Value &params, llvm::StringLiteral prop, T &out, llvm::json::Path path)
Definition: Protocol.cpp:33
static llvm::Expected< std::string > parseFilePathFromURI(StringRef origUri)
Definition: Protocol.cpp:189
static bool shouldEscapeInURI(unsigned char c)
Definition: Protocol.cpp:65
llvm::json::Value toJSON(const URIForFile &value)
Add support for JSON serialization.
Definition: Protocol.cpp:263
raw_ostream & operator<<(raw_ostream &os, const URIForFile &value)
Definition: Protocol.cpp:267
DiagnosticSeverity
Definition: Protocol.h:670
bool operator<(const CompletionItem &lhs, const CompletionItem &rhs)
Definition: Protocol.cpp:805
constexpr auto kCompletionItemKindMin
Definition: Protocol.h:789
MarkupKind
Describes the content type that a client supports in various result literals like Hover.
Definition: Protocol.h:522
bool contains(SMRange range, SMLoc loc)
Returns true if the given range contains the given source location.
bool fromJSON(const llvm::json::Value &value, URIForFile &result, llvm::json::Path path)
Definition: Protocol.cpp:248
CompletionItemKind adjustKindToCapability(CompletionItemKind kind, CompletionItemKindBitset &supportedCompletionItemKinds)
Definition: Protocol.cpp:732
std::bitset< kCompletionItemKindMax+1 > CompletionItemKindBitset
Definition: Protocol.h:793
CompletionTriggerKind
Definition: Protocol.h:903
InlayHintKind
Inlay hint kinds.
Definition: Protocol.h:1072
@ Parameter
An inlay hint that is for a parameter.
@ Type
An inlay hint that for a type annotation.
CompletionItemKind
The kind of a completion entry.
Definition: Protocol.h:758
bool operator==(const TextEdit &lhs, const TextEdit &rhs)
Definition: Protocol.h:744
Include the generated interface declarations.
LogicalResult failure(bool isFailure=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:62
LogicalResult success(bool isSuccess=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:56
bool failed(LogicalResult result)
Utility function that returns true if the provided LogicalResult corresponds to a failure value.
Definition: LogicalResult.h:72
This class represents an efficient way to signal success or failure.
Definition: LogicalResult.h:26
bool codeActionStructure
Client supports CodeAction return value for textDocument/codeAction.
Definition: Protocol.h:163
bool hierarchicalDocumentSymbol
Client supports hierarchical document symbols.
Definition: Protocol.h:159
std::string name
The name of the client as defined by the client.
Definition: Protocol.h:176
std::optional< std::string > version
The client's version as defined by the client.
Definition: Protocol.h:179
std::vector< std::string > only
Requested kind of actions to return.
Definition: Protocol.h:1147
std::vector< Diagnostic > diagnostics
An array of diagnostics known on the client side overlapping the range provided to the textDocument/c...
Definition: Protocol.h:1141
TextDocumentIdentifier textDocument
The document in which the command was invoked.
Definition: Protocol.h:1160
Range range
The range for which the command was invoked.
Definition: Protocol.h:1163
CodeActionContext context
Context carrying additional information.
Definition: Protocol.h:1166
A code action represents a change that can be performed in code, e.g.
Definition: Protocol.h:1199
bool isPreferred
Marks this as a preferred action.
Definition: Protocol.h:1218
std::optional< WorkspaceEdit > edit
The workspace edit this code action performs.
Definition: Protocol.h:1221
std::optional< std::string > kind
The kind of the code action.
Definition: Protocol.h:1205
static const llvm::StringLiteral kRefactor
Definition: Protocol.h:1207
static const llvm::StringLiteral kInfo
Definition: Protocol.h:1208
std::optional< std::vector< Diagnostic > > diagnostics
The diagnostics that this code action resolves.
Definition: Protocol.h:1211
static const llvm::StringLiteral kQuickFix
Definition: Protocol.h:1206
std::string title
A short, human-readable, title for this code action.
Definition: Protocol.h:1201
std::string triggerCharacter
The trigger character (a single character) that has trigger code complete.
Definition: Protocol.h:922
CompletionTriggerKind triggerKind
How the completion was triggered.
Definition: Protocol.h:918
std::string detail
A human-readable string with additional information about this item, like type or symbol information.
Definition: Protocol.h:840
std::string filterText
A string that should be used when filtering a set of completion items.
Definition: Protocol.h:851
std::optional< TextEdit > textEdit
An edit which is applied to a document when selecting this completion.
Definition: Protocol.h:866
std::optional< MarkupContent > documentation
A human-readable string that represents a doc-comment.
Definition: Protocol.h:843
std::string insertText
A string that should be inserted to a document when selecting this completion.
Definition: Protocol.h:855
std::string label
The label of this completion item.
Definition: Protocol.h:832
bool deprecated
Indicates if this item is deprecated.
Definition: Protocol.h:874
CompletionItemKind kind
The kind of this completion item.
Definition: Protocol.h:836
std::vector< TextEdit > additionalTextEdits
An optional array of additional text edits that are applied when selecting this completion.
Definition: Protocol.h:871
InsertTextFormat insertTextFormat
The format of the insert text.
Definition: Protocol.h:859
std::string sortText
A string that should be used when comparing this item with other items.
Definition: Protocol.h:847
Represents a collection of completion items to be presented in the editor.
Definition: Protocol.h:887
std::vector< CompletionItem > items
The completion items.
Definition: Protocol.h:893
bool isIncomplete
The list is not complete.
Definition: Protocol.h:890
CompletionContext context
Definition: Protocol.h:934
Represents a related message and source code location for a diagnostic.
Definition: Protocol.h:650
std::string message
The message of this related diagnostic information.
Definition: Protocol.h:658
Location location
The location of this related diagnostic information.
Definition: Protocol.h:656
std::string source
A human-readable string describing the source of this diagnostic, e.g.
Definition: Protocol.h:690
DiagnosticSeverity severity
The diagnostic's severity.
Definition: Protocol.h:686
Range range
The source range where the message applies.
Definition: Protocol.h:682
std::optional< std::vector< DiagnosticRelatedInformation > > relatedInformation
An array of related diagnostic information, e.g.
Definition: Protocol.h:697
std::string message
The diagnostic's message.
Definition: Protocol.h:693
std::optional< std::string > category
The diagnostic's category.
Definition: Protocol.h:703
VersionedTextDocumentIdentifier textDocument
The document that changed.
Definition: Protocol.h:506
std::vector< TextDocumentContentChangeEvent > contentChanges
The actual content changes.
Definition: Protocol.h:509
TextDocumentIdentifier textDocument
The document that was closed.
Definition: Protocol.h:472
TextDocumentItem textDocument
The document that was opened.
Definition: Protocol.h:459
Parameters for the document link request.
Definition: Protocol.h:1005
TextDocumentIdentifier textDocument
The document to provide document links for.
Definition: Protocol.h:1007
TextDocumentIdentifier textDocument
Definition: Protocol.h:636
Represents programming constructs like variables, classes, interfaces etc.
Definition: Protocol.h:596
std::string detail
More detail for this symbol, e.g the signature of a function.
Definition: Protocol.h:608
Range range
The range enclosing this symbol not including leading/trailing whitespace but everything else like co...
Definition: Protocol.h:617
Range selectionRange
The range that should be selected and revealed when this symbol is being picked, e....
Definition: Protocol.h:621
std::vector< DocumentSymbol > children
Children of this symbol, e.g. properties of a class.
Definition: Protocol.h:624
SymbolKind kind
The kind of this symbol.
Definition: Protocol.h:611
std::string name
The name of this symbol.
Definition: Protocol.h:605
std::optional< Range > range
An optional range is a range inside a text document that is used to visualize a hover,...
Definition: Protocol.h:549
MarkupContent contents
The hover's content.
Definition: Protocol.h:545
std::optional< ClientInfo > clientInfo
Information about the client.
Definition: Protocol.h:205
ClientCapabilities capabilities
The capabilities provided by the client (editor or tool).
Definition: Protocol.h:202
std::optional< TraceLevel > trace
The initial trace setting. If omitted trace is disabled ('off').
Definition: Protocol.h:208
Inlay hint information.
Definition: Protocol.h:1093
bool paddingLeft
Render padding before the hint.
Definition: Protocol.h:1114
InlayHintKind kind
The kind of this hint.
Definition: Protocol.h:1107
Position position
The position of this hint.
Definition: Protocol.h:1097
bool paddingRight
Render padding after the hint.
Definition: Protocol.h:1121
std::string label
The label of this hint.
Definition: Protocol.h:1103
A parameter literal used in inlay hint requests.
Definition: Protocol.h:1055
Range range
The visible document range for which inlay hints should be computed.
Definition: Protocol.h:1060
TextDocumentIdentifier textDocument
The text document.
Definition: Protocol.h:1057
URIForFile uri
The text document's URI.
Definition: Protocol.h:394
A single parameter of a particular signature.
Definition: Protocol.h:946
std::optional< std::pair< unsigned, unsigned > > labelOffsets
Inclusive start and exclusive end offsets withing the containing signature label.
Definition: Protocol.h:952
std::string labelString
The label of this parameter. Ignored when labelOffsets is set.
Definition: Protocol.h:948
std::string documentation
The documentation of this parameter. Optional.
Definition: Protocol.h:955
int line
Line position in a document (zero-based).
Definition: Protocol.h:293
int character
Character offset on a line in a document (zero-based).
Definition: Protocol.h:296
std::vector< Diagnostic > diagnostics
The list of reported diagnostics.
Definition: Protocol.h:722
URIForFile uri
The URI for which diagnostic information is reported.
Definition: Protocol.h:720
int64_t version
The version number of the document the diagnostics are published for.
Definition: Protocol.h:724
Position end
The range's end position.
Definition: Protocol.h:345
Position start
The range's start position.
Definition: Protocol.h:342
bool includeDeclaration
Include the declaration of the current symbol.
Definition: Protocol.h:438
ReferenceContext context
Definition: Protocol.h:446
Represents the signature of a callable.
Definition: Protocol.h:986
int activeSignature
The active signature.
Definition: Protocol.h:991
int activeParameter
The active parameter of the active signature.
Definition: Protocol.h:994
std::vector< SignatureInformation > signatures
The resulting signatures.
Definition: Protocol.h:988
Represents the signature of something callable.
Definition: Protocol.h:966
std::string label
The label of this signature. Mandatory.
Definition: Protocol.h:968
std::string documentation
The documentation of this signature. Optional.
Definition: Protocol.h:971
std::vector< ParameterInformation > parameters
The parameters of this signature.
Definition: Protocol.h:974
std::string text
The new text of the range/document.
Definition: Protocol.h:497
LogicalResult applyTo(std::string &contents) const
Try to apply this change to the given contents string.
Definition: Protocol.cpp:515
std::optional< Range > range
The range of the document that changed.
Definition: Protocol.h:491
std::optional< int > rangeLength
The length of the range that got replaced.
Definition: Protocol.h:494
URIForFile uri
The text document's URI.
Definition: Protocol.h:253
std::string languageId
The text document's language identifier.
Definition: Protocol.h:234
int64_t version
The version number of this document.
Definition: Protocol.h:240
std::string text
The content of the opened text document.
Definition: Protocol.h:237
URIForFile uri
The text document's URI.
Definition: Protocol.h:231
TextDocumentIdentifier textDocument
The text document.
Definition: Protocol.h:422
Position position
The position inside the text document.
Definition: Protocol.h:425
std::string newText
The string to be inserted.
Definition: Protocol.h:741
Range range
The range of the text document to be manipulated.
Definition: Protocol.h:737
int64_t version
The version number of this document.
Definition: Protocol.h:269
URIForFile uri
The text document's URI.
Definition: Protocol.h:267
std::map< std::string, std::vector< TextEdit > > changes
Holds changes to existing resources.
Definition: Protocol.h:1179