File indexing completed on 2024-04-28 05:49:00
0001 /* 0002 SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "lspclientserver.h" 0008 0009 #include "hostprocess.h" 0010 #include "lspclient_debug.h" 0011 0012 #include <QCoreApplication> 0013 #include <QFileInfo> 0014 #include <QJsonArray> 0015 #include <QJsonDocument> 0016 #include <QJsonObject> 0017 #include <QProcess> 0018 0019 #include <utility> 0020 0021 #include <qcompilerdetection.h> 0022 0023 #include <rapidjson/document.h> 0024 #include <rapidjson/prettywriter.h> 0025 #include <rapidjson/stringbuffer.h> 0026 0027 // good/bad old school; allows easier concatenate 0028 #define CONTENT_LENGTH "Content-Length" 0029 0030 static constexpr char MEMBER_ID[] = "id"; 0031 static constexpr char MEMBER_METHOD[] = "method"; 0032 static constexpr char MEMBER_ERROR[] = "error"; 0033 static constexpr char MEMBER_CODE[] = "code"; 0034 static constexpr char MEMBER_MESSAGE[] = "message"; 0035 static constexpr char MEMBER_PARAMS[] = "params"; 0036 static constexpr char MEMBER_RESULT[] = "result"; 0037 static constexpr char MEMBER_URI[] = "uri"; 0038 static constexpr char MEMBER_VERSION[] = "version"; 0039 static constexpr char MEMBER_START[] = "start"; 0040 static constexpr char MEMBER_END[] = "end"; 0041 static constexpr char MEMBER_POSITION[] = "position"; 0042 static constexpr char MEMBER_POSITIONS[] = "positions"; 0043 static constexpr char MEMBER_LOCATION[] = "location"; 0044 static constexpr char MEMBER_RANGE[] = "range"; 0045 static constexpr char MEMBER_LINE[] = "line"; 0046 static constexpr char MEMBER_CHARACTER[] = "character"; 0047 static constexpr char MEMBER_KIND[] = "kind"; 0048 static constexpr char MEMBER_TEXT[] = "text"; 0049 static constexpr char MEMBER_LANGID[] = "languageId"; 0050 static constexpr char MEMBER_LABEL[] = "label"; 0051 static constexpr char MEMBER_DETAIL[] = "detail"; 0052 static constexpr char MEMBER_COMMAND[] = "command"; 0053 static constexpr char MEMBER_ARGUMENTS[] = "arguments"; 0054 static constexpr char MEMBER_DIAGNOSTICS[] = "diagnostics"; 0055 static constexpr char MEMBER_PREVIOUS_RESULT_ID[] = "previousResultId"; 0056 static constexpr char MEMBER_QUERY[] = "query"; 0057 static constexpr char MEMBER_TARGET_URI[] = "targetUri"; 0058 static constexpr char MEMBER_TARGET_SELECTION_RANGE[] = ""; 0059 static constexpr char MEMBER_TARGET_RANGE[] = "targetRange"; 0060 static constexpr char MEMBER_DOCUMENTATION[] = "documentation"; 0061 static constexpr char MEMBER_TITLE[] = "title"; 0062 static constexpr char MEMBER_EDIT[] = "edit"; 0063 static constexpr char MEMBER_ACTIONS[] = "actions"; 0064 0065 static QByteArray rapidJsonStringify(const rapidjson::Value &v) 0066 { 0067 rapidjson::StringBuffer buf; 0068 rapidjson::Writer w(buf); 0069 v.Accept(w); 0070 return QByteArray(buf.GetString(), buf.GetSize()); 0071 } 0072 0073 static const rapidjson::Value &GetJsonValueForKey(const rapidjson::Value &v, std::string_view key) 0074 { 0075 if (v.IsObject()) { 0076 rapidjson::Value keyRef(rapidjson::StringRef(key.data(), key.size())); 0077 auto it = v.FindMember(keyRef); 0078 if (it != v.MemberEnd()) { 0079 return it->value; 0080 } 0081 } 0082 static const rapidjson::Value nullvalue = rapidjson::Value(rapidjson::kNullType); 0083 return nullvalue; 0084 } 0085 0086 static QString GetStringValue(const rapidjson::Value &v, std::string_view key) 0087 { 0088 const auto &value = GetJsonValueForKey(v, key); 0089 if (value.IsString()) { 0090 return QString::fromUtf8(value.GetString(), value.GetStringLength()); 0091 } 0092 return {}; 0093 } 0094 0095 static int GetIntValue(const rapidjson::Value &v, std::string_view key, int defaultValue = -1) 0096 { 0097 const auto &value = GetJsonValueForKey(v, key); 0098 if (value.IsInt()) { 0099 return value.GetInt(); 0100 } 0101 return defaultValue; 0102 } 0103 0104 static bool GetBoolValue(const rapidjson::Value &v, std::string_view key) 0105 { 0106 const auto &value = GetJsonValueForKey(v, key); 0107 if (value.IsBool()) { 0108 return value.GetBool(); 0109 } 0110 return false; 0111 } 0112 0113 static const rapidjson::Value &GetJsonObjectForKey(const rapidjson::Value &v, std::string_view key) 0114 { 0115 const auto &value = GetJsonValueForKey(v, key); 0116 if (value.IsObject()) { 0117 return value; 0118 } 0119 static const rapidjson::Value dummy = rapidjson::Value(rapidjson::kObjectType); 0120 return dummy; 0121 } 0122 0123 static const rapidjson::Value &GetJsonArrayForKey(const rapidjson::Value &v, std::string_view key) 0124 { 0125 const auto &value = GetJsonValueForKey(v, key); 0126 if (value.IsArray()) { 0127 return value; 0128 } 0129 static const rapidjson::Value dummy = rapidjson::Value(rapidjson::kArrayType); 0130 return dummy; 0131 } 0132 0133 static QJsonValue encodeUrl(const QUrl url) 0134 { 0135 return QJsonValue(QLatin1String(url.toEncoded())); 0136 } 0137 0138 // message construction helpers 0139 static QJsonObject to_json(const LSPPosition &pos) 0140 { 0141 return QJsonObject{{QLatin1String(MEMBER_LINE), pos.line()}, {QLatin1String(MEMBER_CHARACTER), pos.column()}}; 0142 } 0143 0144 static QJsonObject to_json(const LSPRange &range) 0145 { 0146 return QJsonObject{{QLatin1String(MEMBER_START), to_json(range.start())}, {QLatin1String(MEMBER_END), to_json(range.end())}}; 0147 } 0148 0149 static QJsonValue to_json(const LSPLocation &location) 0150 { 0151 if (location.uri.isValid()) { 0152 return QJsonObject{{QLatin1String(MEMBER_URI), encodeUrl(location.uri)}, {QLatin1String(MEMBER_RANGE), to_json(location.range)}}; 0153 } 0154 return QJsonValue(); 0155 } 0156 0157 static QJsonValue to_json(const LSPDiagnosticRelatedInformation &related) 0158 { 0159 auto loc = to_json(related.location); 0160 if (loc.isObject()) { 0161 return QJsonObject{{QLatin1String(MEMBER_LOCATION), to_json(related.location)}, {QLatin1String(MEMBER_MESSAGE), related.message}}; 0162 } 0163 return QJsonValue(); 0164 } 0165 0166 static QJsonObject to_json(const LSPDiagnostic &diagnostic) 0167 { 0168 // required 0169 auto result = QJsonObject(); 0170 result[QLatin1String(MEMBER_RANGE)] = to_json(diagnostic.range); 0171 result[QLatin1String(MEMBER_MESSAGE)] = diagnostic.message; 0172 // optional 0173 if (!diagnostic.code.isEmpty()) { 0174 result[QStringLiteral("code")] = diagnostic.code; 0175 } 0176 if (diagnostic.severity != LSPDiagnosticSeverity::Unknown) { 0177 result[QStringLiteral("severity")] = static_cast<int>(diagnostic.severity); 0178 } 0179 if (!diagnostic.source.isEmpty()) { 0180 result[QStringLiteral("source")] = diagnostic.source; 0181 } 0182 QJsonArray relatedInfo; 0183 for (const auto &vrelated : diagnostic.relatedInformation) { 0184 auto related = to_json(vrelated); 0185 if (related.isObject()) { 0186 relatedInfo.push_back(related); 0187 } 0188 } 0189 result[QStringLiteral("relatedInformation")] = relatedInfo; 0190 return result; 0191 } 0192 0193 static QJsonArray to_json(const QList<LSPTextDocumentContentChangeEvent> &changes) 0194 { 0195 QJsonArray result; 0196 for (const auto &change : changes) { 0197 result.push_back(QJsonObject{{QLatin1String(MEMBER_RANGE), to_json(change.range)}, {QLatin1String(MEMBER_TEXT), change.text}}); 0198 } 0199 return result; 0200 } 0201 0202 static QJsonArray to_json(const QList<LSPPosition> &positions) 0203 { 0204 QJsonArray result; 0205 for (const auto &position : positions) { 0206 result.push_back(to_json(position)); 0207 } 0208 return result; 0209 } 0210 0211 static QJsonObject versionedTextDocumentIdentifier(const QUrl &document, int version = -1) 0212 { 0213 QJsonObject map{{QLatin1String(MEMBER_URI), encodeUrl(document)}}; 0214 if (version >= 0) { 0215 map[QLatin1String(MEMBER_VERSION)] = version; 0216 } 0217 return map; 0218 } 0219 0220 static QJsonObject textDocumentItem(const QUrl &document, const QString &lang, const QString &text, int version) 0221 { 0222 auto map = versionedTextDocumentIdentifier(document, version); 0223 map[QLatin1String(MEMBER_TEXT)] = text; 0224 map[QLatin1String(MEMBER_LANGID)] = lang; 0225 return map; 0226 } 0227 0228 static QJsonObject textDocumentParams(const QJsonObject &m) 0229 { 0230 return QJsonObject{{QStringLiteral("textDocument"), m}}; 0231 } 0232 0233 static QJsonObject textDocumentParams(const QUrl &document, int version = -1) 0234 { 0235 return textDocumentParams(versionedTextDocumentIdentifier(document, version)); 0236 } 0237 0238 static QJsonObject textDocumentPositionParams(const QUrl &document, LSPPosition pos) 0239 { 0240 auto params = textDocumentParams(document); 0241 params[QLatin1String(MEMBER_POSITION)] = to_json(pos); 0242 return params; 0243 } 0244 0245 static QJsonObject textDocumentPositionsParams(const QUrl &document, const QList<LSPPosition> &positions) 0246 { 0247 auto params = textDocumentParams(document); 0248 params[QLatin1String(MEMBER_POSITIONS)] = to_json(positions); 0249 return params; 0250 } 0251 0252 static QJsonObject referenceParams(const QUrl &document, LSPPosition pos, bool decl) 0253 { 0254 auto params = textDocumentPositionParams(document, pos); 0255 params[QStringLiteral("context")] = QJsonObject{{QStringLiteral("includeDeclaration"), decl}}; 0256 return params; 0257 } 0258 0259 static QJsonObject formattingOptions(const LSPFormattingOptions &_options) 0260 { 0261 auto options = _options.extra; 0262 options[QStringLiteral("tabSize")] = _options.tabSize; 0263 options[QStringLiteral("insertSpaces")] = _options.insertSpaces; 0264 return options; 0265 } 0266 0267 static QJsonObject documentRangeFormattingParams(const QUrl &document, const LSPRange *range, const LSPFormattingOptions &_options) 0268 { 0269 auto params = textDocumentParams(document); 0270 if (range) { 0271 params[QLatin1String(MEMBER_RANGE)] = to_json(*range); 0272 } 0273 params[QStringLiteral("options")] = formattingOptions(_options); 0274 return params; 0275 } 0276 0277 static QJsonObject documentOnTypeFormattingParams(const QUrl &document, const LSPPosition &pos, const QChar &lastChar, const LSPFormattingOptions &_options) 0278 { 0279 auto params = textDocumentPositionParams(document, pos); 0280 params[QStringLiteral("ch")] = QString(lastChar); 0281 params[QStringLiteral("options")] = formattingOptions(_options); 0282 return params; 0283 } 0284 0285 static QJsonObject renameParams(const QUrl &document, const LSPPosition &pos, const QString &newName) 0286 { 0287 auto params = textDocumentPositionParams(document, pos); 0288 params[QStringLiteral("newName")] = newName; 0289 return params; 0290 } 0291 0292 static QJsonObject codeActionParams(const QUrl &document, const LSPRange &range, const QList<QString> &kinds, const QList<LSPDiagnostic> &diagnostics) 0293 { 0294 auto params = textDocumentParams(document); 0295 params[QLatin1String(MEMBER_RANGE)] = to_json(range); 0296 QJsonObject context; 0297 QJsonArray diags; 0298 for (const auto &diagnostic : diagnostics) { 0299 diags.push_back(to_json(diagnostic)); 0300 } 0301 context[QLatin1String(MEMBER_DIAGNOSTICS)] = diags; 0302 if (kinds.length()) { 0303 context[QStringLiteral("only")] = QJsonArray::fromStringList(kinds); 0304 } 0305 params[QStringLiteral("context")] = context; 0306 return params; 0307 } 0308 0309 static QJsonObject executeCommandParams(const LSPCommand &command) 0310 { 0311 const auto doc = QJsonDocument::fromJson(command.arguments); 0312 QJsonValue args; 0313 if (doc.isArray()) { 0314 args = doc.array(); 0315 } else { 0316 args = doc.object(); 0317 } 0318 return QJsonObject{{QLatin1String(MEMBER_COMMAND), command.command}, {QLatin1String(MEMBER_ARGUMENTS), args}}; 0319 } 0320 0321 static QJsonObject applyWorkspaceEditResponse(const LSPApplyWorkspaceEditResponse &response) 0322 { 0323 return QJsonObject{{QStringLiteral("applied"), response.applied}, {QStringLiteral("failureReason"), response.failureReason}}; 0324 } 0325 0326 static QJsonObject workspaceFolder(const LSPWorkspaceFolder &response) 0327 { 0328 return QJsonObject{{QLatin1String(MEMBER_URI), encodeUrl(response.uri)}, {QStringLiteral("name"), response.name}}; 0329 } 0330 0331 static QJsonObject changeConfigurationParams(const QJsonValue &settings) 0332 { 0333 return QJsonObject{{QStringLiteral("settings"), settings}}; 0334 } 0335 0336 static QJsonArray to_json(const QList<LSPWorkspaceFolder> &l) 0337 { 0338 QJsonArray result; 0339 for (const auto &e : l) { 0340 result.push_back(workspaceFolder(e)); 0341 } 0342 return result; 0343 } 0344 0345 static QJsonObject changeWorkspaceFoldersParams(const QList<LSPWorkspaceFolder> &added, const QList<LSPWorkspaceFolder> &removed) 0346 { 0347 QJsonObject event; 0348 event[QStringLiteral("added")] = to_json(added); 0349 event[QStringLiteral("removed")] = to_json(removed); 0350 return QJsonObject{{QStringLiteral("event"), event}}; 0351 } 0352 0353 static void from_json(QList<QChar> &trigger, const rapidjson::Value &json) 0354 { 0355 if (json.IsArray()) { 0356 const auto triggersArray = json.GetArray(); 0357 trigger.reserve(triggersArray.Size()); 0358 for (const auto &t : triggersArray) { 0359 if (t.IsString() && t.GetStringLength() > 0) { 0360 trigger << QChar::fromLatin1(t.GetString()[0]); 0361 } 0362 } 0363 } 0364 } 0365 0366 static void from_json(LSPCompletionOptions &options, const rapidjson::Value &json) 0367 { 0368 if (json.IsObject()) { 0369 options.provider = true; 0370 options.resolveProvider = GetBoolValue(json, "resolveProvider"); 0371 from_json(options.triggerCharacters, GetJsonArrayForKey(json, "triggerCharacters")); 0372 } 0373 } 0374 0375 static void from_json(LSPSignatureHelpOptions &options, const rapidjson::Value &json) 0376 { 0377 if (json.IsObject()) { 0378 options.provider = true; 0379 from_json(options.triggerCharacters, GetJsonArrayForKey(json, "triggerCharacters")); 0380 } 0381 } 0382 0383 static void from_json(LSPDocumentOnTypeFormattingOptions &options, const rapidjson::Value &json) 0384 { 0385 if (json.IsObject()) { 0386 options.provider = true; 0387 from_json(options.triggerCharacters, GetJsonArrayForKey(json, "moreTriggerCharacter")); 0388 const QString trigger = GetStringValue(json, "firstTriggerCharacter"); 0389 if (!trigger.isEmpty()) { 0390 options.triggerCharacters.insert(0, trigger.at(0)); 0391 } 0392 } 0393 } 0394 0395 static void from_json(LSPWorkspaceFoldersServerCapabilities &options, const rapidjson::Value &json) 0396 { 0397 if (json.IsObject()) { 0398 options.supported = GetBoolValue(json, "supported"); 0399 auto it = json.FindMember("changeNotifications"); 0400 if (it != json.MemberEnd()) { 0401 if (it->value.IsString()) { 0402 options.changeNotifications = it->value.GetStringLength() > 0; 0403 } else if (it->value.IsTrue()) { 0404 options.changeNotifications = true; 0405 } 0406 } 0407 } 0408 } 0409 0410 static void from_json(LSPSemanticTokensOptions &options, const rapidjson::Value &json) 0411 { 0412 if (!json.IsObject()) { 0413 return; 0414 } 0415 0416 auto it = json.FindMember("full"); 0417 if (it != json.MemberEnd()) { 0418 if (it->value.IsObject()) { 0419 options.fullDelta = GetBoolValue(it->value, "delta"); 0420 } else { 0421 options.full = it->value.IsTrue(); 0422 } 0423 } 0424 0425 options.range = GetBoolValue(json, "range"); 0426 0427 it = json.FindMember("legend"); 0428 if (it != json.MemberEnd()) { 0429 const auto &tokenTypes = GetJsonArrayForKey(it->value, "tokenTypes"); 0430 const auto tokenTypesArray = tokenTypes.GetArray(); 0431 std::vector<QString> types; 0432 types.reserve(tokenTypesArray.Size()); 0433 for (const auto &tokenType : tokenTypesArray) { 0434 if (tokenType.IsString()) { 0435 types.push_back(QString::fromUtf8(tokenType.GetString())); 0436 } 0437 } 0438 options.legend.initialize(types); 0439 } 0440 // options.types = QList<QString>(types.begin(), types.end()); 0441 // Disabled 0442 // const auto tokenMods = legend.value(QStringLiteral("tokenModifiers")).toArray(); 0443 // std::vector<QString> modifiers; 0444 // modifiers.reserve(tokenMods.size()); 0445 // std::transform(tokenMods.cbegin(), tokenMods.cend(), std::back_inserter(modifiers), [](const QJsonValue &jv) { 0446 // return jv.toString(); 0447 // }); 0448 } 0449 0450 static void from_json(LSPServerCapabilities &caps, const rapidjson::Value &json) 0451 { 0452 const auto &sync = GetJsonValueForKey(json, "textDocumentSync"); 0453 if (sync.IsObject()) { 0454 caps.textDocumentSync.change = (LSPDocumentSyncKind)GetIntValue(sync, "change", (int)LSPDocumentSyncKind::None); 0455 auto it = sync.FindMember("save"); 0456 if (it != sync.MemberEnd()) { 0457 caps.textDocumentSync.save = {GetBoolValue(it->value, "includeText")}; 0458 } 0459 } else if (sync.IsInt()) { 0460 caps.textDocumentSync.change = LSPDocumentSyncKind(sync.GetInt()); 0461 } else { 0462 caps.textDocumentSync.change = LSPDocumentSyncKind::None; 0463 } 0464 0465 // in older protocol versions a support option is simply a boolean 0466 // in newer version it may be an object instead; 0467 // it should not be sent unless such support is announced, but let's handle it anyway 0468 // so consider an object there as a (good?) sign that the server is suitably capable 0469 // HasMember will thus just check the existence of a given key 0470 0471 caps.hoverProvider = json.HasMember("hoverProvider"); 0472 from_json(caps.completionProvider, GetJsonObjectForKey(json, "completionProvider")); 0473 from_json(caps.signatureHelpProvider, GetJsonObjectForKey(json, "signatureHelpProvider")); 0474 caps.definitionProvider = json.HasMember("definitionProvider"); 0475 caps.declarationProvider = json.HasMember("declarationProvider"); 0476 caps.typeDefinitionProvider = json.HasMember("typeDefinitionProvider"); 0477 caps.referencesProvider = json.HasMember("referencesProvider"); 0478 caps.implementationProvider = json.HasMember("implementationProvider"); 0479 caps.documentSymbolProvider = json.HasMember("documentSymbolProvider"); 0480 caps.documentHighlightProvider = json.HasMember("documentHighlightProvider"); 0481 caps.documentFormattingProvider = json.HasMember("documentFormattingProvider"); 0482 caps.documentRangeFormattingProvider = json.HasMember("documentRangeFormattingProvider"); 0483 from_json(caps.documentOnTypeFormattingProvider, GetJsonObjectForKey(json, "documentOnTypeFormattingProvider")); 0484 caps.renameProvider = json.HasMember("renameProvider"); 0485 caps.codeActionProvider = json.HasMember("codeActionProvider"); 0486 from_json(caps.semanticTokenProvider, GetJsonObjectForKey(json, "semanticTokensProvider")); 0487 const auto &workspace = GetJsonObjectForKey(json, "workspace"); 0488 from_json(caps.workspaceFolders, GetJsonObjectForKey(workspace, "workspaceFolders")); 0489 caps.selectionRangeProvider = json.HasMember("selectionRangeProvider"); 0490 caps.inlayHintProvider = json.HasMember("inlayHintProvider"); 0491 } 0492 0493 // follow suit; as performed in kate docmanager 0494 // normalize at this stage/layer to avoid surprises elsewhere 0495 // sadly this is not a single QUrl method as one might hope ... 0496 static QUrl normalizeUrl(const QUrl &url) 0497 { 0498 // Resolve symbolic links for local files (done anyway in KTextEditor) 0499 if (url.isLocalFile()) { 0500 QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath(); 0501 if (!normalizedUrl.isEmpty()) { 0502 return QUrl::fromLocalFile(normalizedUrl); 0503 } 0504 } 0505 0506 // else: cleanup only the .. stuff 0507 return url.adjusted(QUrl::NormalizePathSegments); 0508 } 0509 0510 static void from_json(LSPVersionedTextDocumentIdentifier &id, const rapidjson::Value &json) 0511 { 0512 if (json.IsObject()) { 0513 id.uri = normalizeUrl(QUrl(GetStringValue(json, MEMBER_URI))); 0514 id.version = GetIntValue(json, MEMBER_VERSION, -1); 0515 } 0516 } 0517 0518 static LSPResponseError parseResponseError(const rapidjson::Value &v) 0519 { 0520 LSPResponseError ret; 0521 if (v.IsObject()) { 0522 ret.code = LSPErrorCode(GetIntValue(v, MEMBER_CODE)); 0523 ret.message = GetStringValue(v, MEMBER_MESSAGE); 0524 auto it = v.FindMember("data"); 0525 if (it != v.MemberEnd()) { 0526 ret.data = rapidJsonStringify(it->value); 0527 } 0528 } 0529 return ret; 0530 } 0531 0532 static LSPMarkupContent parseMarkupContent(const rapidjson::Value &v) 0533 { 0534 LSPMarkupContent ret; 0535 if (v.IsObject()) { 0536 ret.value = GetStringValue(v, "value"); 0537 auto kind = GetStringValue(v, MEMBER_KIND); 0538 if (kind == QLatin1String("plaintext")) { 0539 ret.kind = LSPMarkupKind::PlainText; 0540 } else if (kind == QLatin1String("markdown")) { 0541 ret.kind = LSPMarkupKind::MarkDown; 0542 } 0543 } else if (v.IsString()) { 0544 ret.kind = LSPMarkupKind::PlainText; 0545 ret.value = QString::fromUtf8(v.GetString(), v.GetStringLength()); 0546 } 0547 return ret; 0548 } 0549 0550 static bool isPositionValid(const LSPPosition &pos) 0551 { 0552 return pos.isValid(); 0553 } 0554 0555 static LSPPosition parsePosition(const rapidjson::Value &m) 0556 { 0557 auto line = GetIntValue(m, MEMBER_LINE); 0558 auto column = GetIntValue(m, MEMBER_CHARACTER); 0559 return {line, column}; 0560 } 0561 0562 static LSPRange parseRange(const rapidjson::Value &range) 0563 { 0564 auto start = parsePosition(GetJsonObjectForKey(range, MEMBER_START)); 0565 auto end = parsePosition(GetJsonObjectForKey(range, MEMBER_END)); 0566 return {start, end}; 0567 } 0568 0569 static std::shared_ptr<LSPSelectionRange> parseSelectionRange(const rapidjson::Value &selectionRange) 0570 { 0571 auto current = std::make_shared<LSPSelectionRange>(LSPSelectionRange{}); 0572 std::shared_ptr<LSPSelectionRange> ret = current; 0573 const rapidjson::Value *selRange = &selectionRange; 0574 while (selRange->IsObject()) { 0575 current->range = parseRange(GetJsonObjectForKey(*selRange, MEMBER_RANGE)); 0576 auto it = selRange->FindMember("parent"); 0577 if (it == selRange->MemberEnd() || !it->value.IsObject()) { 0578 current->parent = nullptr; 0579 break; 0580 } 0581 0582 selRange = &(it->value); 0583 current->parent = std::make_shared<LSPSelectionRange>(LSPSelectionRange{}); 0584 current = current->parent; 0585 } 0586 0587 return ret; 0588 } 0589 0590 static QList<std::shared_ptr<LSPSelectionRange>> parseSelectionRanges(const rapidjson::Value &result) 0591 { 0592 QList<std::shared_ptr<LSPSelectionRange>> ret; 0593 if (!result.IsArray()) { 0594 return ret; 0595 } 0596 auto selectionRanges = result.GetArray(); 0597 for (const auto &selectionRange : selectionRanges) { 0598 ret.push_back(parseSelectionRange(selectionRange)); 0599 } 0600 0601 return ret; 0602 } 0603 0604 static LSPLocation parseLocation(const rapidjson::Value &loc) 0605 { 0606 auto uri = normalizeUrl(QUrl(GetStringValue(loc, MEMBER_URI))); 0607 KTextEditor::Range range; 0608 auto it = loc.FindMember(MEMBER_RANGE); 0609 if (it != loc.MemberEnd()) { 0610 range = parseRange(it->value); 0611 } 0612 return {QUrl(uri), range}; 0613 } 0614 0615 static LSPLocation parseLocationLink(const rapidjson::Value &loc) 0616 { 0617 auto urlString = GetStringValue(loc, MEMBER_TARGET_URI); 0618 auto uri = normalizeUrl(QUrl(urlString)); 0619 // both should be present, selection contained by the other 0620 // so let's preferentially pick the smallest one 0621 KTextEditor::Range range; 0622 if (loc.HasMember(MEMBER_TARGET_SELECTION_RANGE)) { 0623 range = parseRange(loc[MEMBER_TARGET_SELECTION_RANGE]); 0624 } else { 0625 range = parseRange(loc[MEMBER_TARGET_RANGE]); 0626 } 0627 return {QUrl(uri), range}; 0628 } 0629 0630 static QList<LSPTextEdit> parseTextEdit(const rapidjson::Value &result) 0631 { 0632 QList<LSPTextEdit> ret; 0633 ret.reserve(result.Size()); 0634 for (const auto &edit : result.GetArray()) { 0635 auto text = GetStringValue(edit, "newText"); 0636 auto range = parseRange(GetJsonObjectForKey(edit, MEMBER_RANGE)); 0637 ret.push_back({range, std::move(text)}); 0638 } 0639 return ret; 0640 } 0641 0642 static LSPDocumentHighlight parseDocumentHighlight(const rapidjson::Value &result) 0643 { 0644 auto range = parseRange(GetJsonObjectForKey(result, MEMBER_RANGE)); 0645 // default is DocumentHighlightKind.Text 0646 auto kind = (LSPDocumentHighlightKind)GetIntValue(result, MEMBER_KIND, (int)LSPDocumentHighlightKind::Text); 0647 return {range, kind}; 0648 } 0649 0650 static QList<LSPDocumentHighlight> parseDocumentHighlightList(const rapidjson::Value &result) 0651 { 0652 QList<LSPDocumentHighlight> ret; 0653 // could be array 0654 if (result.IsArray()) { 0655 const auto defs = result.GetArray(); 0656 for (const auto &def : defs) { 0657 ret.push_back(parseDocumentHighlight(def)); 0658 } 0659 } else if (result.IsObject()) { 0660 // or a single value 0661 ret.push_back(parseDocumentHighlight(result)); 0662 } 0663 return ret; 0664 } 0665 0666 static LSPMarkupContent parseHoverContentElement(const rapidjson::Value &contents) 0667 { 0668 return parseMarkupContent(contents); 0669 } 0670 0671 static LSPHover parseHover(const rapidjson::Value &hover) 0672 { 0673 LSPHover ret; 0674 if (!hover.IsObject()) { 0675 return ret; 0676 } 0677 0678 // normalize content which can be of many forms 0679 // NOTE: might be invalid 0680 ret.range = parseRange(GetJsonObjectForKey(hover, MEMBER_RANGE)); 0681 0682 auto it = hover.FindMember("contents"); 0683 0684 // support the deprecated MarkedString[] variant, used by e.g. Rust rls 0685 if (it != hover.MemberEnd() && it->value.IsArray()) { 0686 const auto elements = it->value.GetArray(); 0687 for (const auto &c : elements) { 0688 ret.contents.push_back(parseHoverContentElement(c)); 0689 } 0690 } else if (it != hover.MemberEnd()) { // String | Object 0691 ret.contents.push_back(parseHoverContentElement(it->value)); 0692 } 0693 return ret; 0694 } 0695 0696 static std::list<LSPSymbolInformation> parseDocumentSymbols(const rapidjson::Value &result) 0697 { 0698 // the reply could be old SymbolInformation[] or new (hierarchical) DocumentSymbol[] 0699 // try to parse it adaptively in any case 0700 // if new style, hierarchy is specified clearly in reply 0701 // if old style, it is assumed the values enter linearly, that is; 0702 // * a parent/container is listed before its children 0703 // * if a name is defined/declared several times and then used as a parent, 0704 // then we try to find such a parent whose range contains current range 0705 // (otherwise fall back to using the last instance as a parent) 0706 0707 std::list<LSPSymbolInformation> ret; 0708 if (!result.IsArray()) { 0709 return ret; 0710 } 0711 // std::list provides stable references/iterators, so index by direct pointer is safe 0712 QMultiMap<QString, LSPSymbolInformation *> index; 0713 0714 std::function<void(const rapidjson::Value &symbol, LSPSymbolInformation *parent)> parseSymbol = [&](const rapidjson::Value &symbol, 0715 LSPSymbolInformation *parent) { 0716 const auto &location = GetJsonObjectForKey(symbol, MEMBER_LOCATION); 0717 LSPRange range; 0718 if (symbol.HasMember(MEMBER_RANGE)) { 0719 range = parseRange(symbol[MEMBER_RANGE]); 0720 } else { 0721 range = parseRange(GetJsonObjectForKey(location, MEMBER_RANGE)); 0722 } 0723 0724 // if flat list, try to find parent by name 0725 if (!parent) { 0726 QString container = GetStringValue(symbol, "containerName"); 0727 auto it = index.find(container); 0728 // default to last inserted 0729 if (it != index.end()) { 0730 parent = it.value(); 0731 } 0732 // but prefer a containing range 0733 while (it != index.end() && it.key() == container) { 0734 if (it.value()->range.contains(range)) { 0735 parent = it.value(); 0736 break; 0737 } 0738 ++it; 0739 } 0740 } 0741 auto list = parent ? &parent->children : &ret; 0742 if (isPositionValid(range.start()) && isPositionValid(range.end())) { 0743 QString name = GetStringValue(symbol, "name"); 0744 LSPSymbolKind kind = (LSPSymbolKind)GetIntValue(symbol, MEMBER_KIND); 0745 QString detail = GetStringValue(symbol, MEMBER_DETAIL); 0746 0747 list->push_back({name, kind, range, detail}); 0748 index.insert(name, &list->back()); 0749 // proceed recursively 0750 const auto &children = GetJsonArrayForKey(symbol, "children"); 0751 for (const auto &child : children.GetArray()) { 0752 parseSymbol(child, &list->back()); 0753 } 0754 } 0755 }; 0756 0757 const auto symInfos = result.GetArray(); 0758 for (const auto &info : symInfos) { 0759 parseSymbol(info, nullptr); 0760 } 0761 return ret; 0762 } 0763 0764 static QList<LSPLocation> parseDocumentLocation(const rapidjson::Value &result) 0765 { 0766 QList<LSPLocation> ret; 0767 // could be array 0768 if (result.IsArray()) { 0769 const auto locs = result.GetArray(); 0770 ret.reserve(locs.Size()); 0771 for (const auto &def : locs) { 0772 ret << parseLocation(def); 0773 0774 // bogus server might have sent LocationLink[] instead 0775 // let's try to handle it, but not announce in capabilities 0776 if (ret.back().uri.isEmpty()) { 0777 ret.back() = parseLocationLink(def); 0778 } 0779 } 0780 } else if (result.IsObject()) { 0781 // or a single value 0782 ret.push_back(parseLocation(result)); 0783 } 0784 return ret; 0785 } 0786 0787 static QList<LSPCompletionItem> parseDocumentCompletion(const rapidjson::Value &result) 0788 { 0789 QList<LSPCompletionItem> ret; 0790 const rapidjson::Value *items = &result; 0791 0792 // might be CompletionList 0793 auto &subItems = GetJsonArrayForKey(result, "items"); 0794 if (!result.IsArray()) { 0795 items = &subItems; 0796 } 0797 0798 if (!items->IsArray()) { 0799 qCWarning(LSPCLIENT) << "Unexpected, completion items is not an array"; 0800 return ret; 0801 } 0802 0803 const auto array = items->GetArray(); 0804 for (const auto &item : array) { 0805 auto label = GetStringValue(item, MEMBER_LABEL); 0806 auto detail = GetStringValue(item, MEMBER_DETAIL); 0807 LSPMarkupContent doc; 0808 auto it = item.FindMember(MEMBER_DOCUMENTATION); 0809 if (it != item.MemberEnd()) { 0810 doc = parseMarkupContent(it->value); 0811 } 0812 0813 auto sortText = GetStringValue(item, "sortText"); 0814 if (sortText.isEmpty()) { 0815 sortText = label; 0816 } 0817 auto insertText = GetStringValue(item, "insertText"); 0818 LSPTextEdit lspTextEdit; 0819 const auto &textEdit = GetJsonObjectForKey(item, "textEdit"); 0820 if (textEdit.IsObject()) { 0821 // Not a proper implementation of textEdit, but a workaround for KDE bug #445085 0822 auto newText = GetStringValue(textEdit, "newText"); 0823 // Only override insertText with newText if insertText is empty. This avoids issues with 0824 // servers such typescript-language-server which will provide a different value in newText 0825 // which makes sense only if its used in combination with range. E.g., 0826 // string.length is expected 0827 // but user gets => string..length because newText contains ".length" 0828 insertText = insertText.isEmpty() ? newText : insertText; 0829 lspTextEdit.newText = newText; 0830 lspTextEdit.range = parseRange(GetJsonObjectForKey(textEdit, "range")); 0831 } 0832 if (insertText.isEmpty()) { 0833 // if this happens, the server is broken but lets try the label anyways 0834 insertText = label; 0835 } 0836 auto kind = static_cast<LSPCompletionItemKind>(GetIntValue(item, MEMBER_KIND, 1)); 0837 const auto additionalTextEdits = parseTextEdit(GetJsonArrayForKey(item, "additionalTextEdits")); 0838 0839 auto dataIt = item.FindMember("data"); 0840 QByteArray data; 0841 if (dataIt != item.MemberEnd()) { 0842 data = rapidJsonStringify(dataIt->value); 0843 } 0844 0845 ret.push_back({label, label, kind, detail, doc, sortText, insertText, additionalTextEdits, lspTextEdit, data}); 0846 } 0847 return ret; 0848 } 0849 0850 static LSPCompletionItem parseDocumentCompletionResolve(const rapidjson::Value &result) 0851 { 0852 LSPCompletionItem ret; 0853 if (!result.IsObject()) { 0854 return ret; 0855 } 0856 // we only support additionalTextEdits in textDocument/completion/resolve atm 0857 ret.additionalTextEdits = parseTextEdit(GetJsonArrayForKey(result, "additionalTextEdits")); 0858 return ret; 0859 } 0860 0861 static LSPSignatureInformation parseSignatureInformation(const rapidjson::Value &json) 0862 { 0863 LSPSignatureInformation info; 0864 0865 info.label = GetStringValue(json, MEMBER_LABEL); 0866 auto it = json.FindMember(MEMBER_DOCUMENTATION); 0867 if (it != json.MemberEnd()) { 0868 info.documentation = parseMarkupContent(it->value); 0869 } 0870 const auto ¶ms = GetJsonArrayForKey(json, "parameters"); 0871 for (const auto &par : params.GetArray()) { 0872 auto label = par.FindMember(MEMBER_LABEL); 0873 int begin = -1, end = -1; 0874 if (label->value.IsArray()) { 0875 auto range = label->value.GetArray(); 0876 if (range.Size() == 2) { 0877 begin = range[0].GetInt(); 0878 end = range[1].GetInt(); 0879 if (begin > info.label.length()) { 0880 begin = -1; 0881 } 0882 if (end > info.label.length()) { 0883 end = -1; 0884 } 0885 } 0886 } else if (label->value.IsString()) { 0887 auto str = label->value.GetString(); 0888 QString sub = QString::fromUtf8(str, label->value.GetStringLength()); 0889 if (sub.size()) { 0890 begin = info.label.indexOf(sub); 0891 if (begin >= 0) { 0892 end = begin + sub.length(); 0893 } 0894 } 0895 } 0896 info.parameters.push_back({begin, end}); 0897 } 0898 return info; 0899 } 0900 0901 static LSPSignatureHelp parseSignatureHelp(const rapidjson::Value &result) 0902 { 0903 LSPSignatureHelp ret; 0904 if (!result.IsObject()) { 0905 return ret; 0906 } 0907 const auto sigInfos = GetJsonArrayForKey(result, "signatures").GetArray(); 0908 for (const auto &info : sigInfos) { 0909 ret.signatures.push_back(parseSignatureInformation(info)); 0910 } 0911 ret.activeSignature = GetIntValue(result, "activeSignature", 0); 0912 ret.activeParameter = GetIntValue(result, "activeParameter", 0); 0913 ret.activeSignature = qMin(qMax(ret.activeSignature, 0), ret.signatures.size()); 0914 ret.activeParameter = qMax(ret.activeParameter, 0); 0915 if (!ret.signatures.isEmpty()) { 0916 ret.activeParameter = qMin(ret.activeParameter, ret.signatures.at(ret.activeSignature).parameters.size()); 0917 } 0918 return ret; 0919 } 0920 0921 static QString parseClangdSwitchSourceHeader(const rapidjson::Value &result) 0922 { 0923 return result.IsString() ? QString::fromUtf8(result.GetString(), result.GetStringLength()) : QString(); 0924 } 0925 0926 static LSPExpandedMacro parseExpandedMacro(const rapidjson::Value &result) 0927 { 0928 LSPExpandedMacro ret; 0929 ret.name = GetStringValue(result, "name"); 0930 ret.expansion = GetStringValue(result, "expansion"); 0931 return ret; 0932 } 0933 0934 static LSPTextDocumentEdit parseTextDocumentEdit(const rapidjson::Value &result) 0935 { 0936 LSPTextDocumentEdit ret; 0937 0938 from_json(ret.textDocument, GetJsonObjectForKey(result, "textDocument")); 0939 const auto &edits = GetJsonArrayForKey(result, "edits"); 0940 ret.edits = parseTextEdit(edits.GetArray()); 0941 return ret; 0942 } 0943 0944 static LSPWorkspaceEdit parseWorkSpaceEdit(const rapidjson::Value &result) 0945 { 0946 LSPWorkspaceEdit ret; 0947 if (!result.IsObject()) { 0948 return ret; 0949 } 0950 0951 const auto &changes = GetJsonObjectForKey(result, "changes"); 0952 for (const auto &change : changes.GetObject()) { 0953 auto url = QString::fromUtf8(change.name.GetString()); 0954 ret.changes.insert(normalizeUrl(QUrl(url)), parseTextEdit(change.value.GetArray())); 0955 } 0956 0957 const auto &documentChanges = GetJsonArrayForKey(result, "documentChanges"); 0958 // resourceOperations not supported for now 0959 for (const auto &edit : documentChanges.GetArray()) { 0960 ret.documentChanges.push_back(parseTextDocumentEdit(edit)); 0961 } 0962 return ret; 0963 } 0964 0965 static LSPCommand parseCommand(const rapidjson::Value &result) 0966 { 0967 auto title = GetStringValue(result, MEMBER_TITLE); 0968 auto command = GetStringValue(result, MEMBER_COMMAND); 0969 auto args = rapidJsonStringify(GetJsonArrayForKey(result, MEMBER_ARGUMENTS)); 0970 return {title, command, args}; 0971 } 0972 0973 static QList<LSPDiagnostic> parseDiagnosticsArray(const rapidjson::Value &result) 0974 { 0975 QList<LSPDiagnostic> ret; 0976 if (!result.IsArray()) { 0977 return ret; 0978 } 0979 const auto diags = result.GetArray(); 0980 ret.reserve(diags.Size()); 0981 for (const auto &vdiag : diags) { 0982 auto diag = vdiag.GetObject(); 0983 0984 auto it = diag.FindMember(MEMBER_RANGE); 0985 if (it == diag.end()) { 0986 continue; 0987 } 0988 auto range = parseRange(it->value); 0989 auto severity = static_cast<LSPDiagnosticSeverity>(GetIntValue(diag, "severity")); 0990 0991 const auto &codeValue = GetJsonValueForKey(diag, "code"); 0992 QString code; 0993 // code can be string or an integer 0994 if (codeValue.IsString()) { 0995 code = QString::fromUtf8(codeValue.GetString(), codeValue.GetStringLength()); 0996 } else if (codeValue.IsInt()) { 0997 code = QString::number(codeValue.GetInt()); 0998 } 0999 auto source = GetStringValue(diag, "source"); 1000 auto message = GetStringValue(diag, MEMBER_MESSAGE); 1001 1002 QList<LSPDiagnosticRelatedInformation> relatedInfoList; 1003 const auto &relInfoJson = GetJsonArrayForKey(diag, "relatedInformation"); 1004 for (const auto &related : relInfoJson.GetArray()) { 1005 if (!related.IsObject()) { 1006 continue; 1007 } 1008 LSPLocation relLocation = parseLocation(GetJsonObjectForKey(related, MEMBER_LOCATION)); 1009 auto relMessage = GetStringValue(related, MEMBER_MESSAGE); 1010 relatedInfoList.push_back({relLocation, relMessage}); 1011 } 1012 1013 ret.push_back({range, severity, code, source, message, relatedInfoList}); 1014 } 1015 return ret; 1016 } 1017 1018 static QList<LSPCodeAction> parseCodeAction(const rapidjson::Value &result) 1019 { 1020 QList<LSPCodeAction> ret; 1021 if (!result.IsArray()) { 1022 return ret; 1023 } 1024 1025 const auto codeActions = result.GetArray(); 1026 for (const auto &action : codeActions) { 1027 // entry could be Command or CodeAction 1028 auto it = action.FindMember(MEMBER_COMMAND); 1029 const bool isCommand = it != action.MemberEnd() && it->value.IsString(); 1030 if (!isCommand) { 1031 // CodeAction 1032 auto title = GetStringValue(action, MEMBER_TITLE); 1033 auto kind = GetStringValue(action, MEMBER_KIND); 1034 1035 auto &commandJson = GetJsonObjectForKey(action, MEMBER_COMMAND); 1036 auto command = parseCommand(commandJson); 1037 auto edit = parseWorkSpaceEdit(GetJsonObjectForKey(action, MEMBER_EDIT)); 1038 1039 auto diagnostics = parseDiagnosticsArray(GetJsonArrayForKey(action, MEMBER_DIAGNOSTICS)); 1040 ret.push_back({title, kind, diagnostics, edit, command}); 1041 } else { 1042 // Command 1043 auto command = parseCommand(action); 1044 ret.push_back({command.title, QString(), {}, {}, command}); 1045 } 1046 } 1047 return ret; 1048 } 1049 1050 static QJsonArray supportedSemanticTokenTypes() 1051 { 1052 return QJsonArray({QStringLiteral("namespace"), QStringLiteral("type"), QStringLiteral("class"), QStringLiteral("enum"), 1053 QStringLiteral("interface"), QStringLiteral("struct"), QStringLiteral("typeParameter"), QStringLiteral("parameter"), 1054 QStringLiteral("variable"), QStringLiteral("property"), QStringLiteral("enumMember"), QStringLiteral("event"), 1055 QStringLiteral("function"), QStringLiteral("method"), QStringLiteral("macro"), QStringLiteral("keyword"), 1056 QStringLiteral("modifier"), QStringLiteral("comment"), QStringLiteral("string"), QStringLiteral("number"), 1057 QStringLiteral("regexp"), QStringLiteral("operator")}); 1058 } 1059 1060 /** 1061 * Used for both delta and full 1062 */ 1063 static LSPSemanticTokensDelta parseSemanticTokensDelta(const rapidjson::Value &result) 1064 { 1065 LSPSemanticTokensDelta ret; 1066 if (!result.IsObject()) { 1067 return ret; 1068 } 1069 1070 ret.resultId = GetStringValue(result, "resultId"); 1071 1072 const auto &edits = GetJsonArrayForKey(result, "edits"); 1073 for (const auto &edit : edits.GetArray()) { 1074 if (!edit.IsObject()) { 1075 continue; 1076 } 1077 1078 LSPSemanticTokensEdit e; 1079 e.start = GetIntValue(edit, "start"); 1080 e.deleteCount = GetIntValue(edit, "deleteCount"); 1081 1082 const auto &data = GetJsonArrayForKey(edit, "data"); 1083 const auto dataArray = data.GetArray(); 1084 e.data.reserve(dataArray.Size()); 1085 std::transform(dataArray.begin(), dataArray.end(), std::back_inserter(e.data), [](const rapidjson::Value &v) { 1086 return v.GetInt(); 1087 }); 1088 1089 ret.edits.push_back(e); 1090 } 1091 1092 auto data = GetJsonArrayForKey(result, "data").GetArray(); 1093 ret.data.reserve(data.Size()); 1094 std::transform(data.begin(), data.end(), std::back_inserter(ret.data), [](const rapidjson::Value &v) { 1095 return v.GetInt(); 1096 }); 1097 1098 return ret; 1099 } 1100 1101 static QList<LSPInlayHint> parseInlayHints(const rapidjson::Value &result) 1102 { 1103 QList<LSPInlayHint> ret; 1104 if (!result.IsArray()) { 1105 return ret; 1106 } 1107 1108 const auto hints = result.GetArray(); 1109 for (const auto &hint : hints) { 1110 LSPInlayHint h; 1111 auto labelIt = hint.FindMember("label"); 1112 if (labelIt->value.IsArray()) { 1113 for (const auto &part : labelIt->value.GetArray()) { 1114 h.label += GetStringValue(part, "value"); 1115 } 1116 } else if (labelIt->value.IsString()) { 1117 h.label = QString::fromUtf8(labelIt->value.GetString()); 1118 } 1119 // skip if empty 1120 if (h.label.isEmpty()) { 1121 continue; 1122 } 1123 1124 h.position = parsePosition(GetJsonObjectForKey(hint, "position")); 1125 h.paddingLeft = GetBoolValue(hint, "paddingLeft"); 1126 h.paddingRight = GetBoolValue(hint, "paddingRight"); 1127 // if the last position and current one is same, merge the labels 1128 if (!ret.empty() && ret.back().position == h.position) { 1129 ret.back().label += h.label; 1130 } else { 1131 ret.push_back(h); 1132 } 1133 } 1134 auto comp = [](const LSPInlayHint &l, const LSPInlayHint &r) { 1135 return l.position < r.position; 1136 }; 1137 1138 // it is likely to be already sorted 1139 if (!std::is_sorted(ret.begin(), ret.end(), comp)) { 1140 std::sort(ret.begin(), ret.end(), comp); 1141 } 1142 1143 // printf("%s\n", QJsonDocument(result.toArray()).toJson().constData()); 1144 return ret; 1145 } 1146 1147 static LSPPublishDiagnosticsParams parseDiagnostics(const rapidjson::Value &result) 1148 { 1149 LSPPublishDiagnosticsParams ret; 1150 1151 auto it = result.FindMember(MEMBER_URI); 1152 if (it != result.MemberEnd()) { 1153 ret.uri = QUrl(QString::fromUtf8(it->value.GetString(), it->value.GetStringLength())); 1154 } 1155 1156 it = result.FindMember(MEMBER_DIAGNOSTICS); 1157 if (it != result.MemberEnd()) { 1158 ret.diagnostics = parseDiagnosticsArray(it->value); 1159 } 1160 1161 return ret; 1162 } 1163 1164 static LSPApplyWorkspaceEditParams parseApplyWorkspaceEditParams(const rapidjson::Value &result) 1165 { 1166 LSPApplyWorkspaceEditParams ret; 1167 ret.label = GetStringValue(result, MEMBER_LABEL); 1168 ret.edit = parseWorkSpaceEdit(GetJsonObjectForKey(result, MEMBER_EDIT)); 1169 return ret; 1170 } 1171 1172 static LSPShowMessageParams parseMessage(const rapidjson::Value &result) 1173 { 1174 LSPShowMessageParams ret; 1175 ret.type = static_cast<LSPMessageType>(GetIntValue(result, "type", static_cast<int>(LSPMessageType::Log))); 1176 ret.message = GetStringValue(result, MEMBER_MESSAGE); 1177 return ret; 1178 } 1179 1180 void from_json(LSPWorkDoneProgressValue &value, const rapidjson::Value &json) 1181 { 1182 if (!json.IsObject()) { 1183 return; 1184 } 1185 auto kind = GetStringValue(json, "kind"); 1186 if (kind == QStringLiteral("begin")) { 1187 value.kind = LSPWorkDoneProgressKind::Begin; 1188 } else if (kind == QStringLiteral("report")) { 1189 value.kind = LSPWorkDoneProgressKind::Report; 1190 } else if (kind == QStringLiteral("end")) { 1191 value.kind = LSPWorkDoneProgressKind::End; 1192 } 1193 1194 value.title = GetStringValue(json, "title"); 1195 value.message = GetStringValue(json, "message"); 1196 value.cancellable = GetBoolValue(json, "cancellable"); 1197 int percentage = GetIntValue(json, "percentage", -1); 1198 if (percentage >= 0) { 1199 if (percentage > 100) { 1200 percentage = 100; 1201 } 1202 // force it to 100 if its not 1203 if (value.kind == LSPWorkDoneProgressKind::End && percentage != 100) { 1204 percentage = 100; 1205 } 1206 value.percentage = percentage; 1207 } 1208 } 1209 1210 template<typename T> 1211 static LSPProgressParams<T> parseProgress(const rapidjson::Value &json) 1212 { 1213 LSPProgressParams<T> ret; 1214 1215 ret.token = GetStringValue(json, "token"); 1216 auto it = json.FindMember("value"); 1217 if (it != json.MemberEnd()) { 1218 from_json(ret.value, it->value); 1219 } 1220 return ret; 1221 } 1222 1223 static LSPWorkDoneProgressParams parseWorkDone(const rapidjson::Value &json) 1224 { 1225 return parseProgress<LSPWorkDoneProgressValue>(json); 1226 } 1227 1228 static std::vector<LSPSymbolInformation> parseWorkspaceSymbols(const rapidjson::Value &result) 1229 { 1230 std::vector<LSPSymbolInformation> symbols; 1231 if (!result.IsArray()) { 1232 return symbols; 1233 } 1234 1235 auto res = result.GetArray(); 1236 1237 symbols.reserve(res.Size()); 1238 1239 std::transform(res.begin(), res.end(), std::back_inserter(symbols), [](const rapidjson::Value &jv) { 1240 LSPSymbolInformation symInfo; 1241 if (!jv.IsObject()) { 1242 return symInfo; 1243 } 1244 auto symbol = jv.GetObject(); 1245 1246 auto location = parseLocation(GetJsonObjectForKey(symbol, MEMBER_LOCATION)); 1247 if (symbol.HasMember(MEMBER_RANGE)) { 1248 location.range = parseRange(GetJsonObjectForKey(symbol, MEMBER_RANGE)); 1249 } 1250 1251 auto containerName = GetStringValue(symbol, "containerName"); 1252 if (!containerName.isEmpty()) { 1253 containerName.append(QStringLiteral("::")); 1254 } 1255 symInfo.name = containerName + GetStringValue(symbol, "name"); 1256 symInfo.kind = (LSPSymbolKind)GetIntValue(symbol, MEMBER_KIND); 1257 symInfo.range = location.range; 1258 symInfo.url = location.uri; 1259 auto scoreIt = symbol.FindMember("score"); 1260 if (scoreIt != symbol.MemberEnd()) { 1261 symInfo.score = scoreIt->value.GetDouble(); 1262 } 1263 symInfo.tags = (LSPSymbolTag)GetIntValue(symbol, "tags"); 1264 return symInfo; 1265 }); 1266 1267 std::sort(symbols.begin(), symbols.end(), [](const LSPSymbolInformation &l, const LSPSymbolInformation &r) { 1268 return l.score > r.score; 1269 }); 1270 1271 return symbols; 1272 } 1273 1274 using GenericReplyType = rapidjson::Value; 1275 using GenericReplyHandler = ReplyHandler<GenericReplyType>; 1276 1277 class LSPClientServer::LSPClientServerPrivate 1278 { 1279 typedef LSPClientServerPrivate self_type; 1280 1281 LSPClientServer *q; 1282 // server cmd line 1283 QStringList m_server; 1284 // workspace root to pass along 1285 QUrl m_root; 1286 // language id 1287 QString m_langId; 1288 // user provided init 1289 QJsonValue m_init; 1290 // additional tweaks 1291 ExtraServerConfig m_config; 1292 // server process 1293 QProcess m_sproc; 1294 // server declared capabilities 1295 LSPServerCapabilities m_capabilities; 1296 // server state 1297 State m_state = State::None; 1298 // last msg id 1299 int m_id = 0; 1300 // receive buffer 1301 QByteArray m_receive; 1302 // registered reply handlers 1303 // (result handler, error result handler) 1304 QHash<int, std::pair<GenericReplyHandler, GenericReplyHandler>> m_handlers; 1305 // pending request responses 1306 static constexpr int MAX_REQUESTS = 5; 1307 QVariantList m_requests{MAX_REQUESTS + 1}; 1308 1309 // currently accumulated stderr output, used to output to the message view on line level 1310 QString m_currentStderrOutput; 1311 1312 public: 1313 LSPClientServerPrivate(LSPClientServer *_q, 1314 const QStringList &server, 1315 const QUrl &root, 1316 const QString &langId, 1317 const QJsonValue &init, 1318 ExtraServerConfig config) 1319 : q(_q) 1320 , m_server(server) 1321 , m_root(root) 1322 , m_langId(langId) 1323 , m_init(init) 1324 , m_config(config) 1325 { 1326 // setup async reading 1327 QObject::connect(&m_sproc, &QProcess::readyReadStandardOutput, utils::mem_fun(&self_type::readStandardOutput, this)); 1328 QObject::connect(&m_sproc, &QProcess::readyReadStandardError, utils::mem_fun(&self_type::readStandardError, this)); 1329 QObject::connect(&m_sproc, &QProcess::stateChanged, utils::mem_fun(&self_type::onStateChanged, this)); 1330 } 1331 1332 ~LSPClientServerPrivate() 1333 { 1334 stop(TIMEOUT_SHUTDOWN, TIMEOUT_SHUTDOWN); 1335 } 1336 1337 const QStringList &cmdline() const 1338 { 1339 return m_server; 1340 } 1341 1342 const QUrl &root() const 1343 { 1344 return m_root; 1345 } 1346 1347 const QString &langId() const 1348 { 1349 return m_langId; 1350 } 1351 1352 State state() 1353 { 1354 return m_state; 1355 } 1356 1357 const LSPServerCapabilities &capabilities() 1358 { 1359 return m_capabilities; 1360 } 1361 1362 int cancel(int reqid) 1363 { 1364 if (m_handlers.remove(reqid)) { 1365 auto params = QJsonObject{{QLatin1String(MEMBER_ID), reqid}}; 1366 write(init_request(QStringLiteral("$/cancelRequest"), params)); 1367 } 1368 return -1; 1369 } 1370 1371 private: 1372 void setState(State s) 1373 { 1374 if (m_state != s) { 1375 m_state = s; 1376 Q_EMIT q->stateChanged(q); 1377 } 1378 } 1379 1380 RequestHandle write(const QJsonObject &msg, const GenericReplyHandler &h = nullptr, const GenericReplyHandler &eh = nullptr, const QVariant &id = {}) 1381 { 1382 RequestHandle ret; 1383 ret.m_server = q; 1384 1385 if (!running()) { 1386 return ret; 1387 } 1388 1389 auto ob = msg; 1390 ob.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0")); 1391 // notification == no handler 1392 if (h) { 1393 ob.insert(QLatin1String(MEMBER_ID), ++m_id); 1394 ret.m_id = m_id; 1395 m_handlers[m_id] = {h, eh}; 1396 } else if (!id.isNull()) { 1397 ob.insert(QLatin1String(MEMBER_ID), QJsonValue::fromVariant(id)); 1398 } 1399 1400 QJsonDocument json(ob); 1401 auto sjson = json.toJson(); 1402 1403 qCInfo(LSPCLIENT) << "calling" << msg[QLatin1String(MEMBER_METHOD)].toString(); 1404 qCDebug(LSPCLIENT) << "sending message:\n" << QString::fromUtf8(sjson); 1405 // some simple parsers expect length header first 1406 auto hdr = QStringLiteral(CONTENT_LENGTH ": %1\r\n").arg(sjson.length()); 1407 // write is async, so no blocking wait occurs here 1408 m_sproc.write(hdr.toLatin1()); 1409 m_sproc.write("\r\n"); 1410 m_sproc.write(sjson); 1411 1412 return ret; 1413 } 1414 1415 RequestHandle send(const QJsonObject &msg, const GenericReplyHandler &h = nullptr, const GenericReplyHandler &eh = nullptr) 1416 { 1417 if (m_state == State::Running) { 1418 return write(msg, h, eh); 1419 } else { 1420 qCWarning(LSPCLIENT) << "send for non-running server"; 1421 } 1422 return RequestHandle(); 1423 } 1424 1425 void readStandardOutput() 1426 { 1427 // accumulate in buffer 1428 m_receive.append(m_sproc.readAllStandardOutput()); 1429 1430 // try to get one (or more) message 1431 QByteArray &buffer = m_receive; 1432 1433 while (true) { 1434 qCDebug(LSPCLIENT) << "buffer size" << buffer.length(); 1435 auto header = QByteArray(CONTENT_LENGTH ":"); 1436 int index = buffer.indexOf(header); 1437 if (index < 0) { 1438 // avoid collecting junk 1439 if (buffer.length() > 1 << 20) { 1440 buffer.clear(); 1441 } 1442 break; 1443 } 1444 index += header.length(); 1445 int endindex = buffer.indexOf("\r\n", index); 1446 auto msgstart = buffer.indexOf("\r\n\r\n", index); 1447 if (endindex < 0 || msgstart < 0) { 1448 break; 1449 } 1450 msgstart += 4; 1451 bool ok = false; 1452 auto length = buffer.mid(index, endindex - index).toInt(&ok, 10); 1453 // FIXME perhaps detect if no reply for some time 1454 // then again possibly better left to user to restart in such case 1455 if (!ok) { 1456 qCWarning(LSPCLIENT) << "invalid " CONTENT_LENGTH; 1457 // flush and try to carry on to some next header 1458 buffer.remove(0, msgstart); 1459 continue; 1460 } 1461 // sanity check to avoid extensive buffering 1462 if (length > 1 << 29) { 1463 qCWarning(LSPCLIENT) << "excessive size"; 1464 buffer.clear(); 1465 continue; 1466 } 1467 if (msgstart + length > buffer.length()) { 1468 break; 1469 } 1470 // now onto payload 1471 auto payload = buffer.mid(msgstart, length); 1472 buffer.remove(0, msgstart + length); 1473 qCInfo(LSPCLIENT) << "got message payload size " << length; 1474 qCDebug(LSPCLIENT) << "message payload:\n" << payload; 1475 1476 rapidjson::Document doc; 1477 doc.ParseInsitu(payload.data()); 1478 if (doc.HasParseError()) { 1479 qWarning(LSPCLIENT) << "invalid response payload" << doc.GetParseError() << doc.GetErrorOffset(); 1480 continue; 1481 } 1482 1483 rapidjson::GenericObject result = doc.GetObject(); 1484 auto memIdIt = result.FindMember(MEMBER_ID); 1485 int msgid = -1; 1486 if (memIdIt != result.MemberEnd()) { 1487 // allow id to be returned as a string value, happens e.g. for Perl LSP server 1488 if (memIdIt->value.IsString()) { 1489 msgid = QByteArray(memIdIt->value.GetString()).toInt(); 1490 } else { 1491 msgid = memIdIt->value.GetInt(); 1492 } 1493 1494 } else { 1495 processNotification(result); 1496 continue; 1497 } 1498 1499 // could be request 1500 if (result.HasMember(MEMBER_METHOD)) { 1501 processRequest(result); 1502 continue; 1503 } 1504 1505 // a valid reply; what to do with it now 1506 auto it = m_handlers.find(msgid); 1507 if (it != m_handlers.end()) { 1508 // copy handler to local storage 1509 const auto handler = *it; 1510 1511 // remove handler from our set, do this pre handler execution to avoid races 1512 m_handlers.erase(it); 1513 1514 // run handler, might e.g. trigger some new LSP actions for this server 1515 // process and provide error if caller interested, 1516 // otherwise reply will resolve to 'empty' response 1517 auto &h = handler.first; 1518 auto &eh = handler.second; 1519 if (auto it = result.FindMember(MEMBER_ERROR); it != result.MemberEnd() && eh) { 1520 eh(it->value); 1521 } else { 1522 // result can be object or array so just extract value 1523 h(GetJsonValueForKey(result, MEMBER_RESULT)); 1524 } 1525 } else { 1526 // could have been canceled 1527 qCDebug(LSPCLIENT) << "unexpected reply id" << msgid; 1528 } 1529 } 1530 } 1531 1532 void readStandardError() 1533 { 1534 // append new stuff to our buffer, we assume UTF-8 output 1535 m_currentStderrOutput.append(QString::fromUtf8(m_sproc.readAllStandardError())); 1536 1537 // now, cut out all full lines 1538 LSPShowMessageParams msg; 1539 if (const int lastNewLineIndex = m_currentStderrOutput.lastIndexOf(QLatin1Char('\n')); lastNewLineIndex >= 0) { 1540 msg.message = m_currentStderrOutput.left(lastNewLineIndex); 1541 m_currentStderrOutput.remove(0, lastNewLineIndex + 1); 1542 } 1543 1544 // emit the output lines if non-empty 1545 // this might strip empty lines in error output, but that is better then a spammed output view 1546 if (!msg.message.isEmpty()) { 1547 msg.type = LSPMessageType::Log; 1548 Q_EMIT q->logMessage(msg); 1549 } 1550 } 1551 1552 static QJsonObject init_error(const LSPErrorCode code, const QString &msg) 1553 { 1554 return QJsonObject{ 1555 {QLatin1String(MEMBER_ERROR), QJsonObject{{QLatin1String(MEMBER_CODE), static_cast<int>(code)}, {QLatin1String(MEMBER_MESSAGE), msg}}}}; 1556 } 1557 1558 static QJsonObject init_request(const QString &method, const QJsonObject ¶ms = QJsonObject()) 1559 { 1560 return QJsonObject{{QLatin1String(MEMBER_METHOD), method}, {QLatin1String(MEMBER_PARAMS), params}}; 1561 } 1562 1563 static QJsonObject init_response(const QJsonValue &result = QJsonValue()) 1564 { 1565 return QJsonObject{{QLatin1String(MEMBER_RESULT), result}}; 1566 } 1567 1568 bool running() 1569 { 1570 return m_sproc.state() == QProcess::Running; 1571 } 1572 1573 void onStateChanged(QProcess::ProcessState nstate) 1574 { 1575 if (nstate == QProcess::NotRunning) { 1576 setState(State::None); 1577 } 1578 } 1579 1580 void shutdown() 1581 { 1582 if (m_state == State::Running) { 1583 qCInfo(LSPCLIENT) << "shutting down" << m_server; 1584 // cancel all pending 1585 m_handlers.clear(); 1586 // shutdown sequence 1587 send(init_request(QStringLiteral("shutdown"))); 1588 // maybe we will get/see reply on the above, maybe not 1589 // but not important or useful either way 1590 send(init_request(QStringLiteral("exit"))); 1591 // no longer fit for regular use 1592 setState(State::Shutdown); 1593 } 1594 } 1595 1596 void applyTriggerOverride(QList<QChar> &characters, const TriggerCharactersOverride &adjust) 1597 { 1598 // these are expected 'small' sets, so the simple way should do 1599 for (const auto &c : adjust.exclude) { 1600 characters.removeAll(c); 1601 } 1602 characters.append(adjust.include); 1603 } 1604 1605 void onInitializeReply(const rapidjson::Value &value) 1606 { 1607 // only parse parts that we use later on 1608 from_json(m_capabilities, GetJsonObjectForKey(value, "capabilities")); 1609 // tweak triggers as specified 1610 applyTriggerOverride(m_capabilities.completionProvider.triggerCharacters, m_config.completion); 1611 applyTriggerOverride(m_capabilities.signatureHelpProvider.triggerCharacters, m_config.signature); 1612 // finish init 1613 initialized(); 1614 } 1615 1616 void initialize() 1617 { 1618 // clang-format off 1619 QJsonObject codeAction{{QStringLiteral("codeActionLiteralSupport"), 1620 QJsonObject{{ 1621 QStringLiteral("codeActionKind"), QJsonObject{{ 1622 QStringLiteral("valueSet"), QJsonArray({ 1623 QStringLiteral("quickfix"), 1624 QStringLiteral("refactor"), 1625 QStringLiteral("source") 1626 }) 1627 }} 1628 }} 1629 }}; 1630 1631 QJsonObject semanticTokens{{QStringLiteral("requests"), 1632 QJsonObject{ 1633 {QStringLiteral("range"), true}, 1634 {QStringLiteral("full"), QJsonObject{{QStringLiteral("delta"), true}}} 1635 } 1636 }, 1637 {QStringLiteral("tokenTypes"), supportedSemanticTokenTypes()}, 1638 {QStringLiteral("tokenModifiers"), QJsonArray()}, 1639 {QStringLiteral("formats"), QJsonArray({QStringLiteral("relative")})}, 1640 }; 1641 QJsonObject capabilities{{QStringLiteral("textDocument"), 1642 QJsonObject{ 1643 {QStringLiteral("documentSymbol"), QJsonObject{{QStringLiteral("hierarchicalDocumentSymbolSupport"), true}} }, 1644 {QStringLiteral("publishDiagnostics"), QJsonObject{{QStringLiteral("relatedInformation"), true}}}, 1645 {QStringLiteral("codeAction"), codeAction}, 1646 {QStringLiteral("semanticTokens"), semanticTokens}, 1647 {QStringLiteral("synchronization"), QJsonObject{{QStringLiteral("didSave"), true}}}, 1648 {QStringLiteral("selectionRange"), QJsonObject{{QStringLiteral("dynamicRegistration"), false}}}, 1649 {QStringLiteral("hover"), QJsonObject{ 1650 {QStringLiteral("contentFormat"), QJsonArray{ 1651 QStringLiteral("markdown"), 1652 QStringLiteral("plaintext") 1653 }} 1654 }}, 1655 {QStringLiteral("completion"), QJsonObject{ 1656 {QStringLiteral("completionItem"), QJsonObject{ 1657 {QStringLiteral("snippetSupport"), m_config.caps.snippetSupport}, 1658 {QStringLiteral("resolveSupport"), QJsonObject{ 1659 {QStringLiteral("properties"), QJsonArray{ QStringLiteral("additionalTextEdits") }} 1660 }} 1661 }} 1662 }}, 1663 {QStringLiteral("inlayHint"), QJsonObject{ 1664 {QStringLiteral("dynamicRegistration"), false} 1665 }} 1666 }, 1667 }, 1668 {QStringLiteral("window"), 1669 QJsonObject{ 1670 {QStringLiteral("workDoneProgress"), true}, 1671 {QStringLiteral("showMessage"), QJsonObject{ 1672 {QStringLiteral("messageActionItem"), QJsonObject{ 1673 {QStringLiteral("additionalPropertiesSupport"), true} 1674 }} 1675 }} 1676 } 1677 } 1678 }; 1679 // only declare workspace support if folders so specified 1680 const auto &folders = m_config.folders; 1681 if (folders) { 1682 capabilities[QStringLiteral("workspace")] = QJsonObject{{QStringLiteral("workspaceFolders"), true}}; 1683 } 1684 // NOTE a typical server does not use root all that much, 1685 // other than for some corner case (in) requests 1686 QJsonObject params{{QStringLiteral("processId"), QCoreApplication::applicationPid()}, 1687 {QStringLiteral("rootPath"), m_root.isValid() ? m_root.toLocalFile() : QJsonValue()}, 1688 {QStringLiteral("rootUri"), m_root.isValid() ? m_root.toString() : QJsonValue()}, 1689 {QStringLiteral("capabilities"), capabilities}, 1690 {QStringLiteral("initializationOptions"), m_init}}; 1691 // only add new style workspaces init if so specified 1692 if (folders) { 1693 params[QStringLiteral("workspaceFolders")] = to_json(*folders); 1694 } 1695 // 1696 write(init_request(QStringLiteral("initialize"), params), utils::mem_fun(&self_type::onInitializeReply, this)); 1697 // clang-format on 1698 } 1699 1700 void initialized() 1701 { 1702 write(init_request(QStringLiteral("initialized"))); 1703 setState(State::Running); 1704 } 1705 1706 public: 1707 bool start(bool forwardStdError) 1708 { 1709 if (m_state != State::None) { 1710 return true; 1711 } 1712 1713 auto program = m_server.front(); 1714 auto args = m_server; 1715 args.pop_front(); 1716 qCInfo(LSPCLIENT) << "starting" << m_server << "with root" << m_root; 1717 1718 // start LSP server in project root 1719 m_sproc.setWorkingDirectory(m_root.toLocalFile()); 1720 1721 // we handle stdout/stderr internally, important stuff via stdout 1722 m_sproc.setProcessChannelMode(forwardStdError ? QProcess::ForwardedErrorChannel : QProcess::SeparateChannels); 1723 m_sproc.setReadChannel(QProcess::QProcess::StandardOutput); 1724 startHostProcess(m_sproc, program, args); 1725 const bool result = m_sproc.waitForStarted(); 1726 if (result) { 1727 setState(State::Started); 1728 // perform initial handshake 1729 initialize(); 1730 } 1731 return result; 1732 } 1733 1734 void stop(int to_term, int to_kill) 1735 { 1736 if (running()) { 1737 shutdown(); 1738 if ((to_term >= 0) && !m_sproc.waitForFinished(to_term)) { 1739 m_sproc.terminate(); 1740 } 1741 if ((to_kill >= 0) && !m_sproc.waitForFinished(to_kill)) { 1742 m_sproc.kill(); 1743 } 1744 } 1745 } 1746 1747 RequestHandle documentSymbols(const QUrl &document, const GenericReplyHandler &h, const GenericReplyHandler &eh) 1748 { 1749 auto params = textDocumentParams(document); 1750 return send(init_request(QStringLiteral("textDocument/documentSymbol"), params), h, eh); 1751 } 1752 1753 RequestHandle documentDefinition(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1754 { 1755 auto params = textDocumentPositionParams(document, pos); 1756 return send(init_request(QStringLiteral("textDocument/definition"), params), h); 1757 } 1758 1759 RequestHandle documentDeclaration(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1760 { 1761 auto params = textDocumentPositionParams(document, pos); 1762 return send(init_request(QStringLiteral("textDocument/declaration"), params), h); 1763 } 1764 1765 RequestHandle documentTypeDefinition(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1766 { 1767 auto params = textDocumentPositionParams(document, pos); 1768 return send(init_request(QStringLiteral("textDocument/typeDefinition"), params), h); 1769 } 1770 1771 RequestHandle documentImplementation(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1772 { 1773 auto params = textDocumentPositionParams(document, pos); 1774 return send(init_request(QStringLiteral("textDocument/implementation"), params), h); 1775 } 1776 1777 RequestHandle documentHover(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1778 { 1779 auto params = textDocumentPositionParams(document, pos); 1780 return send(init_request(QStringLiteral("textDocument/hover"), params), h); 1781 } 1782 1783 RequestHandle documentHighlight(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1784 { 1785 auto params = textDocumentPositionParams(document, pos); 1786 return send(init_request(QStringLiteral("textDocument/documentHighlight"), params), h); 1787 } 1788 1789 RequestHandle documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, const GenericReplyHandler &h) 1790 { 1791 auto params = referenceParams(document, pos, decl); 1792 return send(init_request(QStringLiteral("textDocument/references"), params), h); 1793 } 1794 1795 RequestHandle documentCompletion(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1796 { 1797 auto params = textDocumentPositionParams(document, pos); 1798 return send(init_request(QStringLiteral("textDocument/completion"), params), h); 1799 } 1800 1801 RequestHandle documentCompletionResolve(const LSPCompletionItem &c, const GenericReplyHandler &h) 1802 { 1803 QJsonObject params; 1804 auto dataDoc = QJsonDocument::fromJson(c.data); 1805 if (dataDoc.isObject()) { 1806 params[QStringLiteral("data")] = dataDoc.object(); 1807 } else { 1808 params[QStringLiteral("data")] = dataDoc.array(); 1809 } 1810 params[QLatin1String(MEMBER_DETAIL)] = c.detail; 1811 params[QStringLiteral("insertText")] = c.insertText; 1812 params[QStringLiteral("sortText")] = c.sortText; 1813 params[QStringLiteral("textEdit")] = QJsonObject{{QStringLiteral("newText"), c.textEdit.newText}, {QStringLiteral("range"), to_json(c.textEdit.range)}}; 1814 params[QLatin1String(MEMBER_LABEL)] = c.originalLabel; 1815 params[QLatin1String(MEMBER_KIND)] = (int)c.kind; 1816 return send(init_request(QStringLiteral("completionItem/resolve"), params), h); 1817 } 1818 1819 RequestHandle signatureHelp(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1820 { 1821 auto params = textDocumentPositionParams(document, pos); 1822 return send(init_request(QStringLiteral("textDocument/signatureHelp"), params), h); 1823 } 1824 1825 RequestHandle selectionRange(const QUrl &document, const QList<LSPPosition> &positions, const GenericReplyHandler &h) 1826 { 1827 auto params = textDocumentPositionsParams(document, positions); 1828 return send(init_request(QStringLiteral("textDocument/selectionRange"), params), h); 1829 } 1830 1831 RequestHandle clangdSwitchSourceHeader(const QUrl &document, const GenericReplyHandler &h) 1832 { 1833 auto params = QJsonObject{{QLatin1String(MEMBER_URI), encodeUrl(document)}}; 1834 return send(init_request(QStringLiteral("textDocument/switchSourceHeader"), params), h); 1835 } 1836 1837 RequestHandle clangdMemoryUsage(const GenericReplyHandler &h) 1838 { 1839 return send(init_request(QStringLiteral("$/memoryUsage"), QJsonObject()), h); 1840 } 1841 1842 RequestHandle rustAnalyzerExpandMacro(const QUrl &document, const LSPPosition &pos, const GenericReplyHandler &h) 1843 { 1844 auto params = textDocumentPositionParams(document, pos); 1845 return send(init_request(QStringLiteral("rust-analyzer/expandMacro"), params), h); 1846 } 1847 1848 RequestHandle documentFormatting(const QUrl &document, const LSPFormattingOptions &options, const GenericReplyHandler &h) 1849 { 1850 auto params = documentRangeFormattingParams(document, nullptr, options); 1851 return send(init_request(QStringLiteral("textDocument/formatting"), params), h); 1852 } 1853 1854 RequestHandle documentRangeFormatting(const QUrl &document, const LSPRange &range, const LSPFormattingOptions &options, const GenericReplyHandler &h) 1855 { 1856 auto params = documentRangeFormattingParams(document, &range, options); 1857 return send(init_request(QStringLiteral("textDocument/rangeFormatting"), params), h); 1858 } 1859 1860 RequestHandle 1861 documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, QChar lastChar, const LSPFormattingOptions &options, const GenericReplyHandler &h) 1862 { 1863 auto params = documentOnTypeFormattingParams(document, pos, lastChar, options); 1864 return send(init_request(QStringLiteral("textDocument/onTypeFormatting"), params), h); 1865 } 1866 1867 RequestHandle documentRename(const QUrl &document, const LSPPosition &pos, const QString &newName, const GenericReplyHandler &h) 1868 { 1869 auto params = renameParams(document, pos, newName); 1870 return send(init_request(QStringLiteral("textDocument/rename"), params), h); 1871 } 1872 1873 RequestHandle 1874 documentCodeAction(const QUrl &document, const LSPRange &range, const QList<QString> &kinds, QList<LSPDiagnostic> diagnostics, const GenericReplyHandler &h) 1875 { 1876 auto params = codeActionParams(document, range, kinds, diagnostics); 1877 return send(init_request(QStringLiteral("textDocument/codeAction"), params), h); 1878 } 1879 1880 RequestHandle documentSemanticTokensFull(const QUrl &document, bool delta, const QString requestId, const LSPRange &range, const GenericReplyHandler &h) 1881 { 1882 auto params = textDocumentParams(document); 1883 // Delta 1884 if (delta && !requestId.isEmpty()) { 1885 params[QLatin1String(MEMBER_PREVIOUS_RESULT_ID)] = requestId; 1886 return send(init_request(QStringLiteral("textDocument/semanticTokens/full/delta"), params), h); 1887 } 1888 // Range 1889 if (range.isValid()) { 1890 params[QLatin1String(MEMBER_RANGE)] = to_json(range); 1891 return send(init_request(QStringLiteral("textDocument/semanticTokens/range"), params), h); 1892 } 1893 1894 return send(init_request(QStringLiteral("textDocument/semanticTokens/full"), params), h); 1895 } 1896 1897 RequestHandle documentInlayHint(const QUrl &document, const LSPRange &range, const GenericReplyHandler &h) 1898 { 1899 auto params = textDocumentParams(document); 1900 params[QLatin1String(MEMBER_RANGE)] = to_json(range); 1901 return send(init_request(QStringLiteral("textDocument/inlayHint"), params), h); 1902 } 1903 1904 void executeCommand(const LSPCommand &command) 1905 { 1906 auto params = executeCommandParams(command); 1907 // Pass an empty lambda as reply handler because executeCommand is a Request, but we ignore the result 1908 send(init_request(QStringLiteral("workspace/executeCommand"), params), [](const auto &) {}); 1909 } 1910 1911 void didOpen(const QUrl &document, int version, const QString &langId, const QString &text) 1912 { 1913 auto params = textDocumentParams(textDocumentItem(document, langId, text, version)); 1914 send(init_request(QStringLiteral("textDocument/didOpen"), params)); 1915 } 1916 1917 void didChange(const QUrl &document, int version, const QString &text, const QList<LSPTextDocumentContentChangeEvent> &changes) 1918 { 1919 Q_ASSERT(text.isEmpty() || changes.empty()); 1920 auto params = textDocumentParams(document, version); 1921 params[QStringLiteral("contentChanges")] = text.size() ? QJsonArray{QJsonObject{{QLatin1String(MEMBER_TEXT), text}}} : to_json(changes); 1922 send(init_request(QStringLiteral("textDocument/didChange"), params)); 1923 } 1924 1925 void didSave(const QUrl &document, const QString &text) 1926 { 1927 auto params = textDocumentParams(document); 1928 if (!text.isNull()) { 1929 params[QStringLiteral("text")] = text; 1930 } 1931 send(init_request(QStringLiteral("textDocument/didSave"), params)); 1932 } 1933 1934 void didClose(const QUrl &document) 1935 { 1936 auto params = textDocumentParams(document); 1937 send(init_request(QStringLiteral("textDocument/didClose"), params)); 1938 } 1939 1940 void didChangeConfiguration(const QJsonValue &settings) 1941 { 1942 auto params = changeConfigurationParams(settings); 1943 send(init_request(QStringLiteral("workspace/didChangeConfiguration"), params)); 1944 } 1945 1946 void didChangeWorkspaceFolders(const QList<LSPWorkspaceFolder> &added, const QList<LSPWorkspaceFolder> &removed) 1947 { 1948 auto params = changeWorkspaceFoldersParams(added, removed); 1949 send(init_request(QStringLiteral("workspace/didChangeWorkspaceFolders"), params)); 1950 } 1951 1952 void workspaceSymbol(const QString &symbol, const GenericReplyHandler &h) 1953 { 1954 auto params = QJsonObject{{QLatin1String(MEMBER_QUERY), symbol}}; 1955 send(init_request(QStringLiteral("workspace/symbol"), params), h); 1956 } 1957 1958 void processNotification(const rapidjson::Value &msg) 1959 { 1960 auto methodId = msg.FindMember(MEMBER_METHOD); 1961 if (methodId == msg.MemberEnd()) { 1962 return; 1963 } 1964 auto methodParamsIt = msg.FindMember(MEMBER_PARAMS); 1965 if (methodParamsIt == msg.MemberEnd()) { 1966 qWarning() << "Ignore because no 'params' member in notification" << QByteArray(methodId->value.GetString()); 1967 return; 1968 } 1969 1970 auto methodString = methodId->value.GetString(); 1971 auto methodLen = methodId->value.GetStringLength(); 1972 std::string_view method(methodString, methodLen); 1973 1974 const bool isObj = methodParamsIt->value.IsObject(); 1975 auto &obj = methodParamsIt->value; 1976 if (isObj && method == "textDocument/publishDiagnostics") { 1977 Q_EMIT q->publishDiagnostics(parseDiagnostics(obj)); 1978 } else if (isObj && method == "window/showMessage") { 1979 Q_EMIT q->showMessage(parseMessage(obj)); 1980 } else if (isObj && method == "window/logMessage") { 1981 Q_EMIT q->logMessage(parseMessage(obj)); 1982 } else if (isObj && method == "$/progress") { 1983 Q_EMIT q->workDoneProgress(parseWorkDone(obj)); 1984 } else { 1985 qCWarning(LSPCLIENT) << "discarding notification" << method.data() << ", params is object:" << isObj; 1986 } 1987 } 1988 1989 ReplyHandler<QJsonValue> prepareResponse(const QVariant &msgid) 1990 { 1991 // allow limited number of outstanding requests 1992 auto ctx = QPointer<LSPClientServer>(q); 1993 m_requests.push_back(msgid); 1994 if (m_requests.size() > MAX_REQUESTS) { 1995 m_requests.pop_front(); 1996 } 1997 1998 auto h = [ctx, this, msgid](const QJsonValue &response) { 1999 if (!ctx) { 2000 return; 2001 } 2002 auto index = m_requests.indexOf(msgid); 2003 if (index >= 0) { 2004 m_requests.remove(index); 2005 write(init_response(response), nullptr, nullptr, msgid); 2006 } else { 2007 qCWarning(LSPCLIENT) << "discarding response" << msgid; 2008 } 2009 }; 2010 return h; 2011 } 2012 2013 template<typename ReplyType> 2014 static ReplyHandler<ReplyType> responseHandler(const ReplyHandler<QJsonValue> &h, 2015 typename utils::identity<std::function<QJsonValue(const ReplyType &)>>::type c) 2016 { 2017 return [h, c](const ReplyType &m) { 2018 h(c(m)); 2019 }; 2020 } 2021 2022 // pretty rare and limited use, but anyway 2023 void processRequest(const rapidjson::Value &msg) 2024 { 2025 auto method = GetStringValue(msg, MEMBER_METHOD); 2026 2027 // could be number or string, let's retain as-is 2028 QVariant msgId; 2029 if (msg[MEMBER_ID].IsString()) { 2030 msgId = GetStringValue(msg, MEMBER_ID); 2031 } else { 2032 msgId = GetIntValue(msg, MEMBER_ID, -1); 2033 } 2034 2035 const auto ¶ms = GetJsonObjectForKey(msg, MEMBER_PARAMS); 2036 bool handled = false; 2037 if (method == QLatin1String("workspace/applyEdit")) { 2038 auto h = responseHandler<LSPApplyWorkspaceEditResponse>(prepareResponse(msgId), applyWorkspaceEditResponse); 2039 Q_EMIT q->applyEdit(parseApplyWorkspaceEditParams(params), h, handled); 2040 } else if (method == QLatin1String("workspace/workspaceFolders")) { 2041 // helper to convert from array to value 2042 auto workspaceFolders = [](const QList<LSPWorkspaceFolder> &p) -> QJsonValue { 2043 return to_json(p); 2044 }; 2045 auto h = responseHandler<QList<LSPWorkspaceFolder>>(prepareResponse(msgId), workspaceFolders); 2046 Q_EMIT q->workspaceFolders(h, handled); 2047 } else if (method == QLatin1String("window/workDoneProgress/create") || method == QLatin1String("client/registerCapability")) { 2048 // void reply to accept 2049 // that should trigger subsequent progress notifications 2050 // for now; also no need to extract supplied token 2051 auto h = prepareResponse(msgId); 2052 h(QJsonValue()); 2053 } else if (method == QLatin1String("workspace/semanticTokens/refresh")) { 2054 // void reply to accept, we don't handle this at the moment, but some servers send it and require some valid reply 2055 // e.g. typst-lsp, see https://invent.kde.org/utilities/kate/-/issues/108 2056 auto h = prepareResponse(msgId); 2057 h(QJsonValue()); 2058 } else if (method == QLatin1String("window/showMessageRequest")) { 2059 auto actions = GetJsonArrayForKey(params, MEMBER_ACTIONS).GetArray(); 2060 QList<LSPMessageRequestAction> v; 2061 auto responder = prepareResponse(msgId); 2062 for (const auto &action : actions) { 2063 QString title = GetStringValue(action, MEMBER_TITLE); 2064 QJsonObject actionToSubmit = QJsonDocument::fromJson(rapidJsonStringify(action)).object(); 2065 v.append(LSPMessageRequestAction{title, [=]() { 2066 responder(actionToSubmit); 2067 }}); 2068 } 2069 auto nullResponse = [responder]() { 2070 responder(QJsonObject()); 2071 }; 2072 Q_EMIT q->showMessageRequest(parseMessage(params), v, nullResponse, handled); 2073 } else { 2074 write(init_error(LSPErrorCode::MethodNotFound, method), nullptr, nullptr, msgId); 2075 qCWarning(LSPCLIENT) << "discarding request" << method; 2076 } 2077 } 2078 }; 2079 2080 // generic convert handler 2081 // sprinkle some connection-like context safety 2082 // not so likely relevant/needed due to typical sequence of events, 2083 // but in case the latter would be changed in surprising ways ... 2084 template<typename ReplyType> 2085 static GenericReplyHandler 2086 make_handler(const ReplyHandler<ReplyType> &h, const QObject *context, typename utils::identity<std::function<ReplyType(const GenericReplyType &)>>::type c) 2087 { 2088 // empty provided handler leads to empty handler 2089 if (!h || !c) { 2090 return nullptr; 2091 } 2092 2093 QPointer<const QObject> ctx(context); 2094 return [ctx, h, c](const GenericReplyType &m) { 2095 if (ctx) { 2096 h(c(m)); 2097 } 2098 }; 2099 } 2100 2101 LSPClientServer::LSPClientServer(const QStringList &server, const QUrl &root, const QString &langId, const QJsonValue &init, ExtraServerConfig config) 2102 : d(new LSPClientServerPrivate(this, server, root, langId, init, config)) 2103 { 2104 } 2105 2106 LSPClientServer::~LSPClientServer() 2107 { 2108 delete d; 2109 } 2110 2111 const QStringList &LSPClientServer::cmdline() const 2112 { 2113 return d->cmdline(); 2114 } 2115 2116 const QUrl &LSPClientServer::root() const 2117 { 2118 return d->root(); 2119 } 2120 2121 const QString &LSPClientServer::langId() const 2122 { 2123 return d->langId(); 2124 } 2125 2126 LSPClientServer::State LSPClientServer::state() const 2127 { 2128 return d->state(); 2129 } 2130 2131 const LSPServerCapabilities &LSPClientServer::capabilities() const 2132 { 2133 return d->capabilities(); 2134 } 2135 2136 bool LSPClientServer::start(bool forwardStdError) 2137 { 2138 return d->start(forwardStdError); 2139 } 2140 2141 void LSPClientServer::stop(int to_t, int to_k) 2142 { 2143 return d->stop(to_t, to_k); 2144 } 2145 2146 int LSPClientServer::cancel(int reqid) 2147 { 2148 return d->cancel(reqid); 2149 } 2150 2151 LSPClientServer::RequestHandle 2152 LSPClientServer::documentSymbols(const QUrl &document, const QObject *context, const DocumentSymbolsReplyHandler &h, const ErrorReplyHandler &eh) 2153 { 2154 return d->documentSymbols(document, make_handler(h, context, parseDocumentSymbols), make_handler(eh, context, parseResponseError)); 2155 } 2156 2157 LSPClientServer::RequestHandle 2158 LSPClientServer::documentDefinition(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) 2159 { 2160 return d->documentDefinition(document, pos, make_handler(h, context, parseDocumentLocation)); 2161 } 2162 2163 LSPClientServer::RequestHandle 2164 LSPClientServer::documentImplementation(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) 2165 { 2166 return d->documentImplementation(document, pos, make_handler(h, context, parseDocumentLocation)); 2167 } 2168 2169 LSPClientServer::RequestHandle 2170 LSPClientServer::documentDeclaration(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) 2171 { 2172 return d->documentDeclaration(document, pos, make_handler(h, context, parseDocumentLocation)); 2173 } 2174 2175 LSPClientServer::RequestHandle 2176 LSPClientServer::documentTypeDefinition(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentDefinitionReplyHandler &h) 2177 { 2178 return d->documentTypeDefinition(document, pos, make_handler(h, context, parseDocumentLocation)); 2179 } 2180 2181 LSPClientServer::RequestHandle 2182 LSPClientServer::documentHover(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHoverReplyHandler &h) 2183 { 2184 return d->documentHover(document, pos, make_handler(h, context, parseHover)); 2185 } 2186 2187 LSPClientServer::RequestHandle 2188 LSPClientServer::documentHighlight(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentHighlightReplyHandler &h) 2189 { 2190 return d->documentHighlight(document, pos, make_handler(h, context, parseDocumentHighlightList)); 2191 } 2192 2193 LSPClientServer::RequestHandle 2194 LSPClientServer::documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, const QObject *context, const DocumentDefinitionReplyHandler &h) 2195 { 2196 return d->documentReferences(document, pos, decl, make_handler(h, context, parseDocumentLocation)); 2197 } 2198 2199 LSPClientServer::RequestHandle 2200 LSPClientServer::documentCompletion(const QUrl &document, const LSPPosition &pos, const QObject *context, const DocumentCompletionReplyHandler &h) 2201 { 2202 return d->documentCompletion(document, pos, make_handler(h, context, parseDocumentCompletion)); 2203 } 2204 2205 LSPClientServer::RequestHandle 2206 LSPClientServer::documentCompletionResolve(const LSPCompletionItem &c, const QObject *context, const DocumentCompletionResolveReplyHandler &h) 2207 { 2208 return d->documentCompletionResolve(c, make_handler(h, context, parseDocumentCompletionResolve)); 2209 } 2210 2211 LSPClientServer::RequestHandle 2212 LSPClientServer::signatureHelp(const QUrl &document, const LSPPosition &pos, const QObject *context, const SignatureHelpReplyHandler &h) 2213 { 2214 return d->signatureHelp(document, pos, make_handler(h, context, parseSignatureHelp)); 2215 } 2216 2217 LSPClientServer::RequestHandle 2218 LSPClientServer::selectionRange(const QUrl &document, const QList<LSPPosition> &positions, const QObject *context, const SelectionRangeReplyHandler &h) 2219 { 2220 return d->selectionRange(document, positions, make_handler(h, context, parseSelectionRanges)); 2221 } 2222 2223 LSPClientServer::RequestHandle LSPClientServer::clangdSwitchSourceHeader(const QUrl &document, const QObject *context, const SwitchSourceHeaderHandler &h) 2224 { 2225 return d->clangdSwitchSourceHeader(document, make_handler(h, context, parseClangdSwitchSourceHeader)); 2226 } 2227 2228 LSPClientServer::RequestHandle LSPClientServer::clangdMemoryUsage(const QObject *context, const MemoryUsageHandler &h) 2229 { 2230 auto identity = [](const rapidjson::Value &p) -> QString { 2231 rapidjson::StringBuffer buf; 2232 rapidjson::PrettyWriter w(buf); 2233 p.Accept(w); 2234 return QString::fromUtf8(buf.GetString(), buf.GetSize()); 2235 }; 2236 return d->clangdMemoryUsage(make_handler(h, context, identity)); 2237 } 2238 2239 LSPClientServer::RequestHandle 2240 LSPClientServer::rustAnalyzerExpandMacro(const QObject *context, const QUrl &document, const LSPPosition &pos, const ExpandMacroHandler &h) 2241 { 2242 return d->rustAnalyzerExpandMacro(document, pos, make_handler(h, context, parseExpandedMacro)); 2243 } 2244 2245 LSPClientServer::RequestHandle 2246 LSPClientServer::documentFormatting(const QUrl &document, const LSPFormattingOptions &options, const QObject *context, const FormattingReplyHandler &h) 2247 { 2248 return d->documentFormatting(document, options, make_handler(h, context, parseTextEdit)); 2249 } 2250 2251 LSPClientServer::RequestHandle LSPClientServer::documentRangeFormatting(const QUrl &document, 2252 const LSPRange &range, 2253 const LSPFormattingOptions &options, 2254 const QObject *context, 2255 const FormattingReplyHandler &h) 2256 { 2257 return d->documentRangeFormatting(document, range, options, make_handler(h, context, parseTextEdit)); 2258 } 2259 2260 LSPClientServer::RequestHandle LSPClientServer::documentOnTypeFormatting(const QUrl &document, 2261 const LSPPosition &pos, 2262 const QChar lastChar, 2263 const LSPFormattingOptions &options, 2264 const QObject *context, 2265 const FormattingReplyHandler &h) 2266 { 2267 return d->documentOnTypeFormatting(document, pos, lastChar, options, make_handler(h, context, parseTextEdit)); 2268 } 2269 2270 LSPClientServer::RequestHandle LSPClientServer::documentRename(const QUrl &document, 2271 const LSPPosition &pos, 2272 const QString &newName, 2273 const QObject *context, 2274 const WorkspaceEditReplyHandler &h) 2275 { 2276 return d->documentRename(document, pos, newName, make_handler(h, context, parseWorkSpaceEdit)); 2277 } 2278 2279 LSPClientServer::RequestHandle LSPClientServer::documentCodeAction(const QUrl &document, 2280 const LSPRange &range, 2281 const QList<QString> &kinds, 2282 QList<LSPDiagnostic> diagnostics, 2283 const QObject *context, 2284 const CodeActionReplyHandler &h) 2285 { 2286 return d->documentCodeAction(document, range, kinds, std::move(diagnostics), make_handler(h, context, parseCodeAction)); 2287 } 2288 2289 LSPClientServer::RequestHandle 2290 LSPClientServer::documentSemanticTokensFull(const QUrl &document, const QString requestId, const QObject *context, const SemanticTokensDeltaReplyHandler &h) 2291 { 2292 auto invalidRange = KTextEditor::Range::invalid(); 2293 return d->documentSemanticTokensFull(document, /* delta = */ false, requestId, invalidRange, make_handler(h, context, parseSemanticTokensDelta)); 2294 } 2295 2296 LSPClientServer::RequestHandle LSPClientServer::documentSemanticTokensFullDelta(const QUrl &document, 2297 const QString requestId, 2298 const QObject *context, 2299 const SemanticTokensDeltaReplyHandler &h) 2300 { 2301 auto invalidRange = KTextEditor::Range::invalid(); 2302 return d->documentSemanticTokensFull(document, /* delta = */ true, requestId, invalidRange, make_handler(h, context, parseSemanticTokensDelta)); 2303 } 2304 2305 LSPClientServer::RequestHandle 2306 LSPClientServer::documentSemanticTokensRange(const QUrl &document, const LSPRange &range, const QObject *context, const SemanticTokensDeltaReplyHandler &h) 2307 { 2308 return d->documentSemanticTokensFull(document, /* delta = */ false, QString(), range, make_handler(h, context, parseSemanticTokensDelta)); 2309 } 2310 2311 LSPClientServer::RequestHandle 2312 LSPClientServer::documentInlayHint(const QUrl &document, const LSPRange &range, const QObject *context, const InlayHintsReplyHandler &h) 2313 { 2314 return d->documentInlayHint(document, range, make_handler(h, context, parseInlayHints)); 2315 } 2316 2317 void LSPClientServer::executeCommand(const LSPCommand &command) 2318 { 2319 return d->executeCommand(command); 2320 } 2321 2322 void LSPClientServer::didOpen(const QUrl &document, int version, const QString &langId, const QString &text) 2323 { 2324 return d->didOpen(document, version, langId, text); 2325 } 2326 2327 void LSPClientServer::didChange(const QUrl &document, int version, const QString &text, const QList<LSPTextDocumentContentChangeEvent> &changes) 2328 { 2329 return d->didChange(document, version, text, changes); 2330 } 2331 2332 void LSPClientServer::didSave(const QUrl &document, const QString &text) 2333 { 2334 return d->didSave(document, text); 2335 } 2336 2337 void LSPClientServer::didClose(const QUrl &document) 2338 { 2339 return d->didClose(document); 2340 } 2341 2342 void LSPClientServer::didChangeConfiguration(const QJsonValue &settings) 2343 { 2344 return d->didChangeConfiguration(settings); 2345 } 2346 2347 void LSPClientServer::didChangeWorkspaceFolders(const QList<LSPWorkspaceFolder> &added, const QList<LSPWorkspaceFolder> &removed) 2348 { 2349 return d->didChangeWorkspaceFolders(added, removed); 2350 } 2351 2352 void LSPClientServer::workspaceSymbol(const QString &symbol, const QObject *context, const WorkspaceSymbolsReplyHandler &h) 2353 { 2354 return d->workspaceSymbol(symbol, make_handler(h, context, parseWorkspaceSymbols)); 2355 } 2356 2357 #include "moc_lspclientserver.cpp"