lspclientserver.cpp 48.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*  SPDX-License-Identifier: MIT

    Copyright (C) 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com>

    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
24 25 26 27 28 29 30 31 32 33 34

#include "lspclientserver.h"

#include "lspclient_debug.h"

#include <QScopedPointer>
#include <QProcess>
#include <QVariantMap>

#include <QJsonDocument>
#include <QJsonObject>
35
#include <QJsonArray>
36 37
#include <QCoreApplication>
#include <QTime>
38
#include <QFileInfo>
39

40 41 42
// good/bad old school; allows easier concatenate
#define CONTENT_LENGTH  "Content-Length"

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
static const QString MEMBER_ID = QStringLiteral("id");
static const QString MEMBER_METHOD = QStringLiteral("method");
static const QString MEMBER_ERROR = QStringLiteral("error");
static const QString MEMBER_CODE = QStringLiteral("code");
static const QString MEMBER_MESSAGE = QStringLiteral("message");
static const QString MEMBER_PARAMS = QStringLiteral("params");
static const QString MEMBER_RESULT = QStringLiteral("result");
static const QString MEMBER_URI = QStringLiteral("uri");
static const QString MEMBER_VERSION = QStringLiteral("version");
static const QString MEMBER_START = QStringLiteral("start");
static const QString MEMBER_END = QStringLiteral("end");
static const QString MEMBER_POSITION = QStringLiteral("position");
static const QString MEMBER_LOCATION = QStringLiteral("location");
static const QString MEMBER_RANGE = QStringLiteral("range");
static const QString MEMBER_LINE = QStringLiteral("line");
static const QString MEMBER_CHARACTER = QStringLiteral("character");
static const QString MEMBER_KIND = QStringLiteral("kind");
static const QString MEMBER_TEXT = QStringLiteral("text");
static const QString MEMBER_LANGID = QStringLiteral("languageId");
static const QString MEMBER_LABEL = QStringLiteral("label");
static const QString MEMBER_DOCUMENTATION = QStringLiteral("documentation");
static const QString MEMBER_DETAIL = QStringLiteral("detail");
65 66 67 68 69
static const QString MEMBER_COMMAND = QStringLiteral("command");
static const QString MEMBER_EDIT = QStringLiteral("edit");
static const QString MEMBER_TITLE = QStringLiteral("title");
static const QString MEMBER_ARGUMENTS = QStringLiteral("arguments");
static const QString MEMBER_DIAGNOSTICS = QStringLiteral("diagnostics");
70 71

// message construction helpers
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
static QJsonObject
to_json(const LSPPosition & pos)
{
    return QJsonObject {
        { MEMBER_LINE, pos.line() },
        { MEMBER_CHARACTER, pos.column() }
    };
}

static QJsonObject
to_json(const LSPRange & range)
{
    return QJsonObject {
        { MEMBER_START, to_json(range.start()) },
        { MEMBER_END, to_json(range.end()) }
    };
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
static QJsonValue
to_json(const LSPLocation & location)
{
    if (location.uri.isValid()) {
        return QJsonObject {
            { MEMBER_URI, location.uri.toString() },
            { MEMBER_RANGE, to_json(location.range) }
        };
    }
    return QJsonValue();
}

static QJsonValue
to_json(const LSPDiagnosticRelatedInformation & related)
{
    auto loc = to_json(related.location);
    if (loc.isObject()) {
        return QJsonObject {
            { MEMBER_LOCATION, to_json(related.location) },
            { MEMBER_MESSAGE, related.message }
        };
    }
    return QJsonValue();
}

static QJsonObject
to_json(const LSPDiagnostic & diagnostic)
{
    // required
    auto result = QJsonObject();
    result[MEMBER_RANGE] = to_json(diagnostic.range);
    result[MEMBER_MESSAGE] = diagnostic.message;
    // optional
    if (!diagnostic.code.isEmpty())
        result[QStringLiteral("code")] = diagnostic.code;
    if (diagnostic.severity != LSPDiagnosticSeverity::Unknown)
        result[QStringLiteral("severity")] = (int) diagnostic.severity;
    if (!diagnostic.source.isEmpty())
        result[QStringLiteral("source")] = diagnostic.source;
    auto related = to_json(diagnostic.relatedInformation);
    if (related.isObject()) {
        result[QStringLiteral("relatedInformation")] = related;
    }
    return result;
}

136 137 138 139 140 141 142 143 144 145 146 147 148
static QJsonArray
to_json(const QList<LSPTextDocumentContentChangeEvent> & changes)
{
    QJsonArray result;
    for (const auto &change: changes) {
        result.push_back(QJsonObject {
                             {MEMBER_RANGE, to_json(change.range)},
                             {MEMBER_TEXT, change.text}
                         });
    }
    return result;
}

149
static QJsonObject
150 151
versionedTextDocumentIdentifier(const QUrl & document, int version = -1)
{
152
    QJsonObject map { { MEMBER_URI, document.toString() } };
153 154 155 156 157
    if (version >= 0)
        map[MEMBER_VERSION] = version;
    return map;
}

158
static QJsonObject
159 160 161 162 163 164 165 166 167
textDocumentItem(const QUrl & document, const QString & lang,
                 const QString & text, int version)
{
    auto map = versionedTextDocumentIdentifier(document, version);
    map[MEMBER_TEXT] = text;
    map[MEMBER_LANGID] = lang;
    return map;
}

168 169
static QJsonObject
textDocumentParams(const QJsonObject & m)
170
{
171
    return QJsonObject {
172 173 174 175
        { QStringLiteral("textDocument"), m}
    };
}

176
static QJsonObject
177 178 179
textDocumentParams(const QUrl & document, int version = -1)
{ return textDocumentParams(versionedTextDocumentIdentifier(document, version)); }

180
static QJsonObject
181 182 183
textDocumentPositionParams(const QUrl & document, LSPPosition pos)
{
    auto params = textDocumentParams(document);
184
    params[MEMBER_POSITION] = to_json(pos);
185 186 187
    return params;
}

188 189 190 191 192 193 194 195 196 197
static QJsonObject
referenceParams(const QUrl & document, LSPPosition pos, bool decl)
{
    auto params = textDocumentPositionParams(document, pos);
    params[QStringLiteral("context")] = QJsonObject {
        { QStringLiteral("includeDeclaration"), decl }
    };
    return params;
}

198 199 200 201 202 203 204 205 206
static QJsonObject
formattingOptions(const LSPFormattingOptions & _options)
{
    auto options = _options.extra;
    options[QStringLiteral("tabSize")] = _options.tabSize;
    options[QStringLiteral("insertSpaces")] = _options.insertSpaces;
    return options;
}

207 208
static QJsonObject
documentRangeFormattingParams(const QUrl & document, const LSPRange *range,
209
    const LSPFormattingOptions & _options)
210 211 212 213 214
{
    auto params = textDocumentParams(document);
    if (range) {
        params[MEMBER_RANGE] = to_json(*range);
    }
215 216 217 218 219 220 221 222 223 224 225
    params[QStringLiteral("options")] = formattingOptions(_options);
    return params;
}

static QJsonObject
documentOnTypeFormattingParams(const QUrl & document, const LSPPosition & pos,
    const QChar & lastChar, const LSPFormattingOptions & _options)
{
    auto params = textDocumentPositionParams(document, pos);
    params[QStringLiteral("ch")] = QString(lastChar);
    params[QStringLiteral("options")] = formattingOptions(_options);
226 227 228
    return params;
}

229 230 231 232 233 234 235 236
static QJsonObject
renameParams(const QUrl & document, const LSPPosition & pos, const QString & newName)
{
    auto params = textDocumentPositionParams(document, pos);
    params[QStringLiteral("newName")] = newName;
    return params;
}

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
static QJsonObject
codeActionParams(const QUrl & document, const LSPRange & range,
    QList<QString> kinds, QList<LSPDiagnostic> diagnostics)
{
    auto params = textDocumentParams(document);
    params[MEMBER_RANGE] = to_json(range);
    QJsonObject context;
    QJsonArray diags;
    for (const auto& diagnostic: diagnostics) {
        diags.push_back(to_json(diagnostic));
    }
    context[MEMBER_DIAGNOSTICS] = diags;
    if (kinds.length())
        context[QStringLiteral("only")] = QJsonArray::fromStringList(kinds);
    params[QStringLiteral("context")] = context;
    return params;
}
254

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
static QJsonObject
executeCommandParams(const QString & command, const QJsonValue & args)
{
    return QJsonObject {
        { MEMBER_COMMAND, command },
        { MEMBER_ARGUMENTS, args }
    };
}

static QJsonObject
applyWorkspaceEditResponse(const LSPApplyWorkspaceEditResponse & response)
{
    return QJsonObject {
        { QStringLiteral("applied"), response.applied },
        { QStringLiteral("failureReason"), response.failureReason }
    };
}

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
static void
from_json(QVector<QChar> & trigger, const QJsonValue & json)
{
    for (const auto & t : json.toArray()) {
        auto st = t.toString();
        if (st.length())
            trigger.push_back(st.at(0));
    }
}

static void
from_json(LSPCompletionOptions & options, const QJsonValue & json)
{
    if (json.isObject()) {
        auto ob = json.toObject();
        options.provider = true;
        options.resolveProvider = ob.value(QStringLiteral("resolveProvider")).toBool();
        from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters")));
    }
}

static void
from_json(LSPSignatureHelpOptions & options, const QJsonValue & json)
{
    if (json.isObject()) {
        auto ob = json.toObject();
        options.provider = true;
        from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters")));
    }
}

304 305 306 307 308 309 310 311 312 313 314 315 316 317
static void
from_json(LSPDocumentOnTypeFormattingOptions & options, const QJsonValue & json)
{
    if (json.isObject()) {
        auto ob = json.toObject();
        options.provider = true;
        from_json(options.triggerCharacters, ob.value(QStringLiteral("moreTriggerCharacter")));
        auto trigger = ob.value(QStringLiteral("firstTriggerCharacter")).toString();
        if (trigger.size()) {
            options.triggerCharacters.insert(0, trigger.at(0));
        }
    }
}

318 319 320 321 322 323 324 325 326 327 328 329 330
static void
from_json(LSPServerCapabilities & caps, const QJsonObject & json)
{
    auto sync = json.value(QStringLiteral("textDocumentSync"));
    caps.textDocumentSync = (LSPDocumentSyncKind)
            (sync.isObject() ? sync.toObject().value(QStringLiteral("change")) : sync).toInt((int)LSPDocumentSyncKind::None);
    caps.hoverProvider = json.value(QStringLiteral("hoverProvider")).toBool();
    from_json(caps.completionProvider, json.value(QStringLiteral("completionProvider")));
    from_json(caps.signatureHelpProvider, json.value(QStringLiteral("signatureHelpProvider")));
    caps.definitionProvider = json.value(QStringLiteral("definitionProvider")).toBool();
    caps.declarationProvider = json.value(QStringLiteral("declarationProvider")).toBool();
    caps.referencesProvider = json.value(QStringLiteral("referencesProvider")).toBool();
    caps.documentSymbolProvider = json.value(QStringLiteral("documentSymbolProvider")).toBool();
331
    caps.documentHighlightProvider = json.value(QStringLiteral("documentHighlightProvider")).toBool();
332 333
    caps.documentFormattingProvider = json.value(QStringLiteral("documentFormattingProvider")).toBool();
    caps.documentRangeFormattingProvider = json.value(QStringLiteral("documentRangeFormattingProvider")).toBool();
334
    from_json(caps.documentOnTypeFormattingProvider, json.value(QStringLiteral("documentOnTypeFormattingProvider")));
335
    caps.renameProvider = json.value(QStringLiteral("renameProvider")).toBool();
336 337
    auto codeActionProvider = json.value(QStringLiteral("codeActionProvider"));
    caps.codeActionProvider = codeActionProvider.toBool() || codeActionProvider.isObject();
338 339
}

340 341 342 343 344 345 346 347 348 349 350 351 352
// follow suit; as performed in kate docmanager
// normalize at this stage/layer to avoid surprises elsewhere
// sadly this is not a single QUrl method as one might hope ...
static QUrl
normalizeUrl(const QUrl & url)
{
    // Resolve symbolic links for local files (done anyway in KTextEditor)
    if (url.isLocalFile()) {
        QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath();
        if (!normalizedUrl.isEmpty()) {
            return QUrl::fromLocalFile(normalizedUrl);
        }
    }
353

354 355 356
    // else: cleanup only the .. stuff
    return url.adjusted(QUrl::NormalizePathSegments);
}
357

358 359
static LSPMarkupContent
parseMarkupContent(const QJsonValue & v)
360
{
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
    LSPMarkupContent ret;
    if (v.isObject()) {
        const auto& vm = v.toObject();
        ret.value = vm.value(QStringLiteral("value")).toString();
        auto kind = vm.value(MEMBER_KIND).toString();
        if (kind == QStringLiteral("plaintext")) {
            ret.kind = LSPMarkupKind::PlainText;
        } else if (kind == QStringLiteral("markdown")) {
            ret.kind = LSPMarkupKind::MarkDown;
        }
    } else if (v.isString()) {
        ret.kind = LSPMarkupKind::PlainText;
        ret.value = v.toString();
    }
    return ret;
}
377

378 379 380 381 382 383 384
static LSPPosition
parsePosition(const QJsonObject & m)
{
    auto line = m.value(MEMBER_LINE).toInt(-1);
    auto column = m.value(MEMBER_CHARACTER).toInt(-1);
    return {line, column};
}
385

386 387 388
static bool
isPositionValid(const LSPPosition & pos)
{ return pos.isValid(); }
389

390 391 392 393 394 395 396
static LSPRange
parseRange(const QJsonObject & range)
{
    auto startpos = parsePosition(range.value(MEMBER_START).toObject());
    auto endpos = parsePosition(range.value(MEMBER_END).toObject());
    return {startpos, endpos};
}
397

398 399 400 401 402 403 404
static LSPLocation
parseLocation(const QJsonObject & loc)
{
    auto uri = normalizeUrl(QUrl(loc.value(MEMBER_URI).toString()));
    auto range = parseRange(loc.value(MEMBER_RANGE).toObject());
    return {QUrl(uri), range};
}
405

406 407 408 409 410 411 412 413
static LSPDocumentHighlight
parseDocumentHighlight(const QJsonValue & result)
{
    auto hover = result.toObject();
    auto range = parseRange(hover.value(MEMBER_RANGE).toObject());
    auto kind = (LSPDocumentHighlightKind)hover.value(MEMBER_KIND).toInt((int)LSPDocumentHighlightKind::Text); // default is DocumentHighlightKind.Text
    return {range, kind};
}
414

415 416 417 418 419 420 421 422 423 424 425 426
static QList<LSPDocumentHighlight>
parseDocumentHighlightList(const QJsonValue & result)
{
    QList<LSPDocumentHighlight> ret;
    // could be array
    if (result.isArray()) {
        for (const auto & def : result.toArray()) {
            ret.push_back(parseDocumentHighlight(def));
        }
    } else if (result.isObject()) {
        // or a single value
        ret.push_back(parseDocumentHighlight(result));
427
    }
428 429
    return ret;
}
430

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
static LSPMarkupContent
parseHoverContentElement(const QJsonValue & contents)
{
    LSPMarkupContent result;
    if (contents.isString()) {
        result.value = contents.toString();
    } else {
        // should be object, pretend so
        auto cont = contents.toObject();
        auto text = cont.value(QStringLiteral("value")).toString();
        if (text.isEmpty()) {
            // nothing to lose, try markdown
            result = parseMarkupContent(contents);
        } else {
            result.value = text;
446
        }
447
    }
448 449 450 451
    if (result.value.length())
        result.kind = LSPMarkupKind::PlainText;
    return result;
}
452

453 454 455 456 457 458 459 460 461 462 463 464 465
static LSPHover
parseHover(const QJsonValue & result)
{
    LSPHover ret;
    auto hover = result.toObject();
    // normalize content which can be of many forms
    ret.range = parseRange(hover.value(MEMBER_RANGE).toObject());
    auto contents = hover.value(QStringLiteral("contents"));

    // support the deprecated MarkedString[] variant, used by e.g. Rust rls
    if (contents.isArray()) {
        for (const auto & c : contents.toArray()) {
            ret.contents.push_back(parseHoverContentElement(c));
466
        }
467 468
    } else {
        ret.contents.push_back(parseHoverContentElement(contents));
469 470
    }

471 472
    return ret;
}
473

474 475 476 477 478 479 480 481 482 483
static QList<LSPSymbolInformation>
parseDocumentSymbols(const QJsonValue & result)
{
    // the reply could be old SymbolInformation[] or new (hierarchical) DocumentSymbol[]
    // try to parse it adaptively in any case
    // if new style, hierarchy is specified clearly in reply
    // if old style, it is assumed the values enter linearly, that is;
    // * a parent/container is listed before its children
    // * if a name is defined/declared several times and then used as a parent,
    //   then it is the last instance that is used as a parent
484

485 486
    QList<LSPSymbolInformation> ret;
    QMap<QString, LSPSymbolInformation*> index;
487

488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
    std::function<void(const QJsonObject & symbol, LSPSymbolInformation *parent)> parseSymbol =
        [&] (const QJsonObject & symbol, LSPSymbolInformation *parent) {
        // if flat list, try to find parent by name
        if (!parent) {
            auto container = symbol.value(QStringLiteral("containerName")).toString();
            parent = index.value(container, nullptr);
        }
        auto list = parent ? &parent->children : &ret;
        const auto& location = symbol.value(MEMBER_LOCATION).toObject();
        const auto& mrange = symbol.contains(MEMBER_RANGE) ?
                    symbol.value(MEMBER_RANGE) : location.value(MEMBER_RANGE);
        auto range = parseRange(mrange.toObject());
        if (isPositionValid(range.start()) && isPositionValid(range.end())) {
            auto name = symbol.value(QStringLiteral("name")).toString();
            auto kind = (LSPSymbolKind) symbol.value(MEMBER_KIND).toInt();
            auto detail = symbol.value(MEMBER_DETAIL).toString();
            list->push_back({name, kind, range, detail});
            index[name] = &list->back();
            // proceed recursively
            for (const auto &child : symbol.value(QStringLiteral("children")).toArray())
                parseSymbol(child.toObject(), &list->back());
        }
    };
511

512 513
    for (const auto& info : result.toArray()) {
        parseSymbol(info.toObject(), nullptr);
514
    }
515 516
    return ret;
}
517

518 519 520 521 522 523 524 525 526 527 528 529
static QList<LSPLocation>
parseDocumentLocation(const QJsonValue & result)
{
    QList<LSPLocation> ret;
    // could be array
    if (result.isArray()) {
        for (const auto & def : result.toArray()) {
            ret.push_back(parseLocation(def.toObject()));
        }
    } else if (result.isObject()) {
        // or a single value
        ret.push_back(parseLocation(result.toObject()));
530
    }
531 532
    return ret;
}
533

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
static QList<LSPCompletionItem>
parseDocumentCompletion(const QJsonValue & result)
{
    QList<LSPCompletionItem> ret;
    QJsonArray items = result.toArray();
    // might be CompletionList
    if (items.size() == 0) {
        items = result.toObject().value(QStringLiteral("items")).toArray();
    }
    for (const auto & vitem : items) {
        const auto & item = vitem.toObject();
        auto label = item.value(MEMBER_LABEL).toString();
        auto detail = item.value(MEMBER_DETAIL).toString();
        auto doc = parseMarkupContent(item.value(MEMBER_DOCUMENTATION));
        auto sortText = item.value(QStringLiteral("sortText")).toString();
        if (sortText.isEmpty())
            sortText = label;
        auto insertText = item.value(QStringLiteral("insertText")).toString();
        if (insertText.isEmpty())
            insertText = label;
        auto kind = (LSPCompletionItemKind) item.value(MEMBER_KIND).toInt();
        ret.push_back({label, kind, detail, doc, sortText, insertText});
    }
    return ret;
}
559

560 561 562 563
static LSPSignatureInformation
parseSignatureInformation(const QJsonObject & json)
{
    LSPSignatureInformation info;
564

565 566 567 568 569 570 571 572 573 574 575 576 577 578 579
    info.label = json.value(MEMBER_LABEL).toString();
    info.documentation = parseMarkupContent(json.value(MEMBER_DOCUMENTATION));
    for (const auto & rpar : json.value(QStringLiteral("parameters")).toArray()) {
        auto par = rpar.toObject();
        auto label = par.value(MEMBER_LABEL);
        int begin = -1, end = -1;
        if (label.isArray()) {
            auto range = label.toArray();
            if (range.size() == 2) {
                begin = range.at(0).toInt(-1);
                end = range.at(1).toInt(-1);
                if (begin > info.label.length())
                    begin = -1;
                if (end > info.label.length())
                    end = -1;
580
            }
581 582 583 584 585 586 587
        } else {
            auto sub = label.toString();
            if (sub.length()) {
                begin = info.label.indexOf(sub);
                if (begin >= 0) {
                    end = begin + sub.length();
                }
588 589
            }
        }
590
        info.parameters.push_back({begin, end});
591
    }
592 593
    return info;
}
594

595 596 597 598 599
static LSPSignatureHelp
parseSignatureHelp(const QJsonValue & result)
{
    LSPSignatureHelp ret;
    QJsonObject sig = result.toObject();
600

601 602
    for (const auto & info: sig.value(QStringLiteral("signatures")).toArray()) {
        ret.signatures.push_back(parseSignatureInformation(info.toObject()));
603
    }
604 605 606 607 608 609
    ret.activeSignature = sig.value(QStringLiteral("activeSignature")).toInt(0);
    ret.activeParameter = sig.value(QStringLiteral("activeParameter")).toInt(0);
    ret.activeSignature = qMin(qMax(ret.activeSignature, 0), ret.signatures.size());
    ret.activeParameter = qMin(qMax(ret.activeParameter, 0), ret.signatures.size());
    return ret;
}
610

611 612 613 614 615 616 617 618 619
static QList<LSPTextEdit>
parseTextEdit(const QJsonValue & result)
{
    QList<LSPTextEdit> ret;
    for (const auto &redit: result.toArray()) {
        auto edit = redit.toObject();
        auto text = edit.value(QStringLiteral("newText")).toString();
        auto range = parseRange(edit.value(MEMBER_RANGE).toObject());
        ret.push_back({range, text});
620
    }
621 622
    return ret;
}
623

624 625 626 627 628 629 630
static LSPWorkspaceEdit
parseWorkSpaceEdit(const QJsonValue & result)
{
    QHash<QUrl, QList<LSPTextEdit>> ret;
    auto changes = result.toObject().value(QStringLiteral("changes")).toObject();
    for (auto it = changes.begin(); it != changes.end(); ++it) {
        ret.insert(normalizeUrl(QUrl(it.key())), parseTextEdit(it.value()));
631
    }
632 633
    return {ret};
}
634

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
static LSPCommand
parseCommand(const QJsonObject & result)
{
    auto title = result.value(MEMBER_TITLE).toString();
    auto command = result.value(MEMBER_COMMAND).toString();
    auto args = result.value(MEMBER_ARGUMENTS).toArray();
    return { title, command, args };
}

static QList<LSPDiagnostic>
parseDiagnostics(const QJsonArray &result)
{
    QList<LSPDiagnostic> ret;
    for (const auto & vdiag : result) {
        auto diag = vdiag.toObject();
        auto range = parseRange(diag.value(MEMBER_RANGE).toObject());
        auto severity = (LSPDiagnosticSeverity) diag.value(QStringLiteral("severity")).toInt();
        auto code = diag.value(QStringLiteral("code")).toString();
        auto source = diag.value(QStringLiteral("source")).toString();
        auto message = diag.value(MEMBER_MESSAGE).toString();
        auto related = diag.value(QStringLiteral("relatedInformation")).toObject();
        auto relLocation = parseLocation(related.value(MEMBER_LOCATION).toObject());
        auto relMessage = related.value(MEMBER_MESSAGE).toString();
        ret.push_back({range, severity, code, source, message, relLocation, relMessage});
659
    }
660 661
    return ret;
}
662

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
static QList<LSPCodeAction>
parseCodeAction(const QJsonValue & result)
{
    QList<LSPCodeAction> ret;
    for (const auto &vaction: result.toArray()) {
        auto action = vaction.toObject();
        // entry could be Command or CodeAction
        if (!action.value(MEMBER_COMMAND).isString()) {
            // CodeAction
            auto title = action.value(MEMBER_TITLE).toString();
            auto kind = action.value(MEMBER_KIND).toString();
            auto command = parseCommand(action.value(MEMBER_COMMAND).toObject());
            auto edit = parseWorkSpaceEdit(action.value(MEMBER_EDIT));
            auto diagnostics = parseDiagnostics(action.value(MEMBER_DIAGNOSTICS).toArray());
            ret.push_back({title, kind, diagnostics, edit, command});
        } else {
            // Command
            auto command = parseCommand(action);
            ret.push_back({command.title, QString(), {}, {}, command});
682 683
        }
    }
684 685
    return ret;
}
686

687 688 689 690
static LSPPublishDiagnosticsParams
parseDiagnostics(const QJsonObject & result)
{
    LSPPublishDiagnosticsParams ret;
691

692 693 694 695
    ret.uri = normalizeUrl(QUrl(result.value(MEMBER_URI).toString()));
    ret.diagnostics = parseDiagnostics(result.value(MEMBER_DIAGNOSTICS).toArray());
    return ret;
}
696

697 698 699 700
static LSPApplyWorkspaceEditParams
parseApplyWorkspaceEditParams(const QJsonObject & result)
{
    LSPApplyWorkspaceEditParams ret;
701

702 703 704 705
    ret.label = result.value(MEMBER_LABEL).toString();
    ret.edit = parseWorkSpaceEdit(result.value(MEMBER_EDIT));
    return ret;
}
706

707

708 709
using GenericReplyType = QJsonValue;
using GenericReplyHandler = ReplyHandler<GenericReplyType>;
710

711 712 713
class LSPClientServer::LSPClientServerPrivate
{
    typedef LSPClientServerPrivate self_type;
714

715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    LSPClientServer *q;
    // server cmd line
    QStringList m_server;
    // workspace root to pass along
    QUrl m_root;
    // user provided init
    QJsonValue m_init;
    // server process
    QProcess m_sproc;
    // server declared capabilites
    LSPServerCapabilities m_capabilities;
    // server state
    State m_state = State::None;
    // last msg id
    int m_id = 0;
    // receive buffer
    QByteArray m_receive;
    // registered reply handlers
    QHash<int, GenericReplyHandler> m_handlers;
    // pending request responses
    static constexpr int MAX_REQUESTS = 5;
    QVector<int> m_requests{MAX_REQUESTS + 1};
737

738 739 740 741
public:
    LSPClientServerPrivate(LSPClientServer * _q, const QStringList & server,
        const QUrl & root, const QJsonValue & init)
        : q(_q), m_server(server), m_root(root), m_init(init)
742
    {
743 744 745
        // setup async reading
        QObject::connect(&m_sproc, &QProcess::readyRead, utils::mem_fun(&self_type::read, this));
        QObject::connect(&m_sproc, &QProcess::stateChanged, utils::mem_fun(&self_type::onStateChanged, this));
746 747
    }

748
    ~LSPClientServerPrivate()
749
    {
750
        stop(TIMEOUT_SHUTDOWN, TIMEOUT_SHUTDOWN);
751 752
    }

753
    const QStringList& cmdline() const
754
    {
755
        return m_server;
756 757
    }

758
    State state()
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
759
    {
760
        return m_state;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
761 762
    }

763 764
    const LSPServerCapabilities&
    capabilities()
765
    {
766
        return m_capabilities;
767 768
    }

769
    int cancel(int reqid)
770
    {
771 772 773 774 775
        if (m_handlers.remove(reqid) > 0) {
            auto params = QJsonObject { { MEMBER_ID, reqid } };
            write(init_request(QStringLiteral("$/cancelRequest"), params));
        }
        return -1;
776 777
    }

778 779
private:
    void setState(State s)
780
    {
781 782 783 784
        if (m_state != s) {
            m_state = s;
            emit q->stateChanged(q);
        }
785 786
    }

787
    RequestHandle write(const QJsonObject & msg, const GenericReplyHandler & h = nullptr, const int * id = nullptr)
788
    {
789 790
        RequestHandle ret;
        ret.m_server = q;
791

792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
        if (!running())
            return ret;

        auto ob = msg;
        ob.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0"));
        // notification == no handler
        if (h) {
            ob.insert(MEMBER_ID, ++m_id);
            ret.m_id = m_id;
            m_handlers[m_id] = h;
        } else if (id) {
            ob.insert(MEMBER_ID, *id);
        }

        QJsonDocument json(ob);
        auto sjson = json.toJson();

        qCInfo(LSPCLIENT) << "calling" << msg[MEMBER_METHOD].toString();
        qCDebug(LSPCLIENT) << "sending message:\n" << QString::fromUtf8(sjson);
        // some simple parsers expect length header first
        auto hdr = QStringLiteral(CONTENT_LENGTH ": %1\r\n").arg(sjson.length());
        // write is async, so no blocking wait occurs here
        m_sproc.write(hdr.toLatin1());
        m_sproc.write("\r\n");
        m_sproc.write(sjson);
817

818
        return ret;
819 820
    }

821 822
    RequestHandle
    send(const QJsonObject & msg, const GenericReplyHandler & h = nullptr)
823
    {
824 825 826 827 828
        if (m_state == State::Running)
            return write(msg, h);
        else
            qCWarning(LSPCLIENT) << "send for non-running server";
        return RequestHandle();
829 830
    }

831
    void read()
832
    {
833 834
        // accumulate in buffer
        m_receive.append(m_sproc.readAllStandardOutput());
835

836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
        // try to get one (or more) message
        QByteArray &buffer = m_receive;

        while (true) {
            qCDebug(LSPCLIENT) << "buffer size" << buffer.length();
            auto header = QByteArray(CONTENT_LENGTH ":");
            int index = buffer.indexOf(header);
            if (index < 0) {
                // avoid collecting junk
                if (buffer.length() > 1 << 20)
                    buffer.clear();
                break;
            }
            index += header.length();
            int endindex = buffer.indexOf("\r\n", index);
            auto msgstart = buffer.indexOf("\r\n\r\n", index);
            if (endindex < 0 || msgstart < 0)
                break;
            msgstart += 4;
            bool ok = false;
            auto length = buffer.mid(index, endindex - index).toInt(&ok, 10);
            // FIXME perhaps detect if no reply for some time
            // then again possibly better left to user to restart in such case
            if (!ok) {
                qCWarning(LSPCLIENT) << "invalid " CONTENT_LENGTH;
                // flush and try to carry on to some next header
                buffer.remove(0, msgstart);
                continue;
            }
            // sanity check to avoid extensive buffering
            if (length > 1 << 29) {
                qCWarning(LSPCLIENT) << "excessive size";
                buffer.clear();
                continue;
            }
            if (msgstart + length > buffer.length())
                break;
            // now onto payload
            auto payload = buffer.mid(msgstart, length);
            buffer.remove(0, msgstart + length);
            qCInfo(LSPCLIENT) << "got message payload size " << length;
            qCDebug(LSPCLIENT) << "message payload:\n" << payload;
            QJsonParseError error{};
            auto msg = QJsonDocument::fromJson(payload, &error);
            if (error.error != QJsonParseError::NoError || !msg.isObject()) {
                qCWarning(LSPCLIENT) << "invalid response payload";
                continue;
            }
            auto result = msg.object();
            // check if it is the expected result
            int msgid = -1;
            if (result.contains(MEMBER_ID)) {
                msgid = result[MEMBER_ID].toInt();
            } else {
                processNotification(result);
                continue;
            }
            // could be request
            if (result.contains(MEMBER_METHOD)) {
                processRequest(result);
                continue;
            }
            // a valid reply; what to do with it now
            auto it = m_handlers.find(msgid);
            if (it != m_handlers.end()) {
901
                // run handler, might e.g. trigger some new LSP actions for this server
902
                (*it)(result.value(MEMBER_RESULT));
903 904 905

                // don't use the iterator, might have been invalidated by actions of handler, leads to crashs
                m_handlers.remove(msgid);
906 907 908 909 910
            } else {
                // could have been canceled
                qCDebug(LSPCLIENT) << "unexpected reply id";
            }
        }
911 912
    }

913 914
    static QJsonObject
    init_error(const LSPErrorCode code, const QString & msg)
915
    {
916 917 918 919 920 921 922
        return QJsonObject {
            { MEMBER_ERROR, QJsonObject {
                    { MEMBER_CODE, (int) code },
                    { MEMBER_MESSAGE, msg }
                }
            }
        };
923 924
    }

925 926
    static QJsonObject
    init_request(const QString & method, const QJsonObject & params = QJsonObject())
927
    {
928 929 930 931
        return QJsonObject {
            { MEMBER_METHOD, method },
            { MEMBER_PARAMS, params }
        };
932 933
    }

934 935
    static QJsonObject
    init_response(const QJsonValue & result = QJsonValue())
936
    {
937 938 939
        return QJsonObject {
            { MEMBER_RESULT, result }
        };
940 941
    }

942
    bool running()
943
    {
944
        return m_sproc.state() == QProcess::Running;
945 946
    }

947
    void onStateChanged(QProcess::ProcessState nstate)
948
    {
949 950 951
        if (nstate == QProcess::NotRunning) {
            setState(State::None);
        }
952
    }
953

954
    void shutdown()
955
    {
956 957 958 959 960 961 962 963 964 965 966
        if (m_state == State::Running) {
            qCInfo(LSPCLIENT) << "shutting down" << m_server;
            // cancel all pending
            m_handlers.clear();
            // shutdown sequence
            send(init_request(QStringLiteral("shutdown")));
            // maybe we will get/see reply on the above, maybe not
            // but not important or useful either way
            send(init_request(QStringLiteral("exit")));
            // no longer fit for regular use
            setState(State::Shutdown);
967 968
        }
    }
969

970
    void onInitializeReply(const QJsonValue & value)
971
    {
972 973 974 975
        // only parse parts that we use later on
        from_json(m_capabilities, value.toObject().value(QStringLiteral("capabilities")).toObject());
        // finish init
        initialized();
976 977
    }

978
    void initialize()
979
    {
980 981 982 983 984 985 986
        QJsonObject codeAction {
            { QStringLiteral("codeActionLiteralSupport"), QJsonObject {
                    { QStringLiteral("codeActionKind"), QJsonObject {
                        { QStringLiteral("valueSet"), QJsonArray() }
                        }
                    }
                }
987
            }
988 989 990 991 992 993 994 995 996 997 998 999
        };
        QJsonObject capabilities {
            { QStringLiteral("textDocument"),
                QJsonObject {
                    { QStringLiteral("documentSymbol"),
                                QJsonObject { { QStringLiteral("hierarchicalDocumentSymbolSupport"), true } },
                    },
                    { QStringLiteral("publishDiagnostics"),
                                QJsonObject { { QStringLiteral("relatedInformation"), true } }
                    },
                    { QStringLiteral("codeAction"), codeAction }
                }
1000 1001
            }
        };
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
        // NOTE a typical server does not use root all that much,
        // other than for some corner case (in) requests
        QJsonObject params {
            { QStringLiteral("processId"), QCoreApplication::applicationPid() },
            { QStringLiteral("rootPath"), m_root.path() },
            { QStringLiteral("rootUri"), m_root.toString() },
            { QStringLiteral("capabilities"), capabilities },
            { QStringLiteral("initializationOptions"), m_init }
        };
        //
        write(init_request(QStringLiteral("initialize"), params),
             utils::mem_fun(&self_type::onInitializeReply, this));
1014 1015
    }

1016 1017 1018 1019
    void initialized()
    {
        write(init_request(QStringLiteral("initialized")));
        setState(State::Running);
1020
    }
1021

1022 1023 1024 1025 1026
public:
    bool start()
    {
        if (m_state != State::None)
            return true;
1027

1028 1029 1030 1031
        auto program = m_server.front();
        auto args = m_server;
        args.pop_front();
        qCInfo(LSPCLIENT) << "starting" << m_server << "with root" << m_root;
1032

1033 1034
        // start LSP server in project root
        m_sproc.setWorkingDirectory(m_root.path());
1035

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
        // at least we see some errors somewhere then
        m_sproc.setProcessChannelMode(QProcess::ForwardedErrorChannel);
        m_sproc.setReadChannel(QProcess::QProcess::StandardOutput);
        m_sproc.start(program, args);
        bool result = m_sproc.waitForStarted();
        if (!result) {
            qCWarning(LSPCLIENT) << m_sproc.error();
        } else {
            setState(State::Started);
            // perform initial handshake
            initialize();
1047
        }
1048
        return result;
1049 1050
    }

1051 1052 1053 1054 1055 1056 1057 1058
    void stop(int to_term, int to_kill)
    {
        if (running()) {
            shutdown();
            if ((to_term >= 0) && !m_sproc.waitForFinished(to_term))
                m_sproc.terminate();
            if ((to_kill >= 0) && !m_sproc.waitForFinished(to_kill))
                m_sproc.kill();
Christoph Cullmann's avatar
Christoph Cullmann committed
1059 1060 1061
        }
    }

1062 1063 1064 1065 1066
    RequestHandle documentSymbols(const QUrl & document, const GenericReplyHandler & h)
    {
        auto params = textDocumentParams(document);
        return send(init_request(QStringLiteral("textDocument/documentSymbol"), params), h);
    }
Christoph Cullmann's avatar
Christoph Cullmann committed
1067

1068 1069 1070 1071 1072
    RequestHandle documentDefinition(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/definition"), params), h);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1073
    }
Christoph Cullmann's avatar
Christoph Cullmann committed
1074

1075 1076 1077 1078 1079 1080
    RequestHandle documentDeclaration(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/declaration"), params), h);
    }
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1081

1082 1083 1084 1085 1086 1087
    RequestHandle documentHover(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/hover"), params), h);
    }
1088

1089 1090 1091 1092 1093 1094
    RequestHandle documentHighlight(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/documentHighlight"), params), h);
    }
1095

1096 1097 1098 1099 1100 1101
    RequestHandle documentReferences(const QUrl & document, const LSPPosition & pos, bool decl,
        const GenericReplyHandler & h)
    {
        auto params = referenceParams(document, pos, decl);
        return send(init_request(QStringLiteral("textDocument/references"), params), h);
    }
1102

1103 1104 1105 1106 1107
    RequestHandle documentCompletion(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/completion"), params), h);
1108 1109
    }

1110 1111 1112 1113 1114
    RequestHandle signatureHelp(const QUrl & document, const LSPPosition & pos,
        const GenericReplyHandler & h)
    {
        auto params = textDocumentPositionParams(document, pos);
        return send(init_request(QStringLiteral("textDocument/signatureHelp"), params), h);
1115 1116
    }

1117 1118 1119 1120 1121
    RequestHandle documentFormatting(const QUrl & document, const LSPFormattingOptions & options,
        const GenericReplyHandler & h)
    {
        auto params = documentRangeFormattingParams(document, nullptr, options);
        return send(init_request(QStringLiteral("textDocument/formatting"), params), h);
1122
    }
1123 1124 1125 1126 1127 1128

    RequestHandle documentRangeFormatting(const QUrl & document, const LSPRange & range,
        const LSPFormattingOptions & options, const GenericReplyHandler & h)
    {
        auto params = documentRangeFormattingParams(document, &range, options);
        return send(init_request(QStringLiteral("textDocument/rangeFormatting"), params), h);
1129 1130
    }

1131 1132 1133 1134 1135 1136
    RequestHandle documentOnTypeFormatting(const QUrl & document, const LSPPosition & pos,
        QChar lastChar, const LSPFormattingOptions & options, const GenericReplyHandler & h)
    {
        auto params = documentOnTypeFormattingParams(document, pos, lastChar, options);
        return send(init_request(QStringLiteral("textDocument/onTypeFormatting"), params), h);
    }
1137

1138 1139 1140 1141 1142
    RequestHandle documentRename(const QUrl & document, const LSPPosition & pos,
        const QString newName, const GenericReplyHandler & h)
    {
        auto params = renameParams(document, pos, newName);
        return send(init_request(QStringLiteral("textDocument/rename"), params), h);
1143 1144
    }

1145 1146 1147 1148 1149 1150 1151
    RequestHandle documentCodeAction(const QUrl & document, const LSPRange & range,
        const QList<QString> & kinds, QList<LSPDiagnostic> diagnostics,
        const GenericReplyHandler & h)
    {
        auto params = codeActionParams(document, range, kinds, diagnostics);
        return send(init_request(QStringLiteral("textDocument/codeAction"), params), h);
    }
1152

1153 1154 1155 1156
    void executeCommand(const QString & command, const QJsonValue & args)
    {
        auto params = executeCommandParams(command, args);
        send(init_request(QStringLiteral("workspace/executeCommand"), params));
1157 1158
    }

1159 1160 1161 1162
    void didOpen(const QUrl & document, int version, const QString & langId, const QString & text)
    {
        auto params = textDocumentParams(textDocumentItem(document, langId, text, version));
        send(init_request(QStringLiteral("textDocument/didOpen"), params));
1163 1164
    }

1165 1166 1167 1168 1169 1170 1171 1172 1173
    void didChange(const QUrl & document, int version, const QString & text,
        const QList<LSPTextDocumentContentChangeEvent> & changes)
    {
        Q_ASSERT(text.size() == 0 || changes.size() == 0);
        auto params = textDocumentParams(document, version);
        params[QStringLiteral("contentChanges")] = text.size()
                ? QJsonArray { QJsonObject {{MEMBER_TEXT, text}} }
                : to_json(changes);
        send(init_request(QStringLiteral("textDocument/didChange"), params));
1174
    }
1175

1176 1177 1178 1179 1180 1181
    void didSave(const QUrl & document, const QString & text)
    {
        auto params = textDocumentParams(document);
        params[QStringLiteral("text")] = text;
        send(init_request(QStringLiteral("textDocument/didSave"), params));
    }
1182

1183 1184 1185 1186
    void didClose(const QUrl & document)
    {
        auto params = textDocumentParams(document);
        send(init_request(QStringLiteral("textDocument/didClose"), params));
1187 1188
    }

1189 1190 1191 1192 1193
    void processNotification(const QJsonObject & msg)
    {
        auto method = msg[MEMBER_METHOD].toString();
        if (method == QStringLiteral("textDocument/publishDiagnostics")) {
            emit q->publishDiagnostics(parseDiagnostics(msg[MEMBER_PARAMS].toObject()));
1194
        } else {
1195
            qCWarning(LSPCLIENT) << "discarding notification" << method;
1196
        }
1197 1198
    }

1199 1200 1201 1202 1203 1204 1205 1206
    template<typename ReplyType>
    static GenericReplyHandler make_handler(const ReplyHandler<ReplyType> & h,
        const QObject *context,
        typename utils::identity<std::function<ReplyType(const GenericReplyType&)>>::type c)
    {
        QPointer<const QObject> ctx(context);
        return [ctx, h, c] (const GenericReplyType & m) { if (ctx) h(c(m)); };
    }
1207

1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231
    GenericReplyHandler
    prepareResponse(int msgid)
    {
        // allow limited number of outstanding requests
        auto ctx = QPointer<LSPClientServer>(q);
        m_requests.push_back(msgid);
        if (m_requests.size() > MAX_REQUESTS) {
            m_requests.pop_front();
        }
        auto h = [ctx, this, msgid] (const GenericReplyType & response)
        {
            if (!ctx) {
                return;
            }
            auto index = m_requests.indexOf(msgid);
            if (index >= 0) {
                m_requests.remove(index);
                write(init_response(response), nullptr, &msgid);
            } else {
                qCWarning(LSPCLIENT) << "discarding response" << msgid;
            }
        };
        return h;
    }
1232

1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253
    template<typename ReplyType>
    static ReplyHandler<ReplyType> responseHandler(const GenericReplyHandler & h,
        typename utils::identity<std::function<GenericReplyType(const ReplyType&)>>::type c)
    { return [h, c] (const ReplyType & m) { h(c(m)); }; }

    // pretty rare and limited use, but anyway
    void processRequest(const QJsonObject & msg)
    {
        auto method = msg[MEMBER_METHOD].toString();
        auto msgid = msg[MEMBER_ID].toInt();
        auto params = msg[MEMBER_PARAMS];
        bool handled = false;
        if (method == QStringLiteral("workspace/applyEdit"))