lspclientpluginview.cpp 95.1 KB
Newer Older
1
/*
2
    SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com>
3

4
    SPDX-License-Identifier: MIT
5
*/
6
7
8

#include "lspclientpluginview.h"
#include "lspclientcompletion.h"
9
#include "lspclienthover.h"
10
11
12
#include "lspclientplugin.h"
#include "lspclientservermanager.h"
#include "lspclientsymbolview.h"
13
14
15

#include "lspclient_debug.h"

16
#include <KAcceleratorManager>
17
18
19
20
21
22
23
24
25
#include <KActionCollection>
#include <KActionMenu>
#include <KLocalizedString>
#include <KStandardAction>
#include <KXMLGUIFactory>

#include <KTextEditor/CodeCompletionInterface>
#include <KTextEditor/Document>
#include <KTextEditor/MainWindow>
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
26
#include <KTextEditor/Message>
27
#include <KTextEditor/MovingInterface>
28
#include <KTextEditor/View>
29
30
#include <KXMLGUIClient>

31
#include <ktexteditor/configinterface.h>
32
33
34
35
#include <ktexteditor/markinterface.h>
#include <ktexteditor/movinginterface.h>
#include <ktexteditor/movingrange.h>

36
#include <QAction>
37
#include <QApplication>
38
#include <QDateTime>
39
40
#include <QFileInfo>
#include <QHBoxLayout>
41
#include <QHeaderView>
42
#include <QInputDialog>
43
44
#include <QJsonObject>
#include <QKeyEvent>
45
#include <QMenu>
46
#include <QPlainTextEdit>
47
#include <QSet>
48
#include <QStandardItem>
49
#include <QTextCodec>
50
51
#include <QTimer>
#include <QTreeView>
52
#include <unordered_map>
Filip Gawin's avatar
Filip Gawin committed
53
#include <utility>
54

55
56
namespace RangeData
{
57
enum {
58
59
60
    // preserve UserRole for generic use where needed
    FileUrlRole = Qt::UserRole + 1,
    RangeRole,
61
    KindRole,
62
63
};

64
65
66
67
class KindEnum
{
public:
    enum _kind {
68
69
70
71
72
73
74
        Text = static_cast<int>(LSPDocumentHighlightKind::Text),
        Read = static_cast<int>(LSPDocumentHighlightKind::Read),
        Write = static_cast<int>(LSPDocumentHighlightKind::Write),
        Error = 10 + static_cast<int>(LSPDiagnosticSeverity::Error),
        Warning = 10 + static_cast<int>(LSPDiagnosticSeverity::Warning),
        Information = 10 + static_cast<int>(LSPDiagnosticSeverity::Information),
        Hint = 10 + static_cast<int>(LSPDiagnosticSeverity::Hint),
75
76
77
        Related
    };

78
79
    KindEnum(int v)
    {
80
        m_value = _kind(v);
81
    }
82

83
    KindEnum(LSPDocumentHighlightKind hl)
84
        : KindEnum(static_cast<_kind>(hl))
85
86
    {
    }
87

88
    KindEnum(LSPDiagnosticSeverity sev)
89
        : KindEnum(_kind(10 + static_cast<int>(sev)))
90
91
    {
    }
92

93
94
95
96
    operator _kind()
    {
        return m_value;
    }
97
98
99
100
101

private:
    _kind m_value;
};

102
103
104
105
static constexpr KTextEditor::MarkInterface::MarkTypes markType = KTextEditor::MarkInterface::markType31;
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagError = KTextEditor::MarkInterface::Error;
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagWarning = KTextEditor::MarkInterface::Warning;
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagOther = KTextEditor::MarkInterface::markType30;
Alexander Lohnau's avatar
Alexander Lohnau committed
106
107
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagAll =
    KTextEditor::MarkInterface::MarkTypes(markTypeDiagError | markTypeDiagWarning | markTypeDiagOther);
108
109

}
110

111
static QIcon diagnosticsIcon(LSPDiagnosticSeverity severity)
112
{
113
    // clang-format off
114
#define RETURN_CACHED_ICON(name, fallbackname) \
115
    { \
116
117
        static QIcon icon(QIcon::fromTheme(QStringLiteral(name), \
                                           QIcon::fromTheme(QStringLiteral(fallbackname)))); \
118
        return icon; \
119
    }
120
    // clang-format on
121
    switch (severity) {
122
    case LSPDiagnosticSeverity::Error:
123
        RETURN_CACHED_ICON("data-error", "dialog-error")
124
    case LSPDiagnosticSeverity::Warning:
125
        RETURN_CACHED_ICON("data-warning", "dialog-warning")
126
127
    case LSPDiagnosticSeverity::Information:
    case LSPDiagnosticSeverity::Hint:
128
        RETURN_CACHED_ICON("data-information", "dialog-information")
129
130
    default:
        break;
131
132
133
134
    }
    return QIcon();
}

135
static QIcon codeActionIcon()
136
137
138
139
140
{
    static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")));
    return icon;
}

141
KTextEditor::Document *findDocument(KTextEditor::MainWindow *mainWindow, const QUrl &url)
142
143
{
    auto views = mainWindow->views();
144
    for (const auto v : views) {
145
        auto doc = v->document();
146
        if (doc && doc->url() == url) {
147
            return doc;
148
        }
149
150
151
152
    }
    return nullptr;
}

153
154
155
156
157
// helper to read lines from unopened documents
// lightweight and does not require additional symbols
class FileLineReader
{
    QFile file;
158
159
    int lastLineNo = -1;
    QString lastLine;
160
161

public:
162
    FileLineReader(const QUrl &url)
163
        : file(url.toLocalFile())
164
165
166
    {
        file.open(QIODevice::ReadOnly);
    }
167

168
    // called with non-descending lineno
169
170
    QString line(int lineno)
    {
171
172
173
        if (lineno == lastLineNo) {
            return lastLine;
        }
174
175
        while (file.isOpen() && !file.atEnd()) {
            auto line = file.readLine();
176
            if (++lastLineNo == lineno) {
177
178
179
180
181
182
                QTextCodec::ConverterState state;
                QTextCodec *codec = QTextCodec::codecForName("UTF-8");
                QString text = codec->toUnicode(line.constData(), line.size(), &state);
                if (state.invalidChars > 0) {
                    text = QString::fromLatin1(line);
                }
183
                while (text.size() && text.at(text.size() - 1).isSpace()) {
184
                    text.chop(1);
185
                }
186
                lastLine = text;
187
188
189
190
191
192
193
                return text;
            }
        }
        return QString();
    }
};

194
195
196
/**
 * @brief This is just a helper class that provides "underline" on Ctrl + click
 */
197
class CtrlHoverFeedback : public QObject
198
{
199
    Q_OBJECT
200
public:
Alexander Lohnau's avatar
Alexander Lohnau committed
201
    void highlight(KTextEditor::View *activeView)
202
    {
203
        // sanity checks
204
        if (!activeView) {
205
            return;
206
        }
207
208

        auto doc = activeView->document();
209
        if (!doc) {
210
            return;
211
        }
212
213

        // set the cursor
214
        auto &data = docs[doc];
215
        if (w) {
216
217
218
219
220
            // track original cursor
            if (!data.wid) {
                data.wid = w;
                data.cursor = w->cursor();
            }
221
            w->setCursor(Qt::PointingHandCursor);
222
        }
223

224
        // underline the hovered word
225
        auto &mr = data.range;
226
227
228
        if (mr) {
            mr->setRange(range);
        } else {
Alexander Lohnau's avatar
Alexander Lohnau committed
229
            auto miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
230
            if (!miface) {
231
                return;
232
            }
233
            mr.reset(miface->newMovingRange(range));
234
            // clang-format off
Alexander Lohnau's avatar
Alexander Lohnau committed
235
            connect(doc,
236
                    SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
237
                    this,
238
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
239
240
                    Qt::UniqueConnection);
            connect(doc,
241
                    SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
242
                    this,
243
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
244
                    Qt::UniqueConnection);
245
            // clang-format on
246
247
248
249
250
251
        }

        static KTextEditor::Attribute::Ptr attr;
        if (!attr) {
            attr = new KTextEditor::Attribute;
            attr->setUnderlineStyle(QTextCharFormat::SingleUnderline);
252
        }
253
        mr->setAttribute(attr);
254
255
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
256
    void clear(KTextEditor::View *activeView)
257
    {
258
259
        if (activeView) {
            auto doc = activeView->document();
260
261
            auto it = docs.find(doc);
            if (it != docs.end()) {
262
                auto &data = it->second;
263
                auto &mr = data.range;
264
                if (mr) {
265
                    mr->setRange(KTextEditor::Range::invalid());
266
                }
267
268
269
270
                if (data.wid) {
                    data.wid->setCursor(data.cursor);
                    data.wid = nullptr;
                }
271
            }
272
        }
273
        w.clear();
274
275
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
276
    void setRangeAndWidget(const KTextEditor::Range &r, QWidget *wid)
277
278
279
280
281
282
283
    {
        range = r;
        w = wid;
    }

    bool isValid() const
    {
284
        return !w.isNull();
285
286
    }

287
private:
Alexander Lohnau's avatar
Alexander Lohnau committed
288
    Q_SLOT void clear(KTextEditor::Document *doc)
289
    {
290
        if (doc) {
291
292
            auto it = docs.find(doc);
            if (it != docs.end()) {
293
294
295
                auto &data = it->second;
                if (data.wid) {
                    data.wid->setCursor(data.cursor);
296
297
                }
                docs.erase(it);
298
299
            }
        }
300
301
    }

302
private:
303
    struct DocumentData {
304
        std::unique_ptr<KTextEditor::MovingRange> range = nullptr;
305
306
307
308
309
        // widget to restore cursor on
        QPointer<QWidget> wid;
        QCursor cursor;
    };

310
    QPointer<QWidget> w;
311
    std::unordered_map<KTextEditor::Document *, DocumentData> docs;
312
313
314
    KTextEditor::Range range;
};

315
class LSPClientActionView : public QObject
316
317
318
{
    Q_OBJECT

319
    typedef LSPClientActionView self_type;
320
321
322

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
323
    KXMLGUIClient *m_client;
324
    QSharedPointer<LSPClientServerManager> m_serverManager;
325
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
326
    QScopedPointer<LSPClientCompletion> m_completion;
327
    QScopedPointer<LSPClientHover> m_hover;
328
    QScopedPointer<KTextEditor::TextHintProvider> m_forwardHover;
329
330
    QScopedPointer<QObject> m_symbolView;

331
332
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
333
    QPointer<QAction> m_findTypeDef;
334
    QPointer<QAction> m_findRef;
335
    QPointer<QAction> m_findImpl;
336
    QPointer<QAction> m_triggerHighlight;
337
    QPointer<QAction> m_triggerSymbolInfo;
338
    QPointer<QAction> m_triggerFormat;
339
    QPointer<QAction> m_triggerRename;
340
    QPointer<QAction> m_complDocOn;
341
    QPointer<QAction> m_signatureHelp;
342
    QPointer<QAction> m_refDeclaration;
343
    QPointer<QAction> m_autoHover;
344
    QPointer<QAction> m_onTypeFormatting;
345
    QPointer<QAction> m_incrementalSync;
346
    QPointer<QAction> m_highlightGoto;
347
348
349
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
350
    QPointer<QAction> m_diagnosticsHover;
351
    QPointer<QAction> m_diagnosticsSwitch;
352
    QPointer<QAction> m_messages;
353
    QPointer<QAction> m_closeDynamic;
354
355
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
356
    QPointer<QAction> m_switchSourceHeader;
357
    QPointer<KActionMenu> m_requestCodeAction;
358

359
360
361
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
362
    // applied search ranges
363
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
364
    RangeCollection m_ranges;
Christoph Cullmann's avatar
Christoph Cullmann committed
365
    QHash<KTextEditor::Document *, QHash<int, QVector<KTextEditor::MovingRange *>>> m_semanticHighlightRanges;
366
    // applied marks
367
    typedef QSet<KTextEditor::Document *> DocumentCollection;
368
    DocumentCollection m_marks;
369
370
371
372
    // modelis either owned by tree added to tabwidget or owned here
    QScopedPointer<QStandardItemModel> m_ownedModel;
    // in either case, the model that directs applying marks/ranges
    QPointer<QStandardItemModel> m_markModel;
373
374
375
    // goto definition and declaration jump list is more a menu than a
    // search result, so let's not keep adding new tabs for those
    // previous tree for definition result
376
    QPointer<QTreeView> m_defTree;
377
    // ... and for declaration
378
    QPointer<QTreeView> m_declTree;
379
380
    // ... and for type definition
    QPointer<QTreeView> m_typeDefTree;
381

382
    // diagnostics tab
383
    QPointer<QTreeView> m_diagnosticsTree;
384
    // tree widget is either owned here or by tab
385
386
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
387
388
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
389
390
    // and marks
    DocumentCollection m_diagnosticsMarks;
391

392
393
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
394
395
396
397

    // views on which hovers have been registered
    QSet<KTextEditor::View *> m_hoverViews;

398
399
400
401
402
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

403
404
    // accept incoming applyEdit
    bool m_accept_edit = false;
405
406
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
407

408
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
409

410
411
412
413
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
414

415
416
417
418
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
419

420
421
422
423
424
425
426
427
428
429
430
431
432
    public:
        ForwardingTextHintProvider(LSPClientActionView *parent)
            : m_parent(parent)
        {
            Q_ASSERT(m_parent);
        }

        virtual QString textHint(KTextEditor::View *view, const KTextEditor::Cursor &position) override
        {
            return m_parent->onTextHint(view, position);
        }
    };

433
434
    SemanticHighlighter m_semHighlightingManager;

435
436
437
438
439
440
441
442
Q_SIGNALS:
    /**
     * Signal for outgoing message, the host application will handle them!
     * Will only be handled inside the main windows of this plugin view.
     * @param message outgoing message we send to the host application
     */
    void message(const QVariantMap &message);

443
    void addPositionToHistory(const QUrl &url, KTextEditor::Cursor c);
444

445
public:
446
    LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer<LSPClientServerManager> serverManager)
447
448
449
450
451
        : QObject(mainWin)
        , m_plugin(plugin)
        , m_mainWindow(mainWin)
        , m_client(client)
        , m_serverManager(std::move(serverManager))
452
        , m_completion(LSPClientCompletion::new_(m_serverManager))
453
        , m_hover(LSPClientHover::new_(m_serverManager))
454
        , m_forwardHover(new ForwardingTextHintProvider(this))
455
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
456
457
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
458
459
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
460
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
461

462
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
463
        m_findDef->setText(i18n("Go to Definition"));
464
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
465
        m_findDecl->setText(i18n("Go to Declaration"));
466
467
        m_findTypeDef = actionCollection()->addAction(QStringLiteral("lspclient_find_type_definition"), this, &self_type::goToTypeDefinition);
        m_findTypeDef->setText(i18n("Go to Type Definition"));
468
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
469
        m_findRef->setText(i18n("Find References"));
470
471
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
472
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
473
        m_triggerHighlight->setText(i18n("Highlight"));
474
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
475
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
476
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
477
        m_triggerFormat->setText(i18n("Format"));
478
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
479
        m_triggerRename->setText(i18n("Rename"));
480
481
        m_switchSourceHeader = actionCollection()->addAction(QStringLiteral("lspclient_clangd_switchheader"), this, &self_type::clangdSwitchSourceHeader);
        m_switchSourceHeader->setText(i18n("Switch Source Header"));
482
        actionCollection()->setDefaultShortcut(m_switchSourceHeader, Qt::Key_F12);
483
484
485
        m_requestCodeAction = actionCollection()->add<KActionMenu>(QStringLiteral("lspclient_code_action"));
        m_requestCodeAction->setText(i18n("Code Action"));
        connect(m_requestCodeAction->menu(), &QMenu::aboutToShow, this, &self_type::requestCodeAction);
486

487
        // general options
488
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
489
490
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
491
492
493
        m_signatureHelp = actionCollection()->addAction(QStringLiteral("lspclient_signature_help"), this, &self_type::displayOptionChanged);
        m_signatureHelp->setText(i18n("Enable signature help with auto completion"));
        m_signatureHelp->setCheckable(true);
494
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
495
496
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
497
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
498
499
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
500
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
501
502
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
503
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
504
505
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
506
507
508
        m_highlightGoto = actionCollection()->addAction(QStringLiteral("lspclient_highlight_goto"), this, &self_type::displayOptionChanged);
        m_highlightGoto->setText(i18n("Highlight goto location"));
        m_highlightGoto->setCheckable(true);
509

510
        // diagnostics
511
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
512
513
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
514
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
515
516
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
517
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
518
519
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
520
521
522
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
523
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
524
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
525

526
527
528
529
530
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);

531
532
533
        // server control and misc actions
        m_closeDynamic = actionCollection()->addAction(QStringLiteral("lspclient_close_dynamic"), this, &self_type::closeDynamic);
        m_closeDynamic->setText(i18n("Close all dynamic reference tabs"));
534
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
535
        m_restartServer->setText(i18n("Restart LSP Server"));
536
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
537
538
        m_restartAll->setText(i18n("Restart All LSP Servers"));

539
540
541
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
542
543
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
544
        menu->addAction(m_findTypeDef);
545
        menu->addAction(m_findRef);
546
        menu->addAction(m_findImpl);
547
        menu->addAction(m_switchSourceHeader);
548
        menu->addAction(m_triggerHighlight);
549
        menu->addAction(m_triggerSymbolInfo);
550
        menu->addAction(m_triggerFormat);
551
        menu->addAction(m_triggerRename);
552
        menu->addAction(m_requestCodeAction);
553
        menu->addSeparator();
554
        menu->addAction(m_diagnosticsSwitch);
555
        menu->addAction(m_closeDynamic);
556
        menu->addSeparator();
557
558
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
559
560
561
562
563
        menu->addSeparator();
        // more options
        auto moreOptions = new KActionMenu(i18n("More options"), this);
        menu->addAction(moreOptions);
        moreOptions->addAction(m_complDocOn);
564
        moreOptions->addAction(m_signatureHelp);
565
566
567
568
        moreOptions->addAction(m_refDeclaration);
        moreOptions->addAction(m_autoHover);
        moreOptions->addAction(m_onTypeFormatting);
        moreOptions->addAction(m_incrementalSync);
569
        moreOptions->addAction(m_highlightGoto);
570
571
572
573
574
575
576
        moreOptions->addSeparator();
        moreOptions->addAction(m_diagnostics);
        moreOptions->addAction(m_diagnosticsHighlight);
        moreOptions->addAction(m_diagnosticsMark);
        moreOptions->addAction(m_diagnosticsHover);
        moreOptions->addSeparator();
        moreOptions->addAction(m_messages);
577
578
579

        // sync with plugin settings if updated
        connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated);
580

581
        // toolview
Alexander Lohnau's avatar
Alexander Lohnau committed
582
583
584
585
586
        m_toolView.reset(mainWin->createToolView(plugin,
                                                 QStringLiteral("kate_lspclient"),
                                                 KTextEditor::MainWindow::Bottom,
                                                 QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")),
                                                 i18n("LSP Client")));
587
        m_tabWidget = new QTabWidget(m_toolView.data());
588
        m_toolView->layout()->addWidget(m_tabWidget);
589
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
590
591
592
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
593
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
594

595
        // diagnostics tab
596
        m_diagnosticsTree = new QTreeView();
597
        m_diagnosticsTree->setAlternatingRowColors(true);
598
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
599
        m_diagnosticsModel.reset(new QStandardItemModel());
600
        m_diagnosticsModel->setColumnCount(1);
601
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
602
        configureTreeView(m_diagnosticsTree);
603
604
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
605

606
607
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
608
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
609

610
611
612
613
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

        connect(this, &LSPClientActionView::ctrlClickDefRecieved, this, &LSPClientActionView::onCtrlMouseMove);

614
        configUpdated();
615
616
617
        updateState();
    }

618
619
620
621
622
623
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
624
                if (c) {
625
                    c->installEventFilter(this);
626
                }
627
628
629
630
            }
        }
    }

631
632
633
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
659
660
661
662
663
664
665
666
667
668
669
670
671
672
    void doSemanticHighlighting(KTextEditor::View *view)
    {
        if (!view) {
            return;
        }

        auto server = m_serverManager->findServer(view);
        if (server) {
            auto reqId = m_semHighlightingManager.resultIdForDoc(view->document()->url());
            m_semHighlightingManager.setLegend(&server->capabilities().semanticTokenProvider.legend);
            //             m_semHighlightingManager.setTypes(server->capabilities().semanticTokenProvider.types);
            /**
             * Full delta only if server supports it or if we don't have a result ID for this document
             */
            if (reqId.isEmpty() || !server->capabilities().semanticTokenProvider.fullDelta) {
                auto h = [this, view](const LSPSemanticTokens &st) {
                    auto url = view->document()->url();
                    m_semHighlightingManager.setCurrentView(view);
                    m_semHighlightingManager.insert(url, st.resultId, st.data);
                    m_semHighlightingManager.highlight(url);
                };
                server->documentSemanticTokensFull(view->document()->url(), {}, this, h);
            } else {
                auto h = [this, view](const LSPSemanticTokensDelta &st) {
                    auto url = view->document()->url();
                    m_semHighlightingManager.setCurrentView(view);

                    for (const auto &semTokenEdit : st.edits) {
                        m_semHighlightingManager.update(url, st.resultId, semTokenEdit.start, semTokenEdit.deleteCount, semTokenEdit.data);
                    }

                    if (!st.data.empty()) {
                        m_semHighlightingManager.insert(url, st.resultId, st.data);
                    }
                    m_semHighlightingManager.highlight(url);
                };

                server->documentSemanticTokensFullDelta(view->document()->url(), reqId, this, h);
            }
        }
    }

673
    // This is taken from KDevelop :)
Alexander Lohnau's avatar
Alexander Lohnau committed
674
    KTextEditor::View *viewFromWidget(QWidget *widget)
675
    {
676
        if (!widget) {
677
            return nullptr;
678
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
679
        auto *view = qobject_cast<KTextEditor::View *>(widget);
680
        if (view) {
681
            return view;
682
        } else {
683
            return viewFromWidget(widget->parentWidget());
684
        }
685
686
    }

687
688
689
690
691
692
    /**
     * @brief normal word range queried from doc->wordRangeAt() will not include
     * full header from a #include line. For example in line "#include <abc/some>"
     * we get either abc or some. But for Ctrl + click we need to highlight it as
     * one thing, so this function expands the range from wordRangeAt() to do that.
     */
Alexander Lohnau's avatar
Alexander Lohnau committed
693
    static void expandToFullHeaderRange(KTextEditor::Range &range, QStringView lineText)
694
695
696
697
698
    {
        auto expandRangeTo = [lineText, &range](QChar c, int startPos) {
            int end = lineText.indexOf(c, startPos);
            if (end > -1) {
                auto startC = range.start();
699
                startC.setColumn(startPos);
700
701
702
703
704
705
706
707
708
                auto endC = range.end();
                endC.setColumn(end);
                range.setStart(startC);
                range.setEnd(endC);
            }
        };

        int angleBracketPos = lineText.indexOf(QLatin1Char('<'), 7);
        if (angleBracketPos > -1) {
709
            expandRangeTo(QLatin1Char('>'), angleBracketPos + 1);
710
711
712
        } else {
            int startPos = lineText.indexOf(QLatin1Char('"'), 7);
            if (startPos > -1) {
713
                expandRangeTo(QLatin1Char('"'), startPos + 1);
714
715
716
717
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
718
719
720
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
721

722
        // we are only concerned with mouse events for now :)
723
        if (!mouseEvent) {
724
            return false;
725
        }
726
727

        // common stuff that we need for both events
Alexander Lohnau's avatar
Alexander Lohnau committed
728
        auto wid = qobject_cast<QWidget *>(obj);
729
        auto v = viewFromWidget(wid);
730
        if (!v) {
731
            return false;
732
        }
733
734

        const auto coords = wid->mapTo(v, mouseEvent->pos());
Alexander Lohnau's avatar
Alexander Lohnau committed
735
        const auto cur = v->coordinatesToCursor(coords);
736
        // there isn't much we can do now, just bail out
737
        if (!cur.isValid()) {
738
            return false;
739
        }
740

741
        auto doc = v->document();
742
        if (!doc) {
743
            return false;
744
        }
745
746
747

        const auto word = doc->wordAt(cur);

748
749
750
751
752
        // The user pressed Ctrl + Click
        if (event->type() == QEvent::MouseButtonPress) {
            if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() == Qt::ControlModifier) {
                // must set cursor else we will be jumping somewhere else!!
                if (!word.isEmpty()) {
753
                    v->setCursorPosition(cur);
754
755
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
                    goToDefinition();
756
757
                }
            }
758
759
760
761
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
762
                auto range = doc->wordRangeAt(cur);
763
                if (!word.isEmpty() && range.isValid()) {
764
765
766
767
768
769
770
                    // check if we are in #include
                    // and expand the word range
                    auto lineText = doc->line(range.start().line());
                    if (lineText.startsWith(QLatin1String("#include")) && range.start().column() > 7) {
                        expandToFullHeaderRange(range, lineText);
                    }

771
772
773
774
775
                    m_ctrlHoverFeedback.setRangeAndWidget(range, wid);
                    // this will not go anywhere actually, but just signal whether we have a definition
                    // Also, please rethink very hard if you are going to reuse this method. It's made
                    // only for Ctrl+Hover
                    processCtrlMouseHover(cur);
776
                } else {
777
                    // if there is no word, unset the cursor and remove the highlight
778
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
779
                }
780
781
782
783
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
                m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
784
785
786
787
788
789
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
790
    ~LSPClientActionView() override
791
    {
792
793
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
794
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
795
796
797
798
        }

        // unregister all text-hint providers, else we might crash
        for (auto view : qAsConst(m_hoverViews)) {
799
            qobject_cast<KTextEditor::TextHintInterface *>(view)->unregisterTextHintProvider(m_forwardHover.data());
800
801
        }

802
803
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
804
805
    }

806
807
808
809
810
811
812
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
813
814
815
816
817
818

        // context menu
        treeView->setContextMenuPolicy(Qt::CustomContextMenu);
        auto menu = new QMenu(treeView);
        menu->addAction(i18n("Expand All"), treeView, &QTreeView::expandAll);
        menu->addAction(i18n("Collapse All"), treeView, &QTreeView::collapseAll);
Alexander Lohnau's avatar
Alexander Lohnau committed
819
820
821
        auto h = [menu](const QPoint &) {
            menu->popup(QCursor::pos());
        };
822
        connect(treeView, &QTreeView::customContextMenuRequested, h);
823
824
    }

825
826
    void displayOptionChanged()
    {
827
828
        m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
        m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
829
        m_diagnosticsHover->setEnabled(m_diagnostics->isChecked());
830

831
832
        // diagnstics tab next
        int diagnosticsIndex = m_tabWidget->indexOf(m_diagnosticsTree);
833
834
835
        // setTabEnabled may still show it ... so let's be more forceful
        if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.take();
836
            m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
837
838
        } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
839
            m_tabWidget->removeTab(diagnosticsIndex);
840
        }
841
        m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
842
        m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
843
        updateState();
844
845
846
847
    }

    void configUpdated()
    {
848
        if (m_complDocOn) {
849
            m_complDocOn->setChecked(m_plugin->m_complDoc);
Alexander Lohnau's avatar