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

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

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

#include "lspclient_debug.h"

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

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

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

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

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

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

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

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

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

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

private:
    _kind m_value;
};

101
102
103
104
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
105
106
static constexpr KTextEditor::MarkInterface::MarkTypes markTypeDiagAll =
    KTextEditor::MarkInterface::MarkTypes(markTypeDiagError | markTypeDiagWarning | markTypeDiagOther);
107
108

}
109

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

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

140
KTextEditor::Document *findDocument(KTextEditor::MainWindow *mainWindow, const QUrl &url)
141
142
{
    auto views = mainWindow->views();
143
    for (const auto v : views) {
144
        auto doc = v->document();
145
        if (doc && doc->url() == url) {
146
            return doc;
147
        }
148
149
150
151
    }
    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
                }
185
                lastLine = text;
186
187
188
189
190
191
192
                return text;
            }
        }
        return QString();
    }
};

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

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

        // set the cursor
213
        if (w) {
214
            w->setCursor(Qt::PointingHandCursor);
215
        }
216

217
218
219
220
221
        // underline the hovered word
        auto mr = ranges[doc];
        if (mr) {
            mr->setRange(range);
        } else {
Alexander Lohnau's avatar
Alexander Lohnau committed
222
            auto miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
223
            if (!miface) {
224
                return;
225
            }
226
227
            mr = miface->newMovingRange(range);
            ranges[doc] = mr;
228
            // clang-format off
Alexander Lohnau's avatar
Alexander Lohnau committed
229
            connect(doc,
230
                    SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
231
                    this,
232
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
233
234
                    Qt::UniqueConnection);
            connect(doc,
235
                    SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
236
                    this,
237
                    SLOT(clear(KTextEditor::Document*)),
Alexander Lohnau's avatar
Alexander Lohnau committed
238
                    Qt::UniqueConnection);
239
            // clang-format on
240
241
242
243
244
245
        }

        static KTextEditor::Attribute::Ptr attr;
        if (!attr) {
            attr = new KTextEditor::Attribute;
            attr->setUnderlineStyle(QTextCharFormat::SingleUnderline);
246
        }
247
        mr->setAttribute(attr);
248
249
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
250
    void clear(KTextEditor::View *activeView)
251
    {
252
253
254
        if (activeView) {
            auto doc = activeView->document();
            if (doc) {
Alexander Lohnau's avatar
Alexander Lohnau committed
255
                auto &mr = ranges[doc];
256
                if (mr) {
257
                    mr->setRange(KTextEditor::Range::invalid());
258
                }
259
            }
260
        }
261
        w.clear();
262
263
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
264
    void setRangeAndWidget(const KTextEditor::Range &r, QWidget *wid)
265
266
267
268
269
270
271
    {
        range = r;
        w = wid;
    }

    bool isValid() const
    {
272
        return !w.isNull();
273
274
    }

275
private:
Alexander Lohnau's avatar
Alexander Lohnau committed
276
    Q_SLOT void clear(KTextEditor::Document *doc)
277
    {
278
279
280
281
        if (doc) {
            auto it = ranges.find(doc);
            if (it != ranges.end()) {
                delete *it;
Waqar Ahmed's avatar
Waqar Ahmed committed
282
                ranges.erase(it);
283
284
            }
        }
285
286
    }

287
private:
288
    QPointer<QWidget> w;
Alexander Lohnau's avatar
Alexander Lohnau committed
289
    QHash<KTextEditor::Document *, KTextEditor::MovingRange *> ranges;
290
291
292
    KTextEditor::Range range;
};

293
class LSPClientActionView : public QObject
294
295
296
{
    Q_OBJECT

297
    typedef LSPClientActionView self_type;
298
299
300

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
301
    KXMLGUIClient *m_client;
302
    QSharedPointer<LSPClientServerManager> m_serverManager;
303
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
304
    QScopedPointer<LSPClientCompletion> m_completion;
305
    QScopedPointer<LSPClientHover> m_hover;
306
    QScopedPointer<KTextEditor::TextHintProvider> m_forwardHover;
307
308
    QScopedPointer<QObject> m_symbolView;

309
310
311
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
    QPointer<QAction> m_findRef;
312
    QPointer<QAction> m_findImpl;
313
    QPointer<QAction> m_triggerHighlight;
314
    QPointer<QAction> m_triggerSymbolInfo;
315
    QPointer<QAction> m_triggerFormat;
316
    QPointer<QAction> m_triggerRename;
317
    QPointer<QAction> m_complDocOn;
318
    QPointer<QAction> m_refDeclaration;
319
    QPointer<QAction> m_autoHover;
320
    QPointer<QAction> m_onTypeFormatting;
321
    QPointer<QAction> m_incrementalSync;
322
323
324
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
325
    QPointer<QAction> m_diagnosticsHover;
326
    QPointer<QAction> m_diagnosticsSwitch;
327
    QPointer<QAction> m_messages;
328
    QPointer<QAction> m_closeDynamic;
329
330
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
331
    QPointer<QAction> m_switchSourceHeader;
332

333
334
335
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
336
    // applied search ranges
337
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
338
    RangeCollection m_ranges;
Christoph Cullmann's avatar
Christoph Cullmann committed
339
    QHash<KTextEditor::Document *, QHash<int, QVector<KTextEditor::MovingRange *>>> m_semanticHighlightRanges;
340
    // applied marks
341
    typedef QSet<KTextEditor::Document *> DocumentCollection;
342
    DocumentCollection m_marks;
343
344
345
346
    // 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;
347
348
349
    // 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
350
    QPointer<QTreeView> m_defTree;
351
    // ... and for declaration
352
    QPointer<QTreeView> m_declTree;
353

354
    // diagnostics tab
355
    QPointer<QTreeView> m_diagnosticsTree;
356
    // tree widget is either owned here or by tab
357
358
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
359
360
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
361
362
    // and marks
    DocumentCollection m_diagnosticsMarks;
363

364
365
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
366
367
368
369

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

370
371
372
373
374
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

375
376
    // accept incoming applyEdit
    bool m_accept_edit = false;
377
378
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
379

380
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
381

382
383
384
385
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
386

387
388
389
390
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
391

392
393
394
395
396
397
398
399
400
401
402
403
404
    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);
        }
    };

405
406
407
408
409
410
411
412
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);

413
    void addPositionToHistory(const QUrl &url, KTextEditor::Cursor c);
414

415
public:
416
    LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer<LSPClientServerManager> serverManager)
417
418
419
420
421
        : QObject(mainWin)
        , m_plugin(plugin)
        , m_mainWindow(mainWin)
        , m_client(client)
        , m_serverManager(std::move(serverManager))
422
        , m_completion(LSPClientCompletion::new_(m_serverManager, plugin))
423
        , m_hover(LSPClientHover::new_(m_serverManager))
424
        , m_forwardHover(new ForwardingTextHintProvider(this))
425
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
426
427
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
428
429
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
430
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
431

432
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
433
        m_findDef->setText(i18n("Go to Definition"));
434
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
435
        m_findDecl->setText(i18n("Go to Declaration"));
436
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
437
        m_findRef->setText(i18n("Find References"));
438
439
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
440
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
441
        m_triggerHighlight->setText(i18n("Highlight"));
442
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
443
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
444
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
445
        m_triggerFormat->setText(i18n("Format"));
446
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
447
        m_triggerRename->setText(i18n("Rename"));
448
449
        m_switchSourceHeader = actionCollection()->addAction(QStringLiteral("lspclient_clangd_switchheader"), this, &self_type::clangdSwitchSourceHeader);
        m_switchSourceHeader->setText(i18n("Switch Source Header"));
450

451
        // general options
452
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
453
454
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
455
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
456
457
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
458
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
459
460
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
461
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
462
463
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
464
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
465
466
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
467

468
        // diagnostics
469
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
470
471
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
472
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
473
474
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
475
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
476
477
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
478
479
480
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
481
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
482
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
483

484
485
486
487
488
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);

489
490
491
        // 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"));
492
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
493
        m_restartServer->setText(i18n("Restart LSP Server"));
494
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
495
496
        m_restartAll->setText(i18n("Restart All LSP Servers"));

497
498
499
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
500
501
502
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
        menu->addAction(m_findRef);
503
        menu->addAction(m_findImpl);
504
        menu->addAction(m_switchSourceHeader);
505
        menu->addAction(m_triggerHighlight);
506
        menu->addAction(m_triggerSymbolInfo);
507
        menu->addAction(m_triggerFormat);
508
        menu->addAction(m_triggerRename);
509
        menu->addSeparator();
510
        menu->addAction(m_diagnosticsSwitch);
511
        menu->addAction(m_closeDynamic);
512
        menu->addSeparator();
513
514
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
        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);
531
532
533

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

535
        // toolview
Alexander Lohnau's avatar
Alexander Lohnau committed
536
537
538
539
540
        m_toolView.reset(mainWin->createToolView(plugin,
                                                 QStringLiteral("kate_lspclient"),
                                                 KTextEditor::MainWindow::Bottom,
                                                 QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")),
                                                 i18n("LSP Client")));
541
        m_tabWidget = new QTabWidget(m_toolView.data());
542
        m_toolView->layout()->addWidget(m_tabWidget);
543
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
544
545
546
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
547
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
548

549
        // diagnostics tab
550
        m_diagnosticsTree = new QTreeView();
551
        m_diagnosticsTree->setAlternatingRowColors(true);
552
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
553
        m_diagnosticsModel.reset(new QStandardItemModel());
554
        m_diagnosticsModel->setColumnCount(1);
555
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
556
        configureTreeView(m_diagnosticsTree);
557
558
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
559

560
561
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
562
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
563

564
565
566
567
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

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

568
        configUpdated();
569
570
571
        updateState();
    }

572
573
574
575
576
577
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
578
                if (c) {
579
                    c->installEventFilter(this);
580
                }
581
582
583
584
            }
        }
    }

585
    // This is taken from KDevelop :)
Alexander Lohnau's avatar
Alexander Lohnau committed
586
    KTextEditor::View *viewFromWidget(QWidget *widget)
587
    {
588
        if (!widget) {
589
            return nullptr;
590
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
591
        auto *view = qobject_cast<KTextEditor::View *>(widget);
592
        if (view) {
593
            return view;
594
        } else {
595
            return viewFromWidget(widget->parentWidget());
596
        }
597
598
    }

599
600
601
602
603
604
    /**
     * @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
605
    static void expandToFullHeaderRange(KTextEditor::Range &range, QStringView lineText)
606
607
608
609
610
    {
        auto expandRangeTo = [lineText, &range](QChar c, int startPos) {
            int end = lineText.indexOf(c, startPos);
            if (end > -1) {
                auto startC = range.start();
611
                startC.setColumn(startPos);
612
613
614
615
616
617
618
619
620
                auto endC = range.end();
                endC.setColumn(end);
                range.setStart(startC);
                range.setEnd(endC);
            }
        };

        int angleBracketPos = lineText.indexOf(QLatin1Char('<'), 7);
        if (angleBracketPos > -1) {
621
            expandRangeTo(QLatin1Char('>'), angleBracketPos + 1);
622
623
624
        } else {
            int startPos = lineText.indexOf(QLatin1Char('"'), 7);
            if (startPos > -1) {
625
                expandRangeTo(QLatin1Char('"'), startPos + 1);
626
627
628
629
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
630
631
632
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
633

634
        // we are only concerned with mouse events for now :)
635
        if (!mouseEvent) {
636
            return false;
637
        }
638
639

        // common stuff that we need for both events
Alexander Lohnau's avatar
Alexander Lohnau committed
640
        auto wid = qobject_cast<QWidget *>(obj);
641
        auto v = viewFromWidget(wid);
642
        if (!v) {
643
            return false;
644
        }
645
646

        const auto coords = wid->mapTo(v, mouseEvent->pos());
Alexander Lohnau's avatar
Alexander Lohnau committed
647
        const auto cur = v->coordinatesToCursor(coords);
648
        // there isn't much we can do now, just bail out
649
        if (!cur.isValid()) {
650
            return false;
651
        }
652

653
        auto doc = v->document();
654
        if (!doc) {
655
            return false;
656
        }
657
658
659

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

660
661
662
663
664
        // 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()) {
665
                    v->setCursorPosition(cur);
666
667
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
                    goToDefinition();
668
669
                }
            }
670
671
672
673
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
674
                auto range = doc->wordRangeAt(cur);
675
                if (!word.isEmpty() && range.isValid()) {
676
677
678
679
680
681
682
                    // 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);
                    }

683
684
685
686
687
                    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);
688
                } else {
689
                    // if there is no word, unset the cursor and remove the highlight
690
                    if (wid) {
691
                        wid->unsetCursor();
692
                    }
693
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
694
                }
695
696
697
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
698
                if (wid) {
699
                    wid->unsetCursor();
700
                }
701
                m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
702
703
704
705
706
707
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
708
    ~LSPClientActionView() override
709
    {
710
711
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
712
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
713
714
715
716
        }

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

720
721
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
722
723
    }

724
725
726
727
728
729
730
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
731
732
733
734
735
736

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

743
744
    void displayOptionChanged()
    {
745
746
        m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
        m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
747
        m_diagnosticsHover->setEnabled(m_diagnostics->isChecked());
748

749
750
        // diagnstics tab next
        int diagnosticsIndex = m_tabWidget->indexOf(m_diagnosticsTree);
751
752
753
        // setTabEnabled may still show it ... so let's be more forceful
        if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.take();
754
            m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
755
756
        } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
757
            m_tabWidget->removeTab(diagnosticsIndex);
758
        }
759
        m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
760
        m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
761
        updateState();
762
763
764
765
    }

    void configUpdated()
    {
766
        if (m_complDocOn) {
767
            m_complDocOn->setChecked(m_plugin->m_complDoc);
768
769
        }
        if (m_refDeclaration) {
770
            m_refDeclaration->setChecked(m_plugin->m_refDeclaration);
771
772
        }
        if (m_autoHover) {
773
            m_autoHover->setChecked(m_plugin->m_autoHover);
774
775
        }
        if (m_onTypeFormatting) {
776
            m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting);
777
778
        }
        if (m_incrementalSync) {
779
            m_incrementalSync->setChecked(m_plugin->m_incrementalSync);
780
781
        }
        if (m_diagnostics) {
782
            m_diagnostics->setChecked(m_plugin->m_diagnostics);
783
784
        }
        if (m_diagnosticsHighlight) {
785
            m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight);
786
787
        }
        if (m_diagnosticsMark) {
788
            m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark);
789
790
        }
        if (m_diagnosticsHover) {
791
            m_diagnosticsHover->setChecked(m_plugin->m_diagnosticsHover);
792
793
        }
        if (m_messages) {
794
            m_messages->setChecked(m_plugin->m_messages);
795
        }
796
797
798
        displayOptionChanged();
    }

799
800
801
802
    void restartCurrent()
    {
        KTextEditor::View *activeView = m_mainWindow->activeView();
        auto server = m_serverManager->findServer(activeView);
803
        if (server) {
804
            m_serverManager->restart(server.data());
805
        }
806
807
    }

808
809
810
811
    void restartAll()
    {
        m_serverManager->restart(nullptr);
    }
812

813
    static void clearMarks(KTextEditor::Document *doc, RangeCollection &ranges, DocumentCollection &docs, uint markType)
814
    {
815
        KTextEditor::MarkInterface *iface = docs.contains(doc) ? qobject_cast<KTextEditor::MarkInterface *>(doc) : nullptr;
816
        if (iface) {
817
818
            const QHash<int, KTextEditor::Mark *> marks = iface->marks();
            QHashIterator<int, KTextEditor::Mark *> i(marks);
819
820
            while (i.hasNext()) {
                i.next();
821
822
                if (i.value()->type & markType) {
                    iface->removeMark(i.value()->line, markType);
823
824
                }
            }
825
            docs.remove(doc);