diff --git a/kdevplatform/debugger/variable/variablecollection.cpp b/kdevplatform/debugger/variable/variablecollection.cpp index 16d8fb82c2..b6ca2e73c9 100644 --- a/kdevplatform/debugger/variable/variablecollection.cpp +++ b/kdevplatform/debugger/variable/variablecollection.cpp @@ -1,556 +1,571 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "variablecollection.h" #include #include #include #include #include #include #include #include "../../interfaces/icore.h" #include "../../interfaces/idocumentcontroller.h" #include "../../interfaces/iuicontroller.h" #include "../../sublime/controller.h" #include "../../sublime/view.h" #include "../../interfaces/idebugcontroller.h" #include "../interfaces/idebugsession.h" #include "../interfaces/ivariablecontroller.h" #include #include "util/texteditorhelpers.h" #include "variabletooltip.h" #include namespace KDevelop { IDebugSession* currentSession() { return ICore::self()->debugController()->currentSession(); } IDebugSession::DebuggerState currentSessionState() { if (!currentSession()) return IDebugSession::NotStartedState; return currentSession()->state(); } bool hasStartedSession() { IDebugSession::DebuggerState s = currentSessionState(); return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; } Variable::Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display) : TreeItem(model, parent) , m_expression(expression) , m_inScope(true) , m_topLevel(true) , m_changed(false) , m_showError(false) , m_format(Natural) { // FIXME: should not duplicate the data, instead overload 'data' // and return expression_ directly. if (display.isEmpty()) setData(QVector{expression, QString(), QString()}); else setData(QVector{display, QString(), QString()}); } QString Variable::expression() const { return m_expression; } bool Variable::inScope() const { return m_inScope; } void Variable::setValue(const QString& v) { itemData[VariableCollection::ValueColumn] = v; reportChange(); } QString Variable::value() const { return itemData[VariableCollection::ValueColumn].toString(); } void Variable::setType(const QString& type) { itemData[VariableCollection::TypeColumn] = type; reportChange(); } QString Variable::type() const { return itemData[VariableCollection::TypeColumn].toString(); } void Variable::setTopLevel(bool v) { m_topLevel = v; } void Variable::setInScope(bool v) { m_inScope = v; for (int i=0; i < childCount(); ++i) { if (auto *var = qobject_cast(child(i))) { var->setInScope(v); } } reportChange(); } void Variable::setShowError (bool v) { m_showError = v; reportChange(); } bool Variable::showError() { return m_showError; } Variable::~Variable() { } void Variable::die() { removeSelf(); deleteLater(); } void Variable::setChanged(bool c) { m_changed=c; reportChange(); } void Variable::resetChanged() { setChanged(false); for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } Variable::format_t Variable::str2format(const QString& str) { if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; return Natural; // maybe most reasonable default } QString Variable::format2str(format_t format) { switch(format) { case Natural: return QStringLiteral("natural"); case Binary: return QStringLiteral("binary"); case Octal: return QStringLiteral("octal"); case Decimal: return QStringLiteral("decimal"); case Hexadecimal: return QStringLiteral("hexadecimal"); } return QString(); } void Variable::setFormat(Variable::format_t format) { if (m_format != format) { m_format = format; formatChanged(); } } void Variable::formatChanged() { } bool Variable::isPotentialProblematicValue() const { const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); return value == QLatin1String("0x0"); } QVariant Variable::data(int column, int role) const { if (m_showError) { if (role == Qt::FontRole) { QVariant ret = TreeItem::data(column, role); QFont font = ret.value(); font.setStyle(QFont::StyleItalic); return font; } else if (column == 1 && role == Qt::DisplayRole) { return i18n("Error"); } } if (column == 1 && role == Qt::TextColorRole) { KColorScheme scheme(QPalette::Active); if (!m_inScope) { return scheme.foreground(KColorScheme::InactiveText).color(); } else if (isPotentialProblematicValue()) { return scheme.foreground(KColorScheme::NegativeText).color(); } else if (m_changed) { return scheme.foreground(KColorScheme::NeutralText).color(); } } if (role == Qt::ToolTipRole) { return TreeItem::data(column, Qt::DisplayRole); } return TreeItem::data(column, role); } Watches::Watches(TreeModel* model, TreeItem* parent) : TreeItem(model, parent), finishResult_(nullptr) { setData(QVector{i18n("Auto"), QString()}); } Variable* Watches::add(const QString& expression) { if (!hasStartedSession()) return nullptr; Variable* v = currentSession()->variableController()->createVariable( model(), this, expression); appendChild(v); v->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return v; } Variable *Watches::addFinishResult(const QString& convenienceVarible) { if( finishResult_ ) { removeFinishResult(); } finishResult_ = currentSession()->variableController()->createVariable( model(), this, convenienceVarible, QStringLiteral("$ret")); appendChild(finishResult_); finishResult_->attachMaybe(); if (childCount() == 1 && !isExpanded()) { setExpanded(true); } return finishResult_; } void Watches::removeFinishResult() { if (finishResult_) { finishResult_->die(); finishResult_ = nullptr; } } void Watches::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } QVariant Watches::data(int column, int role) const { #if 0 if (column == 0 && role == Qt::FontRole) { /* FIXME: is creating font again and again efficient? */ QFont f = font(); f.setBold(true); return f; } #endif return TreeItem::data(column, role); } void Watches::reinstall() { for (int i = 0; i < childItems.size(); ++i) { auto* v = static_cast(child(i)); v->attachMaybe(); } } Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) : TreeItem(model, parent) { setData(QVector{name, QString()}); } QList Locals::updateLocals(const QStringList& locals) { QSet existing, current; for (int i = 0; i < childItems.size(); i++) { Q_ASSERT(qobject_cast(child(i))); auto* var= static_cast(child(i)); existing << var->expression(); } for (const QString& var : locals) { current << var; // If we currently don't display this local var, add it. if( !existing.contains( var ) ) { // FIXME: passing variableCollection this way is awkward. // In future, variableCollection probably should get a // method to create variable. Variable* v = currentSession()->variableController()->createVariable( ICore::self()->debugController()->variableCollection(), this, var ); appendChild( v, false ); } } for (int i = 0; i < childItems.size(); ++i) { auto* v = static_cast(child(i)); if (!current.contains(v->expression())) { removeChild(i); --i; // FIXME: check that -var-delete is sent. delete v; } } if (hasMore()) { setHasMore(false); } // Variables which changed just value are updated by a call to -var-update. // Variables that changed type -- likewise. QList ret; ret.reserve(childItems.size()); for (TreeItem* i : qAsConst(childItems)) { Q_ASSERT(qobject_cast(i)); ret << static_cast(i); } return ret; } void Locals::resetChanged() { for (int i=0; i(childItem)) { static_cast(childItem)->resetChanged(); } } } VariablesRoot::VariablesRoot(TreeModel* model) : TreeItem(model) , m_watches(new Watches(model, this)) { appendChild(m_watches, true); } Locals* VariablesRoot::locals(const QString& name) { auto localsIt = m_locals.find(name); if (localsIt == m_locals.end()) { localsIt = m_locals.insert(name, new Locals(model(), this, name)); appendChild(*localsIt); } return *localsIt; } QHash VariablesRoot::allLocals() const { return m_locals; } void VariablesRoot::resetChanged() { m_watches->resetChanged(); for (Locals* l : qAsConst(m_locals)) { l->resetChanged(); } } VariableCollection::VariableCollection(IDebugController* controller) : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller) , m_widgetVisible(false) , m_textHintProvider(this) { m_universe = new VariablesRoot(this); setRootItem(m_universe); //new ModelTest(this); connect (ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &VariableCollection::textDocumentCreated ); connect(controller, &IDebugController::currentSessionChanged, this, &VariableCollection::updateAutoUpdate); // Qt5 signal slot syntax does not support default arguments auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); } void VariableCollection::variableWidgetHidden() { m_widgetVisible = false; updateAutoUpdate(); } void VariableCollection::variableWidgetShown() { m_widgetVisible = true; updateAutoUpdate(); } void VariableCollection::updateAutoUpdate(IDebugSession* session) { if (!session) session = currentSession(); qCDebug(DEBUGGER) << session; if (!session) return; if (!m_widgetVisible) { session->variableController()->setAutoUpdate(IVariableController::UpdateNone); } else { QFlags t = IVariableController::UpdateNone; if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; session->variableController()->setAutoUpdate(t); } } VariableCollection::~ VariableCollection() { + for (auto* view : qAsConst(m_textHintProvidedViews)) { + auto* iface = qobject_cast(view); + iface->unregisterTextHintProvider(&m_textHintProvider); + } } void VariableCollection::textDocumentCreated(IDocument* doc) { connect( doc->textDocument(), &KTextEditor::Document::viewCreated, this, &VariableCollection::viewCreated ); const auto views = doc->textDocument()->views(); for (KTextEditor::View* view : views) { viewCreated( doc->textDocument(), view ); } } void VariableCollection::viewCreated(KTextEditor::Document* doc, KTextEditor::View* view) { Q_UNUSED(doc); using namespace KTextEditor; auto* iface = qobject_cast(view); if( !iface ) return; + if (m_textHintProvidedViews.contains(view)) { + return; + } + connect(view, &View::destroyed, this, &VariableCollection::viewDestroyed); + iface->registerTextHintProvider(&m_textHintProvider); + m_textHintProvidedViews.append(view); +} + +void VariableCollection::viewDestroyed(QObject* obj) +{ + m_textHintProvidedViews.removeOne(static_cast(obj)); } Locals* VariableCollection::locals(const QString &name) const { return m_universe->locals(name.isEmpty() ? i18n("Locals") : name); } VariableProvider::VariableProvider(VariableCollection* collection) : KTextEditor::TextHintProvider() , m_collection(collection) { } QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) { if (!hasStartedSession()) return QString(); if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) return QString(); //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. if (QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == Qt::AltModifier){ return QString(); } KTextEditor::Document* doc = view->document(); KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); if (!expressionRange.isValid()) return QString(); QString expression = doc->text(expressionRange).trimmed(); // Don't do anything if there's already an open tooltip with matching range if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression) return QString(); if (expression.isEmpty()) return QString(); QPoint local = view->cursorToCoordinate(cursor); QPoint global = view->mapToGlobal(local); QWidget* w = view->childAt(local); if (!w) w = view; m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression); m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, expressionRange)); return QString(); } } diff --git a/kdevplatform/debugger/variable/variablecollection.h b/kdevplatform/debugger/variable/variablecollection.h index d702c1fa1c..8ee8a803b3 100644 --- a/kdevplatform/debugger/variable/variablecollection.h +++ b/kdevplatform/debugger/variable/variablecollection.h @@ -1,253 +1,256 @@ /* * KDevelop Debugger Support * * Copyright 2007 Hamish Rodda * Copyright 2008 Vladimir Prus * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_VARIABLECOLLECTION_H #define KDEVPLATFORM_VARIABLECOLLECTION_H #include #include #include #include #include "../util/treemodel.h" #include "../util/treeitem.h" #include "../../interfaces/idocument.h" #include "../../interfaces/idebugcontroller.h" namespace KDevMI { namespace GDB { class GdbTest; } } namespace KDevelop { class VariableToolTip; class IDebugSession; class KDEVPLATFORMDEBUGGER_EXPORT Variable : public TreeItem { Q_OBJECT friend class KDevMI::GDB::GdbTest; public: protected: Variable(TreeModel* model, TreeItem* parent, const QString& expression, const QString& display = {}); public: enum format_t { Natural, Binary, Octal, Decimal, Hexadecimal }; static format_t str2format(const QString& str); static QString format2str(format_t format); QString expression() const; bool inScope() const; void setInScope(bool v); void setValue(const QString &v); QString value() const; void setType(const QString& type); QString type() const; void setTopLevel(bool v); void setShowError(bool v); bool showError(); using TreeItem::setHasMore; using TreeItem::setHasMoreInitial; using TreeItem::appendChild; using TreeItem::deleteChildren; using TreeItem::isExpanded; using TreeItem::parent; using TreeItem::model; ~Variable() override; /* Connects this variable to debugger, fetching the current value and otherwise preparing this variable to be displayed everywhere. The attempt may fail, for example if the expression is invalid. Calls slot 'callbackMethod' in 'callback' to notify of the result. The slot should be taking 'bool ok' parameter. */ virtual void attachMaybe(QObject *callback = nullptr, const char *callbackMethod = nullptr) = 0; virtual bool canSetFormat() const { return false; } void setFormat(format_t format); format_t format() const { return m_format; } virtual void formatChanged(); // get/set 'changed' state, if the variable changed it will be highlighted bool isChanged() const { return m_changed; } void setChanged(bool c); void resetChanged(); public Q_SLOTS: void die(); protected: bool topLevel() const { return m_topLevel; } private: // TreeItem overrides QVariant data(int column, int role) const override; private: bool isPotentialProblematicValue() const; QString m_expression; bool m_inScope; bool m_topLevel; bool m_changed; bool m_showError; format_t m_format; }; class KDEVPLATFORMDEBUGGER_EXPORT TooltipRoot : public TreeItem { Q_OBJECT public: explicit TooltipRoot(TreeModel* model) : TreeItem(model) {} void init(Variable *var) { appendChild(var); } void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT Watches : public TreeItem { Q_OBJECT friend class KDevMI::GDB::GdbTest; public: Watches(TreeModel* model, TreeItem* parent); Variable* add(const QString& expression); void reinstall(); Variable *addFinishResult(const QString& convenienceVarible); void removeFinishResult(); void resetChanged(); using TreeItem::childCount; friend class VariableCollection; friend class IVariableController; private: QVariant data(int column, int role) const override; void fetchMoreChildren() override {} Variable* finishResult_; }; class KDEVPLATFORMDEBUGGER_EXPORT Locals : public TreeItem { Q_OBJECT public: Locals(TreeModel* model, TreeItem* parent, const QString &name); QList updateLocals(const QStringList& locals); void resetChanged(); using TreeItem::deleteChildren; using TreeItem::setHasMore; friend class VariableCollection; friend class IVariableController; private: void fetchMoreChildren() override {} }; class KDEVPLATFORMDEBUGGER_EXPORT VariablesRoot : public TreeItem { Q_OBJECT public: explicit VariablesRoot(TreeModel* model); Watches *watches() const { return m_watches; } Locals *locals(const QString &name = QStringLiteral("Locals")); QHash allLocals() const; void fetchMoreChildren() override {} void resetChanged(); private: Watches *m_watches; QHash m_locals; }; class VariableProvider : public KTextEditor::TextHintProvider { public: explicit VariableProvider(VariableCollection* collection); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: VariableCollection* m_collection; }; class KDEVPLATFORMDEBUGGER_EXPORT VariableCollection : public TreeModel { Q_OBJECT public: enum Column { NameColumn, ValueColumn, TypeColumn }; explicit VariableCollection(IDebugController* parent); ~VariableCollection() override; VariablesRoot* root() const { return m_universe; } Watches* watches() const { return m_universe->watches(); } Locals* locals(const QString &name = QString()) const; QHash allLocals() const { return m_universe->allLocals(); } public Q_SLOTS: void variableWidgetShown(); void variableWidgetHidden(); private Q_SLOTS: void updateAutoUpdate(KDevelop::IDebugSession* session = nullptr); void textDocumentCreated( KDevelop::IDocument*); void viewCreated(KTextEditor::Document*, KTextEditor::View*); + void viewDestroyed(QObject* obj); private: VariablesRoot* m_universe; QPointer m_activeTooltip; bool m_widgetVisible; friend class VariableProvider; VariableProvider m_textHintProvider; + + QVector m_textHintProvidedViews; }; } #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 07006311dc..e3169e92f6 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1601 +1,1616 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using KTextEditor::Attribute; using KTextEditor::View; using namespace KDevelop; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while (w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while (ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* contextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration* decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor( view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory : public KDevelop::IToolViewFactory { public: explicit ContextBrowserViewFactory(ContextBrowserPlugin* plugin) : m_plugin(plugin) {} QWidget* create(QWidget* parent = nullptr) override { auto* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin* m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect( ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setAutoRaise(true); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(m_previousButton); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setAutoRaise(true); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(m_nextButton); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); auto* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral( "org.kdevelop.IQuickOpen")); if (quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n( "Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if (m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText(i18n("Source &Browse Mode")); sourceBrowseMode->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText(i18n("&Previous Visited Context")); previousContext->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-context"))); actions.setDefaultShortcut(previousContext, Qt::META | Qt::Key_Left); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText(i18n("&Next Visited Context")); nextContext->setIcon(QIcon::fromTheme(QStringLiteral("go-next-context"))); actions.setDefaultShortcut(nextContext, Qt::META | Qt::Key_Right); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText(i18n("&Previous Use")); previousUse->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-use"))); actions.setDefaultShortcut(previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText(i18n("&Next Use")); nextUse->setIcon(QIcon::fromTheme(QStringLiteral("go-next-use"))); actions.setDefaultShortcut(nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); auto* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin(); ) ContextBrowserPlugin::ContextBrowserPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { qRegisterMetaType("KDevelop::IndexedDeclaration"); core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated); connect(DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect(ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged); connect(DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect(m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); + + const auto documents = core()->documentController()->openDocuments(); + for (KDevelop::IDocument* document : documents) { + textDocumentCreated(document); + } } ContextBrowserPlugin::~ContextBrowserPlugin() { + for (auto* view : qAsConst(m_textHintProvidedViews)) { + auto* iface = qobject_cast(view); + iface->unregisterTextHintProvider(&m_textHintProvider); + } + ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent); auto* codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if (!codeContext->declaration().data()) return menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if (!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n( "Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if (!toolView) { return; } auto* view = qobject_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = qobject_cast(view->navigationWidget()); if (widget && widget->context()) { auto nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if (widget) { widget->setContext(nextContext); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if (!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "could not cast to view"; } else { m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if (!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if (m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::Range& handleRange) { QVector problems; handleRange = KTextEditor::Range::invalid(); const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { const auto modelProblems = modelData.model->problems(topContext->url()); for (const auto& problem : modelProblems) { DocumentRange problemRange = problem->finalLocation(); if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) { problems += problem; // first? if (!handleRange.isValid()) { handleRange = problemRange; } else { handleRange.confineToRange(problemRange); } } } } return problems; } static QVector findProblemsCloseToCursor(const TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::Range& handleRange) { handleRange = KTextEditor::Range::invalid(); QVector allProblems; const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { const auto problems = modelData.model->problems(topContext->url()); allProblems.reserve(allProblems.size() + problems.size()); for (const auto& problem : problems) { allProblems += problem; } } if (allProblems.isEmpty()) return allProblems; std::sort(allProblems.begin(), allProblems.end(), [position](const KDevelop::IProblem::Ptr& a, const KDevelop::IProblem::Ptr& b) { const auto aRange = a->finalLocation(); const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); QVector closestProblems; // Show problems, located on the same line for (auto& problem : qAsConst(allProblems)) { auto r = problem->finalLocation(); if (r.onSingleLine() && r.start().line() == position.line()) closestProblems += problem; else break; } if (!closestProblems.isEmpty()) { auto it = closestProblems.constBegin(); handleRange = (*it)->finalLocation(); ++it; for (auto end = closestProblems.constEnd(); it != end; ++it) { handleRange.confineToRange((*it)->finalLocation()); } } return closestProblems; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position, KTextEditor::Range& itemRange) { QUrl viewUrl = view->document()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); for (const auto language : languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, position); auto navigationWidget = qobject_cast(widget.first); if (navigationWidget) { itemRange = widget.second; return navigationWidget; } } // Find problems under the cursor (first pass) TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); QVector problems; if (topContext) { problems = findProblemsUnderCursor(topContext, position, itemRange); } // Find decl (declaration) under the cursor const auto itemUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position); auto declUnderCursor = itemUnderCursor.declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { auto* alias = dynamic_cast(decl); Q_ASSERT(alias); decl = alias->aliasedDeclaration().declaration(); } // Return nullptr if the found problems / decl are already being shown in the tool tip currently. if (m_currentToolTip && problems == m_currentToolTipProblems && IndexedDeclaration(decl) == m_currentToolTipDeclaration) { return nullptr; } // Create a widget for problems, if any have been found. AbstractNavigationWidget* problemWidget = nullptr; if (!problems.isEmpty()) { problemWidget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); problemWidget->setContext(NavigationContextPointer(context)); } // Let the context create a widget for decl, if there is one. // Note that createNavigationWidget() might also return nullptr for a valid decl however. AbstractNavigationWidget* declWidget = nullptr; if (decl) { if (itemRange.isValid()) { itemRange.expandToRange(itemUnderCursor.range); } else { itemRange = itemUnderCursor.range; } declWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } // If at least one widget was created for problems or decl, show it. // If two widgets were created, combine them. if (problemWidget || declWidget) { // Remember current tool tip state. m_currentToolTipProblems = problems; m_currentToolTipDeclaration = IndexedDeclaration(decl); if (problemWidget && declWidget) { auto* combinedWidget = new QuickOpenEmbeddedWidgetCombiner; combinedWidget->layout()->addWidget(problemWidget); combinedWidget->layout()->addWidget(declWidget); return combinedWidget; } if (problemWidget) { return problemWidget; } return declWidget; } // Nothing has been found so far which created a widget. // Thus, find the closest problem to the cursor in a second pass. if (topContext) { problems = findProblemsCloseToCursor(topContext, position, itemRange); if (!problems.isEmpty()) { // Return nullptr if the correct contents are already being shown in the tool tip currently. if (m_currentToolTip && problems == m_currentToolTipProblems && !m_currentToolTipDeclaration.isValid()) { return nullptr; } // Remember current tool tip state. m_currentToolTipProblems = problems; m_currentToolTipDeclaration = {}; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext:: ShowLocation))); return widget; } } // Nothing to show has been found under or next to the cursor, so hide the tool tip (if visible). hideToolTip(); return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if (contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself KTextEditor::Range itemRange = KTextEditor::Range::invalid(); auto navigationWidget = navigationWidgetForPosition(view, position, itemRange); if (navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if (contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if (m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); if (!itemRange.isValid()) { qCWarning(PLUGIN_CONTEXTBROWSER) << "Got navigationwidget with invalid itemrange"; itemRange = KTextEditor::Range(position, 0); } tooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, itemRange)); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); QObject::connect(view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip); QObject::connect(view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if (!navigationWidget->property("DoNotCloseOnCursorMove").toBool()) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } } else { qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if (!m_highlightAttribute) { m_highlightAttribute = Attribute::Ptr(new Attribute()); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight(View* view, KDevelop::Declaration* decl) { if (!view || !decl) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { const auto currentRevisionUses = decl->usesCurrentRevision(); for (auto fileIt = currentRevisionUses.constBegin(); fileIt != currentRevisionUses.constEnd(); ++fileIt) { for (auto useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if (FunctionDefinition* def = FunctionDefinition::definition(decl)) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if (m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); } else { //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor( view->document()->url(), position).declaration); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { auto* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) const { const auto masterWidgetOfWidget = masterWidget(widget); auto it = std::find_if(m_views.begin(), m_views.end(), [&](ContextBrowserView* contextView) { return (masterWidget(contextView) == masterWidgetOfWidget); }); return (it != m_views.end()) ? *it : nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if (view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if (m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if (ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; } else { language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if (specialRange.isValid()) { // Highlight a special language object if (allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if (updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition).first); } else { KDevelop::DUChainReadLocker lock(DUChain::lock(), 100); if (!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if (core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if (foundDeclaration) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if (allowHighlight) addHighlight(view, foundDeclaration); if (updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); } else { if (updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { for (View* view : qAsConst(m_updateViews)) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if (view) m_updateViews << view; if (!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for (QMap::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if (it.key()->document()->url() == url) { if (!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if (!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); connect(document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated); const auto views = document->textDocument()->views(); for (View* view : views) { viewCreated(document->textDocument(), view); } } void ContextBrowserPlugin::documentActivated(IDocument* doc) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed(QObject* obj) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); + m_textHintProvidedViews.removeOne(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged(View* view) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout / 2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged(View* view, const KTextEditor::Cursor& newPosition) { const bool atInsertPosition = (view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos); if (atInsertPosition) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); } const auto viewHighlightsIt = m_highlightedRanges.find(view); if (viewHighlightsIt != m_highlightedRanges.end()) { viewHighlightsIt->keep = atInsertPosition; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout / 2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated(KTextEditor::Document*, View* v) { disconnect(v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged); ///Just to make sure that multiple connections don't happen connect(v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged); connect(v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed); disconnect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); auto* iface = qobject_cast(v); if (!iface) return; + if (m_textHintProvidedViews.contains(v)) { + return; + } iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); + m_textHintProvidedViews.append(v); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if (view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock(DUChain::lock()); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if (chosen) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping for (ContextBrowserView* view : qAsConst(m_views)) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if (decl) break; } if (!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { auto* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if (decl) { Declaration* target = nullptr; if (forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if (decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if (target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument(document, cursorToRange(jumpTo)); return; } else { //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if (!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if (decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if (DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if (decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if (!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if (top) { QVector useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if (!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision( forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QVector localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for (int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a + 1 : a - 1); bool pick = localUses[a].contains(c); if (!pick && forward && a + 1 < localUses.size() && localUses[a].end <= c && localUses[a + 1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if (!pick && !forward && a - 1 >= 0 && c < localUses[a].start && c >= localUses[a - 1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if (!pick && a == 0 && c < localUses[a].start) { if (!forward) { //Will automatically jump to previous file } else { nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if (!pick && a == localUses.size() - 1 && c >= localUses[a].end) { if (forward) { //Will automatically jump to next file } else { //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if (pick) { //Make sure we end up behind the use if (nextUse != a) while (forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if (nextUse != a) while (!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if (nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if (indexInFiles != -1) { int nextFile = (forward ? indexInFiles + 1 : indexInFiles - 1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if (nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if (nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if (definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; } else { TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QVector nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if (!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision( forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } } else { qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } } else { QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow(Sublime::MainWindow* window) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed(KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if (previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = contextAt(previousDocument->url(), previousCursor); if (context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); } else { //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if (newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = contextAt(newDocument->url(), newCursor); if (context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); } else { //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled(m_nextHistoryIndex < m_history.size()); m_previousButton->setEnabled(m_nextHistoryIndex >= 2); } void ContextBrowserPlugin::historyNext() { if (m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect( ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect( ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if (m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex - 1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if (actionText.isEmpty()) actionText = entry.alternativeString; if (actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line() + 1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; indices.reserve(m_history.size() - m_nextHistoryIndex); for (int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; indices.reserve(m_nextHistoryIndex - 1); for (int a = m_nextHistoryIndex - 2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); for (int index : historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex - 1); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if (m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if (!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if (m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex - 1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if (m_history.size() > (maxHistoryLength + 5)) { m_history.remove(0, m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if (!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if (function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if (m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { auto* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if (historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { auto* view = qobject_cast(sender()); if (!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if (!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if (!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if (contextView) widget = contextView->navigationWidget(); } if (auto* navWidget = dynamic_cast(widget)) { switch (action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(const KDevelop::DocumentCursor& pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if (ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if (!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); DocumentCursor ret; if (context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); } else { ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); if (context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index 439f37ccaa..fb3a16b9e9 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,289 +1,291 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class DUContext; class TopDUContext; class ReferencedTopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class ContextBrowserPlugin; class BrowseManager; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: ContextBrowserPlugin* m_plugin; }; QWidget* masterWidget(QWidget* w); struct ViewHighlights { ViewHighlights() : keep(false) { } // Whether the same highlighting should be kept highlighted (usually during typing) bool keep; // The declaration that is highlighted for this view KDevelop::IndexedDeclaration declaration; // Highlighted ranges. Those may also be contained by different views. QList highlights; }; class ContextBrowserPlugin : public KDevelop::IPlugin , public KDevelop::IContextBrowser { Q_OBJECT Q_INTERFACES(KDevelop::IContextBrowser) public: explicit ContextBrowserPlugin(QObject* parent, const QVariantList& = QVariantList()); ~ContextBrowserPlugin() override; void unload() override; void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; KXMLGUIClient* createGUIForMainWindow(Sublime::MainWindow* window) override; ///duchain must be locked ///@param force When this is true, the history-entry is added, no matter whether the context is "interesting" or not void updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void showUses(const KDevelop::DeclarationPointer& declaration) override; KTextEditor::Attribute::Ptr highlightedUseAttribute(KTextEditor::View* view) const; KTextEditor::Attribute::Ptr highlightedSpecialObjectAttribute(KTextEditor::View* view) const; public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); void textDocumentCreated(KDevelop::IDocument* document); void documentActivated(KDevelop::IDocument*); void viewDestroyed(QObject* obj); void cursorPositionChanged(KTextEditor::View* view, const KTextEditor::Cursor& newPosition); void viewCreated(KTextEditor::Document*, KTextEditor::View*); void updateViews(); void hideToolTip(); void findUses(); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void selectionChanged(KTextEditor::View*); void historyNext(); void historyPrevious(); void colorSetupChanged(); private Q_SLOTS: // history browsing void documentJumpPerformed(KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position, KTextEditor::Range& itemRange); void switchUse(bool forward); void clearMouseHover(); void addHighlight(KTextEditor::View* view, KDevelop::Declaration* decl); /** helper for updateBrowserView(). * Tries to find a 'specialLanguageObject' (eg macro) in @p view under cursor @c. * If found returns true and sets @p pickedLanguage to the language this object belongs to */ KDevelop::Declaration* findDeclaration(KTextEditor::View* view, const KTextEditor::Cursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KTextEditor::Cursor& cursor) const; QString actionTextFor(int historyIndex) const; void updateButtonState(); void openDocument(int historyIndex); void fillHistoryPopup(QMenu* menu, const QList& historyIndices); enum NavigationActionType { Accept, Back, Down, Up, Left, Right }; void doNavigate(NavigationActionType action); private: // Returns the currently active and visible context browser view that belongs // to the same context (mainwindow and area) as the given widget ContextBrowserView* browserViewForWidget(QWidget* widget) const; void showToolTip(KTextEditor::View* view, KTextEditor::Cursor position); QTimer* m_updateTimer; //Contains the range, the old attribute, and the attribute it was replaced with QSet m_updateViews; QMap m_highlightedRanges; //Holds a list of all active context browser tool views QList m_views; + QVector m_textHintProvidedViews; + //Used to override the next declaration that will be highlighted KDevelop::IndexedDeclaration m_useDeclaration; KDevelop::IndexedDeclaration m_lastHighlightedDeclaration; QUrl m_mouseHoverDocument; KTextEditor::Cursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; QVector m_currentToolTipProblems; QAction* m_findUses; QPointer m_lastInsertionDocument; KTextEditor::Cursor m_lastInsertionPos; // outline toolbar QPointer m_outlineLine; QPointer m_toolbarWidgetLayout; QPointer m_toolbarWidget; // history browsing struct HistoryEntry { //Duchain must be locked explicit HistoryEntry( KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); explicit HistoryEntry(const KDevelop::DocumentCursor& pos); //Duchain must be locked void setCursorPosition(const KTextEditor::Cursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KTextEditor::Cursor relativeCursorPosition; //Cursor position relative to the start line of the context QString alternativeString; }; QVector m_history; QPointer m_previousButton; QPointer m_nextButton; QPointer m_previousMenu, m_nextMenu; QList m_listDeclarations; KDevelop::IndexedString m_listUrl; BrowseManager* m_browseManager; //Used to not record jumps triggered by the context-browser as history entries QPointer m_focusBackWidget; int m_nextHistoryIndex; mutable KTextEditor::Attribute::Ptr m_highlightAttribute; friend class ContextBrowserHintProvider; ContextBrowserHintProvider m_textHintProvider; }; #endif // KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H