diff --git a/interfaces/iuicontroller.h b/interfaces/iuicontroller.h index 6ee0faebb..fe7d07ff0 100644 --- a/interfaces/iuicontroller.h +++ b/interfaces/iuicontroller.h @@ -1,184 +1,174 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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 Library 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_IUICONTROLLER_H #define KDEVPLATFORM_IUICONTROLLER_H #include "interfacesexport.h" #include #include class QAction; template class QExplicitlySharedDataPointer; namespace KParts { class MainWindow; } namespace Sublime{ class Controller; class View; class Area; } namespace KDevelop { class IDocument; class IAssistant; class KDEVPLATFORMINTERFACES_EXPORT IToolViewFactory { public: virtual ~IToolViewFactory() {} /** * called to create a new widget for this toolview * @param parent the parent to use as parent for the widget * @returns the new widget for the toolview */ virtual QWidget* create(QWidget *parent = 0) = 0; /** * @returns the identifier of this toolview. The identifier * is used to remember which areas the tool view should appear * in, and must never change. */ virtual QString id() const = 0; /** * @returns the default position where this toolview should appear */ virtual Qt::DockWidgetArea defaultPosition() = 0; /** * Fetch a list of actions to add to the toolbar of the toolview @p view * @param view the view to which the actions should be added * @returns a list of actions to be added to the toolbar */ virtual QList toolBarActions( QWidget* viewWidget ) const { return viewWidget->actions(); } /** * Fetch a list of actions to be shown in the context menu of the toolview @p view. * The default implementation will return all actions of @p viewWidget. * * @param view the view for which the context menu should be shown * @returns a list of actions to be shown in the context menu */ virtual QList contextMenuActions( QWidget* viewWidget ) const { return viewWidget->actions(); } /** * called when a new view is created from this template * @param view the new sublime view that is being shown */ virtual void viewCreated(Sublime::View* view); /** * @returns if multiple tool views can by created by this factory in the same area. */ virtual bool allowMultiple() const { return false; } }; /** * * Allows to access various parts of the user-interface, like the toolviews or the mainwindow */ class KDEVPLATFORMINTERFACES_EXPORT IUiController { public: virtual ~IUiController(); enum SwitchMode { ThisWindow /**< indicates that the area switch should be in the this window */, NewWindow /**< indicates that the area switch should be using a new window */ }; enum FindFlags { None = 0, Create = 1, ///The tool-view is created if it doesn't exist in the current area yet Raise = 2, ///The tool-view is raised if it was found/created CreateAndRaise = Create | Raise ///The tool view is created and raised }; virtual void switchToArea(const QString &areaName, SwitchMode switchMode) = 0; virtual void addToolView(const QString &name, IToolViewFactory *factory, FindFlags state = Create) = 0; virtual void removeToolView(IToolViewFactory *factory) = 0; /** Makes sure that this tool-view exists in the current area, raises it, and returns the contained widget * Returns zero on failure */ virtual QWidget* findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags = CreateAndRaise) = 0; /** * Makes sure that the toolview that contains the widget @p toolViewWidget is visible to the user. */ virtual void raiseToolView(QWidget* toolViewWidget) = 0; /** @return active mainwindow or 0 if no such mainwindow is active.*/ virtual KParts::MainWindow *activeMainWindow() = 0; /*! @p status must implement KDevelop::IStatus */ virtual void registerStatus(QObject* status) = 0; - /** - * Shows an assistant popup at bottom within the current central widget - * @p assistant the assistant that will be shown in a popup */ - virtual void popUpAssistant(const QExplicitlySharedDataPointer& assistant) = 0; - - /** - * Hides the assistant if it is currently being shown - */ - virtual void hideAssistant() = 0; - /** * This is meant to be used by IDocument subclasses to initialize the * Sublime::Document. */ virtual Sublime::Controller* controller() = 0; /** Shows an error message in the status bar. * * Unlike all other functions in this class, this function is thread-safe. * You can call it from the background. * * @p message The message * @p timeout The timeout in seconds how long to show the message */ virtual void showErrorMessage(const QString& message, int timeout = 1) = 0; /** @return area for currently active sublime mainwindow or 0 if no sublime mainwindow is active.*/ virtual Sublime::Area *activeArea() = 0; /** * Widget which is currently responsible for consuming special events in the UI * (such as shortcuts) * * @sa IToolViewActionListener * @return QWidget implementing the IToolViewActionListener interface */ virtual QWidget* activeToolViewActionListener() const = 0; /** * @returns all areas in the shell * * @note there will be one per mainwindow, of each type, plus the default ones. */ virtual QList allAreas() const = 0; protected: IUiController(); }; } #endif diff --git a/language/CMakeLists.txt b/language/CMakeLists.txt index bbe645609..2065f837b 100644 --- a/language/CMakeLists.txt +++ b/language/CMakeLists.txt @@ -1,398 +1,399 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") # Check whether malloc_trim(3) is supported. include(CheckIncludeFile) include(CheckSymbolExists) check_include_file("malloc.h" HAVE_MALLOC_H) check_symbol_exists(malloc_trim "malloc.h" HAVE_MALLOC_TRIM) add_subdirectory(highlighting/tests) add_subdirectory(duchain/tests) add_subdirectory(backgroundparser/tests) add_subdirectory(codegen/tests) add_subdirectory(util/tests) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/language-features.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/language-features.h ) set(KDevPlatformLanguage_LIB_SRCS assistant/staticassistantsmanager.cpp assistant/renameaction.cpp assistant/renameassistant.cpp assistant/renamefileaction.cpp assistant/staticassistant.cpp editor/persistentmovingrangeprivate.cpp editor/persistentmovingrange.cpp editor/modificationrevisionset.cpp editor/modificationrevision.cpp backgroundparser/backgroundparser.cpp backgroundparser/parsejob.cpp backgroundparser/documentchangetracker.cpp backgroundparser/parseprojectjob.cpp backgroundparser/urlparselock.cpp duchain/specializationstore.cpp duchain/codemodel.cpp duchain/duchain.cpp duchain/waitforupdate.cpp duchain/duchainpointer.cpp duchain/ducontext.cpp duchain/indexedducontext.cpp duchain/indexedtopducontext.cpp duchain/localindexedducontext.cpp duchain/indexeddeclaration.cpp duchain/localindexeddeclaration.cpp duchain/topducontext.cpp duchain/topducontextdynamicdata.cpp duchain/topducontextutils.cpp duchain/functiondefinition.cpp duchain/declaration.cpp duchain/classmemberdeclaration.cpp duchain/classfunctiondeclaration.cpp duchain/classdeclaration.cpp duchain/use.cpp duchain/forwarddeclaration.cpp duchain/duchainbase.cpp duchain/duchainlock.cpp duchain/identifier.cpp duchain/parsingenvironment.cpp duchain/abstractfunctiondeclaration.cpp duchain/functiondeclaration.cpp duchain/stringhelpers.cpp duchain/namespacealiasdeclaration.cpp duchain/aliasdeclaration.cpp duchain/dumpdotgraph.cpp duchain/duchainutils.cpp duchain/declarationid.cpp duchain/definitions.cpp duchain/uses.cpp duchain/importers.cpp duchain/duchaindumper.cpp duchain/duchainregister.cpp duchain/persistentsymboltable.cpp duchain/instantiationinformation.cpp duchain/problem.cpp duchain/types/typesystem.cpp duchain/types/typeregister.cpp duchain/types/typerepository.cpp duchain/types/identifiedtype.cpp duchain/types/abstracttype.cpp duchain/types/integraltype.cpp duchain/types/functiontype.cpp duchain/types/structuretype.cpp duchain/types/pointertype.cpp duchain/types/referencetype.cpp duchain/types/delayedtype.cpp duchain/types/arraytype.cpp duchain/types/indexedtype.cpp duchain/types/enumerationtype.cpp duchain/types/constantintegraltype.cpp duchain/types/enumeratortype.cpp duchain/types/typeutils.cpp duchain/types/typealiastype.cpp duchain/types/unsuretype.cpp duchain/types/containertypes.cpp duchain/builders/dynamiclanguageexpressionvisitor.cpp duchain/navigation/problemnavigationcontext.cpp duchain/navigation/abstractnavigationwidget.cpp duchain/navigation/abstractnavigationcontext.cpp duchain/navigation/usesnavigationcontext.cpp duchain/navigation/abstractdeclarationnavigationcontext.cpp duchain/navigation/abstractincludenavigationcontext.cpp duchain/navigation/useswidget.cpp duchain/navigation/usescollector.cpp interfaces/abbreviations.cpp interfaces/iastcontainer.cpp interfaces/ilanguagesupport.cpp interfaces/quickopendataprovider.cpp interfaces/iquickopen.cpp interfaces/editorcontext.cpp interfaces/codecontext.cpp interfaces/icreateclasshelper.cpp interfaces/icontextbrowser.cpp codecompletion/codecompletion.cpp codecompletion/codecompletionworker.cpp codecompletion/codecompletionmodel.cpp codecompletion/codecompletionitem.cpp codecompletion/codecompletioncontext.cpp codecompletion/codecompletionitemgrouper.cpp codecompletion/codecompletionhelper.cpp codecompletion/normaldeclarationcompletionitem.cpp codegen/applychangeswidget.cpp codegen/coderepresentation.cpp codegen/documentchangeset.cpp codegen/duchainchangeset.cpp codegen/utilities.cpp codegen/codedescription.cpp codegen/basicrefactoring.cpp codegen/progressdialogs/refactoringdialog.cpp util/setrepository.cpp util/includeitem.cpp util/navigationtooltip.cpp util/debug.cpp highlighting/colorcache.cpp highlighting/configurablecolors.cpp highlighting/codehighlighting.cpp checks/dataaccessrepository.cpp checks/dataaccess.cpp checks/controlflowgraph.cpp checks/controlflownode.cpp classmodel/classmodel.cpp classmodel/classmodelnode.cpp classmodel/classmodelnodescontroller.cpp classmodel/allclassesfolder.cpp classmodel/documentclassesfolder.cpp classmodel/projectfolder.cpp ) set(grantlee_LIB_SRCS codegen/templatesmodel.cpp codegen/templateclassgenerator.cpp codegen/sourcefiletemplate.cpp codegen/templaterenderer.cpp codegen/templateengine.cpp codegen/archivetemplateloader.cpp ) if (Grantlee5_FOUND) list(APPEND KDevPlatformLanguage_LIB_SRCS ${grantlee_LIB_SRCS}) endif() ki18n_wrap_ui(KDevPlatformLanguage_LIB_SRCS codegen/basicrefactoring.ui codegen/progressdialogs/refactoringdialog.ui) kdevplatform_add_library(KDevPlatformLanguage SOURCES ${KDevPlatformLanguage_LIB_SRCS}) target_include_directories(KDevPlatformLanguage PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(KDevPlatformLanguage LINK_PUBLIC KF5::ThreadWeaver KDev::Interfaces KDev::Serialization LINK_PRIVATE KF5::GuiAddons KF5::TextEditor KF5::Parts KF5::Archive + KF5::IconThemes KDev::Util KDev::Project ) if (Grantlee5_FOUND) target_link_libraries(KDevPlatformLanguage LINK_PRIVATE Grantlee5::Templates) endif() install(FILES assistant/renameaction.h assistant/renameassistant.h assistant/staticassistant.h assistant/staticassistantsmanager.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/assistant COMPONENT Devel ) install(FILES interfaces/ilanguagesupport.h interfaces/icodehighlighting.h interfaces/quickopendataprovider.h interfaces/quickopenfilter.h interfaces/iquickopen.h interfaces/codecontext.h interfaces/editorcontext.h interfaces/iastcontainer.h interfaces/icreateclasshelper.h interfaces/icontextbrowser.h interfaces/abbreviations.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/interfaces COMPONENT Devel ) install(FILES editor/persistentmovingrange.h editor/documentrange.h editor/documentcursor.h editor/cursorinrevision.h editor/rangeinrevision.h editor/modificationrevision.h editor/modificationrevisionset.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/editor COMPONENT Devel ) install(FILES backgroundparser/backgroundparser.h backgroundparser/parsejob.h backgroundparser/parseprojectjob.h backgroundparser/urlparselock.h backgroundparser/documentchangetracker.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/backgroundparser COMPONENT Devel ) install(FILES util/navigationtooltip.h util/setrepository.h util/basicsetrepository.h util/includeitem.h util/debuglanguageparserhelper.h util/kdevhash.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/util COMPONENT Devel ) install(FILES duchain/parsingenvironment.h duchain/duchain.h duchain/codemodel.h duchain/ducontext.h duchain/ducontextdata.h duchain/topducontext.h duchain/topducontextutils.h duchain/topducontextdata.h duchain/declaration.h duchain/declarationdata.h duchain/classmemberdeclaration.h duchain/classmemberdeclarationdata.h duchain/classfunctiondeclaration.h duchain/classdeclaration.h duchain/functiondefinition.h duchain/use.h duchain/forwarddeclaration.h duchain/duchainbase.h duchain/duchainpointer.h duchain/duchainlock.h duchain/identifier.h duchain/abstractfunctiondeclaration.h duchain/functiondeclaration.h duchain/stringhelpers.h duchain/safetycounter.h duchain/namespacealiasdeclaration.h duchain/aliasdeclaration.h duchain/dumpdotgraph.h duchain/duchainutils.h duchain/duchaindumper.h duchain/declarationid.h duchain/appendedlist.h duchain/duchainregister.h duchain/persistentsymboltable.h duchain/instantiationinformation.h duchain/specializationstore.h duchain/persistentsetmap.h duchain/indexedducontext.h duchain/indexedtopducontext.h duchain/localindexedducontext.h duchain/indexeddeclaration.h duchain/localindexeddeclaration.h duchain/definitions.h duchain/problem.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain COMPONENT Devel ) install(FILES duchain/types/unsuretype.h duchain/types/identifiedtype.h duchain/types/typesystem.h duchain/types/typeregister.h duchain/types/typerepository.h duchain/types/typepointer.h duchain/types/typesystemdata.h duchain/types/abstracttype.h duchain/types/integraltype.h duchain/types/functiontype.h duchain/types/structuretype.h duchain/types/pointertype.h duchain/types/referencetype.h duchain/types/delayedtype.h duchain/types/arraytype.h duchain/types/indexedtype.h duchain/types/enumerationtype.h duchain/types/constantintegraltype.h duchain/types/enumeratortype.h duchain/types/alltypes.h duchain/types/typeutils.h duchain/types/typealiastype.h duchain/types/containertypes.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/types COMPONENT Devel ) install(FILES duchain/builders/abstractcontextbuilder.h duchain/builders/abstractdeclarationbuilder.h duchain/builders/abstracttypebuilder.h duchain/builders/abstractusebuilder.h duchain/builders/dynamiclanguageexpressionvisitor.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/builders COMPONENT Devel ) install(FILES codecompletion/codecompletion.h codecompletion/codecompletionworker.h codecompletion/codecompletionmodel.h codecompletion/codecompletionitem.h codecompletion/codecompletioncontext.h codecompletion/codecompletionitemgrouper.h codecompletion/codecompletionhelper.h codecompletion/normaldeclarationcompletionitem.h codecompletion/abstractincludefilecompletionitem.h codecompletion/codecompletiontesthelper.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/codecompletion COMPONENT Devel ) install(FILES codegen/applychangeswidget.h codegen/astchangeset.h codegen/duchainchangeset.h codegen/documentchangeset.h codegen/coderepresentation.h codegen/utilities.h codegen/templatesmodel.h codegen/templaterenderer.h codegen/templateengine.h codegen/sourcefiletemplate.h codegen/templateclassgenerator.h codegen/codedescription.h codegen/basicrefactoring.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/codegen COMPONENT Devel ) install(FILES duchain/navigation/usesnavigationcontext.h duchain/navigation/abstractnavigationcontext.h duchain/navigation/abstractdeclarationnavigationcontext.h duchain/navigation/abstractincludenavigationcontext.h duchain/navigation/abstractnavigationwidget.h duchain/navigation/navigationaction.h duchain/navigation/useswidget.h duchain/navigation/usescollector.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/duchain/navigation COMPONENT Devel ) install(FILES highlighting/codehighlighting.h highlighting/colorcache.h highlighting/configurablecolors.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/highlighting COMPONENT Devel ) install(FILES checks/dataaccess.h checks/dataaccessrepository.h checks/controlflowgraph.h checks/controlflownode.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/checks COMPONENT Devel ) install(FILES classmodel/classmodel.h classmodel/classmodelnode.h classmodel/classmodelnodescontroller.h classmodel/allclassesfolder.h classmodel/documentclassesfolder.h classmodel/projectfolder.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/language/classmodel COMPONENT Devel ) diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index 048f2d1ac..dfc7fb8e0 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,223 +1,238 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" #include "util/debug.h" #include "../codegen/basicrefactoring.h" #include "../codegen/documentchangeset.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include #include #include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } -Declaration* getDeclarationForChangedRange(KTextEditor::View* view, const KTextEditor::Range& changed) +Declaration* getDeclarationForChangedRange(KTextEditor::Document* doc, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); - Declaration* declaration = DUChainUtils::itemUnderCursor(view->document()->url(), cursor); + Declaration* declaration = DUChainUtils::itemUnderCursor(doc->url(), cursor); //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { - declaration = DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)); + declaration = DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)); } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { return 0; } return declaration; } } struct RenameAssistant::Private { Private(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; + KTextEditor::Cursor m_lastChangedLocation; + QPointer m_lastChangedDocument = nullptr; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new Private(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return tr("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } -void RenameAssistant::textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText) +void RenameAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); + d->m_lastChangedLocation = invocationRange.end(); + d->m_lastChangedDocument = doc; if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } - if (!view) + if (!doc) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName("^[0-9a-zA-Z_]*$"); - if (removedText.isEmpty() && !validDeclName.exactMatch(view->document()->text(invocationRange))) { + if (removedText.isEmpty() && !validDeclName.exactMatch(doc->text(invocationRange))) { d->reset(); return; } - const QUrl url = view->document()->url(); + const QUrl url = doc->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); - Declaration* declAtCursor = getDeclarationForChangedRange(view, invocationRange); + Declaration* declAtCursor = getDeclarationForChangedRange(doc, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { QMap< IndexedString, QList > declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration has no uses return; } for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); - if(currentRange.isEmpty() || view->document()->text(currentRange) != declAtCursor->identifier().identifier().str()) { + if(currentRange.isEmpty() || doc->text(currentRange) != declAtCursor->identifier().identifier().str()) { return; // One of the uses is invalid. Maybe the replacement has already been performed. } } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } - d->m_newDeclarationName = view->document()->text(d->m_newDeclarationRange->range()); + d->m_newDeclarationName = doc->text(d->m_newDeclarationRange->range()); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action = new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } +KTextEditor::Range KDevelop::RenameAssistant::displayRange() const +{ + if ( !d->m_lastChangedDocument ) { + return {}; + } + auto range = d->m_lastChangedDocument->wordRangeAt(d->m_lastChangedLocation); + qDebug() << "range:" << range; + return range; +} + + #include "moc_renameassistant.cpp" diff --git a/language/assistant/renameassistant.h b/language/assistant/renameassistant.h index bcbe61c8b..f3fdf0580 100644 --- a/language/assistant/renameassistant.h +++ b/language/assistant/renameassistant.h @@ -1,55 +1,57 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KDEVPLATFORM_RENAMEEASSISTANT_H #define KDEVPLATFORM_RENAMEEASSISTANT_H #include #include #include #include #include "renameaction.h" namespace KTextEditor { class View; } namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT RenameAssistant : public StaticAssistant { Q_OBJECT public: explicit RenameAssistant(ILanguageSupport* supportedLanguage); ~RenameAssistant() override; - void textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText = QString()) override; + void textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, + const QString& removedText = QString()) override; bool isUseful() const override; + KTextEditor::Range displayRange() const override; QString title() const override; private: struct Private; QScopedPointer const d; }; } #endif // KDEVPLATFORM_RENAMEEASSISTANT_H diff --git a/language/assistant/staticassistant.h b/language/assistant/staticassistant.h index 9846604b6..0a8179436 100644 --- a/language/assistant/staticassistant.h +++ b/language/assistant/staticassistant.h @@ -1,94 +1,105 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef KDEVPLATFORM_STATICASSISTANT_H #define KDEVPLATFORM_STATICASSISTANT_H #include +#include +#include #include +#include + namespace KTextEditor { class Document; class View; class Range; } namespace KDevelop { class ILanguageSupport; /** * @brief This class serves as a base for long-living assistants * * Normally, an assistant only lives for a short time, and is indirectly owned by a KDevelop::Problem. * Static assistants are owned by the language support plugins and exist as long as the language plugin is loaded. * They are not created by KDevelop::Problem instances, instead, they check for problematic code for themselves, * by tracking document changes via the textChanged() method. * * Note that static assistants are not bound to a single document/view. * Instead the current document/view we're looking at is passed via the textChanged() method. * * Register instances of this class view StaticAssistantsManager::registerAssistant * * @sa textChanged() * @sa StaticAssistantsManager::registerAssistant */ class KDEVPLATFORMLANGUAGE_EXPORT StaticAssistant : public IAssistant { Q_OBJECT public: using Ptr = QExplicitlySharedDataPointer; explicit StaticAssistant(ILanguageSupport* supportedLanguage); ~StaticAssistant() override; /** * Language this static assistant supports * * textChanged() will only be called for documents with language equal to this assistant's language * @sa textChanged(0) */ ILanguageSupport* supportedLanguage() const; /** * Invoked whenever text inside a view was changed by the user * * Reimplement in subclass */ - virtual void textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, + virtual void textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText = QString()) = 0; /** * Whether it's worth showing this assistant to the user * * Reimplement in subclass */ virtual bool isUseful() const = 0; + /** + * The range the assistant should be displayed in. + */ + virtual KTextEditor::Range displayRange() const = 0; + + virtual void updateReady(const IndexedString&, const KDevelop::ReferencedTopDUContext&) { } + private: struct Private; QScopedPointer const d; }; } #endif // KDEVPLATFORM_STATICASSISTANT_H diff --git a/language/assistant/staticassistantsmanager.cpp b/language/assistant/staticassistantsmanager.cpp index 755789246..29ccdc111 100644 --- a/language/assistant/staticassistantsmanager.cpp +++ b/language/assistant/staticassistantsmanager.cpp @@ -1,321 +1,171 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 + 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 "staticassistantsmanager.h" +#include #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; struct StaticAssistantsManager::Private { Private(StaticAssistantsManager* qq) : q(qq) - { - connect(DUChain::self(), &DUChain::updateReady, - q, [this] (const IndexedString& url, const ReferencedTopDUContext& topContext) { - updateReady(url, topContext); - }); - } - - void eventuallyStartAssistant(); - void startAssistant(KDevelop::IAssistant::Ptr assistant); - void checkAssistantForProblems(KDevelop::TopDUContext* top); + { } + void updateReady(const IndexedString& document, const KDevelop::ReferencedTopDUContext& topContext); void documentLoaded(KDevelop::IDocument*); - void textInserted(Document* document, const Cursor& cursor, const QString& text); - void textRemoved(Document* document, const Range& cursor, const QString& removedText); - void updateReady(const IndexedString& document, const ReferencedTopDUContext& topContext); - void documentActivated(KDevelop::IDocument*); - void cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&); - void timeout(); + void textInserted(KTextEditor::Document* document, const Cursor& cursor, const QString& text); + void textRemoved(KTextEditor::Document* document, const Range& cursor, const QString& removedText); StaticAssistantsManager* q; - QPointer m_currentView; - KTextEditor::Cursor m_assistantStartedAt; - KDevelop::IndexedString m_currentDocument; - QExplicitlySharedDataPointer m_activeAssistant; - QList m_registeredAssistants; - bool m_activeProblemAssistant = false; - QTimer* m_timer; - - SafeDocumentPointer m_eventualDocument; - KTextEditor::Range m_eventualRange; - QString m_eventualRemovedText; - QMetaObject::Connection m_cursorPositionChangeConnection; + QVector m_registeredAssistants; }; StaticAssistantsManager::StaticAssistantsManager(QObject* parent) : QObject(parent) , d(new Private(this)) { - d->m_timer = new QTimer(this); - d->m_timer->setSingleShot(true); - d->m_timer->setInterval(400); - connect(d->m_timer, &QTimer::timeout, this, [&] { d->timeout(); }); - connect(KDevelop::ICore::self()->documentController(), &IDocumentController::documentLoaded, this, [&] (IDocument* document) { d->documentLoaded(document); }); - connect(KDevelop::ICore::self()->documentController(), - &IDocumentController::documentActivated, - this, [&] (IDocument* doc) { d->documentActivated(doc); }); - foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { d->documentLoaded(document); } } StaticAssistantsManager::~StaticAssistantsManager() { } -QExplicitlySharedDataPointer StaticAssistantsManager::activeAssistant() -{ - return d->m_activeAssistant; -} - void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) { if (d->m_registeredAssistants.contains(assistant)) return; d->m_registeredAssistants << assistant; } void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) { d->m_registeredAssistants.removeOne(assistant); } -QList StaticAssistantsManager::registeredAssistants() const +QVector StaticAssistantsManager::registeredAssistants() const { return d->m_registeredAssistants; } void StaticAssistantsManager::Private::documentLoaded(IDocument* document) { if (document->textDocument()) { - connect(document->textDocument(), - &Document::textInserted, q, - [&] (Document* document, const Cursor& cursor, const QString& text) { textInserted(document, cursor, text); }); - connect(document->textDocument(), - &Document::textRemoved, q, - [&] (Document* document, const Range& range, const QString& removedText) { textRemoved(document, range, removedText); }); + auto doc = document->textDocument(); + connect(doc, &KTextEditor::Document::textInserted, q, + [&] (KTextEditor::Document* doc, const Cursor& cursor, const QString& text) { + textInserted(doc, cursor, text); + }); + connect(doc, &KTextEditor::Document::textRemoved, q, + [&] (KTextEditor::Document* doc, const Range& range, const QString& removedText) { + textRemoved(doc, range, removedText); + }); } } -void StaticAssistantsManager::hideAssistant() +void StaticAssistantsManager::Private::textInserted(Document* doc, const Cursor& cursor, const QString& text) { - d->m_activeAssistant = QExplicitlySharedDataPointer(); - d->m_activeProblemAssistant = false; - emit activeAssistantChanged(); -} - -void StaticAssistantsManager::Private::textInserted(Document* document, const Cursor& cursor, const QString& text) -{ - m_eventualDocument = document; - m_eventualRange = Range(cursor, text.size()); - m_eventualRemovedText.clear(); - QMetaObject::invokeMethod(q, "eventuallyStartAssistant", Qt::QueuedConnection); + Q_FOREACH ( auto assistant, m_registeredAssistants ) { + auto range = Range(cursor, cursor+Cursor(0, text.size())); + assistant->textChanged(doc, range, {}); + } } -void StaticAssistantsManager::Private::textRemoved(Document* document, const Range& range, +void StaticAssistantsManager::Private::textRemoved(Document* doc, const Range& range, const QString& removedText) { - m_eventualDocument = document; - m_eventualRange = range; - m_eventualRemovedText = removedText; - QMetaObject::invokeMethod(q, "eventuallyStartAssistant", Qt::QueuedConnection); + Q_FOREACH ( auto assistant, m_registeredAssistants ) { + assistant->textChanged(doc, range, removedText); + } } -void StaticAssistantsManager::Private::eventuallyStartAssistant() +void StaticAssistantsManager::notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context) { - if (!m_eventualDocument) { - return; + Q_FOREACH ( auto assistant, d->m_registeredAssistants ) { + assistant->updateReady(url, context); } +} +QVector KDevelop::StaticAssistantsManager::problemsForContext(const KDevelop::ReferencedTopDUContext& top) +{ View* view = ICore::self()->documentController()->activeTextDocumentView(); - if (!view) { - return; - } - if (view->document() != m_eventualDocument) { - qWarning(LANGUAGE) << "Active view does not belong to document of last observed change!"; - return; + if (!view || !top || IndexedString(view->document()->url()) != top->url()) { + return {}; } - auto language = ICore::self()->languageController()->languagesForUrl(m_eventualDocument.data()->url()).value(0); + auto doc = top->url(); + auto language = ICore::self()->languageController()->languagesForUrl(doc.toUrl()).value(0); if (!language) { - return; + return {}; } + auto ret = QVector(); qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); - foreach (const auto& assistant, m_registeredAssistants) { + foreach (const auto& assistant, d->m_registeredAssistants) { if (assistant->supportedLanguage() != language) continue; - // notify assistant about editor changes - assistant->textChanged(view, m_eventualRange, m_eventualRemovedText); - if (assistant->isUseful()) { - startAssistant(IAssistant::Ptr(assistant.data())); - break; + qDebug() << "assistant is now useful:" << assistant.data(); + + auto p = new KDevelop::StaticAssistantProblem(); + auto range = assistant->displayRange(); + qDebug() << "range:" << range; + p->setFinalLocation(DocumentRange(doc, range)); + p->setSource(KDevelop::IProblem::SemanticAnalysis); + p->setSeverity(KDevelop::IProblem::Warning); + p->setDescription(assistant->title()); + p->setSolutionAssistant(IAssistant::Ptr(assistant.data())); + + ret.append(KDevelop::Problem::Ptr(p)); } } - - // optimize, esp. for setText() calls as done in e.g. reformat source - // only start the assitant once for multiple textRemoved/textInserted signals - m_eventualDocument.clear(); - m_eventualRange = Range::invalid(); - m_eventualRemovedText.clear(); + return ret; } -void StaticAssistantsManager::Private::startAssistant(IAssistant::Ptr assistant) -{ - if (assistant == m_activeAssistant) { - return; - } - - qCDebug(LANGUAGE()) << "Starting assistant:" << assistant->title(); - - if (m_activeAssistant) { - m_activeAssistant->doHide(); - } - - if (!m_currentView) - return; - - m_activeAssistant = assistant; - if (m_activeAssistant) { - connect(m_activeAssistant.data(), &IAssistant::hide, q, &StaticAssistantsManager::hideAssistant, Qt::UniqueConnection); - ICore::self()->uiController()->popUpAssistant(IAssistant::Ptr(m_activeAssistant.data())); - - m_assistantStartedAt = m_currentView.data()->cursorPosition(); - } - - emit q->activeAssistantChanged(); -} -void StaticAssistantsManager::Private::updateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) -{ - if (ICore::self()->shuttingDown()) { - return; - } - - if (url != m_currentDocument) { - return; - } - - if (m_activeAssistant) { - if (m_activeProblemAssistant) { - m_activeAssistant->doHide(); //Hide the assistant, as we will create a new one if the problem is still there - } else { - return; - } - } - - DUChainReadLocker lock(DUChain::lock(), 300); - if (!lock.locked()) { - return; - } - - if (topContext) { - checkAssistantForProblems(topContext); - } -} - -void StaticAssistantsManager::Private::cursorPositionChanged(View*, const Cursor& pos) -{ - if (m_activeAssistant && m_assistantStartedAt.isValid() - && abs(m_assistantStartedAt.line() - pos.line()) >= 1) - { - m_activeAssistant->doHide(); - } - - m_timer->start(); -} - -void StaticAssistantsManager::Private::documentActivated(IDocument* doc) -{ - if (doc) { - m_currentDocument = IndexedString(doc->url()); - } - - if (m_currentView) { - QObject::disconnect(m_cursorPositionChangeConnection); - m_currentView.clear(); - } - - m_currentView = ICore::self()->documentController()->activeTextDocumentView(); - - if (m_currentView) { - m_cursorPositionChangeConnection = connect(m_currentView.data(), - &View::cursorPositionChanged, q, - [&] (View* v, const Cursor& pos) { cursorPositionChanged(v, pos); }); - } -} - -void StaticAssistantsManager::Private::checkAssistantForProblems(TopDUContext* top) -{ - foreach (ProblemPointer problem, top->problems()) { - if (m_currentView && m_currentView.data()->cursorPosition().line() == problem->range().start.line) { - IAssistant::Ptr solution = problem->solutionAssistant(); - if(solution) { - startAssistant(solution); - m_activeProblemAssistant = true; - break; - } - } - } -} - -void StaticAssistantsManager::Private::timeout() -{ - if (!m_activeAssistant && m_currentView) { - DUChainReadLocker lock(DUChain::lock(), 300); - if (!lock.locked()) { - return; - } - - TopDUContext* top = DUChainUtils::standardContextForUrl(m_currentDocument.toUrl()); - if (top) { - checkAssistantForProblems(top); - } - } -} #include "moc_staticassistantsmanager.cpp" diff --git a/language/assistant/staticassistantsmanager.h b/language/assistant/staticassistantsmanager.h index 3862422d6..5212c9318 100644 --- a/language/assistant/staticassistantsmanager.h +++ b/language/assistant/staticassistantsmanager.h @@ -1,79 +1,76 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KDEVPLATFORM_STATICASSISTANTSMANAGER_H #define KDEVPLATFORM_STATICASSISTANTSMANAGER_H #include #include #include "staticassistant.h" #include +#include +#include +#include #include #include #include class QTimer; typedef QPointer SafeDocumentPointer; namespace KDevelop { class IDocument; class DUContext; class TopDUContext; /** * @brief Class managing instances of StaticAssistant * * Invokes the appropriate methods on registered StaticAssistant instances, such as StaticAssistant::textChanged * * @sa StaticAssistant::textChanged */ class KDEVPLATFORMLANGUAGE_EXPORT StaticAssistantsManager : public QObject { Q_OBJECT public: explicit StaticAssistantsManager(QObject* parent = nullptr); ~StaticAssistantsManager() override; - QExplicitlySharedDataPointer activeAssistant(); - void registerAssistant(const StaticAssistant::Ptr assistant); void unregisterAssistant(const StaticAssistant::Ptr assistant); - QList registeredAssistants() const; - -public slots: - void hideAssistant(); + QVector registeredAssistants() const; + void notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context); -signals: - void activeAssistantChanged(); + QVector problemsForContext(const ReferencedTopDUContext& top); private: struct Private; QScopedPointer const d; - Q_PRIVATE_SLOT(d, void eventuallyStartAssistant()) }; } #endif // KDEVPLATFORM_STATICASSISTANTSMANAGER_H diff --git a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp index 92c98dd1b..c9281cfcb 100644 --- a/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp +++ b/language/duchain/navigation/abstractdeclarationnavigationcontext.cpp @@ -1,764 +1,765 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "abstractdeclarationnavigationcontext.h" #include #include #include "../functiondeclaration.h" #include "../functiondefinition.h" #include "../classfunctiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../forwarddeclaration.h" #include "../types/enumeratortype.h" #include "../types/enumerationtype.h" #include "../types/functiontype.h" #include "../duchainutils.h" #include "../types/pointertype.h" #include "../types/referencetype.h" #include "../types/typeutils.h" #include "../types/typesystem.h" #include "../persistentsymboltable.h" #include "util/debug.h" #include #include #include #include #include namespace KDevelop { AbstractDeclarationNavigationContext::AbstractDeclarationNavigationContext( DeclarationPointer decl, TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : AbstractNavigationContext((topContext ? topContext : TopDUContextPointer(decl ? decl->topContext() : 0)), previousContext), m_declaration(decl), m_fullBackwardSearch(false) { //Jump from definition to declaration if possible FunctionDefinition* definition = dynamic_cast(m_declaration.data()); if(definition && definition->declaration()) m_declaration = DeclarationPointer(definition->declaration()); } QString AbstractDeclarationNavigationContext::name() const { if(m_declaration.data()) return prettyQualifiedIdentifier(m_declaration).toString(); else return declarationName(m_declaration); } QString AbstractDeclarationNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; modifyHtml() += "

" + fontSizePrefix(shorten); addExternalHtml(m_prefix); if(!m_declaration.data()) { modifyHtml() += i18n("
lost declaration
"); return currentHtml(); } if( m_previousContext ) { QString link = createLink( m_previousContext->name(), m_previousContext->name(), NavigationAction(m_previousContext) ); modifyHtml() += navigationHighlight(i18n("Back to %1
", link)); } QExplicitlySharedDataPointer doc; if( !shorten ) { doc = ICore::self()->documentationController()->documentationForDeclaration(m_declaration.data()); const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); if( function ) { htmlFunction(); } else if( m_declaration->isTypeAlias() || m_declaration->type() || m_declaration->kind() == Declaration::Instance ) { if( m_declaration->isTypeAlias() ) modifyHtml() += importantHighlight(QStringLiteral("typedef ")); if(m_declaration->type()) modifyHtml() += i18n("enumerator "); AbstractType::Ptr useType = m_declaration->abstractType(); if(m_declaration->isTypeAlias()) { //Do not show the own name as type of typedefs if(useType.cast()) useType = useType.cast()->type(); } eventuallyMakeTypeLinks( useType ); modifyHtml() += ' ' + identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); if(auto integralType = m_declaration->type()) { const QString plainValue = integralType->valueAsString(); if (!plainValue.isEmpty()) { modifyHtml() += QStringLiteral(" = ") + plainValue; } } modifyHtml() += QStringLiteral("
"); }else{ if( m_declaration->kind() == Declaration::Type && m_declaration->abstractType().cast() ) { htmlClass(); } if ( m_declaration->kind() == Declaration::Namespace ) { modifyHtml() += i18n("namespace %1 ", identifierHighlight(m_declaration->qualifiedIdentifier().toString().toHtmlEscaped(), m_declaration)); } else if ( m_declaration->kind() == Declaration::NamespaceAlias ) { modifyHtml() += identifierHighlight(declarationName(m_declaration).toHtmlEscaped(), m_declaration); } if(m_declaration->type()) { EnumerationType::Ptr enumeration = m_declaration->type(); modifyHtml() += i18n("enumeration %1 ", identifierHighlight(m_declaration->identifier().toString().toHtmlEscaped(), m_declaration)); } if(m_declaration->isForwardDeclaration()) { ForwardDeclaration* forwardDec = static_cast(m_declaration.data()); Declaration* resolved = forwardDec->resolve(m_topContext.data()); if(resolved) { modifyHtml() += i18n("(resolved forward-declaration: "); makeLink(resolved->identifier().toString(), DeclarationPointer(resolved), NavigationAction::NavigateDeclaration ); modifyHtml() += i18n(") "); }else{ modifyHtml() += i18n("(unresolved forward-declaration) "); QualifiedIdentifier id = forwardDec->qualifiedIdentifier(); const auto& forwardDecFile = forwardDec->topContext()->parsingEnvironmentFile(); uint count; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(id, count, decls); for(uint a = 0; a < count; ++a) { auto dec = decls[a].data(); if (!dec || dec->isForwardDeclaration()) { continue; } const auto& decFile = forwardDec->topContext()->parsingEnvironmentFile(); if ((static_cast(decFile) != static_cast(forwardDecFile)) || (decFile && forwardDecFile && decFile->language() != forwardDecFile->language())) { // the language of the declarations must match continue; } modifyHtml() += QStringLiteral("
"); makeLink(i18n("possible resolution from"), DeclarationPointer(dec), NavigationAction::NavigateDeclaration); modifyHtml() += ' ' + dec->url().str(); } } } modifyHtml() += QStringLiteral("
"); } }else{ AbstractType::Ptr showType = m_declaration->abstractType(); if(showType && showType.cast()) { showType = showType.cast()->returnType(); if(showType) modifyHtml() += labelHighlight(i18n("Returns: ")); }else if(showType) { modifyHtml() += labelHighlight(i18n("Type: ")); } if(showType) { eventuallyMakeTypeLinks(showType); modifyHtml() += QStringLiteral(" "); } } QualifiedIdentifier identifier = m_declaration->qualifiedIdentifier(); if( identifier.count() > 1 ) { if( m_declaration->context() && m_declaration->context()->owner() ) { Declaration* decl = m_declaration->context()->owner(); FunctionDefinition* definition = dynamic_cast(decl); if(definition && definition->declaration()) decl = definition->declaration(); if(decl->abstractType().cast()) modifyHtml() += labelHighlight(i18n("Enum: ")); else modifyHtml() += labelHighlight(i18n("Container: ")); makeLink( declarationName(DeclarationPointer(decl)), DeclarationPointer(decl), NavigationAction::NavigateDeclaration ); modifyHtml() += QStringLiteral(" "); } else { QualifiedIdentifier parent = identifier; parent.pop(); modifyHtml() += labelHighlight(i18n("Scope: %1 ", typeHighlight(parent.toString().toHtmlEscaped()))); } } if( shorten && !m_declaration->comment().isEmpty() ) { QString comment = QString::fromUtf8(m_declaration->comment()); if( comment.length() > 60 ) { comment.truncate(60); comment += QLatin1String("..."); } comment.replace('\n', QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); comment.replace(QLatin1String("
"), QLatin1String(" ")); modifyHtml() += commentHighlight(comment.toHtmlEscaped()) + " "; } QString access = stringFromAccess(m_declaration); if( !access.isEmpty() ) modifyHtml() += labelHighlight(i18n("Access: %1 ", propertyHighlight(access.toHtmlEscaped()))); ///@todo Enumerations QString detailsHtml; QStringList details = declarationDetails(m_declaration); if( !details.isEmpty() ) { bool first = true; foreach( const QString &str, details ) { if( !first ) detailsHtml += QLatin1String(", "); first = false; detailsHtml += propertyHighlight(str); } } QString kind = declarationKind(m_declaration); if( !kind.isEmpty() ) { if( !detailsHtml.isEmpty() ) modifyHtml() += labelHighlight(i18n("Kind: %1 %2 ", importantHighlight(kind.toHtmlEscaped()), detailsHtml)); else modifyHtml() += labelHighlight(i18n("Kind: %1 ", importantHighlight(kind.toHtmlEscaped()))); } if (m_declaration->isDeprecated()) { modifyHtml() += labelHighlight(i18n("Status: %1 ", propertyHighlight(i18n("Deprecated")))); } modifyHtml() += QStringLiteral("
"); if(!shorten) htmlAdditionalNavigation(); if( !shorten ) { if(dynamic_cast(m_declaration.data())) modifyHtml() += labelHighlight(i18n( "Def.: " )); else modifyHtml() += labelHighlight(i18n( "Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( m_declaration->url().toUrl().fileName() ).arg( m_declaration->rangeInCurrentRevision().start().line()+1 ), m_declaration, NavigationAction::JumpToSource ); modifyHtml() += QStringLiteral(" "); //modifyHtml() += "
"; if(!dynamic_cast(m_declaration.data())) { if( FunctionDefinition* definition = FunctionDefinition::definition(m_declaration.data()) ) { modifyHtml() += labelHighlight(i18n( " Def.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->url().toUrl().fileName() ).arg( definition->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition), NavigationAction::JumpToSource ); } } if( FunctionDefinition* definition = dynamic_cast(m_declaration.data()) ) { if(definition->declaration()) { modifyHtml() += labelHighlight(i18n( " Decl.: " )); makeLink( QStringLiteral("%1 :%2").arg( definition->declaration()->url().toUrl().fileName() ).arg( definition->declaration()->rangeInCurrentRevision().start().line()+1 ), DeclarationPointer(definition->declaration()), NavigationAction::JumpToSource ); } } modifyHtml() += QStringLiteral(" "); //The action name _must_ stay "show_uses", since that is also used from outside makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(m_declaration, NavigationAction::NavigateUses)); } QByteArray declarationComment = m_declaration->comment(); if( !shorten && (!declarationComment.isEmpty() || doc) ) { - modifyHtml() += QStringLiteral("
"); + modifyHtml() += QStringLiteral("

"); if(doc) { QString comment = doc->description(); connect(doc.data(), &IDocumentation::descriptionChanged, this, &AbstractDeclarationNavigationContext::contentsChanged); if(!comment.isEmpty()) { - modifyHtml() += "
" + commentHighlight(comment); + modifyHtml() += "

" + commentHighlight(comment) + "

"; } } QString comment = QString::fromUtf8(declarationComment); if(!comment.isEmpty()) { // if the first paragraph does not contain a tag, we assume that this is a plain-text comment if (!Qt::mightBeRichText(comment)) { // still might contain extra html tags for line breaks (this is the case for doxygen-style comments sometimes) // let's protect them from being removed completely comment.replace(QRegExp("
"), QStringLiteral("\n")); comment = comment.toHtmlEscaped(); comment.replace('\n', QLatin1String("
")); //Replicate newlines in html } modifyHtml() += commentHighlight(comment); - modifyHtml() += QStringLiteral("
"); + modifyHtml() += QStringLiteral("

"); } } if(!shorten && doc) { - modifyHtml() += "
" + i18n("Show documentation for "); + modifyHtml() += "

" + i18n("Show documentation for "); makeLink(prettyQualifiedName(m_declaration), m_declaration, NavigationAction::ShowDocumentation); + modifyHtml() += "

"; } //modifyHtml() += "
"; addExternalHtml(m_suffix); modifyHtml() += fontSizeSuffix(shorten) + "

"; return currentHtml(); } AbstractType::Ptr AbstractDeclarationNavigationContext::typeToShow(AbstractType::Ptr type) { return type; } void AbstractDeclarationNavigationContext::htmlFunction() { const AbstractFunctionDeclaration* function = dynamic_cast(m_declaration.data()); Q_ASSERT(function); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); const FunctionType::Ptr type = m_declaration->abstractType().cast(); if( !type ) { modifyHtml() += errorHighlight(QStringLiteral("Invalid type
")); return; } if( !classFunDecl || (!classFunDecl->isConstructor() && !classFunDecl->isDestructor()) ) { // only print return type for global functions and non-ctor/dtor methods eventuallyMakeTypeLinks( type->returnType() ); } modifyHtml() += ' ' + identifierHighlight(prettyIdentifier(m_declaration).toString().toHtmlEscaped(), m_declaration); if( type->indexedArgumentsSize() == 0 ) { modifyHtml() += QStringLiteral("()"); } else { modifyHtml() += QStringLiteral("( "); bool first = true; int firstDefaultParam = type->indexedArgumentsSize() - function->defaultParametersSize(); int currentArgNum = 0; QVector decls; if (DUContext* argumentContext = DUChainUtils::getArgumentContext(m_declaration.data())) { decls = argumentContext->localDeclarations(m_topContext.data()); } foreach(const AbstractType::Ptr& argType, type->arguments()) { if( !first ) modifyHtml() += QStringLiteral(", "); first = false; eventuallyMakeTypeLinks( argType ); if (currentArgNum < decls.size()) { modifyHtml() += ' ' + identifierHighlight(decls[currentArgNum]->identifier().toString().toHtmlEscaped(), m_declaration); } if( currentArgNum >= firstDefaultParam ) modifyHtml() += " = " + function->defaultParameters()[ currentArgNum - firstDefaultParam ].str().toHtmlEscaped(); ++currentArgNum; } modifyHtml() += QStringLiteral(" )"); } modifyHtml() += QStringLiteral("
"); } Identifier AbstractDeclarationNavigationContext::prettyIdentifier(DeclarationPointer decl) const { Identifier ret; QualifiedIdentifier q = prettyQualifiedIdentifier(decl); if(!q.isEmpty()) ret = q.last(); return ret; } QualifiedIdentifier AbstractDeclarationNavigationContext::prettyQualifiedIdentifier(DeclarationPointer decl) const { if(decl) return decl->qualifiedIdentifier(); else return QualifiedIdentifier(); } QString AbstractDeclarationNavigationContext::prettyQualifiedName(DeclarationPointer decl) const { const auto qid = prettyQualifiedIdentifier(decl); if (qid.isEmpty()) { return i18nc("An anonymous declaration (class, function, etc.)", ""); } return qid.toString(); } void AbstractDeclarationNavigationContext::htmlAdditionalNavigation() { ///Check if the function overrides or hides another one const ClassFunctionDeclaration* classFunDecl = dynamic_cast(m_declaration.data()); if(classFunDecl) { Declaration* overridden = DUChainUtils::getOverridden(m_declaration.data()); if(overridden) { modifyHtml() += i18n("Overrides a "); makeLink(i18n("function"), QStringLiteral("jump_to_overridden"), NavigationAction(DeclarationPointer(overridden), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(overridden->context()->owner())), QStringLiteral("jump_to_overridden_container"), NavigationAction(DeclarationPointer(overridden->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); }else{ //Check if this declarations hides other declarations QList decls; foreach(const DUContext::Import &import, m_declaration->context()->importedParentContexts()) if(import.context(m_topContext.data())) decls += import.context(m_topContext.data())->findDeclarations(QualifiedIdentifier(m_declaration->identifier()), CursorInRevision::invalid(), AbstractType::Ptr(), m_topContext.data(), DUContext::DontSearchInParent); uint num = 0; foreach(Declaration* decl, decls) { modifyHtml() += i18n("Hides a "); makeLink(i18n("function"), QStringLiteral("jump_to_hide_%1").arg(num), NavigationAction(DeclarationPointer(decl), NavigationAction::NavigateDeclaration)); modifyHtml() += i18n(" from "); makeLink(prettyQualifiedName(DeclarationPointer(decl->context()->owner())), QStringLiteral("jump_to_hide_container_%1").arg(num), NavigationAction(DeclarationPointer(decl->context()->owner()), NavigationAction::NavigateDeclaration)); modifyHtml() += QStringLiteral("
"); ++num; } } ///Show all places where this function is overridden if(classFunDecl->isVirtual()) { Declaration* classDecl = m_declaration->context()->owner(); if(classDecl) { uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList overriders = DUChainUtils::getOverriders(classDecl, classFunDecl, maxAllowedSteps); if(!overriders.isEmpty()) { modifyHtml() += i18n("Overridden in "); bool first = true; foreach(Declaration* overrider, overriders) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const auto owner = DeclarationPointer(overrider->context()->owner()); const QString name = prettyQualifiedName(owner); makeLink(name, name, NavigationAction(DeclarationPointer(overrider), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(overriders.isEmpty() ? i18n("Overriders possible, show all") : i18n("More overriders possible, show all")); } } } ///Show all classes that inherit this one uint maxAllowedSteps = m_fullBackwardSearch ? (uint)-1 : 10; QList inheriters = DUChainUtils::getInheriters(m_declaration.data(), maxAllowedSteps); if(!inheriters.isEmpty()) { modifyHtml() += i18n("Inherited by "); bool first = true; foreach(Declaration* importer, inheriters) { if(!first) modifyHtml() += QStringLiteral(", "); first = false; const QString importerName = prettyQualifiedName(DeclarationPointer(importer)); makeLink(importerName, importerName, NavigationAction(DeclarationPointer(importer), NavigationAction::NavigateDeclaration)); } modifyHtml() += QStringLiteral("
"); } if(maxAllowedSteps == 0) createFullBackwardSearchLink(inheriters.isEmpty() ? i18n("Inheriters possible, show all") : i18n("More inheriters possible, show all")); } void AbstractDeclarationNavigationContext::createFullBackwardSearchLink(QString string) { makeLink(string, QStringLiteral("m_fullBackwardSearch=true"), NavigationAction(QStringLiteral("m_fullBackwardSearch=true"))); modifyHtml() += QStringLiteral("
"); } NavigationContextPointer AbstractDeclarationNavigationContext::executeKeyAction( QString key ) { if(key == QLatin1String("m_fullBackwardSearch=true")) { m_fullBackwardSearch = true; clear(); } return NavigationContextPointer(this); } void AbstractDeclarationNavigationContext::htmlClass() { StructureType::Ptr klass = m_declaration->abstractType().cast(); Q_ASSERT(klass); ClassDeclaration* classDecl = dynamic_cast(klass->declaration(m_topContext.data())); if(classDecl) { switch ( classDecl->classType() ) { case ClassDeclarationData::Class: modifyHtml() += QStringLiteral("class "); break; case ClassDeclarationData::Struct: modifyHtml() += QStringLiteral("struct "); break; case ClassDeclarationData::Union: modifyHtml() += QStringLiteral("union "); break; case ClassDeclarationData::Interface: modifyHtml() += QStringLiteral("interface "); break; case ClassDeclarationData::Trait: modifyHtml() += QStringLiteral("trait "); break; default: modifyHtml() += QStringLiteral(" "); break; } eventuallyMakeTypeLinks( klass.cast() ); FOREACH_FUNCTION( const BaseClassInstance& base, classDecl->baseClasses ) { modifyHtml() += ", " + stringFromAccess(base.access) + " " + (base.virtualInheritance ? QStringLiteral("virtual") : QString()) + " "; eventuallyMakeTypeLinks(base.baseClass.abstractType()); } } else { /// @todo How can we get here? and should this really be a class? modifyHtml() += QStringLiteral("class "); eventuallyMakeTypeLinks( klass.cast() ); } modifyHtml() += QStringLiteral(" "); } void AbstractDeclarationNavigationContext::htmlIdentifiedType(AbstractType::Ptr type, const IdentifiedType* idType) { Q_ASSERT(type); Q_ASSERT(idType); if( Declaration* decl = idType->declaration(m_topContext.data()) ) { //Remove the last template-identifiers, because we create those directly QualifiedIdentifier id = prettyQualifiedIdentifier(DeclarationPointer(decl)); Identifier lastId = id.last(); id.pop(); lastId.clearTemplateIdentifiers(); id.push(lastId); if(decl->context() && decl->context()->owner()) { //Also create full type-links for the context around AbstractType::Ptr contextType = decl->context()->owner()->abstractType(); IdentifiedType* contextIdType = dynamic_cast(contextType.data()); if(contextIdType && !contextIdType->equals(idType)) { //Create full type information for the context if(!id.isEmpty()) id = id.mid(id.count()-1); htmlIdentifiedType(contextType, contextIdType); modifyHtml() += QStringLiteral("::").toHtmlEscaped(); } } //We leave out the * and & reference and pointer signs, those are added to the end makeLink(id.toString() , DeclarationPointer(idType->declaration(m_topContext.data())), NavigationAction::NavigateDeclaration ); } else { qCDebug(LANGUAGE) << "could not resolve declaration:" << idType->declarationId().isDirect() << idType->qualifiedIdentifier().toString() << "in top-context" << m_topContext->url().str(); modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } void AbstractDeclarationNavigationContext::eventuallyMakeTypeLinks( AbstractType::Ptr type ) { type = typeToShow(type); if( !type ) { modifyHtml() += typeHighlight(QStringLiteral("").toHtmlEscaped()); return; } AbstractType::Ptr target = TypeUtils::targetTypeKeepAliases( type, m_topContext.data() ); const IdentifiedType* idType = dynamic_cast( target.data() ); qCDebug(LANGUAGE) << "making type-links for" << type->toString(); if( idType && idType->declaration(m_topContext.data()) ) { ///@todo This is C++ specific, move into subclass if(target->modifiers() & AbstractType::ConstModifier) modifyHtml() += typeHighlight(QStringLiteral("const ")); htmlIdentifiedType(target, idType); //We need to exchange the target type, else template-parameters may confuse this SimpleTypeExchanger exchangeTarget(target, AbstractType::Ptr()); AbstractType::Ptr exchanged = exchangeTarget.exchange(type); if(exchanged) { QString typeSuffixString = exchanged->toString(); QRegExp suffixExp("\\&|\\*"); int suffixPos = typeSuffixString.indexOf(suffixExp); if(suffixPos != -1) modifyHtml() += typeHighlight(typeSuffixString.mid(suffixPos)); } } else { if(idType) { qCDebug(LANGUAGE) << "identified type could not be resolved:" << idType->qualifiedIdentifier() << idType->declarationId().isValid() << idType->declarationId().isDirect(); } modifyHtml() += typeHighlight(type->toString().toHtmlEscaped()); } } DeclarationPointer AbstractDeclarationNavigationContext::declaration() const { return m_declaration; } QString AbstractDeclarationNavigationContext::identifierHighlight(const QString& identifier, const DeclarationPointer& decl) const { QString ret = nameHighlight(identifier); if (!decl) { return ret; } if (decl->isDeprecated()) { ret = QStringLiteral("") + ret + QStringLiteral(""); } return ret; } QString AbstractDeclarationNavigationContext::stringFromAccess(Declaration::AccessPolicy access) { switch(access) { case Declaration::Private: return QStringLiteral("private"); case Declaration::Protected: return QStringLiteral("protected"); case Declaration::Public: return QStringLiteral("public"); default: break; } return QString(); } QString AbstractDeclarationNavigationContext::stringFromAccess(DeclarationPointer decl) { const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { return stringFromAccess(memberDecl->accessPolicy()); } return QString(); } QString AbstractDeclarationNavigationContext::declarationName( DeclarationPointer decl ) const { if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) return "using namespace " + alias->importIdentifier().toString(); else return "namespace " + alias->identifier().toString() + " = " + alias->importIdentifier().toString(); } if( !decl ) return i18nc("A declaration that is unknown", "Unknown"); else return prettyIdentifier(decl).toString(); } QStringList AbstractDeclarationNavigationContext::declarationDetails(DeclarationPointer decl) { QStringList details; const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); const ClassMemberDeclaration* memberDecl = dynamic_cast(decl.data()); if( memberDecl ) { if( memberDecl->isMutable() ) details << QStringLiteral("mutable"); if( memberDecl->isRegister() ) details << QStringLiteral("register"); if( memberDecl->isStatic() ) details << QStringLiteral("static"); if( memberDecl->isAuto() ) details << QStringLiteral("auto"); if( memberDecl->isExtern() ) details << QStringLiteral("extern"); if( memberDecl->isFriend() ) details << QStringLiteral("friend"); } if( decl->isDefinition() ) details << i18nc("tells if a declaration is defining the variable's value", "definition"); if( decl->isExplicitlyDeleted() ) details << QStringLiteral("deleted"); if( memberDecl && memberDecl->isForwardDeclaration() ) details << i18nc("as in c++ forward declaration", "forward"); AbstractType::Ptr t(decl->abstractType()); if( t ) { if( t->modifiers() & AbstractType::ConstModifier ) details << i18nc("a variable that won't change, const", "constant"); if( t->modifiers() & AbstractType::VolatileModifier ) details << QStringLiteral("volatile"); } if( function ) { if( function->isInline() ) details << QStringLiteral("inline"); if( function->isExplicit() ) details << QStringLiteral("explicit"); if( function->isVirtual() ) details << QStringLiteral("virtual"); const ClassFunctionDeclaration* classFunDecl = dynamic_cast(decl.data()); if( classFunDecl ) { if( classFunDecl->isSignal() ) details << QStringLiteral("signal"); if( classFunDecl->isSlot() ) details << QStringLiteral("slot"); if( classFunDecl->isConstructor() ) details << QStringLiteral("constructor"); if( classFunDecl->isDestructor() ) details << QStringLiteral("destructor"); if( classFunDecl->isConversionFunction() ) details << QStringLiteral("conversion-function"); if( classFunDecl->isAbstract() ) details << QStringLiteral("abstract"); } } return details; } } diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 87baf1769..0300f66fd 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,493 +1,499 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "abstractnavigationcontext.h" #include #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include "util/debug.h" #include #include #include namespace KDevelop { void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { m_topContext = context; } KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { return m_topContext; } AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), m_previousContext(previousContext), m_topContext(topContext) { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, QString, const NavigationAction& action) { if(m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QString("link_%1").arg(m_links.count()); m_links[ hrefId ] = action; m_intLinks[ m_linkCount ] = action; m_linkLines[ m_linkCount ] = m_currentLine; if(m_currentPositionLine == m_currentLine) { m_currentPositionLine = -1; m_selectedLink = m_linkCount; } QString str = name.toHtmlEscaped(); if( m_linkCount == m_selectedLink ) str = "" + str + ""; QString ret = "" + str + ""; if( m_selectedLink == m_linkCount ) m_selectedLinkAction = action; ++m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { m_linkCount = 0; m_currentLine = 0; m_currentText.clear(); m_links.clear(); m_intLinks.clear(); m_linkLines.clear(); } NavigationContextPointer AbstractNavigationContext::executeLink (QString link) { if(!m_links.contains(link)) return NavigationContextPointer(this); return execute(m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer( m_previousContext ); return AbstractNavigationContext::registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } case NavigationAction::ShowUses: return registerChild(new UsesNavigationContext(action.decl.data(), this)); case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { m_children << NavigationContextPointer(context); return m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; m_currentPositionLine = fromLine + lineJump; if(m_currentPositionLine > m_currentLine) m_currentPositionLine = m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = m_currentLine; m_currentPositionLine = fromLine - lineJump; if(m_currentPositionLine < 0) m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) m_selectedLink = (m_selectedLink+1) % m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) { --m_selectedLink; if( m_selectedLink < 0 ) m_selectedLink += m_linkCount; } Q_ASSERT(m_selectedLink >= 0); } +int AbstractNavigationContext::linkCount() const +{ + return m_linkCount; +} + void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { m_prefix = prefix; m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(m_previousContext) return NavigationContextPointer(m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) { NavigationAction action = m_intLinks[m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return 0; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++m_currentLine; if(m_currentLine == m_currentPositionLine) { m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { + return m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/navigation/abstractnavigationcontext.h b/language/duchain/navigation/abstractnavigationcontext.h index ce581c5d7..337ed6bae 100644 --- a/language/duchain/navigation/abstractnavigationcontext.h +++ b/language/duchain/navigation/abstractnavigationcontext.h @@ -1,191 +1,194 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONCONTEXT_H #include #include #include "../indexeddeclaration.h" #include "navigationaction.h" namespace KDevelop { /** A helper-class for elegant colorization of html-strings . * * Initialize it with a html-color like "990000". and colorize strings * using operator() */ struct KDEVPLATFORMLANGUAGE_EXPORT Colorizer { enum FormattingFlag { Nothing = 0x0, Bold = 0x1, Italic = 0x2, Fixed = 0x4 }; Q_DECLARE_FLAGS(Formatting, FormattingFlag) Colorizer(const QString& color, Formatting formatting = Nothing) : m_color(color), m_formatting(formatting) { } QString operator()(const QString& str) const; QString m_color; Formatting m_formatting; }; class AbstractNavigationContext; typedef QExplicitlySharedDataPointer NavigationContextPointer; class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationContext : public QObject, public QSharedData { Q_OBJECT public: explicit AbstractNavigationContext( KDevelop::TopDUContextPointer topContext = KDevelop::TopDUContextPointer(), AbstractNavigationContext* previousContext = nullptr ); ~AbstractNavigationContext() override { } void nextLink(); void previousLink(); + + int linkCount() const; + void up(); void down(); void setPrefixSuffix( const QString& prefix, const QString& suffix ); NavigationContextPointer accept(); NavigationContextPointer back(); NavigationContextPointer accept(IndexedDeclaration decl); NavigationContextPointer acceptLink(const QString& link); NavigationAction currentAction() const; virtual QString name() const = 0; ///Here the context can return html to be displayed. virtual QString html(bool shorten = false); ///Here the context can return a widget to be displayed. ///The widget stays owned by this navigation-context. ///The widget may have a signal "navigateDeclaration(KDevelop::IndexedDeclaration)". ///If that signal is emitted, the new declaration is navigated in the navigation-wdiget. virtual QWidget* widget() const; ///Whether the widget returned by widget() should take the maximum possible spsace. ///The default implementation returns true. virtual bool isWidgetMaximized() const; ///Returns whether this context's string has already been computed, and is up to date. ///After clear() was called, this returns false again. bool alreadyComputed() const; void setTopContext(TopDUContextPointer context); TopDUContextPointer topContext() const; NavigationContextPointer executeLink(QString link); NavigationContextPointer execute(const NavigationAction& action); Q_SIGNALS: void contentsChanged(); protected: /// Returns the html font-size prefix (aka. or similar) for the given mode QString fontSizePrefix(bool shorten) const; /// Returns the html font-size suffix (aka. or similar) for the given mode QString fontSizeSuffix(bool shorten) const; virtual void setPreviousContext(AbstractNavigationContext* previous); struct TextHandler { TextHandler(AbstractNavigationContext* c) : context(c) { } void operator+=(const QString& str) const { context->addHtml(str); } AbstractNavigationContext* context; }; ///Override this to execute own key-actions using NavigationAction virtual NavigationContextPointer executeKeyAction(QString key); ///Adds given the text to currentHtml() void addHtml(QString html); ///Returns the html text being built in its current state QString currentHtml() const; ///Returns a convenience object that allows writing "modifyHtml() += "Hallo";" TextHandler modifyHtml() { return TextHandler(this); } //Clears the computed html and links void clear(); void addExternalHtml( const QString& text ); ///Creates and registers a link to the given declaration, labeled by the given name virtual void makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ); ///Creates a link that executes the given action and adds it to the current context void makeLink( const QString& name, QString targetId, const NavigationAction& action); ///Creates a link that executes the given action and returns it QString createLink(const QString& name, QString targetId, const NavigationAction& action); int m_selectedLink; //The link currently selected NavigationAction m_selectedLinkAction; //Target of the currently selected link NavigationContextPointer registerChild(DeclarationPointer /*declaration*/); NavigationContextPointer registerChild( AbstractNavigationContext* context ); QList m_children; //Useed to keep alive all children until this is deleted bool m_shorten; int m_currentLine; //A counter used while building the html-code to count the used links. int m_linkCount; //Something else than -1 if the current position is represented by a line-number, not a link. int m_currentPositionLine; QMap m_links; QMap m_linkLines; //Holds the line for each link QMap m_intLinks; AbstractNavigationContext* m_previousContext; QString m_prefix, m_suffix; KDevelop::TopDUContextPointer m_topContext; virtual QString declarationKind(DeclarationPointer decl); static const Colorizer typeHighlight; static const Colorizer errorHighlight; static const Colorizer labelHighlight; static const Colorizer codeHighlight; static const Colorizer propertyHighlight; static const Colorizer navigationHighlight; static const Colorizer importantHighlight; static const Colorizer commentHighlight; static const Colorizer nameHighlight; private: QString m_currentText; //Here the text is built }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::Colorizer::Formatting); #endif diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index bb6d01dcc..c1eaeb43c 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,291 +1,301 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "abstractnavigationwidget.h" #include #include #include #include #include #include #include "../declaration.h" #include "../ducontext.h" #include "../duchain.h" #include "../duchainlock.h" #include "util/debug.h" namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(0), m_currentWidget(0) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 580; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); if(m_idealTextSize.height()>=300) { //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); m_browser->setPalette( p ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(m_browser == 0) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setTextWidth(maxNavigationWidgetWidth); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html = m_context->html(); if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); + // TODO: Only show that the first time, or the first few times this context is shown? + html += QStringLiteral("

"); + if (m_context->linkCount() > 0) { + html += i18n("(Hold 'Alt' to show. Navigate via arrow keys, activate by pressing 'Enter')"); + } else { + html += i18n("(Hold 'Alt' to show this tooltip)"); + } + html += QStringLiteral("

"); + m_browser->setHtml( html ); m_currentText = html; + m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); m_browser->scrollToAnchor(QStringLiteral("currentPosition")); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); m_currentWidget->setParent(0); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { DUChainReadLocker lock( DUChain::lock() ); setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { DUChainReadLocker lock( DUChain::lock() ); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { DUChainReadLocker lock( DUChain::lock() ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { DUChainReadLocker lock( DUChain::lock() ); m_context->up(); update(); } void AbstractNavigationWidget::down() { DUChainReadLocker lock( DUChain::lock() ); m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index a292f722e..6a0d306d7 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,128 +1,222 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "problemnavigationcontext.h" +#include +#include + +#include "util/debug.h" #include #include +#include +#include #include #include #include #include #include -#include #include +#include + using namespace KDevelop; -ProblemNavigationContext::ProblemNavigationContext(const IProblem::Ptr& problem) +namespace { + +QString KEY_INVOKE_ACTION(int num) { return QString("invoke_action_%1").arg(num); } + +QString iconForSeverity(IProblem::Severity severity) +{ + switch (severity) { + case IProblem::Hint: + return QStringLiteral("dialog-information"); + case IProblem::Warning: + return QStringLiteral("dialog-warning"); + case IProblem::Error: + return QStringLiteral("dialog-error"); + } + return {}; +} + +QString htmlImg(const QString& iconName, KIconLoader::Group group) +{ + KIconLoader loader; + const int size = loader.currentSize(group); + return QString::fromLatin1("") + .arg(size) + .arg(loader.iconPath(iconName, group)); +} + +} + +ProblemNavigationContext::ProblemNavigationContext(const IProblem::Ptr& problem, const Flags flags) : m_problem(problem) + , m_flags(flags) , m_widget(nullptr) { - QExplicitlySharedDataPointer< IAssistant > solution = problem->solutionAssistant(); - if(solution && !solution->actions().isEmpty()) { - m_widget = new QWidget; - QHBoxLayout* layout = new QHBoxLayout(m_widget); - RichTextPushButton* button = new RichTextPushButton; -// button->setPopupMode(QToolButton::InstantPopup); - if(!solution->title().isEmpty()) - button->setHtml(i18n("Solve: %1", solution->title())); - else - button->setHtml(i18n("Solve")); - - QMenu* menu = new QMenu; - menu->setFocusPolicy(Qt::NoFocus); - foreach(IAssistantAction::Ptr action, solution->actions()) { - menu->addAction(action->toKAction(this)); - } - button->setMenu(menu); - - layout->addWidget(button); - layout->setAlignment(button, Qt::AlignLeft); - m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - } } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } - QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; - modifyHtml() += QStringLiteral("

"); + auto iconPath = iconForSeverity(m_problem->severity()); - modifyHtml() += i18n("Problem in %1:
", m_problem->sourceString()); - modifyHtml() += m_problem->description().toHtmlEscaped(); + modifyHtml() += QStringLiteral(""); + + modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); + + // BEGIN: right column + modifyHtml() += QStringLiteral(""); + // END: right column + + modifyHtml() += QStringLiteral("
%1"); + + modifyHtml() += i18n("Problem in %1", m_problem->sourceString()); modifyHtml() += QStringLiteral("
"); - modifyHtml() += "" + m_problem->explanation().toHtmlEscaped() + ""; + + if (m_flags & ShowLocation) { + const auto duchainProblem = dynamic_cast(m_problem.data()); + if (duchainProblem) { + modifyHtml() += labelHighlight(i18n("Location: ")); + makeLink(QStringLiteral("%1 :%2") + .arg(duchainProblem->finalLocation().document.toUrl().fileName()) + .arg(duchainProblem->rangeInCurrentRevision().start().line() + 1), + QString(), + NavigationAction(duchainProblem->finalLocation().document.toUrl(), duchainProblem->finalLocation().start()) + ); + modifyHtml() += QStringLiteral("
"); + } + } + + modifyHtml() += m_problem->description().toHtmlEscaped(); + if ( !m_problem->explanation().isEmpty() ) { + modifyHtml() += "

" + m_problem->explanation().toHtmlEscaped() + "

"; + } + + modifyHtml() += QStringLiteral("
"); const QVector diagnostics = m_problem->diagnostics(); if (!diagnostics.isEmpty()) { - modifyHtml() += QStringLiteral("
"); DUChainReadLocker lock; for (auto diagnostic : diagnostics) { + modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()); if (declaration) { - modifyHtml() += i18n("
See: "); + modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } else if (range.start().isValid()) { - modifyHtml() += i18n("
See: "); + modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") .arg(url.fileName()) .arg(range.start().line() + 1), url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } - modifyHtml() += QStringLiteral("
"); + modifyHtml() += QStringLiteral("

"); } } - modifyHtml() += QStringLiteral("

"); + if (!m_cachedAssistant) { + m_cachedAssistant = m_problem->solutionAssistant(); + } + auto assistant = m_cachedAssistant; + if (assistant && !assistant->actions().isEmpty()) { + modifyHtml() += QString::fromLatin1("").arg("#b3d4ff"); + + modifyHtml() += QStringLiteral(""; + modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); + int index = 0; + foreach (auto assistantAction, assistant->actions()) { + if (index != 0) { + modifyHtml() += "
"; + } + makeLink(i18n("Solution (%1)", index + 1), KEY_INVOKE_ACTION(index), + NavigationAction(KEY_INVOKE_ACTION(index))); + modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); + ++index; + } + modifyHtml() += "
"); + } + return currentHtml(); } + +NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) +{ + auto assistant = m_cachedAssistant; + if (!assistant) + return {}; + if (key.startsWith(QLatin1String("invoke_action_"))) { + const auto index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); + executeAction(index); + } + + return {}; +} + +void ProblemNavigationContext::executeAction(int index) +{ + auto assistant = m_problem->solutionAssistant(); + if (!assistant) + return; + + auto action = assistant->actions().value(index); + if (action) { + action->execute(); + if ( topContext() ) { + DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); + } + } else { + qCWarning(LANGUAGE()) << "No such action"; + return; + } +} diff --git a/language/duchain/navigation/problemnavigationcontext.h b/language/duchain/navigation/problemnavigationcontext.h index 02bac5fb9..35c3318a0 100644 --- a/language/duchain/navigation/problemnavigationcontext.h +++ b/language/duchain/navigation/problemnavigationcontext.h @@ -1,49 +1,64 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #define KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H #include +#include + #include #include #include namespace KDevelop { class KDEVPLATFORMLANGUAGE_EXPORT ProblemNavigationContext : public AbstractNavigationContext { Q_OBJECT public: - explicit ProblemNavigationContext(const IProblem::Ptr& problem); + enum Flag { + NoFlag = 0, + ShowLocation = 1 << 0, + }; + Q_DECLARE_FLAGS(Flags, Flag) + + explicit ProblemNavigationContext(const IProblem::Ptr& problem, const Flags flags = {}); ~ProblemNavigationContext() override; QString name() const override; QString html(bool shorten = false) override; QWidget* widget() const override; bool isWidgetMaximized() const override; + NavigationContextPointer executeKeyAction(QString key) override; + +public slots: + void executeAction(int index); // TODO: Add API in base class? + private: IProblem::Ptr m_problem; + Flags m_flags; QPointer m_widget; + IAssistant::Ptr m_cachedAssistant; // cache assistant, calling IAssistant::solutionAssistant() might be expensive }; } #endif // KDEVPLATFORM_PROBLEMNAVIGATIONCONTEXT_H diff --git a/language/duchain/problem.h b/language/duchain/problem.h index 3bb556766..ae58876b4 100644 --- a/language/duchain/problem.h +++ b/language/duchain/problem.h @@ -1,247 +1,261 @@ /* This file is part of KDevelop Copyright 2007 Hamish Rodda 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. */ #ifndef KDEVPLATFORM_PROBLEM_H #define KDEVPLATFORM_PROBLEM_H #include #include #include "../editor/documentrange.h" #include #include "duchainbase.h" #include #include "indexedtopducontext.h" #include +#include namespace KDevelop { class IAssistant; class Problem; using ProblemPointer = QExplicitlySharedDataPointer; /** * Represents a problem only by its index within the top-context * * Fixme: share code with the other LocalIndexed* classes */ class KDEVPLATFORMLANGUAGE_EXPORT LocalIndexedProblem { public: LocalIndexedProblem(const ProblemPointer& problem, const TopDUContext* top); explicit LocalIndexedProblem(uint index = 0) : m_index(index) {} /** * \note Duchain must be read locked */ ProblemPointer data(const TopDUContext* top) const; bool operator==(const LocalIndexedProblem& rhs) const { return m_index == rhs.m_index; } bool isValid() const { return m_index; } /** * Index of the Declaration within the top context */ uint localIndex() const { return m_index; } private: uint m_index; }; KDEVPLATFORMLANGUAGE_EXPORT DECLARE_LIST_MEMBER_HASH(ProblemData, diagnostics, LocalIndexedProblem) class KDEVPLATFORMLANGUAGE_EXPORT ProblemData : public DUChainBaseData { public: ProblemData() : source(IProblem::Unknown) , severity(IProblem::Error) { initializeAppendedLists(); } ProblemData(const ProblemData& rhs) : DUChainBaseData(rhs) , source(rhs.source) , severity(rhs.severity) , url(rhs.url) , description(rhs.description) , explanation(rhs.explanation) { initializeAppendedLists(); copyListsFrom(rhs); } ~ProblemData() { freeAppendedLists(); } IProblem::Source source; IProblem::Severity severity; IndexedString url; IndexedString description; IndexedString explanation; START_APPENDED_LISTS_BASE(ProblemData, DUChainBaseData); APPENDED_LIST_FIRST(ProblemData, LocalIndexedProblem, diagnostics); END_APPENDED_LISTS(ProblemData, diagnostics); }; /** * An object representing a problem in preprocessing, parsing, definition-use chain compilation, etc. * * You should always use ProblemPointer, because Problem may be subclassed. * The subclass would be lost while copying. * * @warning Access to problems must be serialized through DUChainLock. */ class KDEVPLATFORMLANGUAGE_EXPORT Problem : public DUChainBase, public IProblem { public: using Ptr = QExplicitlySharedDataPointer; Problem(); explicit Problem(ProblemData& data); ~Problem(); Source source() const override; void setSource(IProblem::Source source) override; /** * Returns a string version of the problem source */ QString sourceString() const override; TopDUContext* topContext() const override; KDevelop::IndexedString url() const override; /** * Location where this problem occurred * @warning Must only be called from the foreground * */ DocumentRange finalLocation() const override; void setFinalLocation(const DocumentRange& location) override; /** * Returns child diagnostics of this particular problem * * Example: * @code * void foo(unsigned int); * void foo(const char*); * int main() { foo(0); } * @endcode * * => foo(0) is ambigous. This will give us a ProblemPointer pointing to 'foo(0)'. * * Additionally, @p diagnostics may return the two locations to the ambiguous overloads, * with descriptions such as 'test.cpp:1: candidate : ...' */ void clearDiagnostics() override; QVector diagnostics() const override; void setDiagnostics(const QVector &diagnostics) override; void addDiagnostic(const IProblem::Ptr &diagnostic) override; /** * A brief description of the problem. */ QString description() const override; void setDescription(const QString& description) override; /** * A (detailed) explanation of why the problem occurred. */ QString explanation() const override; void setExplanation(const QString& explanation) override; /** * Get the severity of this problem. * This is used for example to decide for a highlighting color. * * @see setSeverity() */ Severity severity() const override; /** * Set the severity of this problem. */ void setSeverity(Severity severity) override; /** * Returns a string representation of the severity. */ QString severityString() const override; /** * If this problem can be solved, this may return an assistant for the solution. */ virtual QExplicitlySharedDataPointer solutionAssistant() const override; enum { Identity = 15 }; /** * Returns a string representation of this problem, useful for debugging. */ virtual QString toString() const; private: void rebuildDynamicData(DUContext* parent, uint ownIndex) override; Q_DISABLE_COPY(Problem) DUCHAIN_DECLARE_DATA(Problem) friend class TopDUContext; friend class TopDUContextDynamicData; friend class LocalIndexedProblem; //BEGIN dynamic data TopDUContextPointer m_topContext; mutable QList m_diagnostics; uint m_indexInTopContext; //END dynamic data }; +class KDEVPLATFORMLANGUAGE_EXPORT StaticAssistantProblem : public KDevelop::Problem { + public: + KDevelop::IAssistant::Ptr solutionAssistant() const override { + return m_solution; + } + void setSolutionAssistant(KDevelop::IAssistant::Ptr p) { + m_solution = p; + } + + private: + KDevelop::IAssistant::Ptr m_solution; +}; + } Q_DECLARE_TYPEINFO(KDevelop::LocalIndexedProblem, Q_MOVABLE_TYPE); KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::Problem& problem); KDEVPLATFORMLANGUAGE_EXPORT QDebug operator<<(QDebug s, const KDevelop::ProblemPointer& problem); #endif // KDEVPLATFORM_PROBLEM_H diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index bd615f0a0..7cd783172 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,331 +1,341 @@ /* * This file is part of KDevelop * * Copyright 2008 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 "browsemanager.h" #include #include #include #include #include #include #include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { avoidMenuAltFocus(); if(m_browsingByKey == Qt::Key_Alt && m_browsingStartedInView) emit startDelayedBrowsing(m_browsingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller) , m_plugin(controller) , m_browsing(false) , m_browsingByKey(0) , m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return 0; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } BrowseManager::JumpLocation BrowseManager::determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const { // @todo find out why this is needed, fix the code in kate if (textCursor.column() > 0) { textCursor.setColumn(textCursor.column() - 1); } // Step 1: Look for a special language object(Macro, included header, etc.) foreach (const auto& language, ICore::self()->languageController()->languagesForUrl(viewUrl)) { auto jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, textCursor); if (jumpTo.first.isValid() && jumpTo.second.isValid()) { return {jumpTo}; } } // Step 2: Look for a declaration/use DUChainReadLocker lock; // Jump to definition by default, unless a definition itself was selected, // in which case jump to declaration. if (auto selectedDeclaration = DUChainUtils::itemUnderCursor(viewUrl, textCursor)) { auto jumpDestination = selectedDeclaration; if (selectedDeclaration->isDefinition()) { // A definition was clicked directly - jump to declaration instead. if (auto declaration = DUChainUtils::declarationForDefinition(selectedDeclaration)) { jumpDestination = declaration; } } else if (selectedDeclaration == DUChainUtils::declarationForDefinition(selectedDeclaration)) { // Clicked the declaration - jump to definition if (auto definition = FunctionDefinition::definition(selectedDeclaration)) { jumpDestination = definition; } } return {{jumpDestination->url().toUrl(), jumpDestination->rangeInCurrentRevision().start()}}; } return {}; } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; KTextEditor::View* view = viewFromWidget(widget); //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_delayedBrowsingTimer->start(300); // always start the timer, to get consistent behavior regarding the ALT key and the menu activation m_browsingByKey = keyEvent->key(); if(!view) { return false; } if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Completion is active. avoidMenuAltFocus(); }else{ m_browsingStartedInView = view; } } } + if (keyEvent && m_browsingByKey && m_browsingStartedInView && keyEvent->type() == QEvent::KeyPress) { + if (keyEvent->key() >= Qt::Key_1 && keyEvent->key() <= Qt::Key_9) { + // user wants to trigger an action in the code browser + const int index = keyEvent->key() - Qt::Key_1; + emit invokeAction(index); + stopDelayedBrowsing(); + return true; + } + } + if(!view) { return false; } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus()) || event->type() == QEvent::WindowDeactivate) { m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = view->coordinatesToCursor(coordinatesInView); if (textCursor.isValid()) { JumpLocation jumpTo = determineJumpLoc(textCursor, view->document()->url()); if (jumpTo.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.url, jumpTo.cursor); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/contextbrowser/browsemanager.h b/plugins/contextbrowser/browsemanager.h index e7594a950..2ccf31312 100644 --- a/plugins/contextbrowser/browsemanager.h +++ b/plugins/contextbrowser/browsemanager.h @@ -1,125 +1,126 @@ /* * This file is part of KDevelop * * Copyright 2008 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_BROWSEMANAGER_H #define KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #include #include #include #include #include #include #include class QWidget; namespace KTextEditor { class View; class Document; } namespace KDevelop { class IDocument; } class EditorViewWatcher : public QObject { Q_OBJECT public: ///@param sameWindow If this is true, only views that are child of the same window as the given widget are registered explicit EditorViewWatcher(QObject* parent = nullptr); QList allViews(); private: ///Called for every added view. Reimplement this to catch them. virtual void viewAdded(KTextEditor::View*); private slots: void viewDestroyed(QObject* view); void viewCreated(KTextEditor::Document*, KTextEditor::View*); void documentCreated( KDevelop::IDocument* document ); private: void addViewInternal(KTextEditor::View* view); QList m_views; }; class ContextBrowserPlugin; class BrowseManager; class Watcher : public EditorViewWatcher { Q_OBJECT public: explicit Watcher(BrowseManager* manager); void viewAdded(KTextEditor::View*) override; private: BrowseManager* m_manager; }; /** * Integrates the context-browser with the editor views, by listening for navigation events, and implementing html-like source browsing */ class BrowseManager : public QObject { Q_OBJECT public: explicit BrowseManager(ContextBrowserPlugin* controller); void viewAdded(KTextEditor::View* view); //Installs/uninstalls the event-filter void applyEventFilter(QWidget* object, bool install); Q_SIGNALS: //Emitted when browsing was started using the magic-modifier void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); + void invokeAction(int index); public slots: ///Enabled/disables the browsing mode void setBrowsing(bool); private slots: void eventuallyStartDelayedBrowsing(); private: struct JumpLocation { QUrl url; KTextEditor::Cursor cursor; JumpLocation(const QPair& pair = {}) : url(pair.first) , cursor(pair.second) {} bool isValid() const { return url.isValid() && cursor.isValid(); } }; void resetChangedCursor(); JumpLocation determineJumpLoc(KTextEditor::Cursor textCursor, const QUrl& viewUrl) const; void setHandCursor(QWidget* widget); void avoidMenuAltFocus(); bool eventFilter(QObject * watched, QEvent * event) override ; ContextBrowserPlugin* m_plugin; bool m_browsing; int m_browsingByKey; //Whether the browsing was started because of a key Watcher m_watcher; //Maps widgets to their previously set cursors QMap, QCursor> m_oldCursors; QTimer* m_delayedBrowsingTimer; QPointer m_browsingStartedInView; KTextEditor::Cursor m_buttonPressPosition; }; #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index 0f215cf4a..629f261bf 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1357 +1,1471 @@ /* * 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 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()))); 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( 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 = {}; } } -void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { +static ProblemPointer findProblemUnderCursor(const TopDUContext* topContext, KTextEditor::Cursor position) +{ + foreach (auto problem, topContext->problems()) { + if (problem->rangeInCurrentRevision().contains(position)) { + return problem; + } + } - 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 + return {}; +} + +static ProblemPointer findProblemCloseToCursor(const TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) +{ + auto problems = topContext->problems(); + 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); - QWidget* navigationWidget = 0; - { - DUChainReadLocker lock(DUChain::lock()); - foreach (const auto language, languages) { - auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); - navigationWidget = qobject_cast(widget); - if(navigationWidget) - break; - } + 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; + } - if(!navigationWidget) { - Declaration* decl = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(viewUrl, KTextEditor::Cursor(position)) ); - if (decl && decl->kind() == Declaration::Alias) { - AliasDeclaration* alias = dynamic_cast(decl); - Q_ASSERT(alias); - DUChainReadLocker lock; - decl = alias->aliasedDeclaration().declaration(); + 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; } - if(decl) { - if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) - return; - m_currentToolTipDeclaration = IndexedDeclaration(decl); - navigationWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); + + 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* 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; - itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, KTextEditor::Cursor(position)); + auto viewUrl = view->document()->url(); + itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, position); } 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)); // 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; } 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; } 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()->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()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute()); 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) ); 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()->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); 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 260694cb0..3c616a18f 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,271 +1,275 @@ /* * 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; 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(); 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; 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/plugins/problemreporter/problemhighlighter.cpp b/plugins/problemreporter/problemhighlighter.cpp index cfa134876..e7b39c7df 100644 --- a/plugins/problemreporter/problemhighlighter.cpp +++ b/plugins/problemreporter/problemhighlighter.cpp @@ -1,260 +1,206 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 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 "problemhighlighter.h" #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KTextEditor; using namespace KDevelop; namespace { QColor colorForSeverity(IProblem::Severity severity) { KColorScheme scheme(QPalette::Active); switch (severity) { case IProblem::Error: return scheme.foreground(KColorScheme::NegativeText).color(); case IProblem::Warning: return scheme.foreground(KColorScheme::NeutralText).color(); case IProblem::Hint: default: return scheme.foreground(KColorScheme::PositiveText).color(); } } } ProblemHighlighter::ProblemHighlighter(KTextEditor::Document* document) : m_document(document) - , m_textHintProvider(this) { Q_ASSERT(m_document); - foreach (KTextEditor::View* view, m_document->views()) - viewCreated(document, view); - - connect(m_document.data(), &Document::viewCreated, this, &ProblemHighlighter::viewCreated); connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemHighlighter::settingsChanged); connect(m_document.data(), &Document::aboutToReload, this, &ProblemHighlighter::clearProblems); if (qobject_cast(m_document)) { // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearProblems())); } connect(m_document, SIGNAL(aboutToRemoveText(KTextEditor::Range)), this, SLOT(aboutToRemoveText(KTextEditor::Range))); } void ProblemHighlighter::settingsChanged() { // Re-highlight setProblems(m_problems); } -void ProblemHighlighter::viewCreated(Document*, View* view) -{ - KTextEditor::TextHintInterface* iface = dynamic_cast(view); - if (!iface) - return; - - iface->registerTextHintProvider(&m_textHintProvider); -} - -ProblemTextHintProvider::ProblemTextHintProvider(ProblemHighlighter* highlighter) - : m_highlighter(highlighter) -{ -} - -QString ProblemTextHintProvider::textHint(View* view, const Cursor& pos) -{ - KTextEditor::MovingInterface* moving = dynamic_cast(view->document()); - if (moving) { - ///@todo Sort the ranges when writing them, and do binary search instead of linear - foreach (MovingRange* range, m_highlighter->m_topHLRanges) { - if (m_highlighter->m_problemsForRanges.contains(range) && range->contains(pos)) { - // There is a problem which's range contains the cursor - IProblem::Ptr problem = m_highlighter->m_problemsForRanges[range]; - if (problem->source() == IProblem::ToDo) { - continue; - } - - if (m_currentHintRange == range->toRange()) { - continue; - } - m_currentHintRange = range->toRange(); - - KDevelop::AbstractNavigationWidget* widget = new KDevelop::AbstractNavigationWidget; - widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problem))); - - KDevelop::NavigationToolTip* tooltip - = new KDevelop::NavigationToolTip(view, QCursor::pos() + QPoint(20, 40), widget); - - tooltip->resize(widget->sizeHint() + QSize(10, 10)); - tooltip->setHandleRect(KTextEditorHelpers::getItemBoundingRect(view, m_currentHintRange)); - tooltip->connect(tooltip, &ActiveToolTip::destroyed, [&] () { m_currentHintRange = {}; }); - ActiveToolTip::showToolTip(tooltip, 99, QStringLiteral("problem-tooltip")); - return QString(); - } - } - } - return QString(); -} - ProblemHighlighter::~ProblemHighlighter() { if (m_topHLRanges.isEmpty() || !m_document) return; qDeleteAll(m_topHLRanges); } void ProblemHighlighter::setProblems(const QVector& problems) { + qDebug() << "updating problem highlight"; if (!m_document) return; const bool hadProblems = !m_problems.isEmpty(); m_problems = problems; qDeleteAll(m_topHLRanges); m_topHLRanges.clear(); m_problemsForRanges.clear(); IndexedString url(m_document->url()); /// TODO: create a better MarkInterface that makes it possible to add the marks to the scrollbar /// but having no background. /// also make it nicer together with other plugins, this would currently fail with /// this method... const uint errorMarkType = KTextEditor::MarkInterface::Error; const uint warningMarkType = KTextEditor::MarkInterface::Warning; KTextEditor::MarkInterface* markIface = dynamic_cast(m_document.data()); if (markIface && hadProblems) { // clear previously added marks foreach (KTextEditor::Mark* mark, markIface->marks()) { if (mark->type == errorMarkType || mark->type == warningMarkType) { markIface->removeMark(mark->line, mark->type); } } } if (problems.isEmpty()) { return; } DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(m_document->url()); KTextEditor::MovingInterface* iface = dynamic_cast(m_document.data()); Q_ASSERT(iface); foreach (const IProblem::Ptr& problem, problems) { if (problem->finalLocation().document != url || !problem->finalLocation().isValid()) continue; KTextEditor::Range range; if (top) range = top->transformFromLocalRevision(RangeInRevision::castFromSimpleRange(problem->finalLocation())); else range = problem->finalLocation(); if (range.end().line() >= m_document->lines()) range.end() = KTextEditor::Cursor(m_document->endOfLine(m_document->lines() - 1)); if (range.isEmpty()) { range.setEnd(range.end() + KTextEditor::Cursor(0, 1)); } KTextEditor::MovingRange* problemRange = iface->newMovingRange(range); m_problemsForRanges.insert(problemRange, problem); m_topHLRanges.append(problemRange); if (problem->source() != IProblem::ToDo && (problem->severity() != IProblem::Hint || ICore::self()->languageController()->completionSettings()->highlightSemanticProblems())) { KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->setUnderlineStyle(QTextCharFormat::WaveUnderline); attribute->setUnderlineColor(colorForSeverity(problem->severity())); problemRange->setAttribute(attribute); } if (markIface && ICore::self()->languageController()->completionSettings()->highlightProblematicLines()) { uint mark; if (problem->severity() == IProblem::Error) { mark = errorMarkType; } else if (problem->severity() == IProblem::Warning) { mark = warningMarkType; } else { continue; } markIface->addMark(problem->finalLocation().start().line(), mark); } } } void ProblemHighlighter::aboutToRemoveText(const KTextEditor::Range& range) { if (range.onSingleLine()) { // no need to optimize this return; } QList::iterator it = m_topHLRanges.begin(); while (it != m_topHLRanges.end()) { if (range.contains((*it)->toRange())) { m_problemsForRanges.remove(*it); delete (*it); it = m_topHLRanges.erase(it); } else { ++it; } } } void ProblemHighlighter::clearProblems() { setProblems({}); } diff --git a/plugins/problemreporter/problemhighlighter.h b/plugins/problemreporter/problemhighlighter.h index 5a9f10c70..6026818b6 100644 --- a/plugins/problemreporter/problemhighlighter.h +++ b/plugins/problemreporter/problemhighlighter.h @@ -1,73 +1,54 @@ /* * KDevelop Problem Reporter * * Copyright 2008 Hamish Rodda * Copyright 2008-2009 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_PROBLEM_HIGHLIGHT_H #define KDEVPLATFORM_PLUGIN_PROBLEM_HIGHLIGHT_H #include #include #include -#include #include -class ProblemHighlighter; - -class ProblemTextHintProvider : public KTextEditor::TextHintProvider -{ -public: - explicit ProblemTextHintProvider(ProblemHighlighter* highlighter); - - QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; - -private: - ProblemHighlighter* m_highlighter; - KTextEditor::Range m_currentHintRange; -}; - class ProblemHighlighter : public QObject { Q_OBJECT public: explicit ProblemHighlighter(KTextEditor::Document* document); ~ProblemHighlighter() override; void setProblems(const QVector& problems); private slots: - void viewCreated(KTextEditor::Document*, KTextEditor::View*); void aboutToRemoveText(const KTextEditor::Range& range); void clearProblems(); private: QPointer m_document; QList m_topHLRanges; QVector m_problems; QMap m_problemsForRanges; - friend class ProblemTextHintProvider; - ProblemTextHintProvider m_textHintProvider; - public slots: void settingsChanged(); }; #endif // KDEVPLATFORM_PLUGIN_PROBLEM_HIGHLIGHT_H diff --git a/plugins/problemreporter/problemreporterplugin.cpp b/plugins/problemreporter/problemreporterplugin.cpp index b765188bd..0b6c98e6a 100644 --- a/plugins/problemreporter/problemreporterplugin.cpp +++ b/plugins/problemreporter/problemreporterplugin.cpp @@ -1,198 +1,211 @@ /* * KDevelop Problem Reporter * * Copyright 2006 Adam Treat * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 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 "problemreporterplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemhighlighter.h" #include "problemtreeview.h" #include "problemreportermodel.h" +#include "language/assistant/staticassistantsmanager.h" #include #include #include #include #include #include #include "shell/problemmodelset.h" #include "problemsview.h" #include Q_LOGGING_CATEGORY(PLUGIN_PROBLEMREPORTER, "kdevplatform.plugins.problemreporter") K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", registerPlugin();) using namespace KDevelop; class ProblemReporterFactory : public KDevelop::IToolViewFactory { public: QWidget* create(QWidget* parent = 0) override { Q_UNUSED(parent); ProblemsView* v = new ProblemsView(); v->load(); return v; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } }; ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) , m_factory(new ProblemReporterFactory) , m_model(new ProblemReporterModel(this)) { KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("Parser"), m_model); core()->uiController()->addToolView(i18n("Problems"), m_factory); setXMLFile(QStringLiteral("kdevproblemreporter.rc")); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemReporterPlugin::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &ProblemReporterPlugin::textDocumentCreated); connect(DUChain::self(), &DUChain::updateReady, this, &ProblemReporterPlugin::updateReady); } ProblemReporterPlugin::~ProblemReporterPlugin() { qDeleteAll(m_highlighters); } ProblemReporterModel* ProblemReporterPlugin::model() const { return m_model; } void ProblemReporterPlugin::unload() { KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); pms->removeModel(QStringLiteral("Parser")); core()->uiController()->removeToolView(m_factory); } void ProblemReporterPlugin::documentClosed(IDocument* doc) { if (!doc->textDocument()) return; IndexedString url(doc->url()); delete m_highlighters.take(url); } void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); m_highlighters.insert(IndexedString(document->url()), new ProblemHighlighter(document->textDocument())); - DUChainReadLocker lock(DUChain::lock()); DUChain::self()->updateContextForUrl(IndexedString(document->url()), KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); } -void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) +void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext& top) { + { + DUChainWriteLocker lock(DUChain::lock(), 300); + if ( !lock.locked() ) { + return; + } + ICore::self()->languageController()->staticAssistantsManager()->notifyAssistants(url, top); + auto assistantProblems = ICore::self()->languageController()->staticAssistantsManager()->problemsForContext(top); + Q_FOREACH ( const auto p, assistantProblems ) { + qDebug() << "adding problem:" << p << "assistant:" << p->solutionAssistant().data() << p->solutionAssistant()->actions().size(); + top->addProblem(p); + } + } + m_model->problemsUpdated(url); ProblemHighlighter* ph = m_highlighters.value(url); if (ph) { - QVector allProblems = m_model->problems(url, false); + auto allProblems = m_model->problems(url, false); ph->setProblems(allProblems); } } KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension; KDevelop::EditorContext* editorContext = dynamic_cast(context); if (editorContext) { DUChainReadLocker lock(DUChain::lock(), 1000); if (!lock.locked()) { qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; return extension; } QString title; QList actions; TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); if (top) { foreach (KDevelop::ProblemPointer problem, top->problems()) { if (problem->range().contains( top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); if (solution) { title = solution->title(); foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) actions << action->toKAction(); } } } } if (!actions.isEmpty()) { QString text; if (title.isEmpty()) text = i18n("Solve Problem"); else { text = i18n("Solve: %1", KDevelop::htmlToPlainText(title)); } QAction* menuAction = new QAction(text, 0); QMenu* menu(new QMenu(text, 0)); menuAction->setMenu(menu); foreach (QAction* action, actions) menu->addAction(action); extension.addAction(ContextMenuExtension::ExtensionGroup, menuAction); } } return extension; } #include "problemreporterplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/shell/AssistantButton.qml b/shell/AssistantButton.qml deleted file mode 100644 index 2d07c5704..000000000 --- a/shell/AssistantButton.qml +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright 2014 Sven Brauch - Copyright 2014 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - 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. -*/ - -import QtQuick 2.2 - -// Component which provides a single button for the assistant widget. - -Rectangle { - id: root - - property color foreground - property color background - property color highlight - - property bool highlighted: false - // text on the button - property string text - // text in the shortcut field - property int button - // emitted when the button is clicked with the mouse - signal triggered() - - y: -1 - width: text.width + 4 - height: number.height + 4 - - color: Qt.lighter(root.background, 1.5) - border.color: Qt.lighter(root.foreground, 1.5) - - Behavior on opacity { - NumberAnimation { duration: 150; } - } - MouseArea { - id: mouseArea - - anchors.fill: parent - onClicked: root.triggered() - - hoverEnabled: true - - Row { - // row containing the separators, shortcut text, and button text - z: 3 - id: text - anchors.centerIn: parent - spacing: 0 - Rectangle { width: 2; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding - Text { - // shortcut key - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - id: number - color: root.foreground - text: button - z: 2 - } - Rectangle { width: 3; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding - Rectangle { y: 1; width: 1; color: root.foreground; height: root.height - 1; opacity: 0.3 } // line - Rectangle { width: 4; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding - Text { - // actual button text - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - color: root.foreground - text: root.text - textFormat: Text.PlainText - } - Rectangle { width: 2; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding - } - Rectangle { - // the background color for the shortcut key box, invisible by default. - id: highlightArea - Behavior on opacity { - NumberAnimation { duration: 200 } - } - opacity: (root.highlighted || mouseArea.containsMouse) ? 0.5 : 0.0 - x: 1 - y: 1 - z: 1 - height: text.height - width: number.width + 6 - color: root.highlight - } - } -} diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index 19a956014..0e55f96de 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,175 +1,172 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") add_subdirectory(tests) set(KDevPlatformShell_LIB_SRCS workingsetcontroller.cpp workingsets/workingset.cpp workingsets/workingsetfilelabel.cpp workingsets/workingsettoolbutton.cpp workingsets/workingsettooltipwidget.cpp workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp - assistantpopup.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp ktexteditorpluginintegration.cpp shellextension.cpp core.cpp uicontroller.cpp projectcontroller.cpp project.cpp partcontroller.cpp #document.cpp partdocument.cpp textdocument.cpp documentcontroller.cpp languagecontroller.cpp statusbar.cpp runcontroller.cpp unitylauncher.cpp sessioncontroller.cpp session.cpp sessionlock.cpp sessionchooserdialog.cpp savedialog.cpp sourceformattercontroller.cpp completionsettings.cpp openprojectpage.cpp openprojectdialog.cpp projectinfopage.cpp selectioncontroller.cpp documentationcontroller.cpp debugcontroller.cpp launchconfiguration.cpp launchconfigurationdialog.cpp loadedpluginsdialog.cpp testcontroller.cpp projectsourcepage.cpp debug.cpp configdialog.cpp editorconfigpage.cpp environmentconfigurebutton.cpp checkerstatus.cpp problem.cpp problemmodelset.cpp problemmodel.cpp problemstore.cpp watcheddocumentset.cpp filteredproblemstore.cpp progresswidget/progressmanager.cpp progresswidget/statusbarprogresswidget.cpp progresswidget/overlaywidget.cpp progresswidget/progressdialog.cpp areadisplay.cpp settings/uipreferences.cpp settings/pluginpreferences.cpp settings/sourceformattersettings.cpp settings/editstyledialog.cpp settings/projectpreferences.cpp settings/environmentwidget.cpp settings/environmentgroupmodel.cpp settings/environmentpreferences.cpp settings/languagepreferences.cpp settings/bgpreferences.cpp settings/templateconfig.cpp settings/templatepage.cpp ) kconfig_add_kcfg_files(KDevPlatformShell_LIB_SRCS settings/uiconfig.kcfgc settings/projectconfig.kcfgc settings/languageconfig.kcfgc settings/bgconfig.kcfgc ) ki18n_wrap_ui(KDevPlatformShell_LIB_SRCS projectinfopage.ui launchconfigurationdialog.ui projectsourcepage.ui settings/uiconfig.ui settings/editstyledialog.ui settings/sourceformattersettings.ui settings/projectpreferences.ui settings/environmentwidget.ui settings/languagepreferences.ui settings/bgpreferences.ui settings/templateconfig.ui settings/templatepage.ui ) qt5_add_resources(KDevPlatformShell_LIB_SRCS kdevplatformshell.qrc) kdevplatform_add_library(KDevPlatformShell SOURCES ${KDevPlatformShell_LIB_SRCS}) target_link_libraries(KDevPlatformShell LINK_PUBLIC KF5::XmlGui KDev::Sublime KDev::OutputView KDev::Interfaces LINK_PRIVATE Qt5::Quick Qt5::QuickWidgets KF5::GuiAddons KF5::IconThemes KF5::KIOFileWidgets KF5::KIOWidgets KF5::Parts KF5::Notifications KF5::NotifyConfig KF5::TextEditor KF5::ThreadWeaver KF5::JobWidgets KF5::ItemViews KF5::WindowSystem KF5::KCMUtils #for KPluginSelector, not sure why it is in kcmutils KF5::NewStuff # template config page KF5::Archive # template config page KDev::Debugger KDev::Project KDev::Vcs KDev::Language KDev::Util KDev::Documentation ) install(FILES mainwindow.h plugincontroller.h shellextension.h core.h uicontroller.h projectcontroller.h project.h partcontroller.h partdocument.h textdocument.h documentcontroller.h languagecontroller.h session.h sessioncontroller.h sessionlock.h sourceformattercontroller.h selectioncontroller.h runcontroller.h launchconfiguration.h environmentconfigurebutton.h checkerstatus.h problem.h problemmodel.h problemmodelset.h problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel ) - -install( FILES AssistantButton.qml assistantpopup.qml DESTINATION ${KDE_INSTALL_DATADIR}/kdevelop ) diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp deleted file mode 100644 index f5e08a12d..000000000 --- a/shell/assistantpopup.cpp +++ /dev/null @@ -1,381 +0,0 @@ -/* - Copyright 2009 David Nolden - Copyright 2012 Milian Wolff - Copyright 2014 Sven Brauch - Copyright 2014 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - 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 "assistantpopup.h" -#include "sublime/holdupdates.h" -#include "util/kdevstringhandler.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace KDevelop; - -namespace { - -const int ASSISTANT_MODIFIER = -#ifdef Q_OS_MAC -Qt::CTRL; -#else -Qt::ALT; -#endif - -const int ASSISTANT_MOD_KEY = -#ifdef Q_OS_MAC -Qt::Key_Control; -#else -Qt::Key_Alt; -#endif - -QWidget* findByClassname(const KTextEditor::View* view, const QString& klass) -{ - auto children = view->findChildren(); - foreach ( auto child, children ) { - if ( child->metaObject()->className() == klass ) { - return child; - } - } - return nullptr; -}; - -/** - * @brief Get the geometry of the inner part (with the text) of the KTextEditor::View being used. - */ -QRect textWidgetGeometry(const KTextEditor::View *view) -{ - // Subtract the width of the right scrollbar - int scrollbarWidth = 0; - if ( auto scrollbar = findByClassname(view, QStringLiteral("KateScrollBar")) ) { - scrollbarWidth = scrollbar->width(); - } - // Subtract the width of the bottom scrollbar - int bottomScrollbarWidth = 0; - if ( auto bottom = findByClassname(view, QStringLiteral("QScrollBar")) ) { - bottomScrollbarWidth = bottom->height(); - } - auto geom = view->geometry(); - - geom.adjust(0, 0, -scrollbarWidth, -bottomScrollbarWidth); - return geom; -} - -} - -AssistantPopupConfig::AssistantPopupConfig(QObject *parent) - : QObject(parent) - , m_active(false) -{ -} - -void AssistantPopupConfig::setColorsFromView(QObject *view) -{ - auto iface = dynamic_cast(view); - Q_ASSERT(iface); - m_foreground = iface->configValue(QStringLiteral("line-number-color")).value(); - m_background = iface->configValue(QStringLiteral("icon-border-color")).value(); - m_highlight = iface->configValue(QStringLiteral("folding-marker-color")).value(); - if ( KColorUtils::luma(m_background) < 0.3 ) { - m_foreground = KColorUtils::lighten(m_foreground, 0.7); - } - const float lumaDiff = KColorUtils::luma(m_highlight) - KColorUtils::luma(m_background); - if ( qAbs(lumaDiff) < 0.5 ) { - m_highlight = QColor::fromHsv(m_highlight.hue(), - qMin(255, m_highlight.saturation() + 80), - lumaDiff > 0 ? qMin(255, m_highlight.value() + 120) - : qMax(80, m_highlight.value() - 40)); - } - emit colorsChanged(); -} - -bool AssistantPopupConfig::isActive() const -{ - return m_active; -} - -void AssistantPopupConfig::setActive(bool active) -{ - if (m_active == active) { - return; - } - - m_active = active; - emit activeChanged(m_active); -} - -void AssistantPopupConfig::setViewSize(const QSize& size) -{ - if (size != m_viewSize) { - m_viewSize = size; - emit viewSizeChanged(size); - } -} - -void AssistantPopupConfig::setTitle(const QString& title) -{ - if (m_title == title) { - return; - } - - m_title = title; - emit titleChanged(m_title); -} - -void AssistantPopupConfig::setModel(const QList& model) -{ - if (m_model == model) { - return; - } - - qDeleteAll( m_model ); - m_model = model; - emit modelChanged(model); -} - -AssistantPopup::AssistantPopup() -// main window as parent to use maximal space available in worst case - : QQuickWidget(ICore::self()->uiController()->activeMainWindow()) - , m_config(new AssistantPopupConfig(this)) - , m_firstLayoutCompleted(false) -{ - setAttribute(Qt::WA_ShowWithoutActivating); - - rootContext()->setContextProperty(QStringLiteral("config"), m_config); - - setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/assistantpopup.qml")))); - if (!rootObject()) { - qWarning() << "Failed to load assistant markup! The assistant will not work."; - } else { - connect(rootObject(), &QQuickItem::widthChanged, this, &AssistantPopup::updateLayout); - connect(rootObject(), &QQuickItem::heightChanged, this, &AssistantPopup::updateLayout); - } - - for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { - m_shortcuts.append(new QShortcut(ASSISTANT_MODIFIER + i, this)); - } - setActive(false); - - connect(qApp, &QApplication::applicationStateChanged, this, [this]{ setActive(false); }); -} - -void AssistantPopup::reset(KTextEditor::View* view, const IAssistant::Ptr& assistant) -{ - setView(view); - setAssistant(assistant); - updateState(); -} - -void AssistantPopup::setView(KTextEditor::View* view) -{ - if (m_view == view) { - return; - } - - setActive(false); - - if (m_view) { - m_view->removeEventFilter(this); - disconnect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, - this, &AssistantPopup::updatePosition); - } - m_view = view; - m_config->setViewSize(m_view ? m_view->size() : QSize()); - if (m_view) { - m_view->installEventFilter(this); - connect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, - this, &AssistantPopup::updatePosition); - } -} - -void AssistantPopup::setAssistant(const IAssistant::Ptr& assistant) -{ - if (m_assistant == assistant) { - return; - } - - if (m_assistant) { - disconnect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); - disconnect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); - } - m_assistant = assistant; - if (m_assistant) { - connect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); - connect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); - } else { - hide(); - } -} - -void AssistantPopup::setActive(bool active) -{ - m_config->setActive(active); - foreach (auto shortcut, m_shortcuts) { - shortcut->setEnabled(active); - } -} - -bool AssistantPopup::eventFilter(QObject* object, QEvent* event) -{ - Q_UNUSED(object); - - if (!m_view || (object != m_view.data())) - return false; - - if (event->type() == QEvent::Resize) { - updateLayout(); - } else if (event->type() == QEvent::Hide) { - executeHideAction(); - } else if (event->type() == QEvent::KeyPress) { - auto keyEvent = static_cast(event); - if (keyEvent->modifiers() == ASSISTANT_MODIFIER) { - setActive(true); - } - if (keyEvent->key() == Qt::Key_Escape) { - executeHideAction(); - } - } else if (event->type() == QEvent::KeyRelease) { - auto keyEvent = static_cast(event); - if (keyEvent->modifiers() == ASSISTANT_MODIFIER || keyEvent->key() == ASSISTANT_MOD_KEY) { - setActive(false); - } - } - return false; -} - -void AssistantPopup::updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos) -{ - static const int MARGIN = 12; - - if (newPos.isValid() && newPos.line() == 0) { - // the position is not going to change; don't waste time - return; - } - - auto editorGeometry = textWidgetGeometry(view); - const auto startCursorCoordinate = view->cursorToCoordinate(KTextEditor::Cursor(0, 0)); - - // algorithm for popup positioning: - // if we are scrolled to the top: show at bottom - // else: - // if: current cursor position is in upper half => show at bottom - // else: show at top - const bool showAtBottom = startCursorCoordinate.y() == 0 ? true : - view->cursorPositionCoordinates().y() < view->height()/2; - const QPoint targetLocation = showAtBottom ? - parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.bottomRight() - + QPoint(-width() - MARGIN, -MARGIN - height()))) : - parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.topRight() - + QPoint(-width() - MARGIN, MARGIN))); - if (pos() == targetLocation) { - return; - } - - Sublime::HoldUpdates hold(ICore::self()->uiController()->activeMainWindow()); - move(targetLocation); -} - -IAssistant::Ptr AssistantPopup::assistant() const -{ - return m_assistant; -} - -void AssistantPopup::executeHideAction() -{ - if ( isVisible() ) { - m_assistant->doHide(); - } -} - -void AssistantPopup::hideAssistant() -{ - reset(nullptr, {}); // indirectly calls hide() -} - -void AssistantPopup::updateLayout() -{ - auto root = rootObject(); - if (!m_view || !root) { - return; - } - - m_config->setViewSize(m_view->size()); - // https://bugreports.qt.io/browse/QTBUG-44876 - resize(root->width(), root->height()); - updatePosition(m_view, KTextEditor::Cursor::invalid()); - - // HACK: QQuickWidget is corrupted due to above resize on the first show - if (!m_firstLayoutCompleted) { - hide(); - show(); - m_firstLayoutCompleted = true; - } -} - -void AssistantPopup::updateState() -{ - if (!m_assistant || m_assistant->actions().isEmpty() || !m_view) { - hide(); - return; - } - - auto curShortcut = m_shortcuts.constBegin(); - auto hideAction = new QAction(i18n("Hide"), this); - connect(*curShortcut, &QShortcut::activated, hideAction, &QAction::trigger); - connect(hideAction, &QAction::triggered, this, &AssistantPopup::executeHideAction); - - QList items; - foreach (IAssistantAction::Ptr action, m_assistant->actions()) { - QAction* asQAction = action->toKAction(); - items << asQAction; - asQAction->setParent(this); - //For some reason, QAction's setShortcut does nothing, so we manage with QShortcut - if (++curShortcut != m_shortcuts.constEnd()) { - connect(*curShortcut, &QShortcut::activated, asQAction, &QAction::trigger); - } - connect(action.data(), SIGNAL(executed(IAssistantAction*)), hideAction, SLOT(trigger())); - } - items << hideAction; - - auto view = ICore::self()->documentController()->activeTextDocumentView(); - m_config->setColorsFromView(view); - m_config->setModel(items); - m_config->setTitle(m_assistant->title()); - setActive(false); - - show(); -} - diff --git a/shell/assistantpopup.h b/shell/assistantpopup.h deleted file mode 100644 index b192a17eb..000000000 --- a/shell/assistantpopup.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright 2009 David Nolden - Copyright 2012 Milian Wolff - Copyright 2014 Sven Brauch - Copyright 2014 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - 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. -*/ - -#ifndef KDEVPLATFORM_ASSISTANTPOPUP_H -#define KDEVPLATFORM_ASSISTANTPOPUP_H - -#include -#include -#include - -namespace KTextEditor -{ -class View; -class Cursor; -} - -class AssistantPopupConfig : public QObject -{ - Q_OBJECT - Q_PROPERTY(QColor foreground READ foreground NOTIFY colorsChanged) - Q_PROPERTY(QColor background READ background NOTIFY colorsChanged) - Q_PROPERTY(QColor highlight READ highlight NOTIFY colorsChanged) - - Q_PROPERTY(QString title READ title NOTIFY titleChanged) - Q_PROPERTY(QList model READ model NOTIFY modelChanged) - Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) - Q_PROPERTY(QSize viewSize READ viewSize WRITE setViewSize NOTIFY viewSizeChanged) - -public: - explicit AssistantPopupConfig(QObject *parent = nullptr); - - QColor foreground() const { return m_foreground; } - QColor background() const { return m_background; } - QColor highlight() const { return m_highlight; } - - QSize viewSize() const { return m_viewSize; }; - void setViewSize(const QSize &size); - - QString title() const { return m_title; } - void setTitle(const QString& title); - QList model() const { return m_model; } - void setModel(const QList& model); - - void setColorsFromView(QObject *view); - - bool isActive() const; - void setActive(bool active); - -signals: - void colorsChanged(); - - void titleChanged(const QString& title); - void modelChanged(const QList& model); - void activeChanged(bool active); - void viewSizeChanged(const QSize& size); - -private: - QColor m_foreground; - QColor m_background; - QColor m_highlight; - - QString m_title; - QList m_model; - bool m_active; - QSize m_viewSize; -}; - -Q_DECLARE_METATYPE(AssistantPopupConfig*) - -class AssistantPopup : public QQuickWidget -{ - Q_OBJECT - -public: - typedef QExplicitlySharedDataPointer Ptr; - - /** - * The current main window will be used as parent widget for the popup. - * This is to make use of the maximal space available and prevent any lines - * in e.g. the editor to be hidden by the popup. - */ - AssistantPopup(); - - /** - * Reset this popup for view @p view and show assistant @p assistant - * - * @p view The widget below which the assistant should be shown. - */ - void reset(KTextEditor::View *view, const KDevelop::IAssistant::Ptr &assistant); - - KDevelop::IAssistant::Ptr assistant() const; - -private slots: - void updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos); - void updateState(); - void updateLayout(); - - void executeHideAction(); - void hideAssistant(); - -protected: - bool eventFilter(QObject* object, QEvent* event) override; - -private: - void setView(KTextEditor::View* view); - void setAssistant(const KDevelop::IAssistant::Ptr& assistant); - void setActive( bool active ); - - KDevelop::IAssistant::Ptr m_assistant; - QPointer m_view; - AssistantPopupConfig* m_config; - QList m_shortcuts; - bool m_firstLayoutCompleted; -}; - -#endif // KDEVPLATFORM_ASSISTANTPOPUP_H diff --git a/shell/assistantpopup.qml b/shell/assistantpopup.qml deleted file mode 100644 index 63c00e875..000000000 --- a/shell/assistantpopup.qml +++ /dev/null @@ -1,89 +0,0 @@ -/* - Copyright 2014 Sven Brauch - Copyright 2014 Kevin Funk - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - 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. -*/ - -// This file provides the whole assistant, including title and buttons. - -import QtQuick 2.2 - -Rectangle { - id: root - - readonly property int vSpacing: 4 - readonly property int hSpacing: 4 - readonly property real itemsWidth: { - var totalWidth = title.width; - for (var i = 0; i < items.count; ++i) { - totalWidth += items.itemAt(i).width; - } - return totalWidth + (items.count + 2) * hSpacing; - } - readonly property bool useVerticalLayout: config.viewSize.width * 0.90 < itemsWidth - - // QQuickWidget crashes if either of these is zero - // Use ceil to ensure the widget always fits the non-integral content size - width: Math.ceil(Math.max(hSpacing, mainFlow.width + hSpacing * 2)) - height: Math.ceil(Math.max(vSpacing, mainFlow.height + vSpacing * 2)) - - border.width: 1 - border.color: Qt.lighter(config.foreground) - gradient: Gradient { - GradientStop { position: 0.0; color: Qt.lighter(config.background) } - GradientStop { position: 1.0; color: config.background } - } - - Flow { - id: mainFlow - - anchors { - centerIn: parent - } - - flow: root.useVerticalLayout ? Flow.TopToBottom : Flow.LeftToRight - spacing: root.useVerticalLayout ? root.vSpacing : root.hSpacing - - Text { - id: title - - color: config.foreground - font.bold: true - text: config.title - } - - - Repeater { - id: items - objectName: "items" - - y: 5 - model: config.model - - AssistantButton { - text: modelData.text - highlighted: config.active - // what is displayed in the hotkey field of the button - button: index == items.model.length - 1 ? 0 : index + 1 - foreground: config.foreground - background: config.background - highlight: config.highlight - - onTriggered: { modelData.trigger() } - } - } - } -} diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp index ef8261f29..ba5b1a6bd 100644 --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -1,772 +1,740 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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 Library 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 "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" -#include "assistantpopup.h" #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" namespace KDevelop { class UiControllerPrivate { public: UiControllerPrivate(UiController *controller) : areasRestored(false), m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("tools-report-bug")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("applications-engineering")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = 0; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { Sublime::MainWindow* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core *core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; - /// Currently shown assistant popup. - QPointer currentShownAssistant; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = 0) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory *m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: ViewSelectorItem(const QString &text, QListWidget *parent = 0, int type = Type) :QListWidgetItem(text, parent, type) {} IToolViewFactory *factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: NewToolViewListWidget(MainWindow *mw, QWidget* parent = 0) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(QModelIndex index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the toolview can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow *m_mw; }; UiController::UiController(Core *core) :Sublime::Controller(0), IUiController(), d(new UiControllerPrivate(this)) { setObjectName(QStringLiteral("UiController")); d->core = core; if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [&] (QWidget* old, QWidget* now) { d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() { delete d; } void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } MainWindow *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) foreach (KXMLGUIClient *client, oldMain->guiFactory()->clients()) main->guiFactory()->addClient(client); #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { if(!d->areasRestored || !activeArea()) return 0; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = 0; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { if(!d->areasRestored) return; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { if (!factory) return; qCDebug(SHELL) ; Sublime::ToolDocument *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { foreach (Sublime::Area* area, allAreas()) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { foreach( Sublime::Area* area, allAreas() ) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); foreach (Sublime::View* toolView, area->toolViews()) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted foreach (Sublime::View *view, doc->views()) { foreach (Sublime::Area *area, allAreas()) if (area->removeToolView(view)) view->deleteLater(); } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return 0; } Sublime::MainWindow *UiController::activeSublimeWindow() { return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { foreach (Sublime::MainWindow* w, mainWindows()) w->saveSettings(); saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { if (!mw || !mw->area()) return; QDialog *dia = new QDialog(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); NewToolViewListWidget *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), list); item->factory = it.key(); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the toolview is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { foreach (QListWidgetItem* item, list->selectedItems()) { addNewToolView(mw, item); } } delete dia; } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { ViewSelectorItem *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { auto editorConfigPage = new EditorConfigPage(activeMainWindow()); auto languageConfigPage = new LanguagePreferences(activeMainWindow()); auto configPages = QVector { new UiPreferences(activeMainWindow()), new PluginPreferences(activeMainWindow()), new SourceFormatterSettings(activeMainWindow()), new ProjectPreferences(activeMainWindow()), new EnvironmentPreferences(QString(), activeMainWindow()), new BGPreferences(activeMainWindow()), new TemplateConfig(activeMainWindow()), editorConfigPage }; ConfigDialog cfgDlg(configPages, activeMainWindow()); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if(page && page->configPageType() == ConfigPage::LanguageConfigPage){ cfgDlg.addSubConfigPage(languageConfigPage, page); } else { // insert them before the editor config page cfgDlg.addConfigPage(page, editorConfigPage); } } }; cfgDlg.addConfigPage(languageConfigPage, configPages[5]); foreach (IPlugin* plugin, ICore::self()->pluginController()->loadedPlugins()) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); foreach (Sublime::Area* defaultArea, defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); QList changedAreas; /* Offer all toolviews to the default areas. */ foreach (Sublime::Area *area, defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ foreach (Sublime::Area* defaultArea, defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which toolviews the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { foreach (Sublime::View *view, doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, static_cast(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } -void UiController::hideAssistant() -{ - if (d->currentShownAssistant) { - d->currentShownAssistant->hide(); - } -} - -void UiController::popUpAssistant(const KDevelop::IAssistant::Ptr& assistant) -{ - if(!assistant) - return; - - Sublime::View* view = d->activeSublimeWindow->activeView(); - if( !view ) - { - qCDebug(SHELL) << "no active view in mainwindow"; - return; - } - - auto editorView = qobject_cast(view->widget()); - Q_ASSERT(editorView); - if (editorView) { - if ( !d->currentShownAssistant ) { - d->currentShownAssistant = new AssistantPopup; - } - d->currentShownAssistant->reset(editorView, assistant); - } -} - const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/shell/uicontroller.h b/shell/uicontroller.h index b4b1f0eb7..a03a39f02 100644 --- a/shell/uicontroller.h +++ b/shell/uicontroller.h @@ -1,133 +1,129 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * 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 Library 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_UICONTROLLER_H #define KDEVPLATFORM_UICONTROLLER_H #include "shellexport.h" #include #include #include #include class QListWidgetItem; namespace Sublime { class AreaIndex; class ToolDocument; } namespace KDevelop { class Core; class MainWindow; class KDEVPLATFORMSHELL_EXPORT UiController: public Sublime::Controller, public IUiController { Q_OBJECT public: explicit UiController(Core *core); ~UiController() override; /** @return area for currently active sublime mainwindow or 0 if no sublime mainwindow is active.*/ Sublime::Area *activeArea() override; /** @return active sublime mainwindow or 0 if no such mainwindow is active.*/ virtual Sublime::MainWindow *activeSublimeWindow(); /** @return active sublime mainwindow or 0 if no such mainwindow is active.*/ KParts::MainWindow *activeMainWindow() override; /** @return default main window - the main window for default area in the shell. No guarantee is given that it always exists so this method may return 0.*/ MainWindow *defaultMainWindow(); void switchToArea(const QString &areaName, SwitchMode switchMode) override; void addToolView(const QString &name, IToolViewFactory *factory, FindFlags state) override; void removeToolView(IToolViewFactory *factory) override; QWidget* findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) override; void raiseToolView(QWidget* toolViewWidget) override; void selectNewToolViewToAdd(MainWindow *mw); void initialize(); void cleanup(); void showSettingsDialog(); Sublime::Controller* controller() override; void mainWindowAdded(Sublime::MainWindow* mainWindow); void saveAllAreas(KSharedConfigPtr config); void saveArea(Sublime::Area* area, KConfigGroup & group); void loadAllAreas(KSharedConfigPtr config); void loadArea(Sublime::Area* area, const KConfigGroup & group); /*! @p status must implement KDevelop::IStatus */ void registerStatus(QObject* status) override; - void popUpAssistant(const KDevelop::IAssistant::Ptr& assistant) override; - void showErrorMessage(const QString& message, int timeout) override; /// Returns list of available view factories together with their ToolDocuments. /// @see addToolView(), removeToolView(), findToolView() const QHash& factoryDocuments() const; /// Adds a tool view in the active area to the dock area @p area. /// @see activeArea() void addToolViewToDockArea(KDevelop::IToolViewFactory* factory, Qt::DockWidgetArea area); bool toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area); QWidget* activeToolViewActionListener() const override; QList allAreas() const override; public Q_SLOTS: void raiseToolView(Sublime::View * view); private Q_SLOTS: void addNewToolView(MainWindow* mw, QListWidgetItem* item); void slotAreaChanged(Sublime::Area* area); void slotActiveToolViewChanged(Sublime::View* view); - void hideAssistant() override; - private: void addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area); Sublime::View* addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p=Sublime::AllPositions); void setupActions(); class UiControllerPrivate* const d; friend class UiControllerPrivate; Q_PRIVATE_SLOT(d, void widgetChanged(QWidget*,QWidget*)) }; } #endif