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

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

#include "lspclientpluginview.h"
Waqar Ahmed's avatar
Waqar Ahmed committed
8
#include "gotosymboldialog.h"
9
#include "lspclientcompletion.h"
10
#include "lspclienthover.h"
11
12
13
#include "lspclientplugin.h"
#include "lspclientservermanager.h"
#include "lspclientsymbolview.h"
14
15
16

#include "lspclient_debug.h"

17
#include <KAcceleratorManager>
18
19
20
21
22
23
24
25
26
#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
27
#include <KTextEditor/Message>
28
#include <KTextEditor/MovingInterface>
29
#include <KTextEditor/View>
30
31
#include <KXMLGUIClient>

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

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

59
60
namespace RangeData
{
61
enum {
62
63
64
    // preserve UserRole for generic use where needed
    FileUrlRole = Qt::UserRole + 1,
    RangeRole,
65
    KindRole,
66
67
};

68
69
70
71
class KindEnum
{
public:
    enum _kind {
72
73
74
75
76
77
78
        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),
79
80
81
        Related
    };

82
83
    KindEnum(int v)
    {
84
        m_value = _kind(v);
85
    }
86

87
    KindEnum(LSPDocumentHighlightKind hl)
88
        : KindEnum(static_cast<_kind>(hl))
89
90
    {
    }
91

92
    KindEnum(LSPDiagnosticSeverity sev)
93
        : KindEnum(_kind(10 + static_cast<int>(sev)))
94
95
    {
    }
96

97
98
99
100
    operator _kind()
    {
        return m_value;
    }
101
102
103
104
105

private:
    _kind m_value;
};

106
107
108
109
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
110
111
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagAll =
    KTextEditor::MarkInterface::MarkTypes(markTypeDiagError | markTypeDiagWarning | markTypeDiagOther);
112
113

}
114

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

139
static QIcon codeActionIcon()
140
141
142
143
144
{
    static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")));
    return icon;
}

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

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

public:
166
    FileLineReader(const QUrl &url)
167
        : file(url.toLocalFile())
168
169
170
    {
        file.open(QIODevice::ReadOnly);
    }
171

172
    // called with non-descending lineno
173
174
    QString line(int lineno)
    {
175
176
177
        if (lineno == lastLineNo) {
            return lastLine;
        }
178
179
        while (file.isOpen() && !file.atEnd()) {
            auto line = file.readLine();
180
            if (++lastLineNo == lineno) {
181
182
183
184
185
186
                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);
                }
187
188

                text = text.trimmed();
189
                lastLine = text;
190
191
192
193
194
195
196
                return text;
            }
        }
        return QString();
    }
};

197
198
199
class LocationTreeDelegate : public QStyledItemDelegate
{
public:
Waqar Ahmed's avatar
Waqar Ahmed committed
200
    LocationTreeDelegate(QObject *parent, const QFont &font)
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
        : QStyledItemDelegate(parent)
        , m_monoFont(font)
    {
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        auto options = option;
        initStyleOption(&options, index);

        painter->save();

        QString text = index.data().toString();

        options.text = QString(); // clear old text
        options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);

        QVector<QTextLayout::FormatRange> formats;
        if (!text.startsWith(QStringLiteral("Line: "))) {
            int lastSlash = text.lastIndexOf(QLatin1Char('/'));
            if (lastSlash != -1) {
                QTextCharFormat fmt;
                fmt.setFontWeight(QFont::Bold);
                formats.append({lastSlash + 1, text.length() - (lastSlash + 1), fmt});
            }
        } else {
            constexpr auto len = sizeof("Line: ") - 1;
            int nextColon = text.indexOf(QLatin1Char(':'), len);
            if (nextColon != -1) {
                QTextCharFormat fmt;
                fmt.setFont(m_monoFont);
                int codeStart = nextColon + 1;
                formats.append({codeStart, text.length() - codeStart, fmt});
            }
        }

237
238
239
240
241
        /** There might be an icon? Make sure to not draw over it **/
        auto textRectX = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options, options.widget).x();
        auto width = textRectX - options.rect.x();
        painter->translate(width, 0);

242
243
244
245
246
247
248
249
250
        kfts::paintItemViewText(painter, text, options, formats);

        painter->restore();
    }

private:
    QFont m_monoFont;
};

251
252
253
/**
 * @brief This is just a helper class that provides "underline" on Ctrl + click
 */
254
class CtrlHoverFeedback : public QObject
255
{
256
    Q_OBJECT
257
public:
Alexander Lohnau's avatar
Alexander Lohnau committed
258
    void highlight(KTextEditor::View *activeView)
259
    {
260
        // sanity checks
261
        if (!activeView) {
262
            return;
263
        }
264
265

        auto doc = activeView->document();
266
        if (!doc) {
267
            return;
268
        }
269

270
271
        if (!w) {
            return;
272
        }
273

274
275
        w->setCursor(Qt::PointingHandCursor);

276
        // underline the hovered word
277
        auto &mr = docs[doc];
278
279
280
        if (mr) {
            mr->setRange(range);
        } else {
Alexander Lohnau's avatar
Alexander Lohnau committed
281
            auto miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
282
            if (!miface) {
283
                return;
284
            }
285
            mr.reset(miface->newMovingRange(range));
286
            // clang-format off
Alexander Lohnau's avatar
Alexander Lohnau committed
287
            connect(doc,
288
                    SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
289
                    this,
290
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
291
292
                    Qt::UniqueConnection);
            connect(doc,
293
                    SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
294
                    this,
295
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
296
                    Qt::UniqueConnection);
297
            // clang-format on
298
299
300
301
302
303
        }

        static KTextEditor::Attribute::Ptr attr;
        if (!attr) {
            attr = new KTextEditor::Attribute;
            attr->setUnderlineStyle(QTextCharFormat::SingleUnderline);
304
        }
305
        mr->setAttribute(attr);
306
307
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
308
    void clear(KTextEditor::View *activeView)
309
    {
310
311
        if (activeView) {
            auto doc = activeView->document();
312
313
            auto it = docs.find(doc);
            if (it != docs.end()) {
314
                auto &mr = it->second;
315
                if (mr) {
316
                    mr->setRange(KTextEditor::Range::invalid());
317
                }
318
            }
319
        }
320
321
322
        if (w && w->cursor() != Qt::IBeamCursor) {
            w->setCursor(Qt::IBeamCursor);
        }
323
        w.clear();
324
325
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
326
    void setRangeAndWidget(const KTextEditor::Range &r, QWidget *wid)
327
328
329
330
331
332
333
    {
        range = r;
        w = wid;
    }

    bool isValid() const
    {
334
        return !w.isNull();
335
336
    }

337
private:
Alexander Lohnau's avatar
Alexander Lohnau committed
338
    Q_SLOT void clear(KTextEditor::Document *doc)
339
    {
340
        if (doc) {
341
342
343
            auto it = docs.find(doc);
            if (it != docs.end()) {
                docs.erase(it);
344
345
            }
        }
346
347
    }

348
private:
349
    QPointer<QWidget> w;
350
    std::unordered_map<KTextEditor::Document *, std::unique_ptr<KTextEditor::MovingRange>> docs;
351
352
353
    KTextEditor::Range range;
};

354
class LSPClientActionView : public QObject
355
356
357
{
    Q_OBJECT

358
    typedef LSPClientActionView self_type;
359
360
361

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
362
    KXMLGUIClient *m_client;
363
    QSharedPointer<LSPClientServerManager> m_serverManager;
364
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
365
    QScopedPointer<LSPClientCompletion> m_completion;
366
    QScopedPointer<LSPClientHover> m_hover;
367
    QScopedPointer<KTextEditor::TextHintProvider> m_forwardHover;
368
369
    QScopedPointer<QObject> m_symbolView;

370
371
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
372
    QPointer<QAction> m_findTypeDef;
373
    QPointer<QAction> m_findRef;
374
    QPointer<QAction> m_findImpl;
375
    QPointer<QAction> m_triggerHighlight;
376
    QPointer<QAction> m_triggerSymbolInfo;
377
    QPointer<QAction> m_triggerGotoSymbol;
378
    QPointer<QAction> m_triggerFormat;
379
    QPointer<QAction> m_triggerRename;
380
    QPointer<QAction> m_complDocOn;
381
    QPointer<QAction> m_signatureHelp;
382
    QPointer<QAction> m_refDeclaration;
383
    QPointer<QAction> m_complParens;
384
    QPointer<QAction> m_autoHover;
385
    QPointer<QAction> m_onTypeFormatting;
386
    QPointer<QAction> m_incrementalSync;
387
    QPointer<QAction> m_highlightGoto;
388
389
390
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
391
    QPointer<QAction> m_diagnosticsHover;
392
    QPointer<QAction> m_diagnosticsSwitch;
393
    QPointer<QAction> m_messages;
394
    QPointer<QAction> m_closeDynamic;
395
396
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
397
    QPointer<QAction> m_switchSourceHeader;
398
    QPointer<QAction> m_quickFix;
399
    QPointer<KActionMenu> m_requestCodeAction;
400

401
402
403
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
404
    // applied search ranges
405
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
406
    RangeCollection m_ranges;
407
    // applied marks
408
    typedef QSet<KTextEditor::Document *> DocumentCollection;
409
    DocumentCollection m_marks;
410
411
412
413
    // 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;
414
415
416
    // 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
417
    QPointer<QTreeView> m_defTree;
418
    // ... and for declaration
419
    QPointer<QTreeView> m_declTree;
420
421
    // ... and for type definition
    QPointer<QTreeView> m_typeDefTree;
422

423
    // diagnostics tab
424
    QPointer<QTreeView> m_diagnosticsTree;
425
    // tree widget is either owned here or by tab
426
427
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
428
429
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
430
431
    // and marks
    DocumentCollection m_diagnosticsMarks;
432

433
434
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
435
436
437
438

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

439
440
441
442
443
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

444
445
    // accept incoming applyEdit
    bool m_accept_edit = false;
446
447
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
448

449
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
450

451
452
453
454
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
455

456
457
458
459
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
460

461
462
463
464
465
466
467
468
469
470
471
472
473
    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);
        }
    };

474
475
    SemanticHighlighter m_semHighlightingManager;

476
477
478
479
480
481
482
483
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);

484
    void addPositionToHistory(const QUrl &url, KTextEditor::Cursor c);
485

486
public:
487
    LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer<LSPClientServerManager> serverManager)
488
489
490
491
492
        : QObject(mainWin)
        , m_plugin(plugin)
        , m_mainWindow(mainWin)
        , m_client(client)
        , m_serverManager(std::move(serverManager))
493
        , m_completion(LSPClientCompletion::new_(m_serverManager))
494
        , m_hover(LSPClientHover::new_(m_serverManager))
495
        , m_forwardHover(new ForwardingTextHintProvider(this))
496
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
497
        , m_semHighlightingManager(m_serverManager)
498
499
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
500
501
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
502
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
503
504
        connect(m_serverManager.data(), &LSPClientServerManager::serverShowMessage, this, &self_type::onMessage);
        connect(m_serverManager.data(), &LSPClientServerManager::serverLogMessage, this, &self_type::onMessage);
505

506
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
507
        m_findDef->setText(i18n("Go to Definition"));
508
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
509
        m_findDecl->setText(i18n("Go to Declaration"));
510
511
        m_findTypeDef = actionCollection()->addAction(QStringLiteral("lspclient_find_type_definition"), this, &self_type::goToTypeDefinition);
        m_findTypeDef->setText(i18n("Go to Type Definition"));
512
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
513
        m_findRef->setText(i18n("Find References"));
514
515
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
516
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
517
        m_triggerHighlight->setText(i18n("Highlight"));
518
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
519
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
520
521
522
        m_triggerGotoSymbol = actionCollection()->addAction(QStringLiteral("lspclient_goto_workspace_symbol"), this, &self_type::gotoWorkSpaceSymbol);
        m_triggerGotoSymbol->setText(i18n("Search and Go to Symbol"));
        actionCollection()->setDefaultShortcut(m_triggerGotoSymbol, Qt::ALT | Qt::CTRL | Qt::Key_P);
523
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
524
        m_triggerFormat->setText(i18n("Format"));
525
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
526
        m_triggerRename->setText(i18n("Rename"));
527
528
        m_switchSourceHeader = actionCollection()->addAction(QStringLiteral("lspclient_clangd_switchheader"), this, &self_type::clangdSwitchSourceHeader);
        m_switchSourceHeader->setText(i18n("Switch Source Header"));
529
        actionCollection()->setDefaultShortcut(m_switchSourceHeader, Qt::Key_F12);
530
531
        m_quickFix = actionCollection()->addAction(QStringLiteral("lspclient_quick_fix"), this, &self_type::quickFix);
        m_quickFix->setText(i18n("Quick Fix"));
532
533
534
        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);
535

536
        // general options
537
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
538
539
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
540
541
542
        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);
543
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
544
545
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
546
547
548
        m_complParens = actionCollection()->addAction(QStringLiteral("lspclient_completion_parens"), this, &self_type::displayOptionChanged);
        m_complParens->setText(i18n("Add parentheses upon function completion"));
        m_complParens->setCheckable(true);
549
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
550
551
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
552
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
553
554
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
555
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
556
557
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
558
559
560
        m_highlightGoto = actionCollection()->addAction(QStringLiteral("lspclient_highlight_goto"), this, &self_type::displayOptionChanged);
        m_highlightGoto->setText(i18n("Highlight goto location"));
        m_highlightGoto->setCheckable(true);
561

562
        // diagnostics
563
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
564
565
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
566
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
567
568
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
569
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
570
571
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
572
573
574
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
575
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
576
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
577

578
579
580
581
582
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);

583
584
585
        // 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"));
586
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
587
        m_restartServer->setText(i18n("Restart LSP Server"));
588
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
589
590
        m_restartAll->setText(i18n("Restart All LSP Servers"));

591
592
593
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
594
595
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
596
        menu->addAction(m_findTypeDef);
597
        menu->addAction(m_findRef);
598
        menu->addAction(m_findImpl);
599
        menu->addAction(m_switchSourceHeader);
600
        menu->addAction(m_triggerHighlight);
601
        menu->addAction(m_triggerSymbolInfo);
602
        menu->addAction(m_triggerGotoSymbol);
603
        menu->addAction(m_triggerFormat);
604
        menu->addAction(m_triggerRename);
605
        menu->addAction(m_quickFix);
606
        menu->addAction(m_requestCodeAction);
607
        menu->addSeparator();
608
        menu->addAction(m_diagnosticsSwitch);
609
        menu->addAction(m_closeDynamic);
610
        menu->addSeparator();
611
612
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
613
614
615
616
617
        menu->addSeparator();
        // more options
        auto moreOptions = new KActionMenu(i18n("More options"), this);
        menu->addAction(moreOptions);
        moreOptions->addAction(m_complDocOn);
618
        moreOptions->addAction(m_signatureHelp);
619
        moreOptions->addAction(m_refDeclaration);
620
        moreOptions->addAction(m_complParens);
621
622
623
        moreOptions->addAction(m_autoHover);
        moreOptions->addAction(m_onTypeFormatting);
        moreOptions->addAction(m_incrementalSync);
624
        moreOptions->addAction(m_highlightGoto);
625
626
627
628
629
630
631
        moreOptions->addSeparator();
        moreOptions->addAction(m_diagnostics);
        moreOptions->addAction(m_diagnosticsHighlight);
        moreOptions->addAction(m_diagnosticsMark);
        moreOptions->addAction(m_diagnosticsHover);
        moreOptions->addSeparator();
        moreOptions->addAction(m_messages);
632
633
634

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

636
        // toolview
Alexander Lohnau's avatar
Alexander Lohnau committed
637
638
639
640
641
        m_toolView.reset(mainWin->createToolView(plugin,
                                                 QStringLiteral("kate_lspclient"),
                                                 KTextEditor::MainWindow::Bottom,
                                                 QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")),
                                                 i18n("LSP Client")));
642
        m_tabWidget = new QTabWidget(m_toolView.data());
643
        m_toolView->layout()->addWidget(m_tabWidget);
644
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
645
646
647
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
648
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
649

650
        // diagnostics tab
651
        m_diagnosticsTree = new QTreeView();
652
        m_diagnosticsTree->setAlternatingRowColors(true);
653
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
654
        m_diagnosticsModel.reset(new QStandardItemModel());
655
        m_diagnosticsModel->setColumnCount(1);
656
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
657
        configureTreeView(m_diagnosticsTree);
658
659
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
660

661
662
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
663
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
664

665
666
667
668
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

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

669
        configUpdated();
670
671
672
        updateState();
    }

673
674
675
676
677
678
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
679
                if (c) {
680
                    c->installEventFilter(this);
681
                }
682
683
684
685
            }
        }
    }

686
    // This is taken from KDevelop :)
Alexander Lohnau's avatar
Alexander Lohnau committed
687
    KTextEditor::View *viewFromWidget(QWidget *widget)
688
    {
689
        if (!widget) {
690
            return nullptr;
691
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
692
        auto *view = qobject_cast<KTextEditor::View *>(widget);
693
        if (view) {
694
            return view;
695
        } else {
696
            return viewFromWidget(widget->parentWidget());
697
        }
698
699
    }

700
701
702
703
704
705
    /**
     * @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
706
    static void expandToFullHeaderRange(KTextEditor::Range &range, QStringView lineText)
707
708
709
710
711
    {
        auto expandRangeTo = [lineText, &range](QChar c, int startPos) {
            int end = lineText.indexOf(c, startPos);
            if (end > -1) {
                auto startC = range.start();
712
                startC.setColumn(startPos);
713
714
715
716
717
718
719
720
721
                auto endC = range.end();
                endC.setColumn(end);
                range.setStart(startC);
                range.setEnd(endC);
            }
        };

        int angleBracketPos = lineText.indexOf(QLatin1Char('<'), 7);
        if (angleBracketPos > -1) {
722
            expandRangeTo(QLatin1Char('>'), angleBracketPos + 1);
723
724
725
        } else {
            int startPos = lineText.indexOf(QLatin1Char('"'), 7);
            if (startPos > -1) {
726
                expandRangeTo(QLatin1Char('"'), startPos + 1);
727
728
729
730
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
731
732
733
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
734

735
        // we are only concerned with mouse events for now :)
736
        if (!mouseEvent) {
737
            return false;
738
        }
739
740

        // common stuff that we need for both events
Alexander Lohnau's avatar
Alexander Lohnau committed
741
        auto wid = qobject_cast<QWidget *>(obj);
742
        auto v = viewFromWidget(wid);
743
        if (!v) {
744
            return false;
745
        }
746
747

        const auto coords = wid->mapTo(v, mouseEvent->pos());
Alexander Lohnau's avatar
Alexander Lohnau committed
748
        const auto cur = v->coordinatesToCursor(coords);
749
        // there isn't much we can do now, just bail out
750
        if (!cur.isValid()) {
751
            return false;
752
        }
753

754
        auto doc = v->document();
755
        if (!doc) {
756
            return false;
757
        }
758
759
760

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

761
762
763
764
765
        // 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()) {
766
                    v->setCursorPosition(cur);
767
                    m_ctrlHoverFeedback.clear(v);
768
                    goToDefinition();
769
770
                }
            }
771
772
773
774
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
775
                auto range = doc->wordRangeAt(cur);
776
                if (!word.isEmpty() && range.isValid()) {
777
778
779
780
781
782
783
                    // 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);
                    }

784
785
786
787
788
                    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);
789
                } else {
790
                    // if there is no word, unset the cursor and remove the highlight
791
                    m_ctrlHoverFeedback.clear(v);
792
                }
793
794
795
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
796
                m_ctrlHoverFeedback.clear(v);
797
798
799
800
801
802
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
803
    ~LSPClientActionView() override
804
    {
805
806
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
807
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
808
809
810
811
        }

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

815
816
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
817
818
    }

819
820
821
822
823
824
825
826
827
    QFont getEditorFont()
    {
        auto ciface = qobject_cast<KTextEditor::ConfigInterface *>(m_mainWindow->activeView());
        if (ciface) {
            return ciface->configValue(QStringLiteral("font")).value<QFont>();
        }
        return QFont();
    }

828
829
830
831
832
833
834
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
835

836
        // styling
Waqar Ahmed's avatar
Waqar Ahmed committed
837
        treeView->setItemDelegate(new LocationTreeDelegate(treeView, getEditorFont()));
838

839
840
841
842
843
        // 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
844
845
846
        auto h = [menu](const QPoint &) {
            menu->popup(QCursor::pos());
        };
847
        connect(treeView, &QTreeView::customContextMenuRequested, h);
848
849
    }