lspclientpluginview.cpp 80.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*  SPDX-License-Identifier: MIT

    Copyright (C) 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com>

    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
24 25 26

#include "lspclientpluginview.h"
#include "lspclientcompletion.h"
27
#include "lspclienthover.h"
28 29 30
#include "lspclientplugin.h"
#include "lspclientservermanager.h"
#include "lspclientsymbolview.h"
31 32 33

#include "lspclient_debug.h"

34
#include <KAcceleratorManager>
35 36 37 38 39 40 41 42 43
#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
44
#include <KTextEditor/Message>
45
#include <KTextEditor/MovingInterface>
46
#include <KTextEditor/View>
47 48
#include <KXMLGUIClient>

49
#include <ktexteditor/configinterface.h>
50 51 52
#include <ktexteditor/markinterface.h>
#include <ktexteditor/movinginterface.h>
#include <ktexteditor/movingrange.h>
53
#include <ktexteditor_version.h>
54

55
#include <QAction>
56
#include <QApplication>
57
#include <QDateTime>
58 59
#include <QFileInfo>
#include <QHBoxLayout>
60
#include <QHeaderView>
61
#include <QInputDialog>
62 63
#include <QJsonObject>
#include <QKeyEvent>
64
#include <QMenu>
65
#include <QPlainTextEdit>
66
#include <QSet>
67
#include <QStandardItem>
68
#include <QTextCodec>
69 70
#include <QTimer>
#include <QTreeView>
Filip Gawin's avatar
Filip Gawin committed
71
#include <utility>
72

73 74
namespace RangeData
{
75
enum {
76 77 78
    // preserve UserRole for generic use where needed
    FileUrlRole = Qt::UserRole + 1,
    RangeRole,
79
    KindRole,
80 81
};

82 83 84 85
class KindEnum
{
public:
    enum _kind {
86 87 88 89 90 91 92
        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),
93 94 95
        Related
    };

96 97
    KindEnum(int v)
    {
98
        m_value = _kind(v);
99
    }
100

101
    KindEnum(LSPDocumentHighlightKind hl)
102
        : KindEnum(static_cast<_kind>(hl))
103 104
    {
    }
105

106
    KindEnum(LSPDiagnosticSeverity sev)
107
        : KindEnum(_kind(10 + static_cast<int>(sev)))
108 109
    {
    }
110

111 112 113 114
    operator _kind()
    {
        return m_value;
    }
115 116 117 118 119

private:
    _kind m_value;
};

120 121 122 123 124
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);
125 126

}
127

128
static QIcon diagnosticsIcon(LSPDiagnosticSeverity severity)
129
{
130
    // clang-format off
131
#define RETURN_CACHED_ICON(name, fallbackname) \
132
    { \
133 134
        static QIcon icon(QIcon::fromTheme(QStringLiteral(name), \
                                           QIcon::fromTheme(QStringLiteral(fallbackname)))); \
135
        return icon; \
136
    }
137
    // clang-format on
138
    switch (severity) {
139
    case LSPDiagnosticSeverity::Error:
140
        RETURN_CACHED_ICON("data-error", "dialog-error")
141
    case LSPDiagnosticSeverity::Warning:
142
        RETURN_CACHED_ICON("data-warning", "dialog-warning")
143 144
    case LSPDiagnosticSeverity::Information:
    case LSPDiagnosticSeverity::Hint:
145
        RETURN_CACHED_ICON("data-information", "dialog-information")
146 147
    default:
        break;
148 149 150 151
    }
    return QIcon();
}

152
static QIcon codeActionIcon()
153 154 155 156 157
{
    static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")));
    return icon;
}

158
KTextEditor::Document *findDocument(KTextEditor::MainWindow *mainWindow, const QUrl &url)
159 160
{
    auto views = mainWindow->views();
161
    for (const auto v : views) {
162 163 164 165 166 167 168
        auto doc = v->document();
        if (doc && doc->url() == url)
            return doc;
    }
    return nullptr;
}

169 170 171 172 173
// helper to read lines from unopened documents
// lightweight and does not require additional symbols
class FileLineReader
{
    QFile file;
174 175
    int lastLineNo = -1;
    QString lastLine;
176 177

public:
178
    FileLineReader(const QUrl &url)
179
        : file(url.toLocalFile())
180 181 182
    {
        file.open(QIODevice::ReadOnly);
    }
183

184
    // called with non-descending lineno
185 186
    QString line(int lineno)
    {
187 188 189
        if (lineno == lastLineNo) {
            return lastLine;
        }
190 191
        while (file.isOpen() && !file.atEnd()) {
            auto line = file.readLine();
192
            if (++lastLineNo == lineno) {
193 194 195 196 197 198
                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);
                }
199
                while (text.size() && text.at(text.size() - 1).isSpace())
200
                    text.chop(1);
201
                lastLine = text;
202 203 204 205 206 207 208
                return text;
            }
        }
        return QString();
    }
};

209
class LSPClientActionView : public QObject
210 211 212
{
    Q_OBJECT

213
    typedef LSPClientActionView self_type;
214 215 216

    LSPClientPlugin *m_plugin;
    KTextEditor::MainWindow *m_mainWindow;
217
    KXMLGUIClient *m_client;
218
    QSharedPointer<LSPClientServerManager> m_serverManager;
219
    QScopedPointer<LSPClientViewTracker> m_viewTracker;
220
    QScopedPointer<LSPClientCompletion> m_completion;
221
    QScopedPointer<LSPClientHover> m_hover;
222 223
    QScopedPointer<QObject> m_symbolView;

224 225 226
    QPointer<QAction> m_findDef;
    QPointer<QAction> m_findDecl;
    QPointer<QAction> m_findRef;
227
    QPointer<QAction> m_triggerHighlight;
228
    QPointer<QAction> m_triggerHover;
229
    QPointer<QAction> m_triggerFormat;
230
    QPointer<QAction> m_triggerRename;
231
    QPointer<QAction> m_complDocOn;
232
    QPointer<QAction> m_refDeclaration;
233
    QPointer<QAction> m_autoHover;
234
    QPointer<QAction> m_onTypeFormatting;
235
    QPointer<QAction> m_incrementalSync;
236 237 238
    QPointer<QAction> m_diagnostics;
    QPointer<QAction> m_diagnosticsHighlight;
    QPointer<QAction> m_diagnosticsMark;
239
    QPointer<QAction> m_diagnosticsSwitch;
240 241 242
    QPointer<QAction> m_messages;
    QPointer<KSelectAction> m_messagesAutoSwitch;
    QPointer<QAction> m_messagesSwitch;
243
    QPointer<QAction> m_closeDynamic;
244 245
    QPointer<QAction> m_restartServer;
    QPointer<QAction> m_restartAll;
246

247 248 249
    // toolview
    QScopedPointer<QWidget> m_toolView;
    QPointer<QTabWidget> m_tabWidget;
250
    // applied search ranges
251
    typedef QMultiHash<KTextEditor::Document *, KTextEditor::MovingRange *> RangeCollection;
252
    RangeCollection m_ranges;
253
    QHash<KTextEditor::Document *, QHash<int, QVector<KTextEditor::MovingRange*>>> m_semanticHighlightRanges;
254
    // applied marks
255
    typedef QSet<KTextEditor::Document *> DocumentCollection;
256
    DocumentCollection m_marks;
257 258 259 260
    // 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;
261 262 263
    // 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
264
    QPointer<QTreeView> m_defTree;
265
    // ... and for declaration
266
    QPointer<QTreeView> m_declTree;
267

268
    // diagnostics tab
269
    QPointer<QTreeView> m_diagnosticsTree;
270
    // tree widget is either owned here or by tab
271 272
    QScopedPointer<QTreeView> m_diagnosticsTreeOwn;
    QScopedPointer<QStandardItemModel> m_diagnosticsModel;
273 274
    // diagnostics ranges
    RangeCollection m_diagnosticsRanges;
275 276
    // and marks
    DocumentCollection m_diagnosticsMarks;
277

278 279 280 281 282 283
    using MessagesWidget = QPlainTextEdit;
    // messages tab
    QPointer<MessagesWidget> m_messagesView;
    // widget is either owned here or by tab
    QScopedPointer<MessagesWidget> m_messagesViewOwn;

284 285
    // views on which completions have been registered
    QSet<KTextEditor::View *> m_completionViews;
286 287 288 289

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

290 291 292 293 294
    // outstanding request
    LSPClientServer::RequestHandle m_handle;
    // timeout on request
    bool m_req_timeout = false;

295 296
    // accept incoming applyEdit
    bool m_accept_edit = false;
297 298
    // characters to trigger format request
    QVector<QChar> m_onTypeFormattingTriggers;
299

300 301 302 303
    KActionCollection *actionCollection() const
    {
        return m_client->actionCollection();
    }
304

305
public:
306 307 308 309 310 311 312 313 314
    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))
        , m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager))
315 316
    {
        connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
317 318
        connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
        connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
319
        connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
320

321
        m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
322
        m_findDef->setText(i18n("Go to Definition"));
323
        m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration);
324
        m_findDecl->setText(i18n("Go to Declaration"));
325
        m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences);
326
        m_findRef->setText(i18n("Find References"));
327
        m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight);
328
        m_triggerHighlight->setText(i18n("Highlight"));
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
329 330
        // perhaps hover suggests to do so on mouse-over,
        // but let's just use a (convenient) action/shortcut for it
331
        m_triggerHover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, &self_type::hover);
332
        m_triggerHover->setText(i18n("Hover"));
333
        m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format);
334
        m_triggerFormat->setText(i18n("Format"));
335
        m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename);
336
        m_triggerRename->setText(i18n("Rename"));
337

338
        // general options
339
        m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged);
340 341
        m_complDocOn->setText(i18n("Show selected completion documentation"));
        m_complDocOn->setCheckable(true);
342
        m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged);
343 344
        m_refDeclaration->setText(i18n("Include declaration in references"));
        m_refDeclaration->setCheckable(true);
345
        m_autoHover = actionCollection()->addAction(QStringLiteral("lspclient_auto_hover"), this, &self_type::displayOptionChanged);
346 347
        m_autoHover->setText(i18n("Show hover information"));
        m_autoHover->setCheckable(true);
348
        m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged);
349 350
        m_onTypeFormatting->setText(i18n("Format on typing"));
        m_onTypeFormatting->setCheckable(true);
351
        m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged);
352 353
        m_incrementalSync->setText(i18n("Incremental document synchronization"));
        m_incrementalSync->setCheckable(true);
354

355
        // diagnostics
356
        m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged);
357 358
        m_diagnostics->setText(i18n("Show diagnostics notifications"));
        m_diagnostics->setCheckable(true);
359
        m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged);
360 361
        m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights"));
        m_diagnosticsHighlight->setCheckable(true);
362
        m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged);
363 364
        m_diagnosticsMark->setText(i18n("Show diagnostics marks"));
        m_diagnosticsMark->setCheckable(true);
365
        m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics);
366
        m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab"));
367

368 369 370 371 372 373 374 375 376 377 378
        // 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"));

379 380 381
        // 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"));
382
        m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
383
        m_restartServer->setText(i18n("Restart LSP Server"));
384
        m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll);
385 386
        m_restartAll->setText(i18n("Restart All LSP Servers"));

387 388 389
        // popup menu
        auto menu = new KActionMenu(i18n("LSP Client"), this);
        actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu);
390 391 392
        menu->addAction(m_findDef);
        menu->addAction(m_findDecl);
        menu->addAction(m_findRef);
393
        menu->addAction(m_triggerHighlight);
394
        menu->addAction(m_triggerHover);
395
        menu->addAction(m_triggerFormat);
396
        menu->addAction(m_triggerRename);
397 398
        menu->addSeparator();
        menu->addAction(m_complDocOn);
399
        menu->addAction(m_refDeclaration);
400
        menu->addAction(m_autoHover);
401
        menu->addAction(m_onTypeFormatting);
402
        menu->addAction(m_incrementalSync);
403
        menu->addSeparator();
404 405 406
        menu->addAction(m_diagnostics);
        menu->addAction(m_diagnosticsHighlight);
        menu->addAction(m_diagnosticsMark);
407
        menu->addAction(m_diagnosticsSwitch);
408
        menu->addSeparator();
409 410 411 412
        menu->addAction(m_messages);
        menu->addAction(m_messagesAutoSwitch);
        menu->addAction(m_messagesSwitch);
        menu->addSeparator();
413
        menu->addAction(m_closeDynamic);
414 415
        menu->addAction(m_restartServer);
        menu->addAction(m_restartAll);
416 417 418

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

420
        // toolview
421
        m_toolView.reset(mainWin->createToolView(plugin, QStringLiteral("kate_lspclient"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("LSP Client")));
422
        m_tabWidget = new QTabWidget(m_toolView.data());
423
        m_toolView->layout()->addWidget(m_tabWidget);
424
        m_tabWidget->setFocusPolicy(Qt::NoFocus);
425 426 427
        m_tabWidget->setTabsClosable(true);
        KAcceleratorManager::setNoAccel(m_tabWidget);
        connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
428
        connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
429

430
        // diagnostics tab
431
        m_diagnosticsTree = new QTreeView();
432
        m_diagnosticsTree->setAlternatingRowColors(true);
433
        m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
434
        m_diagnosticsModel.reset(new QStandardItemModel());
435
        m_diagnosticsModel->setColumnCount(1);
436
        m_diagnosticsTree->setModel(m_diagnosticsModel.data());
437
        configureTreeView(m_diagnosticsTree);
438 439
        connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
        connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
440

441 442 443 444 445 446
        // messages tab
        m_messagesView = new QPlainTextEdit();
        m_messagesView->setMaximumBlockCount(100);
        m_messagesView->setReadOnly(true);
        m_messagesViewOwn.reset(m_messagesView);

447 448
        // track position in view to sync diagnostics list
        m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
449
        connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
450

451
        configUpdated();
452 453 454
        updateState();
    }

Filip Gawin's avatar
Filip Gawin committed
455
    ~LSPClientActionView() override
456
    {
457 458
        // unregister all code-completion providers, else we might crash
        for (auto view : qAsConst(m_completionViews)) {
459
            qobject_cast<KTextEditor::CodeCompletionInterface *>(view)->unregisterCompletionModel(m_completion.data());
460 461 462 463
        }

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

467 468
        clearAllLocationMarks();
        clearAllDiagnosticsMarks();
469 470
    }

471 472 473 474 475 476 477
    void configureTreeView(QTreeView *treeView)
    {
        treeView->setHeaderHidden(true);
        treeView->setFocusPolicy(Qt::NoFocus);
        treeView->setLayoutDirection(Qt::LeftToRight);
        treeView->setSortingEnabled(false);
        treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
478 479 480 481 482 483 484 485

        // 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);
486 487
    }

488 489
    void displayOptionChanged()
    {
490 491
        m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
        m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
492 493 494 495 496 497 498 499 500 501 502 503 504
        // 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);
505 506 507
        // setTabEnabled may still show it ... so let's be more forceful
        if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.take();
508
            m_tabWidget->insertTab(messagesIndex + 1, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
509 510
        } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
            m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
511
            m_tabWidget->removeTab(diagnosticsIndex);
512
        }
513
        m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
514
        m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
515
        updateState();
516 517 518 519 520 521
    }

    void configUpdated()
    {
        if (m_complDocOn)
            m_complDocOn->setChecked(m_plugin->m_complDoc);
522 523
        if (m_refDeclaration)
            m_refDeclaration->setChecked(m_plugin->m_refDeclaration);
524 525
        if (m_autoHover)
            m_autoHover->setChecked(m_plugin->m_autoHover);
526 527
        if (m_onTypeFormatting)
            m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting);
528 529
        if (m_incrementalSync)
            m_incrementalSync->setChecked(m_plugin->m_incrementalSync);
530 531 532 533 534 535
        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);
536 537 538 539
        if (m_messages)
            m_messages->setChecked(m_plugin->m_messages);
        if (m_messagesAutoSwitch)
            m_messagesAutoSwitch->setCurrentItem(m_plugin->m_messagesAutoSwitch);
540 541 542
        displayOptionChanged();
    }

543 544 545 546 547
    void restartCurrent()
    {
        KTextEditor::View *activeView = m_mainWindow->activeView();
        auto server = m_serverManager->findServer(activeView);
        if (server)
548
            m_serverManager->restart(server.data());
549 550
    }

551 552 553 554
    void restartAll()
    {
        m_serverManager->restart(nullptr);
    }
555

556
    static void clearMarks(KTextEditor::Document *doc, RangeCollection &ranges, DocumentCollection &docs, uint markType)
557
    {
558
        KTextEditor::MarkInterface *iface = docs.contains(doc) ? qobject_cast<KTextEditor::MarkInterface *>(doc) : nullptr;
559
        if (iface) {
560 561
            const QHash<int, KTextEditor::Mark *> marks = iface->marks();
            QHashIterator<int, KTextEditor::Mark *> i(marks);
562 563
            while (i.hasNext()) {
                i.next();
564 565
                if (i.value()->type & markType) {
                    iface->removeMark(i.value()->line, markType);
566 567
                }
            }
568
            docs.remove(doc);
569 570
        }

571
        for (auto it = ranges.find(doc); it != ranges.end() && it.key() == doc;) {
572
            delete it.value();
573
            it = ranges.erase(it);
574 575 576
        }
    }

577
    static void clearMarks(RangeCollection &ranges, DocumentCollection &docs, uint markType)
578
    {
579
        while (!ranges.empty()) {
580
            clearMarks(ranges.begin().key(), ranges, docs, markType);
581
        }
582 583 584 585
    }

    Q_SLOT void clearAllMarks(KTextEditor::Document *doc)
    {
586 587
        clearMarks(doc, m_ranges, m_marks, RangeData::markType);
        clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll);
588 589 590 591
    }

    void clearAllLocationMarks()
    {
592
        clearMarks(m_ranges, m_marks, RangeData::markType);
593
        // no longer add any again
594 595
        m_ownedModel.reset();
        m_markModel.clear();
596 597
    }

598 599
    void clearAllDiagnosticsMarks()
    {
600
        clearMarks(m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll);
601 602
    }

603
    void addMarks(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, DocumentCollection *docs)
604
    {
605
        Q_ASSERT(item);
606
        KTextEditor::MovingInterface *miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
607
        Q_ASSERT(miface);
608 609 610
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0)
        KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(doc);
#else
611
        KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
612
#endif
613
        Q_ASSERT(iface);
614
        KTextEditor::View *activeView = m_mainWindow->activeView();
615
        KTextEditor::ConfigInterface *ciface = qobject_cast<KTextEditor::ConfigInterface *>(activeView);
616

617
        auto url = item->data(RangeData::FileUrlRole).toUrl();
618 619 620
        // 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())
621 622
            return;

623
        KTextEditor::Range range = item->data(RangeData::RangeRole).value<LSPRange>();
624 625
        if (!range.isValid() || range.isEmpty())
            return;
626
        auto line = range.start().line();
627
        RangeData::KindEnum kind = RangeData::KindEnum(item->data(RangeData::KindRole).toInt());
628 629 630

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

631
        bool enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsHighlight && m_diagnosticsHighlight->isChecked();
632
        KTextEditor::MarkInterface::MarkTypes markType = RangeData::markType;
633
        switch (kind) {
634 635 636 637 638
        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>();
639
            }
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
            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;
671
        }
672
        if (activeView) {
673
            attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color());
674 675 676
        }

        // highlight the range
677
        if (enabled && ranges) {
678
            KTextEditor::MovingRange *mr = miface->newMovingRange(range);
679 680 681
            mr->setAttribute(attr);
            mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
            mr->setAttributeOnlyForViews(true);
682
            ranges->insert(doc, mr);
683
        }
684 685

        // add match mark for range
686 687
        const int ps = 32;
        bool handleClick = true;
688
        enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsMark && m_diagnosticsMark->isChecked();
689
        switch (markType) {
690 691
        case RangeData::markType:
            iface->setMarkDescription(markType, i18n("RangeHighLight"));
692 693 694
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0)
            iface->setMarkIcon(markType, QIcon());
#else
695
            iface->setMarkPixmap(markType, QIcon().pixmap(0, 0));
696
#endif
697 698 699 700 701
            handleClick = false;
            enabled = true;
            break;
        case RangeData::markTypeDiagError:
            iface->setMarkDescription(markType, i18n("Error"));
702 703 704
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0)
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error));
#else
705
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error).pixmap(ps, ps));
706
#endif
707 708 709
            break;
        case RangeData::markTypeDiagWarning:
            iface->setMarkDescription(markType, i18n("Warning"));
710 711 712
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0)
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning));
#else
713
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning).pixmap(ps, ps));
714
#endif
715 716 717
            break;
        case RangeData::markTypeDiagOther:
            iface->setMarkDescription(markType, i18n("Information"));
718 719 720
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,69,0)
            iface->setMarkIcon(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information));
#else
721
            iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information).pixmap(ps, ps));
722
#endif
723 724 725 726
            break;
        default:
            Q_ASSERT(false);
            break;
727
        }
728
        if (enabled && docs) {
729
            iface->addMark(line, markType);
730 731
            docs->insert(doc);
        }
732 733

        // ensure runtime match
734 735
        connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearAllMarks(KTextEditor::Document *)), Qt::UniqueConnection);
        connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearAllMarks(KTextEditor::Document *)), Qt::UniqueConnection);
736 737
        // reload might save/restore marks before/after above signals, so let's clear before that
        connect(doc, &KTextEditor::Document::aboutToReload, this, &self_type::clearAllMarks, Qt::UniqueConnection);
738 739

        if (handleClick) {
740
            connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), this, SLOT(onMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), Qt::UniqueConnection);
741
        }
742 743
    }

744
    void addMarksRec(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, DocumentCollection *docs)
745 746
    {
        Q_ASSERT(item);
747
        addMarks(doc, item, ranges, docs);
748
        for (int i = 0; i < item->rowCount(); ++i) {
749
            addMarksRec(doc, item->child(i), ranges, docs);
750 751 752
        }
    }