lspclientpluginview.cpp 93.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
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
311
    QPointer<QAction> m_findTypeDef;
312
    QPointer<QAction> m_findRef;
313
    QPointer<QAction> m_findImpl;
314
    QPointer<QAction> m_triggerHighlight;
315
    QPointer<QAction> m_triggerSymbolInfo;
316
    QPointer<QAction> m_triggerFormat;
317
    QPointer<QAction> m_triggerRename;
318
    QPointer<QAction> m_complDocOn;
319
    QPointer<QAction> m_refDeclaration;
320
    QPointer<QAction> m_autoHover;
321
    QPointer<QAction> m_onTypeFormatting;
322
    QPointer<QAction> m_incrementalSync;
323
324
325
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
326
    QPointer<QAction> m_diagnosticsHover;
327
    QPointer<QAction> m_diagnosticsSwitch;
328
    QPointer<QAction> m_messages;
329
    QPointer<QAction> m_closeDynamic;
330
331
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
332
    QPointer<QAction> m_switchSourceHeader;
333
    QPointer<KActionMenu> m_requestCodeAction;
334

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

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

368
369
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
370
371
372
373

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

374
375
376
377
378
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

379
380
    // accept incoming applyEdit
    bool m_accept_edit = false;
381
382
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
383

384
    CtrlHoverFeedback m_ctrlHoverFeedback = {};
385

386
387
388
389
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
390

391
392
393
394
    // inner class that forwards directly to method for convenience
    class ForwardingTextHintProvider : public KTextEditor::TextHintProvider
    {
        LSPClientActionView *m_parent;
Christoph Cullmann's avatar
Christoph Cullmann committed
395

396
397
398
399
400
401
402
403
404
405
406
407
408
    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);
        }
    };

409
410
411
412
413
414
415
416
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);

417
    void addPositionToHistory(const QUrl &url, KTextEditor::Cursor c);
418

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

436
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
437
        m_findDef->setText(i18n("Go to Definition"));
438
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
439
        m_findDecl->setText(i18n("Go to Declaration"));
440
441
        m_findTypeDef = actionCollection()->addAction(QStringLiteral("lspclient_find_type_definition"), this, &self_type::goToTypeDefinition);
        m_findTypeDef->setText(i18n("Go to Type Definition"));
442
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
443
        m_findRef->setText(i18n("Find References"));
444
445
        m_findImpl = actionCollection()->addAction(QStringLiteral("lspclient_find_implementations"), this, &self_type::findImplementation);
        m_findImpl->setText(i18n("Find Implementations"));
446
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
447
        m_triggerHighlight->setText(i18n("Highlight"));
448
        m_triggerSymbolInfo = actionCollection()->addAction(QStringLiteral("lspclient_symbol_info"), this, &self_type::symbolInfo);
Andrzej Dabrowski's avatar
Andrzej Dabrowski committed
449
        m_triggerSymbolInfo->setText(i18n("Symbol Info"));
450
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
451
        m_triggerFormat->setText(i18n("Format"));
452
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
453
        m_triggerRename->setText(i18n("Rename"));
454
455
        m_switchSourceHeader = actionCollection()->addAction(QStringLiteral("lspclient_clangd_switchheader"), this, &self_type::clangdSwitchSourceHeader);
        m_switchSourceHeader->setText(i18n("Switch Source Header"));
456
457
458
        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);
459

460
        // general options
461
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
462
463
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
464
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
465
466
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
467
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
468
469
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
470
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
471
472
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
473
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
474
475
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
476

477
        // diagnostics
478
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
479
480
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
481
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
482
483
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
484
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
485
486
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
487
488
489
        m_diagnosticsHover = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_hover"), this, &self_type::displayOptionChanged);
        m_diagnosticsHover->setText(i18n("Show diagnostics on hover"));
        m_diagnosticsHover->setCheckable(true);
490
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
491
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
492

493
494
495
496
497
        // messages
        m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
        m_messages->setText(i18n("Show messages"));
        m_messages->setCheckable(true);

498
499
500
        // 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"));
501
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
502
        m_restartServer->setText(i18n("Restart LSP Server"));
503
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
504
505
        m_restartAll->setText(i18n("Restart All LSP Servers"));

506
507
508
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
509
510
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
511
        menu->addAction(m_findTypeDef);
512
        menu->addAction(m_findRef);
513
        menu->addAction(m_findImpl);
514
        menu->addAction(m_switchSourceHeader);
515
        menu->addAction(m_triggerHighlight);
516
        menu->addAction(m_triggerSymbolInfo);
517
        menu->addAction(m_triggerFormat);
518
        menu->addAction(m_triggerRename);
519
        menu->addAction(m_requestCodeAction);
520
        menu->addSeparator();
521
        menu->addAction(m_diagnosticsSwitch);
522
        menu->addAction(m_closeDynamic);
523
        menu->addSeparator();
524
525
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
        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);
542
543
544

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

546
        // toolview
Alexander Lohnau's avatar
Alexander Lohnau committed
547
548
549
550
551
        m_toolView.reset(mainWin->createToolView(plugin,
                                                 QStringLiteral("kate_lspclient"),
                                                 KTextEditor::MainWindow::Bottom,
                                                 QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")),
                                                 i18n("LSP Client")));
552
        m_tabWidget = new QTabWidget(m_toolView.data());
553
        m_toolView->layout()->addWidget(m_tabWidget);
554
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
555
556
557
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
558
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
559

560
        // diagnostics tab
561
        m_diagnosticsTree = new QTreeView();
562
        m_diagnosticsTree->setAlternatingRowColors(true);
563
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
564
        m_diagnosticsModel.reset(new QStandardItemModel());
565
        m_diagnosticsModel->setColumnCount(1);
566
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
567
        configureTreeView(m_diagnosticsTree);
568
569
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
570

571
572
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
573
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
574

575
576
577
578
        connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &LSPClientActionView::onViewCreated);

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

579
        configUpdated();
580
581
582
        updateState();
    }

583
584
585
586
587
588
    void onViewCreated(KTextEditor::View *view)
    {
        if (view) {
            view->installEventFilter(this);
            auto childs = view->children();
            for (auto c : childs) {
589
                if (c) {
590
                    c->installEventFilter(this);
591
                }
592
593
594
595
            }
        }
    }

596
    // This is taken from KDevelop :)
Alexander Lohnau's avatar
Alexander Lohnau committed
597
    KTextEditor::View *viewFromWidget(QWidget *widget)
598
    {
599
        if (!widget) {
600
            return nullptr;
601
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
602
        auto *view = qobject_cast<KTextEditor::View *>(widget);
603
        if (view) {
604
            return view;
605
        } else {
606
            return viewFromWidget(widget->parentWidget());
607
        }
608
609
    }

610
611
612
613
614
615
    /**
     * @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
616
    static void expandToFullHeaderRange(KTextEditor::Range &range, QStringView lineText)
617
618
619
620
621
    {
        auto expandRangeTo = [lineText, &range](QChar c, int startPos) {
            int end = lineText.indexOf(c, startPos);
            if (end > -1) {
                auto startC = range.start();
622
                startC.setColumn(startPos);
623
624
625
626
627
628
629
630
631
                auto endC = range.end();
                endC.setColumn(end);
                range.setStart(startC);
                range.setEnd(endC);
            }
        };

        int angleBracketPos = lineText.indexOf(QLatin1Char('<'), 7);
        if (angleBracketPos > -1) {
632
            expandRangeTo(QLatin1Char('>'), angleBracketPos + 1);
633
634
635
        } else {
            int startPos = lineText.indexOf(QLatin1Char('"'), 7);
            if (startPos > -1) {
636
                expandRangeTo(QLatin1Char('"'), startPos + 1);
637
638
639
640
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
641
642
643
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
644

645
        // we are only concerned with mouse events for now :)
646
        if (!mouseEvent) {
647
            return false;
648
        }
649
650

        // common stuff that we need for both events
Alexander Lohnau's avatar
Alexander Lohnau committed
651
        auto wid = qobject_cast<QWidget *>(obj);
652
        auto v = viewFromWidget(wid);
653
        if (!v) {
654
            return false;
655
        }
656
657

        const auto coords = wid->mapTo(v, mouseEvent->pos());
Alexander Lohnau's avatar
Alexander Lohnau committed
658
        const auto cur = v->coordinatesToCursor(coords);
659
        // there isn't much we can do now, just bail out
660
        if (!cur.isValid()) {
661
            return false;
662
        }
663

664
        auto doc = v->document();
665
        if (!doc) {
666
            return false;
667
        }
668
669
670

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

671
672
673
674
675
        // 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()) {
676
                    v->setCursorPosition(cur);
677
678
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
                    goToDefinition();
679
680
                }
            }
681
682
683
684
        }
        // The user is hovering with Ctrl pressed
        else if (event->type() == QEvent::MouseMove) {
            if (mouseEvent->modifiers() == Qt::ControlModifier) {
685
                auto range = doc->wordRangeAt(cur);
686
                if (!word.isEmpty() && range.isValid()) {
687
688
689
690
691
692
693
                    // 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);
                    }

694
695
696
697
698
                    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);
699
                } else {
700
                    // if there is no word, unset the cursor and remove the highlight
701
                    if (wid) {
702
                        wid->unsetCursor();
703
                    }
704
                    m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
705
                }
706
707
708
            } else {
                // simple mouse move, make sure to unset the cursor
                // and remove the highlight
709
                if (wid) {
710
                    wid->unsetCursor();
711
                }
712
                m_ctrlHoverFeedback.clear(m_mainWindow->activeView());
713
714
715
716
717
718
            }
        }

        return false;
    }

Filip Gawin's avatar
Filip Gawin committed
719
    ~LSPClientActionView() override
720
    {
721
722
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
723
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
724
725
726
727
        }

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

731
732
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
733
734
    }

735
736
737
738
739
740
741
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
742
743
744
745
746
747

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

754
755
    void displayOptionChanged()
    {
756
757
        m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
        m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
758
        m_diagnosticsHover->setEnabled(m_diagnostics->isChecked());
759

760
761
        // diagnstics tab next
        int diagnosticsIndex = m_tabWidget->indexOf(m_diagnosticsTree);
762
763
764
        // setTabEnabled may still show it ... so let's be more forceful
        if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.take();
765
            m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
766
767
        } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
768
            m_tabWidget->removeTab(diagnosticsIndex);
769
        }
770
        m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
771
        m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
772
        updateState();
773
774
775
776
    }

    void configUpdated()
    {
777
        if (m_complDocOn) {
778
            m_complDocOn->setChecked(m_plugin->m_complDoc);
779
780
        }
        if (m_refDeclaration) {
781
            m_refDeclaration->setChecked(m_plugin->m_refDeclaration);
782
783
        }
        if (m_autoHover) {
784
            m_autoHover->setChecked(m_plugin->m_autoHover);
785
786
        }
        if (m_onTypeFormatting) {
787
            m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting);
788
789
        }
        if (m_incrementalSync) {
790
            m_incrementalSync->setChecked(m_plugin->m_incrementalSync);
791
792
        }
        if (m_diagnostics) {
793
            m_diagnostics->setChecked(m_plugin->m_diagnostics);
794
795
        }
        if (m_diagnosticsHighlight) {
796
            m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight);
797
798
        }
        if (m_diagnosticsMark) {
799
            m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark);
800
801
        }
        if (m_diagnosticsHover) {
802
            m_diagnosticsHover->setChecked(m_plugin->m_diagnosticsHover);
803
804
        }
        if (m_messages) {
805
            m_messages->setChecked(m_plugin->m_messages);
806
        }
807
808
809
        displayOptionChanged();
    }

810
811
812
813
    void restartCurrent()
    {
        KTextEditor::View *activeView = m_mainWindow->activeView();
        auto server = m_serverManager->findServer(activeView);
814
        if (server) {
815
            m_serverManager->restart(server.data());
816
        }
817
818
    }

819
820
821
822
    void restartAll()
    {
        m_serverManager->restart(nullptr);
    }
823

824
    static void clearMarks(KTextEditor::Document *d