lspclientpluginview.cpp 96.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 <QPainter>
47
#include <QPlainTextEdit>
48
#include <QSet>
49
#include <QStandardItem>
50
#include <QStyledItemDelegate>
51
#include <QTextCodec>
52
53
#include <QTimer>
#include <QTreeView>
54
#include <kfts_fuzzy_match.h>
55
#include <unordered_map>
Filip Gawin's avatar
Filip Gawin committed
56
#include <utility>
57

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

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

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

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

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

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

private:
    _kind m_value;
};

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

}
113

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

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

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

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

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

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

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

196
197
198
class LocationTreeDelegate : public QStyledItemDelegate
{
public:
Waqar Ahmed's avatar
Waqar Ahmed committed
199
    LocationTreeDelegate(QObject *parent, const QFont &font)
200
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
        : 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});
            }
        }

236
237
238
239
240
        /** 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);

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

        painter->restore();
    }

private:
    QFont m_monoFont;
};

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

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

        // set the cursor
270
        auto &data = docs[doc];
271
        if (w) {
272
273
274
            // if we had a widget => reset to original cursor for it and track new one
            if (data.wid) {
                data.wid->setCursor(Qt::IBeamCursor);
275
            }
276
            data.wid = w;
277
            w->setCursor(Qt::PointingHandCursor);
278
        }
279

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

        static KTextEditor::Attribute::Ptr attr;
        if (!attr) {
            attr = new KTextEditor::Attribute;
            attr->setUnderlineStyle(QTextCharFormat::SingleUnderline);
308
        }
309
        mr->setAttribute(attr);
310
311
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
312
    void clear(KTextEditor::View *activeView)
313
    {
314
315
        if (activeView) {
            auto doc = activeView->document();
316
317
            auto it = docs.find(doc);
            if (it != docs.end()) {
318
                auto &data = it->second;
319
                auto &mr = data.range;
320
                if (mr) {
321
                    mr->setRange(KTextEditor::Range::invalid());
322
                }
323
                if (data.wid) {
324
                    data.wid->setCursor(Qt::IBeamCursor);
325
326
                    data.wid = nullptr;
                }
327
            }
328
        }
329
        w.clear();
330
331
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
332
    void setRangeAndWidget(const KTextEditor::Range &r, QWidget *wid)
333
334
335
336
337
338
339
    {
        range = r;
        w = wid;
    }

    bool isValid() const
    {
340
        return !w.isNull();
341
342
    }

343
private:
Alexander Lohnau's avatar
Alexander Lohnau committed
344
    Q_SLOT void clear(KTextEditor::Document *doc)
345
    {
346
        if (doc) {
347
348
            auto it = docs.find(doc);
            if (it != docs.end()) {
349
350
                auto &data = it->second;
                if (data.wid) {
351
                    data.wid->setCursor(Qt::IBeamCursor);
352
353
                }
                docs.erase(it);
354
355
            }
        }
356
357
    }

358
private:
359
    struct DocumentData {
360
        std::unique_ptr<KTextEditor::MovingRange> range = nullptr;
361
362
363
364
        // widget to restore cursor on
        QPointer<QWidget> wid;
    };

365
    QPointer<QWidget> w;
366
    std::unordered_map<KTextEditor::Document *, DocumentData> docs;
367
368
369
    KTextEditor::Range range;
};

370
class LSPClientActionView : public QObject
371
372
373
{
    Q_OBJECT

374
    typedef LSPClientActionView self_type;
375
376
377

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
378
    KXMLGUIClient *m_client;
379
    QSharedPointer<LSPClientServerManager> m_serverManager;
380
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
381
    QScopedPointer<LSPClientCompletion> m_completion;
382
    QScopedPointer<LSPClientHover> m_hover;
383
    QScopedPointer<KTextEditor::TextHintProvider> m_forwardHover;
384
385
    QScopedPointer<QObject> m_symbolView;

386
387
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
388
    QPointer<QAction> m_findTypeDef;
389
    QPointer<QAction> m_findRef;
390
    QPointer<QAction> m_findImpl;
391
    QPointer<QAction> m_triggerHighlight;
392
    QPointer<QAction> m_triggerSymbolInfo;
393
    QPointer<QAction> m_triggerFormat;
394
    QPointer<QAction> m_triggerRename;
395
    QPointer<QAction> m_complDocOn;
396
    QPointer<QAction> m_signatureHelp;
397
    QPointer<QAction> m_refDeclaration;
398
    QPointer<QAction> m_complParens;
399
    QPointer<QAction> m_autoHover;
400
    QPointer<QAction> m_onTypeFormatting;
401
    QPointer<QAction> m_incrementalSync;
402
    QPointer<QAction> m_highlightGoto;
403
404
405
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
406
    QPointer<QAction> m_diagnosticsHover;
407
    QPointer<QAction> m_diagnosticsSwitch;
408
    QPointer<QAction> m_messages;
409
    QPointer<QAction> m_closeDynamic;
410
411
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
412
    QPointer<QAction> m_switchSourceHeader;
413
    QPointer<QAction> m_quickFix;
414
    QPointer<KActionMenu> m_requestCodeAction;
415

416
417
418
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
419
    // applied search ranges
420
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
421
    RangeCollection m_ranges;
422
    // applied marks
423
    typedef QSet<KTextEditor::Document *> DocumentCollection;
424
    DocumentCollection m_marks;
425
426
427
428
    // 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;
429
430
431
    // 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
432
    QPointer<QTreeView> m_defTree;
433
    // ... and for declaration
434
    QPointer<QTreeView> m_declTree;
435
436
    // ... and for type definition
    QPointer<QTreeView> m_typeDefTree;
437

438
    // diagnostics tab
439
    QPointer<QTreeView> m_diagnosticsTree;
440
    // tree widget is either owned here or by tab
441
442
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
443
444
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
445
446
    // and marks
    DocumentCollection m_diagnosticsMarks;
447

448
449
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
450
451
452
453

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

454
455
456
457
458
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

459
460
    // accept incoming applyEdit
    bool m_accept_edit = false;
461
462
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
463

464
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
465

466
467
468
469
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
470

471
472
473
474
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
475

476
477
478
479
480
481
482
483
484
485
486
487
488
    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);
        }
    };

489
490
    SemanticHighlighter m_semHighlightingManager;

491
492
493
494
495
496
497
498
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);

499
    void addPositionToHistory(const QUrl &url, KTextEditor::Cursor c);
500

501
public:
502
    LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer<LSPClientServerManager> serverManager)
503
504
505
506
507
        : QObject(mainWin)
        , m_plugin(plugin)
        , m_mainWindow(mainWin)
        , m_client(client)
        , m_serverManager(std::move(serverManager))
508
        , m_completion(LSPClientCompletion::new_(m_serverManager))
509
        , m_hover(LSPClientHover::new_(m_serverManager))
510
        , m_forwardHover(new ForwardingTextHintProvider(this))
511
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
512
        , m_semHighlightingManager(m_serverManager)
513
514
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
515
516
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
517
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
518

519
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
520
        m_findDef->setText(i18n("Go to Definition"));
521
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
522
        m_findDecl->setText(i18n("Go to Declaration"));
523
524
        m_findTypeDef = actionCollection()->addAction(QStringLiteral("lspclient_find_type_definition"), this, &self_type::goToTypeDefinition);
        m_findTypeDef->setText(i18n("Go to Type Definition"));
525
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
526
        m_findRef->setText(i18n("Find References"));
527
528
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
529
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
530
        m_triggerHighlight->setText(i18n("Highlight"));
531
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
532
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
533
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
534
        m_triggerFormat->setText(i18n("Format"));
535
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
536
        m_triggerRename->setText(i18n("Rename"));
537
538
        m_switchSourceHeader = actionCollection()->addAction(QStringLiteral("lspclient_clangd_switchheader"), this, &self_type::clangdSwitchSourceHeader);
        m_switchSourceHeader->setText(i18n("Switch Source Header"));
539
        actionCollection()->setDefaultShortcut(m_switchSourceHeader, Qt::Key_F12);
540
541
        m_quickFix = actionCollection()->addAction(QStringLiteral("lspclient_quick_fix"), this, &self_type::quickFix);
        m_quickFix->setText(i18n("Quick Fix"));
542
543
544
        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);
545

546
        // general options
547
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
548
549
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
550
551
552
        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);
553
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
554
555
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
556
557
558
        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);
559
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
560
561
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
562
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
563
564
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
565
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
566
567
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
568
569
570
        m_highlightGoto = actionCollection()->addAction(QStringLiteral("lspclient_highlight_goto"), this, &self_type::displayOptionChanged);
        m_highlightGoto->setText(i18n("Highlight goto location"));
        m_highlightGoto->setCheckable(true);
571

572
        // diagnostics
573
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
574
575
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
576
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
577
578
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
579
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
580
581
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
582
583
584
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
585
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
586
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
587

588
589
590
591
592
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);

593
594
595
        // 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"));
596
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
597
        m_restartServer->setText(i18n("Restart LSP Server"));
598
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
599
600
        m_restartAll->setText(i18n("Restart All LSP Servers"));

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

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

645
        // toolview
Alexander Lohnau's avatar
Alexander Lohnau committed
646
647
648
649
650
        m_toolView.reset(mainWin->createToolView(plugin,
                                                 QStringLiteral("kate_lspclient"),
                                                 KTextEditor::MainWindow::Bottom,
                                                 QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")),
                                                 i18n("LSP Client")));
651
        m_tabWidget = new QTabWidget(m_toolView.data());
652
        m_toolView->layout()->addWidget(m_tabWidget);
653
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
654
655
656
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
657
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
658

659
        // diagnostics tab
660
        m_diagnosticsTree = new QTreeView();
661
        m_diagnosticsTree->setAlternatingRowColors(true);
662
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
663
        m_diagnosticsModel.reset(new QStandardItemModel());
664
        m_diagnosticsModel->setColumnCount(1);
665
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
666
        configureTreeView(m_diagnosticsTree);
667
668
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
669

670
671
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
672
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
673

674
675
676
677
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

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

678
        configUpdated();
679
680
681
        updateState();
    }

682
683
684
685
686
687
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
688
                if (c) {
689
                    c->installEventFilter(this);
690
                }
691
692
693
694
            }
        }
    }

695
    // This is taken from KDevelop :)
Alexander Lohnau's avatar
Alexander Lohnau committed
696
    KTextEditor::View *viewFromWidget(QWidget *widget)
697
    {
698
        if (!widget) {
699
            return nullptr;
700
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
701
        auto *view = qobject_cast<KTextEditor::View *>(widget);
702
        if (view) {
703
            return view;
704
        } else {
705
            return viewFromWidget(widget->parentWidget());
706
        }
707
708
    }

709
710
711
712
713
714
    /**
     * @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
715
    static void expandToFullHeaderRange(KTextEditor::Range &range, QStringView lineText)
716
717
718
719
720
    {
        auto expandRangeTo = [lineText, &range](QChar c, int startPos) {
            int end = lineText.indexOf(c, startPos);
            if (end > -1) {
                auto startC = range.start();
721
                startC.setColumn(startPos);
722
723
724
725
726
727
728
729
730
                auto endC = range.end();
                endC.setColumn(end);
                range.setStart(startC);
                range.setEnd(endC);
            }
        };

        int angleBracketPos = lineText.indexOf(QLatin1Char('<'), 7);
        if (angleBracketPos > -1) {
731
            expandRangeTo(QLatin1Char('>'), angleBracketPos + 1);
732
733
734
        } else {
            int startPos = lineText.indexOf(QLatin1Char('"'), 7);
            if (startPos > -1) {
735
                expandRangeTo(QLatin1Char('"'), startPos + 1);
736
737
738
739
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
740
741
742
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
743

744
        // we are only concerned with mouse events for now :)
745
        if (!mouseEvent) {
746
            return false;
747
        }
748
749

        // common stuff that we need for both events
Alexander Lohnau's avatar
Alexander Lohnau committed
750
        auto wid = qobject_cast<QWidget *>(obj);
751
        auto v = viewFromWidget(wid);
752
        if (!v) {
753
            return false;
754
        }
755
756

        const auto coords = wid->mapTo(v, mouseEvent->pos());
Alexander Lohnau's avatar
Alexander Lohnau committed
757
        const auto cur = v->coordinatesToCursor(coords);
758
        // there isn't much we can do now, just bail out
759
        if (!cur.isValid()) {
760
            return false;
761
        }
762

763
        auto doc = v->document();
764
        if (!doc) {
765
            return false;
766
        }
767
768
769

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

770
771
772
773
774
        // 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()) {
775
                    v->setCursorPosition(cur);
776
777
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
                    goToDefinition();
778
779
                }
            }
780
781
782
783
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
784
                auto range = doc->wordRangeAt(cur);
785
                if (!word.isEmpty() && range.isValid()) {
786
787
788
789
790
791
792
                    // 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);
                    }

793
794
795
796
797
                    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);
798
                } else {
799
                    // if there is no word, unset the cursor and remove the highlight
800
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
801
                }
802
803
804
805
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
                m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
806
807
808
809
810
811
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
812
    ~LSPClientActionView() override
813
    {
814
815
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
816
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
817
818
819
820
        }

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

824
825
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
826
827
    }

828
829
830
831
832
833
834
835
836
    QFont getEditorFont()
    {
        auto ciface = qobject_cast<KTextEditor::ConfigInterface *>(m_mainWindow->activeView());
        if (ciface) {
            return ciface->configValue(QStringLiteral("font")).value<QFont>();
        }
        return QFont();
    }

837
838
839
840
841
842
843
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
844

845
        // styling
Waqar Ahmed's avatar
Waqar Ahmed committed
846
        treeView->setItemDelegate(new LocationTreeDelegate(treeView, getEditorFont()));
847