diff --git a/addons/lspclient/CMakeLists.txt b/addons/lspclient/CMakeLists.txt --- a/addons/lspclient/CMakeLists.txt +++ b/addons/lspclient/CMakeLists.txt @@ -7,6 +7,7 @@ set(lspclientplugin_SRCS lspclientcompletion.cpp lspclientconfigpage.cpp + lspclienthover.cpp lspclientplugin.cpp lspclientpluginview.cpp lspclientserver.cpp diff --git a/addons/lspclient/lspclienthover.h b/addons/lspclient/lspclienthover.h new file mode 100644 --- /dev/null +++ b/addons/lspclient/lspclienthover.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * 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 LSPCLIENTHOVER_H +#define LSPCLIENTHOVER_H + +#include "lspclientserver.h" +#include "lspclientservermanager.h" + +#include + +class LSPClientHover : public QObject, public KTextEditor::TextHintProvider +{ + Q_OBJECT + +public: + + // implementation factory method + static LSPClientHover* new_(QSharedPointer manager); + + LSPClientHover() + : KTextEditor::TextHintProvider() + {} + + virtual void setServer(QSharedPointer server) = 0; +}; + +#endif diff --git a/addons/lspclient/lspclienthover.cpp b/addons/lspclient/lspclienthover.cpp new file mode 100644 --- /dev/null +++ b/addons/lspclient/lspclienthover.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2012 Christoph Cullmann * + * Copyright (C) 2003 Anders Lund * + * 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 "lspclienthover.h" +#include "lspclientplugin.h" + +#include "lspclient_debug.h" + +#include +#include +#include + +#include + +class LSPClientHoverImpl : public LSPClientHover +{ + Q_OBJECT + + typedef LSPClientHoverImpl self_type; + + QSharedPointer m_manager; + QSharedPointer m_server; + + LSPClientServer::RequestHandle m_handle; + +public: + LSPClientHoverImpl(QSharedPointer manager) + : LSPClientHover(), m_manager(manager), m_server(nullptr) + { + } + + void setServer(QSharedPointer server) override + { + m_server = server; + } + + /** + * This function is called whenever the users hovers over text such + * that the text hint delay passes. Then, textHint() is called + * for each registered TextHintProvider. + * + * Return the text hint (possibly Qt richtext) for @p view at @p position. + * + * If you do not have any contents to show, just return an empty QString(). + * + * \param view the view that requests the text hint + * \param position text cursor under the mouse position + * \return text tool tip to be displayed, may be Qt richtext + */ + QString textHint(KTextEditor::View *view, + const KTextEditor::Cursor &position) override + { + // hack: delayed handling of tooltip on our own, the API is too dumb for a-sync feedback ;=) + if (m_server) { + QPointer v(view); + auto h = [this,v,position] (const LSPHover & info) + { + if (!v || info.contents.value.isEmpty()) { + return; + } + + auto localCoordinates = v->cursorToCoordinate(position); + QToolTip::showText(v->mapToGlobal(localCoordinates), info.contents.value); + }; + + m_handle.cancel() = m_server->documentHover(view->document()->url(), position, this, h); + } + + return QString(); + } + +}; + +LSPClientHover* +LSPClientHover::new_(QSharedPointer manager) +{ + return new LSPClientHoverImpl(manager); +} + +#include "lspclienthover.moc" diff --git a/addons/lspclient/lspclientpluginview.cpp b/addons/lspclient/lspclientpluginview.cpp --- a/addons/lspclient/lspclientpluginview.cpp +++ b/addons/lspclient/lspclientpluginview.cpp @@ -23,6 +23,7 @@ #include "lspclientplugin.h" #include "lspclientservermanager.h" #include "lspclientcompletion.h" +#include "lspclienthover.h" #include "lspclient_debug.h" @@ -180,13 +181,14 @@ 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_highlight; - QPointer m_hover; + QPointer m_triggerHover; QPointer m_complDocOn; QPointer m_refDeclaration; QPointer m_diagnostics; @@ -221,6 +223,10 @@ // 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 @@ -231,6 +237,7 @@ : QObject(mainWin), m_plugin(plugin), m_mainWindow(mainWin), m_serverManager(LSPClientServerManager::new_(plugin, mainWin)), m_completion(LSPClientCompletion::new_(m_serverManager)), + m_hover(LSPClientHover::new_(m_serverManager)), m_symbolView(LSPClientSymbolView::new_(plugin, mainWin, m_serverManager)) { KXMLGUIClient::setComponentName(QStringLiteral("lspclient"), i18n("LSP Client")); @@ -250,8 +257,8 @@ 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")); + m_triggerHover = actionCollection()->addAction(QStringLiteral("lspclient_hover"), this, &self_type::hover); + m_triggerHover->setText(i18n("Hover")); // general options m_complDocOn = actionCollection()->addAction(QStringLiteral("lspclient_completion_doc"), this, &self_type::displayOptionChanged); @@ -285,7 +292,7 @@ menu->addAction(m_findDecl); menu->addAction(m_findRef); menu->addAction(m_highlight); - menu->addAction(m_hover); + menu->addAction(m_triggerHover); menu->addSeparator(); menu->addAction(m_complDocOn); menu->addAction(m_refDeclaration); @@ -882,14 +889,10 @@ 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); + // trigger manually the normally automagic hover + if (auto activeView = m_mainWindow->activeView()) { + m_hover->textHint(activeView, activeView->cursorPosition()); + } } static QTreeWidgetItem* @@ -1049,8 +1052,8 @@ m_findRef->setEnabled(refEnabled); if (m_highlight) m_highlight->setEnabled(highlightEnabled); - if (m_hover) - m_hover->setEnabled(hoverEnabled); + if (m_triggerHover) + m_triggerHover->setEnabled(hoverEnabled); if (m_complDocOn) m_complDocOn->setEnabled(server); if (m_restartServer) @@ -1062,18 +1065,29 @@ m_completion->setSelectedDocumentation(m_complDocOn->isChecked()); updateCompletion(activeView, server.get()); + // update hover with relevant server + m_hover->setServer(server); + updateHover(activeView, server.get()); + // update marks if applicable if (m_markTree && activeView) addMarks(activeView->document(), m_markTree, m_ranges); if (m_diagnosticsTree && activeView) { clearMarks(activeView->document(), m_diagnosticsRanges, RangeData::markTypeDiagAll); addMarks(activeView->document(), m_diagnosticsTree, m_diagnosticsRanges); } + + // connect for cleanup stuff + if (activeView) + 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) @@ -1090,18 +1104,37 @@ 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); } } + + 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.get()); + m_hoverViews.insert(view); + } + + if (registered && !server) { + qCInfo(LSPCLIENT) << "unregistering cci"; + cci->unregisterTextHintProvider(m_hover.get()); + m_hoverViews.remove(view); + } + } }; QObject*