File indexing completed on 2024-04-28 04:38:21

0001 /*
0002     SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "contextbrowser.h"
0008 
0009 #include "contextbrowserview.h"
0010 #include "browsemanager.h"
0011 #include "debug.h"
0012 
0013 #include <cstdlib>
0014 
0015 #include <QAction>
0016 #include <QDebug>
0017 #include <QLayout>
0018 #include <QMenu>
0019 #include <QTimer>
0020 #include <QToolButton>
0021 #include <QWidgetAction>
0022 
0023 #include <KActionCollection>
0024 #include <KLocalizedString>
0025 #include <KPluginFactory>
0026 
0027 #include <KTextEditor/CodeCompletionInterface>
0028 #include <KTextEditor/Document>
0029 #include <KTextEditor/TextHintInterface>
0030 #include <KTextEditor/View>
0031 #include <KTextEditor/ConfigInterface>
0032 
0033 #include <interfaces/icore.h>
0034 #include <interfaces/idocumentcontroller.h>
0035 #include <interfaces/iuicontroller.h>
0036 #include <interfaces/ilanguagecontroller.h>
0037 #include <interfaces/contextmenuextension.h>
0038 #include <interfaces/iplugincontroller.h>
0039 
0040 #include <language/interfaces/codecontext.h>
0041 #include <language/interfaces/ilanguagesupport.h>
0042 #include <language/interfaces/iquickopen.h>
0043 
0044 #include <language/highlighting/colorcache.h>
0045 #include <language/highlighting/configurablecolors.h>
0046 #include <language/highlighting/codehighlighting.h>
0047 
0048 #include <language/duchain/duchain.h>
0049 #include <language/duchain/ducontext.h>
0050 #include <language/duchain/declaration.h>
0051 #include <language/duchain/use.h>
0052 #include <language/duchain/duchainutils.h>
0053 #include <language/duchain/functiondefinition.h>
0054 #include <language/duchain/parsingenvironment.h>
0055 #include <language/duchain/uses.h>
0056 #include <language/duchain/specializationstore.h>
0057 #include <language/duchain/aliasdeclaration.h>
0058 #include <language/duchain/types/functiontype.h>
0059 #include <language/duchain/navigation/abstractnavigationwidget.h>
0060 #include <language/duchain/navigation/problemnavigationcontext.h>
0061 #include <language/duchain/navigation/quickopenembeddedwidgetcombiner.h>
0062 
0063 #include <language/util/navigationtooltip.h>
0064 
0065 #include <shell/problemmodel.h>
0066 #include <shell/problemmodelset.h>
0067 
0068 #include <util/texteditorhelpers.h>
0069 
0070 #include <sublime/mainwindow.h>
0071 
0072 using KTextEditor::Attribute;
0073 using KTextEditor::View;
0074 using namespace KDevelop;
0075 
0076 // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent.
0077 QWidget* masterWidget(QWidget* w)
0078 {
0079     while (w && w->parent() && qobject_cast<QWidget*>(w->parent()))
0080         w = qobject_cast<QWidget*>(w->parent());
0081     return w;
0082 }
0083 
0084 namespace {
0085 const unsigned int highlightingTimeout = 150;
0086 const float highlightingZDepth = -5000;
0087 const int maxHistoryLength = 30;
0088 
0089 // Helper that determines the context to use for highlighting at a specific position
0090 DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext)
0091 {
0092     DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position));
0093     while (ctx && ctx->parentContext()
0094            && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper
0095                || ctx->localScopeIdentifier().isEmpty())) {
0096         ctx = ctx->parentContext();
0097     }
0098     return ctx;
0099 }
0100 
0101 ///Duchain must be locked
0102 DUContext* contextAt(const QUrl& url, KTextEditor::Cursor cursor)
0103 {
0104     TopDUContext* topContext = DUChainUtils::standardContextForUrl(url);
0105     if (!topContext)
0106         return nullptr;
0107     return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext);
0108 }
0109 
0110 DeclarationPointer cursorDeclaration()
0111 {
0112     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0113     if (!view) {
0114         return DeclarationPointer();
0115     }
0116 
0117     DUChainReadLocker lock;
0118 
0119     Declaration* decl =
0120         DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(),
0121                                                                              KTextEditor::Cursor(
0122                                                                                  view->cursorPosition())).declaration);
0123     return DeclarationPointer(decl);
0124 }
0125 }
0126 
0127 class ContextBrowserViewFactory
0128     : public KDevelop::IToolViewFactory
0129 {
0130 public:
0131     explicit ContextBrowserViewFactory(ContextBrowserPlugin* plugin) : m_plugin(plugin) {}
0132 
0133     QWidget* create(QWidget* parent = nullptr) override
0134     {
0135         auto* ret = new ContextBrowserView(m_plugin, parent);
0136         return ret;
0137     }
0138 
0139     Qt::DockWidgetArea defaultPosition() const override
0140     {
0141         return Qt::BottomDockWidgetArea;
0142     }
0143 
0144     QString id() const override
0145     {
0146         return QStringLiteral("org.kdevelop.ContextBrowser");
0147     }
0148 
0149 private:
0150     ContextBrowserPlugin* m_plugin;
0151 };
0152 
0153 KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow(Sublime::MainWindow* window)
0154 {
0155     m_browseManager = new BrowseManager(this);
0156 
0157     KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window);
0158 
0159     connect(
0160         ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this,
0161         &ContextBrowserPlugin::documentJumpPerformed);
0162 
0163     m_previousButton = new QToolButton();
0164     m_previousButton->setToolTip(i18nc("@info:tooltip", "Go back in context history"));
0165     m_previousButton->setAutoRaise(true);
0166     m_previousButton->setPopupMode(QToolButton::MenuButtonPopup);
0167     m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
0168     m_previousButton->setEnabled(false);
0169     m_previousButton->setFocusPolicy(Qt::NoFocus);
0170     m_previousMenu = new QMenu(m_previousButton);
0171     m_previousButton->setMenu(m_previousMenu);
0172     connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious);
0173     connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow);
0174 
0175     m_nextButton = new QToolButton();
0176     m_nextButton->setToolTip(i18nc("@info:tooltip", "Go forward in context history"));
0177     m_nextButton->setAutoRaise(true);
0178     m_nextButton->setPopupMode(QToolButton::MenuButtonPopup);
0179     m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
0180     m_nextButton->setEnabled(false);
0181     m_nextButton->setFocusPolicy(Qt::NoFocus);
0182     m_nextMenu = new QMenu(m_nextButton);
0183     m_nextButton->setMenu(m_nextMenu);
0184     connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext);
0185     connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow);
0186 
0187     auto* quickOpen =
0188         KDevelop::ICore::self()->pluginController()->extensionForPlugin<IQuickOpen>(QStringLiteral(
0189                                                                                         "org.kdevelop.IQuickOpen"));
0190 
0191     if (quickOpen) {
0192         m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList(i18nc("item quick open item type", "Outline")), IQuickOpen::Outline);
0193         m_outlineLine->setPlaceholderText(i18nc("@info:placeholder", "Outline"));
0194         m_outlineLine->setToolTip(i18nc("@info:tooltip", "Navigate outline of active document, click to browse"));
0195     }
0196 
0197     connect(m_browseManager, &BrowseManager::startDelayedBrowsing,
0198             this, &ContextBrowserPlugin::startDelayedBrowsing);
0199     connect(m_browseManager, &BrowseManager::stopDelayedBrowsing,
0200             this, &ContextBrowserPlugin::stopDelayedBrowsing);
0201     connect(m_browseManager, &BrowseManager::invokeAction,
0202             this, &ContextBrowserPlugin::invokeAction);
0203 
0204     m_toolbarWidget = toolbarWidgetForMainWindow(window);
0205     m_toolbarWidgetLayout = new QHBoxLayout;
0206     m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize);
0207     m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0208     m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0209     m_toolbarWidgetLayout->setContentsMargins(0, 0, 0, 0);
0210 
0211     m_toolbarWidgetLayout->addWidget(m_previousButton);
0212     if (m_outlineLine) {
0213         m_toolbarWidgetLayout->addWidget(m_outlineLine);
0214         m_outlineLine->setMaximumWidth(600);
0215         connect(ICore::self()->documentController(), &IDocumentController::documentClosed,
0216                 m_outlineLine.data(), &QLineEdit::clear);
0217     }
0218     m_toolbarWidgetLayout->addWidget(m_nextButton);
0219 
0220     if (m_toolbarWidget->children().isEmpty())
0221         m_toolbarWidget->setLayout(m_toolbarWidgetLayout);
0222 
0223     connect(ICore::self()->documentController(), &IDocumentController::documentActivated,
0224             this, &ContextBrowserPlugin::documentActivated);
0225 
0226     return ret;
0227 }
0228 
0229 void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile,
0230                                                       KActionCollection& actions)
0231 {
0232     xmlFile = QStringLiteral("kdevcontextbrowser.rc");
0233 
0234     QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode"));
0235     sourceBrowseMode->setText(i18nc("@action", "Source &Browse Mode"));
0236     sourceBrowseMode->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0237     sourceBrowseMode->setCheckable(true);
0238     connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing);
0239 
0240     QAction* previousContext = actions.addAction(QStringLiteral("previous_context"));
0241     previousContext->setText(i18nc("@action", "&Previous Visited Context"));
0242     previousContext->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-context")));
0243     actions.setDefaultShortcut(previousContext, Qt::META | Qt::Key_Left);
0244     QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut);
0245 
0246     QAction* nextContext = actions.addAction(QStringLiteral("next_context"));
0247     nextContext->setText(i18nc("@action", "&Next Visited Context"));
0248     nextContext->setIcon(QIcon::fromTheme(QStringLiteral("go-next-context")));
0249     actions.setDefaultShortcut(nextContext, Qt::META | Qt::Key_Right);
0250     QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut);
0251 
0252     QAction* previousUse = actions.addAction(QStringLiteral("previous_use"));
0253     previousUse->setText(i18nc("@action", "&Previous Use"));
0254     previousUse->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-use")));
0255     actions.setDefaultShortcut(previousUse, Qt::META | Qt::SHIFT |  Qt::Key_Left);
0256     QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut);
0257 
0258     QAction* nextUse = actions.addAction(QStringLiteral("next_use"));
0259     nextUse->setText(i18nc("@action", "&Next Use"));
0260     nextUse->setIcon(QIcon::fromTheme(QStringLiteral("go-next-use")));
0261     actions.setDefaultShortcut(nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right);
0262     QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut);
0263 
0264     auto* outline = new QWidgetAction(this);
0265     outline->setText(i18nc("@action", "Context Browser"));
0266     QWidget* w = toolbarWidgetForMainWindow(window);
0267     w->setHidden(false);
0268     outline->setDefaultWidget(w);
0269     actions.addAction(QStringLiteral("outline_line"), outline);
0270     // Add to the actioncollection so one can set global shortcuts for the action
0271     actions.addAction(QStringLiteral("find_uses"), m_findUses);
0272 }
0273 
0274 void ContextBrowserPlugin::nextContextShortcut()
0275 {
0276     // TODO: cleanup
0277     historyNext();
0278 }
0279 
0280 void ContextBrowserPlugin::previousContextShortcut()
0281 {
0282     // TODO: cleanup
0283     historyPrevious();
0284 }
0285 
0286 K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin<ContextBrowserPlugin>(); )
0287 
0288 ContextBrowserPlugin::ContextBrowserPlugin(QObject* parent, const QVariantList&)
0289     : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent)
0290     , m_viewFactory(new ContextBrowserViewFactory(this))
0291     , m_nextHistoryIndex(0)
0292     , m_textHintProvider(this)
0293 {
0294     qRegisterMetaType<KDevelop::IndexedDeclaration>("KDevelop::IndexedDeclaration");
0295 
0296     core()->uiController()->addToolView(i18nc("@title:window", "Code Browser"), m_viewFactory);
0297 
0298     connect(
0299         core()->documentController(), &IDocumentController::textDocumentCreated, this,
0300         &ContextBrowserPlugin::textDocumentCreated);
0301     connect(DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady);
0302     connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged);
0303 
0304     connect(DUChain::self(), &DUChain::declarationSelected,
0305             this, &ContextBrowserPlugin::declarationSelectedInUI);
0306 
0307     m_updateTimer = new QTimer(this);
0308     m_updateTimer->setSingleShot(true);
0309     connect(m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews);
0310 
0311     //Needed global action for the context-menu extensions
0312     m_findUses = new QAction(i18nc("@action", "Find Uses"), this);
0313     connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses);
0314 
0315     const auto documents = core()->documentController()->openDocuments();
0316     for (KDevelop::IDocument* document : documents) {
0317         textDocumentCreated(document);
0318     }
0319 }
0320 
0321 ContextBrowserPlugin::~ContextBrowserPlugin()
0322 {
0323     for (auto* view : qAsConst(m_textHintProvidedViews)) {
0324         auto* iface = qobject_cast<KTextEditor::TextHintInterface*>(view);
0325         iface->unregisterTextHintProvider(&m_textHintProvider);
0326     }
0327 
0328     ///TODO: QObject inheritance should suffice?
0329     delete m_nextMenu;
0330     delete m_previousMenu;
0331     delete m_toolbarWidgetLayout;
0332 
0333     delete m_previousButton;
0334     delete m_outlineLine;
0335     delete m_nextButton;
0336 }
0337 
0338 void ContextBrowserPlugin::unload()
0339 {
0340     core()->uiController()->removeToolView(m_viewFactory);
0341 }
0342 
0343 KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0344 {
0345     KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent);
0346 
0347     auto* codeContext = dynamic_cast<KDevelop::DeclarationContext*>(context);
0348 
0349     if (!codeContext)
0350         return menuExt;
0351 
0352     DUChainReadLocker lock(DUChain::lock());
0353 
0354     if (!codeContext->declaration().data())
0355         return menuExt;
0356 
0357     menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_findUses);
0358 
0359     return menuExt;
0360 }
0361 
0362 void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration)
0363 {
0364     QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection,
0365                               Q_ARG(KDevelop::DeclarationPointer, declaration));
0366 }
0367 
0368 void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration)
0369 {
0370     DUChainReadLocker lock;
0371 
0372     Declaration* decl = declaration.data();
0373     if (!decl) {
0374         return;
0375     }
0376     QWidget* toolView = ICore::self()->uiController()->findToolView(i18nc("@title:window", "Code Browser"), m_viewFactory,
0377                                                                     KDevelop::IUiController::CreateAndRaise);
0378     if (!toolView) {
0379         return;
0380     }
0381     auto* view = qobject_cast<ContextBrowserView*>(toolView);
0382     Q_ASSERT(view);
0383     view->allowLockedUpdate();
0384     view->setDeclaration(decl, decl->topContext(), true);
0385     //We may get deleted while the call to acceptLink, so make sure we don't crash in that case
0386     QPointer<AbstractNavigationWidget> widget = qobject_cast<AbstractNavigationWidget*>(view->navigationWidget());
0387     if (widget && widget->context()) {
0388         auto nextContext = widget->context()->execute(
0389             NavigationAction(declaration, KDevelop::NavigationAction::ShowUses));
0390 
0391         if (widget) {
0392             widget->setContext(nextContext);
0393         }
0394     }
0395 }
0396 
0397 void ContextBrowserPlugin::findUses()
0398 {
0399     showUses(cursorDeclaration());
0400 }
0401 
0402 ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin)
0403     : m_plugin(plugin)
0404 {
0405 }
0406 
0407 QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor)
0408 {
0409     m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor);
0410     if (!view) {
0411         qCWarning(PLUGIN_CONTEXTBROWSER) << "could not cast to view";
0412     } else {
0413         m_plugin->m_mouseHoverDocument = view->document()->url();
0414         m_plugin->m_updateViews << view;
0415     }
0416     m_plugin->m_updateTimer->start(1); // triggers updateViews()
0417 
0418     m_plugin->showToolTip(view, cursor);
0419     return QString();
0420 }
0421 
0422 void ContextBrowserPlugin::stopDelayedBrowsing()
0423 {
0424     hideToolTip();
0425 }
0426 
0427 void ContextBrowserPlugin::invokeAction(int index)
0428 {
0429     if (!m_currentNavigationWidget)
0430         return;
0431 
0432     auto navigationWidget = qobject_cast<AbstractNavigationWidget*>(m_currentNavigationWidget);
0433     if (!navigationWidget)
0434         return;
0435 
0436     // TODO: Add API in AbstractNavigation{Widget,Context}?
0437     QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index));
0438 }
0439 
0440 void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view)
0441 {
0442     if (!m_currentToolTip) {
0443         showToolTip(view, view->cursorPosition());
0444     }
0445 }
0446 
0447 void ContextBrowserPlugin::hideToolTip()
0448 {
0449     if (m_currentToolTip) {
0450         m_currentToolTip->deleteLater();
0451         m_currentToolTip = nullptr;
0452         m_currentNavigationWidget = nullptr;
0453         m_currentToolTipProblems.clear();
0454         m_currentToolTipDeclaration = {};
0455     }
0456 }
0457 
0458 static QVector<KDevelop::IProblem::Ptr> findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position,
0459                                                                 KTextEditor::Range& handleRange)
0460 {
0461     QVector<KDevelop::IProblem::Ptr> problems;
0462     handleRange = KTextEditor::Range::invalid();
0463 
0464     const auto modelsData = ICore::self()->languageController()->problemModelSet()->models();
0465     for (const auto& modelData : modelsData) {
0466         const auto modelProblems = modelData.model->problems(topContext->url());
0467         for (const auto& problem : modelProblems) {
0468             DocumentRange problemRange = problem->finalLocation();
0469             if (problemRange.contains(position) ||
0470                 (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) {
0471                 problems += problem;
0472                 // first?
0473                 if (!handleRange.isValid()) {
0474                     handleRange = problemRange;
0475                 } else {
0476                     handleRange.confineToRange(problemRange);
0477                 }
0478             }
0479         }
0480     }
0481 
0482     return problems;
0483 }
0484 
0485 static QVector<KDevelop::IProblem::Ptr> findProblemsCloseToCursor(const TopDUContext* topContext,
0486                                                                   KTextEditor::Cursor position,
0487                                                                   KTextEditor::Range& handleRange)
0488 {
0489     handleRange = KTextEditor::Range::invalid();
0490 
0491     QVector<KDevelop::IProblem::Ptr> allProblems;
0492     const auto modelsData = ICore::self()->languageController()->problemModelSet()->models();
0493     for (const auto& modelData : modelsData) {
0494         const auto problems = modelData.model->problems(topContext->url());
0495         allProblems.reserve(allProblems.size() + problems.size());
0496         for (const auto& problem : problems) {
0497             allProblems += problem;
0498         }
0499     }
0500 
0501     if (allProblems.isEmpty())
0502         return allProblems;
0503 
0504     std::sort(allProblems.begin(), allProblems.end(),
0505               [position](const KDevelop::IProblem::Ptr& a, const KDevelop::IProblem::Ptr& b) {
0506         const auto aRange = a->finalLocation();
0507         const auto bRange = b->finalLocation();
0508 
0509         const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()),
0510                                         qAbs(aRange.end().line() - position.line()));
0511         const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()),
0512                                         qAbs(bRange.end().line() - position.line()));
0513         if (aLineDistance != bLineDistance) {
0514             return aLineDistance < bLineDistance;
0515         }
0516 
0517         if (aRange.start().line() == bRange.start().line()) {
0518             return qAbs(aRange.start().column() - position.column()) <
0519             qAbs(bRange.start().column() - position.column());
0520         }
0521         return qAbs(aRange.end().column() - position.column()) <
0522         qAbs(bRange.end().column() - position.column());
0523     });
0524 
0525     QVector<KDevelop::IProblem::Ptr> closestProblems;
0526 
0527     // Show problems, located on the same line
0528     for (auto& problem : qAsConst(allProblems)) {
0529         auto r = problem->finalLocation();
0530         if (r.onSingleLine() && r.start().line() == position.line())
0531             closestProblems += problem;
0532         else
0533             break;
0534     }
0535 
0536     if (!closestProblems.isEmpty()) {
0537         auto it = closestProblems.constBegin();
0538         handleRange = (*it)->finalLocation();
0539         ++it;
0540         for (auto end = closestProblems.constEnd(); it != end; ++it) {
0541             handleRange.confineToRange((*it)->finalLocation());
0542         }
0543     }
0544 
0545     return closestProblems;
0546 }
0547 
0548 QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position,
0549                                                            KTextEditor::Range& itemRange)
0550 {
0551     QUrl viewUrl = view->document()->url();
0552     const auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl);
0553 
0554     DUChainReadLocker lock(DUChain::lock());
0555 
0556     for (const auto language : languages) {
0557         auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, position);
0558         auto navigationWidget = qobject_cast<AbstractNavigationWidget*>(widget.first);
0559         if (navigationWidget) {
0560             itemRange = widget.second;
0561             return navigationWidget;
0562         }
0563     }
0564 
0565     // Find problems under the cursor (first pass)
0566     TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url());
0567     QVector<KDevelop::IProblem::Ptr> problems;
0568     if (topContext) {
0569         problems = findProblemsUnderCursor(topContext, position, itemRange);
0570     }
0571 
0572     // Find decl (declaration) under the cursor
0573     const auto itemUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position);
0574     auto declUnderCursor = itemUnderCursor.declaration;
0575     Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor);
0576     if (decl && decl->kind() == Declaration::Alias) {
0577         auto* alias = dynamic_cast<AliasDeclaration*>(decl);
0578         Q_ASSERT(alias);
0579         decl = alias->aliasedDeclaration().declaration();
0580     }
0581 
0582     // Return nullptr if the found problems / decl are already being shown in the tool tip currently.
0583     if (m_currentToolTip &&
0584         problems == m_currentToolTipProblems &&
0585         IndexedDeclaration(decl) == m_currentToolTipDeclaration) {
0586         return nullptr;
0587     }
0588 
0589     // Create a widget for problems, if any have been found.
0590     AbstractNavigationWidget* problemWidget = nullptr;
0591     if (!problems.isEmpty()) {
0592         problemWidget = new AbstractNavigationWidget;
0593         auto context = new ProblemNavigationContext(problems);
0594         context->setTopContext(TopDUContextPointer(topContext));
0595         problemWidget->setContext(NavigationContextPointer(context));
0596     }
0597 
0598     // Let the context create a widget for decl, if there is one.
0599     // Note that createNavigationWidget() might also return nullptr for a valid decl however.
0600     AbstractNavigationWidget* declWidget = nullptr;
0601     if (decl) {
0602         if (itemRange.isValid()) {
0603             itemRange.expandToRange(itemUnderCursor.range);
0604         } else {
0605             itemRange = itemUnderCursor.range;
0606         }
0607         declWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl));
0608     }
0609 
0610     // If at least one widget was created for problems or decl, show it.
0611     // If two widgets were created, combine them.
0612     if (problemWidget || declWidget) {
0613         // Remember current tool tip state.
0614         m_currentToolTipProblems = problems;
0615         m_currentToolTipDeclaration = IndexedDeclaration(decl);
0616 
0617         if (problemWidget && declWidget) {
0618             auto* combinedWidget = new QuickOpenEmbeddedWidgetCombiner;
0619             combinedWidget->layout()->addWidget(problemWidget);
0620             combinedWidget->layout()->addWidget(declWidget);
0621             return combinedWidget;
0622         }
0623         if (problemWidget) {
0624             return problemWidget;
0625         }
0626         return declWidget;
0627     }
0628 
0629     // Nothing has been found so far which created a widget.
0630     // Thus, find the closest problem to the cursor in a second pass.
0631     if (topContext) {
0632         problems = findProblemsCloseToCursor(topContext, position, itemRange);
0633         if (!problems.isEmpty()) {
0634             // Return nullptr if the correct contents are already being shown in the tool tip currently.
0635             if (m_currentToolTip &&
0636                 problems == m_currentToolTipProblems &&
0637                 !m_currentToolTipDeclaration.isValid()) {
0638                 return nullptr;
0639             }
0640 
0641             // Remember current tool tip state.
0642             m_currentToolTipProblems = problems;
0643             m_currentToolTipDeclaration = {};
0644 
0645             auto widget = new AbstractNavigationWidget;
0646             // since the problem is not under cursor: show location
0647             widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems,
0648                                                                                      ProblemNavigationContext::
0649                                                                                      ShowLocation)));
0650             return widget;
0651         }
0652     }
0653 
0654     // Nothing to show has been found under or next to the cursor, so hide the tool tip (if visible).
0655     hideToolTip();
0656     return nullptr;
0657 }
0658 
0659 void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position)
0660 {
0661     ContextBrowserView* contextView = browserViewForWidget(view);
0662     if (contextView && contextView->isVisible() && !contextView->isLocked())
0663         return; // If the context-browser view is visible, it will care about updating by itself
0664 
0665     KTextEditor::Range itemRange = KTextEditor::Range::invalid();
0666     auto navigationWidget = navigationWidgetForPosition(view, position, itemRange);
0667     if (navigationWidget) {
0668         // If we have an invisible context-view, assign the tooltip navigation-widget to it.
0669         // If the user makes the context-view visible, it will instantly contain the correct widget.
0670         if (contextView && !contextView->isLocked())
0671             contextView->setNavigationWidget(navigationWidget);
0672 
0673         if (m_currentToolTip) {
0674             m_currentToolTip->deleteLater();
0675             m_currentToolTip = nullptr;
0676             m_currentNavigationWidget = nullptr;
0677         }
0678 
0679         auto* tooltip =
0680             new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20,
0681                                                                                                                  40),
0682                                             navigationWidget);
0683         if (!itemRange.isValid()) {
0684             qCWarning(PLUGIN_CONTEXTBROWSER) << "Got navigationwidget with invalid itemrange";
0685             itemRange = KTextEditor::Range(position, 0);
0686         }
0687 
0688         tooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, itemRange));
0689         tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10));
0690         QObject::connect(view, &KTextEditor::View::verticalScrollPositionChanged,
0691                          this, &ContextBrowserPlugin::hideToolTip);
0692         QObject::connect(view, &KTextEditor::View::horizontalScrollPositionChanged,
0693                          this, &ContextBrowserPlugin::hideToolTip);
0694         qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size();
0695         m_currentToolTip = tooltip;
0696         m_currentNavigationWidget = navigationWidget;
0697         ActiveToolTip::showToolTip(tooltip);
0698 
0699         if (!navigationWidget->property("DoNotCloseOnCursorMove").toBool()) {
0700             connect(view, &View::cursorPositionChanged,
0701                     this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection);
0702         } else {
0703             disconnect(view, &View::cursorPositionChanged,
0704                        this, &ContextBrowserPlugin::hideToolTip);
0705         }
0706     } else {
0707         qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget";
0708     }
0709 }
0710 
0711 void ContextBrowserPlugin::clearMouseHover()
0712 {
0713     m_mouseHoverCursor = KTextEditor::Cursor::invalid();
0714     m_mouseHoverDocument.clear();
0715 }
0716 
0717 Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute() const
0718 {
0719     if (!m_highlightAttribute) {
0720         m_highlightAttribute = ColorCache::self()->defaultColors()->attribute(CodeHighlightingType::HighlightUses);
0721     }
0722     return m_highlightAttribute;
0723 }
0724 
0725 void ContextBrowserPlugin::colorSetupChanged()
0726 {
0727     m_highlightAttribute = Attribute::Ptr();
0728 }
0729 
0730 Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute() const
0731 {
0732     return highlightedUseAttribute();
0733 }
0734 
0735 void ContextBrowserPlugin::addHighlight(View* view, KDevelop::Declaration* decl)
0736 {
0737     if (!view || !decl) {
0738         qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration";
0739         return;
0740     }
0741 
0742     ViewHighlights& highlights(m_highlightedRanges[view]);
0743 
0744     KDevelop::DUChainReadLocker lock;
0745 
0746     // Highlight the declaration
0747     highlights.highlights << decl->createRangeMoving();
0748     highlights.highlights.back()->setAttribute(highlightedUseAttribute());
0749     highlights.highlights.back()->setZDepth(highlightingZDepth);
0750 
0751     // Highlight uses
0752     {
0753         const auto currentRevisionUses = decl->usesCurrentRevision();
0754         for (auto fileIt = currentRevisionUses.constBegin(); fileIt != currentRevisionUses.constEnd(); ++fileIt) {
0755             const auto& document = fileIt.key();
0756             const auto& documentUses = fileIt.value();
0757             for (auto& use : documentUses) {
0758                 highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(use, document));
0759                 highlights.highlights.back()->setAttribute(highlightedUseAttribute());
0760                 highlights.highlights.back()->setZDepth(highlightingZDepth);
0761             }
0762         }
0763     }
0764 
0765     if (auto* def = FunctionDefinition::definition(decl)) {
0766         highlights.highlights << def->createRangeMoving();
0767         highlights.highlights.back()->setAttribute(highlightedUseAttribute());
0768         highlights.highlights.back()->setZDepth(highlightingZDepth);
0769     }
0770 }
0771 
0772 Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight)
0773 {
0774     Q_UNUSED(mouseHighlight);
0775     ENSURE_CHAIN_READ_LOCKED
0776 
0777     Declaration* foundDeclaration = nullptr;
0778     if (m_useDeclaration.data()) {
0779         foundDeclaration = m_useDeclaration.data();
0780     } else {
0781         //If we haven't found a special language object, search for a use/declaration and eventually highlight it
0782         foundDeclaration =
0783             DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(
0784                                                        view->document()->url(), position).declaration);
0785         if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) {
0786             auto* alias = dynamic_cast<AliasDeclaration*>(foundDeclaration);
0787             Q_ASSERT(alias);
0788             foundDeclaration = alias->aliasedDeclaration().declaration();
0789         }
0790     }
0791     return foundDeclaration;
0792 }
0793 
0794 ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) const
0795 {
0796     const auto masterWidgetOfWidget = masterWidget(widget);
0797     auto it = std::find_if(m_views.begin(), m_views.end(), [&](ContextBrowserView* contextView) {
0798         return (masterWidget(contextView) == masterWidgetOfWidget);
0799     });
0800 
0801     return (it != m_views.end()) ? *it : nullptr;
0802 }
0803 
0804 void ContextBrowserPlugin::updateForView(View* view)
0805 {
0806     bool allowHighlight = true;
0807     if (view->selection()) {
0808         // If something is selected, we unhighlight everything, so that we don't conflict with the
0809         // kate plugin that highlights occurrences of the selected string, and also to reduce the
0810         // overall amount of concurrent highlighting.
0811         allowHighlight = false;
0812     }
0813 
0814     if (m_highlightedRanges[view].keep) {
0815         m_highlightedRanges[view].keep = false;
0816         return;
0817     }
0818 
0819     // Clear all highlighting
0820     m_highlightedRanges.clear();
0821 
0822     // Re-highlight
0823     ViewHighlights& highlights = m_highlightedRanges[view];
0824 
0825     QUrl url = view->document()->url();
0826     IDocument* activeDoc = core()->documentController()->activeDocument();
0827 
0828     bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid());
0829     bool shouldUpdateBrowser =
0830         (mouseHighlight ||
0831          (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc &&
0832           activeDoc->textDocument() == view->document()));
0833 
0834     KTextEditor::Cursor highlightPosition;
0835     if (mouseHighlight)
0836         highlightPosition = m_mouseHoverCursor;
0837     else
0838         highlightPosition = KTextEditor::Cursor(view->cursorPosition());
0839 
0840     ///Pick a language
0841     ILanguageSupport* const language = ICore::self()->languageController()->languagesForUrl(url).value(0);
0842     if (!language) {
0843         qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url;
0844         return;
0845     }
0846 
0847     ///Check whether there is a special language object to highlight (for example a macro)
0848 
0849     KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition);
0850     ContextBrowserView* updateBrowserView = shouldUpdateBrowser ?  browserViewForWidget(view) : nullptr;
0851 
0852     if (specialRange.isValid()) {
0853         // Highlight a special language object
0854         if (allowHighlight) {
0855             highlights.highlights <<
0856                 PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url)));
0857             highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute());
0858             highlights.highlights.back()->setZDepth(highlightingZDepth);
0859         }
0860         if (updateBrowserView)
0861             updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url,
0862                                                                                                           highlightPosition).first);
0863     } else {
0864         KDevelop::DUChainReadLocker lock(DUChain::lock(), 100);
0865         if (!lock.locked()) {
0866             qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time";
0867             return;
0868         }
0869 
0870         TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url());
0871         if (!topContext)
0872             return;
0873         DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext);
0874         if (!ctx)
0875             return;
0876 
0877         //Only update the history if this context is around the text cursor
0878         if (core()->documentController()->activeDocument() &&
0879             highlightPosition == KTextEditor::Cursor(view->cursorPosition()) &&
0880             view->document() == core()->documentController()->activeDocument()->textDocument()) {
0881             updateHistory(ctx, highlightPosition);
0882         }
0883 
0884         Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight);
0885 
0886         if (foundDeclaration) {
0887             m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration);
0888             if (allowHighlight)
0889                 addHighlight(view, foundDeclaration);
0890 
0891             if (updateBrowserView)
0892                 updateBrowserView->setDeclaration(foundDeclaration, topContext);
0893         } else {
0894             if (updateBrowserView)
0895                 updateBrowserView->setContext(ctx);
0896         }
0897     }
0898 }
0899 
0900 void ContextBrowserPlugin::updateViews()
0901 {
0902     for (View* view : qAsConst(m_updateViews)) {
0903         updateForView(view);
0904     }
0905 
0906     m_updateViews.clear();
0907     m_useDeclaration = IndexedDeclaration();
0908 }
0909 
0910 void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl)
0911 {
0912     m_useDeclaration = IndexedDeclaration(decl.data());
0913     KTextEditor::View* view = core()->documentController()->activeTextDocumentView();
0914     if (view)
0915         m_updateViews << view;
0916 
0917     if (!m_updateViews.isEmpty())
0918         m_updateTimer->start(highlightingTimeout); // triggers updateViews()
0919 }
0920 
0921 void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/)
0922 {
0923     const auto url = file.toUrl();
0924     for (QMap<View*, ViewHighlights>::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end();
0925          ++it) {
0926         if (it.key()->document()->url() == url) {
0927             if (!m_updateViews.contains(it.key())) {
0928                 qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update";
0929                 m_updateViews << it.key();
0930 
0931                 // Don't change the highlighted declaration after finished parse-jobs
0932                 (*it).keep = true;
0933             }
0934         }
0935     }
0936 
0937     if (!m_updateViews.isEmpty())
0938         m_updateTimer->start(highlightingTimeout);
0939 }
0940 
0941 void ContextBrowserPlugin::textDocumentCreated(KDevelop::IDocument* document)
0942 {
0943     Q_ASSERT(document->textDocument());
0944 
0945     connect(document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated);
0946 
0947     const auto views = document->textDocument()->views();
0948     for (View* view : views) {
0949         viewCreated(document->textDocument(), view);
0950     }
0951 }
0952 
0953 void ContextBrowserPlugin::documentActivated(IDocument* doc)
0954 {
0955     if (m_outlineLine)
0956         m_outlineLine->clear();
0957 
0958     if (View* view = doc->activeTextView()) {
0959         cursorPositionChanged(view, view->cursorPosition());
0960     }
0961 }
0962 
0963 void ContextBrowserPlugin::viewDestroyed(QObject* obj)
0964 {
0965     m_highlightedRanges.remove(static_cast<KTextEditor::View*>(obj));
0966     m_updateViews.remove(static_cast<View*>(obj));
0967     m_textHintProvidedViews.removeOne(static_cast<KTextEditor::View*>(obj));
0968 }
0969 
0970 void ContextBrowserPlugin::selectionChanged(View* view)
0971 {
0972     clearMouseHover();
0973     m_updateViews.insert(view);
0974     m_updateTimer->start(highlightingTimeout / 2); // triggers updateViews()
0975 }
0976 
0977 void ContextBrowserPlugin::cursorPositionChanged(View* view, const KTextEditor::Cursor& newPosition)
0978 {
0979     const bool atInsertPosition = (view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos);
0980     if (atInsertPosition) {
0981         //Do not update the highlighting while typing
0982         m_lastInsertionDocument = nullptr;
0983         m_lastInsertionPos = KTextEditor::Cursor();
0984     }
0985 
0986     const auto viewHighlightsIt = m_highlightedRanges.find(view);
0987     if (viewHighlightsIt != m_highlightedRanges.end()) {
0988         viewHighlightsIt->keep = atInsertPosition;
0989     }
0990 
0991     clearMouseHover();
0992     m_updateViews.insert(view);
0993     m_updateTimer->start(highlightingTimeout / 2); // triggers updateViews()
0994 }
0995 
0996 void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor,
0997                                         const QString& text)
0998 {
0999     m_lastInsertionDocument = doc;
1000     m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size());
1001 }
1002 
1003 void ContextBrowserPlugin::viewCreated(KTextEditor::Document*, View* v)
1004 {
1005     disconnect(v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged); ///Just to make sure that multiple connections don't happen
1006     connect(v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged);
1007     connect(v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed);
1008     disconnect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted);
1009     connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted);
1010     disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged);
1011 
1012     auto* iface = qobject_cast<KTextEditor::TextHintInterface*>(v);
1013     if (!iface)
1014         return;
1015 
1016     if (m_textHintProvidedViews.contains(v)) {
1017         return;
1018     }
1019     iface->setTextHintDelay(highlightingTimeout);
1020     iface->registerTextHintProvider(&m_textHintProvider);
1021     m_textHintProvidedViews.append(v);
1022 }
1023 
1024 void ContextBrowserPlugin::registerToolView(ContextBrowserView* view)
1025 {
1026     m_views << view;
1027 }
1028 
1029 void ContextBrowserPlugin::previousUseShortcut()
1030 {
1031     switchUse(false);
1032 }
1033 
1034 void ContextBrowserPlugin::nextUseShortcut()
1035 {
1036     switchUse(true);
1037 }
1038 
1039 KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor)
1040 {
1041     return KTextEditor::Range(cursor, cursor);
1042 }
1043 
1044 void ContextBrowserPlugin::switchUse(bool forward)
1045 {
1046     View* view = core()->documentController()->activeTextDocumentView();
1047     if (view) {
1048         KTextEditor::Document* doc = view->document();
1049         KDevelop::DUChainReadLocker lock(DUChain::lock());
1050         KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url());
1051 
1052         if (chosen) {
1053             KTextEditor::Cursor cCurrent(view->cursorPosition());
1054             KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent);
1055 
1056             Declaration* decl = nullptr;
1057             //If we have a locked declaration, use that for jumping
1058             for (ContextBrowserView* view : qAsConst(m_views)) {
1059                 decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple
1060                 if (decl)
1061                     break;
1062             }
1063 
1064             if (!decl) //Try finding a declaration under the cursor
1065                 decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration;
1066 
1067             if (decl && decl->kind() == Declaration::Alias) {
1068                 auto* alias = dynamic_cast<AliasDeclaration*>(decl);
1069                 Q_ASSERT(alias);
1070                 decl = alias->aliasedDeclaration().declaration();
1071             }
1072 
1073             if (decl) {
1074                 Declaration* target = nullptr;
1075                 if (forward)
1076                     //Try jumping from definition to declaration
1077                     target = DUChainUtils::declarationForDefinition(decl, chosen);
1078                 else if (decl->url().toUrl() == doc->url() && decl->range().contains(c))
1079                     //Try jumping from declaration to definition
1080                     target = FunctionDefinition::definition(decl);
1081 
1082                 if (target && target != decl) {
1083                     KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start();
1084                     QUrl document = target->url().toUrl();
1085                     lock.unlock();
1086                     core()->documentController()->openDocument(document, cursorToRange(jumpTo));
1087                     return;
1088                 } else {
1089                     //Always work with the declaration instead of the definition
1090                     decl = DUChainUtils::declarationForDefinition(decl, chosen);
1091                 }
1092             }
1093 
1094             if (!decl) {
1095                 //Pick the last use we have highlighted
1096                 decl = m_lastHighlightedDeclaration.data();
1097             }
1098 
1099             if (decl) {
1100                 KDevVarLengthArray<IndexedTopDUContext> usingFiles = DUChain::uses()->uses(decl->id());
1101 
1102                 if (DUChainUtils::contextHasUse(decl->topContext(),
1103                                                 decl) && usingFiles.indexOf(decl->topContext()) == -1)
1104                     usingFiles.insert(0, decl->topContext());
1105 
1106                 if (decl->range().contains(c) && decl->url() == chosen->url()) {
1107                     //The cursor is directly on the declaration. Jump to the first or last use.
1108                     if (!usingFiles.isEmpty()) {
1109                         TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data();
1110                         if (top) {
1111                             QVector<RangeInRevision> useRanges = allUses(top, decl, true);
1112                             std::sort(useRanges.begin(), useRanges.end());
1113                             if (!useRanges.isEmpty()) {
1114                                 QUrl url = top->url().toUrl();
1115                                 KTextEditor::Range selectUse = chosen->transformFromLocalRevision(
1116                                     forward ? useRanges.first() : useRanges.back());
1117                                 lock.unlock();
1118                                 core()->documentController()->openDocument(url, cursorToRange(selectUse.start()));
1119                             }
1120                         }
1121                     }
1122                     return;
1123                 }
1124                 //Check whether we are within a use
1125                 QVector<RangeInRevision> localUses = allUses(chosen, decl, true);
1126                 std::sort(localUses.begin(), localUses.end());
1127                 for (int a = 0; a < localUses.size(); ++a) {
1128                     int nextUse = (forward ? a + 1 : a - 1);
1129                     bool pick = localUses[a].contains(c);
1130 
1131                     if (!pick && forward && a + 1 < localUses.size() && localUses[a].end <= c &&
1132                         localUses[a + 1].start > c) {
1133                         //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use
1134                         pick = true;
1135                     }
1136                     if (!pick && !forward && a - 1 >= 0 && c < localUses[a].start && c >= localUses[a - 1].end) {
1137                         //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
1138                         pick = true;
1139                     }
1140                     if (!pick && a == 0 && c < localUses[a].start) {
1141                         if (!forward) {
1142                             //Will automatically jump to previous file
1143                         } else {
1144                             nextUse = 0; //We are before the first use, so jump to it.
1145                         }
1146                         pick = true;
1147                     }
1148                     if (!pick && a == localUses.size() - 1 && c >= localUses[a].end) {
1149                         if (forward) {
1150                             //Will automatically jump to next file
1151                         } else { //We are behind the last use, but moving backward. So pick the last use.
1152                             nextUse = a;
1153                         }
1154                         pick = true;
1155                     }
1156 
1157                     if (pick) {
1158                         //Make sure we end up behind the use
1159                         if (nextUse != a)
1160                             while (forward && nextUse < localUses.size() &&
1161                                    (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty()))
1162                                 ++nextUse;
1163 
1164                         //Make sure we end up before the use
1165                         if (nextUse != a)
1166                             while (!forward && nextUse >= 0 &&
1167                                    (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty()))
1168                                 --nextUse;
1169                         //Jump to the next use
1170 
1171                         qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse;
1172 
1173                         if (nextUse < 0 || nextUse == localUses.size()) {
1174                             qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file";
1175                             //Jump to the first use in the next using top-context
1176                             int indexInFiles = usingFiles.indexOf(chosen);
1177                             if (indexInFiles != -1) {
1178                                 int nextFile = (forward ? indexInFiles + 1 : indexInFiles - 1);
1179                                 qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" <<
1180                                     nextFile;
1181 
1182                                 if (nextFile < 0 || nextFile >= usingFiles.size()) {
1183                                     //Open the declaration, or the definition
1184                                     if (nextFile >= usingFiles.size()) {
1185                                         Declaration* definition = FunctionDefinition::definition(decl);
1186                                         if (definition)
1187                                             decl = definition;
1188                                     }
1189                                     QUrl u = decl->url().toUrl();
1190                                     KTextEditor::Range range = decl->rangeInCurrentRevision();
1191                                     range.setEnd(range.start());
1192                                     lock.unlock();
1193                                     core()->documentController()->openDocument(u, range);
1194                                     return;
1195                                 } else {
1196                                     TopDUContext* nextTop = usingFiles[nextFile].data();
1197 
1198                                     QUrl u = nextTop->url().toUrl();
1199 
1200                                     QVector<RangeInRevision> nextTopUses = allUses(nextTop, decl, true);
1201                                     std::sort(nextTopUses.begin(), nextTopUses.end());
1202 
1203                                     if (!nextTopUses.isEmpty()) {
1204                                         KTextEditor::Range range =  chosen->transformFromLocalRevision(
1205                                             forward ? nextTopUses.front() : nextTopUses.back());
1206                                         range.setEnd(range.start());
1207                                         lock.unlock();
1208                                         core()->documentController()->openDocument(u, range);
1209                                     }
1210                                     return;
1211                                 }
1212                             } else {
1213                                 qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list";
1214                             }
1215                         } else {
1216                             QUrl url = chosen->url().toUrl();
1217                             KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]);
1218                             range.setEnd(range.start());
1219                             lock.unlock();
1220                             core()->documentController()->openDocument(url, range);
1221                             return;
1222                         }
1223                     }
1224                 }
1225             }
1226         }
1227     }
1228 }
1229 
1230 void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view)
1231 {
1232     m_views.removeAll(view);
1233 }
1234 
1235 // history browsing
1236 
1237 QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow(Sublime::MainWindow* window)
1238 {
1239     //TODO: support multiple windows (if that ever gets revived)
1240     if (!m_toolbarWidget) {
1241         m_toolbarWidget = new QWidget(window);
1242     }
1243     return m_toolbarWidget;
1244 }
1245 
1246 void ContextBrowserPlugin::documentJumpPerformed(KDevelop::IDocument* newDocument,
1247                                                  const KTextEditor::Cursor& newCursor,
1248                                                  KDevelop::IDocument* previousDocument,
1249                                                  const KTextEditor::Cursor& previousCursor)
1250 {
1251     DUChainReadLocker lock(DUChain::lock());
1252 
1253     /*TODO: support multiple windows if that ever gets revived
1254        if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this))
1255         return;
1256      */
1257 
1258     if (previousDocument && previousCursor.isValid()) {
1259         qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source";
1260         DUContext* context = contextAt(previousDocument->url(), previousCursor);
1261         if (context) {
1262             updateHistory(context, KTextEditor::Cursor(previousCursor), true);
1263         } else {
1264             //We just want this place in the history
1265             m_history.resize(m_nextHistoryIndex); // discard forward history
1266             m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()),
1267                                                          KTextEditor::Cursor(previousCursor))));
1268             ++m_nextHistoryIndex;
1269         }
1270     }
1271     qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor;
1272     if (newDocument && newCursor.isValid()) {
1273         qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target";
1274         DUContext* context = contextAt(newDocument->url(), newCursor);
1275         if (context) {
1276             updateHistory(context, KTextEditor::Cursor(newCursor), true);
1277         } else {
1278             //We just want this place in the history
1279             m_history.resize(m_nextHistoryIndex); // discard forward history
1280             m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()),
1281                                                          KTextEditor::Cursor(newCursor))));
1282             ++m_nextHistoryIndex;
1283             if (m_outlineLine)
1284                 m_outlineLine->clear();
1285         }
1286     }
1287 }
1288 
1289 void ContextBrowserPlugin::updateButtonState()
1290 {
1291     m_nextButton->setEnabled(m_nextHistoryIndex < m_history.size());
1292     m_previousButton->setEnabled(m_nextHistoryIndex >= 2);
1293 }
1294 
1295 void ContextBrowserPlugin::historyNext()
1296 {
1297     if (m_nextHistoryIndex >= m_history.size()) {
1298         return;
1299     }
1300     openDocument(m_nextHistoryIndex); // opening the document at given position
1301                                       // will update the widget for us
1302     ++m_nextHistoryIndex;
1303     updateButtonState();
1304 }
1305 
1306 void ContextBrowserPlugin::openDocument(int historyIndex)
1307 {
1308     Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index");
1309     Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range");
1310     DocumentCursor c = m_history[historyIndex].computePosition();
1311     if (c.isValid() && !c.document.str().isEmpty()) {
1312         disconnect(
1313             ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this,
1314             &ContextBrowserPlugin::documentJumpPerformed);
1315 
1316         ICore::self()->documentController()->openDocument(c.document.toUrl(), c);
1317 
1318         connect(
1319             ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this,
1320             &ContextBrowserPlugin::documentJumpPerformed);
1321 
1322         KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
1323         updateDeclarationListBox(m_history[historyIndex].context.data());
1324     }
1325 }
1326 
1327 void ContextBrowserPlugin::historyPrevious()
1328 {
1329     if (m_nextHistoryIndex < 2) {
1330         return;
1331     }
1332     --m_nextHistoryIndex;
1333     openDocument(m_nextHistoryIndex - 1); // opening the document at given position
1334                                           // will update the widget for us
1335     updateButtonState();
1336 }
1337 
1338 QString ContextBrowserPlugin::actionTextFor(int historyIndex) const
1339 {
1340     const HistoryEntry& entry = m_history.at(historyIndex);
1341     QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString();
1342     if (actionText.isEmpty())
1343         actionText = entry.alternativeString;
1344     if (actionText.isEmpty())
1345         actionText = QStringLiteral("<unnamed>");
1346     actionText += QLatin1String(" @ ");
1347     QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName();
1348     actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line() + 1);
1349     return actionText;
1350 }
1351 
1352 /*
1353    inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he)
1354    {
1355     DocumentCursor c = he.computePosition();
1356     debug << "\n\tHistoryEntry " << c.line << " " << c.document.str();
1357     return debug;
1358    }
1359  */
1360 
1361 void ContextBrowserPlugin::nextMenuAboutToShow()
1362 {
1363     QList<int> indices;
1364     indices.reserve(m_history.size() - m_nextHistoryIndex);
1365     for (int a = m_nextHistoryIndex; a < m_history.size(); ++a) {
1366         indices << a;
1367     }
1368 
1369     fillHistoryPopup(m_nextMenu, indices);
1370 }
1371 
1372 void ContextBrowserPlugin::previousMenuAboutToShow()
1373 {
1374     QList<int> indices;
1375     indices.reserve(m_nextHistoryIndex - 1);
1376     for (int a = m_nextHistoryIndex - 2; a >= 0; --a) {
1377         indices << a;
1378     }
1379 
1380     fillHistoryPopup(m_previousMenu, indices);
1381 }
1382 
1383 void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList<int>& historyIndices)
1384 {
1385     menu->clear();
1386     KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
1387     for (int index : historyIndices) {
1388         auto* action = new QAction(actionTextFor(index), menu);
1389         action->setData(index);
1390         menu->addAction(action);
1391         connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered);
1392     }
1393 }
1394 
1395 bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context,
1396                                            const KTextEditor::Cursor& /*position*/) const
1397 {
1398     if (m_nextHistoryIndex == 0)
1399         return false;
1400     Q_ASSERT(m_nextHistoryIndex <= m_history.count());
1401     const HistoryEntry& he = m_history.at(m_nextHistoryIndex - 1);
1402     KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());   // is this necessary??
1403     Q_ASSERT(context);
1404     return IndexedDUContext(context) == he.context;
1405 }
1406 
1407 void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force)
1408 {
1409     qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history";
1410 
1411     if (m_outlineLine && m_outlineLine->isVisible())
1412         updateDeclarationListBox(context);
1413 
1414     if (!context || (!context->owner() && !force)) {
1415         return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes
1416                 //This keeps the history cleaner
1417     }
1418 
1419     if (isPreviousEntry(context, position)) {
1420         if (m_nextHistoryIndex) {
1421             HistoryEntry& he = m_history[m_nextHistoryIndex - 1];
1422             he.setCursorPosition(position);
1423         }
1424         return;
1425     } else { // Append new history entry
1426         m_history.resize(m_nextHistoryIndex); // discard forward history
1427         m_history.append(HistoryEntry(IndexedDUContext(context), position));
1428         ++m_nextHistoryIndex;
1429 
1430         updateButtonState();
1431         if (m_history.size() > (maxHistoryLength + 5)) {
1432             m_history.remove(0, m_history.size() - maxHistoryLength);
1433             m_nextHistoryIndex = m_history.size();
1434         }
1435     }
1436 }
1437 
1438 void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context)
1439 {
1440     if (!context || !context->owner()) {
1441         qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box";
1442         m_listUrl = IndexedString(); ///@todo Compute the context in the document here
1443         if (m_outlineLine)
1444             m_outlineLine->clear();
1445         return;
1446     }
1447 
1448     Declaration* decl = context->owner();
1449 
1450     m_listUrl = context->url();
1451 
1452     Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext());
1453 
1454     FunctionType::Ptr function = specialDecl->type<FunctionType>();
1455     QString text = specialDecl->qualifiedIdentifier().toString();
1456     if (function)
1457         text += function->partToString(KDevelop::FunctionType::SignatureArguments);
1458 
1459     if (m_outlineLine && !m_outlineLine->hasFocus()) {
1460         m_outlineLine->setText(text);
1461         m_outlineLine->setCursorPosition(0);
1462     }
1463 
1464     qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text;
1465 }
1466 
1467 void ContextBrowserPlugin::actionTriggered()
1468 {
1469     auto* action = qobject_cast<QAction*>(sender());
1470     Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int);
1471     int historyPosition = action->data().toInt();
1472     // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history;
1473     if (historyPosition >= 0 && historyPosition < m_history.size()) {
1474         m_nextHistoryIndex = historyPosition + 1;
1475         openDocument(historyPosition);
1476         updateButtonState();
1477     }
1478 }
1479 
1480 void ContextBrowserPlugin::doNavigate(NavigationActionType action)
1481 {
1482     auto* view = qobject_cast<KTextEditor::View*>(sender());
1483     if (!view) {
1484         qCWarning(PLUGIN_CONTEXTBROWSER) << "sender is not a view";
1485         return;
1486     }
1487     KTextEditor::CodeCompletionInterface* iface = qobject_cast<KTextEditor::CodeCompletionInterface*>(view);
1488     if (!iface || iface->isCompletionActive())
1489         return; // If code completion is active, the actions should be handled by the completion widget
1490 
1491     QWidget* widget = m_currentNavigationWidget.data();
1492 
1493     if (!widget || !widget->isVisible()) {
1494         ContextBrowserView* contextView = browserViewForWidget(view);
1495         if (contextView)
1496             widget = contextView->navigationWidget();
1497     }
1498 
1499     if (auto* navWidget = dynamic_cast<QuickOpenEmbeddedWidgetInterface*>(widget)) {
1500         switch (action) {
1501         case Accept:
1502             navWidget->accept();
1503             break;
1504         case Back:
1505             navWidget->back();
1506             break;
1507         case Left:
1508             navWidget->previous();
1509             break;
1510         case Right:
1511             navWidget->next();
1512             break;
1513         case Up:
1514             navWidget->up();
1515             break;
1516         case Down:
1517             navWidget->down();
1518             break;
1519         }
1520     }
1521 }
1522 
1523 void ContextBrowserPlugin::navigateAccept()
1524 {
1525     doNavigate(Accept);
1526 }
1527 
1528 void ContextBrowserPlugin::navigateBack()
1529 {
1530     doNavigate(Back);
1531 }
1532 
1533 void ContextBrowserPlugin::navigateDown()
1534 {
1535     doNavigate(Down);
1536 }
1537 
1538 void ContextBrowserPlugin::navigateLeft()
1539 {
1540     doNavigate(Left);
1541 }
1542 
1543 void ContextBrowserPlugin::navigateRight()
1544 {
1545     doNavigate(Right);
1546 }
1547 
1548 void ContextBrowserPlugin::navigateUp()
1549 {
1550     doNavigate(Up);
1551 }
1552 
1553 //BEGIN HistoryEntry
1554 ContextBrowserPlugin::HistoryEntry::HistoryEntry(const KDevelop::DocumentCursor& pos)
1555     : absoluteCursorPosition(pos)
1556 {
1557 }
1558 
1559 ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx,
1560                                                  const KTextEditor::Cursor& cursorPosition) : context(ctx)
1561 {
1562     //Use a position relative to the context
1563     setCursorPosition(cursorPosition);
1564     if (ctx.data())
1565         alternativeString = ctx.data()->scopeIdentifier(true).toString();
1566     if (!alternativeString.isEmpty())
1567         alternativeString += i18n("(changed)");     //This is used when the context was deleted in between
1568 }
1569 
1570 DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const
1571 {
1572     KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
1573     DocumentCursor ret;
1574     if (context.data()) {
1575         ret = DocumentCursor(context.data()->url(), relativeCursorPosition);
1576         ret.setLine(ret.line() + context.data()->range().start.line);
1577     } else {
1578         ret = absoluteCursorPosition;
1579     }
1580     return ret;
1581 }
1582 
1583 void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition)
1584 {
1585     KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock());
1586     if (context.data()) {
1587         absoluteCursorPosition =  DocumentCursor(context.data()->url(), cursorPosition);
1588         relativeCursorPosition = cursorPosition;
1589         relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line);
1590     }
1591 }
1592 
1593 #include "contextbrowser.moc"
1594 #include "moc_contextbrowser.cpp"