diff --git a/addons/lspclient/lspclientconfigpage.cpp b/addons/lspclient/lspclientconfigpage.cpp index fe377dede..945adc40a 100644 --- a/addons/lspclient/lspclientconfigpage.cpp +++ b/addons/lspclient/lspclientconfigpage.cpp @@ -1,115 +1,119 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * Copyright (C) 2019 by Mark Nauwelaerts * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #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")); top->addWidget(m_complDoc); + top->addWidget(m_refDeclaration); 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}) + for (const auto & cb : {m_symbolDetails, m_symbolExpand, m_symbolSort, m_symbolTree, m_complDoc, m_refDeclaration}) connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed); connect(m_configPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::changed); connect(m_configPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::changed); } 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_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_configPath->setUrl(m_plugin->m_configPath); } void LSPClientConfigPage::defaults() { reset(); } diff --git a/addons/lspclient/lspclientconfigpage.h b/addons/lspclient/lspclientconfigpage.h index 16325eafc..6a4c4eda6 100644 --- a/addons/lspclient/lspclientconfigpage.h +++ b/addons/lspclient/lspclientconfigpage.h @@ -1,61 +1,62 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * Copyright (C) 2019 by Mark Nauwelaerts * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #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; KUrlRequester *m_configPath; LSPClientPlugin *m_plugin; }; #endif diff --git a/addons/lspclient/lspclientplugin.cpp b/addons/lspclient/lspclientplugin.cpp index df162f6dc..abbbf3b1b 100644 --- a/addons/lspclient/lspclientplugin.cpp +++ b/addons/lspclient/lspclientplugin.cpp @@ -1,98 +1,101 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * Copyright (C) 2019 by Mark Nauwelaerts * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "lspclientplugin.h" #include "lspclientpluginview.h" #include "lspclientconfigpage.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_SERVER_CONFIG { QStringLiteral("ServerConfiguration") }; K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", registerPlugin();) LSPClientPlugin::LSPClientPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { readConfig(); } 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_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_SERVER_CONFIG, m_configPath); emit update(); } #include "lspclientplugin.moc" diff --git a/addons/lspclient/lspclientplugin.h b/addons/lspclient/lspclientplugin.h index 138cca3b2..f064e0880 100644 --- a/addons/lspclient/lspclientplugin.h +++ b/addons/lspclient/lspclientplugin.h @@ -1,60 +1,61 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * Copyright (C) 2019 by Mark Nauwelaerts * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #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; QUrl m_configPath; private: Q_SIGNALS: // signal settings update void update() const; }; #endif diff --git a/addons/lspclient/lspclientpluginview.cpp b/addons/lspclient/lspclientpluginview.cpp index 8833a0dbd..3c7fe50b5 100644 --- a/addons/lspclient/lspclientpluginview.cpp +++ b/addons/lspclient/lspclientpluginview.cpp @@ -1,630 +1,637 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * Copyright (C) 2019 by Mark Nauwelaerts * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "lspclientpluginview.h" #include "lspclientsymbolview.h" #include "lspclientplugin.h" #include "lspclientservermanager.h" #include "lspclientcompletion.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 namespace RangeData { enum { FileUrlRole = Qt::UserRole, StartLineRole, StartColumnRole, EndLineRole, EndColumnRole }; KTextEditor::MarkInterface::MarkTypes markType = KTextEditor::MarkInterface::markType31; } class LSPClientPluginViewImpl : public QObject, public KXMLGUIClient { Q_OBJECT typedef LSPClientPluginViewImpl self_type; LSPClientPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; QSharedPointer m_serverManager; QScopedPointer m_completion; QScopedPointer m_symbolView; QPointer m_findDef; QPointer m_findDecl; QPointer m_findRef; QPointer m_highlight; QPointer m_hover; QPointer m_complDocOn; + QPointer m_refDeclaration; QPointer m_restartServer; QPointer m_restartAll; // toolview QScopedPointer m_toolView; QPointer m_tabWidget; // applied ranges QMultiHash m_ranges; // tree is either added to tabwidget or owned here QScopedPointer m_ownedTree; // in either case, the tree that directs applying marks/ranges QPointer m_markTree; // views on which completions have been registered QSet m_completionViews; // outstanding request LSPClientServer::RequestHandle m_handle; // timeout on request bool m_req_timeout = false; public: LSPClientPluginViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin), m_plugin(plugin), m_mainWindow(mainWin), m_serverManager(LSPClientServerManager::new_(plugin, mainWin)), m_completion(LSPClientCompletion::new_(m_serverManager)), m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager)) { KXMLGUIClient::setComponentName(QStringLiteral("lspclient"), i18n("LSP Client")); setXMLFile(QStringLiteral("ui.rc")); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState); connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc); connect(m_serverManager.get(), &LSPClientServerManager::serverChanged, this, &self_type::updateState); 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->setText(i18n("Go to Declaration")); m_findRef = actionCollection()->addAction(QStringLiteral("lspclient_find_references"), this, &self_type::findReferences); m_findRef->setText(i18n("Find References")); m_highlight = actionCollection()->addAction(QStringLiteral("lspclient_highlight"), this, &self_type::highlight); m_highlight->setText(i18n("Highlight")); // perhaps hover suggests to do so on mouse-over, // but let's just use a (convenient) action/shortcut for it m_hover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, &self_type::hover); m_hover->setText(i18n("Hover")); - // completion configuration + // general options 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->setText(i18n("Include declaration in references")); + m_refDeclaration->setCheckable(true); // server control 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->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_highlight); menu->addAction(m_hover); menu->addSeparator(); menu->addAction(m_complDocOn); + menu->addAction(m_refDeclaration); 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_tabWidget = new QTabWidget(m_toolView.get()); m_tabWidget->setTabsClosable(true); KAcceleratorManager::setNoAccel(m_tabWidget); connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested); configUpdated(); updateState(); m_mainWindow->guiFactory()->addClient(this); } ~LSPClientPluginViewImpl() { clearAllMarks(); m_mainWindow->guiFactory()->removeClient(this); } void displayOptionChanged() { if (m_complDocOn) m_completion->setSelectedDocumentation(m_complDocOn->isChecked()); } void configUpdated() { if (m_complDocOn) m_complDocOn->setChecked(m_plugin->m_complDoc); + if (m_refDeclaration) + m_refDeclaration->setChecked(m_plugin->m_refDeclaration); displayOptionChanged(); } void restartCurrent() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (server) m_serverManager->restart(server.get()); } void restartAll() { m_serverManager->restart(nullptr); } Q_SLOT void clearMarks(KTextEditor::Document *doc) { KTextEditor::MarkInterface* iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & RangeData::markType) { iface->removeMark(i.value()->line, RangeData::markType); } } } for (auto it = m_ranges.find(doc); it != m_ranges.end() && it.key() == doc;) { delete it.value(); it = m_ranges.erase(it); } } void clearAllMarks() { while (!m_ranges.empty()) { clearMarks(m_ranges.begin().key()); } // no longer add any again m_ownedTree.reset(); m_markTree.clear(); } void addMarks(KTextEditor::Document *doc, QTreeWidgetItem *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); if (!miface || !iface) return; auto url = item->data(0, RangeData::FileUrlRole).toUrl(); if (url != doc->url()) return; int line = item->data(0, RangeData::StartLineRole).toInt(); int column = item->data(0, RangeData::StartColumnRole).toInt(); int endLine = item->data(0, RangeData::EndLineRole).toInt(); int endColumn = item->data(0, RangeData::EndColumnRole).toInt(); KTextEditor::Range range(line, column, endLine, endColumn); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); // 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(); } attr->setBackground(rangeColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } // highlight the 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); m_ranges.insert(doc, mr); // add match mark for range iface->setMarkDescription(RangeData::markType, i18n("RangeHighLight")); iface->setMarkPixmap(RangeData::markType, QIcon().pixmap(0,0)); iface->addMark(line, RangeData::markType); // ensure runtime match auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearMarks(KTextEditor::Document*)), Qt::UniqueConnection); Q_ASSERT(conn); conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearMarks(KTextEditor::Document*)), Qt::UniqueConnection); Q_ASSERT(conn); } void addMarks(KTextEditor::Document *doc, QTreeWidget *tree) { // check if already added if (m_ranges.contains(doc)) return; QTreeWidgetItemIterator it(tree, QTreeWidgetItemIterator::All); while (*it) { addMarks(doc, *it); ++it; } } 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(QTreeWidgetItem *it) { if (it) { auto url = it->data(0, RangeData::FileUrlRole).toUrl(); auto line = it->data(0, RangeData::StartLineRole).toInt(); auto column = it->data(0, RangeData::StartColumnRole).toInt(); goToDocumentLocation(url, line, column); } } void tabCloseRequested(int index) { delete m_tabWidget->widget(index); } void makeTree(const QList & locations) { QStringList titles; titles << i18nc("@title:column", "Location"); auto treeWidget = new QTreeWidget(); treeWidget->setHeaderLabels(titles); treeWidget->setRootIsDecorated(0); treeWidget->setFocusPolicy(Qt::NoFocus); treeWidget->setLayoutDirection(Qt::LeftToRight); treeWidget->setColumnCount(1); treeWidget->setSortingEnabled(false); for (const auto & loc: locations) { auto item = new QTreeWidgetItem(treeWidget); item->setText(0, QStringLiteral("%1 %2").arg(loc.uri.path()).arg(loc.range.start().line() + 1, 6)); item->setData(0, RangeData::FileUrlRole, QVariant(loc.uri)); item->setData(0, RangeData::StartLineRole, loc.range.start().line()); item->setData(0, RangeData::StartColumnRole, loc.range.start().column()); item->setData(0, RangeData::EndLineRole, loc.range.end().line()); item->setData(0, RangeData::EndColumnRole, loc.range.end().column()); } treeWidget->sortItems(0, Qt::AscendingOrder); treeWidget->setSortingEnabled(true); m_ownedTree.reset(treeWidget); m_markTree = treeWidget; } void showTree(const QString & title) { // transfer widget from owned to tabwidget auto treeWidget = m_ownedTree.take(); int index = m_tabWidget->addTab(treeWidget, title); connect(treeWidget, &QTreeWidget::itemClicked, this, &self_type::goToItemLocation); // activate the resulting tab m_tabWidget->setCurrentIndex(index); m_mainWindow->showToolView(m_toolView.get()); } void showMessage(const QString & text, KTextEditor::Message::MessageType level) { KTextEditor::View *view = m_mainWindow->activeView(); 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; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (!m_ranges.empty()) { clearAllMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView.get()); } } } template using LocationRequest = std::function; template void positionRequest(const LocationRequest & req, const Handler & h) { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); if (!server) return; KTextEditor::Cursor cursor = activeView->cursorPosition(); clearAllMarks(); 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); } QString currentWord() { KTextEditor::View *activeView = m_mainWindow->activeView(); if (activeView) { KTextEditor::Cursor cursor = activeView->cursorPosition(); return activeView->document()->wordAt(cursor); } else { return QString(); } } void goToLocation(const QString & title, const LocationRequest & req, bool show) { auto h = [this, title, show] (const QList & defs) { if (defs.count() == 0) { showMessage(i18n("No results"), KTextEditor::Message::Information); } else { makeTree(defs); if (defs.count() > 1 || show) { showTree(title); } else { // it's not nice to jump to some location if we are too late if (m_req_timeout) return; auto &def = defs.at(0); const auto &pos = def.range.start(); goToDocumentLocation(def.uri, pos.line(), pos.column()); } // update marks updateState(); } }; positionRequest(req, h); } void goToDefinition() { auto title = i18nc("@title:tab", "Definition: %1", currentWord()); goToLocation(title, &LSPClientServer::documentDefinition, false); } void goToDeclaration() { auto title = i18nc("@title:tab", "Declaration: %1", currentWord()); goToLocation(title, &LSPClientServer::documentDeclaration, false); } void findReferences() { auto title = i18nc("@title:tab", "References: %1", currentWord()); - // TODO declaration configurable ?? - auto req = [] (LSPClientServer & server, const QUrl & document, const LSPPosition & pos, + 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, true, context, h); }; + { return server.documentReferences(document, pos, decl, context, h); }; goToLocation(title, req, true); } void highlight() { // FIXME: we need a way to clear this without reloading the document! // construct handler, remember view we did the request for const QPointer viewForRequest(m_mainWindow->activeView()); auto h = [this, viewForRequest] (const QList & occurences) { // abort if the view we requested this for is away! if (!viewForRequest) return; // need moving interface for the ranges auto miface = qobject_cast(viewForRequest->document()); if (!miface) return; // highlight all occurences for (const auto &occurence : occurences) { // highlight color => FIXME KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); switch (occurence.kind) { case LSPDocumentHighlightKind::Text: attr->setBackground(Qt::yellow); break; case LSPDocumentHighlightKind::Read: attr->setBackground(Qt::green); break; case LSPDocumentHighlightKind::Write: attr->setBackground(Qt::red); break; } KTextEditor::MovingRange* mr = miface->newMovingRange(occurence.range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); } }; positionRequest(&LSPClientServer::documentHighlight, h); } void hover() { auto h = [this] (const LSPHover & info) { // TODO ?? also indicate range in some way ?? auto text = info.contents.value.length() ? info.contents.value : i18n("No Hover Info"); showMessage(text, KTextEditor::Message::Information); }; positionRequest(&LSPClientServer::documentHover, h); } void updateState() { KTextEditor::View *activeView = m_mainWindow->activeView(); auto server = m_serverManager->findServer(activeView); bool defEnabled = false, declEnabled = false, refEnabled = false, hoverEnabled = false, highlightEnabled = false; if (server) { 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; } if (m_findDef) m_findDef->setEnabled(defEnabled); if (m_findDecl) m_findDecl->setEnabled(declEnabled); if (m_findRef) m_findRef->setEnabled(refEnabled); if (m_highlight) m_highlight->setEnabled(highlightEnabled); if (m_hover) m_hover->setEnabled(hoverEnabled); if (m_complDocOn) m_complDocOn->setEnabled(server); if (m_restartServer) m_restartServer->setEnabled(server); // update completion with relevant server m_completion->setServer(server); displayOptionChanged(); updateCompletion(activeView, server.get()); // update marks if applicable if (m_markTree && activeView) addMarks(activeView->document(), m_markTree); } void viewDestroyed(QObject *view) { m_completionViews.remove(static_cast(view)); } void updateCompletion(KTextEditor::View *view, LSPClientServer * server) { bool registered = m_completionViews.contains(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.get()); m_completionViews.insert(view); connect(view, &KTextEditor::View::destroyed, this, &self_type::viewDestroyed, Qt::UniqueConnection); } if (registered && !server) { qCInfo(LSPCLIENT) << "unregistering cci"; cci->unregisterCompletionModel(m_completion.get()); m_completionViews.remove(view); } } }; QObject* LSPClientPluginView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin) { return new LSPClientPluginViewImpl(plugin, mainWin); } #include "lspclientpluginview.moc" diff --git a/addons/lspclient/ui.rc b/addons/lspclient/ui.rc index 207a047f6..11394a096 100644 --- a/addons/lspclient/ui.rc +++ b/addons/lspclient/ui.rc @@ -1,23 +1,24 @@ LSP Client +