aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/languageclient/languageclientfunctionhint.cpp
blob: 67f51db90daf5c580abae9a00949a1601f448443 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "languageclientfunctionhint.h"
#include "client.h"

#include <languageserverprotocol/languagefeatures.h>
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>

#include <QScopedPointer>

using namespace TextEditor;
using namespace LanguageServerProtocol;

namespace LanguageClient {

class FunctionHintProposalModel : public IFunctionHintProposalModel
{
public:
    explicit FunctionHintProposalModel(SignatureHelp signature)
        : m_sigis(signature)
    {}
    void reset() override {}
    int size() const override
    { return m_sigis.signatures().size(); }
    QString text(int index) const override;

    int activeArgument(const QString &/*prefix*/) const override
    { return m_sigis.activeParameter().value_or(0); }

private:
    LanguageServerProtocol::SignatureHelp m_sigis;
};

QString FunctionHintProposalModel::text(int index) const
{
    using Parameters = QList<ParameterInformation>;
    if (index < 0 || m_sigis.signatures().size() <= index)
        return {};
    const SignatureInformation signature = m_sigis.signatures().at(index);
    int parametersIndex = signature.activeParameter().value_or(-1);
    if (parametersIndex < 0) {
        if (index == m_sigis.activeSignature().value_or(-1))
            parametersIndex = m_sigis.activeParameter().value_or(-1);
    }
    QString label = signature.label();
    if (parametersIndex < 0)
        return label;

    const QList<QString> parameters = Utils::transform(signature.parameters().value_or(Parameters()),
                                                       &ParameterInformation::label);
    if (parameters.size() <= parametersIndex)
        return label;

    const QString &parameterText = parameters.at(parametersIndex);
    const int start = label.indexOf(parameterText);
    const int end = start + parameterText.length();
    return label.mid(0, start).toHtmlEscaped() + "<b>" + parameterText.toHtmlEscaped() + "</b>"
           + label.mid(end).toHtmlEscaped();
}

FunctionHintProcessor::FunctionHintProcessor(Client *client)
    : m_client(client)
{}

IAssistProposal *FunctionHintProcessor::perform()
{
    QTC_ASSERT(m_client, return nullptr);
    m_pos = interface()->position();
    QTextCursor cursor(interface()->textDocument());
    cursor.setPosition(m_pos);
    auto uri = m_client->hostPathToServerUri(interface()->filePath());
    SignatureHelpRequest request((TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor))));
    request.setResponseCallback([this](auto response) { this->handleSignatureResponse(response); });
    m_client->addAssistProcessor(this);
    m_client->sendMessage(request);
    m_currentRequest = request.id();
    return nullptr;
}

void FunctionHintProcessor::cancel()
{
    QTC_ASSERT(m_client, return);
    if (running()) {
        m_client->cancelRequest(*m_currentRequest);
        m_client->removeAssistProcessor(this);
        m_currentRequest.reset();
    }
}

void FunctionHintProcessor::handleSignatureResponse(const SignatureHelpRequest::Response &response)
{
    QTC_ASSERT(m_client, setAsyncProposalAvailable(nullptr); return);
    m_currentRequest.reset();
    if (auto error = response.error())
        m_client->log(*error);
    m_client->removeAssistProcessor(this);
    auto result = response.result().value_or(LanguageClientValue<SignatureHelp>());
    if (result.isNull()) {
        setAsyncProposalAvailable(nullptr);
        return;
    }
    const SignatureHelp &signatureHelp = result.value();
    if (signatureHelp.signatures().isEmpty()) {
        setAsyncProposalAvailable(nullptr);
    } else {
        FunctionHintProposalModelPtr model(new FunctionHintProposalModel(signatureHelp));
        setAsyncProposalAvailable(new FunctionHintProposal(m_pos, model));
    }
}

FunctionHintAssistProvider::FunctionHintAssistProvider(Client *client)
    : CompletionAssistProvider(client)
    , m_client(client)
{}

IAssistProcessor *FunctionHintAssistProvider::createProcessor(
    const AssistInterface *) const
{
    return new FunctionHintProcessor(m_client);
}

int FunctionHintAssistProvider::activationCharSequenceLength() const
{
    return m_activationCharSequenceLength;
}

bool FunctionHintAssistProvider::isActivationCharSequence(
    const QString &sequence) const
{
    return Utils::anyOf(m_triggerChars,
                        [sequence](const QString &trigger) { return trigger.endsWith(sequence); });
}

bool FunctionHintAssistProvider::isContinuationChar(const QChar &/*c*/) const
{
    return true;
}

void FunctionHintAssistProvider::setTriggerCharacters(
    const std::optional<QList<QString>> &triggerChars)
{
    m_triggerChars = triggerChars.value_or(QList<QString>());
    for (const QString &trigger : std::as_const(m_triggerChars)) {
        if (trigger.length() > m_activationCharSequenceLength)
            m_activationCharSequenceLength = trigger.length();
    }
}

} // namespace LanguageClient