diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index febd7e974b..ed29cb21bb 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,300 +1,303 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationwidget.h" #include #include #include #include #include #include #include "../duchainlock.h" #include "util/debug.h" namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(nullptr), m_currentWidget(nullptr) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } -const int maxNavigationWidgetWidth = 580; +const int maxNavigationWidgetWidth = 800; +const int maxNavigationWidgetHeight = 400; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); - QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); - if(m_idealTextSize.height()>=300) { //make space for the scrollbar in case it's not fitting + QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), + qMin(m_idealTextSize.height(), maxNavigationWidgetHeight)); + if(m_idealTextSize.height()>=maxNavigationWidgetHeight) { + //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); m_browser->setPalette( p ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(m_browser == nullptr) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setTextWidth(maxNavigationWidgetWidth); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::setDisplayHints(DisplayHints hints) { m_hints = hints; } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html; { DUChainReadLocker lock; html = m_context->html(); } if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); if ( !(m_hints & EmbeddableWidget)) { // TODO: Only show that the first time, or the first few times this context is shown? html += QStringLiteral("

"); if (m_context->linkCount() > 0) { html += i18n("(Hold 'Alt' to show. Navigate via arrow keys, activate by pressing 'Enter')"); } else { html += i18n("(Hold 'Alt' to show this tooltip)"); } html += QStringLiteral("

"); } m_browser->setHtml( html ); m_currentText = html; m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); m_browser->scrollToAnchor(QStringLiteral("currentPosition")); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); m_currentWidget->setParent(nullptr); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { m_context->up(); update(); } void AbstractNavigationWidget::down() { m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 00f21db514..63e5ead430 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,225 +1,274 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" #include #include #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString KEY_INVOKE_ACTION(int num) { return QString("invoke_action_%1").arg(num); } QString iconForSeverity(IProblem::Severity severity) { switch (severity) { case IProblem::Hint: return QStringLiteral("dialog-information"); case IProblem::Warning: return QStringLiteral("dialog-warning"); case IProblem::Error: return QStringLiteral("dialog-error"); case IProblem::NoSeverity: return {}; } Q_UNREACHABLE(); return {}; } QString htmlImg(const QString& iconName, KIconLoader::Group group) { KIconLoader loader; const int size = loader.currentSize(group); return QString::fromLatin1("") .arg(size) .arg(loader.iconPath(iconName, group)); } } -ProblemNavigationContext::ProblemNavigationContext(const IProblem::Ptr& problem, const Flags flags) - : m_problem(problem) +ProblemNavigationContext::ProblemNavigationContext(const QVector& problems, const Flags flags) + : m_problems(problems) , m_flags(flags) , m_widget(nullptr) { + // Sort problems vector: + // 1) By severity + // 2) By sourceString, if severities are equals + std::sort(m_problems.begin(), m_problems.end(), [](const IProblem::Ptr a, const IProblem::Ptr b) { + if (a->severity() < b->severity()) + return true; + + if (a->severity() > b->severity()) + return false; + + if (a->sourceString() < b->sourceString()) + return true; + + return false; + }); } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } -QString ProblemNavigationContext::html(bool shorten) +QString ProblemNavigationContext::escapedHtml(const QString& text) const { - clear(); - m_shorten = shorten; - auto iconPath = iconForSeverity(m_problem->severity()); + static const QString htmlStart = QStringLiteral(""); + static const QString htmlEnd = QStringLiteral(""); + + QString result = text.trimmed(); + + if (!result.startsWith(htmlStart)) + return result.toHtmlEscaped(); + + result.remove(htmlStart); + result.remove(htmlEnd); + + return result; +} + +void ProblemNavigationContext::html(IProblem::Ptr problem) +{ + auto iconPath = iconForSeverity(problem->severity()); modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); // BEGIN: right column modifyHtml() += QStringLiteral(""); // END: right column modifyHtml() += QStringLiteral("
%1"); - modifyHtml() += i18n("Problem in %1", m_problem->sourceString()); + modifyHtml() += i18n("Problem in %1", problem->sourceString()); modifyHtml() += QStringLiteral("
"); if (m_flags & ShowLocation) { - const auto duchainProblem = dynamic_cast(m_problem.data()); - if (duchainProblem) { - modifyHtml() += labelHighlight(i18n("Location: ")); - makeLink(QStringLiteral("%1 :%2") - .arg(duchainProblem->finalLocation().document.toUrl().fileName()) - .arg(duchainProblem->rangeInCurrentRevision().start().line() + 1), - QString(), - NavigationAction(duchainProblem->finalLocation().document.toUrl(), duchainProblem->finalLocation().start()) - ); - modifyHtml() += QStringLiteral("
"); - } + modifyHtml() += labelHighlight(i18n("Location: ")); + makeLink(QStringLiteral("%1 :%2") + .arg(problem->finalLocation().document.toUrl().fileName()) + .arg(problem->finalLocation().start().line() + 1), + QString(), + NavigationAction(problem->finalLocation().document.toUrl(), problem->finalLocation().start()) + ); + + modifyHtml() += QStringLiteral("
"); } - modifyHtml() += m_problem->description().toHtmlEscaped(); - if ( !m_problem->explanation().isEmpty() ) { - modifyHtml() += "

" + m_problem->explanation().toHtmlEscaped() + "

"; - } + QString description = escapedHtml(problem->description()); + QString explanation = escapedHtml(problem->explanation()); + + modifyHtml() += description; + + // Add only non-empty explanation which differs from the problem description. + // Skip this if we have more than one problem. + if (m_problems.size() == 1 && !explanation.isEmpty() && explanation != description) + modifyHtml() += "

" + explanation + "

"; modifyHtml() += QStringLiteral("
"); - const QVector diagnostics = m_problem->diagnostics(); + auto diagnostics = problem->diagnostics(); if (!diagnostics.isEmpty()) { - DUChainReadLocker lock; for (auto diagnostic : diagnostics) { modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()).declaration; if (declaration) { modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), - DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); - } else if (range.start().isValid()) { + DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); + } + + else if (range.start().isValid()) { modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") - .arg(url.fileName()) - .arg(range.start().line() + 1), - url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); + .arg(url.fileName()) + .arg(range.start().line() + 1), + url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } + modifyHtml() += QStringLiteral("

"); } } - if (!m_cachedAssistant) { - m_cachedAssistant = m_problem->solutionAssistant(); - } - auto assistant = m_cachedAssistant; + auto assistant = problem->solutionAssistant(); if (assistant && !assistant->actions().isEmpty()) { modifyHtml() += QString::fromLatin1("").arg("#b3d4ff"); - modifyHtml() += QStringLiteral(""; modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); - int index = 0; + + const int startIndex = m_assistantsActions.size(); + int currentIndex = startIndex; foreach (auto assistantAction, assistant->actions()) { - if (index != 0) { + m_assistantsActions.append(assistantAction); + + if (currentIndex != startIndex) modifyHtml() += "
"; - } - makeLink(i18n("Solution (%1)", index + 1), KEY_INVOKE_ACTION(index), - NavigationAction(KEY_INVOKE_ACTION(index))); + + makeLink(i18n("Solution (%1)", currentIndex + 1), KEY_INVOKE_ACTION( currentIndex ), + NavigationAction(KEY_INVOKE_ACTION( currentIndex ))); modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); - ++index; + + ++currentIndex; } + modifyHtml() += "
"); } +} + +QString ProblemNavigationContext::html(bool shorten) +{ + m_shorten = shorten; + + clear(); + m_assistantsActions.clear(); + + int problemIndex = 0; + foreach (auto problem, m_problems) { + html(problem); + + if (++problemIndex != m_problems.size()) + modifyHtml() += "
"; + } return currentHtml(); } NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) { - auto assistant = m_cachedAssistant; - if (!assistant) - return {}; if (key.startsWith(QLatin1String("invoke_action_"))) { - const auto index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); + const int index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); executeAction(index); } return {}; } void ProblemNavigationContext::executeAction(int index) { - auto assistant = m_problem->solutionAssistant(); - if (!assistant) + if (index < 0 || index >= m_assistantsActions.size()) return; - auto action = assistant->actions().value(index); + auto action = m_assistantsActions.at(index); + Q_ASSERT(action); + if (action) { action->execute(); - if ( topContext() ) { + if ( topContext() ) DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); - } } else { qCWarning(LANGUAGE()) << "No such action"; return; } } diff --git a/language/duchain/navigation/problemnavigationcontext.h b/language/duchain/navigation/problemnavigationcontext.h index 35c3318a0c..9eeccc02ba 100644 --- a/language/duchain/navigation/problemnavigationcontext.h +++ b/language/duchain/navigation/problemnavigationcontext.h @@ -1,64 +1,73 @@ /* Copyright 2009 David Nolden - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #define KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #include #include #include #include #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ProblemNavigationContext : public AbstractNavigationContext { Q_OBJECT public: enum Flag { NoFlag = 0, ShowLocation = 1 << 0, }; Q_DECLARE_FLAGS(Flags, Flag) - explicit ProblemNavigationContext(const IProblem::Ptr& problem, const Flags flags = {}); + explicit ProblemNavigationContext(const QVector& problems, const Flags flags = {}); ~ProblemNavigationContext() override; QString name() const override; QString html(bool shorten = false) override; QWidget* widget() const override; bool isWidgetMaximized() const override; NavigationContextPointer executeKeyAction(QString key) override; public slots: void executeAction(int index); // TODO: Add API in base class? private: - IProblem::Ptr m_problem; + void html(IProblem::Ptr problem); + + /** + * Return HTML-ized text. Used for processing problem's description and explanation. + * Some plugins (kdev-cppcheck for example) return already HTML-ized strings, + * therefore we should make check for this case. + */ + QString escapedHtml(const QString& text) const; + + QVector m_problems; Flags m_flags; QPointer m_widget; - IAssistant::Ptr m_cachedAssistant; // cache assistant, calling IAssistant::solutionAssistant() might be expensive + QVector m_assistantsActions; }; } #endif // KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H diff --git a/plugins/contextbrowser/CMakeLists.txt b/plugins/contextbrowser/CMakeLists.txt index a77c8bbb5d..fcd82f5ebc 100644 --- a/plugins/contextbrowser/CMakeLists.txt +++ b/plugins/contextbrowser/CMakeLists.txt @@ -1,14 +1,14 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcontextbrowser\") ########### next target ############### set(kdevcontextbrowser_PART_SRCS contextbrowser.cpp contextbrowserview.cpp browsemanager.cpp ) qt5_add_resources(kdevcontextbrowser_PART_SRCS kdevcontextbrowser.qrc) kdevplatform_add_plugin(kdevcontextbrowser JSON kdevcontextbrowser.json SOURCES ${kdevcontextbrowser_PART_SRCS}) -target_link_libraries(kdevcontextbrowser KDev::Interfaces KDev::Util KDev::Language KDev::Sublime KF5::TextEditor KF5::Parts) +target_link_libraries(kdevcontextbrowser KDev::Interfaces KDev::Util KDev::Language KDev::Sublime KDev::Shell KF5::TextEditor KF5::Parts) diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 4ad4c84c3a..bfbd880f7a 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1478 +1,1497 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library 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 "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "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 #include #include #include #include #include #include #include #include #include #include +#include +#include + #include #include Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { NavigationContextPointer nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qWarning() << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; - m_currentToolTipProblem = {}; + m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } -static ProblemPointer findProblemUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) +static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { - const auto top = ReferencedTopDUContext(topContext); - foreach (auto problem, DUChainUtils::allProblemsForContext(top)) { - if (problem->rangeInCurrentRevision().contains(position)) { - return problem; + QVector problems; + auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); + foreach (auto modelData, modelsData) { + foreach (auto problem, modelData.model->problems(topContext->url())) { + DocumentRange problemRange = problem->finalLocation(); + if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) + problems += problem; } } - return {}; + return problems; } -static ProblemPointer findProblemCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) +static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { - const auto top = ReferencedTopDUContext(topContext); - auto problems = DUChainUtils::allProblemsForContext(top); - if (problems.isEmpty()) - return {}; + QVector allProblems; + auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); + foreach (auto modelData, modelsData) { + foreach (auto problem, modelData.model->problems(topContext->url())) { + allProblems += problem; + } + } - auto closestProblem = std::min_element(problems.constBegin(), problems.constEnd(), - [position](const ProblemPointer& a, const ProblemPointer& b) { - const auto aRange = a->rangeInCurrentRevision(); - const auto bRange = b->rangeInCurrentRevision(); + if (allProblems.isEmpty()) + return allProblems; + + std::sort(allProblems.begin(), allProblems.end(), + [position](const KDevelop::IProblem::Ptr a, const KDevelop::IProblem::Ptr b) { + const auto aRange = a->finalLocation(); + const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); - auto r = (*closestProblem)->rangeInCurrentRevision(); - if (!r.contains(position)) { - if (r.start().line() == position.line() || r.end().line() == position.line()) { - // problem is on the same line, let's use it - return *closestProblem; - } + QVector closestProblems; - // if not, only show it in case there's only whitespace - // between the current cursor position and the problem line - KTextEditor::Range dist; - KTextEditor::Cursor bound(r.start().line(), 0); - if (position < r.start()) - dist = KTextEditor::Range(position, bound); - else { - bound.setLine(r.end().line() + 1); - dist = KTextEditor::Range(bound, position); - } + // Show problems, located on the same line + foreach (auto problem, allProblems) { + auto r = problem->finalLocation(); + if (r.onSingleLine() && r.start().line() == position.line()) + closestProblems += problem; + else + break; + } - auto textBetween = view->document()->text(dist); - auto isSpace = std::all_of(textBetween.begin(), textBetween.end(), [](QChar c) { return c.isSpace(); }); - if (!isSpace) { - return {}; + // If not, only show it in case there's only whitespace + // between the current cursor position and the problem line + if (closestProblems.isEmpty()) { + foreach (auto problem, allProblems) { + auto r = problem->finalLocation(); + + KTextEditor::Range dist; + KTextEditor::Cursor bound(r.start().line(), 0); + if (position < r.start()) + dist = KTextEditor::Range(position, bound); + else { + bound.setLine(r.end().line() + 1); + dist = KTextEditor::Range(bound, position); + } + + if (view->document()->text(dist).trimmed().isEmpty()) + closestProblems += problem; + else + break; } } - return *closestProblem; + return closestProblems; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor - const auto problem = findProblemUnderCursor(topContext, position); - if (problem) { - if (problem == m_currentToolTipProblem && m_currentToolTip) { + const auto problems = findProblemsUnderCursor(topContext, position); + if (!problems.isEmpty()) { + if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } - m_currentToolTipProblem = problem; + m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; - auto context = new ProblemNavigationContext(problem); + auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor - const auto problem = findProblemCloseToCursor(topContext, position, view); - if (problem) { - if (problem == m_currentToolTipProblem && m_currentToolTip) { + const auto problems = findProblemsCloseToCursor(topContext, position, view); + if (!problems.isEmpty()) { + if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } - m_currentToolTipProblem = problem; + m_currentToolTipProblems = problems; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location - widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem, ProblemNavigationContext::ShowLocation))); + widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { - ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qWarning() << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index 1b17ede9a1..c0b7f7d1f4 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,282 +1,282 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library 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 KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class DUContext; class TopDUContext; class ReferencedTopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class ContextBrowserPlugin; class BrowseManager; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: ContextBrowserPlugin* m_plugin; }; QWidget* masterWidget(QWidget* w); struct ViewHighlights { ViewHighlights() : keep(false) { } // Whether the same highlighting should be kept highlighted (usually during typing) bool keep; // The declaration that is highlighted for this view KDevelop::IndexedDeclaration declaration; // Highlighted ranges. Those may also be contained by different views. QList highlights; }; class ContextBrowserPlugin : public KDevelop::IPlugin, public KDevelop::IContextBrowser { Q_OBJECT Q_INTERFACES( KDevelop::IContextBrowser ) public: explicit ContextBrowserPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ContextBrowserPlugin() override; void unload() override; void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context*) override; KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ) override; ///duchain must be locked ///@param force When this is true, the history-entry is added, no matter whether the context is "interesting" or not void updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void showUses(const KDevelop::DeclarationPointer& declaration) override; KTextEditor::Attribute::Ptr highlightedUseAttribute(KTextEditor::View* view) const; KTextEditor::Attribute::Ptr highlightedSpecialObjectAttribute(KTextEditor::View* view) const; public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); void textDocumentCreated( KDevelop::IDocument* document ); void documentActivated( KDevelop::IDocument* ); void viewDestroyed( QObject* obj ); void cursorPositionChanged( KTextEditor::View* view, const KTextEditor::Cursor& newPosition ); void viewCreated( KTextEditor::Document* , KTextEditor::View* ); void updateViews(); void hideToolTip(); void findUses(); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void selectionChanged(KTextEditor::View*); void historyNext(); void historyPrevious(); void colorSetupChanged(); private slots: // history browsing void documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position); void switchUse(bool forward); void clearMouseHover(); void addHighlight( KTextEditor::View* view, KDevelop::Declaration* decl ); /** helper for updateBrowserView(). * Tries to find a 'specialLanguageObject' (eg macro) in @p view under cursor @c. * If found returns true and sets @p pickedLanguage to the language this object belongs to */ KDevelop::Declaration* findDeclaration(KTextEditor::View* view, const KTextEditor::Cursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KTextEditor::Cursor& cursor) const; QString actionTextFor(int historyIndex) const; void updateButtonState(); void openDocument(int historyIndex); void fillHistoryPopup(QMenu* menu, const QList& historyIndices); enum NavigationActionType { Accept, Back, Down, Up, Left, Right }; void doNavigate(NavigationActionType action); private: // Returns the currently active and visible context browser view that belongs // to the same context (mainwindow and area) as the given widget ContextBrowserView* browserViewForWidget(QWidget* widget); void showToolTip(KTextEditor::View* view, KTextEditor::Cursor position); QTimer* m_updateTimer; //Contains the range, the old attribute, and the attribute it was replaced with QSet m_updateViews; QMap m_highlightedRanges; //Holds a list of all active context browser tool views QList m_views; //Used to override the next declaration that will be highlighted KDevelop::IndexedDeclaration m_useDeclaration; KDevelop::IndexedDeclaration m_lastHighlightedDeclaration; QUrl m_mouseHoverDocument; KTextEditor::Cursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; - KDevelop::Problem::Ptr m_currentToolTipProblem; + QVector m_currentToolTipProblems; QAction* m_findUses; QPointer m_lastInsertionDocument; KTextEditor::Cursor m_lastInsertionPos; // outline toolbar QPointer m_outlineLine; QPointer m_toolbarWidgetLayout; QPointer m_toolbarWidget; // history browsing struct HistoryEntry { //Duchain must be locked HistoryEntry(KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); HistoryEntry(KDevelop::DocumentCursor pos); //Duchain must be locked void setCursorPosition(const KTextEditor::Cursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KTextEditor::Cursor relativeCursorPosition; //Cursor position relative to the start line of the context QString alternativeString; }; QVector m_history; QPointer m_previousButton; QPointer m_nextButton; QPointer m_previousMenu, m_nextMenu; QList m_listDeclarations; KDevelop::IndexedString m_listUrl; BrowseManager* m_browseManager; //Used to not record jumps triggered by the context-browser as history entries QPointer m_focusBackWidget; int m_nextHistoryIndex; mutable KTextEditor::Attribute::Ptr m_highlightAttribute; friend class ContextBrowserHintProvider; ContextBrowserHintProvider m_textHintProvider; }; #endif // KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on