diff --git a/addons/lspclient/lspclientcompletion.cpp b/addons/lspclient/lspclientcompletion.cpp index 1e6614171..8e597951a 100644 --- a/addons/lspclient/lspclientcompletion.cpp +++ b/addons/lspclient/lspclientcompletion.cpp @@ -1,340 +1,336 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #include "lspclientcompletion.h" #include "lspclientplugin.h" #include "lspclient_debug.h" #include #include #include #include #include #include -#define RETURN_CACHED_ICON(name) \ -{ \ - static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ - return icon; \ -} +#define RETURN_CACHED_ICON(name) \ + { \ + static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ + return icon; \ + } -static QIcon -kind_icon(LSPCompletionItemKind kind) +static QIcon kind_icon(LSPCompletionItemKind kind) { - switch (kind) - { + switch (kind) { case LSPCompletionItemKind::Method: case LSPCompletionItemKind::Function: case LSPCompletionItemKind::Constructor: RETURN_CACHED_ICON("code-function") case LSPCompletionItemKind::Variable: RETURN_CACHED_ICON("code-variable") case LSPCompletionItemKind::Class: case LSPCompletionItemKind::Interface: case LSPCompletionItemKind::Struct: RETURN_CACHED_ICON("code-class"); case LSPCompletionItemKind::Module: RETURN_CACHED_ICON("code-block"); case LSPCompletionItemKind::Field: case LSPCompletionItemKind::Property: // align with symbolview RETURN_CACHED_ICON("code-variable"); case LSPCompletionItemKind::Enum: case LSPCompletionItemKind::EnumMember: RETURN_CACHED_ICON("enum"); default: break; } return QIcon(); } static KTextEditor::CodeCompletionModel::CompletionProperty kind_property(LSPCompletionItemKind kind) { using CompletionProperty = KTextEditor::CodeCompletionModel::CompletionProperty; auto p = CompletionProperty::NoProperty; - switch (kind) - { + switch (kind) { case LSPCompletionItemKind::Method: case LSPCompletionItemKind::Function: case LSPCompletionItemKind::Constructor: p = CompletionProperty::Function; break; case LSPCompletionItemKind::Variable: p = CompletionProperty::Variable; break; case LSPCompletionItemKind::Class: case LSPCompletionItemKind::Interface: p = CompletionProperty::Class; break; case LSPCompletionItemKind::Struct: p = CompletionProperty::Class; break; case LSPCompletionItemKind::Module: - p =CompletionProperty::Namespace; + p = CompletionProperty::Namespace; break; case LSPCompletionItemKind::Enum: case LSPCompletionItemKind::EnumMember: p = CompletionProperty::Enum; break; default: break; } return p; } -struct LSPClientCompletionItem : public LSPCompletionItem -{ +struct LSPClientCompletionItem : public LSPCompletionItem { int argumentHintDepth = 0; QString prefix; QString postfix; - LSPClientCompletionItem(const LSPCompletionItem & item) - : LSPCompletionItem(item) + LSPClientCompletionItem(const LSPCompletionItem &item) : LSPCompletionItem(item) { // transform for later display // sigh, remove (leading) whitespace (looking at clangd here) // could skip the [] if empty detail, but it is a handy watermark anyway ;-) - label = QString(label.simplified() + QStringLiteral(" [") + - detail.simplified() + QStringLiteral("]")); + label = QString(label.simplified() + QStringLiteral(" [") + detail.simplified() + + QStringLiteral("]")); } - LSPClientCompletionItem(const LSPSignatureInformation & sig, - int activeParameter, const QString & _sortText) + LSPClientCompletionItem(const LSPSignatureInformation &sig, int activeParameter, + const QString &_sortText) { argumentHintDepth = 1; documentation = sig.documentation; label = sig.label; sortText = _sortText; // transform into prefix, name, suffix if active if (activeParameter >= 0 && activeParameter < sig.parameters.length()) { - const auto& param = sig.parameters.at(activeParameter); - if (param.start >= 0 && param.start < label.length() && - param.end >= 0 && param.end < label.length() && - param.start < param.end) { + const auto ¶m = sig.parameters.at(activeParameter); + if (param.start >= 0 && param.start < label.length() && param.end >= 0 + && param.end < label.length() && param.start < param.end) { prefix = label.mid(0, param.start); postfix = label.mid(param.end); label = label.mid(param.start, param.end - param.start); } } } }; - -static bool compare_match (const LSPCompletionItem & a, const LSPCompletionItem b) -{ return a.sortText < b.sortText; } - +static bool compare_match(const LSPCompletionItem &a, const LSPCompletionItem b) +{ + return a.sortText < b.sortText; +} class LSPClientCompletionImpl : public LSPClientCompletion { Q_OBJECT typedef LSPClientCompletionImpl self_type; QSharedPointer m_manager; QSharedPointer m_server; bool m_selectedDocumentation = false; QVector m_triggersCompletion; QVector m_triggersSignature; bool m_triggerSignature = false; QList m_matches; LSPClientServer::RequestHandle m_handle, m_handleSig; public: LSPClientCompletionImpl(QSharedPointer manager) : LSPClientCompletion(nullptr), m_manager(manager), m_server(nullptr) { } void setServer(QSharedPointer server) override { m_server = server; if (m_server) { - const auto& caps = m_server->capabilities(); + const auto &caps = m_server->capabilities(); m_triggersCompletion = caps.completionProvider.triggerCharacters; m_triggersSignature = caps.signatureHelpProvider.triggerCharacters; } else { m_triggersCompletion.clear(); m_triggersSignature.clear(); } } - virtual void setSelectedDocumentation(bool s) override - { m_selectedDocumentation = s; } + virtual void setSelectedDocumentation(bool s) override { m_selectedDocumentation = s; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid() || index.row() >= m_matches.size()) { return QVariant(); } const auto &match = m_matches.at(index.row()); if (role == Qt::DisplayRole) { if (index.column() == KTextEditor::CodeCompletionModel::Name) { return match.label; } else if (index.column() == KTextEditor::CodeCompletionModel::Prefix) { return match.prefix; } else if (index.column() == KTextEditor::CodeCompletionModel::Postfix) { return match.postfix; } - } else if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) { + } else if (role == Qt::DecorationRole + && index.column() == KTextEditor::CodeCompletionModel::Icon) { return kind_icon(match.kind); } else if (role == KTextEditor::CodeCompletionModel::CompletionRole) { return kind_property(match.kind); } else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) { return match.argumentHintDepth; } else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) { // (ab)use depth to indicate sort order return index.row(); } else if (role == KTextEditor::CodeCompletionModel::IsExpandable) { return !match.documentation.value.isEmpty(); - } else if (role == KTextEditor::CodeCompletionModel::ExpandingWidget && - !match.documentation.value.isEmpty()) { + } else if (role == KTextEditor::CodeCompletionModel::ExpandingWidget + && !match.documentation.value.isEmpty()) { // probably plaintext, but let's show markdown as-is for now // FIXME better presentation of markdown return match.documentation.value; - } else if (role == KTextEditor::CodeCompletionModel::ItemSelected && - !match.argumentHintDepth && !match.documentation.value.isEmpty() && - m_selectedDocumentation) { + } else if (role == KTextEditor::CodeCompletionModel::ItemSelected + && !match.argumentHintDepth && !match.documentation.value.isEmpty() + && m_selectedDocumentation) { return match.documentation.value; } return QVariant(); } bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, - bool userInsertion, const KTextEditor::Cursor &position) override + bool userInsertion, const KTextEditor::Cursor &position) override { qCInfo(LSPCLIENT) << "should start " << userInsertion << insertedText; if (!userInsertion || !m_server || insertedText.isEmpty()) { return false; } // covers most already ... - bool complete = CodeCompletionModelControllerInterface::shouldStartCompletion(view, - insertedText, userInsertion, position); + bool complete = CodeCompletionModelControllerInterface::shouldStartCompletion( + view, insertedText, userInsertion, position); QChar lastChar = insertedText.at(insertedText.count() - 1); m_triggerSignature = false; - complete = complete || m_triggersCompletion.contains(lastChar); + complete = complete || m_triggersCompletion.contains(lastChar); if (m_triggersSignature.contains(lastChar)) { complete = true; m_triggerSignature = true; } return complete; } - void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) override + void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, + InvocationType it) override { Q_UNUSED(it) qCInfo(LSPCLIENT) << "completion invoked" << m_server; // maybe use WaitForReset ?? // but more complex and already looks good anyway - auto handler = [this] (const QList & compl) { + auto handler = [this](const QList & compl) { beginResetModel(); qCInfo(LSPCLIENT) << "adding completions " << compl.size(); - for (const auto & item : compl) + for (const auto &item : compl) m_matches.push_back(item); std::stable_sort(m_matches.begin(), m_matches.end(), compare_match); setRowCount(m_matches.size()); endResetModel(); }; - auto sigHandler = [this] (const LSPSignatureHelp & sig) { + auto sigHandler = [this](const LSPSignatureHelp &sig) { beginResetModel(); qCInfo(LSPCLIENT) << "adding signatures " << sig.signatures.size(); int index = 0; - for (const auto & item : sig.signatures) { + for (const auto &item : sig.signatures) { int sortIndex = 10 + index; int active = -1; if (index == sig.activeSignature) { sortIndex = 0; active = sig.activeParameter; } // trick active first, others after that - m_matches.push_back({item, active, QString(QStringLiteral("%1").arg(sortIndex, 3, 10))}); + m_matches.push_back( + { item, active, QString(QStringLiteral("%1").arg(sortIndex, 3, 10)) }); ++index; } std::stable_sort(m_matches.begin(), m_matches.end(), compare_match); setRowCount(m_matches.size()); endResetModel(); }; beginResetModel(); m_matches.clear(); auto document = view->document(); if (m_server && document) { // the default range is determined based on a reasonable identifier (word) // which is generally fine and nice, but let's pass actual cursor position // (which may be within this typical range) auto position = view->cursorPosition(); auto cursor = qMax(range.start(), qMin(range.end(), position)); m_manager->update(document, false); if (!m_triggerSignature) { - m_handle = m_server->documentCompletion(document->url(), - {cursor.line(), cursor.column()}, this, handler); + m_handle = m_server->documentCompletion( + document->url(), { cursor.line(), cursor.column() }, this, handler); } - m_handleSig = m_server->signatureHelp(document->url(), - {cursor.line(), cursor.column()}, this, sigHandler); + m_handleSig = m_server->signatureHelp( + document->url(), { cursor.line(), cursor.column() }, this, sigHandler); } setRowCount(m_matches.size()); endResetModel(); } - void executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override + void executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, + const QModelIndex &index) const override { if (index.row() < m_matches.size()) view->document()->replaceText(word, m_matches.at(index.row()).insertText); } void aborted(KTextEditor::View *view) override { Q_UNUSED(view); beginResetModel(); m_matches.clear(); m_handle.cancel(); m_handleSig.cancel(); m_triggerSignature = false; endResetModel(); } }; -LSPClientCompletion* -LSPClientCompletion::new_(QSharedPointer manager) +LSPClientCompletion *LSPClientCompletion::new_(QSharedPointer manager) { return new LSPClientCompletionImpl(manager); } #include "lspclientcompletion.moc" diff --git a/addons/lspclient/lspclientcompletion.h b/addons/lspclient/lspclientcompletion.h index da3d62a29..3c161057a 100644 --- a/addons/lspclient/lspclientcompletion.h +++ b/addons/lspclient/lspclientcompletion.h @@ -1,53 +1,51 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTCOMPLETION_H #define LSPCLIENTCOMPLETION_H #include "lspclientserver.h" #include "lspclientservermanager.h" #include #include -class LSPClientCompletion : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface +class LSPClientCompletion : public KTextEditor::CodeCompletionModel, + public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - // implementation factory method - static LSPClientCompletion* new_(QSharedPointer manager); + static LSPClientCompletion *new_(QSharedPointer manager); - LSPClientCompletion(QObject * parent) - : KTextEditor::CodeCompletionModel(parent) - {} + LSPClientCompletion(QObject *parent) : KTextEditor::CodeCompletionModel(parent) {} virtual void setServer(QSharedPointer server) = 0; virtual void setSelectedDocumentation(bool) = 0; }; #endif diff --git a/addons/lspclient/lspclientconfigpage.cpp b/addons/lspclient/lspclientconfigpage.cpp index 63f5903f6..c438117c6 100644 --- a/addons/lspclient/lspclientconfigpage.cpp +++ b/addons/lspclient/lspclientconfigpage.cpp @@ -1,162 +1,161 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #include "lspclientconfigpage.h" #include "lspclientplugin.h" #include #include #include #include #include #include LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugin) : KTextEditor::ConfigPage(parent), m_plugin(plugin) { QVBoxLayout *layout = new QVBoxLayout(this); QGroupBox *outlineBox = new QGroupBox(i18n("Symbol Outline Options"), this); QVBoxLayout *top = new QVBoxLayout(outlineBox); m_symbolDetails = new QCheckBox(i18n("Display symbol details")); m_symbolTree = new QCheckBox(i18n("Tree mode outline")); m_symbolExpand = new QCheckBox(i18n("Automatically expand nodes in tree mode")); m_symbolSort = new QCheckBox(i18n("Sort symbols alphabetically")); top->addWidget(m_symbolDetails); top->addWidget(m_symbolTree); top->addWidget(m_symbolExpand); top->addWidget(m_symbolSort); layout->addWidget(outlineBox); outlineBox = new QGroupBox(i18n("General Options"), this); top = new QVBoxLayout(outlineBox); m_complDoc = new QCheckBox(i18n("Show selected completion documentation")); m_refDeclaration = new QCheckBox(i18n("Include declaration in references")); m_onTypeFormatting = new QCheckBox(i18n("Format on typing")); m_incrementalSync = new QCheckBox(i18n("Incremental document synchronization")); QHBoxLayout *diagLayout = new QHBoxLayout(); m_diagnostics = new QCheckBox(i18n("Show diagnostics notifications")); m_diagnosticsHighlight = new QCheckBox(i18n("Add highlights")); m_diagnosticsMark = new QCheckBox(i18n("Add markers")); top->addWidget(m_complDoc); top->addWidget(m_refDeclaration); top->addWidget(m_onTypeFormatting); top->addWidget(m_incrementalSync); diagLayout->addWidget(m_diagnostics); diagLayout->addStretch(1); diagLayout->addWidget(m_diagnosticsHighlight); diagLayout->addStretch(1); diagLayout->addWidget(m_diagnosticsMark); top->addLayout(diagLayout); layout->addWidget(outlineBox); outlineBox = new QGroupBox(i18n("Server Configuration"), this); top = new QVBoxLayout(outlineBox); m_configPath = new KUrlRequester(this); top->addWidget(m_configPath); layout->addWidget(outlineBox); layout->addStretch(1); reset(); - for (const auto & cb : {m_symbolDetails, m_symbolExpand, m_symbolSort, m_symbolTree, - m_complDoc, m_refDeclaration, m_diagnostics, m_diagnosticsMark, - m_onTypeFormatting, m_incrementalSync}) + for (const auto &cb : { m_symbolDetails, m_symbolExpand, m_symbolSort, m_symbolTree, m_complDoc, + m_refDeclaration, m_diagnostics, m_diagnosticsMark, m_onTypeFormatting, + m_incrementalSync }) connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed); connect(m_configPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::changed); connect(m_configPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::changed); // custom control logic - auto h = [this] () - { + auto h = [this]() { bool enabled = m_diagnostics->isChecked(); m_diagnosticsHighlight->setEnabled(enabled); m_diagnosticsMark->setEnabled(enabled); }; connect(this, &LSPClientConfigPage::changed, this, h); } QString LSPClientConfigPage::name() const { return QString(i18n("LSP Client")); } QString LSPClientConfigPage::fullName() const { return QString(i18n("LSP Client")); } QIcon LSPClientConfigPage::icon() const { return QIcon::fromTheme(QLatin1String("code-context")); } void LSPClientConfigPage::apply() { m_plugin->m_symbolDetails = m_symbolDetails->isChecked(); m_plugin->m_symbolTree = m_symbolTree->isChecked(); m_plugin->m_symbolExpand = m_symbolExpand->isChecked(); m_plugin->m_symbolSort = m_symbolSort->isChecked(); m_plugin->m_complDoc = m_complDoc->isChecked(); m_plugin->m_refDeclaration = m_refDeclaration->isChecked(); m_plugin->m_diagnostics = m_diagnostics->isChecked(); m_plugin->m_diagnosticsHighlight = m_diagnosticsHighlight->isChecked(); m_plugin->m_diagnosticsMark = m_diagnosticsMark->isChecked(); m_plugin->m_onTypeFormatting = m_onTypeFormatting->isChecked(); m_plugin->m_incrementalSync = m_incrementalSync->isChecked(); m_plugin->m_configPath = m_configPath->url(); m_plugin->writeConfig(); } void LSPClientConfigPage::reset() { m_symbolDetails->setChecked(m_plugin->m_symbolDetails); m_symbolTree->setChecked(m_plugin->m_symbolTree); m_symbolExpand->setChecked(m_plugin->m_symbolExpand); m_symbolSort->setChecked(m_plugin->m_symbolSort); m_complDoc->setChecked(m_plugin->m_complDoc); m_refDeclaration->setChecked(m_plugin->m_refDeclaration); m_diagnostics->setChecked(m_plugin->m_diagnostics); m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight); m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark); m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting); m_incrementalSync->setChecked(m_plugin->m_incrementalSync); m_configPath->setUrl(m_plugin->m_configPath); } void LSPClientConfigPage::defaults() { reset(); } diff --git a/addons/lspclient/lspclientconfigpage.h b/addons/lspclient/lspclientconfigpage.h index b623e5fb1..299e2b54e 100644 --- a/addons/lspclient/lspclientconfigpage.h +++ b/addons/lspclient/lspclientconfigpage.h @@ -1,71 +1,71 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTCONFIGPAGE_H #define LSPCLIENTCONFIGPAGE_H #include class LSPClientPlugin; class QCheckBox; class QLineEdit; class KUrlRequester; class LSPClientConfigPage : public KTextEditor::ConfigPage { Q_OBJECT - public: - explicit LSPClientConfigPage(QWidget *parent = nullptr, LSPClientPlugin *plugin = nullptr); - ~LSPClientConfigPage() override {}; - - QString name() const override; - QString fullName() const override; - QIcon icon() const override; - - public Q_SLOTS: - void apply() override; - void defaults() override; - void reset() override; - - private: - QCheckBox* m_symbolDetails; - QCheckBox* m_symbolExpand; - QCheckBox* m_symbolTree; - QCheckBox* m_symbolSort; - QCheckBox* m_complDoc; - QCheckBox* m_refDeclaration; - QCheckBox* m_diagnostics; - QCheckBox* m_diagnosticsHighlight; - QCheckBox* m_diagnosticsMark; - QCheckBox* m_onTypeFormatting; - QCheckBox* m_incrementalSync; - KUrlRequester *m_configPath; - - LSPClientPlugin *m_plugin; +public: + explicit LSPClientConfigPage(QWidget *parent = nullptr, LSPClientPlugin *plugin = nullptr); + ~LSPClientConfigPage() override {}; + + QString name() const override; + QString fullName() const override; + QIcon icon() const override; + +public Q_SLOTS: + void apply() override; + void defaults() override; + void reset() override; + +private: + QCheckBox *m_symbolDetails; + QCheckBox *m_symbolExpand; + QCheckBox *m_symbolTree; + QCheckBox *m_symbolSort; + QCheckBox *m_complDoc; + QCheckBox *m_refDeclaration; + QCheckBox *m_diagnostics; + QCheckBox *m_diagnosticsHighlight; + QCheckBox *m_diagnosticsMark; + QCheckBox *m_onTypeFormatting; + QCheckBox *m_incrementalSync; + KUrlRequester *m_configPath; + + LSPClientPlugin *m_plugin; }; #endif diff --git a/addons/lspclient/lspclientplugin.cpp b/addons/lspclient/lspclientplugin.cpp index b572afd4b..6c8087ee1 100644 --- a/addons/lspclient/lspclientplugin.cpp +++ b/addons/lspclient/lspclientplugin.cpp @@ -1,133 +1,133 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #include "lspclientplugin.h" #include "lspclientpluginview.h" #include "lspclientconfigpage.h" #include "lspclient_debug.h" #include #include #include #include #include static const QString CONFIG_LSPCLIENT { QStringLiteral("lspclient") }; static const QString CONFIG_SYMBOL_DETAILS { QStringLiteral("SymbolDetails") }; static const QString CONFIG_SYMBOL_TREE { QStringLiteral("SymbolTree") }; static const QString CONFIG_SYMBOL_EXPAND { QStringLiteral("SymbolExpand") }; static const QString CONFIG_SYMBOL_SORT { QStringLiteral("SymbolSort") }; static const QString CONFIG_COMPLETION_DOC { QStringLiteral("CompletionDocumentation") }; static const QString CONFIG_REFERENCES_DECLARATION { QStringLiteral("ReferencesDeclaration") }; static const QString CONFIG_TYPE_FORMATTING { QStringLiteral("TypeFormatting") }; static const QString CONFIG_INCREMENTAL_SYNC { QStringLiteral("IncrementalSync") }; static const QString CONFIG_DIAGNOSTICS { QStringLiteral("Diagnostics") }; static const QString CONFIG_DIAGNOSTICS_HIGHLIGHT { QStringLiteral("DiagnosticsHighlight") }; static const QString CONFIG_DIAGNOSTICS_MARK { QStringLiteral("DiagnosticsMark") }; static const QString CONFIG_SERVER_CONFIG { QStringLiteral("ServerConfiguration") }; - -K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", registerPlugin();) +K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", + registerPlugin();) LSPClientPlugin::LSPClientPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { /** * handle plugin verbosity * the m_debugMode will be used to e.g. set debug level for started clangd, too */ m_debugMode = (qgetenv("LSPCLIENT_DEBUG") == QByteArray("1")); if (!m_debugMode) { - QLoggingCategory::setFilterRules(QStringLiteral("katelspclientplugin.debug=false\nkatelspclientplugin.info=false")); + QLoggingCategory::setFilterRules( + QStringLiteral("katelspclientplugin.debug=false\nkatelspclientplugin.info=false")); } else { - QLoggingCategory::setFilterRules(QStringLiteral("katelspclientplugin.debug=true\nkatelspclientplugin.info=true")); + QLoggingCategory::setFilterRules( + QStringLiteral("katelspclientplugin.debug=true\nkatelspclientplugin.info=true")); } readConfig(); } -LSPClientPlugin::~LSPClientPlugin() -{ -} +LSPClientPlugin::~LSPClientPlugin() {} QObject *LSPClientPlugin::createView(KTextEditor::MainWindow *mainWindow) { return LSPClientPluginView::new_(this, mainWindow); } int LSPClientPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *LSPClientPlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } return new LSPClientConfigPage(parent, this); } void LSPClientPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); m_symbolDetails = config.readEntry(CONFIG_SYMBOL_DETAILS, false); m_symbolTree = config.readEntry(CONFIG_SYMBOL_TREE, true); m_symbolExpand = config.readEntry(CONFIG_SYMBOL_EXPAND, true); m_symbolSort = config.readEntry(CONFIG_SYMBOL_SORT, false); m_complDoc = config.readEntry(CONFIG_COMPLETION_DOC, true); m_refDeclaration = config.readEntry(CONFIG_REFERENCES_DECLARATION, true); m_onTypeFormatting = config.readEntry(CONFIG_TYPE_FORMATTING, false); m_incrementalSync = config.readEntry(CONFIG_INCREMENTAL_SYNC, false); m_diagnostics = config.readEntry(CONFIG_DIAGNOSTICS, true); m_diagnosticsHighlight = config.readEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, true); m_diagnosticsMark = config.readEntry(CONFIG_DIAGNOSTICS_MARK, true); m_configPath = config.readEntry(CONFIG_SERVER_CONFIG, QUrl()); emit update(); } void LSPClientPlugin::writeConfig() const { KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); config.writeEntry(CONFIG_SYMBOL_DETAILS, m_symbolDetails); config.writeEntry(CONFIG_SYMBOL_TREE, m_symbolTree); config.writeEntry(CONFIG_SYMBOL_EXPAND, m_symbolExpand); config.writeEntry(CONFIG_SYMBOL_SORT, m_symbolSort); config.writeEntry(CONFIG_COMPLETION_DOC, m_complDoc); config.writeEntry(CONFIG_REFERENCES_DECLARATION, m_refDeclaration); config.writeEntry(CONFIG_TYPE_FORMATTING, m_onTypeFormatting); config.writeEntry(CONFIG_INCREMENTAL_SYNC, m_incrementalSync); config.writeEntry(CONFIG_DIAGNOSTICS, m_diagnostics); config.writeEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, m_diagnosticsHighlight); config.writeEntry(CONFIG_DIAGNOSTICS_MARK, m_diagnosticsMark); config.writeEntry(CONFIG_SERVER_CONFIG, m_configPath); emit update(); } #include "lspclientplugin.moc" diff --git a/addons/lspclient/lspclientplugin.h b/addons/lspclient/lspclientplugin.h index 977b030bb..293203c93 100644 --- a/addons/lspclient/lspclientplugin.h +++ b/addons/lspclient/lspclientplugin.h @@ -1,73 +1,74 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTPLUGIN_H #define LSPCLIENTPLUGIN_H #include #include #include #include class LSPClientPlugin : public KTextEditor::Plugin { Q_OBJECT - public: - explicit LSPClientPlugin(QObject *parent = nullptr, const QList & = QList()); - ~LSPClientPlugin() override; - - QObject *createView(KTextEditor::MainWindow *mainWindow) override; - - int configPages() const override; - KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; - - void readConfig(); - void writeConfig() const; - - // settings - bool m_symbolDetails; - bool m_symbolExpand; - bool m_symbolTree; - bool m_symbolSort; - bool m_complDoc; - bool m_refDeclaration; - bool m_diagnostics; - bool m_diagnosticsHighlight; - bool m_diagnosticsMark; - bool m_onTypeFormatting; - bool m_incrementalSync; - QUrl m_configPath; - - // debug mode? - bool m_debugMode = false; +public: + explicit LSPClientPlugin(QObject *parent = nullptr, + const QList & = QList()); + ~LSPClientPlugin() override; + + QObject *createView(KTextEditor::MainWindow *mainWindow) override; + + int configPages() const override; + KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; + + void readConfig(); + void writeConfig() const; + + // settings + bool m_symbolDetails; + bool m_symbolExpand; + bool m_symbolTree; + bool m_symbolSort; + bool m_complDoc; + bool m_refDeclaration; + bool m_diagnostics; + bool m_diagnosticsHighlight; + bool m_diagnosticsMark; + bool m_onTypeFormatting; + bool m_incrementalSync; + QUrl m_configPath; + + // debug mode? + bool m_debugMode = false; private: - Q_SIGNALS: - // signal settings update - void update() const; +Q_SIGNALS: + // signal settings update + void update() const; }; #endif diff --git a/addons/lspclient/lspclientpluginview.cpp b/addons/lspclient/lspclientpluginview.cpp index d9349264e..46f69c6ee 100644 --- a/addons/lspclient/lspclientpluginview.cpp +++ b/addons/lspclient/lspclientpluginview.cpp @@ -1,1660 +1,1693 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #include "lspclientpluginview.h" #include "lspclientsymbolview.h" #include "lspclientplugin.h" #include "lspclientservermanager.h" #include "lspclientcompletion.h" #include "lspclienthover.h" #include "lspclient_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -namespace RangeData -{ +namespace RangeData { enum { // preserve UserRole for generic use where needed FileUrlRole = Qt::UserRole + 1, RangeRole, KindRole, }; class KindEnum { public: enum _kind { - Text = (int) LSPDocumentHighlightKind::Text, - Read = (int) LSPDocumentHighlightKind::Read, - Write = (int) LSPDocumentHighlightKind::Write, - Error = 10 + (int) LSPDiagnosticSeverity::Error, - Warning = 10 + (int) LSPDiagnosticSeverity::Warning, - Information = 10 + (int) LSPDiagnosticSeverity::Information, - Hint = 10 + (int) LSPDiagnosticSeverity::Hint, + Text = (int)LSPDocumentHighlightKind::Text, + Read = (int)LSPDocumentHighlightKind::Read, + Write = (int)LSPDocumentHighlightKind::Write, + Error = 10 + (int)LSPDiagnosticSeverity::Error, + Warning = 10 + (int)LSPDiagnosticSeverity::Warning, + Information = 10 + (int)LSPDiagnosticSeverity::Information, + Hint = 10 + (int)LSPDiagnosticSeverity::Hint, Related }; - KindEnum(int v) - { m_value = (_kind) v; } + KindEnum(int v) { m_value = (_kind)v; } - KindEnum(LSPDocumentHighlightKind hl) - : KindEnum((_kind) (hl)) - {} + KindEnum(LSPDocumentHighlightKind hl) : KindEnum((_kind)(hl)) {} - KindEnum(LSPDiagnosticSeverity sev) - : KindEnum(_kind(10 + (int) sev)) - {} + KindEnum(LSPDiagnosticSeverity sev) : KindEnum(_kind(10 + (int)sev)) {} - operator _kind() - { return m_value; } + operator _kind() { return m_value; } private: _kind m_value; }; -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 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); + KTextEditor::MarkInterface::MarkTypes(markTypeDiagError | markTypeDiagWarning + | markTypeDiagOther); } - -static QIcon -diagnosticsIcon(LSPDiagnosticSeverity severity) +static QIcon diagnosticsIcon(LSPDiagnosticSeverity severity) { -#define RETURN_CACHED_ICON(name) \ -{ \ -static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ -return icon; \ -} - switch (severity) - { +#define RETURN_CACHED_ICON(name) \ + { \ + static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ + return icon; \ + } + switch (severity) { case LSPDiagnosticSeverity::Error: RETURN_CACHED_ICON("dialog-error") case LSPDiagnosticSeverity::Warning: RETURN_CACHED_ICON("dialog-warning") case LSPDiagnosticSeverity::Information: case LSPDiagnosticSeverity::Hint: RETURN_CACHED_ICON("dialog-information") default: break; } return QIcon(); } -static QIcon -codeActionIcon() +static QIcon codeActionIcon() { static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text"))); return icon; } -KTextEditor::Document* -findDocument(KTextEditor::MainWindow *mainWindow, const QUrl & url) +KTextEditor::Document *findDocument(KTextEditor::MainWindow *mainWindow, const QUrl &url) { auto views = mainWindow->views(); - for (const auto v: views) { + for (const auto v : views) { auto doc = v->document(); if (doc && doc->url() == url) return doc; } return nullptr; } // helper to read lines from unopened documents // lightweight and does not require additional symbols class FileLineReader { QFile file; int lastLineNo = -1; QString lastLine; public: - FileLineReader(const QUrl & url) - : file(url.path()) - { - file.open(QIODevice::ReadOnly); - } + FileLineReader(const QUrl &url) : file(url.path()) { file.open(QIODevice::ReadOnly); } // called with non-descending lineno QString line(int lineno) { if (lineno == lastLineNo) { return lastLine; } while (file.isOpen() && !file.atEnd()) { auto line = file.readLine(); if (++lastLineNo == lineno) { 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); } - while (text.size() && text.at(text.size() -1).isSpace()) + while (text.size() && text.at(text.size() - 1).isSpace()) text.chop(1); lastLine = text; return text; } } return QString(); } }; - class LSPClientActionView : public QObject { Q_OBJECT typedef LSPClientActionView self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; KXMLGUIClient *m_client; QSharedPointer m_serverManager; QScopedPointer m_viewTracker; QScopedPointer m_completion; QScopedPointer m_hover; QScopedPointer m_symbolView; QPointer m_findDef; QPointer m_findDecl; QPointer m_findRef; QPointer m_triggerHighlight; QPointer m_triggerHover; QPointer m_triggerFormat; QPointer m_triggerRename; QPointer m_complDocOn; QPointer m_refDeclaration; QPointer m_onTypeFormatting; QPointer m_incrementalSync; QPointer m_diagnostics; QPointer m_diagnosticsHighlight; QPointer m_diagnosticsMark; QPointer m_diagnosticsSwitch; QPointer m_diagnosticsCloseNon; QPointer m_restartServer; QPointer m_restartAll; // toolview QScopedPointer m_toolView; QPointer m_tabWidget; // applied search ranges - typedef QMultiHash RangeCollection; + typedef QMultiHash RangeCollection; RangeCollection m_ranges; // applied marks - typedef QSet DocumentCollection; + typedef QSet DocumentCollection; DocumentCollection m_marks; // modelis either owned by tree added to tabwidget or owned here QScopedPointer m_ownedModel; // in either case, the model that directs applying marks/ranges QPointer m_markModel; // 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 QPointer m_defTree; // ... and for declaration QPointer m_declTree; // diagnostics tab QPointer m_diagnosticsTree; // tree widget is either owned here or by tab QScopedPointer m_diagnosticsTreeOwn; QScopedPointer m_diagnosticsModel; // diagnostics ranges RangeCollection m_diagnosticsRanges; // and marks DocumentCollection m_diagnosticsMarks; // views on which completions have been registered QSet m_completionViews; // views on which hovers have been registered QSet m_hoverViews; // outstanding request LSPClientServer::RequestHandle m_handle; // timeout on request bool m_req_timeout = false; // accept incoming applyEdit bool m_accept_edit = false; // characters to trigger format request QVector m_onTypeFormattingTriggers; - KActionCollection *actionCollection() const - { return m_client->actionCollection(); } + KActionCollection *actionCollection() const { return m_client->actionCollection(); } public: LSPClientActionView(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, KXMLGUIClient *client, QSharedPointer serverManager) - : QObject(mainWin), m_plugin(plugin), m_mainWindow(mainWin), m_client(client), + : QObject(mainWin), + m_plugin(plugin), + m_mainWindow(mainWin), + m_client(client), m_serverManager(serverManager), m_completion(LSPClientCompletion::new_(m_serverManager)), m_hover(LSPClientHover::new_(m_serverManager)), m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager)) { connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState); - connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc); - connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState); + connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, + &self_type::handleEsc); + connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, + &self_type::updateState); - m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition); + m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, + &self_type::goToDefinition); m_findDef->setText(i18n("Go to Definition")); - m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), this, &self_type::goToDeclaration); + m_findDecl = actionCollection()->addAction(QStringLiteral("lspclient_find_declaration"), + this, &self_type::goToDeclaration); m_findDecl->setText(i18n("Go to Declaration")); - m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences); + m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, + &self_type::findReferences); m_findRef->setText(i18n("Find References")); - m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight); + m_triggerHighlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), + this, &self_type::highlight); m_triggerHighlight->setText(i18n("Highlight")); // perhaps hover suggests to do so on mouse-over, // but let's just use a (convenient) action/shortcut for it - m_triggerHover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, &self_type::hover); + m_triggerHover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, + &self_type::hover); m_triggerHover->setText(i18n("Hover")); - m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, &self_type::format); + m_triggerFormat = actionCollection()->addAction(QStringLiteral("lspclient_format"), this, + &self_type::format); m_triggerFormat->setText(i18n("Format")); - m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, &self_type::rename); + m_triggerRename = actionCollection()->addAction(QStringLiteral("lspclient_rename"), this, + &self_type::rename); m_triggerRename->setText(i18n("Rename")); // general options - m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged); + m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), + this, &self_type::displayOptionChanged); m_complDocOn->setText(i18n("Show selected completion documentation")); m_complDocOn->setCheckable(true); - m_refDeclaration = actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), this, &self_type::displayOptionChanged); + m_refDeclaration = + actionCollection()->addAction(QStringLiteral("lspclient_references_declaration"), + this, &self_type::displayOptionChanged); m_refDeclaration->setText(i18n("Include declaration in references")); m_refDeclaration->setCheckable(true); - m_onTypeFormatting = actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, &self_type::displayOptionChanged); + m_onTypeFormatting = + actionCollection()->addAction(QStringLiteral("lspclient_type_formatting"), this, + &self_type::displayOptionChanged); m_onTypeFormatting->setText(i18n("Format on typing")); m_onTypeFormatting->setCheckable(true); - m_incrementalSync = actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, &self_type::displayOptionChanged); + m_incrementalSync = + actionCollection()->addAction(QStringLiteral("lspclient_incremental_sync"), this, + &self_type::displayOptionChanged); m_incrementalSync->setText(i18n("Incremental document synchronization")); m_incrementalSync->setCheckable(true); // diagnostics - m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, &self_type::displayOptionChanged); + m_diagnostics = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics"), this, + &self_type::displayOptionChanged); m_diagnostics->setText(i18n("Show diagnostics notifications")); m_diagnostics->setCheckable(true); - m_diagnosticsHighlight = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), this, &self_type::displayOptionChanged); + m_diagnosticsHighlight = + actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_highlight"), + this, &self_type::displayOptionChanged); m_diagnosticsHighlight->setText(i18n("Show diagnostics highlights")); m_diagnosticsHighlight->setCheckable(true); - m_diagnosticsMark = actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, &self_type::displayOptionChanged); + m_diagnosticsMark = + actionCollection()->addAction(QStringLiteral("lspclient_diagnostics_mark"), this, + &self_type::displayOptionChanged); m_diagnosticsMark->setText(i18n("Show diagnostics marks")); m_diagnosticsMark->setCheckable(true); - m_diagnosticsSwitch = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, &self_type::switchToDiagnostics); + m_diagnosticsSwitch = + actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_switch"), this, + &self_type::switchToDiagnostics); m_diagnosticsSwitch->setText(i18n("Switch to diagnostics tab")); - m_diagnosticsCloseNon = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_close_non"), this, &self_type::closeNonDiagnostics); + m_diagnosticsCloseNon = + actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_close_non"), + this, &self_type::closeNonDiagnostics); m_diagnosticsCloseNon->setText(i18n("Close all non-diagnostics tabs")); // server control - m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent); + m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), + this, &self_type::restartCurrent); m_restartServer->setText(i18n("Restart LSP Server")); - m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, &self_type::restartAll); + m_restartAll = actionCollection()->addAction(QStringLiteral("lspclient_restart_all"), this, + &self_type::restartAll); m_restartAll->setText(i18n("Restart All LSP Servers")); // popup menu auto menu = new KActionMenu(i18n("LSP Client"), this); actionCollection()->addAction(QStringLiteral("popup_lspclient"), menu); menu->addAction(m_findDef); menu->addAction(m_findDecl); menu->addAction(m_findRef); menu->addAction(m_triggerHighlight); menu->addAction(m_triggerHover); menu->addAction(m_triggerFormat); menu->addAction(m_triggerRename); menu->addSeparator(); menu->addAction(m_complDocOn); menu->addAction(m_refDeclaration); menu->addAction(m_onTypeFormatting); menu->addAction(m_incrementalSync); menu->addSeparator(); menu->addAction(m_diagnostics); menu->addAction(m_diagnosticsHighlight); menu->addAction(m_diagnosticsMark); menu->addAction(m_diagnosticsSwitch); menu->addAction(m_diagnosticsCloseNon); menu->addSeparator(); menu->addAction(m_restartServer); menu->addAction(m_restartAll); // sync with plugin settings if updated connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated); // toolview - m_toolView.reset(mainWin->createToolView(plugin, QStringLiteral("kate_lspclient"), - KTextEditor::MainWindow::Bottom, - QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), - i18n("LSP Client"))); + m_toolView.reset(mainWin->createToolView( + plugin, QStringLiteral("kate_lspclient"), KTextEditor::MainWindow::Bottom, + QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), + i18n("LSP Client"))); m_tabWidget = new QTabWidget(m_toolView.data()); m_toolView->layout()->addWidget(m_tabWidget); m_tabWidget->setFocusPolicy(Qt::NoFocus); m_tabWidget->setTabsClosable(true); KAcceleratorManager::setNoAccel(m_tabWidget); connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested); // diagnostics tab m_diagnosticsTree = new QTreeView(); configureTreeView(m_diagnosticsTree); m_diagnosticsTree->setAlternatingRowColors(true); m_diagnosticsTreeOwn.reset(m_diagnosticsTree); m_diagnosticsModel.reset(new QStandardItemModel()); m_diagnosticsModel->setColumnCount(2); m_diagnosticsTree->setModel(m_diagnosticsModel.data()); connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation); connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction); // track position in view to sync diagnostics list m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500)); - connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState); + connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, + &self_type::onViewState); configUpdated(); updateState(); } ~LSPClientActionView() { // unregister all code-completion providers, else we might crash for (auto view : qAsConst(m_completionViews)) { - qobject_cast(view)->unregisterCompletionModel(m_completion.data()); + qobject_cast(view)->unregisterCompletionModel( + m_completion.data()); } // unregister all text-hint providers, else we might crash for (auto view : qAsConst(m_hoverViews)) { - qobject_cast(view)->unregisterTextHintProvider(m_hover.data()); + qobject_cast(view)->unregisterTextHintProvider( + m_hover.data()); } clearAllLocationMarks(); clearAllDiagnosticsMarks(); } void configureTreeView(QTreeView *treeView) { treeView->setHeaderHidden(true); treeView->setFocusPolicy(Qt::NoFocus); treeView->setLayoutDirection(Qt::LeftToRight); treeView->setSortingEnabled(false); treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); } void displayOptionChanged() { m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked()); m_diagnosticsMark->setEnabled(m_diagnostics->isChecked()); auto index = m_tabWidget->indexOf(m_diagnosticsTree); // setTabEnabled may still show it ... so let's be more forceful if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) { m_diagnosticsTreeOwn.take(); m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics")); } else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) { m_diagnosticsTreeOwn.reset(m_diagnosticsTree); m_tabWidget->removeTab(index); } m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked()); m_serverManager->setIncrementalSync(m_incrementalSync->isChecked()); updateState(); } void configUpdated() { if (m_complDocOn) m_complDocOn->setChecked(m_plugin->m_complDoc); if (m_refDeclaration) m_refDeclaration->setChecked(m_plugin->m_refDeclaration); if (m_onTypeFormatting) m_onTypeFormatting->setChecked(m_plugin->m_onTypeFormatting); if (m_incrementalSync) m_incrementalSync->setChecked(m_plugin->m_incrementalSync); 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); displayOptionChanged(); } void restartCurrent() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (server) m_serverManager->restart(server.data()); } - void restartAll() - { - m_serverManager->restart(nullptr); - } + void restartAll() { m_serverManager->restart(nullptr); } - static - void clearMarks(KTextEditor::Document *doc, RangeCollection & ranges, DocumentCollection & docs, uint markType) + static void clearMarks(KTextEditor::Document *doc, RangeCollection &ranges, + DocumentCollection &docs, uint markType) { - KTextEditor::MarkInterface* iface = - docs.contains(doc) ? qobject_cast(doc) : nullptr; + KTextEditor::MarkInterface *iface = + docs.contains(doc) ? qobject_cast(doc) : nullptr; if (iface) { - const QHash marks = iface->marks(); - QHashIterator i(marks); + const QHash marks = iface->marks(); + QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & markType) { iface->removeMark(i.value()->line, markType); } } docs.remove(doc); } for (auto it = ranges.find(doc); it != ranges.end() && it.key() == doc;) { delete it.value(); it = ranges.erase(it); } } - static - void clearMarks(RangeCollection & ranges, DocumentCollection & docs, uint markType) + static void clearMarks(RangeCollection &ranges, DocumentCollection &docs, uint markType) { while (!ranges.empty()) { clearMarks(ranges.begin().key(), ranges, docs, markType); } } Q_SLOT void clearAllMarks(KTextEditor::Document *doc) { clearMarks(doc, m_ranges, m_marks, RangeData::markType); clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); } void clearAllLocationMarks() { clearMarks(m_ranges, m_marks, RangeData::markType); // no longer add any again m_ownedModel.reset(); m_markModel.clear(); } void clearAllDiagnosticsMarks() { clearMarks(m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); } - void addMarks(KTextEditor::Document *doc, QStandardItem *item, RangeCollection * ranges, DocumentCollection * docs) + void addMarks(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, + DocumentCollection *docs) { Q_ASSERT(item); - KTextEditor::MovingInterface* miface = qobject_cast(doc); - KTextEditor::MarkInterface* iface = qobject_cast(doc); - KTextEditor::View* activeView = m_mainWindow->activeView(); - KTextEditor::ConfigInterface* ciface = qobject_cast(activeView); + KTextEditor::MovingInterface *miface = qobject_cast(doc); + KTextEditor::MarkInterface *iface = qobject_cast(doc); + KTextEditor::View *activeView = m_mainWindow->activeView(); + KTextEditor::ConfigInterface *ciface = + qobject_cast(activeView); if (!miface || !iface) return; auto url = item->data(RangeData::FileUrlRole).toUrl(); if (url != doc->url()) return; KTextEditor::Range range = item->data(RangeData::RangeRole).value(); auto line = range.start().line(); - RangeData::KindEnum kind = (RangeData::KindEnum) item->data(RangeData::KindRole).toInt(); + RangeData::KindEnum kind = (RangeData::KindEnum)item->data(RangeData::KindRole).toInt(); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); - bool enabled = m_diagnostics && m_diagnostics->isChecked() - && m_diagnosticsHighlight && m_diagnosticsHighlight->isChecked(); + bool enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsHighlight + && m_diagnosticsHighlight->isChecked(); KTextEditor::MarkInterface::MarkTypes markType = RangeData::markType; switch (kind) { - case RangeData::KindEnum::Text: - { + 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(); + rangeColor = ciface->configValue(QStringLiteral("search-highlight-color")) + .value(); } 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; } if (activeView) { - attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); + attr->setForeground( + activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } // highlight the range if (enabled && ranges) { - KTextEditor::MovingRange* mr = miface->newMovingRange(range); + KTextEditor::MovingRange *mr = miface->newMovingRange(range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); ranges->insert(doc, mr); } // add match mark for range const int ps = 32; bool handleClick = true; - enabled = m_diagnostics && m_diagnostics->isChecked() - && m_diagnosticsMark && m_diagnosticsMark->isChecked(); + enabled = m_diagnostics && m_diagnostics->isChecked() && m_diagnosticsMark + && m_diagnosticsMark->isChecked(); switch (markType) { case RangeData::markType: iface->setMarkDescription(markType, i18n("RangeHighLight")); iface->setMarkPixmap(markType, QIcon().pixmap(0, 0)); handleClick = false; enabled = true; break; case RangeData::markTypeDiagError: iface->setMarkDescription(markType, i18n("Error")); - iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Error).pixmap(ps, ps)); + iface->setMarkPixmap(markType, + diagnosticsIcon(LSPDiagnosticSeverity::Error).pixmap(ps, ps)); break; case RangeData::markTypeDiagWarning: iface->setMarkDescription(markType, i18n("Warning")); - iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Warning).pixmap(ps, ps)); + iface->setMarkPixmap(markType, + diagnosticsIcon(LSPDiagnosticSeverity::Warning).pixmap(ps, ps)); break; case RangeData::markTypeDiagOther: iface->setMarkDescription(markType, i18n("Information")); - iface->setMarkPixmap(markType, diagnosticsIcon(LSPDiagnosticSeverity::Information).pixmap(ps, ps)); + iface->setMarkPixmap( + markType, diagnosticsIcon(LSPDiagnosticSeverity::Information).pixmap(ps, ps)); break; default: Q_ASSERT(false); break; } if (enabled && docs) { iface->addMark(line, markType); docs->insert(doc); } // ensure runtime match - 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); + 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); if (handleClick) { - connect(doc, SIGNAL(markClicked(KTextEditor::Document*, KTextEditor::Mark, bool&)), - this, SLOT(onMarkClicked(KTextEditor::Document*,KTextEditor::Mark, bool&)), Qt::UniqueConnection); + connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), + this, SLOT(onMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), + Qt::UniqueConnection); } } - void addMarksRec(KTextEditor::Document *doc, QStandardItem *item, RangeCollection * ranges, DocumentCollection * docs) + void addMarksRec(KTextEditor::Document *doc, QStandardItem *item, RangeCollection *ranges, + DocumentCollection *docs) { Q_ASSERT(item); addMarks(doc, item, ranges, docs); for (int i = 0; i < item->rowCount(); ++i) { addMarksRec(doc, item->child(i), ranges, docs); } } - void addMarks(KTextEditor::Document *doc, QStandardItemModel *treeModel, RangeCollection & ranges, DocumentCollection & docs) + void addMarks(KTextEditor::Document *doc, QStandardItemModel *treeModel, + RangeCollection &ranges, DocumentCollection &docs) { // check if already added auto oranges = ranges.contains(doc) ? nullptr : &ranges; auto odocs = docs.contains(doc) ? nullptr : &docs; if (!oranges && !odocs) return; Q_ASSERT(treeModel); addMarksRec(doc, treeModel->invisibleRootItem(), oranges, odocs); } - void goToDocumentLocation(const QUrl & uri, int line, int column) + void goToDocumentLocation(const QUrl &uri, int line, int column) { KTextEditor::View *activeView = m_mainWindow->activeView(); if (!activeView || uri.isEmpty() || line < 0 || column < 0) return; KTextEditor::Document *document = activeView->document(); KTextEditor::Cursor cdef(line, column); if (document && uri == document->url()) { activeView->setCursorPosition(cdef); } else { KTextEditor::View *view = m_mainWindow->openUrl(uri); if (view) { view->setCursorPosition(cdef); } } } - void goToItemLocation(const QModelIndex & index) + void goToItemLocation(const QModelIndex &index) { auto url = index.data(RangeData::FileUrlRole).toUrl(); auto start = index.data(RangeData::RangeRole).value().start(); goToDocumentLocation(url, start.line(), start.column()); } // custom item subclass that captures additional attributes; // a bit more convenient than the variant/role way - struct DiagnosticItem : public QStandardItem - { + struct DiagnosticItem : public QStandardItem { LSPDiagnostic m_diagnostic; LSPCodeAction m_codeAction; QSharedPointer m_snapshot; - DiagnosticItem(const LSPDiagnostic & d) : - m_diagnostic(d) - {} + DiagnosticItem(const LSPDiagnostic &d) : m_diagnostic(d) {} - DiagnosticItem(const LSPCodeAction & c, QSharedPointer s) : - m_codeAction(c), m_snapshot(s) - { m_diagnostic.range = LSPRange::invalid(); } + DiagnosticItem(const LSPCodeAction &c, QSharedPointer s) + : m_codeAction(c), m_snapshot(s) + { + m_diagnostic.range = LSPRange::invalid(); + } - bool isCodeAction() - { return !m_diagnostic.range.isValid() && m_codeAction.title.size(); } + bool isCodeAction() { return !m_diagnostic.range.isValid() && m_codeAction.title.size(); } }; - // double click on: // diagnostic item -> request and add actions (below item) // code action -> perform action (literal edit and/or execute command) // (execution of command may lead to an applyEdit request from server) - void triggerCodeAction(const QModelIndex & index) + void triggerCodeAction(const QModelIndex &index) { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); - auto it = dynamic_cast(m_diagnosticsModel->itemFromIndex(index)); + auto it = dynamic_cast(m_diagnosticsModel->itemFromIndex(index)); if (!server || !document || !it) return; // click on an action ? if (it->isCodeAction()) { - auto& action = it->m_codeAction; + auto &action = it->m_codeAction; // apply edit before command applyWorkspaceEdit(action.edit, it->m_snapshot.data()); const auto &command = action.command; if (command.command.size()) { // accept edit requests that may be sent to execute command m_accept_edit = true; // but only for a short time QTimer::singleShot(2000, this, [this] { m_accept_edit = false; }); server->executeCommand(command.command, command.arguments); } return; } // only engage action if // * active document matches diagnostic document // * if really clicked a diagnostic item // (which is the case as it != nullptr and not a code action) // * if no code action invoked and added already // (note; related items are also children) auto url = it->data(RangeData::FileUrlRole).toUrl(); if (url != document->url() || it->data(Qt::UserRole).toBool()) return; // store some things to find item safely later on QPersistentModelIndex pindex(index); - QSharedPointer snapshot(m_serverManager->snapshot(server.data())); - auto h = [this, url, snapshot, pindex] (const QList actions) - { + QSharedPointer snapshot( + m_serverManager->snapshot(server.data())); + auto h = [this, url, snapshot, pindex](const QList actions) { if (!pindex.isValid()) return; auto child = m_diagnosticsModel->itemFromIndex(pindex); if (!child) return; // add actions below diagnostic item - for (const auto &action: actions) { + for (const auto &action : actions) { auto item = new DiagnosticItem(action, snapshot); child->appendRow(item); - auto text = action.kind.size() ? - QStringLiteral("[%1] %2").arg(action.kind).arg(action.title) : - action.title; + auto text = action.kind.size() + ? QStringLiteral("[%1] %2").arg(action.kind).arg(action.title) + : action.title; item->setData(text, Qt::DisplayRole); item->setData(codeActionIcon(), Qt::DecorationRole); } m_diagnosticsTree->setExpanded(child->index(), true); // mark actions added child->setData(true, Qt::UserRole); }; auto range = activeView->selectionRange(); if (!range.isValid()) { range = document->documentRange(); } - server->documentCodeAction(url, range, {}, {it->m_diagnostic}, this, h); + server->documentCodeAction(url, range, {}, { it->m_diagnostic }, this, h); } void tabCloseRequested(int index) { auto widget = m_tabWidget->widget(index); if (widget != m_diagnosticsTree) { if (m_markModel && widget == m_markModel->parent()) { clearAllLocationMarks(); } delete widget; } } void switchToDiagnostics() { m_tabWidget->setCurrentWidget(m_diagnosticsTree); m_mainWindow->showToolView(m_toolView.data()); } void closeNonDiagnostics() { - for (int i = 0; i < m_tabWidget->count(); ) { + for (int i = 0; i < m_tabWidget->count();) { if (m_tabWidget->widget(i) != m_diagnosticsTree) { tabCloseRequested(i); } else { ++i; } } } // local helper to overcome some differences in LSP types - struct RangeItem - { + struct RangeItem { QUrl uri; LSPRange range; LSPDocumentHighlightKind kind; }; - static - bool compareRangeItem(const RangeItem & a, const RangeItem & b) - { return (a.uri < b.uri) || ((a.uri == b.uri) && a.range < b.range); } + static bool compareRangeItem(const RangeItem &a, const RangeItem &b) + { + return (a.uri < b.uri) || ((a.uri == b.uri) && a.range < b.range); + } // provide Qt::DisplayRole (text) line lazily; // only find line's text content when so requested // This may then involve opening reading some file, at which time // all items for that file will be resolved in one go. - struct LineItem : public QStandardItem - { + struct LineItem : public QStandardItem { KTextEditor::MainWindow *m_mainWindow; - LineItem(KTextEditor::MainWindow *mainWindow) : - m_mainWindow(mainWindow) - {} + LineItem(KTextEditor::MainWindow *mainWindow) : m_mainWindow(mainWindow) {} QVariant data(int role = Qt::UserRole + 1) const override { auto rootItem = this->parent(); if (role != Qt::DisplayRole || !rootItem) { return QStandardItem::data(role); } auto line = data(Qt::UserRole); // either of these mean we tried to obtain line already if (line.isValid() || rootItem->data(RangeData::KindRole).toBool()) { return QStandardItem::data(role).toString().append(line.toString()); } KTextEditor::Document *doc = nullptr; QScopedPointer fr; for (int i = 0; i < rootItem->rowCount(); i++) { auto child = rootItem->child(i); if (i == 0) { auto url = child->data(RangeData::FileUrlRole).toUrl(); doc = findDocument(m_mainWindow, url); if (!doc) { fr.reset(new FileLineReader(url)); } } auto lineno = child->data(RangeData::RangeRole).value().start().line(); auto line = doc ? doc->line(lineno) : fr->line(lineno); child->setData(line, Qt::UserRole); } // mark as processed rootItem->setData(RangeData::KindRole, true); // should work ok return data(role); } - }; - LSPRange transformRange(const QUrl & url, const LSPClientRevisionSnapshot & snapshot, const LSPRange & range) + LSPRange transformRange(const QUrl &url, const LSPClientRevisionSnapshot &snapshot, + const LSPRange &range) { KTextEditor::MovingInterface *miface; qint64 revision; auto result = range; snapshot.find(url, miface, revision); if (miface) { miface->transformRange(result, KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::AllowEmpty, revision); } return result; } - void fillItemRoles(QStandardItem * item, const QUrl & url, const LSPRange _range, - RangeData::KindEnum kind, const LSPClientRevisionSnapshot * snapshot = nullptr) + void fillItemRoles(QStandardItem *item, const QUrl &url, const LSPRange _range, + RangeData::KindEnum kind, + const LSPClientRevisionSnapshot *snapshot = nullptr) { auto range = snapshot ? transformRange(url, *snapshot, _range) : _range; item->setData(QVariant(url), RangeData::FileUrlRole); QVariant vrange; vrange.setValue(range); item->setData(vrange, RangeData::RangeRole); - item->setData((int) kind, RangeData::KindRole); + item->setData((int)kind, RangeData::KindRole); } - void makeTree(const QVector & locations, const LSPClientRevisionSnapshot * snapshot) + void makeTree(const QVector &locations, const LSPClientRevisionSnapshot *snapshot) { // group by url, assuming input is suitably sorted that way auto treeModel = new QStandardItemModel(); treeModel->setColumnCount(1); QUrl lastUrl; QStandardItem *parent = nullptr; - for (const auto & loc: locations) { + for (const auto &loc : locations) { if (loc.uri != lastUrl) { if (parent) { - parent->setText(QStringLiteral("%1: %2").arg(lastUrl.path()).arg(parent->rowCount())); + parent->setText( + QStringLiteral("%1: %2").arg(lastUrl.path()).arg(parent->rowCount())); } lastUrl = loc.uri; parent = new QStandardItem(); treeModel->appendRow(parent); } auto item = new LineItem(m_mainWindow); parent->appendRow(item); // add partial display data; line will be added by item later on item->setText(i18n("Line: %1: ", loc.range.start().line() + 1)); fillItemRoles(item, loc.uri, loc.range, loc.kind, snapshot); } if (parent) parent->setText(QStringLiteral("%1: %2").arg(lastUrl.path()).arg(parent->rowCount())); // plain heuristic; mark for auto-expand all when safe and/or useful to do so if (treeModel->rowCount() <= 2 || locations.size() <= 20) { treeModel->invisibleRootItem()->setData(true, RangeData::KindRole); } m_ownedModel.reset(treeModel); m_markModel = treeModel; } - void showTree(const QString & title, QPointer *targetTree) + void showTree(const QString &title, QPointer *targetTree) { // clean up previous target if any if (targetTree && *targetTree) { int index = m_tabWidget->indexOf(*targetTree); if (index >= 0) tabCloseRequested(index); } // setup view auto treeView = new QTreeView(); configureTreeView(treeView); // transfer model from owned to tree and that in turn to tabwidget auto treeModel = m_ownedModel.take(); treeView->setModel(treeModel); treeModel->setParent(treeView); int index = m_tabWidget->addTab(treeView, title); connect(treeView, &QTreeView::clicked, this, &self_type::goToItemLocation); if (treeModel->invisibleRootItem()->data(RangeData::KindRole).toBool()) { treeView->expandAll(); } // track for later cleanup if (targetTree) *targetTree = treeView; // activate the resulting tab m_tabWidget->setCurrentIndex(index); m_mainWindow->showToolView(m_toolView.data()); } - void showMessage(const QString & text, KTextEditor::Message::MessageType level) + void showMessage(const QString &text, KTextEditor::Message::MessageType level) { KTextEditor::View *view = m_mainWindow->activeView(); - if (!view || !view->document()) return; + if (!view || !view->document()) + return; auto kmsg = new KTextEditor::Message(text, level); kmsg->setPosition(KTextEditor::Message::BottomInView); kmsg->setAutoHide(500); kmsg->setView(view); view->document()->postMessage(kmsg); } void handleEsc(QEvent *e) { - if (!m_mainWindow) return; + if (!m_mainWindow) + return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (!m_ranges.empty()) { clearAllLocationMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView.data()); } } } template - using LocationRequest = std::function; + using LocationRequest = + std::function; template - void positionRequest(const LocationRequest & req, const Handler & h, - QScopedPointer * snapshot = nullptr) + void positionRequest(const LocationRequest &req, const Handler &h, + QScopedPointer *snapshot = nullptr) { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (!server) return; // track revision if requested if (snapshot) { snapshot->reset(m_serverManager->snapshot(server.data())); } KTextEditor::Cursor cursor = activeView->cursorPosition(); clearAllLocationMarks(); m_req_timeout = false; QTimer::singleShot(1000, this, [this] { m_req_timeout = true; }); m_handle.cancel() = req(*server, activeView->document()->url(), - {cursor.line(), cursor.column()}, this, h); + { cursor.line(), cursor.column() }, this, h); } QString currentWord() { KTextEditor::View *activeView = m_mainWindow->activeView(); if (activeView) { KTextEditor::Cursor cursor = activeView->cursorPosition(); return activeView->document()->wordAt(cursor); } else { return QString(); } } // some template and function type trickery here, but at least that buck stops here then ... - template>> - void processLocations(const QString & title, - const typename utils::identity>::type & req, bool onlyshow, - const std::function & itemConverter, - QPointer *targetTree = nullptr) + template>> + void processLocations(const QString &title, + const typename utils::identity>::type &req, + bool onlyshow, + const std::function &itemConverter, + QPointer *targetTree = nullptr) { // no capture for move only using initializers available (yet), so shared outer type // the additional level of indirection is so it can be 'filled-in' after lambda creation - QSharedPointer> s(new QScopedPointer); - auto h = [this, title, onlyshow, itemConverter, targetTree, s] (const QList & defs) - { + QSharedPointer> s( + new QScopedPointer); + auto h = [this, title, onlyshow, itemConverter, targetTree, + s](const QList &defs) { if (defs.count() == 0) { showMessage(i18n("No results"), KTextEditor::Message::Information); } else { // convert to helper type QVector ranges; ranges.reserve(defs.size()); - for (const auto & def: defs) { + for (const auto &def : defs) { ranges.push_back(itemConverter(def)); } // ... so we can sort it also std::stable_sort(ranges.begin(), ranges.end(), compareRangeItem); makeTree(ranges, s.data()->data()); // assuming that reply ranges refer to revision when submitted // (not specified anyway in protocol/reply) if (defs.count() > 1 || onlyshow) { showTree(title, targetTree); } // it's not nice to jump to some location if we are too late if (!m_req_timeout && !onlyshow) { // assuming here that the first location is the best one const auto &item = itemConverter(defs.at(0)); const auto &pos = item.range.start(); goToDocumentLocation(item.uri, pos.line(), pos.column()); } // update marks updateState(); } }; positionRequest(req, h, s.data()); } - static RangeItem - locationToRangeItem(const LSPLocation & loc) - { return {loc.uri, loc.range, LSPDocumentHighlightKind::Text}; } + static RangeItem locationToRangeItem(const LSPLocation &loc) + { + return { loc.uri, loc.range, LSPDocumentHighlightKind::Text }; + } void goToDefinition() { auto title = i18nc("@title:tab", "Definition: %1", currentWord()); - processLocations(title, &LSPClientServer::documentDefinition, - false, &self_type::locationToRangeItem, &m_defTree); + processLocations(title, &LSPClientServer::documentDefinition, false, + &self_type::locationToRangeItem, &m_defTree); } void goToDeclaration() { auto title = i18nc("@title:tab", "Declaration: %1", currentWord()); - processLocations(title, &LSPClientServer::documentDeclaration, - false, &self_type::locationToRangeItem, &m_declTree); + processLocations(title, &LSPClientServer::documentDeclaration, false, + &self_type::locationToRangeItem, &m_declTree); } void findReferences() { auto title = i18nc("@title:tab", "References: %1", currentWord()); bool decl = m_refDeclaration->isChecked(); - auto req = [decl] (LSPClientServer & server, const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentDefinitionReplyHandler & h) - { return server.documentReferences(document, pos, decl, context, h); }; + auto req = [decl](LSPClientServer &server, const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentDefinitionReplyHandler &h) { + return server.documentReferences(document, pos, decl, context, h); + }; processLocations(title, req, true, &self_type::locationToRangeItem); } void highlight() { // determine current url to capture and use later on QUrl url; - const KTextEditor::View* viewForRequest(m_mainWindow->activeView()); + const KTextEditor::View *viewForRequest(m_mainWindow->activeView()); if (viewForRequest && viewForRequest->document()) { url = viewForRequest->document()->url(); } auto title = i18nc("@title:tab", "Highlight: %1", currentWord()); - auto converter = [url] (const LSPDocumentHighlight & hl) - { return RangeItem {url, hl.range, hl.kind}; }; + auto converter = [url](const LSPDocumentHighlight &hl) { + return RangeItem { url, hl.range, hl.kind }; + }; - processLocations(title, &LSPClientServer::documentHighlight, true, converter); + processLocations(title, &LSPClientServer::documentHighlight, + true, converter); } void hover() { // trigger manually the normally automagic hover if (auto activeView = m_mainWindow->activeView()) { m_hover->textHint(activeView, activeView->cursorPosition()); } } - void applyEdits(KTextEditor::Document * doc, const LSPClientRevisionSnapshot * snapshot, - const QList & edits) + void applyEdits(KTextEditor::Document *doc, const LSPClientRevisionSnapshot *snapshot, + const QList &edits) { - KTextEditor::MovingInterface* miface = qobject_cast(doc); + KTextEditor::MovingInterface *miface = qobject_cast(doc); if (!miface) return; // NOTE: // server might be pretty sloppy wrt edits (e.g. python-language-server) // e.g. send one edit for the whole document rather than 'surgical edits' // and that even when requesting format for a limited selection // ... but then we are but a client and do as we are told // all-in-all a low priority feature // all coordinates in edits are wrt original document, // so create moving ranges that will adjust to preceding edits as they are applied - QVector ranges; - for (const auto &edit: edits) { + QVector ranges; + for (const auto &edit : edits) { auto range = snapshot ? transformRange(doc->url(), *snapshot, edit.range) : edit.range; KTextEditor::MovingRange *mr = miface->newMovingRange(range); ranges.append(mr); } // now make one transaction (a.o. for one undo) and apply in sequence { KTextEditor::Document::EditingTransaction transaction(doc); for (int i = 0; i < ranges.length(); ++i) { doc->replaceText(ranges.at(i)->toRange(), edits.at(i).newText); } } qDeleteAll(ranges); } - void applyWorkspaceEdit(const LSPWorkspaceEdit & edit, const LSPClientRevisionSnapshot * snapshot) + void applyWorkspaceEdit(const LSPWorkspaceEdit &edit, const LSPClientRevisionSnapshot *snapshot) { auto currentView = m_mainWindow->activeView(); for (auto it = edit.changes.begin(); it != edit.changes.end(); ++it) { auto document = findDocument(m_mainWindow, it.key()); if (!document) { KTextEditor::View *view = m_mainWindow->openUrl(it.key()); if (view) { document = view->document(); } } applyEdits(document, snapshot, it.value()); } if (currentView) { m_mainWindow->activateView(currentView->document()); } } - void onApplyEdit(const LSPApplyWorkspaceEditParams & edit, const ApplyEditReplyHandler & h, bool &handled) + void onApplyEdit(const LSPApplyWorkspaceEditParams &edit, const ApplyEditReplyHandler &h, + bool &handled) { if (handled) return; handled = true; if (m_accept_edit) { qCInfo(LSPCLIENT) << "applying edit" << edit.label; applyWorkspaceEdit(edit.edit, nullptr); } else { qCInfo(LSPCLIENT) << "ignoring edit"; } - h({m_accept_edit, QString()}); + h({ m_accept_edit, QString() }); } template - void checkEditResult(const Collection & c) + void checkEditResult(const Collection &c) { if (c.size() == 0) { showMessage(i18n("No edits"), KTextEditor::Message::Information); } } - void delayCancelRequest(LSPClientServer::RequestHandle && h, int timeout_ms = 4000) + void delayCancelRequest(LSPClientServer::RequestHandle &&h, int timeout_ms = 4000) { - QTimer::singleShot(timeout_ms, this, [h] () mutable { h.cancel(); }); + QTimer::singleShot(timeout_ms, this, [h]() mutable { h.cancel(); }); } void format(QChar lastChar = QChar()) { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); if (!server || !document) return; int tabSize = 4; bool insertSpaces = true; - auto cfgiface = qobject_cast(document); + auto cfgiface = qobject_cast(document); if (cfgiface) { tabSize = cfgiface->configValue(QStringLiteral("tab-width")).toInt(); insertSpaces = cfgiface->configValue(QStringLiteral("replace-tabs")).toBool(); } // sigh, no move initialization capture ... // (again) assuming reply ranges wrt revisions submitted at this time - QSharedPointer snapshot(m_serverManager->snapshot(server.data())); - auto h = [this, document, snapshot, lastChar] (const QList & edits) - { + QSharedPointer snapshot( + m_serverManager->snapshot(server.data())); + auto h = [this, document, snapshot, lastChar](const QList &edits) { if (lastChar.isNull()) { checkEditResult(edits); } if (document) { applyEdits(document, snapshot.data(), edits); } }; - auto options = LSPFormattingOptions {tabSize, insertSpaces, QJsonObject()}; - auto handle = !lastChar.isNull() ? - server->documentOnTypeFormatting(document->url(), activeView->cursorPosition(), - lastChar, options, this, h) : - (activeView->selection() ? - server->documentRangeFormatting(document->url(), activeView->selectionRange(), - options, this, h) : - server->documentFormatting(document->url(), - options, this, h)); + auto options = LSPFormattingOptions { tabSize, insertSpaces, QJsonObject() }; + auto handle = !lastChar.isNull() + ? server->documentOnTypeFormatting(document->url(), activeView->cursorPosition(), + lastChar, options, this, h) + : (activeView->selection() + ? server->documentRangeFormatting( + document->url(), activeView->selectionRange(), options, this, h) + : server->documentFormatting(document->url(), options, this, h)); delayCancelRequest(std::move(handle)); } void rename() { KTextEditor::View *activeView = m_mainWindow->activeView(); QPointer document = activeView->document(); auto server = m_serverManager->findServer(activeView); if (!server || !document) return; bool ok = false; // results are typically (too) limited // due to server implementation or limited view/scope // so let's add a disclaimer that it's not our fault - QString newName = QInputDialog::getText(activeView, - i18nc("@title:window", "Rename"), - i18nc("@label:textbox", "New name (caution: not all references may be replaced)"), - QLineEdit::Normal, QString(), &ok); + QString newName = QInputDialog::getText( + activeView, i18nc("@title:window", "Rename"), + i18nc("@label:textbox", "New name (caution: not all references may be replaced)"), + QLineEdit::Normal, QString(), &ok); if (!ok) { return; } - QSharedPointer snapshot(m_serverManager->snapshot(server.data())); - auto h = [this, snapshot] (const LSPWorkspaceEdit & edit) - { + QSharedPointer snapshot( + m_serverManager->snapshot(server.data())); + auto h = [this, snapshot](const LSPWorkspaceEdit &edit) { checkEditResult(edit.changes); applyWorkspaceEdit(edit, snapshot.data()); }; - auto handle = server->documentRename(document->url(), - activeView->cursorPosition(), newName, this, h); + auto handle = server->documentRename(document->url(), activeView->cursorPosition(), newName, + this, h); delayCancelRequest(std::move(handle)); } - static QStandardItem* - getItem(const QStandardItemModel &model, const QUrl & url) + static QStandardItem *getItem(const QStandardItemModel &model, const QUrl &url) { auto l = model.findItems(url.path()); if (l.length()) { return l.at(0); } return nullptr; } // select/scroll to diagnostics item for document and (optionally) line bool syncDiagnostics(KTextEditor::Document *document, int line, bool allowTop, bool doShow) { if (!m_diagnosticsTree) return false; auto hint = QAbstractItemView::PositionAtTop; QStandardItem *targetItem = nullptr; QStandardItem *topItem = getItem(*m_diagnosticsModel, document->url()); if (topItem) { int count = topItem->rowCount(); // let's not run wild on a linear search in a flood of diagnostics // user is already in enough trouble as it is ;-) if (count > 50) count = 0; for (int i = 0; i < count; ++i) { auto item = topItem->child(i); int itemline = item->data(RangeData::RangeRole).value().start().line(); if (line == itemline && m_diagnosticsTree) { targetItem = item; hint = QAbstractItemView::PositionAtCenter; break; } } } if (!targetItem && allowTop) { targetItem = topItem; } if (targetItem) { m_diagnosticsTree->blockSignals(true); m_diagnosticsTree->scrollTo(targetItem->index(), hint); m_diagnosticsTree->setCurrentIndex(targetItem->index()); m_diagnosticsTree->blockSignals(false); if (doShow) { m_tabWidget->setCurrentWidget(m_diagnosticsTree); m_mainWindow->showToolView(m_toolView.data()); } } return targetItem != nullptr; } void onViewState(KTextEditor::View *view, LSPClientViewTracker::State newState) { if (!view || !view->document()) return; // select top item on view change, // but otherwise leave selection unchanged if no match - switch(newState) { + switch (newState) { case LSPClientViewTracker::ViewChanged: syncDiagnostics(view->document(), view->cursorPosition().line(), true, false); break; case LSPClientViewTracker::LineChanged: syncDiagnostics(view->document(), view->cursorPosition().line(), false, false); break; default: // should not happen break; } } - Q_SLOT void onMarkClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled) + Q_SLOT void onMarkClicked(KTextEditor::Document *document, KTextEditor::Mark mark, + bool &handled) { // no action if no mark was sprinkled here - if (m_diagnosticsMarks.contains(document) && syncDiagnostics(document, mark.line, false, true)) { + if (m_diagnosticsMarks.contains(document) + && syncDiagnostics(document, mark.line, false, true)) { handled = true; } } - void onDiagnostics(const LSPPublishDiagnosticsParams & diagnostics) + void onDiagnostics(const LSPPublishDiagnosticsParams &diagnostics) { if (!m_diagnosticsTree) return; QStandardItemModel *model = m_diagnosticsModel.data(); QStandardItem *topItem = getItem(*m_diagnosticsModel, diagnostics.uri); if (!topItem) { // no need to create an empty one if (diagnostics.diagnostics.size() == 0) { return; } topItem = new QStandardItem(); model->appendRow(topItem); topItem->setText(diagnostics.uri.path()); } else { topItem->setRowCount(0); } - for (const auto & diag : diagnostics.diagnostics) { + for (const auto &diag : diagnostics.diagnostics) { auto item = new DiagnosticItem(diag); topItem->appendRow(item); QString source; if (diag.source.length()) { source = QStringLiteral("[%1] ").arg(diag.source); } item->setData(diagnosticsIcon(diag.severity), Qt::DecorationRole); item->setText(source + diag.message); fillItemRoles(item, diagnostics.uri, diag.range, diag.severity); const auto &related = diag.relatedInformation; if (!related.location.uri.isEmpty()) { auto relatedItemMessage = new QStandardItem(); relatedItemMessage->setText(related.message); - fillItemRoles(relatedItemMessage, related.location.uri, related.location.range, RangeData::KindEnum::Related); + fillItemRoles(relatedItemMessage, related.location.uri, related.location.range, + RangeData::KindEnum::Related); auto relatedItemPath = new QStandardItem(); auto basename = QFileInfo(related.location.uri.path()).fileName(); - relatedItemPath->setText(QStringLiteral("%1:%2").arg(basename).arg(related.location.range.start().line())); - item->appendRow({relatedItemMessage, relatedItemPath}); + relatedItemPath->setText(QStringLiteral("%1:%2").arg(basename).arg( + related.location.range.start().line())); + item->appendRow({ relatedItemMessage, relatedItemPath }); m_diagnosticsTree->setExpanded(item->index(), true); } } // TODO perhaps add some custom delegate that only shows 1 line // and only the whole text when item selected ?? m_diagnosticsTree->setExpanded(topItem->index(), true); m_diagnosticsTree->setRowHidden(topItem->row(), QModelIndex(), topItem->rowCount() == 0); m_diagnosticsTree->scrollTo(topItem->index(), QAbstractItemView::PositionAtTop); auto header = m_diagnosticsTree->header(); header->setStretchLastSection(false); header->setMinimumSectionSize(0); header->setSectionResizeMode(0, QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); updateState(); } void onDocumentUrlChanged(KTextEditor::Document *doc) { // url already changed by this time and new url not useufl (void)doc; // note; url also changes when closed // spec says; // if a language has a project system, diagnostics are not cleared by *server* // but in either case (url change or close); remove lingering diagnostics // collect active urls QSet fpaths; - for (const auto &view: m_mainWindow->views()) { + for (const auto &view : m_mainWindow->views()) { if (auto doc = view->document()) { fpaths.insert(doc->url().path()); } } // check and clear defunct entries const auto &model = *m_diagnosticsModel; for (int i = 0; i < model.rowCount(); ++i) { auto item = model.item(i); if (item && !fpaths.contains(item->text())) { item->setRowCount(0); if (m_diagnosticsTree) { m_diagnosticsTree->setRowHidden(item->row(), QModelIndex(), true); } } } } void onTextChanged(KTextEditor::Document *doc) { if (m_onTypeFormattingTriggers.size() == 0) return; KTextEditor::View *activeView = m_mainWindow->activeView(); if (!activeView || activeView->document() != doc) return; // NOTE the intendation mode should probably be set to None, // so as not to experience unpleasant interference auto cursor = activeView->cursorPosition(); - QChar lastChar = cursor.column() == 0 ? QChar::fromLatin1('\n') : - doc->characterAt({cursor.line(), cursor.column() - 1}); + QChar lastChar = cursor.column() == 0 + ? QChar::fromLatin1('\n') + : doc->characterAt({ cursor.line(), cursor.column() - 1 }); if (m_onTypeFormattingTriggers.contains(lastChar)) { format(lastChar); } } void updateState() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto doc = activeView ? activeView->document() : nullptr; auto server = m_serverManager->findServer(activeView); bool defEnabled = false, declEnabled = false, refEnabled = false; bool hoverEnabled = false, highlightEnabled = false; bool formatEnabled = false; bool renameEnabled = false; if (server) { - const auto& caps = server->capabilities(); + const auto &caps = server->capabilities(); defEnabled = caps.definitionProvider; // FIXME no real official protocol way to detect, so enable anyway declEnabled = caps.declarationProvider || true; refEnabled = caps.referencesProvider; hoverEnabled = caps.hoverProvider; highlightEnabled = caps.documentHighlightProvider; formatEnabled = caps.documentFormattingProvider || caps.documentRangeFormattingProvider; renameEnabled = caps.renameProvider; - connect(server.data(), &LSPClientServer::publishDiagnostics, - this, &self_type::onDiagnostics, Qt::UniqueConnection); - connect(server.data(), &LSPClientServer::applyEdit, - this, &self_type::onApplyEdit, Qt::UniqueConnection); + connect(server.data(), &LSPClientServer::publishDiagnostics, this, + &self_type::onDiagnostics, Qt::UniqueConnection); + connect(server.data(), &LSPClientServer::applyEdit, this, &self_type::onApplyEdit, + Qt::UniqueConnection); // update format trigger characters - const auto & fmt = caps.documentOnTypeFormattingProvider; + const auto &fmt = caps.documentOnTypeFormattingProvider; if (fmt.provider && m_onTypeFormatting->isChecked()) { - m_onTypeFormattingTriggers = fmt.triggerCharacters; + m_onTypeFormattingTriggers = fmt.triggerCharacters; } else { m_onTypeFormattingTriggers.clear(); } // and monitor for such if (doc) { - connect(doc, &KTextEditor::Document::textChanged, - this, &self_type::onTextChanged, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::documentUrlChanged, - this, &self_type::onDocumentUrlChanged, Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::documentUrlChanged, this, + &self_type::onDocumentUrlChanged, Qt::UniqueConnection); } } if (m_findDef) m_findDef->setEnabled(defEnabled); if (m_findDecl) m_findDecl->setEnabled(declEnabled); if (m_findRef) m_findRef->setEnabled(refEnabled); if (m_triggerHighlight) m_triggerHighlight->setEnabled(highlightEnabled); if (m_triggerHover) m_triggerHover->setEnabled(hoverEnabled); if (m_triggerFormat) m_triggerFormat->setEnabled(formatEnabled); if (m_triggerRename) m_triggerRename->setEnabled(renameEnabled); if (m_complDocOn) m_complDocOn->setEnabled(server); if (m_restartServer) m_restartServer->setEnabled(server); // update completion with relevant server m_completion->setServer(server); if (m_complDocOn) m_completion->setSelectedDocumentation(m_complDocOn->isChecked()); updateCompletion(activeView, server.data()); // update hover with relevant server m_hover->setServer(server); updateHover(activeView, server.data()); // update marks if applicable if (m_markModel && doc) addMarks(doc, m_markModel, m_ranges, m_marks); if (m_diagnosticsModel && doc) { clearMarks(doc, m_diagnosticsRanges, m_diagnosticsMarks, RangeData::markTypeDiagAll); addMarks(doc, m_diagnosticsModel.data(), m_diagnosticsRanges, m_diagnosticsMarks); } // connect for cleanup stuff if (activeView) - connect(activeView, &KTextEditor::View::destroyed, this, - &self_type::viewDestroyed, - Qt::UniqueConnection); + connect(activeView, &KTextEditor::View::destroyed, this, &self_type::viewDestroyed, + Qt::UniqueConnection); } void viewDestroyed(QObject *view) { m_completionViews.remove(static_cast(view)); m_hoverViews.remove(static_cast(view)); } - void updateCompletion(KTextEditor::View *view, LSPClientServer * server) + void updateCompletion(KTextEditor::View *view, LSPClientServer *server) { bool registered = m_completionViews.contains(view); - KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); + KTextEditor::CodeCompletionInterface *cci = + qobject_cast(view); if (!cci) { return; } if (!registered && server && server->capabilities().completionProvider.provider) { qCInfo(LSPCLIENT) << "registering cci"; cci->registerCompletionModel(m_completion.data()); m_completionViews.insert(view); } if (registered && !server) { qCInfo(LSPCLIENT) << "unregistering cci"; cci->unregisterCompletionModel(m_completion.data()); m_completionViews.remove(view); } } - void updateHover(KTextEditor::View *view, LSPClientServer * server) + void updateHover(KTextEditor::View *view, LSPClientServer *server) { bool registered = m_hoverViews.contains(view); KTextEditor::TextHintInterface *cci = qobject_cast(view); if (!cci) { return; } if (!registered && server && server->capabilities().hoverProvider) { qCInfo(LSPCLIENT) << "registering cci"; cci->registerTextHintProvider(m_hover.data()); m_hoverViews.insert(view); } if (registered && !server) { qCInfo(LSPCLIENT) << "unregistering cci"; cci->unregisterTextHintProvider(m_hover.data()); m_hoverViews.remove(view); } } }; - class LSPClientPluginViewImpl : public QObject, public KXMLGUIClient { Q_OBJECT typedef LSPClientPluginViewImpl self_type; KTextEditor::MainWindow *m_mainWindow; QSharedPointer m_serverManager; QScopedPointer m_actionView; public: LSPClientPluginViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) - : QObject(mainWin), m_mainWindow(mainWin), + : QObject(mainWin), + m_mainWindow(mainWin), m_serverManager(LSPClientServerManager::new_(plugin, mainWin)), m_actionView(new LSPClientActionView(plugin, mainWin, this, m_serverManager)) { KXMLGUIClient::setComponentName(QStringLiteral("lspclient"), i18n("LSP Client")); setXMLFile(QStringLiteral("ui.rc")); m_mainWindow->guiFactory()->addClient(this); } ~LSPClientPluginViewImpl() { // minimize/avoid some surprises; // safe construction/destruction by separate (helper) objects; // signals are auto-disconnected when high-level "view" objects are broken down // so it only remains to clean up lowest level here then prior to removal m_actionView.reset(); m_serverManager.reset(); m_mainWindow->guiFactory()->removeClient(this); } }; - -QObject* -LSPClientPluginView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) +QObject *LSPClientPluginView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) { return new LSPClientPluginViewImpl(plugin, mainWin); } #include "lspclientpluginview.moc" diff --git a/addons/lspclient/lspclientpluginview.h b/addons/lspclient/lspclientpluginview.h index 47255a3ac..c9832a76f 100644 --- a/addons/lspclient/lspclientpluginview.h +++ b/addons/lspclient/lspclientpluginview.h @@ -1,45 +1,43 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTPLUGINVIEW_H #define LSPCLIENTPLUGINVIEW_H #include class LSPClientPlugin; namespace KTextEditor { - class MainWindow; +class MainWindow; } class LSPClientPluginView { public: // only needs a factory; no other public interface - static QObject* - new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin); + static QObject *new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin); }; #endif - diff --git a/addons/lspclient/lspclientprotocol.h b/addons/lspclient/lspclientprotocol.h index 9730fdcfa..b779c9de0 100644 --- a/addons/lspclient/lspclientprotocol.h +++ b/addons/lspclient/lspclientprotocol.h @@ -1,341 +1,303 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTPROTOCOL_H #define LSPCLIENTPROTOCOL_H #include #include #include #include #include #include #include #include #include // Following types roughly follow the types/interfaces as defined in LSP protocol spec // although some deviation may arise where it has been deemed useful // Moreover, to avoid introducing a custom 'optional' type, absence of an optional // part/member is usually signalled by some 'invalid' marker (empty, negative). -enum class LSPErrorCode -{ +enum class LSPErrorCode { // Defined by JSON RPC ParseError = -32700, InvalidRequest = -32600, MethodNotFound = -32601, InvalidParams = -32602, InternalError = -32603, serverErrorStart = -32099, serverErrorEnd = -32000, ServerNotInitialized = -32002, UnknownErrorCode = -32001, // Defined by the protocol. RequestCancelled = -32800, ContentModified = -32801 }; -enum class LSPDocumentSyncKind -{ - None = 0, - Full = 1, - Incremental = 2 -}; +enum class LSPDocumentSyncKind { None = 0, Full = 1, Incremental = 2 }; -struct LSPCompletionOptions -{ +struct LSPCompletionOptions { bool provider = false; bool resolveProvider = false; QVector triggerCharacters; }; -struct LSPSignatureHelpOptions -{ +struct LSPSignatureHelpOptions { bool provider = false; QVector triggerCharacters; }; // ensure distinct type -struct LSPDocumentOnTypeFormattingOptions : public LSPSignatureHelpOptions {}; +struct LSPDocumentOnTypeFormattingOptions : public LSPSignatureHelpOptions { +}; -struct LSPServerCapabilities -{ +struct LSPServerCapabilities { LSPDocumentSyncKind textDocumentSync = LSPDocumentSyncKind::None; bool hoverProvider = false; LSPCompletionOptions completionProvider; LSPSignatureHelpOptions signatureHelpProvider; bool definitionProvider = false; // FIXME ? clangd unofficial extension bool declarationProvider = false; bool referencesProvider = false; bool documentSymbolProvider = false; bool documentHighlightProvider = false; bool documentFormattingProvider = false; bool documentRangeFormattingProvider = false; LSPDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider; bool renameProvider = false; // CodeActionOptions not useful/considered at present bool codeActionProvider = false; }; -enum class LSPMarkupKind -{ - None = 0, - PlainText = 1, - MarkDown = 2 -}; +enum class LSPMarkupKind { None = 0, PlainText = 1, MarkDown = 2 }; -struct LSPMarkupContent -{ +struct LSPMarkupContent { LSPMarkupKind kind = LSPMarkupKind::None; QString value; }; /** * Language Server Protocol Position * line + column, 0 based, negative for invalid * maps 1:1 to KTextEditor::Cursor */ using LSPPosition = KTextEditor::Cursor; /** * Language Server Protocol Range * start + end tuple of LSPPosition * maps 1:1 to KTextEditor::Range */ using LSPRange = KTextEditor::Range; -struct LSPLocation -{ +struct LSPLocation { QUrl uri; LSPRange range; }; -struct LSPTextDocumentContentChangeEvent -{ +struct LSPTextDocumentContentChangeEvent { LSPRange range; QString text; }; -enum class LSPDocumentHighlightKind -{ - Text = 1, - Read = 2, - Write = 3 -}; +enum class LSPDocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; -struct LSPDocumentHighlight -{ +struct LSPDocumentHighlight { LSPRange range; LSPDocumentHighlightKind kind; }; -struct LSPHover -{ - // vector for contents to support all three variants: MarkedString | MarkedString[] | MarkupContent +struct LSPHover { + // vector for contents to support all three variants: + // MarkedString | MarkedString[] | MarkupContent // vector variant is still in use e.g. by Rust rls QVector contents; LSPRange range; }; enum class LSPSymbolKind { File = 1, Module = 2, Namespace = 3, Package = 4, Class = 5, Method = 6, Property = 7, Field = 8, Constructor = 9, Enum = 10, Interface = 11, Function = 12, Variable = 13, Constant = 14, String = 15, Number = 16, Boolean = 17, Array = 18, }; -struct LSPSymbolInformation -{ - LSPSymbolInformation(const QString & _name, LSPSymbolKind _kind, - LSPRange _range, const QString & _detail) +struct LSPSymbolInformation { + LSPSymbolInformation(const QString &_name, LSPSymbolKind _kind, LSPRange _range, + const QString &_detail) : name(_name), detail(_detail), kind(_kind), range(_range) - {} + { + } QString name; QString detail; LSPSymbolKind kind; LSPRange range; QList children; }; -enum class LSPCompletionItemKind -{ +enum class LSPCompletionItemKind { Text = 1, Method = 2, Function = 3, Constructor = 4, Field = 5, Variable = 6, Class = 7, Interface = 8, Module = 9, Property = 10, Unit = 11, Value = 12, Enum = 13, Keyword = 14, Snippet = 15, Color = 16, File = 17, Reference = 18, Folder = 19, EnumMember = 20, Constant = 21, Struct = 22, Event = 23, Operator = 24, TypeParameter = 25, }; -struct LSPCompletionItem -{ +struct LSPCompletionItem { QString label; LSPCompletionItemKind kind; QString detail; LSPMarkupContent documentation; QString sortText; QString insertText; }; -struct LSPParameterInformation -{ +struct LSPParameterInformation { // offsets into overall signature label // (-1 if invalid) int start; int end; }; -struct LSPSignatureInformation -{ +struct LSPSignatureInformation { QString label; LSPMarkupContent documentation; QList parameters; }; -struct LSPSignatureHelp -{ +struct LSPSignatureHelp { QList signatures; int activeSignature; int activeParameter; }; -struct LSPFormattingOptions -{ +struct LSPFormattingOptions { int tabSize; bool insertSpaces; // additional fields QJsonObject extra; }; -struct LSPTextEdit -{ +struct LSPTextEdit { LSPRange range; QString newText; }; -enum class LSPDiagnosticSeverity -{ +enum class LSPDiagnosticSeverity { Unknown = 0, Error = 1, Warning = 2, Information = 3, Hint = 4, }; -struct LSPDiagnosticRelatedInformation -{ +struct LSPDiagnosticRelatedInformation { // empty url / invalid range when absent LSPLocation location; QString message; }; -struct LSPDiagnostic -{ +struct LSPDiagnostic { LSPRange range; LSPDiagnosticSeverity severity; QString code; QString source; QString message; LSPDiagnosticRelatedInformation relatedInformation; }; -struct LSPPublishDiagnosticsParams -{ +struct LSPPublishDiagnosticsParams { QUrl uri; QList diagnostics; }; -struct LSPCommand -{ +struct LSPCommand { QString title; QString command; // pretty opaque QJsonArray arguments; }; -struct LSPWorkspaceEdit -{ +struct LSPWorkspaceEdit { // supported part for now QHash> changes; }; -struct LSPCodeAction -{ +struct LSPCodeAction { QString title; QString kind; QList diagnostics; LSPWorkspaceEdit edit; LSPCommand command; }; -struct LSPApplyWorkspaceEditParams -{ +struct LSPApplyWorkspaceEditParams { QString label; LSPWorkspaceEdit edit; }; -struct LSPApplyWorkspaceEditResponse -{ +struct LSPApplyWorkspaceEditResponse { bool applied; QString failureReason; }; #endif diff --git a/addons/lspclient/lspclientserver.cpp b/addons/lspclient/lspclientserver.cpp index 61fd35c12..237ed749d 100644 --- a/addons/lspclient/lspclientserver.cpp +++ b/addons/lspclient/lspclientserver.cpp @@ -1,1384 +1,1366 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #include "lspclientserver.h" #include "lspclient_debug.h" #include #include #include #include #include #include #include #include #include // good/bad old school; allows easier concatenate -#define CONTENT_LENGTH "Content-Length" +#define CONTENT_LENGTH "Content-Length" static const QString MEMBER_ID = QStringLiteral("id"); static const QString MEMBER_METHOD = QStringLiteral("method"); static const QString MEMBER_ERROR = QStringLiteral("error"); static const QString MEMBER_CODE = QStringLiteral("code"); static const QString MEMBER_MESSAGE = QStringLiteral("message"); static const QString MEMBER_PARAMS = QStringLiteral("params"); static const QString MEMBER_RESULT = QStringLiteral("result"); static const QString MEMBER_URI = QStringLiteral("uri"); static const QString MEMBER_VERSION = QStringLiteral("version"); static const QString MEMBER_START = QStringLiteral("start"); static const QString MEMBER_END = QStringLiteral("end"); static const QString MEMBER_POSITION = QStringLiteral("position"); static const QString MEMBER_LOCATION = QStringLiteral("location"); static const QString MEMBER_RANGE = QStringLiteral("range"); static const QString MEMBER_LINE = QStringLiteral("line"); static const QString MEMBER_CHARACTER = QStringLiteral("character"); static const QString MEMBER_KIND = QStringLiteral("kind"); static const QString MEMBER_TEXT = QStringLiteral("text"); static const QString MEMBER_LANGID = QStringLiteral("languageId"); static const QString MEMBER_LABEL = QStringLiteral("label"); static const QString MEMBER_DOCUMENTATION = QStringLiteral("documentation"); static const QString MEMBER_DETAIL = QStringLiteral("detail"); static const QString MEMBER_COMMAND = QStringLiteral("command"); static const QString MEMBER_EDIT = QStringLiteral("edit"); static const QString MEMBER_TITLE = QStringLiteral("title"); static const QString MEMBER_ARGUMENTS = QStringLiteral("arguments"); static const QString MEMBER_DIAGNOSTICS = QStringLiteral("diagnostics"); // message construction helpers -static QJsonObject -to_json(const LSPPosition & pos) +static QJsonObject to_json(const LSPPosition &pos) { - return QJsonObject { - { MEMBER_LINE, pos.line() }, - { MEMBER_CHARACTER, pos.column() } - }; + return QJsonObject { { MEMBER_LINE, pos.line() }, { MEMBER_CHARACTER, pos.column() } }; } -static QJsonObject -to_json(const LSPRange & range) +static QJsonObject to_json(const LSPRange &range) { - return QJsonObject { - { MEMBER_START, to_json(range.start()) }, - { MEMBER_END, to_json(range.end()) } - }; + return QJsonObject { { MEMBER_START, to_json(range.start()) }, + { MEMBER_END, to_json(range.end()) } }; } -static QJsonValue -to_json(const LSPLocation & location) +static QJsonValue to_json(const LSPLocation &location) { if (location.uri.isValid()) { - return QJsonObject { - { MEMBER_URI, location.uri.toString() }, - { MEMBER_RANGE, to_json(location.range) } - }; + return QJsonObject { { MEMBER_URI, location.uri.toString() }, + { MEMBER_RANGE, to_json(location.range) } }; } return QJsonValue(); } -static QJsonValue -to_json(const LSPDiagnosticRelatedInformation & related) +static QJsonValue to_json(const LSPDiagnosticRelatedInformation &related) { auto loc = to_json(related.location); if (loc.isObject()) { - return QJsonObject { - { MEMBER_LOCATION, to_json(related.location) }, - { MEMBER_MESSAGE, related.message } - }; + return QJsonObject { { MEMBER_LOCATION, to_json(related.location) }, + { MEMBER_MESSAGE, related.message } }; } return QJsonValue(); } -static QJsonObject -to_json(const LSPDiagnostic & diagnostic) +static QJsonObject to_json(const LSPDiagnostic &diagnostic) { // required auto result = QJsonObject(); result[MEMBER_RANGE] = to_json(diagnostic.range); result[MEMBER_MESSAGE] = diagnostic.message; // optional if (!diagnostic.code.isEmpty()) result[QStringLiteral("code")] = diagnostic.code; if (diagnostic.severity != LSPDiagnosticSeverity::Unknown) - result[QStringLiteral("severity")] = (int) diagnostic.severity; + result[QStringLiteral("severity")] = (int)diagnostic.severity; if (!diagnostic.source.isEmpty()) result[QStringLiteral("source")] = diagnostic.source; auto related = to_json(diagnostic.relatedInformation); if (related.isObject()) { result[QStringLiteral("relatedInformation")] = related; } return result; } -static QJsonArray -to_json(const QList & changes) +static QJsonArray to_json(const QList &changes) { QJsonArray result; - for (const auto &change: changes) { - result.push_back(QJsonObject { - {MEMBER_RANGE, to_json(change.range)}, - {MEMBER_TEXT, change.text} - }); + for (const auto &change : changes) { + result.push_back(QJsonObject { { MEMBER_RANGE, to_json(change.range) }, + { MEMBER_TEXT, change.text } }); } return result; } -static QJsonObject -versionedTextDocumentIdentifier(const QUrl & document, int version = -1) +static QJsonObject versionedTextDocumentIdentifier(const QUrl &document, int version = -1) { QJsonObject map { { MEMBER_URI, document.toString() } }; if (version >= 0) map[MEMBER_VERSION] = version; return map; } -static QJsonObject -textDocumentItem(const QUrl & document, const QString & lang, - const QString & text, int version) +static QJsonObject textDocumentItem(const QUrl &document, const QString &lang, const QString &text, + int version) { auto map = versionedTextDocumentIdentifier(document, version); map[MEMBER_TEXT] = text; map[MEMBER_LANGID] = lang; return map; } -static QJsonObject -textDocumentParams(const QJsonObject & m) +static QJsonObject textDocumentParams(const QJsonObject &m) { - return QJsonObject { - { QStringLiteral("textDocument"), m} - }; + return QJsonObject { { QStringLiteral("textDocument"), m } }; } -static QJsonObject -textDocumentParams(const QUrl & document, int version = -1) -{ return textDocumentParams(versionedTextDocumentIdentifier(document, version)); } +static QJsonObject textDocumentParams(const QUrl &document, int version = -1) +{ + return textDocumentParams(versionedTextDocumentIdentifier(document, version)); +} -static QJsonObject -textDocumentPositionParams(const QUrl & document, LSPPosition pos) +static QJsonObject textDocumentPositionParams(const QUrl &document, LSPPosition pos) { auto params = textDocumentParams(document); params[MEMBER_POSITION] = to_json(pos); return params; } -static QJsonObject -referenceParams(const QUrl & document, LSPPosition pos, bool decl) +static QJsonObject referenceParams(const QUrl &document, LSPPosition pos, bool decl) { auto params = textDocumentPositionParams(document, pos); - params[QStringLiteral("context")] = QJsonObject { - { QStringLiteral("includeDeclaration"), decl } - }; + params[QStringLiteral("context")] = + QJsonObject { { QStringLiteral("includeDeclaration"), decl } }; return params; } -static QJsonObject -formattingOptions(const LSPFormattingOptions & _options) +static QJsonObject formattingOptions(const LSPFormattingOptions &_options) { auto options = _options.extra; options[QStringLiteral("tabSize")] = _options.tabSize; options[QStringLiteral("insertSpaces")] = _options.insertSpaces; return options; } -static QJsonObject -documentRangeFormattingParams(const QUrl & document, const LSPRange *range, - const LSPFormattingOptions & _options) +static QJsonObject documentRangeFormattingParams(const QUrl &document, const LSPRange *range, + const LSPFormattingOptions &_options) { auto params = textDocumentParams(document); if (range) { params[MEMBER_RANGE] = to_json(*range); } params[QStringLiteral("options")] = formattingOptions(_options); return params; } -static QJsonObject -documentOnTypeFormattingParams(const QUrl & document, const LSPPosition & pos, - const QChar & lastChar, const LSPFormattingOptions & _options) +static QJsonObject documentOnTypeFormattingParams(const QUrl &document, const LSPPosition &pos, + const QChar &lastChar, + const LSPFormattingOptions &_options) { auto params = textDocumentPositionParams(document, pos); params[QStringLiteral("ch")] = QString(lastChar); params[QStringLiteral("options")] = formattingOptions(_options); return params; } -static QJsonObject -renameParams(const QUrl & document, const LSPPosition & pos, const QString & newName) +static QJsonObject renameParams(const QUrl &document, const LSPPosition &pos, + const QString &newName) { auto params = textDocumentPositionParams(document, pos); params[QStringLiteral("newName")] = newName; return params; } -static QJsonObject -codeActionParams(const QUrl & document, const LSPRange & range, - QList kinds, QList diagnostics) +static QJsonObject codeActionParams(const QUrl &document, const LSPRange &range, + QList kinds, QList diagnostics) { auto params = textDocumentParams(document); params[MEMBER_RANGE] = to_json(range); QJsonObject context; QJsonArray diags; - for (const auto& diagnostic: diagnostics) { + for (const auto &diagnostic : diagnostics) { diags.push_back(to_json(diagnostic)); } context[MEMBER_DIAGNOSTICS] = diags; if (kinds.length()) context[QStringLiteral("only")] = QJsonArray::fromStringList(kinds); params[QStringLiteral("context")] = context; return params; } -static QJsonObject -executeCommandParams(const QString & command, const QJsonValue & args) +static QJsonObject executeCommandParams(const QString &command, const QJsonValue &args) { - return QJsonObject { - { MEMBER_COMMAND, command }, - { MEMBER_ARGUMENTS, args } - }; + return QJsonObject { { MEMBER_COMMAND, command }, { MEMBER_ARGUMENTS, args } }; } -static QJsonObject -applyWorkspaceEditResponse(const LSPApplyWorkspaceEditResponse & response) +static QJsonObject applyWorkspaceEditResponse(const LSPApplyWorkspaceEditResponse &response) { - return QJsonObject { - { QStringLiteral("applied"), response.applied }, - { QStringLiteral("failureReason"), response.failureReason } - }; + return QJsonObject { { QStringLiteral("applied"), response.applied }, + { QStringLiteral("failureReason"), response.failureReason } }; } -static void -from_json(QVector & trigger, const QJsonValue & json) +static void from_json(QVector &trigger, const QJsonValue &json) { - for (const auto & t : json.toArray()) { + for (const auto &t : json.toArray()) { auto st = t.toString(); if (st.length()) trigger.push_back(st.at(0)); } } -static void -from_json(LSPCompletionOptions & options, const QJsonValue & json) +static void from_json(LSPCompletionOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; options.resolveProvider = ob.value(QStringLiteral("resolveProvider")).toBool(); from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters"))); } } -static void -from_json(LSPSignatureHelpOptions & options, const QJsonValue & json) +static void from_json(LSPSignatureHelpOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; from_json(options.triggerCharacters, ob.value(QStringLiteral("triggerCharacters"))); } } -static void -from_json(LSPDocumentOnTypeFormattingOptions & options, const QJsonValue & json) +static void from_json(LSPDocumentOnTypeFormattingOptions &options, const QJsonValue &json) { if (json.isObject()) { auto ob = json.toObject(); options.provider = true; from_json(options.triggerCharacters, ob.value(QStringLiteral("moreTriggerCharacter"))); auto trigger = ob.value(QStringLiteral("firstTriggerCharacter")).toString(); if (trigger.size()) { options.triggerCharacters.insert(0, trigger.at(0)); } } } -static void -from_json(LSPServerCapabilities & caps, const QJsonObject & json) +static void from_json(LSPServerCapabilities &caps, const QJsonObject &json) { auto sync = json.value(QStringLiteral("textDocumentSync")); - caps.textDocumentSync = (LSPDocumentSyncKind) - (sync.isObject() ? sync.toObject().value(QStringLiteral("change")) : sync).toInt((int)LSPDocumentSyncKind::None); + caps.textDocumentSync = + (LSPDocumentSyncKind)(sync.isObject() ? sync.toObject().value(QStringLiteral("change")) + : sync) + .toInt((int)LSPDocumentSyncKind::None); caps.hoverProvider = json.value(QStringLiteral("hoverProvider")).toBool(); from_json(caps.completionProvider, json.value(QStringLiteral("completionProvider"))); from_json(caps.signatureHelpProvider, json.value(QStringLiteral("signatureHelpProvider"))); caps.definitionProvider = json.value(QStringLiteral("definitionProvider")).toBool(); caps.declarationProvider = json.value(QStringLiteral("declarationProvider")).toBool(); caps.referencesProvider = json.value(QStringLiteral("referencesProvider")).toBool(); caps.documentSymbolProvider = json.value(QStringLiteral("documentSymbolProvider")).toBool(); - caps.documentHighlightProvider = json.value(QStringLiteral("documentHighlightProvider")).toBool(); - caps.documentFormattingProvider = json.value(QStringLiteral("documentFormattingProvider")).toBool(); - caps.documentRangeFormattingProvider = json.value(QStringLiteral("documentRangeFormattingProvider")).toBool(); - from_json(caps.documentOnTypeFormattingProvider, json.value(QStringLiteral("documentOnTypeFormattingProvider"))); + caps.documentHighlightProvider = + json.value(QStringLiteral("documentHighlightProvider")).toBool(); + caps.documentFormattingProvider = + json.value(QStringLiteral("documentFormattingProvider")).toBool(); + caps.documentRangeFormattingProvider = + json.value(QStringLiteral("documentRangeFormattingProvider")).toBool(); + from_json(caps.documentOnTypeFormattingProvider, + json.value(QStringLiteral("documentOnTypeFormattingProvider"))); caps.renameProvider = json.value(QStringLiteral("renameProvider")).toBool(); auto codeActionProvider = json.value(QStringLiteral("codeActionProvider")); caps.codeActionProvider = codeActionProvider.toBool() || codeActionProvider.isObject(); } // follow suit; as performed in kate docmanager // normalize at this stage/layer to avoid surprises elsewhere // sadly this is not a single QUrl method as one might hope ... -static QUrl -normalizeUrl(const QUrl & url) +static QUrl normalizeUrl(const QUrl &url) { // Resolve symbolic links for local files (done anyway in KTextEditor) if (url.isLocalFile()) { QString normalizedUrl = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!normalizedUrl.isEmpty()) { return QUrl::fromLocalFile(normalizedUrl); } } // else: cleanup only the .. stuff return url.adjusted(QUrl::NormalizePathSegments); } -static LSPMarkupContent -parseMarkupContent(const QJsonValue & v) +static LSPMarkupContent parseMarkupContent(const QJsonValue &v) { LSPMarkupContent ret; if (v.isObject()) { - const auto& vm = v.toObject(); + const auto &vm = v.toObject(); ret.value = vm.value(QStringLiteral("value")).toString(); auto kind = vm.value(MEMBER_KIND).toString(); if (kind == QStringLiteral("plaintext")) { ret.kind = LSPMarkupKind::PlainText; } else if (kind == QStringLiteral("markdown")) { ret.kind = LSPMarkupKind::MarkDown; } } else if (v.isString()) { ret.kind = LSPMarkupKind::PlainText; ret.value = v.toString(); } return ret; } -static LSPPosition -parsePosition(const QJsonObject & m) +static LSPPosition parsePosition(const QJsonObject &m) { auto line = m.value(MEMBER_LINE).toInt(-1); auto column = m.value(MEMBER_CHARACTER).toInt(-1); - return {line, column}; + return { line, column }; } -static bool -isPositionValid(const LSPPosition & pos) -{ return pos.isValid(); } +static bool isPositionValid(const LSPPosition &pos) +{ + return pos.isValid(); +} -static LSPRange -parseRange(const QJsonObject & range) +static LSPRange parseRange(const QJsonObject &range) { auto startpos = parsePosition(range.value(MEMBER_START).toObject()); auto endpos = parsePosition(range.value(MEMBER_END).toObject()); - return {startpos, endpos}; + return { startpos, endpos }; } -static LSPLocation -parseLocation(const QJsonObject & loc) +static LSPLocation parseLocation(const QJsonObject &loc) { auto uri = normalizeUrl(QUrl(loc.value(MEMBER_URI).toString())); auto range = parseRange(loc.value(MEMBER_RANGE).toObject()); - return {QUrl(uri), range}; + return { QUrl(uri), range }; } -static LSPDocumentHighlight -parseDocumentHighlight(const QJsonValue & result) +static LSPDocumentHighlight parseDocumentHighlight(const QJsonValue &result) { auto hover = result.toObject(); auto range = parseRange(hover.value(MEMBER_RANGE).toObject()); - auto kind = (LSPDocumentHighlightKind)hover.value(MEMBER_KIND).toInt((int)LSPDocumentHighlightKind::Text); // default is DocumentHighlightKind.Text - return {range, kind}; + auto kind = (LSPDocumentHighlightKind)hover.value(MEMBER_KIND) + .toInt((int)LSPDocumentHighlightKind::Text); // default is + // DocumentHighlightKind.Text + return { range, kind }; } -static QList -parseDocumentHighlightList(const QJsonValue & result) +static QList parseDocumentHighlightList(const QJsonValue &result) { QList ret; // could be array if (result.isArray()) { - for (const auto & def : result.toArray()) { + for (const auto &def : result.toArray()) { ret.push_back(parseDocumentHighlight(def)); } } else if (result.isObject()) { // or a single value ret.push_back(parseDocumentHighlight(result)); } return ret; } -static LSPMarkupContent -parseHoverContentElement(const QJsonValue & contents) +static LSPMarkupContent parseHoverContentElement(const QJsonValue &contents) { LSPMarkupContent result; if (contents.isString()) { result.value = contents.toString(); } else { // should be object, pretend so auto cont = contents.toObject(); auto text = cont.value(QStringLiteral("value")).toString(); if (text.isEmpty()) { // nothing to lose, try markdown result = parseMarkupContent(contents); } else { result.value = text; } } if (result.value.length()) result.kind = LSPMarkupKind::PlainText; return result; } -static LSPHover -parseHover(const QJsonValue & result) +static LSPHover parseHover(const QJsonValue &result) { LSPHover ret; auto hover = result.toObject(); // normalize content which can be of many forms ret.range = parseRange(hover.value(MEMBER_RANGE).toObject()); auto contents = hover.value(QStringLiteral("contents")); // support the deprecated MarkedString[] variant, used by e.g. Rust rls if (contents.isArray()) { - for (const auto & c : contents.toArray()) { + for (const auto &c : contents.toArray()) { ret.contents.push_back(parseHoverContentElement(c)); } } else { ret.contents.push_back(parseHoverContentElement(contents)); } return ret; } -static QList -parseDocumentSymbols(const QJsonValue & result) +static QList parseDocumentSymbols(const QJsonValue &result) { // the reply could be old SymbolInformation[] or new (hierarchical) DocumentSymbol[] // try to parse it adaptively in any case // if new style, hierarchy is specified clearly in reply // if old style, it is assumed the values enter linearly, that is; // * a parent/container is listed before its children // * if a name is defined/declared several times and then used as a parent, // then it is the last instance that is used as a parent QList ret; - QMap index; - - std::function parseSymbol = - [&] (const QJsonObject & symbol, LSPSymbolInformation *parent) { - // if flat list, try to find parent by name - if (!parent) { - auto container = symbol.value(QStringLiteral("containerName")).toString(); - parent = index.value(container, nullptr); - } - auto list = parent ? &parent->children : &ret; - const auto& location = symbol.value(MEMBER_LOCATION).toObject(); - const auto& mrange = symbol.contains(MEMBER_RANGE) ? - symbol.value(MEMBER_RANGE) : location.value(MEMBER_RANGE); - auto range = parseRange(mrange.toObject()); - if (isPositionValid(range.start()) && isPositionValid(range.end())) { - auto name = symbol.value(QStringLiteral("name")).toString(); - auto kind = (LSPSymbolKind) symbol.value(MEMBER_KIND).toInt(); - auto detail = symbol.value(MEMBER_DETAIL).toString(); - list->push_back({name, kind, range, detail}); - index[name] = &list->back(); - // proceed recursively - for (const auto &child : symbol.value(QStringLiteral("children")).toArray()) - parseSymbol(child.toObject(), &list->back()); - } - }; + QMap index; + + std::function parseSymbol = + [&](const QJsonObject &symbol, LSPSymbolInformation *parent) { + // if flat list, try to find parent by name + if (!parent) { + auto container = symbol.value(QStringLiteral("containerName")).toString(); + parent = index.value(container, nullptr); + } + auto list = parent ? &parent->children : &ret; + const auto &location = symbol.value(MEMBER_LOCATION).toObject(); + const auto &mrange = symbol.contains(MEMBER_RANGE) ? symbol.value(MEMBER_RANGE) + : location.value(MEMBER_RANGE); + auto range = parseRange(mrange.toObject()); + if (isPositionValid(range.start()) && isPositionValid(range.end())) { + auto name = symbol.value(QStringLiteral("name")).toString(); + auto kind = (LSPSymbolKind)symbol.value(MEMBER_KIND).toInt(); + auto detail = symbol.value(MEMBER_DETAIL).toString(); + list->push_back({ name, kind, range, detail }); + index[name] = &list->back(); + // proceed recursively + for (const auto &child : symbol.value(QStringLiteral("children")).toArray()) + parseSymbol(child.toObject(), &list->back()); + } + }; - for (const auto& info : result.toArray()) { + for (const auto &info : result.toArray()) { parseSymbol(info.toObject(), nullptr); } return ret; } -static QList -parseDocumentLocation(const QJsonValue & result) +static QList parseDocumentLocation(const QJsonValue &result) { QList ret; // could be array if (result.isArray()) { - for (const auto & def : result.toArray()) { + for (const auto &def : result.toArray()) { ret.push_back(parseLocation(def.toObject())); } } else if (result.isObject()) { // or a single value ret.push_back(parseLocation(result.toObject())); } return ret; } -static QList -parseDocumentCompletion(const QJsonValue & result) +static QList parseDocumentCompletion(const QJsonValue &result) { QList ret; QJsonArray items = result.toArray(); // might be CompletionList if (items.size() == 0) { items = result.toObject().value(QStringLiteral("items")).toArray(); } - for (const auto & vitem : items) { - const auto & item = vitem.toObject(); + for (const auto &vitem : items) { + const auto &item = vitem.toObject(); auto label = item.value(MEMBER_LABEL).toString(); auto detail = item.value(MEMBER_DETAIL).toString(); auto doc = parseMarkupContent(item.value(MEMBER_DOCUMENTATION)); auto sortText = item.value(QStringLiteral("sortText")).toString(); if (sortText.isEmpty()) sortText = label; auto insertText = item.value(QStringLiteral("insertText")).toString(); if (insertText.isEmpty()) insertText = label; - auto kind = (LSPCompletionItemKind) item.value(MEMBER_KIND).toInt(); - ret.push_back({label, kind, detail, doc, sortText, insertText}); + auto kind = (LSPCompletionItemKind)item.value(MEMBER_KIND).toInt(); + ret.push_back({ label, kind, detail, doc, sortText, insertText }); } return ret; } -static LSPSignatureInformation -parseSignatureInformation(const QJsonObject & json) +static LSPSignatureInformation parseSignatureInformation(const QJsonObject &json) { LSPSignatureInformation info; info.label = json.value(MEMBER_LABEL).toString(); info.documentation = parseMarkupContent(json.value(MEMBER_DOCUMENTATION)); - for (const auto & rpar : json.value(QStringLiteral("parameters")).toArray()) { + for (const auto &rpar : json.value(QStringLiteral("parameters")).toArray()) { auto par = rpar.toObject(); auto label = par.value(MEMBER_LABEL); int begin = -1, end = -1; if (label.isArray()) { auto range = label.toArray(); if (range.size() == 2) { begin = range.at(0).toInt(-1); end = range.at(1).toInt(-1); if (begin > info.label.length()) begin = -1; if (end > info.label.length()) end = -1; } } else { auto sub = label.toString(); if (sub.length()) { begin = info.label.indexOf(sub); if (begin >= 0) { end = begin + sub.length(); } } } - info.parameters.push_back({begin, end}); + info.parameters.push_back({ begin, end }); } return info; } -static LSPSignatureHelp -parseSignatureHelp(const QJsonValue & result) +static LSPSignatureHelp parseSignatureHelp(const QJsonValue &result) { LSPSignatureHelp ret; QJsonObject sig = result.toObject(); - for (const auto & info: sig.value(QStringLiteral("signatures")).toArray()) { + for (const auto &info : sig.value(QStringLiteral("signatures")).toArray()) { ret.signatures.push_back(parseSignatureInformation(info.toObject())); } ret.activeSignature = sig.value(QStringLiteral("activeSignature")).toInt(0); ret.activeParameter = sig.value(QStringLiteral("activeParameter")).toInt(0); ret.activeSignature = qMin(qMax(ret.activeSignature, 0), ret.signatures.size()); ret.activeParameter = qMin(qMax(ret.activeParameter, 0), ret.signatures.size()); return ret; } -static QList -parseTextEdit(const QJsonValue & result) +static QList parseTextEdit(const QJsonValue &result) { QList ret; - for (const auto &redit: result.toArray()) { + for (const auto &redit : result.toArray()) { auto edit = redit.toObject(); auto text = edit.value(QStringLiteral("newText")).toString(); auto range = parseRange(edit.value(MEMBER_RANGE).toObject()); - ret.push_back({range, text}); + ret.push_back({ range, text }); } return ret; } -static LSPWorkspaceEdit -parseWorkSpaceEdit(const QJsonValue & result) +static LSPWorkspaceEdit parseWorkSpaceEdit(const QJsonValue &result) { QHash> ret; auto changes = result.toObject().value(QStringLiteral("changes")).toObject(); for (auto it = changes.begin(); it != changes.end(); ++it) { ret.insert(normalizeUrl(QUrl(it.key())), parseTextEdit(it.value())); } - return {ret}; + return { ret }; } -static LSPCommand -parseCommand(const QJsonObject & result) +static LSPCommand parseCommand(const QJsonObject &result) { auto title = result.value(MEMBER_TITLE).toString(); auto command = result.value(MEMBER_COMMAND).toString(); auto args = result.value(MEMBER_ARGUMENTS).toArray(); return { title, command, args }; } -static QList -parseDiagnostics(const QJsonArray &result) +static QList parseDiagnostics(const QJsonArray &result) { QList ret; - for (const auto & vdiag : result) { + for (const auto &vdiag : result) { auto diag = vdiag.toObject(); auto range = parseRange(diag.value(MEMBER_RANGE).toObject()); - auto severity = (LSPDiagnosticSeverity) diag.value(QStringLiteral("severity")).toInt(); + auto severity = (LSPDiagnosticSeverity)diag.value(QStringLiteral("severity")).toInt(); auto code = diag.value(QStringLiteral("code")).toString(); auto source = diag.value(QStringLiteral("source")).toString(); auto message = diag.value(MEMBER_MESSAGE).toString(); auto related = diag.value(QStringLiteral("relatedInformation")).toObject(); auto relLocation = parseLocation(related.value(MEMBER_LOCATION).toObject()); auto relMessage = related.value(MEMBER_MESSAGE).toString(); - ret.push_back({range, severity, code, source, message, relLocation, relMessage}); + ret.push_back({ range, severity, code, source, message, relLocation, relMessage }); } return ret; } -static QList -parseCodeAction(const QJsonValue & result) +static QList parseCodeAction(const QJsonValue &result) { QList ret; - for (const auto &vaction: result.toArray()) { + for (const auto &vaction : result.toArray()) { auto action = vaction.toObject(); // entry could be Command or CodeAction if (!action.value(MEMBER_COMMAND).isString()) { // CodeAction auto title = action.value(MEMBER_TITLE).toString(); auto kind = action.value(MEMBER_KIND).toString(); auto command = parseCommand(action.value(MEMBER_COMMAND).toObject()); auto edit = parseWorkSpaceEdit(action.value(MEMBER_EDIT)); auto diagnostics = parseDiagnostics(action.value(MEMBER_DIAGNOSTICS).toArray()); - ret.push_back({title, kind, diagnostics, edit, command}); + ret.push_back({ title, kind, diagnostics, edit, command }); } else { // Command auto command = parseCommand(action); - ret.push_back({command.title, QString(), {}, {}, command}); + ret.push_back({ command.title, QString(), {}, {}, command }); } } return ret; } -static LSPPublishDiagnosticsParams -parseDiagnostics(const QJsonObject & result) +static LSPPublishDiagnosticsParams parseDiagnostics(const QJsonObject &result) { LSPPublishDiagnosticsParams ret; ret.uri = normalizeUrl(QUrl(result.value(MEMBER_URI).toString())); ret.diagnostics = parseDiagnostics(result.value(MEMBER_DIAGNOSTICS).toArray()); return ret; } -static LSPApplyWorkspaceEditParams -parseApplyWorkspaceEditParams(const QJsonObject & result) +static LSPApplyWorkspaceEditParams parseApplyWorkspaceEditParams(const QJsonObject &result) { LSPApplyWorkspaceEditParams ret; ret.label = result.value(MEMBER_LABEL).toString(); ret.edit = parseWorkSpaceEdit(result.value(MEMBER_EDIT)); return ret; } - using GenericReplyType = QJsonValue; using GenericReplyHandler = ReplyHandler; class LSPClientServer::LSPClientServerPrivate { typedef LSPClientServerPrivate self_type; LSPClientServer *q; // server cmd line QStringList m_server; // workspace root to pass along QUrl m_root; // user provided init QJsonValue m_init; // server process QProcess m_sproc; // server declared capabilites LSPServerCapabilities m_capabilities; // server state State m_state = State::None; // last msg id int m_id = 0; // receive buffer QByteArray m_receive; // registered reply handlers QHash m_handlers; // pending request responses static constexpr int MAX_REQUESTS = 5; - QVector m_requests{MAX_REQUESTS + 1}; + QVector m_requests { MAX_REQUESTS + 1 }; public: - LSPClientServerPrivate(LSPClientServer * _q, const QStringList & server, - const QUrl & root, const QJsonValue & init) + LSPClientServerPrivate(LSPClientServer *_q, const QStringList &server, const QUrl &root, + const QJsonValue &init) : q(_q), m_server(server), m_root(root), m_init(init) { // setup async reading QObject::connect(&m_sproc, &QProcess::readyRead, utils::mem_fun(&self_type::read, this)); - QObject::connect(&m_sproc, &QProcess::stateChanged, utils::mem_fun(&self_type::onStateChanged, this)); + QObject::connect(&m_sproc, &QProcess::stateChanged, + utils::mem_fun(&self_type::onStateChanged, this)); } - ~LSPClientServerPrivate() - { - stop(TIMEOUT_SHUTDOWN, TIMEOUT_SHUTDOWN); - } + ~LSPClientServerPrivate() { stop(TIMEOUT_SHUTDOWN, TIMEOUT_SHUTDOWN); } - const QStringList& cmdline() const - { - return m_server; - } + const QStringList &cmdline() const { return m_server; } - State state() - { - return m_state; - } + State state() { return m_state; } - const LSPServerCapabilities& - capabilities() - { - return m_capabilities; - } + const LSPServerCapabilities &capabilities() { return m_capabilities; } int cancel(int reqid) { if (m_handlers.remove(reqid) > 0) { auto params = QJsonObject { { MEMBER_ID, reqid } }; write(init_request(QStringLiteral("$/cancelRequest"), params)); } return -1; } private: void setState(State s) { if (m_state != s) { m_state = s; emit q->stateChanged(q); } } - RequestHandle write(const QJsonObject & msg, const GenericReplyHandler & h = nullptr, const int * id = nullptr) + RequestHandle write(const QJsonObject &msg, const GenericReplyHandler &h = nullptr, + const int *id = nullptr) { RequestHandle ret; ret.m_server = q; if (!running()) return ret; auto ob = msg; ob.insert(QStringLiteral("jsonrpc"), QStringLiteral("2.0")); // notification == no handler if (h) { ob.insert(MEMBER_ID, ++m_id); ret.m_id = m_id; m_handlers[m_id] = h; } else if (id) { ob.insert(MEMBER_ID, *id); } QJsonDocument json(ob); auto sjson = json.toJson(); qCInfo(LSPCLIENT) << "calling" << msg[MEMBER_METHOD].toString(); qCDebug(LSPCLIENT) << "sending message:\n" << QString::fromUtf8(sjson); // some simple parsers expect length header first auto hdr = QStringLiteral(CONTENT_LENGTH ": %1\r\n").arg(sjson.length()); // write is async, so no blocking wait occurs here m_sproc.write(hdr.toLatin1()); m_sproc.write("\r\n"); m_sproc.write(sjson); return ret; } - RequestHandle - send(const QJsonObject & msg, const GenericReplyHandler & h = nullptr) + RequestHandle send(const QJsonObject &msg, const GenericReplyHandler &h = nullptr) { if (m_state == State::Running) return write(msg, h); else qCWarning(LSPCLIENT) << "send for non-running server"; return RequestHandle(); } void read() { // accumulate in buffer m_receive.append(m_sproc.readAllStandardOutput()); // try to get one (or more) message QByteArray &buffer = m_receive; while (true) { qCDebug(LSPCLIENT) << "buffer size" << buffer.length(); auto header = QByteArray(CONTENT_LENGTH ":"); int index = buffer.indexOf(header); if (index < 0) { // avoid collecting junk if (buffer.length() > 1 << 20) buffer.clear(); break; } index += header.length(); int endindex = buffer.indexOf("\r\n", index); auto msgstart = buffer.indexOf("\r\n\r\n", index); if (endindex < 0 || msgstart < 0) break; msgstart += 4; bool ok = false; auto length = buffer.mid(index, endindex - index).toInt(&ok, 10); // FIXME perhaps detect if no reply for some time // then again possibly better left to user to restart in such case if (!ok) { qCWarning(LSPCLIENT) << "invalid " CONTENT_LENGTH; // flush and try to carry on to some next header buffer.remove(0, msgstart); continue; } // sanity check to avoid extensive buffering if (length > 1 << 29) { qCWarning(LSPCLIENT) << "excessive size"; buffer.clear(); continue; } if (msgstart + length > buffer.length()) break; // now onto payload auto payload = buffer.mid(msgstart, length); buffer.remove(0, msgstart + length); qCInfo(LSPCLIENT) << "got message payload size " << length; qCDebug(LSPCLIENT) << "message payload:\n" << payload; - QJsonParseError error{}; + QJsonParseError error {}; auto msg = QJsonDocument::fromJson(payload, &error); if (error.error != QJsonParseError::NoError || !msg.isObject()) { qCWarning(LSPCLIENT) << "invalid response payload"; continue; } auto result = msg.object(); // check if it is the expected result int msgid = -1; if (result.contains(MEMBER_ID)) { msgid = result[MEMBER_ID].toInt(); } else { processNotification(result); continue; } // could be request if (result.contains(MEMBER_METHOD)) { processRequest(result); continue; } // a valid reply; what to do with it now auto it = m_handlers.find(msgid); if (it != m_handlers.end()) { // copy handler to local storage const auto handler = *it; // remove handler from our set, do this pre handler execution to avoid races m_handlers.erase(it); // run handler, might e.g. trigger some new LSP actions for this server handler(result.value(MEMBER_RESULT)); } else { // could have been canceled qCDebug(LSPCLIENT) << "unexpected reply id"; } } } - static QJsonObject - init_error(const LSPErrorCode code, const QString & msg) + static QJsonObject init_error(const LSPErrorCode code, const QString &msg) { return QJsonObject { - { MEMBER_ERROR, QJsonObject { - { MEMBER_CODE, (int) code }, - { MEMBER_MESSAGE, msg } - } - } + { MEMBER_ERROR, QJsonObject { { MEMBER_CODE, (int)code }, { MEMBER_MESSAGE, msg } } } }; } - static QJsonObject - init_request(const QString & method, const QJsonObject & params = QJsonObject()) + static QJsonObject init_request(const QString &method, + const QJsonObject ¶ms = QJsonObject()) { - return QJsonObject { - { MEMBER_METHOD, method }, - { MEMBER_PARAMS, params } - }; + return QJsonObject { { MEMBER_METHOD, method }, { MEMBER_PARAMS, params } }; } - static QJsonObject - init_response(const QJsonValue & result = QJsonValue()) + static QJsonObject init_response(const QJsonValue &result = QJsonValue()) { - return QJsonObject { - { MEMBER_RESULT, result } - }; + return QJsonObject { { MEMBER_RESULT, result } }; } - bool running() - { - return m_sproc.state() == QProcess::Running; - } + bool running() { return m_sproc.state() == QProcess::Running; } void onStateChanged(QProcess::ProcessState nstate) { if (nstate == QProcess::NotRunning) { setState(State::None); } } void shutdown() { if (m_state == State::Running) { qCInfo(LSPCLIENT) << "shutting down" << m_server; // cancel all pending m_handlers.clear(); // shutdown sequence send(init_request(QStringLiteral("shutdown"))); // maybe we will get/see reply on the above, maybe not // but not important or useful either way send(init_request(QStringLiteral("exit"))); // no longer fit for regular use setState(State::Shutdown); } } - void onInitializeReply(const QJsonValue & value) + void onInitializeReply(const QJsonValue &value) { // only parse parts that we use later on - from_json(m_capabilities, value.toObject().value(QStringLiteral("capabilities")).toObject()); + from_json(m_capabilities, + value.toObject().value(QStringLiteral("capabilities")).toObject()); // finish init initialized(); } void initialize() { QJsonObject codeAction { - { QStringLiteral("codeActionLiteralSupport"), QJsonObject { - { QStringLiteral("codeActionKind"), QJsonObject { - { QStringLiteral("valueSet"), QJsonArray() } - } - } - } - } + { QStringLiteral("codeActionLiteralSupport"), + QJsonObject { { QStringLiteral("codeActionKind"), + QJsonObject { { QStringLiteral("valueSet"), QJsonArray() } } } } } }; QJsonObject capabilities { { QStringLiteral("textDocument"), - QJsonObject { - { QStringLiteral("documentSymbol"), - QJsonObject { { QStringLiteral("hierarchicalDocumentSymbolSupport"), true } }, - }, - { QStringLiteral("publishDiagnostics"), - QJsonObject { { QStringLiteral("relatedInformation"), true } } - }, - { QStringLiteral("codeAction"), codeAction } - } - } + QJsonObject { + { + QStringLiteral("documentSymbol"), + QJsonObject { { QStringLiteral("hierarchicalDocumentSymbolSupport"), + true } }, + }, + { QStringLiteral("publishDiagnostics"), + QJsonObject { { QStringLiteral("relatedInformation"), true } } }, + { QStringLiteral("codeAction"), codeAction } } } }; // NOTE a typical server does not use root all that much, // other than for some corner case (in) requests - QJsonObject params { - { QStringLiteral("processId"), QCoreApplication::applicationPid() }, - { QStringLiteral("rootPath"), m_root.path() }, - { QStringLiteral("rootUri"), m_root.toString() }, - { QStringLiteral("capabilities"), capabilities }, - { QStringLiteral("initializationOptions"), m_init } - }; + QJsonObject params { { QStringLiteral("processId"), QCoreApplication::applicationPid() }, + { QStringLiteral("rootPath"), m_root.path() }, + { QStringLiteral("rootUri"), m_root.toString() }, + { QStringLiteral("capabilities"), capabilities }, + { QStringLiteral("initializationOptions"), m_init } }; // write(init_request(QStringLiteral("initialize"), params), - utils::mem_fun(&self_type::onInitializeReply, this)); + utils::mem_fun(&self_type::onInitializeReply, this)); } void initialized() { write(init_request(QStringLiteral("initialized"))); setState(State::Running); } public: bool start() { if (m_state != State::None) return true; auto program = m_server.front(); auto args = m_server; args.pop_front(); qCInfo(LSPCLIENT) << "starting" << m_server << "with root" << m_root; // start LSP server in project root m_sproc.setWorkingDirectory(m_root.path()); // at least we see some errors somewhere then m_sproc.setProcessChannelMode(QProcess::ForwardedErrorChannel); m_sproc.setReadChannel(QProcess::QProcess::StandardOutput); m_sproc.start(program, args); bool result = m_sproc.waitForStarted(); if (!result) { qCWarning(LSPCLIENT) << m_sproc.error(); } else { setState(State::Started); // perform initial handshake initialize(); } return result; } void stop(int to_term, int to_kill) { if (running()) { shutdown(); if ((to_term >= 0) && !m_sproc.waitForFinished(to_term)) m_sproc.terminate(); if ((to_kill >= 0) && !m_sproc.waitForFinished(to_kill)) m_sproc.kill(); } } - RequestHandle documentSymbols(const QUrl & document, const GenericReplyHandler & h) + RequestHandle documentSymbols(const QUrl &document, const GenericReplyHandler &h) { auto params = textDocumentParams(document); return send(init_request(QStringLiteral("textDocument/documentSymbol"), params), h); } - RequestHandle documentDefinition(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle documentDefinition(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/definition"), params), h); } - RequestHandle documentDeclaration(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle documentDeclaration(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/declaration"), params), h); } - RequestHandle documentHover(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle documentHover(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/hover"), params), h); } - RequestHandle documentHighlight(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle documentHighlight(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/documentHighlight"), params), h); } - RequestHandle documentReferences(const QUrl & document, const LSPPosition & pos, bool decl, - const GenericReplyHandler & h) + RequestHandle documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, + const GenericReplyHandler &h) { auto params = referenceParams(document, pos, decl); return send(init_request(QStringLiteral("textDocument/references"), params), h); } - RequestHandle documentCompletion(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle documentCompletion(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/completion"), params), h); } - RequestHandle signatureHelp(const QUrl & document, const LSPPosition & pos, - const GenericReplyHandler & h) + RequestHandle signatureHelp(const QUrl &document, const LSPPosition &pos, + const GenericReplyHandler &h) { auto params = textDocumentPositionParams(document, pos); return send(init_request(QStringLiteral("textDocument/signatureHelp"), params), h); } - RequestHandle documentFormatting(const QUrl & document, const LSPFormattingOptions & options, - const GenericReplyHandler & h) + RequestHandle documentFormatting(const QUrl &document, const LSPFormattingOptions &options, + const GenericReplyHandler &h) { auto params = documentRangeFormattingParams(document, nullptr, options); return send(init_request(QStringLiteral("textDocument/formatting"), params), h); } - RequestHandle documentRangeFormatting(const QUrl & document, const LSPRange & range, - const LSPFormattingOptions & options, const GenericReplyHandler & h) + RequestHandle documentRangeFormatting(const QUrl &document, const LSPRange &range, + const LSPFormattingOptions &options, + const GenericReplyHandler &h) { auto params = documentRangeFormattingParams(document, &range, options); return send(init_request(QStringLiteral("textDocument/rangeFormatting"), params), h); } - RequestHandle documentOnTypeFormatting(const QUrl & document, const LSPPosition & pos, - QChar lastChar, const LSPFormattingOptions & options, const GenericReplyHandler & h) + RequestHandle documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, + QChar lastChar, const LSPFormattingOptions &options, + const GenericReplyHandler &h) { auto params = documentOnTypeFormattingParams(document, pos, lastChar, options); return send(init_request(QStringLiteral("textDocument/onTypeFormatting"), params), h); } - RequestHandle documentRename(const QUrl & document, const LSPPosition & pos, - const QString newName, const GenericReplyHandler & h) + RequestHandle documentRename(const QUrl &document, const LSPPosition &pos, + const QString newName, const GenericReplyHandler &h) { auto params = renameParams(document, pos, newName); return send(init_request(QStringLiteral("textDocument/rename"), params), h); } - RequestHandle documentCodeAction(const QUrl & document, const LSPRange & range, - const QList & kinds, QList diagnostics, - const GenericReplyHandler & h) + RequestHandle documentCodeAction(const QUrl &document, const LSPRange &range, + const QList &kinds, QList diagnostics, + const GenericReplyHandler &h) { auto params = codeActionParams(document, range, kinds, diagnostics); return send(init_request(QStringLiteral("textDocument/codeAction"), params), h); } - void executeCommand(const QString & command, const QJsonValue & args) + void executeCommand(const QString &command, const QJsonValue &args) { auto params = executeCommandParams(command, args); send(init_request(QStringLiteral("workspace/executeCommand"), params)); } - void didOpen(const QUrl & document, int version, const QString & langId, const QString & text) + void didOpen(const QUrl &document, int version, const QString &langId, const QString &text) { auto params = textDocumentParams(textDocumentItem(document, langId, text, version)); send(init_request(QStringLiteral("textDocument/didOpen"), params)); } - void didChange(const QUrl & document, int version, const QString & text, - const QList & changes) + void didChange(const QUrl &document, int version, const QString &text, + const QList &changes) { Q_ASSERT(text.size() == 0 || changes.size() == 0); auto params = textDocumentParams(document, version); params[QStringLiteral("contentChanges")] = text.size() - ? QJsonArray { QJsonObject {{MEMBER_TEXT, text}} } + ? QJsonArray { QJsonObject { { MEMBER_TEXT, text } } } : to_json(changes); send(init_request(QStringLiteral("textDocument/didChange"), params)); } - void didSave(const QUrl & document, const QString & text) + void didSave(const QUrl &document, const QString &text) { auto params = textDocumentParams(document); params[QStringLiteral("text")] = text; send(init_request(QStringLiteral("textDocument/didSave"), params)); } - void didClose(const QUrl & document) + void didClose(const QUrl &document) { auto params = textDocumentParams(document); send(init_request(QStringLiteral("textDocument/didClose"), params)); } - void processNotification(const QJsonObject & msg) + void processNotification(const QJsonObject &msg) { auto method = msg[MEMBER_METHOD].toString(); if (method == QStringLiteral("textDocument/publishDiagnostics")) { emit q->publishDiagnostics(parseDiagnostics(msg[MEMBER_PARAMS].toObject())); } else { qCWarning(LSPCLIENT) << "discarding notification" << method; } } template - static GenericReplyHandler make_handler(const ReplyHandler & h, - const QObject *context, - typename utils::identity>::type c) + static GenericReplyHandler make_handler( + const ReplyHandler &h, const QObject *context, + typename utils::identity>::type c) { QPointer ctx(context); - return [ctx, h, c] (const GenericReplyType & m) { if (ctx) h(c(m)); }; + return [ctx, h, c](const GenericReplyType &m) { + if (ctx) + h(c(m)); + }; } - GenericReplyHandler - prepareResponse(int msgid) + GenericReplyHandler prepareResponse(int msgid) { // allow limited number of outstanding requests auto ctx = QPointer(q); m_requests.push_back(msgid); if (m_requests.size() > MAX_REQUESTS) { m_requests.pop_front(); } - auto h = [ctx, this, msgid] (const GenericReplyType & response) - { + auto h = [ctx, this, msgid](const GenericReplyType &response) { if (!ctx) { return; } auto index = m_requests.indexOf(msgid); if (index >= 0) { m_requests.remove(index); write(init_response(response), nullptr, &msgid); } else { qCWarning(LSPCLIENT) << "discarding response" << msgid; } }; return h; } template - static ReplyHandler responseHandler(const GenericReplyHandler & h, - typename utils::identity>::type c) - { return [h, c] (const ReplyType & m) { h(c(m)); }; } + static ReplyHandler responseHandler( + const GenericReplyHandler &h, + typename utils::identity>::type c) + { + return [h, c](const ReplyType &m) { h(c(m)); }; + } // pretty rare and limited use, but anyway - void processRequest(const QJsonObject & msg) + void processRequest(const QJsonObject &msg) { auto method = msg[MEMBER_METHOD].toString(); auto msgid = msg[MEMBER_ID].toInt(); auto params = msg[MEMBER_PARAMS]; bool handled = false; if (method == QStringLiteral("workspace/applyEdit")) { - auto h = responseHandler(prepareResponse(msgid), applyWorkspaceEditResponse); + auto h = responseHandler(prepareResponse(msgid), + applyWorkspaceEditResponse); emit q->applyEdit(parseApplyWorkspaceEditParams(params.toObject()), h, handled); } else { write(init_error(LSPErrorCode::MethodNotFound, method), nullptr, &msgid); qCWarning(LSPCLIENT) << "discarding request" << method; } } }; - // generic convert handler // sprinkle some connection-like context safety // not so likely relevant/needed due to typical sequence of events, // but in case the latter would be changed in surprising ways ... template -static GenericReplyHandler make_handler(const ReplyHandler & h, - const QObject *context, - typename utils::identity>::type c) +static GenericReplyHandler +make_handler(const ReplyHandler &h, const QObject *context, + typename utils::identity>::type c) { QPointer ctx(context); - return [ctx, h, c] (const GenericReplyType & m) { if (ctx) h(c(m)); }; + return [ctx, h, c](const GenericReplyType &m) { + if (ctx) + h(c(m)); + }; } - -LSPClientServer::LSPClientServer(const QStringList & server, const QUrl & root, - const QJsonValue & init) +LSPClientServer::LSPClientServer(const QStringList &server, const QUrl &root, + const QJsonValue &init) : d(new LSPClientServerPrivate(this, server, root, init)) -{} +{ +} LSPClientServer::~LSPClientServer() -{ delete d; } +{ + delete d; +} -const QStringList& LSPClientServer::cmdline() const -{ return d->cmdline(); } +const QStringList &LSPClientServer::cmdline() const +{ + return d->cmdline(); +} LSPClientServer::State LSPClientServer::state() const -{ return d->state(); } +{ + return d->state(); +} -const LSPServerCapabilities& -LSPClientServer::capabilities() const -{ return d->capabilities(); } +const LSPServerCapabilities &LSPClientServer::capabilities() const +{ + return d->capabilities(); +} bool LSPClientServer::start() -{ return d->start(); } +{ + return d->start(); +} void LSPClientServer::stop(int to_t, int to_k) -{ return d->stop(to_t, to_k); } +{ + return d->stop(to_t, to_k); +} int LSPClientServer::cancel(int reqid) -{ return d->cancel(reqid); } +{ + return d->cancel(reqid); +} LSPClientServer::RequestHandle -LSPClientServer::documentSymbols(const QUrl & document, const QObject *context, - const DocumentSymbolsReplyHandler & h) -{ return d->documentSymbols(document, make_handler(h, context, parseDocumentSymbols)); } +LSPClientServer::documentSymbols(const QUrl &document, const QObject *context, + const DocumentSymbolsReplyHandler &h) +{ + return d->documentSymbols(document, make_handler(h, context, parseDocumentSymbols)); +} LSPClientServer::RequestHandle -LSPClientServer::documentDefinition(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentDefinitionReplyHandler & h) -{ return d->documentDefinition(document, pos, make_handler(h, context, parseDocumentLocation)); } +LSPClientServer::documentDefinition(const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentDefinitionReplyHandler &h) +{ + return d->documentDefinition(document, pos, make_handler(h, context, parseDocumentLocation)); +} LSPClientServer::RequestHandle -LSPClientServer::documentDeclaration(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentDefinitionReplyHandler & h) -{ return d->documentDeclaration(document, pos, make_handler(h, context, parseDocumentLocation)); } +LSPClientServer::documentDeclaration(const QUrl &document, const LSPPosition &pos, + const QObject *context, + const DocumentDefinitionReplyHandler &h) +{ + return d->documentDeclaration(document, pos, make_handler(h, context, parseDocumentLocation)); +} -LSPClientServer::RequestHandle -LSPClientServer::documentHover(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentHoverReplyHandler & h) -{ return d->documentHover(document, pos, make_handler(h, context, parseHover)); } +LSPClientServer::RequestHandle LSPClientServer::documentHover(const QUrl &document, + const LSPPosition &pos, + const QObject *context, + const DocumentHoverReplyHandler &h) +{ + return d->documentHover(document, pos, make_handler(h, context, parseHover)); +} LSPClientServer::RequestHandle -LSPClientServer::documentHighlight(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentHighlightReplyHandler & h) -{ return d->documentHighlight(document, pos, make_handler(h, context, parseDocumentHighlightList)); } +LSPClientServer::documentHighlight(const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentHighlightReplyHandler &h) +{ + return d->documentHighlight(document, pos, + make_handler(h, context, parseDocumentHighlightList)); +} LSPClientServer::RequestHandle -LSPClientServer::documentReferences(const QUrl & document, const LSPPosition & pos, bool decl, - const QObject *context, const DocumentDefinitionReplyHandler & h) -{ return d->documentReferences(document, pos, decl, make_handler(h, context, parseDocumentLocation)); } +LSPClientServer::documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, + const QObject *context, const DocumentDefinitionReplyHandler &h) +{ + return d->documentReferences(document, pos, decl, + make_handler(h, context, parseDocumentLocation)); +} LSPClientServer::RequestHandle -LSPClientServer::documentCompletion(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentCompletionReplyHandler & h) -{ return d->documentCompletion(document, pos, make_handler(h, context, parseDocumentCompletion)); } +LSPClientServer::documentCompletion(const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentCompletionReplyHandler &h) +{ + return d->documentCompletion(document, pos, make_handler(h, context, parseDocumentCompletion)); +} -LSPClientServer::RequestHandle -LSPClientServer::signatureHelp(const QUrl & document, const LSPPosition & pos, - const QObject *context, const SignatureHelpReplyHandler & h) -{ return d->signatureHelp(document, pos, make_handler(h, context, parseSignatureHelp)); } +LSPClientServer::RequestHandle LSPClientServer::signatureHelp(const QUrl &document, + const LSPPosition &pos, + const QObject *context, + const SignatureHelpReplyHandler &h) +{ + return d->signatureHelp(document, pos, make_handler(h, context, parseSignatureHelp)); +} LSPClientServer::RequestHandle -LSPClientServer::documentFormatting(const QUrl & document, const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h) -{ return d->documentFormatting(document, options, make_handler(h, context, parseTextEdit)); } +LSPClientServer::documentFormatting(const QUrl &document, const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h) +{ + return d->documentFormatting(document, options, make_handler(h, context, parseTextEdit)); +} LSPClientServer::RequestHandle -LSPClientServer::documentRangeFormatting(const QUrl & document, const LSPRange & range, - const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h) -{ return d->documentRangeFormatting(document, range, options, make_handler(h, context, parseTextEdit)); } +LSPClientServer::documentRangeFormatting(const QUrl &document, const LSPRange &range, + const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h) +{ + return d->documentRangeFormatting(document, range, options, + make_handler(h, context, parseTextEdit)); +} LSPClientServer::RequestHandle -LSPClientServer::documentOnTypeFormatting(const QUrl & document, const LSPPosition & pos, - const QChar lastChar, const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h) -{ return d->documentOnTypeFormatting(document, pos, lastChar, options, make_handler(h, context, parseTextEdit)); } +LSPClientServer::documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, + const QChar lastChar, const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h) +{ + return d->documentOnTypeFormatting(document, pos, lastChar, options, + make_handler(h, context, parseTextEdit)); +} LSPClientServer::RequestHandle -LSPClientServer::documentRename(const QUrl & document, const LSPPosition & pos, - const QString newName, - const QObject *context, const WorkspaceEditReplyHandler & h) -{ return d->documentRename(document, pos, newName, make_handler(h, context, parseWorkSpaceEdit)); } +LSPClientServer::documentRename(const QUrl &document, const LSPPosition &pos, const QString newName, + const QObject *context, const WorkspaceEditReplyHandler &h) +{ + return d->documentRename(document, pos, newName, make_handler(h, context, parseWorkSpaceEdit)); +} LSPClientServer::RequestHandle -LSPClientServer::documentCodeAction(const QUrl & document, const LSPRange & range, - const QList & kinds, QList diagnostics, - const QObject *context, const CodeActionReplyHandler & h) -{ return d->documentCodeAction(document, range, kinds, diagnostics, make_handler(h, context, parseCodeAction)); } +LSPClientServer::documentCodeAction(const QUrl &document, const LSPRange &range, + const QList &kinds, QList diagnostics, + const QObject *context, const CodeActionReplyHandler &h) +{ + return d->documentCodeAction(document, range, kinds, diagnostics, + make_handler(h, context, parseCodeAction)); +} -void LSPClientServer::executeCommand(const QString & command, const QJsonValue & args) -{ return d->executeCommand(command, args); } +void LSPClientServer::executeCommand(const QString &command, const QJsonValue &args) +{ + return d->executeCommand(command, args); +} -void LSPClientServer::didOpen(const QUrl & document, int version, const QString & langId, const QString & text) -{ return d->didOpen(document, version, langId, text); } +void LSPClientServer::didOpen(const QUrl &document, int version, const QString &langId, + const QString &text) +{ + return d->didOpen(document, version, langId, text); +} -void LSPClientServer::didChange(const QUrl & document, int version, const QString & text, - const QList & changes) -{ return d->didChange(document, version, text, changes); } +void LSPClientServer::didChange(const QUrl &document, int version, const QString &text, + const QList &changes) +{ + return d->didChange(document, version, text, changes); +} -void LSPClientServer::didSave(const QUrl & document, const QString & text) -{ return d->didSave(document, text); } +void LSPClientServer::didSave(const QUrl &document, const QString &text) +{ + return d->didSave(document, text); +} -void LSPClientServer::didClose(const QUrl & document) -{ return d->didClose(document); } +void LSPClientServer::didClose(const QUrl &document) +{ + return d->didClose(document); +} diff --git a/addons/lspclient/lspclientserver.h b/addons/lspclient/lspclientserver.h index d2dccd1d3..bcb843229 100644 --- a/addons/lspclient/lspclientserver.h +++ b/addons/lspclient/lspclientserver.h @@ -1,187 +1,182 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTSERVER_H #define LSPCLIENTSERVER_H #include "lspclientprotocol.h" #include #include #include #include #include #include #include #include -namespace utils -{ +namespace utils { // template helper // function bind helpers template -inline std::function -mem_fun(R (T::*pm)(Args ...), Tp object) +inline std::function mem_fun(R (T::*pm)(Args...), Tp object) { - return [object, pm](Args... args) { - return (object->*pm)(std::forward(args)...); - }; + return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } template -inline std::function -mem_fun(R (T::*pm)(Args ...) const, Tp object) +inline std::function mem_fun(R (T::*pm)(Args...) const, Tp object) { - return [object, pm](Args... args) { - return (object->*pm)(std::forward(args)...); - }; + return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } // prevent argument deduction -template struct identity { typedef T type; }; +template +struct identity { + typedef T type; +}; } // namespace utils - static const int TIMEOUT_SHUTDOWN = 200; template using ReplyHandler = std::function; using DocumentSymbolsReplyHandler = ReplyHandler>; using DocumentDefinitionReplyHandler = ReplyHandler>; using DocumentHighlightReplyHandler = ReplyHandler>; using DocumentHoverReplyHandler = ReplyHandler; using DocumentCompletionReplyHandler = ReplyHandler>; using SignatureHelpReplyHandler = ReplyHandler; using FormattingReplyHandler = ReplyHandler>; using CodeActionReplyHandler = ReplyHandler>; using WorkspaceEditReplyHandler = ReplyHandler; using ApplyEditReplyHandler = ReplyHandler; class LSPClientServer : public QObject { Q_OBJECT public: - enum class State - { - None, - Started, - Running, - Shutdown - }; + enum class State { None, Started, Running, Shutdown }; class LSPClientServerPrivate; class RequestHandle { friend class LSPClientServerPrivate; QPointer m_server; int m_id = -1; + public: - RequestHandle& cancel() + RequestHandle &cancel() { if (m_server) m_server->cancel(m_id); return *this; } }; - LSPClientServer(const QStringList & server, const QUrl & root, - const QJsonValue & init = QJsonValue()); + LSPClientServer(const QStringList &server, const QUrl &root, + const QJsonValue &init = QJsonValue()); ~LSPClientServer(); // server management // request start bool start(); // request shutdown/stop // if to_xxx >= 0 -> send signal if not exit'ed after timeout void stop(int to_term_ms, int to_kill_ms); int cancel(int id); // properties - const QStringList& cmdline() const; + const QStringList &cmdline() const; State state() const; - Q_SIGNAL void stateChanged(LSPClientServer * server); + Q_SIGNAL void stateChanged(LSPClientServer *server); - const LSPServerCapabilities& capabilities() const; + const LSPServerCapabilities &capabilities() const; // language - RequestHandle documentSymbols(const QUrl & document, const QObject *context, - const DocumentSymbolsReplyHandler & h); - RequestHandle documentDefinition(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentDefinitionReplyHandler & h); - RequestHandle documentDeclaration(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentDefinitionReplyHandler & h); - RequestHandle documentHighlight(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentHighlightReplyHandler & h); - RequestHandle documentHover(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentHoverReplyHandler & h); - RequestHandle documentReferences(const QUrl & document, const LSPPosition & pos, bool decl, - const QObject *context, const DocumentDefinitionReplyHandler & h); - RequestHandle documentCompletion(const QUrl & document, const LSPPosition & pos, - const QObject *context, const DocumentCompletionReplyHandler & h); - RequestHandle signatureHelp(const QUrl & document, const LSPPosition & pos, - const QObject *context, const SignatureHelpReplyHandler & h); - - RequestHandle documentFormatting(const QUrl & document, const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h); - RequestHandle documentRangeFormatting(const QUrl & document, const LSPRange & range, - const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h); - RequestHandle documentOnTypeFormatting(const QUrl & document, const LSPPosition & pos, - QChar lastChar, const LSPFormattingOptions & options, - const QObject *context, const FormattingReplyHandler & h); - RequestHandle documentRename(const QUrl & document, const LSPPosition & pos, - const QString newName, - const QObject *context, const WorkspaceEditReplyHandler & h); - - RequestHandle documentCodeAction(const QUrl & document, const LSPRange & range, - const QList & kinds, QList diagnostics, - const QObject *context, const CodeActionReplyHandler & h); - void executeCommand(const QString & command, const QJsonValue & args); + RequestHandle documentSymbols(const QUrl &document, const QObject *context, + const DocumentSymbolsReplyHandler &h); + RequestHandle documentDefinition(const QUrl &document, const LSPPosition &pos, + const QObject *context, + const DocumentDefinitionReplyHandler &h); + RequestHandle documentDeclaration(const QUrl &document, const LSPPosition &pos, + const QObject *context, + const DocumentDefinitionReplyHandler &h); + RequestHandle documentHighlight(const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentHighlightReplyHandler &h); + RequestHandle documentHover(const QUrl &document, const LSPPosition &pos, + const QObject *context, const DocumentHoverReplyHandler &h); + RequestHandle documentReferences(const QUrl &document, const LSPPosition &pos, bool decl, + const QObject *context, + const DocumentDefinitionReplyHandler &h); + RequestHandle documentCompletion(const QUrl &document, const LSPPosition &pos, + const QObject *context, + const DocumentCompletionReplyHandler &h); + RequestHandle signatureHelp(const QUrl &document, const LSPPosition &pos, + const QObject *context, const SignatureHelpReplyHandler &h); + + RequestHandle documentFormatting(const QUrl &document, const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h); + RequestHandle documentRangeFormatting(const QUrl &document, const LSPRange &range, + const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h); + RequestHandle documentOnTypeFormatting(const QUrl &document, const LSPPosition &pos, + QChar lastChar, const LSPFormattingOptions &options, + const QObject *context, const FormattingReplyHandler &h); + RequestHandle documentRename(const QUrl &document, const LSPPosition &pos, + const QString newName, const QObject *context, + const WorkspaceEditReplyHandler &h); + + RequestHandle documentCodeAction(const QUrl &document, const LSPRange &range, + const QList &kinds, QList diagnostics, + const QObject *context, const CodeActionReplyHandler &h); + void executeCommand(const QString &command, const QJsonValue &args); // sync - void didOpen(const QUrl & document, int version, const QString & langId, const QString & text); + void didOpen(const QUrl &document, int version, const QString &langId, const QString &text); // only 1 of text or changes should be non-empty and is considered - void didChange(const QUrl & document, int version, const QString & text, - const QList & changes = {}); - void didSave(const QUrl & document, const QString & text); - void didClose(const QUrl & document); + void didChange(const QUrl &document, int version, const QString &text, + const QList &changes = {}); + void didSave(const QUrl &document, const QString &text); + void didClose(const QUrl &document); // notifcation = signal Q_SIGNALS: - void publishDiagnostics(const LSPPublishDiagnosticsParams & ); + void publishDiagnostics(const LSPPublishDiagnosticsParams &); // request = signal - void applyEdit(const LSPApplyWorkspaceEditParams &req, const ApplyEditReplyHandler &h, bool &handled); + void applyEdit(const LSPApplyWorkspaceEditParams &req, const ApplyEditReplyHandler &h, + bool &handled); private: // pimpl data holder - LSPClientServerPrivate * const d; + LSPClientServerPrivate *const d; }; #endif diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp index 5769126b2..29118dbb3 100644 --- a/addons/lspclient/lspclientservermanager.cpp +++ b/addons/lspclient/lspclientservermanager.cpp @@ -1,784 +1,808 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ /* * Some explanation here on server configuration JSON, pending such ending up * in real user level documentation ... * * The default configuration in JSON format is roughly as follows; { "global": { "root": null, }, "servers": { "Python": { "command": "python3 -m pyls --check-parent-process" }, "C": { "command": "clangd -log=verbose --background-index" }, "C++": { "use": "C++" } } } * (the "command" can be an array or a string, which is then split into array) * * From the above, the gist is presumably clear. In addition, each server * entry object may also have an "initializationOptions" entry, which is passed * along to the server as part of the 'initialize' method. A clangd-specific * HACK^Hfeature uses this to add "compilationDatabasePath". * * Various stages of override/merge are applied; * + user configuration (loaded from file) overrides (internal) default configuration * + "lspclient" entry in projectMap overrides the above * + the resulting "global" entry is used to supplement (not override) any server entry * * One server instance is used per (root, servertype) combination. * If "root" is not specified, it default to the $HOME directory. If it is * specified as an absolute path, then it used as-is, otherwise it is relative * to the projectBase. For any document, the resulting "root" then determines * whether or not a separate instance is needed. If so, the "root" is passed * as rootUri/rootPath. * * In general, it is recommended to leave root unspecified, as it is not that * important for a server (your mileage may vary though). Fewer instances * are obviously more efficient, and they also have a 'wider' view than * the view of many separate instances. */ #include "lspclientservermanager.h" #include "lspclient_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // helper to find a proper root dir for the given document & file name that indicate the root dir -static QString rootForDocumentAndRootIndicationFileName(KTextEditor::Document *document, const QString &rootIndicationFileName) +static QString rootForDocumentAndRootIndicationFileName(KTextEditor::Document *document, + const QString &rootIndicationFileName) { // search only feasible if document is local file if (!document->url().isLocalFile()) { return QString(); } // search root upwards QDir dir(QFileInfo(document->url().toLocalFile()).absolutePath()); QSet seenDirectories; while (!seenDirectories.contains(dir.absolutePath())) { // update guard seenDirectories.insert(dir.absolutePath()); // the file that indicates the root dir is there => all fine if (dir.exists(rootIndicationFileName)) { return dir.absolutePath(); } // else: cd up, if possible or abort if (!dir.cdUp()) { break; } } // no root found, bad luck return QString(); } #include // local helper; // recursively merge top json top onto bottom json -static QJsonObject -merge(const QJsonObject & bottom, const QJsonObject & top) +static QJsonObject merge(const QJsonObject &bottom, const QJsonObject &top) { QJsonObject result; for (auto item = top.begin(); item != top.end(); item++) { - const auto & key = item.key(); - if (item.value().isObject()) { + const auto &key = item.key(); + if (item.value().isObject()) { result.insert(key, merge(bottom.value(key).toObject(), item.value().toObject())); } else { result.insert(key, item.value()); } } // parts only in bottom for (auto item = bottom.begin(); item != bottom.end(); item++) { if (!result.contains(item.key())) { result.insert(item.key(), item.value()); } } return result; } // map (highlight)mode to lsp languageId -static QString -languageId(const QString & mode) +static QString languageId(const QString &mode) { // special cases static QHash m; auto it = m.find(mode); if (it != m.end()) { return *it; } // assume sane naming QString result = mode.toLower(); result = result.replace(QStringLiteral("++"), QStringLiteral("pp")); return result.replace(QStringLiteral("#"), QStringLiteral("sharp")); } // helper guard to handle revision (un)lock -struct RevisionGuard -{ +struct RevisionGuard { QPointer m_doc; KTextEditor::MovingInterface *m_movingInterface = nullptr; qint64 m_revision = -1; - RevisionGuard(KTextEditor::Document *doc = nullptr) : - m_doc(doc), - m_movingInterface(qobject_cast(doc)), - m_revision(-1) + RevisionGuard(KTextEditor::Document *doc = nullptr) + : m_doc(doc), + m_movingInterface(qobject_cast(doc)), + m_revision(-1) { if (m_movingInterface) { m_revision = m_movingInterface->revision(); m_movingInterface->lockRevision(m_revision); } } // really only need/allow this one (out of 5) - RevisionGuard(RevisionGuard && other) : RevisionGuard(nullptr) + RevisionGuard(RevisionGuard &&other) : RevisionGuard(nullptr) { std::swap(m_doc, other.m_doc); std::swap(m_movingInterface, other.m_movingInterface); std::swap(m_revision, other.m_revision); } void release() { m_movingInterface = nullptr; m_revision = -1; } ~RevisionGuard() { // NOTE: hopefully the revision is still valid at this time if (m_doc && m_movingInterface && m_revision >= 0) { m_movingInterface->unlockRevision(m_revision); } } }; - class LSPClientRevisionSnapshotImpl : public LSPClientRevisionSnapshot { Q_OBJECT typedef LSPClientRevisionSnapshotImpl self_type; // std::map has more relaxed constraints on value_type std::map m_guards; Q_SLOT void clearRevisions(KTextEditor::Document *doc) { - for (auto &item: m_guards) { + for (auto &item : m_guards) { if (item.second.m_doc == doc) { item.second.release(); } } } public: void add(KTextEditor::Document *doc) { Q_ASSERT(doc); // make sure revision is cleared when needed and no longer used (to unlock or otherwise) // see e.g. implementation in katetexthistory.cpp and assert's in place there - auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), - this, SLOT(clearRevisions(KTextEditor::Document*))); + auto conn = connect( + doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, + SLOT(clearRevisions(KTextEditor::Document *))); Q_ASSERT(conn); - conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), - this, SLOT(clearRevisions(KTextEditor::Document*))); + conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), + this, SLOT(clearRevisions(KTextEditor::Document *))); Q_ASSERT(conn); m_guards.emplace(doc->url(), doc); } - void find(const QUrl & url, KTextEditor::MovingInterface* & miface, qint64 & revision) const override + void find(const QUrl &url, KTextEditor::MovingInterface *&miface, + qint64 &revision) const override { auto it = m_guards.find(url); if (it != m_guards.end()) { miface = it->second.m_movingInterface; revision = it->second.m_revision; } else { miface = nullptr; revision = -1; } } }; // helper class to sync document changes to LSP server class LSPClientServerManagerImpl : public LSPClientServerManager { Q_OBJECT typedef LSPClientServerManagerImpl self_type; - struct DocumentInfo - { + struct DocumentInfo { QSharedPointer server; KTextEditor::MovingInterface *movingInterface; QUrl url; qint64 version; - bool open:1; - bool modified:1; + bool open : 1; + bool modified : 1; // used for incremental update (if non-empty) QList changes; }; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; // merged default and user config QJsonObject m_serverConfig; // root -> (mode -> server) QMap>> m_servers; - QHash m_docs; + QHash m_docs; bool m_incrementalSync = false; typedef QVector> ServerList; public: LSPClientServerManagerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) - : m_plugin(plugin) , m_mainWindow(mainWin) + : m_plugin(plugin), m_mainWindow(mainWin) { connect(plugin, &LSPClientPlugin::update, this, &self_type::updateServerConfig); QTimer::singleShot(100, this, &self_type::updateServerConfig); } ~LSPClientServerManagerImpl() { // stop everything as we go down // several stages; // stage 1; request shutdown of all servers (in parallel) // (give that some time) // stage 2; send TERM // stage 3; send KILL // stage 1 QEventLoop q; QTimer t; connect(&t, &QTimer::timeout, &q, &QEventLoop::quit); - auto run = [&q, &t] (int ms) { + auto run = [&q, &t](int ms) { t.setSingleShot(true); t.start(ms); q.exec(); }; int count = 0; - for (const auto & el: m_servers) { - for (const auto & s: el) { + for (const auto &el : m_servers) { + for (const auto &s : el) { disconnect(s.data(), nullptr, this, nullptr); if (s->state() != LSPClientServer::State::None) { - auto handler = [&q, &count, s] () { + auto handler = [&q, &count, s]() { if (s->state() != LSPClientServer::State::None) { if (--count == 0) { q.quit(); } } }; connect(s.data(), &LSPClientServer::stateChanged, this, handler); ++count; s->stop(-1, -1); } } } run(500); // stage 2 and 3 count = 0; for (count = 0; count < 2; ++count) { - for (const auto & el: m_servers) { - for (const auto & s: el) { + for (const auto &el : m_servers) { + for (const auto &s : el) { s->stop(count == 0 ? 1 : -1, count == 0 ? -1 : 1); } } run(100); } } - void setIncrementalSync(bool inc) override - { m_incrementalSync = inc; } + void setIncrementalSync(bool inc) override { m_incrementalSync = inc; } - QSharedPointer - findServer(KTextEditor::Document *document, bool updatedoc = true) override + QSharedPointer findServer(KTextEditor::Document *document, + bool updatedoc = true) override { if (!document || document->url().isEmpty()) return nullptr; auto it = m_docs.find(document); auto server = it != m_docs.end() ? it->server : nullptr; if (!server) { if ((server = _findServer(document))) trackDocument(document, server); } if (server && updatedoc) update(server.data(), false); return server; } - QSharedPointer - findServer(KTextEditor::View *view, bool updatedoc = true) override - { return view ? findServer(view->document(), updatedoc) : nullptr; } + QSharedPointer findServer(KTextEditor::View *view, + bool updatedoc = true) override + { + return view ? findServer(view->document(), updatedoc) : nullptr; + } // restart a specific server or all servers if server == nullptr - void restart(LSPClientServer * server) override + void restart(LSPClientServer *server) override { ServerList servers; // find entry for server(s) and move out - for (auto & m : m_servers) { - for (auto it = m.begin() ; it != m.end(); ) { + for (auto &m : m_servers) { + for (auto it = m.begin(); it != m.end();) { if (!server || it->data() == server) { servers.push_back(*it); it = m.erase(it); } else { ++it; } } } restart(servers); } virtual qint64 revision(KTextEditor::Document *doc) override { auto it = m_docs.find(doc); return it != m_docs.end() ? it->version : -1; } - virtual LSPClientRevisionSnapshot* snapshot(LSPClientServer *server) override + virtual LSPClientRevisionSnapshot *snapshot(LSPClientServer *server) override { auto result = new LSPClientRevisionSnapshotImpl; for (auto it = m_docs.begin(); it != m_docs.end(); ++it) { if (it->server == server) { // sync server to latest revision that will be recorded update(it.key(), false); result->add(it.key()); } } return result; } private: void showMessage(const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *view = m_mainWindow->activeView(); - if (!view || !view->document()) return; + if (!view || !view->document()) + return; auto kmsg = new KTextEditor::Message(xi18nc("@info", "LSP Client: %1", msg), level); kmsg->setPosition(KTextEditor::Message::AboveView); kmsg->setAutoHide(5000); kmsg->setAutoHideMode(KTextEditor::Message::Immediate); kmsg->setView(view); view->document()->postMessage(kmsg); } // caller ensures that servers are no longer present in m_servers - void restart(const ServerList & servers) + void restart(const ServerList &servers) { // close docs - for (const auto & server : servers) { + for (const auto &server : servers) { // controlling server here, so disable usual state tracking response disconnect(server.data(), nullptr, this, nullptr); - for (auto it = m_docs.begin(); it != m_docs.end(); ) { + for (auto it = m_docs.begin(); it != m_docs.end();) { auto &item = it.value(); if (item.server == server) { // no need to close if server not in proper state if (server->state() != LSPClientServer::State::Running) { item.open = false; } it = _close(it, true); } else { ++it; } } } // helper captures servers - auto stopservers = [servers] (int t, int k) { - for (const auto & server : servers) { + auto stopservers = [servers](int t, int k) { + for (const auto &server : servers) { server->stop(t, k); } }; // trigger server shutdown now stopservers(-1, -1); // initiate delayed stages (TERM and KILL) // async, so give a bit more time - QTimer::singleShot(2 * TIMEOUT_SHUTDOWN, this, [stopservers] () { stopservers(1, -1); }); - QTimer::singleShot(4 * TIMEOUT_SHUTDOWN, this, [stopservers] () { stopservers(-1, 1); }); + QTimer::singleShot(2 * TIMEOUT_SHUTDOWN, this, [stopservers]() { stopservers(1, -1); }); + QTimer::singleShot(4 * TIMEOUT_SHUTDOWN, this, [stopservers]() { stopservers(-1, 1); }); // as for the start part // trigger interested parties, which will again request a server as needed // let's delay this; less chance for server instances to trip over each other - QTimer::singleShot(6 * TIMEOUT_SHUTDOWN, this, [this] () { emit serverChanged(); }); + QTimer::singleShot(6 * TIMEOUT_SHUTDOWN, this, [this]() { emit serverChanged(); }); } void onStateChanged(LSPClientServer *server) { if (server->state() == LSPClientServer::State::Running) { // clear for normal operation emit serverChanged(); } else if (server->state() == LSPClientServer::State::None) { // went down - showMessage(i18n("Server terminated unexpectedly: %1", server->cmdline().join(QLatin1Char(' '))), - KTextEditor::Message::Warning); + showMessage(i18n("Server terminated unexpectedly: %1", + server->cmdline().join(QLatin1Char(' '))), + KTextEditor::Message::Warning); restart(server); } } - QSharedPointer - _findServer(KTextEditor::Document *document) + QSharedPointer _findServer(KTextEditor::Document *document) { QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); - const auto projectBase = QDir(projectView ? projectView->property("projectBaseDir").toString() : QString()); - const auto& projectMap = projectView ? projectView->property("projectMap").toMap() : QVariantMap(); + const auto projectBase = + QDir(projectView ? projectView->property("projectBaseDir").toString() : QString()); + const auto &projectMap = + projectView ? projectView->property("projectMap").toMap() : QVariantMap(); // compute the LSP standardized language id auto langId = languageId(document->highlightingMode()); // merge with project specific - auto projectConfig = QJsonDocument::fromVariant(projectMap).object().value(QStringLiteral("lspclient")).toObject(); + auto projectConfig = QJsonDocument::fromVariant(projectMap) + .object() + .value(QStringLiteral("lspclient")) + .toObject(); auto serverConfig = merge(m_serverConfig, projectConfig); // locate server config QJsonValue config; QSet used; while (true) { qCInfo(LSPCLIENT) << "language id " << langId; used << langId; config = serverConfig.value(QStringLiteral("servers")).toObject().value(langId); if (config.isObject()) { - const auto & base = config.toObject().value(QStringLiteral("use")).toString(); + const auto &base = config.toObject().value(QStringLiteral("use")).toString(); // basic cycle detection if (!base.isEmpty() && !used.contains(base)) { langId = base; continue; } } break; } if (!config.isObject()) return nullptr; // merge global settings - serverConfig = merge(serverConfig.value(QStringLiteral("global")).toObject(), config.toObject()); + serverConfig = + merge(serverConfig.value(QStringLiteral("global")).toObject(), config.toObject()); QString rootpath; auto rootv = serverConfig.value(QStringLiteral("root")); if (rootv.isString()) { auto sroot = rootv.toString(); if (QDir::isAbsolutePath(sroot)) { rootpath = sroot; } else if (!projectBase.isEmpty()) { rootpath = QDir(projectBase).absoluteFilePath(sroot); } } /** * no explicit set root dir? search for a matching root based on some name filters - * this is required for some LSP servers like rls that don't handle that on their own like clangd does + * this is required for some LSP servers like rls that don't handle that on their own like + * clangd does */ if (rootpath.isEmpty()) { - const auto fileNamesForDetection = serverConfig.value(QStringLiteral("rootIndicationFileNames")); + const auto fileNamesForDetection = + serverConfig.value(QStringLiteral("rootIndicationFileNames")); if (fileNamesForDetection.isArray()) { // we try each file name alternative in the listed order // this allows to have preferences for (auto name : fileNamesForDetection.toArray()) { if (name.isString()) { - rootpath = rootForDocumentAndRootIndicationFileName(document, name.toString()); + rootpath = + rootForDocumentAndRootIndicationFileName(document, name.toString()); if (!rootpath.isEmpty()) { break; } } } } } // last fallback: home directory if (rootpath.isEmpty()) { rootpath = QDir::homePath(); } auto root = QUrl::fromLocalFile(rootpath); auto server = m_servers.value(root).value(langId); if (!server) { QStringList cmdline; // choose debug command line for debug mode, fallback to command - auto vcmdline = serverConfig.value(m_plugin->m_debugMode ? QStringLiteral("commandDebug") : QStringLiteral("command")); + auto vcmdline = + serverConfig.value(m_plugin->m_debugMode ? QStringLiteral("commandDebug") + : QStringLiteral("command")); if (vcmdline.isUndefined()) { vcmdline = serverConfig.value(QStringLiteral("command")); } auto scmdline = vcmdline.toString(); if (!scmdline.isEmpty()) { cmdline = scmdline.split(QLatin1Char(' ')); } else { - for (const auto& c : vcmdline.toArray()) { + for (const auto &c : vcmdline.toArray()) { cmdline.push_back(c.toString()); } } if (cmdline.length() > 0) { - server.reset(new LSPClientServer(cmdline, root, serverConfig.value(QStringLiteral("initializationOptions")))); + server.reset(new LSPClientServer( + cmdline, root, + serverConfig.value(QStringLiteral("initializationOptions")))); m_servers[root][langId] = server; - connect(server.data(), &LSPClientServer::stateChanged, - this, &self_type::onStateChanged, Qt::UniqueConnection); + connect(server.data(), &LSPClientServer::stateChanged, this, + &self_type::onStateChanged, Qt::UniqueConnection); if (!server->start()) { showMessage(i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' '))), - KTextEditor::Message::Error); + KTextEditor::Message::Error); } } } return (server && server->state() == LSPClientServer::State::Running) ? server : nullptr; } void updateServerConfig() { // default configuration, compiled into plugin resource, reading can't fail QFile defaultConfigFile(QStringLiteral(":/lspclient/settings.json")); defaultConfigFile.open(QIODevice::ReadOnly); Q_ASSERT(defaultConfigFile.isOpen()); m_serverConfig = QJsonDocument::fromJson(defaultConfigFile.readAll()).object(); // consider specified configuration - const auto& configPath = m_plugin->m_configPath.path(); + const auto &configPath = m_plugin->m_configPath.path(); if (!configPath.isEmpty()) { QFile f(configPath); if (f.open(QIODevice::ReadOnly)) { auto data = f.readAll(); auto json = QJsonDocument::fromJson(data); if (json.isObject()) { m_serverConfig = merge(m_serverConfig, json.object()); } else { showMessage(i18n("Failed to parse server configuration: %1", configPath), - KTextEditor::Message::Error); + KTextEditor::Message::Error); } } else { showMessage(i18n("Failed to read server configuration: %1", configPath), - KTextEditor::Message::Error); + KTextEditor::Message::Error); } } // we could (but do not) perform restartAll here; // for now let's leave that up to user // but maybe we do have a server now where not before, so let's signal emit serverChanged(); } void trackDocument(KTextEditor::Document *doc, QSharedPointer server) { auto it = m_docs.find(doc); if (it == m_docs.end()) { - KTextEditor::MovingInterface* miface = qobject_cast(doc); - it = m_docs.insert(doc, {server, miface, doc->url(), 0, false, false, {}}); + KTextEditor::MovingInterface *miface = + qobject_cast(doc); + it = m_docs.insert(doc, { server, miface, doc->url(), 0, false, false, {} }); // track document - connect(doc, &KTextEditor::Document::documentUrlChanged, this, &self_type::untrack, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::highlightingModeChanged, this, &self_type::untrack, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::aboutToClose, this, &self_type::untrack, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::destroyed, this, &self_type::untrack, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::documentUrlChanged, this, &self_type::untrack, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::highlightingModeChanged, this, &self_type::untrack, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::aboutToClose, this, &self_type::untrack, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::destroyed, this, &self_type::untrack, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, + Qt::UniqueConnection); // in case of incremental change - connect(doc, &KTextEditor::Document::textInserted, this, &self_type::onTextInserted, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::textRemoved, this, &self_type::onTextRemoved, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::lineWrapped, this, &self_type::onLineWrapped, Qt::UniqueConnection); - connect(doc, &KTextEditor::Document::lineUnwrapped, this, &self_type::onLineUnwrapped, Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::textInserted, this, &self_type::onTextInserted, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::textRemoved, this, &self_type::onTextRemoved, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::lineWrapped, this, &self_type::onLineWrapped, + Qt::UniqueConnection); + connect(doc, &KTextEditor::Document::lineUnwrapped, this, &self_type::onLineUnwrapped, + Qt::UniqueConnection); } else { it->server = server; } } - decltype(m_docs)::iterator - _close(decltype(m_docs)::iterator it, bool remove) + decltype(m_docs)::iterator _close(decltype(m_docs)::iterator it, bool remove) { if (it != m_docs.end()) { if (it->open) { // release server side (use url as registered with) (it->server)->didClose(it->url); it->open = false; } if (remove) { disconnect(it.key(), nullptr, this, nullptr); it = m_docs.erase(it); } } return it; } void _close(KTextEditor::Document *doc, bool remove) { auto it = m_docs.find(doc); if (it != m_docs.end()) { _close(it, remove); } } void untrack(QObject *doc) { - _close(qobject_cast(doc), true); + _close(qobject_cast(doc), true); emit serverChanged(); } - void close(KTextEditor::Document *doc) - { _close(doc, false); } + void close(KTextEditor::Document *doc) { _close(doc, false); } - void update(const decltype(m_docs)::iterator & it, bool force) + void update(const decltype(m_docs)::iterator &it, bool force) { auto doc = it.key(); if (it != m_docs.end() && it->server) { if (it->movingInterface) { it->version = it->movingInterface->revision(); } else if (it->modified) { ++it->version; } if (!m_incrementalSync) { it->changes.clear(); } if (it->open) { if (it->modified || force) { - (it->server)->didChange(it->url, it->version, - (it->changes.size() == 0) ? doc->text() : QString(), - it->changes); + (it->server) + ->didChange(it->url, it->version, + (it->changes.size() == 0) ? doc->text() : QString(), + it->changes); } } else { - (it->server)->didOpen(it->url, it->version, languageId(doc->highlightingMode()), doc->text()); + (it->server) + ->didOpen(it->url, it->version, languageId(doc->highlightingMode()), + doc->text()); it->open = true; } it->modified = false; it->changes.clear(); } } void update(KTextEditor::Document *doc, bool force) override { update(m_docs.find(doc), force); } - void update(LSPClientServer * server, bool force) + void update(LSPClientServer *server, bool force) { for (auto it = m_docs.begin(); it != m_docs.end(); ++it) { if (it->server == server) { update(it, force); } } } void onTextChanged(KTextEditor::Document *doc) { auto it = m_docs.find(doc); if (it != m_docs.end()) { it->modified = true; } } - DocumentInfo* - getDocumentInfo(KTextEditor::Document *doc) + DocumentInfo *getDocumentInfo(KTextEditor::Document *doc) { if (!m_incrementalSync) return nullptr; auto it = m_docs.find(doc); if (it != m_docs.end() && it->server) { - const auto& caps = it->server->capabilities(); + const auto &caps = it->server->capabilities(); if (caps.textDocumentSync == LSPDocumentSyncKind::Incremental) { return &(*it); } } return nullptr; } - void onTextInserted(KTextEditor::Document *doc, const KTextEditor::Cursor &position, const QString &text) + void onTextInserted(KTextEditor::Document *doc, const KTextEditor::Cursor &position, + const QString &text) { auto info = getDocumentInfo(doc); if (info) { - info->changes.push_back({LSPRange{position, position}, text}); + info->changes.push_back({ LSPRange { position, position }, text }); } } - void onTextRemoved(KTextEditor::Document *doc, const KTextEditor::Range &range, const QString &text) + void onTextRemoved(KTextEditor::Document *doc, const KTextEditor::Range &range, + const QString &text) { (void)text; auto info = getDocumentInfo(doc); if (info) { - info->changes.push_back({range, QString()}); + info->changes.push_back({ range, QString() }); } } void onLineWrapped(KTextEditor::Document *doc, const KTextEditor::Cursor &position) { // so a 'newline' has been inserted at position // could have been UNIX style or other kind, let's ask the document - auto text = doc->text({position, {position.line() + 1, 0}}); + auto text = doc->text({ position, { position.line() + 1, 0 } }); onTextInserted(doc, position, text); } void onLineUnwrapped(KTextEditor::Document *doc, int line) { // lines line-1 and line got replaced by current content of line-1 Q_ASSERT(line > 0); auto info = getDocumentInfo(doc); if (info) { - LSPRange oldrange {{line - 1, 0}, {line + 1, 0}}; - LSPRange newrange {{line - 1, 0}, {line, 0}}; + LSPRange oldrange { { line - 1, 0 }, { line + 1, 0 } }; + LSPRange newrange { { line - 1, 0 }, { line, 0 } }; auto text = doc->text(newrange); - info->changes.push_back({oldrange, text}); + info->changes.push_back({ oldrange, text }); } } - }; QSharedPointer LSPClientServerManager::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) { return QSharedPointer(new LSPClientServerManagerImpl(plugin, mainWin)); } #include "lspclientservermanager.moc" diff --git a/addons/lspclient/lspclientservermanager.h b/addons/lspclient/lspclientservermanager.h index 5925c8157..e7ac71ba2 100644 --- a/addons/lspclient/lspclientservermanager.h +++ b/addons/lspclient/lspclientservermanager.h @@ -1,93 +1,94 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts 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. */ #ifndef LSPCLIENTSERVERMANAGER_H #define LSPCLIENTSERVERMANAGER_H #include "lspclientserver.h" #include "lspclientplugin.h" #include namespace KTextEditor { - class MainWindow; - class Document; - class View; - class MovingInterface; +class MainWindow; +class Document; +class View; +class MovingInterface; } class LSPClientRevisionSnapshot; /* * A helper class that manages LSP servers in relation to a KTextDocument. * That is, spin up a server for a document when so requested, and then * monitor when the server is up (or goes down) and the document (for changes). * Those changes may then be synced to the server when so requested (e.g. prior * to another component performing an LSP request for a document). * So, other than managing servers, it also manages the document-server * relationship (and document), what's in a name ... */ class LSPClientServerManager : public QObject { Q_OBJECT public: // factory method; private implementation by interface - static QSharedPointer - new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin); + static QSharedPointer new_(LSPClientPlugin *plugin, + KTextEditor::MainWindow *mainWin); - virtual QSharedPointer - findServer(KTextEditor::Document *document, bool updatedoc = true) = 0; + virtual QSharedPointer findServer(KTextEditor::Document *document, + bool updatedoc = true) = 0; - virtual QSharedPointer - findServer(KTextEditor::View *view, bool updatedoc = true) = 0; + virtual QSharedPointer findServer(KTextEditor::View *view, + bool updatedoc = true) = 0; virtual void update(KTextEditor::Document *doc, bool force) = 0; virtual void restart(LSPClientServer *server) = 0; virtual void setIncrementalSync(bool inc) = 0; // latest sync'ed revision of doc (-1 if N/A) virtual qint64 revision(KTextEditor::Document *doc) = 0; // lock all relevant documents' current revision and sync that to server // locks are released when returned snapshot is delete'd - virtual LSPClientRevisionSnapshot* snapshot(LSPClientServer *server) = 0; + virtual LSPClientRevisionSnapshot *snapshot(LSPClientServer *server) = 0; public: Q_SIGNALS: void serverChanged(); }; class LSPClientRevisionSnapshot : public QObject { Q_OBJECT public: // find a locked revision for url in snapshot - virtual void find(const QUrl & url, KTextEditor::MovingInterface* & miface, qint64 & revision) const = 0; + virtual void find(const QUrl &url, KTextEditor::MovingInterface *&miface, + qint64 &revision) const = 0; }; #endif diff --git a/addons/lspclient/lspclientsymbolview.cpp b/addons/lspclient/lspclientsymbolview.cpp index 9fe236d32..020f0cd7e 100644 --- a/addons/lspclient/lspclientsymbolview.cpp +++ b/addons/lspclient/lspclientsymbolview.cpp @@ -1,544 +1,554 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Copyright (C) 2019 Christoph Cullmann 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. */ #include "lspclientsymbolview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class LSPClientViewTrackerImpl : public LSPClientViewTracker { Q_OBJECT typedef LSPClientViewTrackerImpl self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; // timers to delay some todo's QTimer m_changeTimer; int m_change; QTimer m_motionTimer; int m_motion; int m_oldCursorLine = -1; public: - LSPClientViewTrackerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, int change_ms, int motion_ms) + LSPClientViewTrackerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, + int change_ms, int motion_ms) : m_plugin(plugin), m_mainWindow(mainWin), m_change(change_ms), m_motion(motion_ms) { // get updated m_changeTimer.setSingleShot(true); - auto ch = [this] () { emit newState(m_mainWindow->activeView(), TextChanged); }; + auto ch = [this]() { emit newState(m_mainWindow->activeView(), TextChanged); }; connect(&m_changeTimer, &QTimer::timeout, this, ch); m_motionTimer.setSingleShot(true); - auto mh = [this] () { emit newState(m_mainWindow->activeView(), LineChanged); }; + auto mh = [this]() { emit newState(m_mainWindow->activeView(), LineChanged); }; connect(&m_motionTimer, &QTimer::timeout, this, mh); // track views connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::viewChanged); } void viewChanged(KTextEditor::View *view) { m_motionTimer.stop(); m_changeTimer.stop(); if (view) { if (m_motion) { - connect(view, &KTextEditor::View::cursorPositionChanged, this, &self_type::cursorPositionChanged, Qt::UniqueConnection); + connect(view, &KTextEditor::View::cursorPositionChanged, this, + &self_type::cursorPositionChanged, Qt::UniqueConnection); } if (m_change > 0 && view->document()) { - connect(view->document(), &KTextEditor::Document::textChanged, this, &self_type::textChanged, Qt::UniqueConnection); + connect(view->document(), &KTextEditor::Document::textChanged, this, + &self_type::textChanged, Qt::UniqueConnection); } emit newState(view, ViewChanged); m_oldCursorLine = view->cursorPosition().line(); } } void textChanged() { m_motionTimer.stop(); m_changeTimer.start(m_change); } void cursorPositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPosition) { if (m_changeTimer.isActive()) { // change trumps motion return; } if (view && newPosition.line() != m_oldCursorLine) { m_oldCursorLine = newPosition.line(); m_motionTimer.start(m_motion); } } }; -LSPClientViewTracker* -LSPClientViewTracker::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, - int change_ms, int motion_ms) +LSPClientViewTracker *LSPClientViewTracker::new_(LSPClientPlugin *plugin, + KTextEditor::MainWindow *mainWin, int change_ms, + int motion_ms) { return new LSPClientViewTrackerImpl(plugin, mainWin, change_ms, motion_ms); } /* * Instantiates and manages the symbol outline toolview. */ class LSPClientSymbolViewImpl : public QObject, public LSPClientSymbolView { Q_OBJECT typedef LSPClientSymbolViewImpl self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; QSharedPointer m_serverManager; QScopedPointer m_toolview; // parent ownership QPointer m_symbols; QPointer m_filter; QScopedPointer m_popup; // initialized/updated from plugin settings // managed by context menu later on // parent ownership QAction *m_detailsOn; QAction *m_expandOn; QAction *m_treeOn; QAction *m_sortOn; // view tracking QScopedPointer m_viewTracker; // outstanding request LSPClientServer::RequestHandle m_handle; // cached outline models - struct ModelData - { + struct ModelData { KTextEditor::Document *document; qint64 revision; std::shared_ptr model; }; QList m_models; // max number to cache static constexpr int MAX_MODELS = 10; // last outline model we constructed std::shared_ptr m_outline; // filter model, setup once KRecursiveFilterProxyModel m_filterModel; // cached icons for model const QIcon m_icon_pkg = QIcon::fromTheme(QStringLiteral("code-block")); const QIcon m_icon_class = QIcon::fromTheme(QStringLiteral("code-class")); const QIcon m_icon_typedef = QIcon::fromTheme(QStringLiteral("code-typedef")); const QIcon m_icon_function = QIcon::fromTheme(QStringLiteral("code-function")); const QIcon m_icon_var = QIcon::fromTheme(QStringLiteral("code-variable")); public: LSPClientSymbolViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, - QSharedPointer manager) - : m_plugin(plugin), m_mainWindow(mainWin), m_serverManager(manager), m_outline(new QStandardItemModel()) + QSharedPointer manager) + : m_plugin(plugin), + m_mainWindow(mainWin), + m_serverManager(manager), + m_outline(new QStandardItemModel()) { - m_toolview.reset(m_mainWindow->createToolView(plugin, QStringLiteral("lspclient_symbol_outline"), - KTextEditor::MainWindow::Right, - QIcon::fromTheme(QStringLiteral("code-context")), - i18n("LSP Client Symbol Outline"))); + m_toolview.reset(m_mainWindow->createToolView( + plugin, QStringLiteral("lspclient_symbol_outline"), KTextEditor::MainWindow::Right, + QIcon::fromTheme(QStringLiteral("code-context")), + i18n("LSP Client Symbol Outline"))); m_symbols = new QTreeView(m_toolview.data()); m_symbols->setFocusPolicy(Qt::NoFocus); m_symbols->setLayoutDirection(Qt::LeftToRight); m_toolview->layout()->setContentsMargins(0, 0, 0, 0); m_toolview->layout()->addWidget(m_symbols); m_toolview->layout()->setSpacing(0); // setup filter line edit m_filter = new KLineEdit(m_toolview.data()); m_toolview->layout()->addWidget(m_filter); m_filter->setPlaceholderText(i18n("Filter...")); m_filter->setClearButtonEnabled(true); connect(m_filter, &KLineEdit::textChanged, this, &self_type::filterTextChanged); m_symbols->setContextMenuPolicy(Qt::CustomContextMenu); m_symbols->setIndentation(10); m_symbols->setEditTriggers(QAbstractItemView::NoEditTriggers); m_symbols->setAllColumnsShowFocus(true); // init filter model once, later we only swap the source model! QItemSelectionModel *m = m_symbols->selectionModel(); m_filterModel.setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel.setSortCaseSensitivity(Qt::CaseInsensitive); m_filterModel.setSourceModel(m_outline.get()); m_symbols->setModel(&m_filterModel); delete m; - connect(m_symbols, &QTreeView::customContextMenuRequested, this, &self_type::showContextMenu); + connect(m_symbols, &QTreeView::customContextMenuRequested, this, + &self_type::showContextMenu); connect(m_symbols, &QTreeView::activated, this, &self_type::goToSymbol); connect(m_symbols, &QTreeView::clicked, this, &self_type::goToSymbol); // context menu m_popup.reset(new QMenu(m_symbols)); m_treeOn = m_popup->addAction(i18n("Tree Mode"), this, &self_type::displayOptionChanged); m_treeOn->setCheckable(true); - m_expandOn = m_popup->addAction(i18n("Automatically Expand Tree"), this, &self_type::displayOptionChanged); + m_expandOn = m_popup->addAction(i18n("Automatically Expand Tree"), this, + &self_type::displayOptionChanged); m_expandOn->setCheckable(true); - m_sortOn = m_popup->addAction(i18n("Sort Alphabetically"), this, &self_type::displayOptionChanged); + m_sortOn = m_popup->addAction(i18n("Sort Alphabetically"), this, + &self_type::displayOptionChanged); m_sortOn->setCheckable(true); - m_detailsOn = m_popup->addAction(i18n("Show Details"), this, &self_type::displayOptionChanged); + m_detailsOn = + m_popup->addAction(i18n("Show Details"), this, &self_type::displayOptionChanged); m_detailsOn->setCheckable(true); m_popup->addSeparator(); m_popup->addAction(i18n("Expand All"), m_symbols.data(), &QTreeView::expandAll); m_popup->addAction(i18n("Collapse All"), m_symbols.data(), &QTreeView::collapseAll); // sync with plugin settings if updated connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated); // get updated m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 500, 100)); - connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState); - connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, - this, [this] () { refresh(false); }); + connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, + &self_type::onViewState); + connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, + [this]() { refresh(false); }); // limit cached models; will not go beyond capacity set here m_models.reserve(MAX_MODELS + 1); // initial trigger of symbols view update configUpdated(); } void displayOptionChanged() { m_expandOn->setEnabled(m_treeOn->isChecked()); refresh(false); } void configUpdated() { m_treeOn->setChecked(m_plugin->m_symbolTree); m_detailsOn->setChecked(m_plugin->m_symbolDetails); m_expandOn->setChecked(m_plugin->m_symbolExpand); m_sortOn->setChecked(m_plugin->m_symbolSort); displayOptionChanged(); } - void showContextMenu(const QPoint&) - { - m_popup->popup(QCursor::pos(), m_treeOn); - } + void showContextMenu(const QPoint &) { m_popup->popup(QCursor::pos(), m_treeOn); } void onViewState(KTextEditor::View *, LSPClientViewTracker::State newState) { - switch(newState) { + switch (newState) { case LSPClientViewTracker::ViewChanged: refresh(true); break; case LSPClientViewTracker::TextChanged: refresh(false); break; case LSPClientViewTracker::LineChanged: updateCurrentTreeItem(); break; } } - void makeNodes(const QList & symbols, bool tree, - bool show_detail, QStandardItemModel * model, QStandardItem * parent, - bool &details) + void makeNodes(const QList &symbols, bool tree, bool show_detail, + QStandardItemModel *model, QStandardItem *parent, bool &details) { const QIcon *icon = nullptr; - for (const auto& symbol: symbols) { + for (const auto &symbol : symbols) { switch (symbol.kind) { case LSPSymbolKind::File: case LSPSymbolKind::Module: case LSPSymbolKind::Namespace: case LSPSymbolKind::Package: if (symbol.children.count() == 0) continue; icon = &m_icon_pkg; break; case LSPSymbolKind::Class: case LSPSymbolKind::Interface: icon = &m_icon_class; break; case LSPSymbolKind::Enum: icon = &m_icon_typedef; break; case LSPSymbolKind::Method: case LSPSymbolKind::Function: case LSPSymbolKind::Constructor: icon = &m_icon_function; break; // all others considered/assumed Variable case LSPSymbolKind::Variable: case LSPSymbolKind::Constant: case LSPSymbolKind::String: case LSPSymbolKind::Number: case LSPSymbolKind::Property: case LSPSymbolKind::Field: default: // skip local variable // property, field, etc unlikely in such case anyway if (parent && parent->icon().cacheKey() == m_icon_function.cacheKey()) continue; icon = &m_icon_var; } - auto node = new QStandardItem(); if (parent && tree) - parent->appendRow(node); - else - model->appendRow(node); + parent->appendRow(node); + else + model->appendRow(node); if (!symbol.detail.isEmpty()) details = true; auto detail = show_detail ? symbol.detail : QString(); node->setText(symbol.name + detail); node->setIcon(*icon); node->setData(QVariant::fromValue(symbol.range), Qt::UserRole); // recurse children makeNodes(symbol.children, tree, show_detail, model, node, details); } } void onDocumentSymbols(const QList &outline) { onDocumentSymbolsOrProblem(outline, QString(), true); } - void onDocumentSymbolsOrProblem(const QList &outline, const QString &problem = QString(), bool cache = false) + void onDocumentSymbolsOrProblem(const QList &outline, + const QString &problem = QString(), bool cache = false) { if (!m_symbols) return; // construct new model for data auto newModel = std::make_shared(); // if we have some problem, just report that, else construct model bool details = false; if (problem.isEmpty()) { - makeNodes(outline, m_treeOn->isChecked(), m_detailsOn->isChecked(), newModel.get(), nullptr, details); + makeNodes(outline, m_treeOn->isChecked(), m_detailsOn->isChecked(), newModel.get(), + nullptr, details); if (cache) { // last request has been placed at head of model list Q_ASSERT(!m_models.isEmpty()); m_models[0].model = newModel; } } else { newModel->appendRow(new QStandardItem(problem)); } // cache detail info with model newModel->invisibleRootItem()->setData(details); // fixup headers - QStringList headers{i18n("Symbols")}; + QStringList headers { i18n("Symbols") }; newModel->setHorizontalHeaderLabels(headers); setModel(newModel); } void setModel(std::shared_ptr newModel) { Q_ASSERT(newModel); // update filter model, do this before the assignment below deletes the old model! m_filterModel.setSourceModel(newModel.get()); // delete old outline if there, keep our new one alive m_outline = newModel; // fixup sorting if (m_sortOn->isChecked()) { m_symbols->setSortingEnabled(true); m_symbols->sortByColumn(0); } else { m_symbols->sortByColumn(-1); } // handle auto-expansion if (m_expandOn->isChecked()) { m_symbols->expandAll(); } // recover detail info from model data bool details = newModel->invisibleRootItem()->data().toBool(); // disable detail setting if no such info available // (as an indication there is nothing to show anyway) m_detailsOn->setEnabled(details); // hide detail column if not needed/wanted bool showDetails = m_detailsOn->isChecked() && details; m_symbols->setColumnHidden(1, !showDetails); // current item tracking updateCurrentTreeItem(); } void refresh(bool clear) { // cancel old request! m_handle.cancel(); // check if we have some server for the current view => trigger request auto view = m_mainWindow->activeView(); if (auto server = m_serverManager->findServer(view)) { // clear current model in any case // this avoids that we show stuff not matching the current view // but let's only do it if needed, e.g. when changing view // so as to avoid unhealthy flickering in other cases if (clear) { onDocumentSymbolsOrProblem(QList(), QString(), false); } // check (valid) cache auto doc = view->document(); auto revision = m_serverManager->revision(doc); auto it = m_models.begin(); for (; it != m_models.end(); ++it) { if (it->document == doc) { break; } } if (it != m_models.end()) { // move to most recently used head m_models.move(it - m_models.begin(), 0); - auto& model = m_models.front(); + auto &model = m_models.front(); // re-use if possible if (revision == model.revision && model.model) { setModel(model.model); return; } it->revision = revision; } else { - m_models.insert(0, {doc, revision, nullptr}); + m_models.insert(0, { doc, revision, nullptr }); if (m_models.size() > MAX_MODELS) { m_models.pop_back(); } } server->documentSymbols(view->document()->url(), this, utils::mem_fun(&self_type::onDocumentSymbols, this)); return; } // else: inform that no server is there - onDocumentSymbolsOrProblem(QList(), i18n("No LSP server for this document.")); + onDocumentSymbolsOrProblem(QList(), + i18n("No LSP server for this document.")); } - QStandardItem* getCurrentItem(QStandardItem * item, int line) + QStandardItem *getCurrentItem(QStandardItem *item, int line) { // first traverse the child items to have deepest match! // only do this if our stuff is expanded - if (item == m_outline->invisibleRootItem() || m_symbols->isExpanded(m_filterModel.mapFromSource(m_outline->indexFromItem(item)))) { + if (item == m_outline->invisibleRootItem() + || m_symbols->isExpanded(m_filterModel.mapFromSource(m_outline->indexFromItem(item)))) { for (int i = 0; i < item->rowCount(); i++) { if (auto citem = getCurrentItem(item->child(i), line)) { return citem; } } } // does the line match our item? - return item->data(Qt::UserRole).value().overlapsLine(line) ? item : nullptr; + return item->data(Qt::UserRole).value().overlapsLine(line) ? item + : nullptr; } void updateCurrentTreeItem() { - KTextEditor::View* editView = m_mainWindow->activeView(); + KTextEditor::View *editView = m_mainWindow->activeView(); if (!editView || !m_symbols) { return; } /** * get item if any */ - QStandardItem *item = getCurrentItem(m_outline->invisibleRootItem(), editView->cursorPositionVirtual().line()); + QStandardItem *item = getCurrentItem(m_outline->invisibleRootItem(), + editView->cursorPositionVirtual().line()); if (!item) { return; } /** * select it */ QModelIndex index = m_filterModel.mapFromSource(m_outline->indexFromItem(item)); m_symbols->scrollTo(index); - m_symbols->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear | QItemSelectionModel::Select); + m_symbols->selectionModel()->setCurrentIndex( + index, QItemSelectionModel::Clear | QItemSelectionModel::Select); } void goToSymbol(const QModelIndex &index) { - KTextEditor::View *kv = m_mainWindow->activeView(); - const auto range = index.data(Qt::UserRole).value(); - if (kv && range.isValid()) { - kv->setCursorPosition(range.start()); - } + KTextEditor::View *kv = m_mainWindow->activeView(); + const auto range = index.data(Qt::UserRole).value(); + if (kv && range.isValid()) { + kv->setCursorPosition(range.start()); + } } private Q_SLOTS: /** * React on filter change * @param filterText new filter text */ void filterTextChanged(const QString &filterText) { if (!m_symbols) { return; } /** * filter */ m_filterModel.setFilterFixedString(filterText); /** * expand */ if (!filterText.isEmpty()) { QTimer::singleShot(100, m_symbols, &QTreeView::expandAll); } } }; - -QObject* -LSPClientSymbolView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, - QSharedPointer manager) +QObject *LSPClientSymbolView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, + QSharedPointer manager) { return new LSPClientSymbolViewImpl(plugin, mainWin, manager); } #include "lspclientsymbolview.moc" diff --git a/addons/lspclient/lspclientsymbolview.h b/addons/lspclient/lspclientsymbolview.h index 515c801fa..33a8e6290 100644 --- a/addons/lspclient/lspclientsymbolview.h +++ b/addons/lspclient/lspclientsymbolview.h @@ -1,62 +1,60 @@ /* SPDX-License-Identifier: MIT Copyright (C) 2019 Mark Nauwelaerts Copyright (C) 2019 Christoph Cullmann 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. */ #ifndef LSPCLIENTSYMBOLVIEW_H #define LSPCLIENTSYMBOLVIEW_H #include "lspclientplugin.h" #include "lspclientservermanager.h" #include class LSPClientSymbolView { public: // only needs a factory; no other public interface - static QObject* - new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, - QSharedPointer manager); + static QObject *new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, + QSharedPointer manager); }; class LSPClientViewTracker : public QObject { Q_OBJECT public: // factory method; private implementation by interface - static LSPClientViewTracker* - new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, - int change_ms, int motion_ms); + static LSPClientViewTracker *new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, + int change_ms, int motion_ms); enum State { ViewChanged, TextChanged, LineChanged, }; Q_SIGNALS: void newState(KTextEditor::View *, State); }; #endif