File indexing completed on 2024-05-19 08:30:14
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"