lspclientcompletion.cpp 12.4 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
24
25
26
27
28
29
30
31
32
33
/***************************************************************************
 *   Copyright (C) 2012 Christoph Cullmann <cullmann@kde.org>              *
 *   Copyright (C) 2003 Anders Lund <anders.lund@lund.tdcadsl.dk>          *
 *   Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com>   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 ***************************************************************************/

#include "lspclientcompletion.h"
#include "lspclientplugin.h"

#include "lspclient_debug.h"

#include <KTextEditor/Cursor>
#include <KTextEditor/Document>
#include <KTextEditor/View>

#include <QIcon>
#include <QUrl>

34
#include <algorithm>
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

#define RETURN_CACHED_ICON(name) \
{ \
    static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \
    return icon; \
}

static QIcon
kind_icon(LSPCompletionItemKind kind)
{
    switch (kind)
    {
    case LSPCompletionItemKind::Method:
    case LSPCompletionItemKind::Function:
    case LSPCompletionItemKind::Constructor:
        RETURN_CACHED_ICON("code-function")
    case LSPCompletionItemKind::Variable:
        RETURN_CACHED_ICON("code-variable")
    case LSPCompletionItemKind::Class:
    case LSPCompletionItemKind::Interface:
    case LSPCompletionItemKind::Struct:
        RETURN_CACHED_ICON("code-class");
    case LSPCompletionItemKind::Module:
        RETURN_CACHED_ICON("code-block");
    case LSPCompletionItemKind::Field:
    case LSPCompletionItemKind::Property:
61
62
        // align with symbolview
        RETURN_CACHED_ICON("code-variable");
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
    case LSPCompletionItemKind::Enum:
    case LSPCompletionItemKind::EnumMember:
        RETURN_CACHED_ICON("enum");
    default:
        break;
    }
    return QIcon();
}

static KTextEditor::CodeCompletionModel::CompletionProperty
kind_property(LSPCompletionItemKind kind)
{
    using CompletionProperty = KTextEditor::CodeCompletionModel::CompletionProperty;
    auto p = CompletionProperty::NoProperty;

    switch (kind)
    {
    case LSPCompletionItemKind::Method:
    case LSPCompletionItemKind::Function:
    case LSPCompletionItemKind::Constructor:
        p = CompletionProperty::Function;
        break;
    case LSPCompletionItemKind::Variable:
        p = CompletionProperty::Variable;
        break;
    case LSPCompletionItemKind::Class:
    case LSPCompletionItemKind::Interface:
        p = CompletionProperty::Class;
        break;
    case LSPCompletionItemKind::Struct:
        p = CompletionProperty::Class;
        break;
    case LSPCompletionItemKind::Module:
        p =CompletionProperty::Namespace;
        break;
    case LSPCompletionItemKind::Enum:
    case LSPCompletionItemKind::EnumMember:
        p = CompletionProperty::Enum;
        break;
    default:
        break;
    }
    return p;
}

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
struct LSPClientCompletionItem : public LSPCompletionItem
{
    int argumentHintDepth = 0;
    QString prefix;
    QString postfix;

    LSPClientCompletionItem(const LSPCompletionItem & item)
        : LSPCompletionItem(item)
    {
        // transform for later display
        // sigh, remove (leading) whitespace (looking at clangd here)
        // could skip the [] if empty detail, but it is a handy watermark anyway ;-)
        label = QString(label.simplified() + QStringLiteral(" [") +
                        detail.simplified() + QStringLiteral("]"));
    }

    LSPClientCompletionItem(const LSPSignatureInformation & sig,
        int activeParameter, const QString & _sortText)
    {
        argumentHintDepth = 1;
        documentation = sig.documentation;
        label = sig.label;
        sortText = _sortText;
        // transform into prefix, name, suffix if active
        if (activeParameter >= 0 && activeParameter < sig.parameters.length()) {
            const auto& param = sig.parameters.at(activeParameter);
            if (param.start >= 0 && param.start < label.length() &&
                    param.end >= 0 && param.end < label.length() &&
                    param.start < param.end) {
                prefix = label.mid(0, param.start);
                postfix = label.mid(param.end);
                label = label.mid(param.start, param.end - param.start);
            }
        }
    }
};


146
147
148
149
150
151
152
153
154
155
156
157
static bool compare_match (const LSPCompletionItem & a, const LSPCompletionItem b)
{ return a.sortText < b.sortText; }


class LSPClientCompletionImpl : public LSPClientCompletion
{
    Q_OBJECT

    typedef LSPClientCompletionImpl self_type;

    QSharedPointer<LSPClientServerManager> m_manager;
    QSharedPointer<LSPClientServer> m_server;
158
159
    bool m_selectedDocumentation = false;

160
161
162
    QVector<QChar> m_triggersCompletion;
    QVector<QChar> m_triggersSignature;
    bool m_triggerSignature = false;
163

164
165
    QList<LSPClientCompletionItem> m_matches;
    LSPClientServer::RequestHandle m_handle, m_handleSig;
166
167
168
169
170
171
172
173

public:
    LSPClientCompletionImpl(QSharedPointer<LSPClientServerManager> manager)
        : LSPClientCompletion(nullptr), m_manager(manager), m_server(nullptr)
    {
    }

    void setServer(QSharedPointer<LSPClientServer> server) override
174
175
176
177
178
179
180
181
182
183
184
    {
        m_server = server;
        if (m_server) {
            const auto& caps = m_server->capabilities();
            m_triggersCompletion = caps.completionProvider.triggerCharacters;
            m_triggersSignature = caps.signatureHelpProvider.triggerCharacters;
        } else {
            m_triggersCompletion.clear();
            m_triggersSignature.clear();
        }
    }
185

186
187
188
    virtual void setSelectedDocumentation(bool s) override
    { m_selectedDocumentation = s; }

189
190
191
192
193
194
195
196
    QVariant data(const QModelIndex &index, int role) const override
    {
        if (!index.isValid() || index.row() >= m_matches.size()) {
            return QVariant();
        }

        const auto &match = m_matches.at(index.row());

197
198
199
200
201
202
203
204
205
        if (role == Qt::DisplayRole) {
            if (index.column() == KTextEditor::CodeCompletionModel::Name) {
                return match.label;
            } else if (index.column() == KTextEditor::CodeCompletionModel::Prefix) {
                return match.prefix;
            } else if (index.column() == KTextEditor::CodeCompletionModel::Postfix) {
                return match.postfix;
            }
        } else if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) {
206
207
208
209
            return kind_icon(match.kind);
        } else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
            return kind_property(match.kind);
        } else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) {
210
            return match.argumentHintDepth;
211
212
213
        } else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
            // (ab)use depth to indicate sort order
            return index.row();
214
215
216
217
218
219
220
221
        } else if (role == KTextEditor::CodeCompletionModel::IsExpandable) {
            return !match.documentation.value.isEmpty();
        } else if (role == KTextEditor::CodeCompletionModel::ExpandingWidget &&
                   !match.documentation.value.isEmpty()) {
            // probably plaintext, but let's show markdown as-is for now
            // FIXME better presentation of markdown
            return match.documentation.value;
        } else if (role == KTextEditor::CodeCompletionModel::ItemSelected &&
222
223
                   !match.argumentHintDepth && !match.documentation.value.isEmpty() &&
                   m_selectedDocumentation) {
224
            return match.documentation.value;
225
226
227
228
229
230
231
232
        }

        return QVariant();
    }

    bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText,
        bool userInsertion, const KTextEditor::Cursor &position) override
    {
233
        qCInfo(LSPCLIENT) << "should start " << userInsertion << insertedText;
234

235
        if (!userInsertion || !m_server || insertedText.isEmpty()) {
236
237
238
            return false;
        }

239
        // covers most already ...
240
241
        bool complete = CodeCompletionModelControllerInterface::shouldStartCompletion(view,
            insertedText, userInsertion, position);
242
        QChar lastChar = insertedText.at(insertedText.count() - 1);
243

244
245
246
247
248
249
        m_triggerSignature = false;
        complete = complete  || m_triggersCompletion.contains(lastChar);
        if (m_triggersSignature.contains(lastChar)) {
            complete = true;
            m_triggerSignature = true;
        }
250
251
252
253
254
255
256
257
258
259

        return complete;
    }

    void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) override
    {
        Q_UNUSED(it)

        qCInfo(LSPCLIENT) << "completion invoked" << m_server;

260
261
262
263
264
265
266
        // maybe use WaitForReset ??
        // but more complex and already looks good anyway
        auto handler = [this] (const QList<LSPCompletionItem> & compl) {
            beginResetModel();
            qCInfo(LSPCLIENT) << "adding completions " << compl.size();
            for (const auto & item : compl)
                m_matches.push_back(item);
267
            std::stable_sort(m_matches.begin(), m_matches.end(), compare_match);
268
269
270
271
272
            setRowCount(m_matches.size());
            endResetModel();
        };

        auto sigHandler = [this] (const LSPSignatureHelp & sig) {
273
            beginResetModel();
274
275
276
277
278
279
280
281
282
283
284
285
286
            qCInfo(LSPCLIENT) << "adding signatures " << sig.signatures.size();
            int index = 0;
            for (const auto & item : sig.signatures) {
                int sortIndex = 10 + index;
                int active = -1;
                if (index == sig.activeSignature) {
                    sortIndex = 0;
                    active = sig.activeParameter;
                }
                // trick active first, others after that
                m_matches.push_back({item, active, QString(QStringLiteral("%1").arg(sortIndex, 3, 10))});
                ++index;
            }
287
            std::stable_sort(m_matches.begin(), m_matches.end(), compare_match);
288
289
290
291
292
293
294
295
296
297
298
299
300
301
            setRowCount(m_matches.size());
            endResetModel();
        };

        beginResetModel();
        m_matches.clear();
        auto document = view->document();
        if (m_server && document) {
            // the default range is determined based on a reasonable identifier (word)
            // which is generally fine and nice, but let's pass actual cursor position
            // (which may be within this typical range)
            auto position = view->cursorPosition();
            auto cursor = qMax(range.start(), qMin(range.end(), position));
            m_manager->update(document);
302
303
304
305
306
307
            if (!m_triggerSignature) {
                m_handle = m_server->documentCompletion(document->url(),
                    {cursor.line(), cursor.column()}, this, handler);
            }
            m_handleSig = m_server->signatureHelp(document->url(),
                {cursor.line(), cursor.column()}, this, sigHandler);
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
        }
        setRowCount(m_matches.size());
        endResetModel();
    }

    void executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override
    {
        if (index.row() < m_matches.size())
            view->document()->replaceText(word, m_matches.at(index.row()).insertText);
    }

    void aborted(KTextEditor::View *view) override
    {
        Q_UNUSED(view);
        beginResetModel();
        m_matches.clear();
        m_handle.cancel();
325
326
        m_handleSig.cancel();
        m_triggerSignature = false;
327
328
329
330
331
332
333
334
335
336
337
        endResetModel();
    }
};

LSPClientCompletion*
LSPClientCompletion::new_(QSharedPointer<LSPClientServerManager> manager)
{
    return new LSPClientCompletionImpl(manager);
}

#include "lspclientcompletion.moc"