Changeset View
Changeset View
Standalone View
Standalone View
plugins/contextbrowser/contextbrowser.cpp
Show First 20 Lines • Show All 61 Lines • ▼ Show 20 Line(s) | |||||
62 | #include <language/duchain/duchainutils.h> | 62 | #include <language/duchain/duchainutils.h> | ||
63 | #include <language/duchain/functiondefinition.h> | 63 | #include <language/duchain/functiondefinition.h> | ||
64 | #include <language/duchain/parsingenvironment.h> | 64 | #include <language/duchain/parsingenvironment.h> | ||
65 | #include <language/duchain/uses.h> | 65 | #include <language/duchain/uses.h> | ||
66 | #include <language/duchain/specializationstore.h> | 66 | #include <language/duchain/specializationstore.h> | ||
67 | #include <language/duchain/aliasdeclaration.h> | 67 | #include <language/duchain/aliasdeclaration.h> | ||
68 | #include <language/duchain/types/functiontype.h> | 68 | #include <language/duchain/types/functiontype.h> | ||
69 | #include <language/duchain/navigation/abstractnavigationwidget.h> | 69 | #include <language/duchain/navigation/abstractnavigationwidget.h> | ||
70 | #include <language/duchain/navigation/problemnavigationcontext.h> | ||||
70 | 71 | | |||
71 | #include <language/util/navigationtooltip.h> | 72 | #include <language/util/navigationtooltip.h> | ||
72 | 73 | | |||
73 | #include <util/texteditorhelpers.h> | 74 | #include <util/texteditorhelpers.h> | ||
74 | 75 | | |||
75 | #include <sublime/mainwindow.h> | 76 | #include <sublime/mainwindow.h> | ||
76 | 77 | | |||
77 | Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") | 78 | Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") | ||
▲ Show 20 Lines • Show All 112 Lines • ▼ Show 20 Line(s) | 189 | if(quickOpen) { | |||
190 | m_outlineLine->setDefaultText(i18n("Outline...")); | 191 | m_outlineLine->setDefaultText(i18n("Outline...")); | ||
191 | m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); | 192 | m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); | ||
192 | } | 193 | } | ||
193 | 194 | | |||
194 | connect(m_browseManager, &BrowseManager::startDelayedBrowsing, | 195 | connect(m_browseManager, &BrowseManager::startDelayedBrowsing, | ||
195 | this, &ContextBrowserPlugin::startDelayedBrowsing); | 196 | this, &ContextBrowserPlugin::startDelayedBrowsing); | ||
196 | connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, | 197 | connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, | ||
197 | this, &ContextBrowserPlugin::stopDelayedBrowsing); | 198 | this, &ContextBrowserPlugin::stopDelayedBrowsing); | ||
199 | connect(m_browseManager, &BrowseManager::invokeAction, | ||||
200 | this, &ContextBrowserPlugin::invokeAction); | ||||
198 | 201 | | |||
199 | m_toolbarWidget = toolbarWidgetForMainWindow(window); | 202 | m_toolbarWidget = toolbarWidgetForMainWindow(window); | ||
200 | m_toolbarWidgetLayout = new QHBoxLayout; | 203 | m_toolbarWidgetLayout = new QHBoxLayout; | ||
201 | m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); | 204 | m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); | ||
202 | m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | 205 | m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | ||
203 | m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | 206 | m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | ||
204 | m_toolbarWidgetLayout->setMargin(0); | 207 | m_toolbarWidgetLayout->setMargin(0); | ||
205 | 208 | | |||
▲ Show 20 Lines • Show All 194 Lines • ▼ Show 20 Line(s) | 393 | { | |||
400 | m_plugin->showToolTip(view, cursor); | 403 | m_plugin->showToolTip(view, cursor); | ||
401 | return QString(); | 404 | return QString(); | ||
402 | } | 405 | } | ||
403 | 406 | | |||
404 | void ContextBrowserPlugin::stopDelayedBrowsing() { | 407 | void ContextBrowserPlugin::stopDelayedBrowsing() { | ||
405 | hideToolTip(); | 408 | hideToolTip(); | ||
406 | } | 409 | } | ||
407 | 410 | | |||
411 | void ContextBrowserPlugin::invokeAction(int index) | ||||
412 | { | ||||
413 | if (!m_currentNavigationWidget) | ||||
414 | return; | ||||
415 | | ||||
416 | | ||||
417 | auto navigationWidget = qobject_cast<AbstractNavigationWidget*>(m_currentNavigationWidget); | ||||
418 | if (!navigationWidget) | ||||
419 | return; | ||||
420 | | ||||
421 | // TODO: Add API in AbstractNavigation{Widget,Context}? | ||||
422 | QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); | ||||
423 | } | ||||
424 | | ||||
408 | void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { | 425 | void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { | ||
409 | if(!m_currentToolTip) { | 426 | if(!m_currentToolTip) { | ||
410 | showToolTip(view, view->cursorPosition()); | 427 | showToolTip(view, view->cursorPosition()); | ||
411 | } | 428 | } | ||
412 | } | 429 | } | ||
413 | 430 | | |||
414 | void ContextBrowserPlugin::hideToolTip() { | 431 | void ContextBrowserPlugin::hideToolTip() { | ||
415 | if(m_currentToolTip) { | 432 | if(m_currentToolTip) { | ||
416 | m_currentToolTip->deleteLater(); | 433 | m_currentToolTip->deleteLater(); | ||
417 | m_currentToolTip = 0; | 434 | m_currentToolTip = 0; | ||
418 | m_currentNavigationWidget = 0; | 435 | m_currentNavigationWidget = 0; | ||
436 | m_currentToolTipProblem = {}; | ||||
437 | m_currentToolTipDeclaration = {}; | ||||
419 | } | 438 | } | ||
420 | } | 439 | } | ||
421 | 440 | | |||
422 | void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { | 441 | static ProblemPointer findProblemUnderCursor(const TopDUContext* topContext, KTextEditor::Cursor position) | ||
442 | { | ||||
443 | foreach (auto problem, topContext->problems()) { | ||||
444 | if (problem->rangeInCurrentRevision().contains(position)) { | ||||
445 | return problem; | ||||
446 | } | ||||
447 | } | ||||
423 | 448 | | |||
424 | ContextBrowserView* contextView = browserViewForWidget(view); | 449 | return {}; | ||
425 | if(contextView && contextView->isVisible() && !contextView->isLocked()) | 450 | } | ||
426 | return; // If the context-browser view is visible, it will care about updating by itself | 451 | | ||
452 | static ProblemPointer findProblemCloseToCursor(const TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) | ||||
453 | { | ||||
454 | auto problems = topContext->problems(); | ||||
455 | if (problems.isEmpty()) | ||||
456 | return {}; | ||||
457 | | ||||
458 | auto closestProblem = std::min_element(problems.constBegin(), problems.constEnd(), | ||||
459 | [position](const ProblemPointer& a, const ProblemPointer& b) { | ||||
460 | const auto aRange = a->rangeInCurrentRevision(); | ||||
461 | const auto bRange = b->rangeInCurrentRevision(); | ||||
462 | | ||||
463 | const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), | ||||
464 | qAbs(aRange.end().line() - position.line())); | ||||
465 | const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), | ||||
466 | qAbs(bRange.end().line() - position.line())); | ||||
467 | if (aLineDistance != bLineDistance) { | ||||
468 | return aLineDistance < bLineDistance; | ||||
469 | } | ||||
470 | | ||||
471 | if (aRange.start().line() == bRange.start().line()) { | ||||
472 | return qAbs(aRange.start().column() - position.column()) < | ||||
473 | qAbs(bRange.start().column() - position.column()); | ||||
474 | } | ||||
475 | return qAbs(aRange.end().column() - position.column()) < | ||||
476 | qAbs(bRange.end().column() - position.column()); | ||||
477 | }); | ||||
478 | | ||||
479 | auto r = (*closestProblem)->rangeInCurrentRevision(); | ||||
480 | if (!r.contains(position)) { | ||||
481 | if (r.start().line() == position.line() || r.end().line() == position.line()) { | ||||
482 | // problem is on the same line, let's use it | ||||
483 | return *closestProblem; | ||||
484 | } | ||||
485 | | ||||
486 | // if not, only show it in case there's only whitespace between the current cursor pos and the problem | ||||
487 | auto dist = position < r.start() ? KTextEditor::Range(position, r.start()) : KTextEditor::Range(r.end(), position); | ||||
488 | auto textBetween = view->document()->text(dist); | ||||
489 | auto isSpace = std::all_of(textBetween.begin(), textBetween.end(), [](QChar c) { return c.isSpace(); }); | ||||
490 | if (!isSpace) { | ||||
491 | return {}; | ||||
492 | } | ||||
493 | } | ||||
494 | | ||||
495 | return *closestProblem; | ||||
496 | } | ||||
427 | 497 | | |||
498 | QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) | ||||
499 | { | ||||
428 | QUrl viewUrl = view->document()->url(); | 500 | QUrl viewUrl = view->document()->url(); | ||
429 | auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); | 501 | auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); | ||
430 | 502 | | |||
431 | QWidget* navigationWidget = 0; | | |||
432 | { | | |||
433 | DUChainReadLocker lock(DUChain::lock()); | 503 | DUChainReadLocker lock(DUChain::lock()); | ||
434 | foreach (const auto language, languages) { | 504 | foreach (const auto language, languages) { | ||
435 | auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); | 505 | auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); | ||
436 | navigationWidget = qobject_cast<AbstractNavigationWidget*>(widget); | 506 | auto navigationWidget = qobject_cast<AbstractNavigationWidget*>(widget); | ||
437 | if(navigationWidget) | 507 | if(navigationWidget) | ||
438 | break; | 508 | return navigationWidget; | ||
509 | } | ||||
510 | | ||||
511 | TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); | ||||
512 | if (topContext) { | ||||
513 | // first pass: find problems under the cursor | ||||
514 | const auto problem = findProblemUnderCursor(topContext, position); | ||||
515 | if (problem) { | ||||
516 | if (problem == m_currentToolTipProblem && m_currentToolTip) { | ||||
517 | return nullptr; | ||||
518 | } | ||||
519 | | ||||
520 | m_currentToolTipProblem = problem; | ||||
521 | auto widget = new AbstractNavigationWidget; | ||||
522 | auto context = new ProblemNavigationContext(problem); | ||||
523 | context->setTopContext(TopDUContextPointer(topContext)); | ||||
524 | widget->setContext(NavigationContextPointer(context)); | ||||
525 | return widget; | ||||
526 | } | ||||
439 | } | 527 | } | ||
440 | 528 | | |||
441 | if(!navigationWidget) { | 529 | auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position); | ||
442 | Declaration* decl = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(viewUrl, KTextEditor::Cursor(position)) ); | 530 | Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); | ||
443 | if (decl && decl->kind() == Declaration::Alias) { | 531 | if (decl && decl->kind() == Declaration::Alias) { | ||
444 | AliasDeclaration* alias = dynamic_cast<AliasDeclaration*>(decl); | 532 | AliasDeclaration* alias = dynamic_cast<AliasDeclaration*>(decl); | ||
445 | Q_ASSERT(alias); | 533 | Q_ASSERT(alias); | ||
446 | DUChainReadLocker lock; | 534 | DUChainReadLocker lock; | ||
447 | decl = alias->aliasedDeclaration().declaration(); | 535 | decl = alias->aliasedDeclaration().declaration(); | ||
448 | } | 536 | } | ||
449 | if(decl) { | 537 | if(decl) { | ||
450 | if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) | 538 | if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) | ||
451 | return; | 539 | return nullptr; | ||
540 | | ||||
452 | m_currentToolTipDeclaration = IndexedDeclaration(decl); | 541 | m_currentToolTipDeclaration = IndexedDeclaration(decl); | ||
453 | navigationWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); | 542 | return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); | ||
454 | } | 543 | } | ||
544 | | ||||
545 | if (topContext) { | ||||
546 | // second pass: find closest problem to the cursor | ||||
547 | const auto problem = findProblemCloseToCursor(topContext, position, view); | ||||
548 | if (problem) { | ||||
549 | if (problem == m_currentToolTipProblem && m_currentToolTip) { | ||||
550 | return nullptr; | ||||
455 | } | 551 | } | ||
552 | | ||||
553 | m_currentToolTipProblem = problem; | ||||
554 | auto widget = new AbstractNavigationWidget; | ||||
555 | // since the problem is not under cursor: show location | ||||
556 | widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem, ProblemNavigationContext::ShowLocation))); | ||||
557 | return widget; | ||||
558 | } | ||||
559 | } | ||||
560 | | ||||
561 | return nullptr; | ||||
456 | } | 562 | } | ||
457 | 563 | | |||
564 | void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { | ||||
565 | | ||||
566 | ContextBrowserView* contextView = browserViewForWidget(view); | ||||
567 | if(contextView && contextView->isVisible() && !contextView->isLocked()) | ||||
568 | return; // If the context-browser view is visible, it will care about updating by itself | ||||
569 | | ||||
570 | auto navigationWidget = navigationWidgetForPosition(view, position); | ||||
458 | if(navigationWidget) { | 571 | if(navigationWidget) { | ||
459 | 572 | | |||
460 | // If we have an invisible context-view, assign the tooltip navigation-widget to it. | 573 | // If we have an invisible context-view, assign the tooltip navigation-widget to it. | ||
461 | // If the user makes the context-view visible, it will instantly contain the correct widget. | 574 | // If the user makes the context-view visible, it will instantly contain the correct widget. | ||
462 | if(contextView && !contextView->isLocked()) | 575 | if(contextView && !contextView->isLocked()) | ||
463 | contextView->setNavigationWidget(navigationWidget); | 576 | contextView->setNavigationWidget(navigationWidget); | ||
464 | 577 | | |||
465 | if(m_currentToolTip) { | 578 | if(m_currentToolTip) { | ||
466 | m_currentToolTip->deleteLater(); | 579 | m_currentToolTip->deleteLater(); | ||
467 | m_currentToolTip = 0; | 580 | m_currentToolTip = 0; | ||
468 | m_currentNavigationWidget = 0; | 581 | m_currentNavigationWidget = 0; | ||
469 | } | 582 | } | ||
470 | 583 | | |||
471 | KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); | 584 | KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); | ||
472 | KTextEditor::Range itemRange; | 585 | KTextEditor::Range itemRange; | ||
473 | { | 586 | { | ||
474 | DUChainReadLocker lock; | 587 | DUChainReadLocker lock; | ||
475 | itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, KTextEditor::Cursor(position)); | 588 | auto viewUrl = view->document()->url(); | ||
589 | itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, position); | ||||
476 | } | 590 | } | ||
477 | tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); | 591 | tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); | ||
478 | tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); | 592 | tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); | ||
479 | QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, | 593 | QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, | ||
480 | this, &ContextBrowserPlugin::hideToolTip ); | 594 | this, &ContextBrowserPlugin::hideToolTip ); | ||
481 | QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, | 595 | QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, | ||
482 | this, &ContextBrowserPlugin::hideToolTip ); | 596 | this, &ContextBrowserPlugin::hideToolTip ); | ||
483 | qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); | 597 | qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); | ||
▲ Show 20 Lines • Show All 874 Lines • Show Last 20 Lines |