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
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
/***************************************************************************
 *   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>


#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:
60
61
        // align with symbolview
        RETURN_CACHED_ICON("code-variable");
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
    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;
}

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
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);
            }
        }
    }
};


145
146
147
148
149
150
151
152
153
154
155
156
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;
157
158
    bool m_selectedDocumentation = false;

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

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

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

    void setServer(QSharedPointer<LSPClientServer> server) override
173
174
175
176
177
178
179
180
181
182
183
    {
        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();
        }
    }
184

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

188
189
190
191
192
193
194
195
    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());

196
197
198
199
200
201
202
203
204
        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) {
205
206
207
208
            return kind_icon(match.kind);
        } else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
            return kind_property(match.kind);
        } else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) {
209
            return match.argumentHintDepth;
210
211
212
        } else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
            // (ab)use depth to indicate sort order
            return index.row();
213
214
215
216
217
218
219
220
        } 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 &&
221
222
                   !match.argumentHintDepth && !match.documentation.value.isEmpty() &&
                   m_selectedDocumentation) {
223
            return match.documentation.value;
224
225
226
227
228
229
230
231
        }

        return QVariant();
    }

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

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

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

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

        return complete;
    }

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

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

259
260
261
262
263
264
265
266
267
268
269
270
271
        // 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);
            qSort(m_matches.begin(), m_matches.end(), compare_match);
            setRowCount(m_matches.size());
            endResetModel();
        };

        auto sigHandler = [this] (const LSPSignatureHelp & sig) {
272
            beginResetModel();
273
274
275
276
277
278
279
280
281
282
283
284
285
            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;
            }
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
            qSort(m_matches.begin(), m_matches.end(), compare_match);
            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);
301
302
303
304
305
306
            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);
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
        }
        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();
324
325
        m_handleSig.cancel();
        m_triggerSignature = false;
326
327
328
329
330
331
332
333
334
335
336
        endResetModel();
    }
};

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

#include "lspclientcompletion.moc"