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 &params = 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 &params = 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 &params = 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"