lspclientpluginview.cpp 90.2 KB
Newer Older
1
2
/*  SPDX-License-Identifier: MIT

3
    SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com>
4

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

#include "lspclientpluginview.h"
#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
#include <ktexteditor/markinterface.h>
#include <ktexteditor/movinginterface.h>
#include <ktexteditor/movingrange.h>
36
#include <ktexteditor_version.h>
37

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

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

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

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

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

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

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

private:
    _kind m_value;
};

103
104
105
106
107
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;
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
146
147
148
149
150
151
        auto doc = v->document();
        if (doc && doc->url() == url)
            return doc;
    }
    return nullptr;
}

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

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

167
    // called with non-descending lineno
168
169
    QString line(int lineno)
    {
170
171
172
        if (lineno == lastLineNo) {
            return lastLine;
        }
173
174
        while (file.isOpen() && !file.atEnd()) {
            auto line = file.readLine();
175
            if (++lastLineNo == lineno) {
176
177
178
179
180
181
                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);
                }
182
                while (text.size() && text.at(text.size() - 1).isSpace())
183
                    text.chop(1);
184
                lastLine = text;
185
186
187
188
189
190
191
                return text;
            }
        }
        return QString();
    }
};

192
193
194
195

/**
 * @brief This is just a helper class that provides "underline" on Ctrl + click
 */
196
struct CtrlHoverFeedback : public QObject
197
{
198
    Q_OBJECT
199
200
201
202
public:

    void highlight(KTextEditor::View* activeView)
    {
203
204
205
206
207
208
209
210
211
        // sanity checks
        if (!activeView)
            return;

        auto doc = activeView->document();
        if (!doc)
            return;

        // set the cursor
212
213
214
        if (w)
            w->setCursor(Qt::PointingHandCursor);

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
        // underline the hovered word
        auto mr = ranges[doc];
        if (mr) {
            mr->setRange(range);
        } else {
            auto miface = qobject_cast<KTextEditor::MovingInterface*>(doc);
            if (!miface)
                return;
            mr = miface->newMovingRange(range);
            ranges[doc] = mr;
            connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clear(KTextEditor::Document *)), Qt::UniqueConnection);
            connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clear(KTextEditor::Document *)), Qt::UniqueConnection);
        }

        static KTextEditor::Attribute::Ptr attr;
        if (!attr) {
            attr = new KTextEditor::Attribute;
            attr->setUnderlineStyle(QTextCharFormat::SingleUnderline);
233
        }
234
        mr->setAttribute(attr);
235
236
    }

237
    void clear(KTextEditor::View* activeView)
238
    {
239
240
241
242
243
        if (activeView) {
            auto doc = activeView->document();
            if (doc) {
                auto& mr = ranges[doc];
                if (mr)
244
                    mr->setRange(KTextEditor::Range::invalid());
245
            }
246
247
248
249
250
251
252
253
254
255
256
257
258
259
        }
    }

    void setRangeAndWidget(const KTextEditor::Range& r, QWidget* wid)
    {
        range = r;
        w = wid;
    }

    bool isValid() const
    {
        return w != nullptr;
    }

260
261
262
263
264
265
266
267
private:

    Q_SLOT void clear(KTextEditor::Document* doc)
    {
        if (doc)
            delete ranges[doc];
    }

268
269
private:
    QWidget* w = nullptr;
270
    QHash<KTextEditor::Document*, KTextEditor::MovingRange*> ranges;
271
272
273
    KTextEditor::Range range;
};

274
class LSPClientActionView : public QObject
275
276
277
{
    Q_OBJECT

278
    typedef LSPClientActionView self_type;
279
280
281

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
282
    KXMLGUIClient *m_client;
283
    QSharedPointer<LSPClientServerManager> m_serverManager;
284
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
285
    QScopedPointer<LSPClientCompletion> m_completion;
286
    QScopedPointer<LSPClientHover> m_hover;
287
    QScopedPointer<KTextEditor::TextHintProvider> m_forwardHover;
288
289
    QScopedPointer<QObject> m_symbolView;

290
291
292
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
    QPointer<QAction> m_findRef;
293
    QPointer<QAction> m_findImpl;
294
    QPointer<QAction> m_triggerHighlight;
295
    QPointer<QAction> m_triggerSymbolInfo;
296
    QPointer<QAction> m_triggerFormat;
297
    QPointer<QAction> m_triggerRename;
298
    QPointer<QAction> m_complDocOn;
299
    QPointer<QAction> m_refDeclaration;
300
    QPointer<QAction> m_autoHover;
301
    QPointer<QAction> m_onTypeFormatting;
302
    QPointer<QAction> m_incrementalSync;
303
304
305
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
306
    QPointer<QAction> m_diagnosticsHover;
307
    QPointer<QAction> m_diagnosticsSwitch;
308
309
310
    QPointer<QAction> m_messages;
    QPointer<KSelectAction> m_messagesAutoSwitch;
    QPointer<QAction> m_messagesSwitch;
311
    QPointer<QAction> m_closeDynamic;
312
313
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
314

315
316
317
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
318
    // applied search ranges
319
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
320
    RangeCollection m_ranges;
Christoph Cullmann's avatar
Christoph Cullmann committed
321
    QHash<KTextEditor::Document *, QHash<int, QVector<KTextEditor::MovingRange *>>> m_semanticHighlightRanges;
322
    // applied marks
323
    typedef QSet<KTextEditor::Document *> DocumentCollection;
324
    DocumentCollection m_marks;
325
326
327
328
    // 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;
329
330
331
    // 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
332
    QPointer<QTreeView> m_defTree;
333
    // ... and for declaration
334
    QPointer<QTreeView> m_declTree;
335

336
    // diagnostics tab
337
    QPointer<QTreeView> m_diagnosticsTree;
338
    // tree widget is either owned here or by tab
339
340
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
341
342
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
343
344
    // and marks
    DocumentCollection m_diagnosticsMarks;
345

346
347
348
349
350
351
    using MessagesWidget = QPlainTextEdit;
    // messages tab
    QPointer<MessagesWidget> m_messagesView;
    // widget is either owned here or by tab
    QScopedPointer<MessagesWidget> m_messagesViewOwn;

352
353
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
354
355
356
357

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

358
359
360
361
362
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

363
364
    // accept incoming applyEdit
    bool m_accept_edit = false;
365
366
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
367

368
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
369

370
371
372
373
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
374

375
376
377
378
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
379

380
381
382
383
384
385
386
387
388
389
390
391
392
    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);
        }
    };

393
public:
394
395
396
397
398
399
400
401
    LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer<LSPClientServerManager> serverManager)
        : QObject(mainWin)
        , m_plugin(plugin)
        , m_mainWindow(mainWin)
        , m_client(client)
        , m_serverManager(std::move(serverManager))
        , m_completion(LSPClientCompletion::new_(m_serverManager))
        , m_hover(LSPClientHover::new_(m_serverManager))
402
        , m_forwardHover(new ForwardingTextHintProvider(this))
403
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
404
405
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
406
407
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
408
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
409

410
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
411
        m_findDef->setText(i18n("Go to Definition"));
412
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
413
        m_findDecl->setText(i18n("Go to Declaration"));
414
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
415
        m_findRef->setText(i18n("Find References"));
416
417
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
418
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
419
        m_triggerHighlight->setText(i18n("Highlight"));
420
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
421
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
422
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
423
        m_triggerFormat->setText(i18n("Format"));
424
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
425
        m_triggerRename->setText(i18n("Rename"));
426

427
        // general options
428
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
429
430
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
431
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
432
433
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
434
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
435
436
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
437
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
438
439
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
440
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
441
442
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
443

444
        // diagnostics
445
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
446
447
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
448
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
449
450
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
451
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
452
453
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
454
455
456
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
457
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
458
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
459

460
461
462
463
464
465
466
467
468
469
470
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);
        m_messagesAutoSwitch = new KSelectAction(i18n("Switch to messages tab upon message level"), this);
        actionCollection()->addAction(QStringLiteral("lspclient_messages_auto_switch"), m_messagesAutoSwitch);
        const QStringList list {i18nc("@info", "Never"), i18nc("@info", "Error"), i18nc("@info", "Warning"), i18nc("@info", "Information"), i18nc("@info", "Log")};
        m_messagesAutoSwitch->setItems(list);
        m_messagesSwitch = actionCollection()->addAction(QStringLiteral("lspclient_messages_switch"), this, &self_type::switchToMessages);
        m_messagesSwitch->setText(i18n("Switch to messages tab"));

471
472
473
        // 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"));
474
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
475
        m_restartServer->setText(i18n("Restart LSP Server"));
476
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
477
478
        m_restartAll->setText(i18n("Restart All LSP Servers"));

479
480
481
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
482
483
484
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
        menu->addAction(m_findRef);
485
        menu->addAction(m_findImpl);
486
        menu->addAction(m_triggerHighlight);
487
        menu->addAction(m_triggerSymbolInfo);
488
        menu->addAction(m_triggerFormat);
489
        menu->addAction(m_triggerRename);
490
        menu->addSeparator();
491
        menu->addAction(m_diagnosticsSwitch);
492
        menu->addAction(m_messagesSwitch);
493
        menu->addAction(m_closeDynamic);
494
        menu->addSeparator();
495
496
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
        menu->addSeparator();
        // more options
        auto moreOptions = new KActionMenu(i18n("More options"), this);
        menu->addAction(moreOptions);
        moreOptions->addAction(m_complDocOn);
        moreOptions->addAction(m_refDeclaration);
        moreOptions->addAction(m_autoHover);
        moreOptions->addAction(m_onTypeFormatting);
        moreOptions->addAction(m_incrementalSync);
        moreOptions->addSeparator();
        moreOptions->addAction(m_diagnostics);
        moreOptions->addAction(m_diagnosticsHighlight);
        moreOptions->addAction(m_diagnosticsMark);
        moreOptions->addAction(m_diagnosticsHover);
        moreOptions->addSeparator();
        moreOptions->addAction(m_messages);
        moreOptions->addAction(m_messagesAutoSwitch);
514
515
516

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

518
        // toolview
519
        m_toolView.reset(mainWin->createToolView(plugin, QStringLiteral("kate_lspclient"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("LSP Client")));
520
        m_tabWidget = new QTabWidget(m_toolView.data());
521
        m_toolView->layout()->addWidget(m_tabWidget);
522
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
523
524
525
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
526
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
527

528
        // diagnostics tab
529
        m_diagnosticsTree = new QTreeView();
530
        m_diagnosticsTree->setAlternatingRowColors(true);
531
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
532
        m_diagnosticsModel.reset(new QStandardItemModel());
533
        m_diagnosticsModel->setColumnCount(1);
534
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
535
        configureTreeView(m_diagnosticsTree);
536
537
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
538

539
540
541
542
543
544
        // messages tab
        m_messagesView = new QPlainTextEdit();
        m_messagesView->setMaximumBlockCount(100);
        m_messagesView->setReadOnly(true);
        m_messagesViewOwn.reset(m_messagesView);

545
546
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
547
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
548

549
550
551
552
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

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

553
        configUpdated();
554
555
556
        updateState();
    }

557
558
559
560
561
562
563
564
565
566
567
568
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
                if (c)
                    c->installEventFilter(this);
            }
        }
    }

569
    // This is taken from KDevelop :)
570
571
572
573
574
575
576
577
578
579
580
581
582
583
    KTextEditor::View* viewFromWidget(QWidget* widget)
    {
        if (!widget)
            return nullptr;
        auto* view = qobject_cast<KTextEditor::View*>(widget);
        if (view)
            return view;
        else
            return viewFromWidget(widget->parentWidget());
    }

    bool eventFilter(QObject* obj, QEvent* event) override {
        auto mouseEvent = dynamic_cast<QMouseEvent*>(event);

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
        // we are only concerned with mouse events for now :)
        if (!mouseEvent)
            return false;

        // common stuff that we need for both events
        auto wid = qobject_cast<QWidget*>(obj);
        auto v = viewFromWidget(wid);
        if (!v)
            return false;

        const auto coords = wid->mapTo(v, mouseEvent->pos());
        const auto cur =  v->coordinatesToCursor(coords);
        const auto word = v->document()->wordAt(cur);

        // 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!!
                v->setCursorPosition(cur);
                if (!word.isEmpty()) {
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
                    goToDefinition();
606
607
                }
            }
608
609
610
611
612
613
614
615
616
617
618
619
620
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
                auto doc = v->document();
                const auto hoveredWord = doc->wordAt(cur);
                const auto range = doc->wordRangeAt(cur);
                if (!hoveredWord.isEmpty() && range.isValid()) {
                    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);
621
                } else {
622
                    // if there is no word, unset the cursor and remove the highlight
623
624
                    if (wid)
                        wid->unsetCursor();
625
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
626
                }
627
628
629
630
631
632
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
                if (wid)
                    wid->unsetCursor();
                m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
633
634
635
636
637
638
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
639
    ~LSPClientActionView() override
640
    {
641
642
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
643
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
644
645
646
647
        }

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

651
652
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
653
654
    }

655
656
657
658
659
660
661
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
662
663
664
665
666
667
668
669

        // 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);
        auto h = [menu](const QPoint &) { menu->popup(QCursor::pos()); };
        connect(treeView, &QTreeView::customContextMenuRequested, h);
670
671
    }

672
673
    void displayOptionChanged()
    {
674
675
        m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
        m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
676
        m_diagnosticsHover->setEnabled(m_diagnostics->isChecked());
677
678
679
680
681
682
683
684
685
686
687
688
689
        // messages tab should go first
        int messagesIndex = m_tabWidget->indexOf(m_messagesView);
        if (m_messages->isChecked() && m_messagesViewOwn) {
            m_tabWidget->insertTab(0, m_messagesView, i18nc("@title:tab", "Messages"));
            messagesIndex = 0;
            m_messagesViewOwn.take();
        } else if (!m_messages->isChecked() && !m_messagesViewOwn) {
            m_messagesViewOwn.reset(m_messagesView);
            m_tabWidget->removeTab(messagesIndex);
            messagesIndex = -1;
        }
        // diagnstics tab next
        int diagnosticsIndex = m_tabWidget->indexOf(m_diagnosticsTree);
690
691
692
        // setTabEnabled may still show it ... so let's be more forceful
        if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.take();
693
            m_tabWidget->insertTab(messagesIndex + 1, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
694
695
        } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
696
            m_tabWidget->removeTab(diagnosticsIndex);
697
        }
698
        m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
699
        m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
700
        updateState();
701
702
703
704
705
706
    }

    void configUpdated()
    {
        if (m_complDocOn)
            m_complDocOn->setChecked(m_plugin->m_complDoc);
707
708
        if (m_refDeclaration)
            m_refDeclaration->setChecked(m_plugin->m_refDeclaration);
709
710
        if (m_autoHover)
            m_autoHover->setChecked(m_plugin->m_autoHover);
711
712
        if (m_onTypeFormatting)
            m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting);
713
714
        if (m_incrementalSync)
            m_incrementalSync->setChecked(m_plugin->m_incrementalSync);
715
716
717
718
719
720
        if (m_diagnostics)
            m_diagnostics->setChecked(m_plugin->m_diagnostics);
        if (m_diagnosticsHighlight)
            m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight);
        if (m_diagnosticsMark)
            m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark);
721
722
        if (m_diagnosticsHover)
            m_diagnosticsHover->setChecked(m_plugin->m_diagnosticsHover);
723
724
725
726
        if (m_messages)
            m_messages->setChecked(m_plugin->m_messages);
        if (m_messagesAutoSwitch)
            m_messagesAutoSwitch->setCurrentItem(m_plugin->m_messagesAutoSwitch);
727
728
729
        displayOptionChanged();
    }

730
731
732
733
734
    void restartCurrent()
    {
        KTextEditor::View *activeView = m_mainWindow->activeView();
        auto server = m_serverManager->findServer(activeView);
        if (server)
735
            m_serverManager->restart(server.data());
736
737
    }

738
739
740
741
    void restartAll()
    {
        m_serverManager->restart(nullptr);
    }
742

743
    static void clearMarks(KTextEditor::Document *doc, RangeCollection &ranges, DocumentCollection &docs, uint markType)
744
    {
745
        KTextEditor::MarkInterface *iface = docs.contains(doc) ? qobject_cast<KTextEditor::MarkInterface *>(doc) : nullptr;
746
        if (iface) {
747
748
            const QHash<int, KTextEditor::Mark *> marks = iface->marks();
            QHashIterator<int, KTextEditor::Mark *> i(marks);
749
750
            while (i.hasNext()) {
                i.next();
751
752
                if (i.value()->type & markType) {
                    iface->removeMark(i.value()->line, markType);
753
754
                }
            }
755
            docs.remove(doc);
756
757
        }

758
        for (auto it = ranges.find(doc); it != ranges.end() && it.key() == doc;) {
759
            delete it.value();
760
            it = ranges.erase(it);
761
762
763
        }
    }

764
    static void clearMarks(RangeCollection &ranges, DocumentCollection &docs, uint markType)
765
    {
766
        while (!ranges.empty()) {
767
            clearMarks(ranges.begin().key(), ranges, docs, markType);
768
        }
769
770
771
772
    }

    Q_SLOT void clearAllMarks(KTextEditor::Document *doc)
    {
773
774
        clearMarks(doc, m_ranges, m_marks, RangeData::markType);
        clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll);
775
776
777
778
    }

    void clearAllLocationMarks()
    {
779
        clearMarks(m_ranges, m_marks, RangeData::markType);
780
        // no longer add any again
781
782
        m_ownedModel.reset();
        m_markModel.clear();
783
784
    }

785
786
    void clearAllDiagnosticsMarks()
    {
787
        clearMarks(m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll);
788
789
    }

790
    void addMarks(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, DocumentCollection *docs)
791
    {
792
        Q_ASSERT(item);
793
        KTextEditor::MovingInterface *miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
794
        Q_ASSERT(miface);
Christoph Cullmann's avatar
Christoph Cullmann committed
795
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
796
797
        KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(doc);
#else
798
        KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
799
#endif
800
        Q_ASSERT(iface);
801
        KTextEditor::View *activeView = m_mainWindow->activeView();
802
        KTextEditor::ConfigInterface *ciface = qobject_cast<KTextEditor::ConfigInterface *>(activeView);
803

804
        auto url = item->data(RangeData::FileUrlRole).toUrl();
805
806
807
        // document url could end up empty while in intermediate reload state
        // (and then it might match a parent item with no RangeData at all)
        if (url != doc->url() || url.isEmpty())
808
809
            return;

810
        KTextEditor::Range range = item->data(RangeData::RangeRole).value<LSPRange>();
811
812
        if (!range.isValid() || range.isEmpty())
            return;
813
        auto line = range.start().line();
814
        RangeData::KindEnum kind = RangeData::KindEnum(item->data(RangeData::KindRole).toInt());
815
816
817

        KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());

818
        bool enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsHighlight && m_diagnosticsHighlight->isChecked();
819
        KTextEditor::MarkInterface::MarkTypes markType = RangeData::markType;
820
        switch (kind) {
821
822
823
824
825
        case RangeData::KindEnum::Text: {
            // well, it's a bit like searching for something, so re-use that color
            QColor rangeColor = Qt::yellow;
            if (ciface) {
                rangeColor = ciface->configValue(QStringLiteral("search-highlight-color")).value<QColor>();
826
            }
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
            attr->setBackground(rangeColor);
            enabled = true;
            break;
        }
        // FIXME are there any symbolic/configurable ways to pick these colors?
        case RangeData::KindEnum::Read:
            attr->setBackground(Qt::green);
            enabled = true;
            break;
        case RangeData::KindEnum::Write:
            attr->setBackground(Qt::red);
            enabled = true;
            break;
        // use underlining for diagnostics to avoid lots of fancy flickering
        case RangeData::KindEnum::Error:
            markType = RangeData::markTypeDiagError;
            attr->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
            attr->setUnderlineColor(Qt::red);
            break;
        case RangeData::KindEnum::Warning:
            markType = RangeData::markTypeDiagWarning;
            attr->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
            attr->setUnderlineColor(QColor(255, 128, 0));
            break;
        case RangeData::KindEnum::Information:
        case RangeData::KindEnum::Hint:
        case RangeData::KindEnum::Related:
            markType = RangeData::markTypeDiagOther;
            attr->setUnderlineStyle(QTextCharFormat::DashUnderline);
            attr->setUnderlineColor(Qt::blue);
            break;
858
        }
859
        if (activeView) {
860
            attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color());
861
862
863
        }

        // highlight the range
864
        if (enabled && ranges) {
865
            KTextEditor::MovingRange *mr = miface->newMovingRange(range);
866
867
868
            mr->setAttribute(attr);
            mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
            mr->setAttributeOnlyForViews(true);
869
            ranges->insert(doc, mr);
870
        }
871
872

        // add match mark for range
873
        bool handleClick = true;
874
        enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsMark && m_diagnosticsMark->isChecked();
875
        switch (markType) {
876
877
        case RangeData::markType:
            iface->setMarkDescription(markType, i18n("RangeHighLight"));
Christoph Cullmann's avatar
Christoph Cullmann committed
878
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
879
880
            iface->setMarkIcon(markType, QIcon());
#else
881
            iface->setMarkPixmap(markType, QIcon().pixmap(0, 0));
882
#endif
883
884
885
886
887
            handleClick = false;
            enabled = true;
            break;
        case RangeData::markTypeDiagError:
            iface->setMarkDescription(markType, i18n("Error"));
Christoph Cullmann's avatar
Christoph Cullmann committed
888
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
889
890
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error));
#else
891
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error).pixmap(ps, ps));
892
#endif
893
894
895
            break;
        case RangeData::markTypeDiagWarning:
            iface->setMarkDescription(markType, i18n("Warning"));
Christoph Cullmann's avatar
Christoph Cullmann committed
896
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
897
898
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning));
#else
899
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning).pixmap(ps, ps));
900
#endif
901
902
903
            break;
        case RangeData::markTypeDiagOther:
            iface->setMarkDescription(markType, i18n("Information"));
Christoph Cullmann's avatar
Christoph Cullmann committed
904
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
905
906
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information));
#else
907
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information).pixmap(ps, ps));
908
#endif
909
910
911
912
            break;
        default:
            Q_ASSERT(false);
            break;
913
        }
914
        if (enabled && docs) {