diff --git a/language/highlighting/configurablecolors.cpp b/language/highlighting/configurablecolors.cpp index e9e8c5307..bac352c9d 100644 --- a/language/highlighting/configurablecolors.cpp +++ b/language/highlighting/configurablecolors.cpp @@ -1,90 +1,90 @@ /* * This file is part of KDevelop * * Copyright 2007-2008 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * 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 "configurablecolors.h" #include "util/debug.h" #define ifDebug(x) namespace KDevelop { KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::defaultAttribute() const { return m_defaultAttribute; } void ConfigurableHighlightingColors::setDefaultAttribute(KTextEditor::Attribute::Ptr defaultAttrib) { m_defaultAttribute = defaultAttrib; } KTextEditor::Attribute::Ptr ConfigurableHighlightingColors::getAttribute(int number) const { return m_attributes[number]; } void ConfigurableHighlightingColors::addAttribute(int number, KTextEditor::Attribute::Ptr attribute) { m_attributes[number] = attribute; } -ConfigurableHighlightingColors::ConfigurableHighlightingColors(QString highlightingName) : m_highlightingName(highlightingName) +ConfigurableHighlightingColors::ConfigurableHighlightingColors() { KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); setDefaultAttribute(a); } #define ADD_COLOR(type, color_) \ { \ KTextEditor::Attribute::Ptr a(new KTextEditor::Attribute); \ a->setForeground(QColor(cache->blendGlobalColor(color_))); \ addAttribute(CodeHighlighting::type, a); \ ifDebug(qCDebug(LANGUAGE) << #type << "color: " << #color_ << "->" << a->foreground().color().name();) \ } -CodeHighlightingColors::CodeHighlightingColors(ColorCache* cache) : ConfigurableHighlightingColors(QStringLiteral("KDev Semantic Highlighting")) +CodeHighlightingColors::CodeHighlightingColors(ColorCache* cache) : ConfigurableHighlightingColors() { // TODO: The set of colors doesn't work very well. Many colors simply too dark (even on the maximum "Global colorization intensity" they hardly distinguishable from grey) and look alike. ADD_COLOR(ClassType, 0x005912) //Dark green ADD_COLOR(TypeAliasType, 0x35938d) ADD_COLOR(EnumType, 0x6c101e) //Dark red ADD_COLOR(EnumeratorType, 0x862a38) //Greyish red ADD_COLOR(FunctionType, 0x21005A) //Navy blue ADD_COLOR(MemberVariableType, 0x443069) //Dark Burple (blue/purple) ADD_COLOR(LocalClassMemberType, 0xae7d00) //Light orange ADD_COLOR(InheritedClassMemberType, 0x705000) //Dark orange ADD_COLOR(LocalVariableType, 0x0C4D3C) ADD_COLOR(FunctionVariableType, 0x300085) //Less dark navy blue ADD_COLOR(NamespaceVariableType, 0x9F3C5F) //Rose ADD_COLOR(GlobalVariableType, 0x12762B) //Grass green ADD_COLOR(NamespaceType, 0x6B2840) //Dark rose ADD_COLOR(ErrorVariableType, 0x8b0019) //Pure red ADD_COLOR(ForwardDeclarationType, 0x5C5C5C) //Gray ADD_COLOR(MacroType, 0xA41239) ADD_COLOR(MacroFunctionLikeType, 0x008080) } } // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/highlighting/configurablecolors.h b/language/highlighting/configurablecolors.h index 379b2148b..52b07be4d 100644 --- a/language/highlighting/configurablecolors.h +++ b/language/highlighting/configurablecolors.h @@ -1,61 +1,61 @@ /* * This file is part of KDevelop * * Copyright 2007-2008 David Nolden * Copyright 2006 Hamish Rodda * Copyright 2009 Milian Wolff * * 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_CONFIGURABLECOLORS_H #define KDEVPLATFORM_CONFIGURABLECOLORS_H #include "codehighlighting.h" #include "colorcache.h" #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ConfigurableHighlightingColors { public: - explicit ConfigurableHighlightingColors(QString highlightingName); + explicit ConfigurableHighlightingColors(); void addAttribute(int number, KTextEditor::Attribute::Ptr attribute); KTextEditor::Attribute::Ptr getAttribute(int number) const; void setDefaultAttribute(KTextEditor::Attribute::Ptr defaultAttrib); KTextEditor::Attribute::Ptr defaultAttribute() const; private: KTextEditor::Attribute::Ptr m_defaultAttribute; QHash m_attributes; QString m_highlightingName; }; // ######### start CodeHighlightingColors class CodeHighlightingColors : public ConfigurableHighlightingColors { public: explicit CodeHighlightingColors(ColorCache* cache); }; } #endif // KDEVPLATFORM_CONFIGURABLECOLORS_H // kate: space-indent on; indent-width 2; remove-trailing-spaces all; show-tabs on; tab-indents on; tab-width 2; diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 0f7dc2693..69203f739 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1473 +1,1469 @@ /* * 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 Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return 0; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = 0) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); + connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { NavigationContextPointer nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qWarning() << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; m_currentToolTipProblem = {}; m_currentToolTipDeclaration = {}; } } static ProblemPointer findProblemUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) { const auto top = ReferencedTopDUContext(topContext); foreach (auto problem, DUChainUtils::allProblemsForContext(top)) { if (problem->rangeInCurrentRevision().contains(position)) { return problem; } } return {}; } static ProblemPointer findProblemCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) { const auto top = ReferencedTopDUContext(topContext); auto problems = DUChainUtils::allProblemsForContext(top); if (problems.isEmpty()) return {}; auto closestProblem = std::min_element(problems.constBegin(), problems.constEnd(), [position](const ProblemPointer& a, const ProblemPointer& b) { const auto aRange = a->rangeInCurrentRevision(); const auto bRange = b->rangeInCurrentRevision(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); auto r = (*closestProblem)->rangeInCurrentRevision(); if (!r.contains(position)) { if (r.start().line() == position.line() || r.end().line() == position.line()) { // problem is on the same line, let's use it return *closestProblem; } // if not, only show it in case there's only whitespace between the current cursor pos and the problem auto dist = position < r.start() ? KTextEditor::Range(position, r.start()) : KTextEditor::Range(r.end(), position); auto textBetween = view->document()->text(dist); auto isSpace = std::all_of(textBetween.begin(), textBetween.end(), [](QChar c) { return c.isSpace(); }); if (!isSpace) { return {}; } } return *closestProblem; } QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) { QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); foreach (const auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); auto navigationWidget = qobject_cast(widget); if(navigationWidget) return navigationWidget; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor const auto problem = findProblemUnderCursor(topContext, position); if (problem) { if (problem == m_currentToolTipProblem && m_currentToolTip) { return nullptr; } m_currentToolTipProblem = problem; auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problem); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor const auto problem = findProblemCloseToCursor(topContext, position, view); if (problem) { if (problem == m_currentToolTipProblem && m_currentToolTip) { return nullptr; } m_currentToolTipProblem = problem; auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself auto navigationWidget = navigationWidgetForPosition(view, position); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); KTextEditor::Range itemRange; { DUChainReadLocker lock; auto viewUrl = view->document()->url(); itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; } tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } -Attribute::Ptr highlightedUseAttribute() { - static Attribute::Ptr standardAttribute = Attribute::Ptr(); - if( !standardAttribute ) { - standardAttribute= Attribute::Ptr( new Attribute() ); - standardAttribute->setBackgroundFillWhitespace(true); - - // mixing (255, 255, 0, 100) with white yields this: - standardAttribute->setBackground(QColor(251, 250, 150)); +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); - // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar - // foreground color could change, hence apply it everytime - standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color + auto iface = qobject_cast(view); + auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); + m_highlightAttribute->setBackground(background); } - return standardAttribute; -} - -Attribute::Ptr highlightedSpecialObjectAttribute() { - static Attribute::Ptr standardAttribute = Attribute::Ptr(); - if( !standardAttribute ) { - standardAttribute = Attribute::Ptr( new Attribute() ); - standardAttribute->setBackgroundFillWhitespace(true); - // mixing (90, 255, 0, 100) with white yields this: - standardAttribute->setBackground(QColor(190, 255, 155)); - // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar - // foreground color could change, hence apply it everytime - standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color - } - return standardAttribute; + 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()); + highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); - highlights.highlights.back()->setAttribute(highlightedUseAttribute()); + 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()); + 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 = 0; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return 0; } 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) : 0; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); - highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute()); + highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = 0; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = 0; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = 0; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qWarning() << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index 3c616a18f..1b17ede9a 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,275 +1,282 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class DUContext; class TopDUContext; class ReferencedTopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class ContextBrowserPlugin; class BrowseManager; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: ContextBrowserPlugin* m_plugin; }; QWidget* masterWidget(QWidget* w); struct ViewHighlights { ViewHighlights() : keep(false) { } // Whether the same highlighting should be kept highlighted (usually during typing) bool keep; // The declaration that is highlighted for this view KDevelop::IndexedDeclaration declaration; // Highlighted ranges. Those may also be contained by different views. QList highlights; }; class ContextBrowserPlugin : public KDevelop::IPlugin, public KDevelop::IContextBrowser { Q_OBJECT Q_INTERFACES( KDevelop::IContextBrowser ) public: explicit ContextBrowserPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ContextBrowserPlugin() override; void unload() override; void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context*) override; KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ) override; ///duchain must be locked ///@param force When this is true, the history-entry is added, no matter whether the context is "interesting" or not void updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void showUses(const KDevelop::DeclarationPointer& declaration) override; + KTextEditor::Attribute::Ptr highlightedUseAttribute(KTextEditor::View* view) const; + KTextEditor::Attribute::Ptr highlightedSpecialObjectAttribute(KTextEditor::View* view) const; + public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); void textDocumentCreated( KDevelop::IDocument* document ); void documentActivated( KDevelop::IDocument* ); void viewDestroyed( QObject* obj ); void cursorPositionChanged( KTextEditor::View* view, const KTextEditor::Cursor& newPosition ); void viewCreated( KTextEditor::Document* , KTextEditor::View* ); void updateViews(); void hideToolTip(); void findUses(); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void selectionChanged(KTextEditor::View*); void historyNext(); void historyPrevious(); + void colorSetupChanged(); + private slots: // history browsing void documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position); void switchUse(bool forward); void clearMouseHover(); void addHighlight( KTextEditor::View* view, KDevelop::Declaration* decl ); /** helper for updateBrowserView(). * Tries to find a 'specialLanguageObject' (eg macro) in @p view under cursor @c. * If found returns true and sets @p pickedLanguage to the language this object belongs to */ KDevelop::Declaration* findDeclaration(KTextEditor::View* view, const KTextEditor::Cursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KTextEditor::Cursor& cursor) const; QString actionTextFor(int historyIndex) const; void updateButtonState(); void openDocument(int historyIndex); void fillHistoryPopup(QMenu* menu, const QList& historyIndices); enum NavigationActionType { Accept, Back, Down, Up, Left, Right }; void doNavigate(NavigationActionType action); private: // Returns the currently active and visible context browser view that belongs // to the same context (mainwindow and area) as the given widget ContextBrowserView* browserViewForWidget(QWidget* widget); void showToolTip(KTextEditor::View* view, KTextEditor::Cursor position); QTimer* m_updateTimer; //Contains the range, the old attribute, and the attribute it was replaced with QSet m_updateViews; QMap m_highlightedRanges; //Holds a list of all active context browser tool views QList m_views; //Used to override the next declaration that will be highlighted KDevelop::IndexedDeclaration m_useDeclaration; KDevelop::IndexedDeclaration m_lastHighlightedDeclaration; QUrl m_mouseHoverDocument; KTextEditor::Cursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; KDevelop::Problem::Ptr m_currentToolTipProblem; QAction* m_findUses; QPointer m_lastInsertionDocument; KTextEditor::Cursor m_lastInsertionPos; // outline toolbar QPointer m_outlineLine; QPointer m_toolbarWidgetLayout; QPointer m_toolbarWidget; // history browsing struct HistoryEntry { //Duchain must be locked HistoryEntry(KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); HistoryEntry(KDevelop::DocumentCursor pos); //Duchain must be locked void setCursorPosition(const KTextEditor::Cursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KTextEditor::Cursor relativeCursorPosition; //Cursor position relative to the start line of the context QString alternativeString; }; QVector m_history; QPointer m_previousButton; QPointer m_nextButton; QPointer m_previousMenu, m_nextMenu; QList m_listDeclarations; KDevelop::IndexedString m_listUrl; BrowseManager* m_browseManager; //Used to not record jumps triggered by the context-browser as history entries QPointer m_focusBackWidget; int m_nextHistoryIndex; + mutable KTextEditor::Attribute::Ptr m_highlightAttribute; + friend class ContextBrowserHintProvider; ContextBrowserHintProvider m_textHintProvider; }; #endif // KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index fb5def679..151539799 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1202 +1,1204 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library 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 library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectcontroller.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 #include "core.h" // TODO: Should get rid off this include (should depend on IProject only) #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include "sessioncontroller.h" #include "session.h" #include "debug.h" namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QItemSelectionModel* selectionModel; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate( ProjectController* p ) : m_core(0), model(0), selectionModel(0), dialog(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; QVector configPages; auto mainWindow = m_core->uiController()->activeMainWindow(); ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; foreach (IPlugin* plugin, findPluginsForProject(proj)) { for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); } } std::sort(configPages.begin(), configPages.end(), [](const ConfigPage* a, const ConfigPage* b) { return a->name() < b->name(); }); auto cfgDlg = new KDevelop::ConfigDialog(configPages, mainWindow); cfgDlg->setAttribute(Qt::WA_DeleteOnClose); cfgDlg->setModal(true); QObject::connect(cfgDlg, &ConfigDialog::configSaved, cfgDlg, [this, proj](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(proj, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(proj); }); cfgDlg->setWindowTitle(i18n("Configure Project %1", proj->name())); QObject::connect(cfgDlg, &KDevelop::ConfigDialog::finished, [this, proj]() { proj->projectConfiguration()->sync(); }); cfgDlg->show(); } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); foreach( IProject* project, m_projects ) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } foreach(auto plugin, plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const QUrl& url_) { QUrl url(url_); if (url.isLocalFile()) { const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!path.isEmpty()) { url = QUrl::fromLocalFile(path); } } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile))); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile) ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFile().toUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code")); ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code")); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "CreatedFrom", createdFrom ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } // Here and above we take .filename() part of the selectedUrl() to make it relative to the project root, // thus keeping .kdev file relocatable return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo::exists( u.toLocalFile() ); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return QUrl(); QUrl projectFileUrl = dlg.projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); if ( dlg.projectManager() == "" ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl == dlg.selectedUrl() ) { if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg); } } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, &dlg)) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); d->m_core = core; d->model = new ProjectModel(); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( QStringLiteral("project_open") ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows one to select a KDevelop4 project file " "or an existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) ); action->setEnabled( false ); action = ac->addAction( QStringLiteral("commit_current_project") ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent Project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); QAction* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } closeAllProjects(); } void ProjectController::initialize() { d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); d->selectionModel = new QItemSelectionModel(d->model); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"), this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); QList openProjects = group.readEntry( "Open Projects", QList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects)); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [&] (Context* ctx) { d->updateActionStates(ctx); } ); } void ProjectController::openProjects(const QList& projects) { foreach (const QUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return 0; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(QLatin1String(".kdev4"))) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename); QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif } } } if ( ! existingSessions.isEmpty() ) { QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); dialog.setWindowTitle(i18n("Project Already Open")); auto mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); QRadioButton* newSession = new QRadioButton(i18n("Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); foreach ( const Session* session, existingSessions ) { QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); bool success = dialog.exec(); if (!success) return; foreach ( const QObject* obj, sessions.children() ) { if ( const QRadioButton* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { qWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); - d->saveListOfOpenedProjects(); + if ( d->m_currentlyOpening.isEmpty() ) { + d->saveListOfOpenedProjects(); + } if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFile().toUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl())); d->m_currentlyOpening.removeAll(project->projectFile().toUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); }); } void ProjectController::takeProject(IProject* proj) { if (!proj) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); } void ProjectController::closeProject(IProject* proj) { takeProject(proj); proj->deleteLater(); // be safe when deleting } void ProjectController::closeAllProjects() { foreach (auto project, d->m_projects) { closeProject(project); } } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { if (d->m_projects.isEmpty()) { return 0; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return 0; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } return 0; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { Q_ASSERT(project); if (d->m_projects.contains(project)) { qWarning() << "Project already tracked by this project controller:" << project; return; } // fake-emit signals so listeners are aware of a new project being added emit projectAboutToBeOpened(project); project->setParent(this); d->m_projects.append(project); emit projectOpened(project); } QItemSelectionModel* ProjectController::projectSelectionModel() { return d->selectionModel; } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) ); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->path().toUrl().isParentOf(url)) { project = candidateProject; break; } } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + ':'; } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath = relativePath.mid(2); } if (!relativePath.isEmpty()) { prefixText += relativePath + '/'; } } else { prefixText = parent.pathOrUrl() + '/'; } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Path path(path_); IProject* sourceDirProject = 0, *buildDirProject = 0; Q_FOREACH(IProject* proj, d->m_projects) { if(proj->path().isParentOf(path) || proj->path() == path) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void ProjectController::reparseProject( IProject* project, bool forceUpdate ) { if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } #include "moc_projectcontroller.cpp"