diff --git a/interfaces/iprojectcontroller.h b/interfaces/iprojectcontroller.h index bf91815e0c..a7d79f7954 100644 --- a/interfaces/iprojectcontroller.h +++ b/interfaces/iprojectcontroller.h @@ -1,194 +1,194 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_IPROJECTCONTROLLER_H #define KDEVPLATFORM_IPROJECTCONTROLLER_H #include #include #include #include "interfacesexport.h" class QItemSelectionModel; namespace KDevelop { class IProject; class ProjectBuildSetModel; class ProjectModel; class ProjectBaseItem; class ProjectChangesModel; /** * @class IProjectController */ class KDEVPLATFORMINTERFACES_EXPORT IProjectController : public QObject { Q_OBJECT public: IProjectController( QObject *parent = 0 ); virtual ~IProjectController(); Q_SCRIPTABLE virtual IProject* projectAt( int ) const = 0; Q_SCRIPTABLE virtual int projectCount() const = 0; Q_SCRIPTABLE virtual QList projects() const = 0; /** * Provides access to the model representing the open projects * @returns the model containing the projects and their items */ Q_SCRIPTABLE virtual ProjectModel* projectModel() = 0; /** * @returns an instance to the model that keeps track of the state * of the files per project. */ Q_SCRIPTABLE virtual ProjectChangesModel* changesModel() = 0; Q_SCRIPTABLE virtual ProjectBuildSetModel* buildSetModel() = 0; /** * Find an open project using the name of the project * @param name the name of the project to be found * @returns the project or null if no project with that name is open */ Q_SCRIPTABLE virtual KDevelop::IProject* findProjectByName( const QString& name ) = 0; /** * Finding an open project for a given file or folder in the project * @param url the url of a file/folder belonging to an open project * @returns the first open project containing the url or null if no such * project can be found */ Q_SCRIPTABLE virtual IProject* findProjectForUrl( const KUrl& url ) const = 0; /** * Checks whether the given project name is used already or not. The project * controller supports only 1 project with a given name to be open at any time * @param name the name of the project to be opened or created * @returns whether the name is already used for an open project */ Q_SCRIPTABLE virtual bool isProjectNameUsed( const QString& name ) const = 0; virtual KUrl projectsBaseDirectory() const = 0; enum FormattingOptions { FormatHtml, FormatPlain }; /** * Returns a pretty short representation of the base path of the url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * The returned path always has a training slash. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFilePath(KUrl url, FormattingOptions format = FormatHtml) const = 0; /** * Returns a pretty short representation of the given url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFileName(KUrl url, FormattingOptions format = FormatHtml) const = 0; /** - * @returns wether project files should be parsed or not + * @returns whether project files should be parsed or not */ static bool parseAllProjectSources(); public Q_SLOTS: /** * Tries finding a project-file for the given source-url and opens it. * If no .kdev4 project file is found, the user is asked to import a project. */ virtual void openProjectForUrl( const KUrl &sourceUrl ) = 0; /** * open the project from the given kdev4 project file. This only reads * the file and starts creating the project model from it. The opening process * is finished once @ref projectOpened signal is emitted. * @param url a kdev4 project file top open * @returns true if the given project could be opened, false otherwise */ virtual void openProject( const KUrl & url = KUrl() ) = 0; /** * close the given project. Closing the project is done in steps and * the @ref projectClosing and @ref projectClosed signals are emitted. Only when * the latter signal is emitted it is guaranteed that the project has been closed. * The @ref IProject object will be deleted after the closing has finished. * @returns true if the project could be closed, false otherwise */ virtual void closeProject( IProject* ) = 0; virtual void configureProject( IProject* ) = 0; // virtual void changeCurrentProject( KDevelop::ProjectBaseItem* ) = 0; Q_SIGNALS: /** * Emitted right before a project is started to be loaded. * * At this point all sanity checks have been done, so the project * is really going to be loaded. Will be followed by @ref projectOpened signal * when loading completes or by @ref projectOpeningAborted if there are errors during loading * or it is aborted. * * @param project the project that is about to be opened. */ void projectAboutToBeOpened( KDevelop::IProject* project ); /** * emitted after a project is completely opened and the project model * has been populated. * @param project the project that has been opened. */ void projectOpened( KDevelop::IProject* project ); /** * emitted when starting to close a project that has been completely loaded before * (the @ref projectOpened signal has been emitted). * @param project the project that is going to be closed. */ void projectClosing( KDevelop::IProject* project ); /** * emitted when a project has been closed completely. * The project object is still valid, the deletion will be done * delayed during the next run of the event loop. * @param project the project that has been closed. */ void projectClosed( KDevelop::IProject* project ); /** * emitted when a project could not be loaded correctly or loading was aborted. * @ref project contents may not be initialized properly. * @param project the project which loading has been aborted. */ void projectOpeningAborted( KDevelop::IProject* project ); /** * emitted whenever the project configuration dialog accepted * changes * @param project the project whose configuration has changed */ void projectConfigurationChanged( KDevelop::IProject* project ); }; } #endif diff --git a/interfaces/kdevelopplugin.desktop b/interfaces/kdevelopplugin.desktop index 1982b146bf..f4c8aaaaee 100644 --- a/interfaces/kdevelopplugin.desktop +++ b/interfaces/kdevelopplugin.desktop @@ -1,112 +1,112 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KDevelop/Plugin X-KDE-Derived=KPluginInfo Name=KDevelop Plugin Name[bg]=KDevelop приставка Name[bs]=KDevelop dodatak Name[ca]=Connector KDevelop Name[ca@valencia]=Connector KDevelop Name[cs]=Modul KDevelop Name[da]=KDevelop-plugin Name[de]=KDevelop-Modul Name[el]=Πρόσθετο του KDevelop Name[en_GB]=KDevelop Plugin Name[es]=Complemento de KDevelop Name[et]=KDevelopi plugin Name[fi]=KDevelop-liitännäinen Name[fr]=Module externe pour KDevelop Name[ga]=Breiseán KDevelop Name[gl]=Engadido para KDevelop Name[hr]=KDevelop priključak Name[hu]=KDevelop bővítmény Name[it]=Estensione KDevelop Name[ja]=KDevelop プラグイン Name[kk]=KDevelop плагині Name[lv]=KDevelop spraudnis Name[nb]=KDevelop programtillegg Name[nds]=KDevelop-Moduul Name[nl]=Plugin van KDevelop Name[pa]=KDevelop ਪਲੱਗਇਨ Name[pl]=Wtyczka do KDevelopa Name[pt]='Plugin' do KDevelop Name[pt_BR]=Extensão do KDevelop Name[ro]=Modul KDevelop Name[ru]=Расширение KDevelop Name[sk]=Plugin KDevelop Name[sl]=Vstavek za KDevelop Name[sv]=KDevelop-insticksprogram Name[tr]=KDevelop Eklentisi Name[ug]=KDevelop قىستۇرمىسى Name[uk]=Додаток KDevelop Name[x-test]=xxKDevelop Pluginxx Name[zh_CN]=KDevelop 插件 Name[zh_TW]=KDevelop 外掛程式 # mandatory, versioning - prevent DLL hell [PropertyDef::X-KDevelop-Version] Type=int # optional, determines whether a plugin is loaded only after # a project is opened, or is a global plugin. # If it is not set, the plugin can only be loaded by the # user or via requesting one of its dependencies # allowed values: Global, Project [PropertyDef::X-KDevelop-Category] Type=QString # mandatory, GUI-Operation Mode, determines whether a plugin # can work without having a mainwindow/partcontroller # running # allowed values: GUI, NoGUI [PropertyDef::X-KDevelop-Mode] Type=QString # optional, Arguments to pass to the plugin [PropertyDef::X-KDevelop-Args] Type=QString # optional, Plugin type, might be set to Kross # to indicate a Kross plugin [PropertyDef::X-KDevelop-PluginType] Type=QString # optional, Interfaces that a plugin implements # usually values start with org.kdevelop [PropertyDef::X-KDevelop-Interfaces] Type=QStringList # optional, interfaces that this plugin depends # on [PropertyDef::X-KDevelop-IRequired] Type=QStringList # optional, interfaces that this plugin can use, # but the plugin still works if the interfaces are # not available. [PropertyDef::X-KDevelop-IOptional] Type=QStringList # optional, mimetypes supported by a language plugin [PropertyDef::X-KDevelop-SupportedMimeTypes] Type=QStringList # optional, language supported by a language plugin [PropertyDef::X-KDevelop-Language] Type=QString -# optional, defines wether the plugin can be disabled +# optional, defines whether the plugin can be disabled # by the user. Possible values are "AlwaysOn" and "UserSelectable". # If the property is missing then UserSelectable is assumed [PropertyDef::X-KDevelop-LoadMode] Type=QString # optional, list of filters for "projectfiles" for the project plugin # For example: Makefile,Makefile.* for Makefile's [PropertyDef::X-KDevelop-ProjectFilesFilter] Type=QStringList # optional, description for the projectfiles filter [PropertyDef::X-KDevelop-ProjectFilesFilterDescription] Type=QString diff --git a/interfaces/launchconfigurationtype.h b/interfaces/launchconfigurationtype.h index c090d632a6..5136c457f6 100644 --- a/interfaces/launchconfigurationtype.h +++ b/interfaces/launchconfigurationtype.h @@ -1,152 +1,152 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_LAUNCHCONFIGURATIONTYPE_H #define KDEVPLATFORM_LAUNCHCONFIGURATIONTYPE_H #include "interfacesexport.h" #include #include class QMenu; class KIcon; class KUrl; class KConfigGroup; namespace KDevelop { class IProject; class ILaunchConfiguration; class ProjectBaseItem; class ILauncher; class LaunchConfigurationPageFactory; /** * Launch configuration types are used to be able to create * new launch configurations. Each launch configuration has a * specific type, which specifies which launchers can be used * for the configuration as well as which config pages are needed * to setup the launch configuration */ class KDEVPLATFORMINTERFACES_EXPORT LaunchConfigurationType : public QObject { Q_OBJECT public: LaunchConfigurationType(); virtual ~LaunchConfigurationType(); /** * Provide a unique identifier for the type * among other things this will be used to create a config group in launch * configurations for the pages of this config type * @returns a unique identifier for this launch configuration type */ virtual QString id() const = 0; /** * Provide a user visible name for the type * @returns a translatable string for the type */ virtual QString name() const = 0; /** * Add @p starter to this configuration type * @param starter the launcher that can start configurations of this type */ void addLauncher( ILauncher* starter ); /** * remove @p starter from this configuration type * @param starter the launcher that should not start configurations of this type */ void removeLauncher( ILauncher* starter ); /** * Access all launchers that are usable with this type * @returns a list of launchers that can be used with configurations of this type */ QList launchers() const; /** * Convenience method to access a launcher given its @p id * @param id the id of the launcher to be found * @returns the launcher with the given id or 0 if there's no such launcher in this configuration type */ ILauncher* launcherForId( const QString& id ); /** * Provide a list of widgets to configure a launch configuration for this type * @returns a list of factories to create config pages from. */ virtual QList configPages() const = 0; /** * Provide an icon for this launch configuration type * @returns an icon to be used for representing launch configurations of this type */ virtual KIcon icon() const = 0; /** - * Check wether this launch configuration type can launch the given project item + * Check whether this launch configuration type can launch the given project item * @param item the project tree item to test * @returns true if this configuration type can launch the given item, false otherwise */ virtual bool canLaunch( KDevelop::ProjectBaseItem* item ) const = 0; /** * Configure the given launch configuration to execute the selected item * @param config the configuration to setup * @param item the item to launch */ virtual void configureLaunchFromItem( KConfigGroup config, KDevelop::ProjectBaseItem* item ) const = 0; /** * Configure the given launch configuration to execute the selected item * @param config the configuration to setup * @param item the item to launch */ virtual void configureLaunchFromCmdLineArguments( KConfigGroup config, const QStringList &args ) const = 0; /** - * Check wether this launch configuration type can launch the given file + * Check whether this launch configuration type can launch the given file * @param file the file to test launchability * @returns true if this configuration type can launch the given file, false otherwise */ virtual bool canLaunch( const KUrl& file ) const = 0; /** * Returns a menu that will be added to the UI where the interface will be * able to add any suggestion it needs, like default targets. */ virtual QMenu* launcherSuggestions() { return 0; } signals: void signalAddLaunchConfiguration(KDevelop::ILaunchConfiguration* launch); private: class LaunchConfigurationTypePrivate* const d; }; } #endif diff --git a/language/backgroundparser/documentchangetracker.h b/language/backgroundparser/documentchangetracker.h index 4ea5eb48e4..ea3e204fa1 100644 --- a/language/backgroundparser/documentchangetracker.h +++ b/language/backgroundparser/documentchangetracker.h @@ -1,270 +1,270 @@ /* * This file is part of KDevelop * * Copyright 2010 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_DOCUMENTCHANGETRACKER_H #define KDEVPLATFORM_DOCUMENTCHANGETRACKER_H #include "../languageexport.h" #include #include #include #include #include #include namespace KTextEditor { class Document; class MovingRange; class MovingInterface; } namespace KDevelop { class DocumentChangeTracker; /** * These objects belongs to the foreground, and thus can only be accessed from background threads if the foreground lock is held. * */ class RevisionLockerAndClearerPrivate; /** * Helper class that locks a revision, and clears it on its destruction within the foreground thread. * Just delete it using deleteLater(). * */ class KDEVPLATFORMLANGUAGE_EXPORT RevisionLockerAndClearer : public KShared { public: typedef KSharedPtr Ptr; ~RevisionLockerAndClearer(); /** * Returns the revision number * */ qint64 revision() const; /** * Whether the revision is still being held. It may have been lost due to document-reloads, * in which case the revision must not be used. * */ bool valid() const; /** * Transform a range from this document revision to the given @p to. * */ RangeInRevision transformToRevision(const RangeInRevision& range, const Ptr& to) const; /** * Transform a cursor from this document revision to the given @p to. * If a zero target revision is given, the transformation is done to the current document revision. * */ CursorInRevision transformToRevision(const CursorInRevision& cursor, const Ptr& to, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from this revision into the current revision. */ SimpleRange transformToCurrentRevision(const RangeInRevision& range) const; /** * Transforms the given cursor from this revision into the current revision. */ SimpleCursor transformToCurrentRevision(const CursorInRevision& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ RangeInRevision transformFromRevision(const RangeInRevision& range, const Ptr& from = Ptr()) const; /** * Transform ranges from the given document revision @p from to the this one. * If a zero @p from revision is given, the transformation is done from the current document revision. * */ CursorInRevision transformFromRevision(const CursorInRevision& cursor, const Ptr& from = Ptr(), KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /** * Transforms the given range from the current revision into this revision. */ RangeInRevision transformFromCurrentRevision(const SimpleRange& range) const; /** * Transforms the given cursor from the current revision into this revision. */ CursorInRevision transformFromCurrentRevision(const SimpleCursor& cursor, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; private: friend class DocumentChangeTracker; RevisionLockerAndClearerPrivate* m_p; }; typedef RevisionLockerAndClearer::Ptr RevisionReference; /** * Internal helper class for RevisionLockerAndClearer * */ class KDEVPLATFORMLANGUAGE_EXPORT RevisionLockerAndClearerPrivate : public QObject { Q_OBJECT public: RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision); ~RevisionLockerAndClearerPrivate(); inline qint64 revision() const { return m_revision; } private: friend class RevisionLockerAndClearer; QPointer m_tracker; qint64 m_revision; }; class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeTracker : public QObject { Q_OBJECT public: DocumentChangeTracker( KTextEditor::Document* document ); virtual ~DocumentChangeTracker(); /** * Completions of the users current edits that are supposed to complete * not-yet-finished statements, like for example for-loops for parsing. * */ virtual QList > completions() const; /** * Resets the tracking to the current revision. * */ virtual void reset(); /** * Returns the document revision at which reset() was called last. * * The revision is being locked by the tracker in MovingInterface, * it will be unlocked as soon as reset() is called, so if you want to use * the revision afterwards, you have to lock it before calling reset. * * zero is returned if the revisions were invalidated after the last call. * */ RevisionReference revisionAtLastReset() const; /** * Returns the document text at the last reset * */ QString textAtLastReset() const; /** * Returns the current revision (which is not locked by the tracker) * */ RevisionReference currentRevision(); /** * Returns the range that was changed since the last reset * */ virtual KTextEditor::Range changedRange() const; /** * Whether the changes that happened since the last reset are significant enough to require an update * */ virtual bool needUpdate() const; /** * Returns the tracked document **/ KTextEditor::Document* document() const; KTextEditor::MovingInterface* documentMovingInterface() const; /** * Returns the revision object which locks the revision representing the on-disk state. * Returns a zero object if the file is not on disk. * */ RevisionReference diskRevision() const; /** * Returns whether the given revision is being current held, so that it can be used * for transformations in MovingInterface * */ bool holdingRevision(qint64 revision) const; /** - * Use this fuction to acquire a revision. As long as the returned object is stored somewhere, + * Use this function to acquire a revision. As long as the returned object is stored somewhere, * the revision can be used for transformations in MovingInterface, and especially for * DocumentChangeTracker::transformBetweenRevisions. * * Returns a zero revision object if the revision could not be acquired (it wasn't held). * */ RevisionReference acquireRevision(qint64 revision); /** * Safely maps the given range between the two given revisions. * The mapping is only done if both the from- and to- revision are held, * else the original range is returned. * * @warning: Make sure that you actually hold the referenced revisions, else no transformation will be done. * @note It is much less error-prone to use RevisionReference->transformToRevision() and RevisionReference->transformFromRevision() directly. * */ RangeInRevision transformBetweenRevisions(RangeInRevision range, qint64 fromRevision, qint64 toRevision) const; CursorInRevision transformBetweenRevisions(CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; SimpleRange transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const; SimpleCursor transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; /// Transform the range from the current revision into the given one RangeInRevision transformToRevision(SimpleRange range, qint64 toRevision) const; /// Transform the cursor from the current revision into the given one CursorInRevision transformToRevision(SimpleCursor cursor, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior = KTextEditor::MovingCursor::StayOnInsert) const; protected: QString m_textAtLastReset; RevisionReference m_revisionAtLastReset; bool m_needUpdate; QString m_currentCleanedInsertion; KTextEditor::Cursor m_lastInsertionPosition; KTextEditor::MovingRange* m_changedRange; KTextEditor::Document* m_document; KTextEditor::MovingInterface* m_moving; KDevelop::IndexedString m_url; void updateChangedRange(KTextEditor::Range changed); public slots: virtual void textInserted( KTextEditor::Document*,KTextEditor::Range ); virtual void textRemoved( KTextEditor::Document* document, KTextEditor::Range oldRange, QString oldText ); virtual void textChanged( KTextEditor::Document* document, KTextEditor::Range oldRange, QString oldText, KTextEditor::Range newRange ); void documentDestroyed( QObject* ); void aboutToInvalidateMovingInterfaceContent ( KTextEditor::Document* document ); void documentSavedOrUploaded(KTextEditor::Document*,bool); private: virtual bool checkMergeTokens(const KTextEditor::Range& range, QString oldText, QString newText); friend class RevisionLockerAndClearerPrivate; void lockRevision(qint64 revision); void unlockRevision(qint64 revision); QMap m_revisionLocks; ILanguageSupport::WhitespaceSensitivity m_whitespaceSensitivity; }; } #endif diff --git a/language/codecompletion/codecompletionworker.h b/language/codecompletion/codecompletionworker.h index ab85868dac..d15899564d 100644 --- a/language/codecompletion/codecompletionworker.h +++ b/language/codecompletion/codecompletionworker.h @@ -1,113 +1,113 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006-2008 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. */ #ifndef KDEVPLATFORM_CODECOMPLETIONWORKER_H #define KDEVPLATFORM_CODECOMPLETIONWORKER_H #include #include #include #include #include "../languageexport.h" #include "../duchain/duchainpointer.h" #include "../codecompletion/codecompletioncontext.h" class QMutex; namespace KTextEditor { class Range; class View; class Cursor; } namespace KDevelop { class CodeCompletion; class CompletionTreeElement; class CodeCompletionModel; class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionWorker : public QObject { Q_OBJECT public: CodeCompletionWorker(CodeCompletionModel* model); virtual ~CodeCompletionWorker(); virtual void abortCurrentCompletion(); void setFullCompletion(bool); bool fullCompletion() const; KDevelop::CodeCompletionModel* model() const; ///When this is called, the result is shown in the completion-list. ///Call this from within your code void foundDeclarations(QList >, KSharedPtr completionContext); Q_SIGNALS: ///Internal connections into the foreground completion model void foundDeclarationsReal(QList >, KSharedPtr completionContext); protected: virtual void computeCompletions(DUContextPointer context, const KTextEditor::Cursor& position, QString followingText, const KTextEditor::Range& contextRange, const QString& contextText); - ///This can be overriden to compute an own grouping in the completion-list. + ///This can be overridden to compute an own grouping in the completion-list. ///The default implementation groups items in a way that improves the efficiency of the completion-model, thus the default-implementation should be preferred. virtual QList > computeGroups(QList items, KSharedPtr completionContext); ///If you don't need to reimplement computeCompletions, you can implement only this. virtual KDevelop::CodeCompletionContext* createCompletionContext(KDevelop::DUContextPointer context, const QString &contextText, const QString &followingText, const CursorInRevision &position) const; ///Override this to change the text-range which is used as context-information for the completion context ///The foreground-lock and a DUChain read lock are held when this is called virtual void updateContextRange(KTextEditor::Range& contextRange, KTextEditor::View* view, DUContextPointer context) const; ///Can be used to retrieve and set the aborting flag(Enabling it is equivalent to caling abortCompletion()) ///Is always reset from within computeCompletions bool& aborting(); ///Emits foundDeclarations() with an empty list. Always call this when you abort the process of computing completions void failed(); public Q_SLOTS: ///Connection from the foreground thread within CodeCompletionModel void computeCompletions(KDevelop::DUContextPointer context, const KTextEditor::Cursor& position, KTextEditor::View* view); ///This can be used to do special processing within the background, completely bypassing the normal computeCompletions(..) etc. system. ///It will be executed within the background when the model emits doSpecialProcessingInBackground virtual void doSpecialProcessing(uint data); private: bool m_hasFoundDeclarations; QMutex* m_mutex; bool m_abort; bool m_fullCompletion; KDevelop::CodeCompletionModel* m_model; }; } #endif // KDEVPLATFORM_CODECOMPLETIONWORKER_H diff --git a/language/codegen/sourcefiletemplate.h b/language/codegen/sourcefiletemplate.h index 4b348de3de..3197c6f006 100644 --- a/language/codegen/sourcefiletemplate.h +++ b/language/codegen/sourcefiletemplate.h @@ -1,312 +1,312 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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_SOURCEFILETEMPLATE_H #define KDEVPLATFORM_SOURCEFILETEMPLATE_H #include #include #include #include "../languageexport.h" class QStringList; class KArchiveDirectory; namespace KDevelop { class TemplateRenderer; /** * Represents a source file template archive * * @section TemplateStructure Template Archive Structure * * Source file templates in KDevPlatform are archive files. * The archive must contain at least one .desktop file with the template's description. * If multiple such files are present, the one with the same base name as the archive itself will be used. * * The description file must contain a @c [General] section with the following keys: * @li @c Name - The user-visible name of this template * @li @c Comment - A short user-visible comment * @li @c Category - The category of this template. It can be nested, in which case levels are separated * with forward slashes. The top-level category is usually the language followed by the framework. * @li @c Type An optional type, which then adds some more default wizard pages and configuration. * Currently supported are @c Class and @c Test. * @li @c Language The @c X-KDevelop-Language id of a language plugin which is then asked for a custom * createClassHelper. * @li @c Files - List of files generated by this template. These are not actual file names, but names * of config groups describing those files. * @li @c OptionsFile (optional) - If the template uses custom configuration options, specify a path to * the options file here. @ref CustomOptions * * For each file name in the @c Files array, TemplateClassGenerator expects a section with the same name. * this section should contain three keys: - * @li @c Name - User-visible name for this file. This will be show the the user in the dialog and can be translated. + * @li @c Name - User-visible name for this file. This will be show the user in the dialog and can be translated. * @li @c File - The input file name in the template archive. The template for this file will be read from here. * @li @c OutputFile - The suggested output file name. This will be renderer as a template, so it can contain variables. * * An example template description is below. It shows all features described above. * * @code * [General] * Name=Example * Comment=Example description for a C++ Class * Category=C++/Basic * Type=Class * Language=C++ * Options=options.kcfg * Files=Header,Implementation * * [Header] * Name=Header * File=class.h * OutputFile={{ name }}.h * * [Implementation] * Name=Implementation * File=class.cpp * OutputFile={{ name }}.cpp * @endcode * * @section CustomOptions * * Templates can expose additional configurations options. * This is done through a file with the same syntax and .kcfg files used by KConfig XT. * The name of this file is specified with the @c OptionsFile key in the [General] section of the description file. * * @note * The options are not parsed by TemplateClassGenerator. * Instead, hasCustomOptions() returns true if the template specifies a custom options file, * and customOptions() returns the full text of that file. * The parsing is done by TemplateOptionsPage. * * The file can (and should) provide a type, name, label and default value for each configuration option. * So far, only variables with types String, Int and Bool are recognized. * Label is the user-visible string that will be shown next to the input field. * The default value will be rendered as a template, so it can contain variables. * * After the user configures the options, they will be available to the template as context variables. * The variable name will match the option name, and its value will be the one set by the user. * * An example options.kcfg file for a class template is included below. * * @code * * * * * * * {{ name }}Private * * * * d * * * * @endcode * * In this example, if the class name is Example, the default value for the private class name will be ExamplePrivate. * After the user accepts the option values, the template will access them through the @c private_class_name * and @c private_member_name variables. * * For more information regarding the XML file format, refer to the KConfig XT documentation. */ class KDEVPLATFORMLANGUAGE_EXPORT SourceFileTemplate { public: /** * Describes a single output file in this template */ struct OutputFile { /** * A unique identifier, equal to the list element in the @c OutputFiles entry */ QString identifier; /** * The name of the input file within the archive, equal to the @c File entry */ QString fileName; /** * User-visible label, equal to the @c Name entry in this file's group in the template description file */ QString label; /** * The default output file name, equal to the @c OutputFile entry * * @note The output name can contain template variables, so make sure to pass it through * TemplateRenderer::render before using it or showing it to the user. */ QString outputName; }; /** * Describes one configuration option */ struct ConfigOption { /** * The type of this option. * * Currently supported are Int, String and Bool */ QString type; /** * A unique identifier for this option */ QString name; /** * User-visible label */ QString label; /** * A context description for the option, shown to the user as a tooltip */ QString context; /** * The default value of this option */ QVariant value; /** * The maximum value of this entry, as a string * * This is applicable only to integers */ QString maxValue; /** * The minimum value of this entry, as a string * * This is applicable only to integers */ QString minValue; }; /** * Creates a SourceFileTemplate representing the template archive with * description file @p templateDescription. * * @param templateDescription template description file, used to find the * archive and read information */ SourceFileTemplate(const QString& templateDescription); /** * Copy constructor * * Creates a SourceFileTemplate representing the same template archive as @p other. * This new objects shares no data with the @p other, so they can be read and deleted independently. * * @param other the template to copy */ SourceFileTemplate(const SourceFileTemplate& other); /** * Creates an invalid SourceFileTemplate */ SourceFileTemplate(); /** * Destroys this SourceFileTemplate */ ~SourceFileTemplate(); SourceFileTemplate& operator=(const SourceFileTemplate& other); /** * @param resourcePrefix ugly ugly parameter required for testing :-/ */ void setTemplateDescription(const QString& templateDescription, const QString& resourcePrefix = QString("kdevfiletemplates")); /** * Returns true if this SourceFileTemplate represents an actual template archive, and false otherwise */ bool isValid() const; /** * The name of this template, corresponds to the @c Name entry in the description file */ QString name() const; /** * The top-level directory in the template archive * * @sa KArchive::directory() */ const KArchiveDirectory* directory() const; /** * The list of all output files in this template */ QList outputFiles() const; /** * @return true if the template uses any custom options, false otherwise **/ bool hasCustomOptions() const; /** * Return the custom options this template exposes **/ QHash > customOptions(TemplateRenderer* renderer) const; /** * @return The type of this template. * * This can be any string, but TemplateClassAssistant only supports @c Class and @c Test so far. */ QString type() const; /** * aA optional @c X-KDevelop-Language by which a language plugin for this template can be found. */ QString languageName() const; /** * The category of this template. */ QStringList category() const; /** * @return the list of base classes specified by this template. * If this is not a class template, or if it specifies no default base classes, an empty list is returned. * * Each element is the full inheritance description, such as "public QObject". */ QStringList defaultBaseClasses() const; private: class SourceFileTemplatePrivate* const d; }; } Q_DECLARE_TYPEINFO(KDevelop::SourceFileTemplate::OutputFile, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::SourceFileTemplate::ConfigOption, Q_MOVABLE_TYPE); #endif // KDEVPLATFORM_SOURCEFILETEMPLATE_H diff --git a/language/codegen/templaterenderer.h b/language/codegen/templaterenderer.h index c6ccd4ffaa..fc339b439c 100644 --- a/language/codegen/templaterenderer.h +++ b/language/codegen/templaterenderer.h @@ -1,208 +1,208 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * 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_TEMPLATERENDERER_H #define KDEVPLATFORM_TEMPLATERENDERER_H #include #include "../languageexport.h" class KUrl; namespace KDevelop { class TemplateEngine; class SourceFileTemplate; class DocumentChangeSet; /** * @brief Convenience class for rendering multiple templates with the same context * * The TemplateRenderer provides easy access to common template operations. * Internally, it encapsulates a Grantlee::Engine and a Grantlee::Context. * * It is used by adding a set of variables, and then renderering a template string * @code * TemplateRenderer renderer; * renderer.addVariable("greeting", "Hello"); * renderer.addVariable("target", "World"); * QString text = renderer.render("{{ greeting }}, {{ target }}!"); * // text == "Hello, World!" * @endcode * * If you wish to include other templates using the Grantlee {% include %} tag, * make sure TemplateRenderer can find those template by using * addTemplateDirectories() and addArchive(). This adds everything in the specified * directories or archive files to the list of files search for inclusion. * * Directories named "kdevcodegen/templates" in the "data" resource type are always included in the search path, - * there is no need to add them explicitely. Additionally, TemplateRenderer adds the "lib" resource directories + * there is no need to add them explicitly. Additionally, TemplateRenderer adds the "lib" resource directories * to the Grantlee plugin search path, so plugins installed there will be available to templates. * **/ class KDEVPLATFORMLANGUAGE_EXPORT TemplateRenderer { public: /** * Policy for working with empty lines **/ enum EmptyLinesPolicy { /** * Keep empty lines as they are in the rendered output. * The output from the template is returned unmodified. */ KeepEmptyLines, /** * If the template output has more than one line, the renderer * performs a smart trim on the rendered output. * @li single empty lines are removed * @li two or more consecutive empty lines are compressed into a single empty line * @li a single empty line is kept at the end */ TrimEmptyLines, /** * Removes all empty lines from the template output, and appends a newline at the end if needed. */ RemoveEmptyLines }; TemplateRenderer(); virtual ~TemplateRenderer(); /** * Adds @p variables to the Grantlee::Context passed to each template. * * If the context already contains a variables with the same name as a key in @p variables, * it is overwritten. * **/ void addVariables(const QVariantHash& variables); /** * Adds variable with name @p name and value @p value to the Grantlee::Context passed to each template. * * If the context already contains a variables with the same @p name, it is overwritten. * **/ void addVariable(const QString& name, const QVariant& value); /** * Returns the current variables defined for this renderer * * @sa addVariable(), addVariables() */ QVariantHash variables() const; /** * @brief Renders a single template * * Any rendering errors are reported by errorString(). * If there were no errors, errorString() will return an empty string. * * @param content the template content * @param name (optional) the name of this template * @return the rendered template **/ QString render(const QString& content, const QString& name = QString()) const; /** * @brief Renders a single template from a file * * Any rendering errors are reported by errorString(). * If there were no errors, errorString() will return an empty string. * * @param url the URL of the file from which to load the template * @param name (optional) the name of this template * @return the rendered template **/ QString renderFile(const KUrl& url, const QString& name = QString()) const; /** * @brief Renders a list of templates * * This is a convenience method, suitable if you have to render a large number of templates * with the same context. * * @param content the template contents * @return the rendered templates **/ QStringList render(const QStringList& contents) const; /** * @brief Sets the policy for empty lines in the rendered output * * The default is KeepEmptyLines, where the template output is return unmodified. * * @param policy policy for empty lines in the rendered output * @sa EmptyLinesPolicy */ void setEmptyLinesPolicy(EmptyLinesPolicy policy); /** * Returns the currently set policy for empty lines in the rendered output * @sa EmptyLinesPolicy, setEmptyLinesPolicy() */ EmptyLinesPolicy emptyLinesPolicy() const; /** * @brief Renders all templates in the archive represented by @p fileTemplate * * Output files are saved to corresponding URLs in @p fileUrls * * For each output file, TemplateRenderer add two variables named @c output_file_x * and @c output_file_x_absolute, where @c x is replaced * with the file name specified in the template description file. * The variable name is entirely lowercase and cleaned by replacing * all non-alphanumerical characters with underscores. * For example, if the file is named "Public Header" in * the description file, the variable will be @c output_file_public_heder. * * As their name suggests, @c output_file_x contains the relative path from baseUrl() to the URL of the * x's output location, while @c output_file_x_absolute contains x's absolute output URL. - * Both are avaliable to templates as strings. + * Both are available to templates as strings. * * @param fileTemplate the source file template to render * @param baseUrl the base URL used for calculating relative output file URLs * @param fileUrls maps output file identifiers to desired destination URLs * @return KDevelop::DocumentChangeSet */ DocumentChangeSet renderFileTemplate(const KDevelop::SourceFileTemplate& fileTemplate, const KUrl& baseUrl, const QHash& fileUrls); /** * Returns the error string from the last call to render(), renderFile() or renderFileTemplate(). * If the last render was successful and produced no errors, this function returns an empty string. * * @return the last error string **/ QString errorString() const; private: class TemplateRendererPrivate* const d; }; } #endif // KDEVPLATFORM_TEMPLATERENDERER_H diff --git a/language/duchain/parsingenvironment.cpp b/language/duchain/parsingenvironment.cpp index 6cd044866d..8789c5486f 100644 --- a/language/duchain/parsingenvironment.cpp +++ b/language/duchain/parsingenvironment.cpp @@ -1,388 +1,388 @@ /* 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 "parsingenvironment.h" #include "topducontext.h" #include "duchainregister.h" #include "topducontextdynamicdata.h" #include "duchain.h" #include "duchainlock.h" #include "topducontextdata.h" #include #include #define ENSURE_READ_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } #define ENSURE_WRITE_LOCKED if(indexedTopContext().isValid()) { ENSURE_CHAIN_READ_LOCKED } namespace KDevelop { StaticParsingEnvironmentData* ParsingEnvironmentFile::m_staticData = 0; #if 0 ///Wrapper class around objects that are managed through the DUChain, and may contain arbitrary objects ///that support duchain-like store (IndexedString, StorableSet, and the likes). The object must not contain pointers ///or other non-persistent data. /// ///The object is stored during the normal duchain storage/cleanup cycles. template struct PersistentDUChainObject { ///@param fileName File-name that will be used to store the data of the object in the duchain directory PersistentDUChainObject(QString fileName) { object = (T*) new char[sizeof(T)]; if(!DUChain::self()->addPersistentObject(object, fileName, sizeof(T))) { //The constructor is called only if the object did not exist yet new (object) T(); } } ~PersistentDUChainObject() { DUChain::self()->unregisterPersistentObject(object); delete[] object; } T* object; }; #endif REGISTER_DUCHAIN_ITEM(ParsingEnvironmentFile); TopDUContext::Features ParsingEnvironmentFile::features() const { ENSURE_READ_LOCKED return d_func()->m_features; } ParsingEnvironment::ParsingEnvironment() { } ParsingEnvironment::~ParsingEnvironment() { } IndexedString ParsingEnvironmentFile::url() const { ENSURE_READ_LOCKED return d_func()->m_url; } bool ParsingEnvironmentFile::needsUpdate(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions.needsUpdate(); } bool ParsingEnvironmentFile::matchEnvironment(const ParsingEnvironment* /*environment*/) const { ENSURE_READ_LOCKED return true; } void ParsingEnvironmentFile::setTopContext(KDevelop::IndexedTopDUContext context) { if(d_func()->m_topContext == context) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_topContext = context; //Enforce an update of the 'features satisfied' caches TopDUContext::Features oldFeatures = features(); setFeatures(TopDUContext::Empty); setFeatures(oldFeatures); } KDevelop::IndexedTopDUContext ParsingEnvironmentFile::indexedTopContext() const { return d_func()->m_topContext; } const ModificationRevisionSet& ParsingEnvironmentFile::allModificationRevisions() const { ENSURE_READ_LOCKED return d_func()->m_allModificationRevisions; } void ParsingEnvironmentFile::addModificationRevisions(const ModificationRevisionSet& revisions) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions += revisions; } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data, const IndexedString& url) : DUChainBase(data) { d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } ParsingEnvironmentFile::ParsingEnvironmentFile(const IndexedString& url) : DUChainBase(*new ParsingEnvironmentFileData()) { d_func_dynamic()->setClassId(this); d_func_dynamic()->m_url = url; d_func_dynamic()->m_modificationTime = ModificationRevision::revisionForFile(url); addModificationRevision(url, d_func_dynamic()->m_modificationTime); Q_ASSERT(d_func()->m_allModificationRevisions.index()); } TopDUContext* ParsingEnvironmentFile::topContext() const { ENSURE_READ_LOCKED return indexedTopContext().data(); } ParsingEnvironmentFile::~ParsingEnvironmentFile() { } ParsingEnvironmentFile::ParsingEnvironmentFile(ParsingEnvironmentFileData& data) : DUChainBase(data) { //If this triggers, the item has most probably not been initialized with the correct constructor that takes an IndexedString. Q_ASSERT(d_func()->m_allModificationRevisions.index()); } int ParsingEnvironment::type() const { return StandardParsingEnvironment; } int ParsingEnvironmentFile::type() const { ENSURE_READ_LOCKED return StandardParsingEnvironment; } bool ParsingEnvironmentFile::isProxyContext() const { ENSURE_READ_LOCKED return d_func()->m_isProxyContext; } void ParsingEnvironmentFile::setIsProxyContext(bool is) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_isProxyContext = is; } QList< KSharedPtr > ParsingEnvironmentFile::imports() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const DUContext::Import& import, topCtx->d_func()->m_importedContexts) imp << import.indexedContext(); }else{ imp = TopDUContextDynamicData::loadImports(top.index()); } QList< KSharedPtr > ret; foreach(const IndexedDUContext &ctx, imp) { KSharedPtr item = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(item) { ret << item; }else{ kDebug() << url().str() << indexedTopContext().index() << ": invalid import" << ctx.topContextIndex(); } } return ret; } QList< KSharedPtr > ParsingEnvironmentFile::importers() const { ENSURE_READ_LOCKED QList imp; IndexedTopDUContext top = indexedTopContext(); if(top.isLoaded()) { TopDUContext* topCtx = top.data(); FOREACH_FUNCTION(const IndexedDUContext& ctx, topCtx->d_func()->m_importers) imp << ctx; }else{ imp = TopDUContextDynamicData::loadImporters(top.index()); } QList< KSharedPtr > ret; foreach(const IndexedDUContext &ctx, imp) { KSharedPtr f = DUChain::self()->environmentFileForDocument(ctx.topContextIndex()); if(f) ret << f; else kDebug() << url().str() << indexedTopContext().index() << ": invalid importer context" << ctx.topContextIndex(); } return ret; } QMutex featureSatisfactionMutex; inline bool satisfied(TopDUContext::Features features, TopDUContext::Features required) { return (features & required) == required; } -///Makes sure the the file has the correct features attached, and if minimumFeatures contains AllDeclarationsContextsAndUsesForRecursive, then also checks all imports. +///Makes sure the file has the correct features attached, and if minimumFeatures contains AllDeclarationsContextsAndUsesForRecursive, then also checks all imports. bool ParsingEnvironmentFile::featuresMatch(TopDUContext::Features minimumFeatures, QSet& checked) const { if(checked.contains(this)) return true; checked.insert(this); TopDUContext::Features localRequired = (TopDUContext::Features) (minimumFeatures | ParseJob::staticMinimumFeatures(url())); //Check other 'local' requirements localRequired = (TopDUContext::Features)(localRequired & (TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST)); if(!satisfied(features(), localRequired)) return false; if(ParseJob::hasStaticMinimumFeatures()) { //Do a manual recursion to check whether any of the relevant contexts has static minimum features set ///@todo Only do this if one of the imports actually has static features attached (by RecursiveImports set intersection) foreach(const ParsingEnvironmentFilePointer &import, imports()) if(!import->featuresMatch(minimumFeatures & TopDUContext::Recursive ? minimumFeatures : ((TopDUContext::Features)0), checked)) return false; }else if(minimumFeatures & TopDUContext::Recursive) { QMutexLocker lock(&featureSatisfactionMutex); TopDUContext::IndexedRecursiveImports recursiveImportIndices = d_func()->m_importsCache; if(recursiveImportIndices.isEmpty()) { //Unfortunately, we have to load the top-context TopDUContext* top = topContext(); if(top) recursiveImportIndices = top->recursiveImportIndices(); } ///@todo Do not create temporary intersected sets //Use the features-cache to efficiently check the recursive satisfaction of the features if(satisfied(minimumFeatures, TopDUContext::AST) && !((m_staticData->ASTSatisfied & recursiveImportIndices) == recursiveImportIndices)) return false; if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsContextsAndUses)) return (m_staticData->allDeclarationsAndUsesSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::AllDeclarationsAndContexts)) return (m_staticData->allDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::VisibleDeclarationsAndContexts)) return (m_staticData->visibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; else if(satisfied(minimumFeatures, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) return (m_staticData->simplifiedVisibleDeclarationsSatisfied & recursiveImportIndices) == recursiveImportIndices; } return true; } void ParsingEnvironmentFile::setFeatures(TopDUContext::Features features) { if(d_func()->m_features == features) return; ENSURE_WRITE_LOCKED d_func_dynamic()->m_features = features; if(indexedTopContext().isValid()) { QMutexLocker lock(&featureSatisfactionMutex); if(!satisfied(features, TopDUContext::SimplifiedVisibleDeclarationsAndContexts)) m_staticData->simplifiedVisibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->simplifiedVisibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::VisibleDeclarationsAndContexts)) m_staticData->visibleDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->visibleDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsAndContexts)) m_staticData->allDeclarationsSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AllDeclarationsContextsAndUses)) m_staticData->allDeclarationsAndUsesSatisfied.remove(indexedTopContext()); else m_staticData->allDeclarationsAndUsesSatisfied.insert(indexedTopContext()); if(!satisfied(features, TopDUContext::AST)) m_staticData->ASTSatisfied.remove(indexedTopContext()); else m_staticData->ASTSatisfied.insert(indexedTopContext()); } } bool ParsingEnvironmentFile::featuresSatisfied(KDevelop::TopDUContext::Features minimumFeatures) const { ENSURE_READ_LOCKED QSet checked; if(minimumFeatures & TopDUContext::ForceUpdate) return false; return featuresMatch(minimumFeatures, checked); } void ParsingEnvironmentFile::clearModificationRevisions() { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.clear(); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } void ParsingEnvironmentFile::addModificationRevision(const IndexedString& url, const ModificationRevision& revision) { ENSURE_WRITE_LOCKED d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); { //Test Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(url, revision); Q_UNUSED( result ); Q_ASSERT( result ); d_func_dynamic()->m_allModificationRevisions.addModificationRevision(url, revision); } } void ParsingEnvironmentFile::setModificationRevision( const KDevelop::ModificationRevision& rev ) { ENSURE_WRITE_LOCKED Q_ASSERT(d_func_dynamic()->m_allModificationRevisions.index()); bool result = d_func_dynamic()->m_allModificationRevisions.removeModificationRevision(d_func()->m_url, d_func()->m_modificationTime); Q_ASSERT( result ); Q_UNUSED( result ); #ifdef LEXERCACHE_DEBUG if(debugging()) { kDebug() << id(this) << "setting modification-revision" << rev.toString(); } #endif d_func_dynamic()->m_modificationTime = rev; #ifdef LEXERCACHE_DEBUG if(debugging()) { kDebug() << id(this) << "new modification-revision" << m_modificationTime; } #endif d_func_dynamic()->m_allModificationRevisions.addModificationRevision(d_func()->m_url, d_func()->m_modificationTime); } KDevelop::ModificationRevision ParsingEnvironmentFile::modificationRevision() const { ENSURE_READ_LOCKED return d_func()->m_modificationTime; } IndexedString ParsingEnvironmentFile::language() const { return d_func()->m_language; } void ParsingEnvironmentFile::setLanguage(IndexedString language) { d_func_dynamic()->m_language = language; } const KDevelop::TopDUContext::IndexedRecursiveImports& ParsingEnvironmentFile::importsCache() const { return d_func()->m_importsCache; } void ParsingEnvironmentFile::setImportsCache(const KDevelop::TopDUContext::IndexedRecursiveImports& importsCache) { d_func_dynamic()->m_importsCache = importsCache; } } //KDevelop diff --git a/language/duchain/tests/test_duchain.cpp b/language/duchain/tests/test_duchain.cpp index 8e9164d1e0..7fa883db8c 100644 --- a/language/duchain/tests/test_duchain.cpp +++ b/language/duchain/tests/test_duchain.cpp @@ -1,810 +1,810 @@ /* * This file is part of KDevelop * * Copyright 2011 Milian Wolff * Copyright 2006 Hamish Rodda * Copyright 2007-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 "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include // needed for std::insert_iterator on windows #include //Extremely slow // #define TEST_NORMAL_IMPORTS QTEST_MAIN(TestDUChain) using namespace KDevelop; using namespace Utils; typedef BasicSetRepository::Index Index; struct Timer { Timer() { m_timer.start(); } qint64 elapsed() { return m_timer.nsecsElapsed(); } QElapsedTimer m_timer; }; void TestDUChain::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); CodeRepresentation::setDiskChangesForbidden(true); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::testStringSets() { const unsigned int setCount = 8; const unsigned int choiceCount = 40; const unsigned int itemCount = 120; BasicSetRepository rep("test repository"); // kDebug() << "Start repository-layout: \n" << rep.dumpDotGraph(); qint64 repositoryTime = 0; //Time spent on repository-operations qint64 genericTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryIntersectionTime = 0; //Time spent on repository-operations qint64 genericIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 qsetIntersectionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryUnionTime = 0; //Time spent on repository-operations qint64 genericUnionTime = 0; //Time spend on equivalent operations with generic sets qint64 repositoryDifferenceTime = 0; //Time spent on repository-operations qint64 genericDifferenceTime = 0; //Time spend on equivalent operations with generic sets Set sets[setCount]; std::set realSets[setCount]; for(unsigned int a = 0; a < setCount; a++) { std::set chosenIndices; unsigned int thisCount = rand() % choiceCount; if(thisCount == 0) thisCount = 1; for(unsigned int b = 0; b < thisCount; b++) { Index choose = (rand() % itemCount) + 1; while(chosenIndices.find(choose) != chosenIndices.end()) { choose = (rand() % itemCount) + 1; } Timer t; chosenIndices.insert(chosenIndices.end(), choose); genericTime += t.elapsed(); } { Timer t; sets[a] = rep.createSet(chosenIndices); repositoryTime += t.elapsed(); } realSets[a] = chosenIndices; std::set tempSet = sets[a].stdSet(); if(tempSet != realSets[a]) { QString dbg = "created set: "; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; QFAIL("sets are not the same!"); } } for(int cycle = 0; cycle < 100; ++cycle) { if(cycle % 10 == 0) qDebug() << "cycle" << cycle; for(unsigned int a = 0; a < setCount; a++) { for(unsigned int b = 0; b < setCount; b++) { /// ----- SUBTRACTION/DIFFERENCE std::set _realDifference; { Timer t; std::set_difference(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realDifference, _realDifference.begin())); genericDifferenceTime += t.elapsed(); } Set _difference; { Timer t; _difference = sets[a] - sets[b]; repositoryDifferenceTime += t.elapsed(); } if(_difference.stdSet() != _realDifference) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _difference.stdSet(); qDebug() << "SET difference:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realDifference.begin(); it != _realDifference.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _difference.dumpDotGraph() << "\n\n"; } QFAIL("difference sets are not the same!"); } /// ------ UNION std::set _realUnion; { Timer t; std::set_union(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realUnion, _realUnion.begin())); genericUnionTime += t.elapsed(); } Set _union; { Timer t; _union = sets[a] + sets[b]; repositoryUnionTime += t.elapsed(); } if(_union.stdSet() != _realUnion) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _union.stdSet(); qDebug() << "SET union:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realUnion.begin(); it != _realUnion.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _union.dumpDotGraph() << "\n\n"; } QFAIL("union sets are not the same"); } std::set _realIntersection; /// -------- INTERSECTION { Timer t; std::set_intersection(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), realSets[b].end(), std::insert_iterator >(_realIntersection, _realIntersection.begin())); genericIntersectionTime += t.elapsed(); } //Just for fun: Test how fast QSet intersections are QSet first, second; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) { first.insert(*it); } for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) { second.insert(*it); } { Timer t; QSet i = first.intersect(second); qsetIntersectionTime += t.elapsed(); } Set _intersection; { Timer t; _intersection = sets[a] & sets[b]; repositoryIntersectionTime += t.elapsed(); } if(_intersection.stdSet() != _realIntersection) { { qDebug() << "SET a:"; QString dbg = ""; for(std::set::const_iterator it = realSets[a].begin(); it != realSets[a].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; } { qDebug() << "SET b:"; QString dbg = ""; for(std::set::const_iterator it = realSets[b].begin(); it != realSets[b].end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; } { std::set tempSet = _intersection.stdSet(); qDebug() << "SET intersection:"; QString dbg = "real set: "; for(std::set::const_iterator it = _realIntersection.begin(); it != _realIntersection.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; dbg = "repo. set: "; for(std::set::const_iterator it = tempSet.begin(); it != tempSet.end(); ++it) dbg += QString("%1 ").arg(*it); qDebug() << dbg; qDebug() << "DOT-Graph:\n\n" << _intersection.dumpDotGraph() << "\n\n"; } QFAIL("intersection sets are not the same"); } } } qDebug() << "cycle " << cycle; qDebug() << "ns needed for set-building: repository-set: " << float(repositoryTime) << " generic-set: " << float(genericTime); qDebug() << "ns needed for intersection: repository-sets: " << float(repositoryIntersectionTime) << " generic-set: " << float(genericIntersectionTime) << " QSet: " << float(qsetIntersectionTime); qDebug() << "ns needed for union: repository-sets: " << float(repositoryUnionTime) << " generic-set: " << float(genericUnionTime); qDebug() << "ns needed for difference: repository-sets: " << float(repositoryDifferenceTime) << " generic-set: " << float(genericDifferenceTime); } } void TestDUChain::testSymbolTableValid() { DUChainReadLocker lock(DUChain::lock()); PersistentSymbolTable::self().selfAnalysis(); } void TestDUChain::testIndexedStrings() { int testCount = 600000; QHash knownIndices; int a = 0; for(a = 0; a < testCount; ++a) { QString testString; int length = rand() % 10; for(int b = 0; b < length; ++b) testString.append((char)(rand() % 6) + 'a'); QByteArray array = testString.toUtf8(); //kDebug() << "checking with" << testString; //kDebug() << "checking" << a; IndexedString indexed(array.constData(), array.size(), IndexedString::hashString(array.constData(), array.size())); QCOMPARE(indexed.str(), testString); if(knownIndices.contains(testString)) { QCOMPARE(indexed.index(), knownIndices[testString].index()); } else { knownIndices[testString] = indexed; } if(a % (testCount/10) == 0) kDebug() << a << "of" << testCount; } kDebug() << a << "successful tests"; } struct TestContext { TestContext() { static int number = 0; ++number; DUChainWriteLocker lock(DUChain::lock()); m_context = new TopDUContext(IndexedString(QString("/test1/%1").arg(number)), RangeInRevision()); m_normalContext = new DUContext(RangeInRevision(), m_context); DUChain::self()->addDocumentChain(m_context); Q_ASSERT(IndexedDUContext(m_context).context() == m_context); } ~TestContext() { foreach(TestContext* importer, importers) importer->unImport(QList() << this); unImport(imports); DUChainWriteLocker lock(DUChain::lock()); TopDUContextPointer tp(m_context); DUChain::self()->removeDocumentChain(static_cast(m_context)); Q_ASSERT(!tp); } void verify(QList allContexts) { { DUChainReadLocker lock(DUChain::lock()); QCOMPARE(m_context->importedParentContexts().count(), imports.count()); } //Compute a closure of all children, and verify that they are imported. QSet collected; collectImports(collected); collected.remove(this); DUChainReadLocker lock(DUChain::lock()); foreach(TestContext* context, collected) { QVERIFY(m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(m_normalContext->imports(context->m_normalContext)); #endif } //Verify that no other contexts are imported foreach(TestContext* context, allContexts) if(context != this) { QVERIFY(collected.contains(context) || !m_context->imports(context->m_context, CursorInRevision::invalid())); #ifdef TEST_NORMAL_IMPORTS QVERIFY(collected.contains(context) || !m_normalContext->imports(context->m_normalContext, CursorInRevision::invalid())); #endif } } void collectImports(QSet& collected) { if(collected.contains(this)) return; collected.insert(this); foreach(TestContext* context, imports) context->collectImports(collected); } void import(TestContext* ctx) { if(imports.contains(ctx) || ctx == this) return; imports << ctx; ctx->importers << this; DUChainWriteLocker lock(DUChain::lock()); m_context->addImportedParentContext(ctx->m_context); #ifdef TEST_NORMAL_IMPORTS m_normalContext->addImportedParentContext(ctx->m_normalContext); #endif } void unImport(QList ctxList) { QList list; QList normalList; foreach(TestContext* ctx, ctxList) { if(!imports.contains(ctx)) continue; list << ctx->m_context; normalList << ctx->m_normalContext; imports.removeAll(ctx); ctx->importers.removeAll(this); } DUChainWriteLocker lock(DUChain::lock()); m_context->removeImportedParentContexts(list); #ifdef TEST_NORMAL_IMPORTS foreach(DUContext* ctx, normalList) m_normalContext->removeImportedParentContext(ctx); #endif } void clearImports() { { DUChainWriteLocker lock(DUChain::lock()); m_context->clearImportedParentContexts(); m_normalContext->clearImportedParentContexts(); } foreach(TestContext* ctx, imports) { imports.removeAll(ctx); ctx->importers.removeAll(this); } } QList imports; private: TopDUContext* m_context; DUContext* m_normalContext; QList importers; }; void collectReachableNodes(QSet& reachableNodes, uint currentNode) { if(!currentNode) return; reachableNodes.insert(currentNode); const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); collectReachableNodes(reachableNodes, node->leftNode()); collectReachableNodes(reachableNodes, node->rightNode()); } uint collectNaiveNodeCount(uint currentNode) { if(!currentNode) return 0; uint ret = 1; const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); Q_ASSERT(node); ret += collectNaiveNodeCount(node->leftNode()); ret += collectNaiveNodeCount(node->rightNode()); return ret; } void TestDUChain::testImportStructure() { Timer total; kDebug() << "before: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); ///Maintains a naive import-structure along with a real top-context import structure, and allows comparing both. int cycles = 5; //int cycles = 100; //srand(time(NULL)); for(int t = 0; t < cycles; ++t) { QList allContexts; //Create a random structure int contextCount = 50; - int verifyOnceIn = contextCount/*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, becase else the import-structure isn't built on-demand!) + int verifyOnceIn = contextCount/*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, because else the import-structure isn't built on-demand!) int clearOnceIn = contextCount; for(int a = 0; a < contextCount; a++) allContexts << new TestContext(); for(int c = 0; c < cycles; ++c) { //kDebug() << "main-cycle" << t << "sub-cycle" << c; //Add random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int importCount = rand() % 5; //kDebug() << "cnt> " << importCount; for(int i = 0; i < importCount; ++i) { //int importNr = rand() % contextCount; //kDebug() << "nmr > " << importNr; //allContexts[a]->import(allContexts[importNr]); allContexts[a]->import(allContexts[rand() % contextCount]); } for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } //Remove random imports and compare for(int a = 0; a < contextCount; a++) { //Import up to 5 random other contexts into each context int removeCount = rand() % 3; QSet removeImports; for(int i = 0; i < removeCount; ++i) if(allContexts[a]->imports.count()) removeImports.insert(allContexts[a]->imports[rand() % allContexts[a]->imports.count()]); allContexts[a]->unImport(removeImports.toList()); for(int b = 0; b < contextCount; b++) if(rand() % verifyOnceIn == 0) allContexts[b]->verify(allContexts); } for(int a = 0; a < contextCount; a++) { if(rand() % clearOnceIn == 0) { allContexts[a]->clearImports(); allContexts[a]->verify(allContexts); } } } kDebug() << "after: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); for(int a = 0; a < contextCount; ++a) delete allContexts[a]; allContexts.clear(); kDebug() << "after cleanup: " << KDevelop::RecursiveImportRepository::repository()->getDataRepository().statistics().print(); } qDebug() << "total ns needed for import-structure test:" << float(total.elapsed()); } class TestWorker : public QObject { Q_OBJECT public slots: void lockForWrite() { for(int i = 0; i < 10000; ++i) { DUChainWriteLocker lock; } qDebug() << "FINISHED lockForWrite"; } void lockForRead() { for(int i = 0; i < 10000; ++i) { DUChainReadLocker lock; } qDebug() << "FINISHED lockForRead"; } void lockForReadWrite() { for(int i = 0; i < 10000; ++i) { { DUChainReadLocker lock; } { DUChainWriteLocker lock; } } qDebug() << "FINISHED lockForReadWrite"; } static QSharedPointer createWorkerThread(const char* workerSlot) { QThread* thread = new QThread; TestWorker* worker = new TestWorker; connect(thread, SIGNAL(started()), worker, workerSlot); connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater())); worker->moveToThread(thread); return QSharedPointer(thread); } }; class ThreadList : public QVector< QSharedPointer > { public: bool join(int timeout) { qDebug() << "joining" << size() << "threads" << timeout; foreach(const QSharedPointer& thread, *this) { // quit event loop Q_ASSERT(thread->isRunning()); thread->quit(); // wait for finish if (!thread->wait(timeout)) { return false; } Q_ASSERT(thread->isFinished()); } return true; } void start() { foreach(const QSharedPointer& thread, *this) { thread->start(); } } }; void TestDUChain::testLockForWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForWrite())); } threads.start(); QVERIFY(threads.join(1000)); } void TestDUChain::testLockForRead() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForRead())); } threads.start(); QVERIFY(threads.join(1000)); } void TestDUChain::testLockForReadWrite() { ThreadList threads; for(int i = 0; i < 10; ++i) { threads << TestWorker::createWorkerThread(SLOT(lockForReadWrite())); } threads.start(); QVERIFY(threads.join(1000)); } #if 0 ///NOTE: the "unit tests" below are not automated, they - so far - require /// human interpretation which is not useful for a unit test! /// someone should investigate what the expected output should be /// and add proper QCOMPARE/QVERIFY checks accordingly ///FIXME: this needs to be rewritten in order to remove dependencies on formerly run unit tests void TestDUChain::testImportCache() { KDevelop::globalItemRepositoryRegistry().printAllStatistics(); KDevelop::RecursiveImportRepository::repository()->printStatistics(); //Analyze the whole existing import-cache //This is very expensive, since it involves loading all existing top-contexts uint topContextCount = DUChain::self()->newTopContextIndex(); uint analyzedCount = 0; uint totalImportCount = 0; uint naiveNodeCount = 0; QSet reachableNodes; DUChainReadLocker lock(DUChain::lock()); for(uint a = 0; a < topContextCount; ++a) { if(a % qMax(1u, topContextCount / 100) == 0) { kDebug() << "progress:" << (a * 100) / topContextCount; } TopDUContext* context = DUChain::self()->chainForIndex(a); if(context) { TopDUContext::IndexedRecursiveImports imports = context->recursiveImportIndices(); ++analyzedCount; totalImportCount += imports.set().count(); collectReachableNodes(reachableNodes, imports.setIndex()); naiveNodeCount += collectNaiveNodeCount(imports.setIndex()); } } QVERIFY(analyzedCount); kDebug() << "average total count of imports:" << totalImportCount / analyzedCount; kDebug() << "count of reachable nodes:" << reachableNodes.size(); kDebug() << "naive node-count:" << naiveNodeCount << "sharing compression factor:" << ((float)reachableNodes.size()) / ((float)naiveNodeCount); } #endif void TestDUChain::benchCodeModel() { const IndexedString file("testFile"); QVERIFY(!QTypeInfo< KDevelop::CodeModelItem >::isStatic); int i = 0; QBENCHMARK { CodeModel::self().addItem(file, QualifiedIdentifier("testQID" + QString::number(i++)), KDevelop::CodeModelItem::Class); } } void TestDUChain::benchTypeRegistry() { IntegralTypeData data; data.m_dataType = IntegralType::TypeInt; data.typeClassId = IntegralType::Identity; data.inRepository = false; data.m_modifiers = 42; data.m_dynamic = false; data.refCount = 1; IntegralTypeData to; QFETCH(int, func); QBENCHMARK { switch(func) { case 0: TypeSystem::self().dataClassSize(data); break; case 1: TypeSystem::self().dynamicSize(data); break; case 2: TypeSystem::self().create(&data); break; case 3: TypeSystem::self().isFactoryLoaded(data); break; case 4: TypeSystem::self().copy(data, to, !data.m_dynamic); break; case 5: TypeSystem::self().copy(data, to, data.m_dynamic); break; case 6: TypeSystem::self().callDestructor(&data); break; } } } void TestDUChain::benchTypeRegistry_data() { QTest::addColumn("func"); QTest::newRow("dataClassSize") << 0; QTest::newRow("dynamicSize") << 1; QTest::newRow("create") << 2; QTest::newRow("isFactoryLoaded") << 3; QTest::newRow("copy") << 4; QTest::newRow("copyNonDynamic") << 5; QTest::newRow("callDestructor") << 6; } #include "test_duchain.moc" #include "moc_test_duchain.cpp" diff --git a/language/editor/cursorinrevision.h b/language/editor/cursorinrevision.h index 8113578e9d..9a3bb56732 100644 --- a/language/editor/cursorinrevision.h +++ b/language/editor/cursorinrevision.h @@ -1,114 +1,114 @@ /* This file is part of KDevelop Copyright 2010 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_CURSORINREVISION_H #define KDEVPLATFORM_CURSORINREVISION_H #include "../languageexport.h" #include "simplecursor.h" namespace KDevelop { /** * Represents a cursor (line-number and column-number) within a text document. * * In KDevelop, this object is used when referencing a cursor that does _not_ point into the * most most current document revision. Therefore, before applying such a cursor in the text - * documents, it has to be translated into the current document revision explicity, thereby replaying + * documents, it has to be translated into the current document revision explicitly, thereby replaying * eventual changes (see DUChainBase::translate...) */ class KDEVPLATFORMLANGUAGE_EXPORT CursorInRevision { public: int line, column; CursorInRevision() : line(0), column(0) { } CursorInRevision(int _line, int _column) : line(_line), column(_column) { } static CursorInRevision invalid() { return CursorInRevision(-1, -1); } bool isValid() const { return line != -1 || column != -1; } bool operator<(const CursorInRevision& rhs) const { return line < rhs.line || (line == rhs.line && column < rhs.column); } bool operator<=(const CursorInRevision& rhs) const { return line < rhs.line || (line == rhs.line && column <= rhs.column); } bool operator>(const CursorInRevision& rhs) const { return line > rhs.line || (line == rhs.line && column > rhs.column); } bool operator>=(const CursorInRevision& rhs) const { return line > rhs.line || (line == rhs.line && column >= rhs.column); } bool operator ==(const CursorInRevision& rhs) const { return line == rhs.line && column == rhs.column; } bool operator !=(const CursorInRevision& rhs) const { return !(*this == rhs); } CursorInRevision operator +(const CursorInRevision& rhs) const { return CursorInRevision(line + rhs.line, column + rhs.column); } /// @warning Using this is wrong in most cases! If you want /// to transform this cursor to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker SimpleCursor castToSimpleCursor() const { return SimpleCursor(line, column); } /// @warning Using this is wrong in most cases! If you want /// to transform this cursor to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker static CursorInRevision castFromSimpleCursor(const SimpleCursor& cursor) { return CursorInRevision(cursor.line, cursor.column); } /// kDebug() stream operator. Writes this cursor to the debug output in a nicely formatted way. inline friend QDebug operator<< (QDebug s, const CursorInRevision& cursor) { s.nospace() << "(" << cursor.line << ", " << cursor.column << ")"; return s.space(); } }; inline uint qHash(const KDevelop::CursorInRevision& cursor) { return cursor.line * 53 + cursor.column * 47; } } // namespace KDevelop Q_DECLARE_TYPEINFO(KDevelop::CursorInRevision, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::CursorInRevision) #endif diff --git a/language/editor/rangeinrevision.h b/language/editor/rangeinrevision.h index be0051bfc0..8fe63ef9ef 100644 --- a/language/editor/rangeinrevision.h +++ b/language/editor/rangeinrevision.h @@ -1,116 +1,116 @@ /* This file is part of KDevelop Copyright 2010 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_RANGEINREVISION_H #define KDEVPLATFORM_RANGEINREVISION_H #include "../languageexport.h" #include "cursorinrevision.h" #include "simplerange.h" namespace KDevelop { /** * Represents a range (start- and end cursor) within a text document. * * In KDevelop, this object is used when referencing a ranges that do _not_ point into the * most most current document revision. Therefore, before applying such a range in the text - * documents, it has to be translated into the current document revision explicity, thereby replaying + * documents, it has to be translated into the current document revision explicitly, thereby replaying * eventual changes (see DUChainBase::translate...) */ class KDEVPLATFORMLANGUAGE_EXPORT RangeInRevision { public: CursorInRevision start, end; RangeInRevision(const CursorInRevision& _start, const CursorInRevision& _end) : start(_start), end(_end) { } RangeInRevision(const CursorInRevision& _start, int length) : start(_start), end(_start.line, _start.column + length) { } RangeInRevision() { } RangeInRevision(int sLine, int sCol, int eLine, int eCol) : start(sLine, sCol), end(eLine, eCol) { } static RangeInRevision invalid() { return RangeInRevision(-1, -1, -1, -1); } bool isValid() const { return start.column != -1 || start.line != -1 || end.column != -1 || end.line != -1; } bool isEmpty() const { return start == end; } bool contains(const CursorInRevision& position) const { return position >= start && position < end; } bool contains(const RangeInRevision& range) const { return range.start >= start && range.end <= end; } bool operator ==( const RangeInRevision& rhs ) const { return start == rhs.start && end == rhs.end; } bool operator !=( const RangeInRevision& rhs ) const { return !(*this == rhs); } bool operator <( const RangeInRevision& rhs ) const { return start < rhs.start; } /// @warning Using this is wrong in most cases! If you want /// to transform this range to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker SimpleRange castToSimpleRange() const { return SimpleRange(start.castToSimpleCursor(), end.castToSimpleCursor()); } /// @warning Using this is wrong in most cases! If you want /// to transform this range to the current revision, you should do a proper /// mapping instead through @ref KDevelop::DUChainBase or @ref KDevelop::RevisionReference /// or @ref KDevelop::DocumentChangeTracker static RangeInRevision castFromSimpleRange(const SimpleRange& range) { return RangeInRevision(range.start.line, range.start.column, range.end.line, range.end.column); } ///kDebug() stream operator. Writes this range to the debug output in a nicely formatted way. inline friend QDebug operator<< (QDebug s, const RangeInRevision& range) { s.nospace() << '[' << range.start << ", " << range.end << ']'; return s.space(); } }; inline uint qHash(const KDevelop::RangeInRevision& range) { return qHash(range.start) + qHash(range.end)*41; } } // namespace KDevelop Q_DECLARE_TYPEINFO(KDevelop::RangeInRevision, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KDevelop::RangeInRevision) #endif diff --git a/language/highlighting/colorcache.h b/language/highlighting/colorcache.h index fcb79d0c71..ab385baeaa 100644 --- a/language/highlighting/colorcache.h +++ b/language/highlighting/colorcache.h @@ -1,166 +1,166 @@ /* * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_COLORCACHE_H #define KDEVPLATFORM_COLORCACHE_H #include #include #include #include #include "../languageexport.h" #include namespace KTextEditor { class Document; class View; } namespace KDevelop { class CodeHighlightingColors; class IDocument; /** * A singleton which holds the global default colors, adapted to the current color scheme */ class KDEVPLATFORMLANGUAGE_EXPORT ColorCache : public QObject { Q_OBJECT public: ~ColorCache(); /// access the global color cache static ColorCache* self(); /// adapt a given foreground color to the current color scheme /// @p ratio between 0 and 255 where 0 gives @see m_foregroundColor /// and 255 gives @p color /// /// @note if you are looking for a background color, simply setting an alpha /// value should work. QColor blend(QColor color, uchar ratio) const; /// adapt a given background color to the current color scheme /// @p ratio between 0 and 255 where 0 gives @see m_foregroundColor /// and 255 gives @p color /// /// @note if you are looking for a background color, simply setting an alpha /// value should work. QColor blendBackground(QColor color, uchar ratio) const; /// blend a color for local colorization according to the user settings /// @see blend() QColor blendLocalColor(QColor color) const; /// blend a color for global colorization according to the user settings /// @see blend() QColor blendGlobalColor(QColor color) const; /// access the default colors CodeHighlightingColors* defaultColors() const; /// access the generated colors /// @see validColorCount() QColor generatedColor(uint num) const; /// returns the number of valid generated colors /// @see generatedColor() uint validColorCount() const; /// access the foreground color QColor foregroundColor() const; signals: /// will be emitted whenever the colors got changed /// @see update() void colorsGotChanged(); private slots: - /// if neccessary, adapt to the colors of this document + /// if necessary, adapt to the colors of this document void slotDocumentActivated(KDevelop::IDocument*); /// settings got changed, update to the settings of the sender void slotViewSettingsChanged(); /// will regenerate colors from global KDE color scheme void updateColorsFromScheme(); /// will regenerate colors with the proper intensity settings void updateColorsFromSettings(); /// regenerate colors and emits @p colorsGotChanged() /// and finally triggers a rehighlight of the opened documents void updateInternal(); bool tryActiveDocument(); private: ColorCache(QObject *parent = 0); static ColorCache* m_self; /// get @p totalGeneratedColors colors from the color wheel and adapt them to the current color scheme void generateColors(); /// calls @c updateInternal() delayed to prevent double loading of language plugins. void update(); /// try to access the KatePart settings for the given doc or fallback to the global KDE scheme - /// and update the colors if neccessary + /// and update the colors if necessary /// @see generateColors(), updateColorsFromScheme() void updateColorsFromDocument(KTextEditor::Document* doc); /// the default colors for the different types CodeHighlightingColors* m_defaultColors; /// the generated colors QList m_colors; /// Must always be m_colors.count()-1, because the last color must be the fallback text color uint m_validColorCount; /// Maybe make this configurable: An offset where to start stepping through the color wheel uint m_colorOffset; /// the text color for the current color scheme QColor m_foregroundColor; /// the editor background color color for the current color scheme QColor m_backgroundColor; /// How generated colors for local variables should be mixed with the foreground color. /// Between 0 and 255, where 255 means only foreground color, and 0 only the chosen color. uchar m_localColorRatio; /// How global colors (i.e. for types, uses, etc.) should be mixed with the foreground color. /// Between 0 and 255, where 255 means only foreground color, and 0 only the chosen color. uchar m_globalColorRatio; /// The view we are listening to for setting changes. QWeakPointer m_view; }; } #endif // KDEVPLATFORM_COLORCACHE_H // kate: space-indent on; indent-width 2; replace-trailing-space-save on; show-tabs on; tab-indents on; tab-width 2; diff --git a/language/interfaces/ilanguagesupport.h b/language/interfaces/ilanguagesupport.h index 3ab16873df..74892d5aa9 100644 --- a/language/interfaces/ilanguagesupport.h +++ b/language/interfaces/ilanguagesupport.h @@ -1,134 +1,134 @@ /*************************************************************************** * 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_ILANGUAGESUPPORT_H #define KDEVPLATFORM_ILANGUAGESUPPORT_H #include #include "../editor/simplerange.h" #include "../languageexport.h" namespace KDevelop { class IndexedString; class ParseJob; class ILanguage; class TopDUContext; class DocumentRange; class SimpleCursor; class SimpleRange; class ICodeHighlighting; class DocumentChangeTracker; class ICreateClassHelper; class KDEVPLATFORMLANGUAGE_EXPORT ILanguageSupport { public: virtual ~ILanguageSupport() {} /** @return the name of the language.*/ virtual QString name() const = 0; /** @return the parse job that is used by background parser to parse given @p url.*/ virtual ParseJob *createParseJob(const IndexedString &url) = 0; /** @return the language for this support.*/ virtual ILanguage *language(); /** * Only important for languages that can parse multiple different versions of a file, like C++ due to the preprocessor. * The default-implementation for other languages is "return DUChain::chainForDocument(url);" * * @param proxyContext Whether the returned context should be a proxy-contexts. In C++, a proxy-contexts has no direct content. * It mainly just imports an actual content-context, and it holds all the imports. It can also represent * multiple different versions of the same content in the eyes of the preprocessor. Also, a proxy-context may contain the problem- * descriptions of preprocessor problems. * The proxy-context should be preferred whenever the problem-list is required, or for deciding whether a document needs to be updated * (only the proxy-context knows about all the dependencies, since it contains most of the imports) * * @warning The DUChain must be locked before calling this, @see KDevelop::DUChainReadLocker * * @return the standard context used by this language for the given @param url. * */ virtual TopDUContext *standardContext(const KUrl& url, bool proxyContext = false); /** * Should return a code-highlighting instance for this language, or zero. */ virtual ICodeHighlighting* codeHighlighting() const; /** * Should return a document change-tracker for this language that tracks the changes in the given document * */ virtual DocumentChangeTracker* createChangeTrackerForDocument(KTextEditor::Document* document) const; /** * Should return a class creating helper for this language, or zero. * * If zero is returned, a default class helper will be created. * Reimplementing this method is therefore not necessary to have classes created in this language. * */ virtual ICreateClassHelper* createClassHelper() const; /** * The following functions are used to allow navigation-features, tooltips, etc. for non-duchain language objects. * In C++, they are used to allow highlighting and navigation of macro-uses. * */ /**Should return the local range within the given url that belongs to the *special language-object that contains @param position, or (KUrl(), SimpleRange:invalid()) */ virtual SimpleRange specialLanguageObjectRange(const KUrl& url, const SimpleCursor& position); /**Should return the source-range and source-document that the *special language-object that contains @param position refers to, or SimpleRange:invalid(). */ virtual QPair specialLanguageObjectJumpCursor(const KUrl& url, const SimpleCursor& position); /**Should return a navigation-widget for the *special language-object that contains @param position refers, or 0. *If you setProperty("DoNotCloseOnCursorMove", true) on the widget returned, *then the widget will not close when the cursor moves in the document, which *enables you to change the document contents from the widget without immediately closing the widget.*/ virtual QWidget* specialLanguageObjectNavigationWidget(const KUrl& url, const SimpleCursor& position); /**Should return a tiny piece of code which makes it possible for KDevelop to derive the indentation *settings from an automatic source formatter. Example for C++: "class C{\n class D {\n void c() {\n int m;\n }\n }\n};\n" *The sample must be completely unindented (no line must start with leading whitespace), *and it must contain at least 4 indentation levels! *The default implementation returns an empty string.*/ virtual QString indentationSample() const; enum WhitespaceSensitivity { Insensitive = 0, IndentOnly = 1, Sensitive = 2 }; /**Specifies whether this language is sensitive to whitespace changes. * - The default "Insensitive" will only schedule a document for reparsing when * a change in a non-whitespace area happens (non-whitespace chars added or whitespace * added where it was surrounded by characters) - * - "IndentOnly" will additionaly schedule the document for reparsing if a whitespace + * - "IndentOnly" will additionally schedule the document for reparsing if a whitespace * change occurs at the beginning of the line (more exactly, if all characters before the * changed ones are whitespace) * - "Sensitive" will always schedule the document for reparsing, no matter what was changed. */ virtual WhitespaceSensitivity whitespaceSensititivy() const; }; } Q_DECLARE_INTERFACE( KDevelop::ILanguageSupport, "org.kdevelop.ILanguageSupport") #endif diff --git a/plugins/classbrowser/classmodelnode.h b/plugins/classbrowser/classmodelnode.h index 57aeb638ca..ef8392f086 100644 --- a/plugins/classbrowser/classmodelnode.h +++ b/plugins/classbrowser/classmodelnode.h @@ -1,332 +1,332 @@ /* * KDevelop Class Browser * * Copyright 2007-2009 Hamish Rodda * Copyright 2009 Lior Mualem * * 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_CLASSMODELNODE_H #define KDEVPLATFORM_PLUGIN_CLASSMODELNODE_H #include "classmodel.h" #include #include #include #include "classmodelnodescontroller.h" class QTimer; class NodesModelInterface; namespace KDevelop { class ClassDeclaration; class ClassFunctionDeclaration; class ClassMemberDeclaration; class Declaration; } namespace ClassModelNodes { /// Base node class - provides basic functionality. class Node { public: Node(const QString& a_displayName, NodesModelInterface* a_model); virtual ~Node(); public: // Operations /// Clear all the children from the node. void clear(); /// Called by the model to collapse the node and remove sub-items if needed. virtual void collapse() {}; /// Called by the model to expand the node and populate it with sub-nodes if needed. virtual void expand() {}; /// Append a new child node to the list. void addNode(Node* a_child); /// Remove child node from the list and delete it. void removeNode(Node* a_child); /// Remove this node and delete it. void removeSelf() { m_parentNode->removeNode(this); } /// Called once the node has been populated to sort the entire tree / branch. void recursiveSort(); public: // Info retrieval /// Return the parent associated with this node. Node* getParent() const { return m_parentNode; } /// Get my index in the parent node int row(); /// Return the display name for the node. QString displayName() const { return m_displayName; } /// Returns a list of child nodes const QList& getChildren() const { return m_children; } /// Return an icon representation for the node. /// @note It calls the internal getIcon and caches the result. QIcon getCachedIcon(); public: // overridables /// Return a score when sorting the nodes. virtual int getScore() const = 0; /// Return true if the node contains sub-nodes. virtual bool hasChildren() const { return !m_children.empty(); } /// We use this string when sorting items. virtual QString getSortableString() const { return m_displayName; } protected: /// fill a_resultIcon with a display icon for the node. /// @param a_resultIcon returned icon. /// @return true if result was returned. virtual bool getIcon(QIcon& a_resultIcon) = 0; private: Node* m_parentNode; /// Called once the node has been populated to sort the entire tree / branch. void recursiveSortInternal(); protected: typedef QList< Node* > NodesList; NodesList m_children; QString m_displayName; QIcon m_cachedIcon; NodesModelInterface* m_model; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes that generate and populate their child nodes dynamically class DynamicNode : public Node { public: DynamicNode(const QString& a_displayName, NodesModelInterface* a_model); /// Return true if the node was populated already. bool isPopulated() const { return m_populated; } /// Populate the node and mark the flag - called from expand or can be used internally. void performPopulateNode(bool a_forceRepopulate = false); public: // Node overrides. virtual void collapse(); virtual void expand(); virtual bool hasChildren() const; protected: // overridables /// Called by the framework when the node is about to be expanded /// it should be populated with sub-nodes if applicable. virtual void populateNode() {} /// Called after the nodes have been removed. /// It's for derived classes to clean cached data. virtual void nodeCleared() {} private: bool m_populated; /// Clear all the child nodes and mark flag. void performNodeCleanup(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Base class for nodes associated with a @ref KDevelop::QualifiedIdentifier class IdentifierNode : public DynamicNode { public: IdentifierNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model, const QString& a_displayName = QString()); public: /// Returns the qualified identifier for this node by going through the tree const KDevelop::IndexedQualifiedIdentifier& getIdentifier() const { return m_identifier; } public: // Node overrides virtual bool getIcon(QIcon& a_resultIcon); public: // Overridables /// Return the associated declaration /// @note DU CHAIN MUST BE LOCKED FOR READ virtual KDevelop::Declaration* getDeclaration(); private: KDevelop::IndexedQualifiedIdentifier m_identifier; KDevelop::IndexedDeclaration m_indexedDeclaration; KDevelop::DeclarationPointer m_cachedDeclaration; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// A node that represents an enum value. class EnumNode : public IdentifierNode { public: EnumNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides virtual int getScore() const { return 102; } virtual bool getIcon(QIcon& a_resultIcon); virtual void populateNode(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class. class ClassNode : public IdentifierNode, public ClassModelNodeDocumentChangedInterface { public: ClassNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); virtual ~ClassNode(); /// Lookup a contained class and return the related node. - /// @return the the node pointer or 0 if non was found. + /// @return the node pointer or 0 if non was found. ClassNode* findSubClass(const KDevelop::IndexedQualifiedIdentifier& a_id); public: // Node overrides virtual int getScore() const { return 300; } virtual void populateNode(); virtual void nodeCleared(); virtual bool hasChildren() const { return true; } protected: // ClassModelNodeDocumentChangedInterface overrides virtual void documentChanged(const KDevelop::IndexedString& a_file); private: typedef QMap< uint, Node* > SubIdentifiersMap; /// Set of known sub-identifiers. It's used for updates check. SubIdentifiersMap m_subIdentifiers; /// We use this variable to know if we've registered for change notification or not. KDevelop::IndexedString m_cachedUrl; /// Updates the node to reflect changes in the declaration. /// @note DU CHAIN MUST BE LOCKED FOR READ /// @return true if something was updated. bool updateClassDeclarations(); /// Add "Base classes" and "Derived classes" folders, if needed /// @return true if one of the folders was added. bool addBaseAndDerived(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a display for a single class function. class FunctionNode : public IdentifierNode { public: FunctionNode(KDevelop::Declaration* a_decl, NodesModelInterface* a_model); public: // Node overrides virtual int getScore() const { return 400; } virtual QString getSortableString() const { return m_sortableString; } private: QString m_sortableString; }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides display for a single class variable. class ClassMemberNode : public IdentifierNode { public: ClassMemberNode(KDevelop::ClassMemberDeclaration* a_decl, NodesModelInterface* a_model); public: // Node overrides virtual int getScore() const { return 500; } virtual bool getIcon(QIcon& a_resultIcon); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a static list of nodes. class FolderNode : public Node { public: FolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides virtual bool getIcon(QIcon& a_resultIcon); virtual int getScore() const { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Provides a folder node with a dynamic list of nodes. class DynamicFolderNode : public DynamicNode { public: DynamicFolderNode(const QString& a_displayName, NodesModelInterface* a_model); public: // Node overrides virtual bool getIcon(QIcon& a_resultIcon); virtual int getScore() const { return 100; } }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays the base classes for the class it sits in. class BaseClassesFolderNode : public DynamicFolderNode { public: BaseClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides virtual void populateNode(); }; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Special folder - the parent is assumed to be a ClassNode. /// It then displays list of derived classes from the parent class. class DerivedClassesFolderNode : public DynamicFolderNode { public: DerivedClassesFolderNode(NodesModelInterface* a_model); public: // Node overrides virtual void populateNode(); }; } // namespace classModelNodes #endif // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index c6d71c2314..85e90b0718 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1358 +1,1358 @@ /* * 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 #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 static const unsigned int highlightingTimeout = 150; static const float highlightingZDepth = -5000; static const int maxHistoryLength = 30; using KDevelop::ILanguage; using KTextEditor::Attribute; using KTextEditor::View; bool toolTipEnabled = true; // 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; } // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const SimpleCursor& 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(KUrl url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return 0; return contextForHighlightingAt(SimpleCursor(cursor), topContext); } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } virtual Qt::DockWidgetArea defaultPosition() { return Qt::BottomDockWidgetArea; } virtual QString id() const { return "org.kdevelop.ContextBrowser"; } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow( window ); m_browseManager = new BrowseManager(this); connect(ICore::self()->documentController(), SIGNAL(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor)), this, SLOT(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor))); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(KIcon("go-previous")); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton, SIGNAL(clicked(bool)), this, SLOT(historyPrevious())); connect(m_previousMenu, SIGNAL(aboutToShow()), this, SLOT(previousMenuAboutToShow())); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(KIcon("go-next")); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton, SIGNAL(clicked(bool)), this, SLOT(historyNext())); connect(m_nextMenu, SIGNAL(aboutToShow()), this, SLOT(nextMenuAboutToShow())); m_browseButton = new QToolButton(); m_browseButton->setIcon(KIcon("games-hint")); m_browseButton->setToolTip(i18n("Enable/disable source browse mode")); m_browseButton->setWhatsThis(i18n("When this is enabled, you can browse the source-code by clicking in the editor.")); m_browseButton->setCheckable(true); m_browseButton->setFocusPolicy(Qt::NoFocus); connect(m_browseButton, SIGNAL(clicked(bool)), m_browseManager, SLOT(setBrowsing(bool))); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin("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, SIGNAL(startDelayedBrowsing(KTextEditor::View*)), this, SLOT(startDelayedBrowsing(KTextEditor::View*))); connect(m_browseManager, SIGNAL(stopDelayedBrowsing()), this, SLOT(stopDelayedBrowsing())); 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_browseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); m_toolbarWidgetLayout->addWidget(m_nextButton); m_toolbarWidgetLayout->addWidget(m_browseButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), m_outlineLine, SLOT(clear())); connect(ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*))); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = "kdevcontextbrowser.rc" ; KAction* previousContext = actions.addAction("previous_context"); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( KIcon("go-previous-context" ) ); previousContext->setShortcut( Qt::META | Qt::Key_Left ); QObject::connect(previousContext, SIGNAL(triggered(bool)), this, SLOT(previousContextShortcut())); KAction* nextContext = actions.addAction("next_context"); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( KIcon("go-next-context" ) ); nextContext->setShortcut( Qt::META | Qt::Key_Right ); QObject::connect(nextContext, SIGNAL(triggered(bool)), this, SLOT(nextContextShortcut())); KAction* previousUse = actions.addAction("previous_use"); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( KIcon("go-previous-use") ); previousUse->setShortcut( Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, SIGNAL(triggered(bool)), this, SLOT(previousUseShortcut())); KAction* nextUse = actions.addAction("next_use"); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( KIcon("go-next-use") ); nextUse->setShortcut( Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, SIGNAL(triggered(bool)), this, SLOT(nextUseShortcut())); KAction* outline = actions.addAction("outline_line"); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction("find_uses", m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY(ContextBrowserFactory, registerPlugin(); ) K_EXPORT_PLUGIN(ContextBrowserFactory(KAboutData("kdevcontextbrowser","kdevcontextbrowser",ki18n("Context Browser"), "0.1", ki18n("Shows information for the current context"), KAboutData::License_GPL))) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(ContextBrowserFactory::componentData(), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*)) ); connect( core()->languageController()->backgroundParser(), SIGNAL(parseJobFinished(KDevelop::ParseJob*)), this, SLOT(parseJobFinished(KDevelop::ParseJob*))); connect( DUChain::self(), SIGNAL(declarationSelected(KDevelop::DeclarationPointer)), this, SLOT(declarationSelectedInUI(KDevelop::DeclarationPointer)) ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, SIGNAL(timeout()), this, SLOT(updateViews()) ); //Needed global action for the context-menu extensions m_findUses = new KAction(i18n("Find Uses"), this); connect(m_findUses, SIGNAL(triggered(bool)), this, SLOT(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; delete m_browseButton; } 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"); m_findUses->setData(QVariant::fromValue(codeContext->declaration())); 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() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); KDevelop::IndexedDeclaration decl = action->data().value(); showUses(DeclarationPointer(decl.declaration())); } void ContextBrowserPlugin::textHintRequested(const KTextEditor::Cursor& cursor, QString&) { m_mouseHoverCursor = SimpleCursor(cursor); View* view = dynamic_cast(sender()); if(!view) { kWarning() << "could not cast to view"; }else{ m_mouseHoverDocument = view->document()->url(); m_updateViews << view; } m_updateTimer->start(1); // triggers updateViews() if(toolTipEnabled) showToolTip(view, cursor); } void ContextBrowserPlugin::stopDelayedBrowsing() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } } 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; } } // TODO: this is a hack, but Kate does not provide interface for this static int getLineHeight(KTextEditor::View* view, int curLine) { KTextEditor::Cursor c(curLine, 0); int currentHeight = view->cursorToCoordinate(c).y(); c.setLine(curLine + 1); if (view->cursorToCoordinate(c).y() < 0) { c.setLine(curLine - 1); } return std::abs(view->cursorToCoordinate(c).y() - currentHeight); } static QRect getItemBoundingRect(const KUrl& viewUrl, KTextEditor::View* view, KTextEditor::Cursor itemPosition) { DUChainReadLocker lock; KTextEditor::Range itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, SimpleCursor(itemPosition)); QPoint startPoint = view->mapToGlobal(view->cursorToCoordinate(itemRange.start())); QPoint endPoint = view->mapToGlobal(view->cursorToCoordinate(itemRange.end())); endPoint.ry() += getLineHeight(view, itemPosition.line()); return QRect(startPoint, endPoint); } 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 KUrl viewUrl(view->document()->url()); QList languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QWidget* navigationWidget = 0; { DUChainReadLocker lock(DUChain::lock()); foreach( ILanguage* language, languages) { navigationWidget = language->languageSupport()->specialLanguageObjectNavigationWidget(viewUrl, SimpleCursor(position)); if(navigationWidget) break; } if(!navigationWidget) { Declaration* decl = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(viewUrl, SimpleCursor(position)) ); 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; m_currentToolTipDeclaration = IndexedDeclaration(decl); navigationWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } } } 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); tooltip->addExtendRect(getItemBoundingRect(viewUrl, view, position)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); kDebug() << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(hideToolTip()), Qt::UniqueConnection); } connect(view, SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(hideToolTip()), Qt::UniqueConnection); }else{ kDebug() << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = SimpleCursor::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 ) { kDebug() << "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< SimpleRange > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< SimpleRange > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< SimpleRange >::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 SimpleCursor& 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 occurences of the selected string, and also to reduce 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]; KUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view->isActiveView() && activeDoc && activeDoc->textDocument() == view->document())); SimpleCursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = SimpleCursor(view->cursorPosition()); ///Pick a language ILanguage* language = 0; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { kDebug() << "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) SimpleRange specialRange = language->languageSupport()->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->languageSupport()->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { kDebug() << "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 == SimpleCursor(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()); if(core()->documentController()->activeDocument() && core()->documentController()->activeDocument()->textDocument() && core()->documentController()->activeDocument()->textDocument()->activeView()) m_updateViews << core()->documentController()->activeDocument()->textDocument()->activeView(); m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::parseJobFinished(KDevelop::ParseJob* job) { for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == job->document().toUrl()) { if(m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); if(!m_updateViews.contains(it.key())) { kDebug() << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), this, SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*)) ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { m_outlineLine->clear(); if (doc->textDocument() && doc->textDocument()->activeView()) { cursorPositionChanged(doc->textDocument()->activeView(), doc->textDocument()->activeView()->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, KTextEditor::Range range) { m_lastInsertionDocument = doc; m_lastInsertionPos = range.end(); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)) ); ///Just to make sure that multiple connections don't happen connect( v, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)) ); connect( v, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*)) ); disconnect( v->document(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range))); connect( v->document(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range))); disconnect( v, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(selectionChanged(KTextEditor::View*))); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->enableTextHints(highlightingTimeout); connect(v, SIGNAL(needTextHint(KTextEditor::Cursor,QString&)), this, SLOT(textHintRequested(KTextEditor::Cursor,QString&))); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(SimpleCursor cursor) { return KTextEditor::Range(cursor.textCursor(), cursor.textCursor()); } void ContextBrowserPlugin::switchUse(bool forward) { if(core()->documentController()->activeDocument() && core()->documentController()->activeDocument()->textDocument() && core()->documentController()->activeDocument()->textDocument()->activeView()) { KTextEditor::Document* doc = core()->documentController()->activeDocument()->textDocument(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { SimpleCursor cCurrent(doc->activeView()->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) { SimpleCursor jumpTo = target->rangeInCurrentRevision().start; KUrl 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); qSort(useRanges); if(!useRanges.isEmpty()) { KUrl url = top->url().toUrl(); SimpleRange 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); qSort(localUses); 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 kDebug() << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { kDebug() << "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); kDebug() << "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; } KUrl u(decl->url().str()); SimpleRange range = decl->rangeInCurrentRevision(); range.end = range.start; lock.unlock(); core()->documentController()->openDocument(u, range.textRange()); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); KUrl u(nextTop->url().str()); QList nextTopUses = allUses(nextTop, decl, true); qSort(nextTopUses); if(!nextTopUses.isEmpty()) { SimpleRange range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.end = range.start; lock.unlock(); core()->documentController()->openDocument(u, range.textRange()); } return; } }else{ kDebug() << "not found own file in use list"; } }else{ KUrl url(chosen->url().str()); SimpleRange range = chosen->transformFromLocalRevision(localUses[nextUse]); range.end = range.start; lock.unlock(); core()->documentController()->openDocument(url, range.textRange()); 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()) { kDebug() << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, SimpleCursor(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()), SimpleCursor(previousCursor)))); ++m_nextHistoryIndex; } } kDebug() << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { kDebug() << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, SimpleCursor(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()), SimpleCursor(newCursor)))); ++m_nextHistoryIndex; 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(), SIGNAL(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor)), this, SLOT(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor))); ICore::self()->documentController()->openDocument(c.document.toUrl(), c.textCursor()); connect(ICore::self()->documentController(), SIGNAL(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor)), this, SLOT(documentJumpPerformed(KDevelop::IDocument*,KTextEditor::Cursor,KDevelop::IDocument*,KTextEditor::Cursor))); 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 = ""; actionText += " @ "; QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QString("%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, SIGNAL(triggered(bool)), this, SLOT(actionTriggered())); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KDevelop::SimpleCursor& /*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 KDevelop::SimpleCursor& position, bool force) { kDebug() << "updating history"; if(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::setAllowBrowsing(bool allow) { m_browseButton->setChecked(allow); } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { kDebug() << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here 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->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } kDebug() << "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(); // kDebug() << "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) { kWarning() << "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 KDevelop::SimpleCursor& 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.line += context.data()->range().start.line; }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KDevelop::SimpleCursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.line -= context.data()->range().start.line; } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/cvs/DESIGN.txt b/plugins/cvs/DESIGN.txt index 7d7770d955..65c2876c2f 100644 --- a/plugins/cvs/DESIGN.txt +++ b/plugins/cvs/DESIGN.txt @@ -1,59 +1,59 @@ CVS plugin for KDevelop4 ======================== (C) Robert Gruber [rgruber A users!sourceforge!net] The CVS plugin is separated into three part. 1) The CvsPart (cvspart.h) It is the main entry point to the CVS plugin. By implementing the IVersionControl interface it offers a lot of slots that will invoke the different CVS actions. This class serves as a dispatcher. It takes the urls and options for the requested CVS action and passes them to the CvsProxy (#2) to get a CvsJob returned. After starting, the job's result() signal gets connected either directly to CvsMainView (#3) or to a custom output view. 2) CvsProxy (cvsproxy.h) and CvsJob (cvsjob.h) The CvsProxy serves as a single point of entry for creating the CvsJobs. The proxy offers a list of methods for the different supported CVS actions. Each of these methods will return a CvsJob object. So the caller does not have to worry about the whole job and commandline creation. Calling the appropriate method will do all the work and return a CvsJob object ready for starting. 3) The CvsMainView (cvsmainview.h) and custom view (like editorsview.h) The CvsMainView displays output from finished jobs. It holds a KTabWidget to which everbody can add custom output views. For instance when the user requested a "cvs log" action, a new view will be added. This view will then parse the output of the "cvs log" job and display it in a nice way. But it does not make sense to add new views for every cvs action. For example, adding a new view everytime "cvs commit", "cvs edit" or similar actions get executed would be an overkill. Therefore CvsMainView also provides a generic output view that just displays the plain text output from such jobs. Unlike custom views, this - generic view can not be closed and it will simply append the ouput + generic view can not be closed and it will simply append the output from each job that got connected to it. The workflow of the CVS plugin looks like this: 1) The user requests a CVS action 2) The matching slot from CvsPart will be called 3) Optional: The user may be prompted for additional input [ see: CvsPart::commit() ] 4) CvsPart passes all needed options to the CvsProxy by calling it's method for the requested action. 5) CvsProxy assembles the commandline, creates a CvsJob object and returns a pointer to the CvsJobs object 6) CvsPart calls start() for the returned CvsJob object and continues with either #6.1 or #6.2 -6.1) Connectes the job's result() signal to the generic ouput view +6.1) Connectes the job's result() signal to the generic output view from CvsMainView 6.2) Creates a custom view, connects the job's result() signal to it and adds the view to CvsMainView diff --git a/plugins/filetemplates/filetemplatesplugin.cpp b/plugins/filetemplates/filetemplatesplugin.cpp index 5497eee0b8..c34ab4a61f 100644 --- a/plugins/filetemplates/filetemplatesplugin.cpp +++ b/plugins/filetemplates/filetemplatesplugin.cpp @@ -1,294 +1,294 @@ #include "filetemplatesplugin.h" #include "templateclassassistant.h" #include "templatepreviewtoolview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define debug() kDebug(debugArea()) using namespace KDevelop; int debugArea() { static int area = KDebug::registerArea("kdevfiletemplates"); return area; } K_PLUGIN_FACTORY(FileTemplatesFactory, registerPlugin();) K_EXPORT_PLUGIN(FileTemplatesFactory(KAboutData("kdevfiletemplates", "kdevfiletemplates", ki18n("File Templates Configuration"), "0.1", ki18n("Support for managing file templates"), KAboutData::License_GPL))) class TemplatePreviewFactory : public KDevelop::IToolViewFactory { public: TemplatePreviewFactory(FileTemplatesPlugin* plugin) : KDevelop::IToolViewFactory() , m_plugin(plugin) { } virtual QWidget* create(QWidget* parent = 0) { return new TemplatePreviewToolView(m_plugin, parent); } virtual QString id() const { return "org.kdevelop.TemplateFilePreview"; } virtual Qt::DockWidgetArea defaultPosition() { return Qt::RightDockWidgetArea; } private: FileTemplatesPlugin* m_plugin; }; FileTemplatesPlugin::FileTemplatesPlugin(QObject* parent, const QVariantList& args) : IPlugin(FileTemplatesFactory::componentData(), parent) , m_model(0) { Q_UNUSED(args); KDEV_USE_EXTENSION_INTERFACE(ITemplateProvider) setXMLFile("kdevfiletemplates.rc"); KAction* action = actionCollection()->addAction("new_from_template"); action->setText( i18n( "New From Template" ) ); action->setIcon( KIcon( "code-class" ) ); action->setWhatsThis( i18n( "Allows you to create new source code files, such as classes or unit tests, using templates." ) ); action->setStatusTip( i18n( "Create new files from a template" ) ); connect (action, SIGNAL(triggered(bool)), SLOT(createFromTemplate())); m_toolView = new TemplatePreviewFactory(this); core()->uiController()->addToolView(i18n("Template Preview"), m_toolView); } FileTemplatesPlugin::~FileTemplatesPlugin() { } void FileTemplatesPlugin::unload() { core()->uiController()->removeToolView(m_toolView); } ContextMenuExtension FileTemplatesPlugin::contextMenuExtension (Context* context) { ContextMenuExtension ext; KUrl fileUrl; if (context->type() == Context::ProjectItemContext) { ProjectItemContext* projectContext = dynamic_cast(context); QList items = projectContext->items(); if (items.size() != 1) { return ext; } KUrl url; ProjectBaseItem* item = items.first(); if (item->folder()) { url = item->url(); } else if (item->target()) { url = item->parent()->url(); } if (url.isValid()) { KAction* action = new KAction(i18n("Create From Template"), this); action->setIcon(KIcon("code-class")); action->setData(url); connect(action, SIGNAL(triggered(bool)), SLOT(createFromTemplate())); ext.addAction(ContextMenuExtension::FileGroup, action); } if (item->file()) { fileUrl = item->url(); } } else if (context->type() == Context::EditorContext) { KDevelop::EditorContext* editorContext = dynamic_cast(context); fileUrl = editorContext->url(); } if (fileUrl.isValid() && determineTemplateType(fileUrl) != NoTemplate) { KAction* action = new KAction(i18n("Show Template Preview"), this); action->setIcon(KIcon("document-preview")); action->setData(fileUrl); connect(action, SIGNAL(triggered(bool)), SLOT(previewTemplate())); ext.addAction(ContextMenuExtension::ExtensionGroup, action); } return ext; } QString FileTemplatesPlugin::name() const { return i18n("File Templates"); } QIcon FileTemplatesPlugin::icon() const { return KIcon("code-class"); } QAbstractItemModel* FileTemplatesPlugin::templatesModel() { if(!m_model) { m_model = new TemplatesModel("kdevfiletemplates", this); } return m_model; } QString FileTemplatesPlugin::knsConfigurationFile() const { return "kdevfiletemplates.knsrc"; } QStringList FileTemplatesPlugin::supportedMimeTypes() const { QStringList types; types << "application/x-desktop"; types << "application/x-bzip-compressed-tar"; types << "application/zip"; return types; } void FileTemplatesPlugin::reload() { templatesModel(); m_model->refresh(); } void FileTemplatesPlugin::loadTemplate(const QString& fileName) { templatesModel(); m_model->loadTemplateFile(fileName); } void FileTemplatesPlugin::createFromTemplate() { KUrl baseUrl; if (QAction* action = qobject_cast(sender())) { baseUrl = action->data().value(); } if (!baseUrl.isValid()) { // fall-back to currently active document's parent directory IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc && doc->url().isValid()) { baseUrl = doc->url().upUrl(); } } TemplateClassAssistant* assistant = new TemplateClassAssistant(QApplication::activeWindow(), baseUrl); assistant->setAttribute(Qt::WA_DeleteOnClose); assistant->show(); } FileTemplatesPlugin::TemplateType FileTemplatesPlugin::determineTemplateType(const KUrl& url) { QDir dir(url.toLocalFile()); /* * Search for a description file in the url's directory. * If it is not found there, try cascading up a maximum of 5 directories. */ int level = 0; while (dir.cdUp() && level < 5) { QStringList filters; filters << "*.kdevtemplate" << "*.desktop"; foreach (const QString& entry, dir.entryList(filters)) { kDebug() << "Trying entry" << entry; /* * This logic is not perfect, but it works for most cases. * * Project template description files usually have the suffix * ".kdevtemplate", so those are easy to find. For project templates, * all the files in the directory are template files. * * On the other hand, file templates use the generic suffix ".desktop". - * Fortunately, those explicitely list input and output files, so we - * only match the explicitely listed files + * Fortunately, those explicitly list input and output files, so we + * only match the explicitly listed files */ if (entry.endsWith(".kdevtemplate")) { return ProjectTemplate; } KConfig* config = new KConfig(dir.absoluteFilePath(entry), KConfig::SimpleConfig); KConfigGroup group = config->group("General"); kDebug() << "General group keys:" << group.keyList(); if (!group.hasKey("Name") || !group.hasKey("Category")) { continue; } if (group.hasKey("Files")) { kDebug() << "Group has files " << group.readEntry("Files", QStringList()); foreach (const QString& outputFile, group.readEntry("Files", QStringList())) { if (dir.absoluteFilePath(config->group(outputFile).readEntry("File")) == url.toLocalFile()) { return FileTemplate; } } } if (group.hasKey("ShowFilesAfterGeneration")) { return ProjectTemplate; } } ++level; } return NoTemplate; } void FileTemplatesPlugin::previewTemplate() { QAction* action = qobject_cast(sender()); if (!action || !action->data().value().isValid()) { return; } TemplatePreviewToolView* preview = qobject_cast(core()->uiController()->findToolView(i18n("Template Preview"), m_toolView)); if (!preview) { return; } core()->documentController()->activateDocument(core()->documentController()->openDocument(action->data().value())); } diff --git a/plugins/filetemplates/overridespage.h b/plugins/filetemplates/overridespage.h index a460309f5c..db4863a468 100644 --- a/plugins/filetemplates/overridespage.h +++ b/plugins/filetemplates/overridespage.h @@ -1,98 +1,98 @@ /* Copyright 2008 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 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_PLUGIN_OVERRIDESPAGE_H #define KDEVPLATFORM_PLUGIN_OVERRIDESPAGE_H #include #include "language/duchain/declaration.h" class QTreeWidget; class QTreeWidgetItem; namespace KDevelop { /** - * Assistant page for choosing class functions, overriden from base classes. + * Assistant page for choosing class functions, overridden from base classes. */ class OverridesPage : public QWidget { Q_OBJECT public: OverridesPage(QWidget* parent); virtual ~OverridesPage(); /** * Default implementation populates the tree with all virtual functions in the base classes. * Calls @c addPotentialOverride() on each function, where more filtering can be applied. * * @param directBases Declarations of base classes from which the new class inherits directly. - * @param allBases Declarations of all base classes from which functions can be overriden + * @param allBases Declarations of all base classes from which functions can be overridden */ virtual void addBaseClasses(const QList& directBases, const QList& allBases); /** * Add @p childDeclaration as potential override. * * Don't call @c KDevelop::OverridesPage::addPotentialOverride() in overloaded * class to filter a declaration. * * @p classItem The parent class from which @p childDeclaration stems from. * Should be used as parent for the override item. * @p childDeclaration The overridable function. */ virtual void addPotentialOverride(QTreeWidgetItem* classItem, const DeclarationPointer& childDeclaration); /** * Add @p declarations as potential overrides under the category @p category. * * The DUChain must be locked for reading before calling this function * * @param category the user-visible category name - * @param declarations a list of declarations that can be overriden or implemented in the new class + * @param declarations a list of declarations that can be overridden or implemented in the new class */ void addCustomDeclarations(const QString& category, const QList< KDevelop::DeclarationPointer >& declarations); QList selectedOverrides() const; void clear(); QTreeWidget* overrideTree() const; QWidget* extraFunctionsContainer() const; public Q_SLOTS: /** * Selects all functions for overriding */ virtual void selectAll(); /** * Deselects all potential overrides */ virtual void deselectAll(); private: struct OverridesPagePrivate* const d; }; } #endif // KDEVPLATFORM_PLUGIN_OVERRIDESPAGE_H diff --git a/plugins/filetemplates/templateclassassistant.cpp b/plugins/filetemplates/templateclassassistant.cpp index 7b8e6bf6ef..e5136a9365 100644 --- a/plugins/filetemplates/templateclassassistant.cpp +++ b/plugins/filetemplates/templateclassassistant.cpp @@ -1,580 +1,580 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templateclassassistant.h" #include "templateselectionpage.h" #include "templateoptionspage.h" #include "classmemberspage.h" #include "classidentifierpage.h" #include "overridespage.h" #include "licensepage.h" #include "outputpage.h" #include "testcasespage.h" #include "defaultcreateclasshelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define REMOVE_PAGE(name) \ if (d->name##Page) \ { \ removePage(d->name##Page); \ d->name##Page = 0; \ d->name##PageWidget = 0; \ } #define ZERO_PAGE(name) \ d->name##Page = 0; \ d->name##PageWidget = 0; using namespace KDevelop; class KDevelop::TemplateClassAssistantPrivate { public: TemplateClassAssistantPrivate(const KUrl& baseUrl); ~TemplateClassAssistantPrivate(); void addFilesToTarget (const QHash& fileUrls); KPageWidgetItem* templateSelectionPage; KPageWidgetItem* classIdentifierPage; KPageWidgetItem* overridesPage; KPageWidgetItem* membersPage; KPageWidgetItem* testCasesPage; KPageWidgetItem* licensePage; KPageWidgetItem* templateOptionsPage; KPageWidgetItem* outputPage; KPageWidgetItem* dummyPage; TemplateSelectionPage* templateSelectionPageWidget; ClassIdentifierPage* classIdentifierPageWidget; OverridesPage* overridesPageWidget; ClassMembersPage* membersPageWidget; TestCasesPage* testCasesPageWidget; LicensePage* licensePageWidget; TemplateOptionsPage* templateOptionsPageWidget; OutputPage* outputPageWidget; KUrl baseUrl; SourceFileTemplate fileTemplate; ICreateClassHelper* helper; TemplateClassGenerator* generator; TemplateRenderer* renderer; QString type; QVariantHash templateOptions; }; TemplateClassAssistantPrivate::TemplateClassAssistantPrivate(const KUrl& baseUrl) : baseUrl(baseUrl) , helper(0) , generator(0) , renderer(0) { } TemplateClassAssistantPrivate::~TemplateClassAssistantPrivate() { delete helper; if (generator) { delete generator; } else { // if we got a generator, it should keep ownership of the renderer // otherwise, we created a templaterenderer on our own delete renderer; } } void TemplateClassAssistantPrivate::addFilesToTarget (const QHash< QString, KUrl >& fileUrls) { // Add the generated files to a target, if one is found KUrl url = baseUrl; if (!url.isValid()) { // This was probably not launched from the project manager view // Still, we try to find the common URL where the generated files are located if (!fileUrls.isEmpty()) { url = fileUrls.constBegin().value().upUrl(); } } kDebug() << "Searching for targets with URL" << url.prettyUrl(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project || !project->buildSystemManager()) { kDebug() << "No suitable project found"; return; } QList items = project->itemsForUrl(url); if (items.isEmpty()) { kDebug() << "No suitable project items found"; return; } QList targets; ProjectTargetItem* target = 0; foreach (ProjectBaseItem* item, items) { if (ProjectTargetItem* target = item->target()) { targets << target; } } if (targets.isEmpty()) { - // If no target was explicitely found yet, try all the targets in the current folder + // If no target was explicitly found yet, try all the targets in the current folder foreach (ProjectBaseItem* item, items) { targets << item->targetList(); } } if (targets.isEmpty()) { // If still no targets, we traverse the tree up to the first directory with targets ProjectBaseItem* item = items.first()->parent(); while (targets.isEmpty() && item) { targets = item->targetList(); item = item->parent(); } } if (targets.size() == 1) { kDebug() << "Only one candidate target," << targets.first()->text() << ", using it"; target = targets.first(); } else if (targets.size() > 1) { // More than one candidate target, show the chooser dialog QPointer d = new KDialog; QWidget* w = new QWidget(d); w->setLayout(new QVBoxLayout); w->layout()->addWidget(new QLabel(i18n("Choose one target to add the file or cancel if you do not want to do so."))); QListWidget* targetsWidget = new QListWidget(w); targetsWidget->setSelectionMode(QAbstractItemView::SingleSelection); foreach(ProjectTargetItem* target, targets) { targetsWidget->addItem(target->text()); } w->layout()->addWidget(targetsWidget); targetsWidget->setCurrentRow(0); d->setButtons( KDialog::Ok | KDialog::Cancel); d->enableButtonOk(true); d->setMainWidget(w); if(d->exec() == QDialog::Accepted) { if (!targetsWidget->selectedItems().isEmpty()) { target = targets[targetsWidget->currentRow()]; } else { kDebug() << "Did not select anything, not adding to a target"; return; } } else { kDebug() << "Canceled select target dialog, not adding to a target"; return; } } else { // No target, not doing anything kDebug() << "No possible targets for URL" << url; return; } Q_ASSERT(target); QList fileItems; foreach (const KUrl& fileUrl, fileUrls) { foreach (ProjectBaseItem* item, project->itemsForUrl(fileUrl.upUrl())) { if (ProjectFolderItem* folder = item->folder()) { if (ProjectFileItem* file = project->projectFileManager()->addFile(fileUrl, folder)) { fileItems << file; break; } } } } if (!fileItems.isEmpty()) { project->buildSystemManager()->addFilesToTarget(fileItems, target); } } TemplateClassAssistant::TemplateClassAssistant(QWidget* parent, const KUrl& baseUrl) : KAssistantDialog(parent) , d(new TemplateClassAssistantPrivate(baseUrl)) { ZERO_PAGE(templateSelection) ZERO_PAGE(templateOptions) ZERO_PAGE(members) ZERO_PAGE(classIdentifier) ZERO_PAGE(overrides) ZERO_PAGE(license) ZERO_PAGE(output) ZERO_PAGE(testCases) setup(); } TemplateClassAssistant::~TemplateClassAssistant() { delete d; } void TemplateClassAssistant::setup() { if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template in %1", d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template")); } d->templateSelectionPageWidget = new TemplateSelectionPage(this); connect(this, SIGNAL(accepted()), d->templateSelectionPageWidget, SLOT(saveConfig())); d->templateSelectionPage = addPage(d->templateSelectionPageWidget, i18n("Language and Template")); d->templateSelectionPage->setIcon(KIcon("project-development-new-template")); d->dummyPage = addPage(new QWidget(this), QLatin1String("Dummy Page")); showButton(KDialog::Help, false); } void TemplateClassAssistant::templateChosen(const QString& templateDescription) { d->fileTemplate.setTemplateDescription(templateDescription); d->type = d->fileTemplate.type(); d->generator = 0; if (!d->fileTemplate.isValid()) { return; } kDebug() << "Selected template" << templateDescription << "of type" << d->type; removePage(d->dummyPage); if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template %1 in %2", d->fileTemplate.name(), d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template %1", d->fileTemplate.name())); } if (d->type == "Class") { d->classIdentifierPageWidget = new ClassIdentifierPage(this); d->classIdentifierPage = addPage(d->classIdentifierPageWidget, i18n("Class Basics")); d->classIdentifierPage->setIcon(KIcon("classnew")); connect(d->classIdentifierPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->classIdentifierPage, false); d->overridesPageWidget = new OverridesPage(this); d->overridesPage = addPage(d->overridesPageWidget, i18n("Override Methods")); d->overridesPage->setIcon(KIcon("code-class")); setValid(d->overridesPage, true); d->membersPageWidget = new ClassMembersPage(this); d->membersPage = addPage(d->membersPageWidget, i18n("Class Members")); d->membersPage->setIcon(KIcon("field")); setValid(d->membersPage, true); d->helper = 0; QString languageName = d->fileTemplate.languageName(); ILanguage* language = ICore::self()->languageController()->language(languageName); if (language && language->languageSupport()) { d->helper = language->languageSupport()->createClassHelper(); } if (!d->helper) { kDebug() << "No class creation helper for language" << languageName; d->helper = new DefaultCreateClassHelper; } d->generator = d->helper->createGenerator(d->baseUrl); Q_ASSERT(d->generator); d->generator->setTemplateDescription(d->fileTemplate); d->renderer = d->generator->renderer(); } else { if (d->type == "Test") { d->testCasesPageWidget = new TestCasesPage(this); d->testCasesPage = addPage(d->testCasesPageWidget, i18n("Test Cases")); connect(d->testCasesPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->testCasesPage, false); } d->renderer = new TemplateRenderer; d->renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } d->licensePageWidget = new LicensePage(this); d->licensePage = addPage(d->licensePageWidget, i18n("License")); d->licensePage->setIcon(KIcon("text-x-copying")); setValid(d->licensePage, true); d->outputPageWidget = new OutputPage(this); d->outputPageWidget->prepareForm(d->fileTemplate); d->outputPage = addPage(d->outputPageWidget, i18n("Output")); d->outputPage->setIcon(KIcon("document-save")); connect(d->outputPageWidget, SIGNAL(isValid(bool)), SLOT(setCurrentPageValid(bool))); setValid(d->outputPage, false); if (d->fileTemplate.hasCustomOptions()) { kDebug() << "Class generator has custom options"; d->templateOptionsPageWidget = new TemplateOptionsPage(this); d->templateOptionsPage = insertPage(d->outputPage, d->templateOptionsPageWidget, i18n("Template Options")); } setCurrentPage(d->templateSelectionPage); } void TemplateClassAssistant::next() { kDebug() << currentPage()->name() << currentPage()->header(); if (currentPage() == d->templateSelectionPage) { // We have chosen the template // Depending on the template's language, we can now create a helper QString description = d->templateSelectionPageWidget->selectedTemplate(); templateChosen(description); if (!d->fileTemplate.isValid()) { return; } } else if (currentPage() == d->classIdentifierPage) { d->generator->setIdentifier(d->classIdentifierPageWidget->identifier()); foreach (const QString& base, d->classIdentifierPageWidget->inheritanceList()) { d->generator->addBaseClass(base); } } else if (currentPage() == d->overridesPage) { ClassDescription desc = d->generator->description(); desc.methods.clear(); foreach (const DeclarationPointer& declaration, d->overridesPageWidget->selectedOverrides()) { desc.methods << FunctionDescription(declaration); } d->generator->setDescription(desc); } else if (currentPage() == d->membersPage) { ClassDescription desc = d->generator->description(); desc.members = d->membersPageWidget->members(); d->generator->setDescription(desc); } else if (currentPage() == d->licensePage) { if (d->generator) { d->generator->setLicense(d->licensePageWidget->license()); } else { d->renderer->addVariable("license", d->licensePageWidget->license()); } } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { if (d->generator) { d->generator->addVariables(d->templateOptionsPageWidget->templateOptions()); } else { d->renderer->addVariables(d->templateOptionsPageWidget->templateOptions()); } } else if (currentPage() == d->testCasesPage) { d->renderer->addVariable("name", d->testCasesPageWidget->name()); d->renderer->addVariable("testCases", d->testCasesPageWidget->testCases()); } KAssistantDialog::next(); if (currentPage() == d->classIdentifierPage) { d->classIdentifierPageWidget->setInheritanceList(d->fileTemplate.defaultBaseClasses()); } else if (currentPage() == d->membersPage) { d->membersPageWidget->setMembers(d->generator->description().members); } else if (currentPage() == d->overridesPage) { d->overridesPageWidget->clear(); d->overridesPageWidget->addCustomDeclarations(i18n("Default"), d->helper->defaultMethods(d->generator->name())); d->overridesPageWidget->addBaseClasses(d->generator->directBaseClasses(), d->generator->allBaseClasses()); } else if (d->templateOptionsPage && (currentPage() == d->templateOptionsPage)) { d->templateOptionsPageWidget->load(d->fileTemplate, d->renderer); } else if (currentPage() == d->outputPage) { d->outputPageWidget->loadFileTemplate(d->fileTemplate, d->baseUrl, d->renderer); } } void TemplateClassAssistant::back() { KAssistantDialog::back(); if (currentPage() == d->templateSelectionPage) { REMOVE_PAGE(classIdentifier) REMOVE_PAGE(overrides) REMOVE_PAGE(members) REMOVE_PAGE(testCases) REMOVE_PAGE(output) REMOVE_PAGE(templateOptions) REMOVE_PAGE(license) delete d->helper; d->helper = 0; if (d->generator) { delete d->generator; } else { delete d->renderer; } d->generator = 0; d->renderer = 0; if (d->baseUrl.isValid()) { setWindowTitle(i18n("Create Files from Template in %1", d->baseUrl.prettyUrl())); } else { setWindowTitle(i18n("Create Files from Template")); } d->dummyPage = addPage(new QWidget(this), QLatin1String("Dummy Page")); } } void TemplateClassAssistant::accept() { // next() is not called for the last page (when the user clicks Finish), so we have to set output locations here QHash fileUrls = d->outputPageWidget->fileUrls(); QHash filePositions = d->outputPageWidget->filePositions(); DocumentChangeSet changes; if (d->generator) { QHash::const_iterator it = fileUrls.constBegin(); for (; it != fileUrls.constEnd(); ++it) { d->generator->setFileUrl(it.key(), it.value()); d->generator->setFilePosition(it.key(), filePositions.value(it.key())); } d->generator->addVariables(d->templateOptions); changes = d->generator->generate(); } else { changes = d->renderer->renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls); } d->addFilesToTarget(fileUrls); changes.applyAllChanges(); // Open the generated files in the editor foreach (const KUrl& url, fileUrls) { ICore::self()->documentController()->openDocument(url); } KAssistantDialog::accept(); } void TemplateClassAssistant::setCurrentPageValid(bool valid) { setValid(currentPage(), valid); } KUrl TemplateClassAssistant::baseUrl() const { return d->baseUrl; } diff --git a/plugins/filetemplates/templateselectionpage.cpp b/plugins/filetemplates/templateselectionpage.cpp index 9ee0fd00dd..4134ac1210 100644 --- a/plugins/filetemplates/templateselectionpage.cpp +++ b/plugins/filetemplates/templateselectionpage.cpp @@ -1,255 +1,255 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templateselectionpage.h" #include "templateclassassistant.h" #include "templatepreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_templateselection.h" #include #include #include #include #include #include #include using namespace KDevelop; const char* LastUsedTemplateEntry = "LastUsedTemplate"; const char* FileTemplatesGroup = "SourceFileTemplates"; class KDevelop::TemplateSelectionPagePrivate { public: TemplateSelectionPagePrivate(TemplateSelectionPage* page_) : page(page_) {} TemplateSelectionPage* page; Ui::TemplateSelection* ui; QString selectedTemplate; TemplateClassAssistant* assistant; TemplatesModel* model; void currentTemplateChanged(const QModelIndex& index); void getMoreClicked(); void loadFileClicked(); void previewTemplate(const QString& templateFile); }; void TemplateSelectionPagePrivate::currentTemplateChanged(const QModelIndex& index) { // delete preview tabs if (!index.isValid() || index.child(0, 0).isValid()) { // invalid or has child assistant->setValid(assistant->currentPage(), false); ui->previewLabel->setVisible(false); ui->tabWidget->setVisible(false); } else { selectedTemplate = model->data(index, TemplatesModel::DescriptionFileRole).toString(); assistant->setValid(assistant->currentPage(), true); previewTemplate(selectedTemplate); ui->previewLabel->setVisible(true); ui->tabWidget->setVisible(true); ui->previewLabel->setText(i18nc("%1: template comment", "Preview: %1", index.data(TemplatesModel::CommentRole).toString())); } } void TemplateSelectionPagePrivate::previewTemplate(const QString& file) { SourceFileTemplate fileTemplate(file); if (!fileTemplate.isValid() || fileTemplate.outputFiles().isEmpty()) { return; } TemplatePreviewRenderer renderer; renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); KTempDir dir; KUrl base(dir.name()); QHash fileUrls; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { KUrl url(base); url.addPath(renderer.render(out.outputName)); fileUrls.insert(out.identifier, url); } DocumentChangeSet changes = renderer.renderFileTemplate(fileTemplate, base, fileUrls); changes.setActivationPolicy(DocumentChangeSet::DoNotActivate); changes.setUpdateHandling(DocumentChangeSet::NoUpdate); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { return; } int idx = 0; foreach(const SourceFileTemplate::OutputFile& out, fileTemplate.outputFiles()) { TemplatePreview* preview = 0; if (ui->tabWidget->count() > idx) { // reuse existing tab preview = qobject_cast(ui->tabWidget->widget(idx)); ui->tabWidget->setTabText(idx, out.label); Q_ASSERT(preview); } else { // create new tabs on demand preview = new TemplatePreview(page); ui->tabWidget->addTab(preview, out.label); } preview->document()->openUrl(fileUrls.value(out.identifier)); ++idx; } - // remove superflous tabs from last time + // remove superfluous tabs from last time while (ui->tabWidget->count() > fileUrls.size()) { delete ui->tabWidget->widget(fileUrls.size()); } return; } void TemplateSelectionPagePrivate::getMoreClicked() { model->refresh(); } void TemplateSelectionPagePrivate::loadFileClicked() { QString filter = "application/x-desktop application/x-bzip-compressed-tar application/zip"; QString fileName = KFileDialog::getOpenFileName(KUrl("kfiledialog:///kdevclasstemplate"), filter, page); if (!fileName.isEmpty()) { QString destination = model->loadTemplateFile(fileName); QModelIndexList indexes = model->templateIndexes(destination); int n = indexes.size(); if (n > 1) { ui->view->setCurrentIndex(indexes[1]); } } } void TemplateSelectionPage::saveConfig() { KSharedConfig::Ptr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); group.writeEntry(LastUsedTemplateEntry, d->selectedTemplate); group.sync(); } TemplateSelectionPage::TemplateSelectionPage(TemplateClassAssistant* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new TemplateSelectionPagePrivate(this)) { d->assistant = parent; d->ui = new Ui::TemplateSelection; d->ui->setupUi(this); d->model = new TemplatesModel("kdevfiletemplates", this); d->model->refresh(); d->ui->view->setLevels(3); d->ui->view->setHeaderLabels(QStringList() << i18n("Language") << i18n("Framework") << i18n("Template")); d->ui->view->setModel(d->model); connect(d->ui->view, SIGNAL(currentIndexChanged(QModelIndex,QModelIndex)), SLOT(currentTemplateChanged(QModelIndex))); QModelIndex templateIndex = d->model->index(0, 0); while (templateIndex.child(0, 0).isValid()) { templateIndex = templateIndex.child(0, 0); } KSharedConfig::Ptr config; if (IProject* project = ICore::self()->projectController()->findProjectForUrl(d->assistant->baseUrl())) { config = project->projectConfiguration(); } else { config = ICore::self()->activeSession()->config(); } KConfigGroup group(config, FileTemplatesGroup); QString lastTemplate = group.readEntry(LastUsedTemplateEntry); QModelIndexList indexes = d->model->match(d->model->index(0, 0), TemplatesModel::DescriptionFileRole, lastTemplate, 1, Qt::MatchRecursive); if (!indexes.isEmpty()) { templateIndex = indexes.first(); } d->ui->view->setCurrentIndex(templateIndex); KNS3::Button* getMoreButton = new KNS3::Button(i18n("Get More Templates..."), "kdevfiletemplates.knsrc", d->ui->view); connect (getMoreButton, SIGNAL(dialogFinished(KNS3::Entry::List)), SLOT(getMoreClicked())); d->ui->view->addWidget(0, getMoreButton); KPushButton* loadButton = new KPushButton(KIcon("application-x-archive"), i18n("Load Template From File"), d->ui->view); connect (loadButton, SIGNAL(clicked(bool)), SLOT(loadFileClicked())); d->ui->view->addWidget(0, loadButton); d->ui->view->setContentsMargins(0, 0, 0, 0); } TemplateSelectionPage::~TemplateSelectionPage() { delete d->ui; delete d; } QSize TemplateSelectionPage::minimumSizeHint() const { return QSize(400, 600); } QString TemplateSelectionPage::selectedTemplate() const { return d->selectedTemplate; } #include "templateselectionpage.moc" diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index 6c644b22de..36bf2ae77f 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1401 +1,1401 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * 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 . * ***************************************************************************/ #include "gitplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gitclonejob.h" #include #include #include #include "stashmanagerdialog.h" #include #include #include #include #include "gitjob.h" #include "gitmessagehighlighter.h" K_PLUGIN_FACTORY(KDevGitFactory, registerPlugin(); ) K_EXPORT_PLUGIN(KDevGitFactory(KAboutData("kdevgit","kdevgit",ki18n("Git"),"0.1",ki18n("A plugin to support git version control systems"), KAboutData::License_GPL))) using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const KUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir(); static const QString gitDir(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static KUrl::List preventRecursion(const KUrl::List& urls) { KUrl::List ret; foreach(const KUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { KUrl entryUrl = d.absoluteFilePath(entry); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return "^HEAD"; case VcsRevision::Base: return ""; case VcsRevision::Working: return ""; case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return ""; case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && - rev.revisionValue().value()==VcsRevision::Start) //if we want it to the begining just put the revisionInterval + rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const KUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const KUrl::List& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, KDevGitFactory::componentData()), m_oldVersion(false) { if (KStandardDirs::findExe("git").isEmpty()) { m_hasError = true; m_errorDescription = i18n("git is not installed"); return; } KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBasicVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDistributedVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBranchingVersionControl ) m_hasError = false; setObjectName("Git"); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitVersionOutput(KDevelop::DVcsJob*))); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, SIGNAL(dirty(QString)), SLOT(fileChanged(QString))); connect(m_watcher, SIGNAL(created(QString)), SLOT(fileChanged(QString))); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList("list"), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList("-m"), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const KUrl::List& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList("pop"), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { QPointer d = new StashManagerDialog(urlDir(m_urls), this, 0); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QLatin1String("Git"); } KUrl GitPlugin::repositoryRoot(const KUrl& path) { return KUrl(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const KUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); return dir.cd(".git") && dir.exists("HEAD"); } bool GitPlugin::isVersionControlled(const KUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList("--") << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const KUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const KUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const KUrl::List& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const KUrl::List& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitStatusOutput_old(KDevelop::DVcsJob*))); } else { *job << "git" << "status" << "--porcelain"; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitStatusOutput(KDevelop::DVcsJob*))); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const KUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-prefix" << "--no-color" << "--no-ext-diff"; if(srcRevision.revisionType()==VcsRevision::Special && dstRevision.revisionType()==VcsRevision::Special && srcRevision.specialType()==VcsRevision::Base && dstRevision.specialType()==VcsRevision::Working) *job << "HEAD"; else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--" << (recursion == IBasicVersionControl::Recursive ? fileOrDirectory : preventRecursion(fileOrDirectory)); connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitDiffOutput(KDevelop::DVcsJob*))); return job; } VcsJob* GitPlugin::revert(const KUrl::List& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const KUrl::List& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QDir dir = dotGitDirectory(localLocations.front()); DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); KUrl::List files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const KUrl::List& files) { QStringList otherStr = getLsFiles(dir, QStringList() << "--others", KDevelop::OutputJob::Silent); KUrl::List toadd, otherFiles; foreach(const QString& file, otherStr) { KUrl v(dir.absolutePath()); v.addPath(file); otherFiles += v; } //We add the files that are not versioned foreach(const KUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const KUrl::List& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); KUrl::List files_(files); QMutableListIterator i(files_); while (i.hasNext()) { KUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << "--others" << "--" << file.toLocalFile(), KDevelop::OutputJob::Silent); kDebug() << "other files" << otherStr; if(!otherStr.isEmpty()) { //remove files not under version control KUrl::List otherFiles; foreach(const QString &f, otherStr) { otherFiles << KUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } KIO::NetAccess::synchronousRun(KIO::trash(otherFiles), 0); } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that kDebug() << "empty folder, removing" << file; KIO::NetAccess::synchronousRun(KIO::trash(file), 0); //we already deleted it, don't use git rm on it i.remove(); } } } if (files_.isEmpty()) return 0; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const KUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), this, SLOT(parseGitLogOutput(KDevelop::DVcsJob*))); return job; } VcsJob* GitPlugin::log(const KUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QString("-%1").arg(limit); *job << "--" << localLocation; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), this, SLOT(parseGitLogOutput(KDevelop::DVcsJob*))); return job; } KDevelop::VcsJob* GitPlugin::annotate(const KUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), this, SLOT(parseGitBlameOutput(KDevelop::DVcsJob*))); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; VcsAnnotationLine* annotation = 0; QStringList lines = job->output().split('\n'); bool skipNext=false; QMap definedRevisions; for(QStringList::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QString name = it->left(it->indexOf(' ')); QString value = it->right(it->size()-name.size()-1); kDebug() << "last line" << *it; if(name=="author") annotation->setAuthor(value); else if(name=="author-mail") {} //TODO: do smth with the e-mail? else if(name=="author-tz") {} //TODO: does it really matter? else if(name=="author-time") annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name=="summary") annotation->setCommitMessage(value); else if(name.startsWith("committer")) {} //We will just store the authors else if(name=="previous") {} //We don't need that either else if(name=="filename") { skipNext=true; } else if(name=="boundary") { definedRevisions.insert("boundary", VcsAnnotationLine()); } else { QStringList values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name); if(!skipNext) definedRevisions.insert(name, VcsAnnotationLine()); annotation = &definedRevisions[name]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const KUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const KUrl &repository, const QString &branch) { QDir d=urlDir(repository); if(hasModifications(d) && KMessageBox::questionYesNo(0, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const KUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const KUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitCurrentBranch(KDevelop::DVcsJob*))); return job; } VcsJob* GitPlugin::renameBranch(const KUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitCurrentBranch(KDevelop::DVcsJob*))); return job; } VcsJob* GitPlugin::currentBranch(const KUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "rev-parse" << "--abbrev-ref" << "HEAD"; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitCurrentBranch(KDevelop::DVcsJob*))); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); // in detached state, we'd like to return an empty string if (out == "HEAD") { out.clear(); } job->setResults(out); } VcsJob* GitPlugin::branches(const KUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitBranchOutput(KDevelop::DVcsJob*))); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { QStringList branchListDirty = job->output().split('\n', QString::SkipEmptyParts); QStringList branchList; foreach(QString branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains("->")) continue; // Skip entries such as '(no branch)' if (branch.contains("(no branch)")) continue; if (branch.startsWith('*')) branch = branch.right(branch.size()-2); branchList<setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << "--all" << "--pretty" << "--parents"; QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { kDebug() << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // kDebug() << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* kDebug() << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains("Author: ")) ++i; item.setAuthor(commits[i].section("Author: ", 1).trimmed()); // kDebug() << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section("Date: ", 1).trimmed()); // kDebug() << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // kDebug() << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { kDebug()<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } kDebug() << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); kDebug() << parent << " is parent of " << commit; kDebug() << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; kDebug() << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { QStringList gitBranches = runSynchronously(branches(KUrl(repo))).toStringList(); kDebug() << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repo)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // kDebug() << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // kDebug() << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegExp rx_com( "commit \\w{1,40}" ); QStringList lines = job->output().split('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == "Author") { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == "Date") { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1)[0].toAscii()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(" ")) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(KUrl(job->directory().absolutePath()))); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); KUrl d = job->directory().absolutePath(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toAscii()); KUrl url = d; url.addPath(line.right(line.size()-2)); allStatus[url] = status; } QVariantList statuses; QMap< KUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); KUrl workingDir = job->directory().absolutePath(); KUrl dotGit = dotGitDirectory(workingDir).absolutePath();; dotGit.adjustPath(KUrl::AddTrailingSlash); workingDir.adjustPath(KUrl::AddTrailingSlash); QVariantList statuses; KUrl::List processedFiles; foreach(const QString& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QString curr=line.right(line.size()-3); QString state = line.left(2); int arrow = curr.indexOf(" -> "); if(arrow>=0) { VcsStatusInfo status; status.setUrl(KUrl(dotGit, curr.left(arrow))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(KUrl(dotGit, curr)); status.setState(messageToState(state)); processedFiles.append(status.url()); kDebug() << "Checking git status for " << line << curr << messageToState(state); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf("--")+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << "-c" << "--" << paths, OutputJob::Silent); foreach(const QString& file, files) { KUrl fileUrl(workingDir, file); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { QStringList versionString = job->output().trimmed().split(' ').last().split('.'); static const QList minimumVersion = QList() << 1 << 7; kDebug() << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; kWarning() << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QString curr = versionString.takeFirst(); int valcurr = curr.toInt(); m_oldVersion |= valcurr job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(directory), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QString& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == "AA" || msg == "DD") ret = VcsStatusInfo::ItemHasConflicts; else switch(msg[0].toAscii()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg[1] == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: kDebug() << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, SIGNAL(result(KJob*)), SLOT(result(KJob*))); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { m_status=job->error() == 0? JobSucceeded : JobFailed; emitResult(); } VcsJob* GitPlugin::copy(const KUrl& localLocationSrc, const KUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const KUrl& source, const KUrl& destination) { QDir dir = urlDir(source); QStringList otherStr = getLsFiles(dir, QStringList() << "--others" << "--" << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(qVariantFromValue(KUrl(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const KUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, SIGNAL(readyForParsing(KDevelop::DVcsJob*)), SLOT(parseGitRepoLocationOutput(KDevelop::DVcsJob*))); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const KUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const KUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const KUrl::List& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const KUrl::List& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first().toLocalFile()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const KUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(".git/MERGE_MSG")); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { public: GitVcsLocationWidget(QWidget* parent = 0, Qt::WindowFlags f = 0) : StandardVcsLocationWidget(parent, f) {} virtual bool isCorrect() const { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } bool GitPlugin::hasError() const { return m_hasError; } QString GitPlugin::errorDescription() const { return m_errorDescription; } void GitPlugin::registerRepositoryForCurrentBranchChanges(const KUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(".git/HEAD"); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith("HEAD")); KUrl fileUrl(KUrl::fromPath(file)); fileUrl = fileUrl.upUrl(); //SMTH/.git/HEAD -> SMTH/.git/ fileUrl = fileUrl.upUrl(); //SMTH/.git/ -> SMTH/ //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, SLOT(delayedBranchChanged())); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } diff --git a/plugins/git/tests/initTest.cpp b/plugins/git/tests/initTest.cpp index 9e5fec5956..114273f561 100644 --- a/plugins/git/tests/initTest.cpp +++ b/plugins/git/tests/initTest.cpp @@ -1,474 +1,474 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2007 Robert Gruber * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * 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 . * ***************************************************************************/ #include "initTest.h" #include #include #include #include #include #include #include #include #include #include "../gitplugin.h" #define VERIFYJOB(j) \ do { QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == KDevelop::VcsJob::JobSucceeded); } while(0) const QString tempDir = QDir::tempPath(); const QString gitTest_BaseDir(tempDir + "/kdevGit_testdir/"); const QString gitTest_BaseDir2(tempDir + "/kdevGit_testdir2/"); const QString gitRepo(gitTest_BaseDir + ".git"); const QString gitSrcDir(gitTest_BaseDir + "src/"); const QString gitTest_FileName("testfile"); const QString gitTest_FileName2("foo"); const QString gitTest_FileName3("bar"); using namespace KDevelop; void GitInitTest::init() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_plugin = new GitPlugin(TestCore::self()); removeTempDirs(); // Now create the basic directory structure QDir tmpdir(tempDir); tmpdir.mkdir(gitTest_BaseDir); tmpdir.mkdir(gitSrcDir); tmpdir.mkdir(gitTest_BaseDir2); } void GitInitTest::cleanup() { delete m_plugin; TestCore::shutdown(); if (QFileInfo(gitTest_BaseDir).exists()) KIO::NetAccess::del(KUrl(gitTest_BaseDir), 0); if (QFileInfo(gitTest_BaseDir2).exists()) KIO::NetAccess::del(KUrl(gitTest_BaseDir2), 0); } void GitInitTest::repoInit() { kDebug() << "Trying to init repo"; // make job that creates the local repository VcsJob* j = m_plugin->init(KUrl(gitTest_BaseDir)); VERIFYJOB(j); //check if the CVSROOT directory in the new local repository exists now QVERIFY(QFileInfo(gitRepo).exists()); //check if isValidDirectory works QVERIFY(m_plugin->isValidDirectory(KUrl(gitTest_BaseDir))); //and for non-git dir, I hope nobody has /tmp under git QVERIFY(!m_plugin->isValidDirectory(KUrl("/tmp"))); - //we have nothing, so ouput should be empty + //we have nothing, so output should be empty DVcsJob * j2 = m_plugin->gitRevParse(gitRepo, QStringList(QString("--branches"))); QVERIFY(j2); QVERIFY(j2->exec()); QString out = j2->output(); QVERIFY(j2->output().isEmpty()); } void GitInitTest::addFiles() { kDebug() << "Adding files to the repo"; //we start it after repoInit, so we still have empty git repo QFile f(gitTest_BaseDir + gitTest_FileName); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "HELLO WORLD"; } f.close(); f.setFileName(gitTest_BaseDir + gitTest_FileName2); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "No, bar()!"; } f.close(); //test git-status exitCode (see DVcsJob::setExitCode). VcsJob* j = m_plugin->status(KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); // /tmp/kdevGit_testdir/ and testfile j = m_plugin->add(KUrl::List(QString(gitTest_BaseDir + gitTest_FileName))); VERIFYJOB(j); f.setFileName(gitSrcDir + gitTest_FileName3); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "No, foo()! It's bar()!"; } f.close(); //test git-status exitCode again j = m_plugin->status(KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); //repository path without trailing slash and a file in a parent directory // /tmp/repo and /tmp/repo/src/bar j = m_plugin->add(KUrl::List(QStringList(gitSrcDir + gitTest_FileName3))); VERIFYJOB(j); //let's use absolute path, because it's used in ContextMenus j = m_plugin->add(KUrl::List(QStringList(gitTest_BaseDir + gitTest_FileName2))); VERIFYJOB(j); //Now let's create several files and try "git add file1 file2 file3" QStringList files = QStringList() << "file1" << "file2" << "la la"; KUrl::List multipleFiles; foreach(const QString& file, files) { QFile f(gitTest_BaseDir + file); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream input(&f); input << file; f.close(); multipleFiles << KUrl::fromPath(gitTest_BaseDir + file); } j = m_plugin->add(multipleFiles); VERIFYJOB(j); } void GitInitTest::commitFiles() { kDebug() << "Committing..."; //we start it after addFiles, so we just have to commit VcsJob* j = m_plugin->commit(QString("Test commit"), KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); //test git-status exitCode one more time. j = m_plugin->status(KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); - //since we commited the file to the "pure" repository, .git/refs/heads/master should exist + //since we committed the file to the "pure" repository, .git/refs/heads/master should exist //TODO: maybe other method should be used QString headRefName(gitRepo + "/refs/heads/master"); QVERIFY(QFileInfo(headRefName).exists()); //Test the results of the "git add" DVcsJob* jobLs = new DVcsJob(gitTest_BaseDir, m_plugin); *jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD"; if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) { QStringList files = jobLs->output().split('\n'); QVERIFY(files.contains(gitTest_FileName)); QVERIFY(files.contains(gitTest_FileName2)); QVERIFY(files.contains("src/" + gitTest_FileName3)); } QString firstCommit; QFile headRef(headRefName); if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> firstCommit; } headRef.close(); QVERIFY(!firstCommit.isEmpty()); kDebug() << "Committing one more time"; //let's try to change the file and test "git commit -a" QFile f(gitTest_BaseDir + gitTest_FileName); if (f.open(QIODevice::WriteOnly)) { QTextStream input(&f); input << "Just another HELLO WORLD\n"; } f.close(); //add changes j = m_plugin->add(KUrl::List(QStringList(gitTest_BaseDir + gitTest_FileName))); VERIFYJOB(j); j = m_plugin->commit(QString("KDevelop's Test commit2"), KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); QString secondCommit; if (headRef.open(QIODevice::ReadOnly)) { QTextStream output(&headRef); output >> secondCommit; } headRef.close(); QVERIFY(!secondCommit.isEmpty()); QVERIFY(firstCommit != secondCommit); } // void GitInitTest::cloneRepository() // { // kDebug() << "Do not clone people, clone Git repos!"; // // make job that clones the local repository, created in the previous test // DVcsJob* j = m_proxy->createWorkingCopy(KUrl(gitTest_BaseDir), KUrl(gitTest_BaseDir2)); // QVERIFY( j ); // // // try to start the job // QVERIFY( j->exec() ); // // //check if the .git directory in the new local repository exists now // QVERIFY( QFileInfo(QString(gitTest_BaseDir2"kdevGit_testdir/.git/")).exists() ); // } void GitInitTest::testInit() { repoInit(); } void GitInitTest::testAdd() { repoInit(); addFiles(); } void GitInitTest::testCommit() { repoInit(); addFiles(); commitFiles(); } void GitInitTest::testBranch(const QString& newBranch) { //Already tested, so I assume that it works QString oldBranch = runSynchronously(m_plugin->currentBranch(gitTest_BaseDir)).toString(); VcsRevision rev; rev.setRevisionValue(oldBranch, KDevelop::VcsRevision::GlobalNumber); VcsJob* j = m_plugin->branch(KUrl(gitTest_BaseDir), rev, newBranch); VERIFYJOB(j); QVERIFY(runSynchronously(m_plugin->branches(KUrl(gitTest_BaseDir))).toStringList().contains(newBranch)); // switch branch j = m_plugin->switchBranch(KUrl(gitTest_BaseDir), newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(gitTest_BaseDir)).toString(), newBranch); // get into detached head state j = m_plugin->switchBranch(KUrl(gitTest_BaseDir), "HEAD~1"); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(gitTest_BaseDir)).toString(), QString("")); // switch back j = m_plugin->switchBranch(KUrl(gitTest_BaseDir), newBranch); VERIFYJOB(j); QCOMPARE(runSynchronously(m_plugin->currentBranch(gitTest_BaseDir)).toString(), newBranch); j = m_plugin->deleteBranch(KUrl(gitTest_BaseDir), oldBranch); VERIFYJOB(j); QVERIFY(!runSynchronously(m_plugin->branches(KUrl(gitTest_BaseDir))).toStringList().contains(oldBranch)); } void GitInitTest::testBranching() { repoInit(); addFiles(); commitFiles(); VcsJob* j = m_plugin->branches(KUrl(gitTest_BaseDir)); VERIFYJOB(j); QString curBranch = runSynchronously(m_plugin->currentBranch(gitTest_BaseDir)).toString(); QCOMPARE(curBranch, QString("master")); testBranch("new"); testBranch("averylongbranchnamejusttotestlongnames"); testBranch("KDE/4.10"); } void GitInitTest::revHistory() { repoInit(); addFiles(); commitFiles(); QList commits = m_plugin->getAllCommits(gitTest_BaseDir); QVERIFY(!commits.isEmpty()); QStringList logMessages; for (int i = 0; i < commits.count(); ++i) logMessages << commits[i].getLog(); QCOMPARE(commits.count(), 2); QCOMPARE(logMessages[0], QString("KDevelop's Test commit2")); //0 is later than 1! QCOMPARE(logMessages[1], QString("Test commit")); QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1! QVERIFY(!commits[0].getParents().isEmpty()); //initial commit is on the top QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$"))); } void GitInitTest::testAnnotation() { repoInit(); addFiles(); commitFiles(); // called after commitFiles QFile f(gitTest_BaseDir + gitTest_FileName); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); VcsJob* j = m_plugin->commit(QString("KDevelop's Test commit3"), KUrl::List(gitTest_BaseDir)); VERIFYJOB(j); j = m_plugin->annotate(KUrl(gitTest_BaseDir + gitTest_FileName), VcsRevision::createSpecialRevision(VcsRevision::Head)); VERIFYJOB(j); QList results = j->fetchResults().toList(); QCOMPARE(results.size(), 2); QVERIFY(results.at(0).canConvert()); VcsAnnotationLine annotation = results.at(0).value(); QCOMPARE(annotation.lineNumber(), 0); QCOMPARE(annotation.commitMessage(), QString("KDevelop's Test commit2")); QVERIFY(results.at(1).canConvert()); annotation = results.at(1).value(); QCOMPARE(annotation.lineNumber(), 1); QCOMPARE(annotation.commitMessage(), QString("KDevelop's Test commit3")); } void GitInitTest::testRemoveEmptyFolder() { repoInit(); QDir d(gitTest_BaseDir); d.mkdir("emptydir"); VcsJob* j = m_plugin->remove(KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir+"emptydir/"))); if (j) VERIFYJOB(j); QVERIFY(!d.exists("emptydir")); } void GitInitTest::testRemoveEmptyFolderInFolder() { repoInit(); QDir d(gitTest_BaseDir); d.mkdir("dir"); QDir d2(gitTest_BaseDir+"dir"); d2.mkdir("emptydir"); VcsJob* j = m_plugin->remove(KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir+"dir/"))); if (j) VERIFYJOB(j); QVERIFY(!d.exists("dir")); } void GitInitTest::testRemoveUnindexedFile() { repoInit(); QFile f(gitTest_BaseDir + gitTest_FileName); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); VcsJob* j = m_plugin->remove(KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir + gitTest_FileName))); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(gitTest_BaseDir + gitTest_FileName)); } void GitInitTest::testRemoveFolderContainingUnversionedFiles() { repoInit(); QDir d(gitTest_BaseDir); d.mkdir("dir"); { QFile f(gitTest_BaseDir + "dir/foo"); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); } VcsJob* j = m_plugin->add(KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir+"dir"))); VERIFYJOB(j); j = m_plugin->commit("initial commit", KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir))); VERIFYJOB(j); { QFile f(gitTest_BaseDir + "dir/bar"); QVERIFY(f.open(QIODevice::Append)); QTextStream input(&f); input << "An appended line"; f.close(); } j = m_plugin->remove(KUrl::List(KUrl::fromLocalFile(gitTest_BaseDir + "dir"))); if (j) VERIFYJOB(j); QVERIFY(!QFile::exists(gitTest_BaseDir + "dir")); } void GitInitTest::removeTempDirs() { if (QFileInfo(gitTest_BaseDir).exists()) if (!KIO::NetAccess::del(KUrl(gitTest_BaseDir), 0)) qDebug() << "KIO::NetAccess::del(" << gitTest_BaseDir << ") returned false"; if (QFileInfo(gitTest_BaseDir2).exists()) if (!KIO::NetAccess::del(KUrl(gitTest_BaseDir2), 0)) qDebug() << "KIO::NetAccess::del(" << gitTest_BaseDir2 << ") returned false"; } QTEST_KDEMAIN(GitInitTest, GUI) // #include "gittest.moc" diff --git a/plugins/grepview/greputil.h b/plugins/grepview/greputil.h index 19e145c2c3..026cc2f01d 100644 --- a/plugins/grepview/greputil.h +++ b/plugins/grepview/greputil.h @@ -1,26 +1,26 @@ /*************************************************************************** * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // some utility functions used at various places #ifndef KDEVPLATFORM_PLUGIN_GREPUTIL_H #define KDEVPLATFORM_PLUGIN_GREPUTIL_H #include #include class QComboBox; /// Returns the contents of a QComboBox as a QStringList QStringList qCombo2StringList( QComboBox* combo, bool allowEmpty = false ); -/// Replaces each occurence of "%s" in pattern by searchString (and "%%" by "%") +/// Replaces each occurrence of "%s" in pattern by searchString (and "%%" by "%") QString substitudePattern(const QString& pattern, const QString& searchString); #endif diff --git a/plugins/patchreview/libdiff2/diffmodel.h b/plugins/patchreview/libdiff2/diffmodel.h index c1e1e4cfc5..201768ec41 100644 --- a/plugins/patchreview/libdiff2/diffmodel.h +++ b/plugins/patchreview/libdiff2/diffmodel.h @@ -1,155 +1,155 @@ /*************************************************************************** diffmodel.h ----------- begin : Sun Mar 4 2001 Copyright 2001-2004,2009 Otto Bruggeman Copyright 2001-2003 John Firebaugh ****************************************************************************/ /*************************************************************************** ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DIFFMODEL_H #define KDEVPLATFORM_PLUGIN_DIFFMODEL_H #include #include #include "diffhunk.h" #include "kompare.h" #include "diff2export.h" namespace Diff2 { class DiffHunk; class Difference; class DIFF2_EXPORT DiffModel : public QObject { Q_OBJECT public: DiffModel( const QString& srcBaseURL, const QString& destBaseURL ); DiffModel(); ~DiffModel(); private: DiffModel( const DiffModel& ) : QObject() {}; public: int parseDiff( enum Kompare::Format format, const QStringList& list ); QString recreateDiff() const; int hunkCount() const { return m_hunks.count(); } int differenceCount() const { return m_differences.count(); } int appliedCount() const { return m_appliedCount; } DiffHunk* hunkAt( int i ) { return ( m_hunks.at( i ) ); } const Difference* differenceAt( int i ) const { return ( m_differences.at( i ) ); } Difference* differenceAt( int i ) { return ( m_differences.at( i ) ); } DiffHunkList* hunks() { return &m_hunks; } const DiffHunkList* hunks() const { return &m_hunks; } DifferenceList* differences() { return &m_differences; } const DifferenceList* differences() const { return &m_differences; } int findDifference( Difference* diff ) const { return m_differences.indexOf( diff ); } Difference* firstDifference(); Difference* lastDifference(); Difference* prevDifference(); Difference* nextDifference(); const QString source() const { return m_source; } const QString destination() const { return m_destination; } const QString sourceFile() const; const QString destinationFile() const; const QString sourcePath() const; const QString destinationPath() const; const QString sourceTimestamp() const { return m_sourceTimestamp; } const QString destinationTimestamp() const { return m_destinationTimestamp; } const QString sourceRevision() const { return m_sourceRevision; } const QString destinationRevision() const { return m_destinationRevision; } void setSourceFile( QString path ); void setDestinationFile( QString path ); void setSourceTimestamp( QString timestamp ); void setDestinationTimestamp( QString timestamp ); void setSourceRevision( QString revision ); void setDestinationRevision( QString revision ); void addHunk( DiffHunk* hunk ); void addDiff( Difference* diff ); bool hasUnsavedChanges() const; int diffIndex( void ) const { return m_diffIndex; } void setDiffIndex( int diffIndex ) { m_diffIndex = diffIndex; } void applyDifference( bool apply ); void applyAllDifferences( bool apply ); bool setSelectedDifference( Difference* diff ); DiffModel& operator=( const DiffModel& model ); bool operator<( const DiffModel& model ); int localeAwareCompareSource( const DiffModel& model ); bool isBlended() const { return m_blended; } void setBlended( bool blended ) { m_blended = blended; } /** * @p oldlines - lines that were removed. * @p newLines - lines that were inserted. - * @p startPos - number of line at which the change occured + * @p startPos - number of line at which the change occurred */ QPair, QList > linesChanged(const QStringList& oldLines, const QStringList& newLines, int editLineNumber); private: void splitSourceInPathAndFileName(); void splitDestinationInPathAndFileName(); void computeDiffStats(Difference* diff); void processStartMarker(Difference* diff, const QStringList& lines, MarkerListConstIterator& currentMarker, int& currentListLine, bool isSource); private: QString m_source; QString m_destination; QString m_sourcePath; QString m_destinationPath; QString m_sourceFile; QString m_destinationFile; QString m_sourceTimestamp; QString m_destinationTimestamp; QString m_sourceRevision; QString m_destinationRevision; DiffHunkList m_hunks; DifferenceList m_differences; int m_appliedCount; int m_diffIndex; Difference* m_selectedDifference; bool m_blended; private slots: void slotDifferenceApplied( Difference* diff ); }; } // End of namespace Diff2 #endif diff --git a/plugins/patchreview/libdiff2/komparemodellist.cpp b/plugins/patchreview/libdiff2/komparemodellist.cpp index 4d434eb4d4..6512235d5c 100644 --- a/plugins/patchreview/libdiff2/komparemodellist.cpp +++ b/plugins/patchreview/libdiff2/komparemodellist.cpp @@ -1,1473 +1,1473 @@ /*************************************************************************** komparemodellist.cpp -------------------- begin : Tue Jun 26 2001 Copyright 2001-2005,2009 Otto Bruggeman Copyright 2001-2003 John Firebaugh Copyright 2007-2009 Kevin Kofler ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "komparemodellist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "difference.h" #include "diffhunk.h" #include "diffmodel.h" #include "diffmodellist.h" #include "kompareprocess.h" #include "parser.h" using namespace Diff2; KompareModelList::KompareModelList( DiffSettings* diffSettings, QWidget* widgetForKIO, QObject* parent, const char* name ) : QObject( parent ), m_diffProcess( 0 ), m_diffSettings( diffSettings ), m_models( 0 ), m_selectedModel( 0 ), m_selectedDifference( 0 ), m_modelIndex( 0 ), m_info( 0 ), m_textCodec( 0 ), m_widgetForKIO( widgetForKIO ) { kDebug(8101) << "Show me the arguments: " << diffSettings << ", " << widgetForKIO << ", " << parent << ", " << name << endl; // KActionCollection *ac = new KActionCollection; // m_applyDifference = ac->addAction( "difference_apply", this, SLOT(slotActionApplyDifference()) ); // m_applyDifference->setIcon( KIcon("arrow-right") ); // m_applyDifference->setText( i18n("&Apply Difference") ); // m_applyDifference->setShortcut( QKeySequence(Qt::Key_Space) ); // m_unApplyDifference = ac->addAction( "difference_unapply", this, SLOT(slotActionUnApplyDifference()) ); // m_unApplyDifference->setIcon( KIcon("arrow-left") ); // m_unApplyDifference->setText( i18n("Un&apply Difference") ); // m_unApplyDifference->setShortcut( QKeySequence(Qt::Key_Backspace) ); // m_applyAll = ac->addAction( "difference_applyall", this, SLOT(slotActionApplyAllDifferences()) ); // m_applyAll->setIcon( KIcon("arrow-right-double") ); // m_applyAll->setText( i18n("App&ly All") ); // m_applyAll->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_A) ); // m_unapplyAll = ac->addAction( "difference_unapplyall", this, SLOT(slotActionUnapplyAllDifferences()) ); // m_unapplyAll->setIcon( KIcon("arrow-left-double") ); // m_unapplyAll->setText( i18n("&Unapply All") ); // m_unapplyAll->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_U) ); // m_previousFile = ac->addAction( "difference_previousfile", this, SLOT(slotPreviousModel()) ); // m_previousFile->setIcon( KIcon("arrow-up-double") ); // m_previousFile->setText( i18n("P&revious File") ); // m_previousFile->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_PageUp) ); // m_nextFile = ac->addAction( "difference_nextfile", this, SLOT(slotNextModel()) ); // m_nextFile->setIcon( KIcon("arrow-down-double") ); // m_nextFile->setText( i18n("N&ext File") ); // m_nextFile->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_PageDown) ); // m_previousDifference = ac->addAction( "difference_previous", this, SLOT(slotPreviousDifference()) ); // m_previousDifference->setIcon( KIcon("arrow-up") ); // m_previousDifference->setText( i18n("&Previous Difference") ); // m_previousDifference->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Up) ); // m_nextDifference = ac->addAction( "difference_next", this, SLOT(slotNextDifference()) ); // m_nextDifference->setIcon( KIcon("arrow-down") ); // m_nextDifference->setText( i18n("&Next Difference") ); // m_nextDifference->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Down) ); // m_previousDifference->setEnabled( false ); // m_nextDifference->setEnabled( false ); // // m_save = KStandardAction::save( this, SLOT(slotSaveDestination()), ac ); // m_save->setEnabled( false ); updateModelListActions(); } KompareModelList::~KompareModelList() { m_selectedModel = 0; m_selectedDifference = 0; m_info = 0; delete m_models; } bool KompareModelList::isDirectory( const QString& url ) const { QFileInfo fi( url ); if ( fi.isDir() ) return true; else return false; } bool KompareModelList::isDiff( const QString& mimeType ) const { if ( mimeType == "text/x-patch" ) return true; else return false; } bool KompareModelList::compare() { bool result = false; bool sourceIsDirectory = isDirectory( m_info->localSource ); bool destinationIsDirectory = isDirectory( m_info->localDestination ); if ( sourceIsDirectory && destinationIsDirectory ) { m_info->mode = Kompare::ComparingDirs; result = compare(m_info->mode); } else if ( !sourceIsDirectory && !destinationIsDirectory ) { QFile sourceFile( m_info->localSource ); sourceFile.open( QIODevice::ReadOnly ); QString sourceMimeType = ( KMimeType::findByContent( sourceFile.readAll() ) )->name(); sourceFile.close(); kDebug(8101) << "Mimetype source : " << sourceMimeType << endl; QFile destinationFile( m_info->localDestination ); destinationFile.open( QIODevice::ReadOnly ); QString destinationMimeType = ( KMimeType::findByContent( destinationFile.readAll() ) )->name(); destinationFile.close(); kDebug(8101) << "Mimetype destination: " << destinationMimeType << endl; // Not checking if it is a text file/something diff can even compare, we'll let diff handle that if ( !isDiff( sourceMimeType ) && isDiff( destinationMimeType ) ) { kDebug(8101) << "Blending destination into source..." << endl; m_info->mode = Kompare::BlendingFile; result = openFileAndDiff(); } else if ( isDiff( sourceMimeType ) && !isDiff( destinationMimeType ) ) { kDebug(8101) << "Blending source into destination..." << endl; m_info->mode = Kompare::BlendingFile; // Swap source and destination before calling this m_info->swapSourceWithDestination(); // Do we need to notify anyone we swapped source and destination? // No we do not need to notify anyone about swapping source with destination result = openFileAndDiff(); } else { kDebug(8101) << "Comparing source with destination" << endl; m_info->mode = Kompare::ComparingFiles; result = compare(m_info->mode); } } else if ( sourceIsDirectory && !destinationIsDirectory ) { m_info->mode = Kompare::BlendingDir; result = openDirAndDiff(); } else { m_info->mode = Kompare::BlendingDir; // Swap source and destination first in m_info m_info->swapSourceWithDestination(); // Do we need to notify anyone we swapped source and destination? // No we do not need to notify anyone about swapping source with destination result = openDirAndDiff(); } return result; } bool KompareModelList::compare(Kompare::Mode mode) { clear(); // Destroy the old models... m_diffProcess = new KompareProcess( m_diffSettings, Kompare::Custom, m_info->localSource, m_info->localDestination, QString(), mode ); m_diffProcess->setEncoding( m_encoding ); connect( m_diffProcess, SIGNAL(diffHasFinished(bool)), this, SLOT(slotDiffProcessFinished(bool)) ); emit status( Kompare::RunningDiff ); m_diffProcess->start(); return true; } QString lstripSeparators( const QString & from, uint count ) { int position = 0; for ( uint i = 0; i < count; ++i ) { position = from.indexOf('/', position); if ( position == -1 ) { break; } } if ( position == -1 ) { return ""; } else { return from.mid(position); } } void KompareModelList::setDepthAndApplied() { // Splice to avoid calling ~DiffModelList QList splicedModelList(*m_models); foreach(DiffModel* model, splicedModelList) { model->setSourceFile( lstripSeparators(model->source(), m_info->depth) ); model->setDestinationFile( lstripSeparators(model->destination(), m_info->depth) ); model->applyAllDifferences(m_info->applied); } } bool KompareModelList::openFileAndDiff() { clear(); if ( m_info->localDestination.isEmpty() ) return false; if ( parseDiffOutput( readFile( m_info->localDestination ) ) != 0 ) { emit error( i18n( "No models or no differences, this file: %1, is not a valid diff file.", m_info->destination.url() ) ); return false; } setDepthAndApplied(); if ( !blendOriginalIntoModelList( m_info->localSource ) ) { kDebug(8101) << "Oops cant blend original file into modellist : " << m_info->localSource << endl; emit( i18n( "There were problems applying the diff %1 to the file %2.", m_info->destination.url(), m_info->source.url() ) ); return false; } updateModelListActions(); show(); return true; } bool KompareModelList::openDirAndDiff() { clear(); if ( m_info->localDestination.isEmpty() ) return false; if ( parseDiffOutput( readFile( m_info->localDestination ) ) != 0 ) { emit error( i18n( "No models or no differences, this file: %1, is not a valid diff file.", m_info->destination.url() ) ); return false; } setDepthAndApplied(); // Do our thing :) if ( !blendOriginalIntoModelList( m_info->localSource ) ) { // Trouble blending the original into the model kDebug(8101) << "Oops cant blend original dir into modellist : " << m_info->localSource << endl; emit error( i18n( "There were problems applying the diff %1 to the folder %2.", m_info->destination.url(), m_info->source.url() ) ); return false; } updateModelListActions(); show(); return true; } void KompareModelList::slotSaveDestination() { // Unnecessary safety check! We can now guarantee that saving is only possible when there is a model and there are unsaved changes if ( m_selectedModel ) { saveDestination( m_selectedModel ); m_save->setEnabled( false ); emit updateActions(); } } bool KompareModelList::saveDestination( DiffModel* model ) { kDebug(8101) << "KompareModelList::saveDestination: " << endl; - // Unecessary safety check, we can guarantee there are unsaved changes!!! + // Unnecessary safety check, we can guarantee there are unsaved changes!!! if( !model->hasUnsavedChanges() ) return true; KTemporaryFile temp; bool correct=temp.open(); if( correct ) { emit error( i18n( "Could not open a temporary file." ) ); temp.remove(); return false; } QTextStream stream( &temp ); QStringList list; DiffHunkListConstIterator hunkIt = model->hunks()->constBegin(); DiffHunkListConstIterator hEnd = model->hunks()->constEnd(); for( ; hunkIt != hEnd; ++hunkIt ) { DiffHunk* hunk = *hunkIt; DifferenceListConstIterator diffIt = hunk->differences().constBegin(); DifferenceListConstIterator dEnd = hunk->differences().constEnd(); Difference* diff; for( ; diffIt != dEnd; ++diffIt ) { diff = *diffIt; if( !diff->applied() ) { DifferenceStringListConstIterator stringIt = diff->destinationLines().begin(); DifferenceStringListConstIterator sEnd = diff->destinationLines().end(); for ( ; stringIt != sEnd; ++stringIt ) { list.append( ( *stringIt )->string() ); } } else { DifferenceStringListConstIterator stringIt = diff->sourceLines().begin(); DifferenceStringListConstIterator sEnd = diff->sourceLines().end(); for ( ; stringIt != sEnd; ++stringIt ) { list.append( ( *stringIt )->string() ); } } } } // kDebug(8101) << "Everything: " << endl << list.join( "\n" ) << endl; if( list.count() > 0 ) stream << list.join( "" ); temp.close(); if( false/* || temp.status() != 0 */) { emit error( i18n( "Could not write to the temporary file %1, deleting it.", temp.fileName() ) ); temp.remove(); return false; } bool result = false; // Make sure the destination directory exists, it is possible when using -N to not have the destination dir/file available if ( m_info->mode == Kompare::ComparingDirs ) { // Dont use destination which was used for creating the diff directly, use the original URL!!! // FIXME!!! Wrong destination this way! Need to add the sub directory to the url!!!! kDebug(8101) << "Tempfilename (save) : " << temp.fileName() << endl; kDebug(8101) << "Model->path+file : " << model->destinationPath() << model->destinationFile() << endl; kDebug(8101) << "info->localdest : " << m_info->localDestination << endl; QString tmp = model->destinationPath() + model->destinationFile(); if ( tmp.startsWith( m_info->localDestination ) ) // It should, if not serious trouble... tmp.remove( 0, m_info->localDestination.size() ); kDebug(8101) << "DestinationURL : " << m_info->destination << endl; kDebug(8101) << "tmp : " << tmp << endl; KIO::UDSEntry entry; KUrl fullDestinationPath( m_info->destination ); fullDestinationPath.addPath( tmp ); kDebug(8101) << "fullDestinationPath : " << fullDestinationPath << endl; if ( !KIO::NetAccess::stat( fullDestinationPath.directory(), entry, m_widgetForKIO ) ) { if ( !KIO::NetAccess::mkdir( fullDestinationPath.directory(), m_widgetForKIO ) ) { emit error( i18n( "Could not create destination directory %1.\nThe file has not been saved.", fullDestinationPath.directory() ) ); return false; } } result = KIO::NetAccess::upload( temp.fileName(), fullDestinationPath, m_widgetForKIO ); } else { kDebug(8101) << "Tempfilename : " << temp.fileName() << endl; kDebug(8101) << "DestinationURL : " << m_info->destination << endl; result = KIO::NetAccess::upload( temp.fileName(), m_info->destination, m_widgetForKIO ); kDebug(8101) << "true or false?" << result << endl; } if ( !result ) { // FIXME: Wrong first argument given in case of comparing directories! emit error( i18n( "Could not upload the temporary file to the destination location %1. The temporary file is still available under: %2. You can manually copy it to the right place.", m_info->destination.url(), temp.fileName() ) ); //Don't remove file when we delete temp and don't leak it. temp.setAutoRemove(false); } else { temp.remove(); } // If saving was fine set all differences to saved so we can start again with a clean slate if ( result ) { DifferenceListConstIterator diffIt = model->differences()->constBegin(); DifferenceListConstIterator endIt = model->differences()->constEnd(); for (; diffIt != endIt; ++diffIt ) { (*diffIt)->setUnsaved( false ); } } return true; } bool KompareModelList::saveAll() { if ( modelCount() == 0 ) return false; DiffModelListIterator it = m_models->begin(); DiffModelListIterator end = m_models->end(); for ( ; it != end; ++it ) { if( !saveDestination( *it ) ) return false; } return true; } void KompareModelList::setEncoding( const QString& encoding ) { m_encoding = encoding; if ( !encoding.compare( "default", Qt::CaseInsensitive ) ) { m_textCodec = QTextCodec::codecForLocale(); } else { kDebug(8101) << "Encoding : " << encoding << endl; m_textCodec = KGlobal::charsets()->codecForName( encoding.toLatin1() ); kDebug(8101) << "TextCodec: " << m_textCodec << endl; if ( !m_textCodec ) m_textCodec = QTextCodec::codecForLocale(); } kDebug(8101) << "TextCodec: " << m_textCodec << endl; } void KompareModelList::slotDiffProcessFinished( bool success ) { if ( success ) { emit status( Kompare::Parsing ); if ( parseDiffOutput( m_diffProcess->diffOutput() ) != 0 ) { emit error( i18n( "Could not parse diff output." ) ); } else { if ( m_info->mode != Kompare::ShowingDiff ) { kDebug(8101) << "Blend this crap please and do not give me any conflicts..." << endl; blendOriginalIntoModelList( m_info->localSource ); } updateModelListActions(); show(); } emit status( Kompare::FinishedParsing ); } else if ( m_diffProcess->exitStatus() == 0 ) { emit error( i18n( "The files are identical." ) ); } else { emit error( m_diffProcess->stdErr() ); } m_diffProcess->deleteLater(); m_diffProcess = 0; } void KompareModelList::slotDirectoryChanged( const QString& /*dir*/ ) { // some debug output to see if watching works properly kDebug(8101) << "Yippie directories are being watched !!! :)" << endl; if ( m_diffProcess ) { emit status( Kompare::ReRunningDiff ); m_diffProcess->start(); } } void KompareModelList::slotFileChanged( const QString& /*file*/ ) { // some debug output to see if watching works properly kDebug(8101) << "Yippie files are being watched !!! :)" << endl; if ( m_diffProcess ) { emit status( Kompare::ReRunningDiff ); m_diffProcess->start(); } } QStringList KompareModelList::split( const QString& fileContents ) { QString contents = fileContents; QStringList list; int pos = 0; int oldpos = 0; // split that does not strip the split char #ifdef QT_OS_MAC const char split = '\r'; #else const char split = '\n'; #endif while ( ( pos = contents.indexOf( split, oldpos ) ) >= 0 ) { list.append( contents.mid( oldpos, pos - oldpos + 1 ) ); oldpos = pos + 1; } if ( contents.length() > oldpos ) { list.append( contents.right( contents.length() - oldpos ) ); } return list; } QString KompareModelList::readFile( const QString& fileName ) { QStringList list; QFile file( fileName ); file.open( QIODevice::ReadOnly ); QTextStream stream( &file ); kDebug(8101) << "Codec = " << m_textCodec << endl; if ( !m_textCodec ) m_textCodec = QTextCodec::codecForLocale(); stream.setCodec( m_textCodec ); QString contents = stream.readAll(); file.close(); return contents; } bool KompareModelList::openDiff( const QString& diffFile ) { kDebug(8101) << "Stupid :) Url = " << diffFile << endl; if ( diffFile.isEmpty() ) return false; QString diff = readFile( diffFile ); clear(); // Clear the current models emit status( Kompare::Parsing ); if ( parseDiffOutput( diff ) != 0 ) { emit error( i18n( "Could not parse diff output." ) ); return false; } updateModelListActions(); show(); emit status( Kompare::FinishedParsing ); return true; } QString KompareModelList::recreateDiff() const { QString diff; DiffModelListConstIterator modelIt = m_models->constBegin(); DiffModelListConstIterator mEnd = m_models->constEnd(); for ( ; modelIt != mEnd; ++modelIt ) { diff += (*modelIt)->recreateDiff(); } return diff; } bool KompareModelList::saveDiff( const QString& url, QString directory, DiffSettings* diffSettings ) { kDebug(8101) << "KompareModelList::saveDiff: " << endl; m_diffTemp = new KTemporaryFile(); m_diffURL = url; bool opened=m_diffTemp->open(); if( opened ) { emit error( i18n( "Could not open a temporary file." ) ); m_diffTemp->remove(); delete m_diffTemp; m_diffTemp = 0; return false; } m_diffProcess = new KompareProcess( diffSettings, Kompare::Custom, m_info->localSource, m_info->localDestination, directory ); m_diffProcess->setEncoding( m_encoding ); connect( m_diffProcess, SIGNAL(diffHasFinished(bool)), this, SLOT(slotWriteDiffOutput(bool)) ); emit status( Kompare::RunningDiff ); m_diffProcess->start(); return true; } void KompareModelList::slotWriteDiffOutput( bool success ) { kDebug(8101) << "Success = " << success << endl; if( success ) { QTextStream stream( m_diffTemp ); stream << m_diffProcess->diffOutput(); m_diffTemp->close(); if( false /*|| m_diffTemp->status() != 0 */) { emit error( i18n( "Could not write to the temporary file." ) ); } KIO::NetAccess::upload( m_diffTemp->fileName(), KUrl( m_diffURL ), m_widgetForKIO ); emit status( Kompare::FinishedWritingDiff ); } m_diffURL.truncate( 0 ); m_diffTemp->remove(); delete m_diffTemp; m_diffTemp = 0; delete m_diffProcess; m_diffProcess = 0; } void KompareModelList::slotSelectionChanged( const Diff2::DiffModel* model, const Diff2::Difference* diff ) { // This method will signal all the other objects about a change in selection, // it will emit setSelection( const DiffModel*, const Difference* ) to all who are connected kDebug(8101) << "KompareModelList::slotSelectionChanged( " << model << ", " << diff << " )" << endl; kDebug(8101) << "Sender is : " << sender()->metaObject()->className() << endl; // kDebug(8101) << kBacktrace() << endl; m_selectedModel = const_cast(model); m_modelIndex = m_models->indexOf( m_selectedModel ); kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; m_selectedDifference = const_cast(diff); m_selectedModel->setSelectedDifference( m_selectedDifference ); // setSelected* search for the argument in the lists and return false if not found // if found they return true and set the m_selected* if ( !setSelectedModel( m_selectedModel ) ) { // Backup plan m_selectedModel = firstModel(); m_selectedDifference = m_selectedModel->firstDifference(); } else if ( !m_selectedModel->setSelectedDifference( m_selectedDifference ) ) { // Another backup plan m_selectedDifference = m_selectedModel->firstDifference(); } emit setSelection( model, diff ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } void KompareModelList::slotSelectionChanged( const Diff2::Difference* diff ) { // This method will emit setSelection( const Difference* ) to whomever is listening // when for instance in kompareview the selection has changed kDebug(8101) << "KompareModelList::slotSelectionChanged( " << diff << " )" << endl; kDebug(8101) << "Sender is : " << sender()->metaObject()->className() << endl; m_selectedDifference = const_cast(diff); if ( !m_selectedModel->setSelectedDifference( m_selectedDifference ) ) { // Backup plan m_selectedDifference = m_selectedModel->firstDifference(); } emit setSelection( diff ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } void KompareModelList::slotPreviousModel() { if ( ( m_selectedModel = prevModel() ) != 0 ) { m_selectedDifference = m_selectedModel->firstDifference(); } else { m_selectedModel = firstModel(); m_selectedDifference = m_selectedModel->firstDifference(); } emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } void KompareModelList::slotNextModel() { if ( ( m_selectedModel = nextModel() ) != 0 ) { m_selectedDifference = m_selectedModel->firstDifference(); } else { m_selectedModel = lastModel(); m_selectedDifference = m_selectedModel->firstDifference(); } emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } DiffModel* KompareModelList::firstModel() { kDebug(8101) << "KompareModelList::firstModel()" << endl; m_modelIndex = 0; kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; m_selectedModel = m_models->first(); return m_selectedModel; } DiffModel* KompareModelList::lastModel() { kDebug(8101) << "KompareModelList::lastModel()" << endl; m_modelIndex = m_models->count() - 1; kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; m_selectedModel = m_models->last(); return m_selectedModel; } DiffModel* KompareModelList::prevModel() { kDebug(8101) << "KompareModelList::prevModel()" << endl; if ( m_modelIndex > 0 && --m_modelIndex < m_models->count() ) { kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; m_selectedModel = (*m_models)[ m_modelIndex ]; } else { m_selectedModel = 0; m_modelIndex = 0; kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; } return m_selectedModel; } DiffModel* KompareModelList::nextModel() { kDebug(8101) << "KompareModelList::nextModel()" << endl; if ( ++m_modelIndex < m_models->count() ) { kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; m_selectedModel = (*m_models)[ m_modelIndex ]; } else { m_selectedModel = 0; m_modelIndex = 0; kDebug(8101) << "m_modelIndex = " << m_modelIndex << endl; } return m_selectedModel; } void KompareModelList::slotPreviousDifference() { kDebug(8101) << "slotPreviousDifference called" << endl; if ( ( m_selectedDifference = m_selectedModel->prevDifference() ) != 0 ) { emit setSelection( m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); return; } kDebug(8101) << "**** no previous difference... ok lets find the previous model..." << endl; if ( ( m_selectedModel = prevModel() ) != 0 ) { m_selectedDifference = m_selectedModel->lastDifference(); emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); return; } kDebug(8101) << "**** !!! No previous model, ok backup plan activated..." << endl; // Backup plan m_selectedModel = firstModel(); m_selectedDifference = m_selectedModel->firstDifference(); emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } void KompareModelList::slotNextDifference() { kDebug(8101) << "slotNextDifference called" << endl; if ( ( m_selectedDifference = m_selectedModel->nextDifference() ) != 0 ) { emit setSelection( m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); return; } kDebug(8101) << "**** no next difference... ok lets find the next model..." << endl; if ( ( m_selectedModel = nextModel() ) != 0 ) { m_selectedDifference = m_selectedModel->firstDifference(); emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); return; } kDebug(8101) << "**** !!! No next model, ok backup plan activated..." << endl; // Backup plan m_selectedModel = lastModel(); m_selectedDifference = m_selectedModel->lastDifference(); emit setSelection( m_selectedModel, m_selectedDifference ); emit setStatusBarModelInfo( findModel( m_selectedModel ), m_selectedModel->findDifference( m_selectedDifference ), modelCount(), differenceCount(), m_selectedModel->appliedCount() ); updateModelListActions(); } void KompareModelList::slotApplyDifference( bool apply ) { m_selectedModel->applyDifference( apply ); emit applyDifference( apply ); } void KompareModelList::slotApplyAllDifferences( bool apply ) { m_selectedModel->applyAllDifferences( apply ); emit applyAllDifferences( apply ); } int KompareModelList::parseDiffOutput( const QString& diff ) { kDebug(8101) << "KompareModelList::parseDiffOutput" << endl; emit diffString( diff ); QStringList diffLines = split( diff ); Parser* parser = new Parser( this ); m_models = parser->parse( diffLines ); m_info->generator = parser->generator(); m_info->format = parser->format(); delete parser; if ( m_models ) { m_selectedModel = firstModel(); kDebug(8101) << "Ok there are differences..." << endl; m_selectedDifference = m_selectedModel->firstDifference(); emit setStatusBarModelInfo( 0, 0, modelCount(), differenceCount(), 0); } else { // Wow trouble, no models, so no differences... kDebug(8101) << "Now i'll be damned, there should be models here !!!" << endl; return -1; } return 0; } bool KompareModelList::blendOriginalIntoModelList( const QString& localURL ) { kDebug(8101) << "Hurrah we are blending..." << endl; QFileInfo fi( localURL ); bool result = false; DiffModel* model; QString fileContents; if ( fi.isDir() ) { // is a dir kDebug(8101) << "Blend Dir" << endl; // QDir dir( localURL, QString(), QDir::Name|QDir::DirsFirst, QDir::TypeMask ); DiffModelListIterator modelIt = m_models->begin(); DiffModelListIterator mEnd = m_models->end(); for ( ; modelIt != mEnd; ++modelIt ) { model = *modelIt; kDebug(8101) << "Model : " << model << endl; QString filename = model->source(); if ( !filename.startsWith( localURL ) ) filename = QDir(localURL).filePath(filename); QFileInfo fi2( filename ); if ( fi2.exists() ) { kDebug(8101) << "Reading from: " << filename << endl; fileContents = readFile( filename ); result = blendFile( model, fileContents ); } else { kDebug(8101) << "File " << filename << " does not exist !" << endl; kDebug(8101) << "Assume empty file !" << endl; fileContents.truncate( 0 ); result = blendFile( model, fileContents ); } } kDebug(8101) << "End of Blend Dir" << endl; } else if ( fi.isFile() ) { // is a file kDebug(8101) << "Blend File" << endl; kDebug(8101) << "Reading from: " << localURL << endl; fileContents = readFile( localURL ); result = blendFile( (*m_models)[ 0 ], fileContents ); kDebug(8101) << "End of Blend File" << endl; } return result; } bool KompareModelList::blendFile( DiffModel* model, const QString& fileContents ) { if ( !model ) { kDebug(8101) << "**** model is null :(" << endl; return false; } model->setBlended( true ); int srcLineNo = 1, destLineNo = 1; QStringList list = split( fileContents ); QLinkedList lines; foreach (const QString &str, list) { lines.append(str); } QLinkedList::ConstIterator linesIt = lines.begin(); QLinkedList::ConstIterator lEnd = lines.end(); DiffHunkList* hunks = model->hunks(); kDebug(8101) << "Hunks in hunklist: " << hunks->count() << endl; DiffHunkListIterator hunkIt = hunks->begin(); DiffHunk* newHunk = 0; Difference* newDiff = 0; // FIXME: this approach is not very good, we should first check if the hunk applies cleanly // and without offset and if not use that new linenumber with offset to compare against // This will only work for files we just diffed with kompare but not for blending where // file(s) to patch has/have potentially changed for ( ; hunkIt != hunks->end(); ++hunkIt ) { // Do we need to insert a new hunk before this one ? DiffHunk* hunk = *hunkIt; if ( srcLineNo < hunk->sourceLineNumber() ) { newHunk = new DiffHunk( srcLineNo, destLineNo, "", DiffHunk::AddedByBlend ); hunkIt = ++hunks->insert( hunkIt, newHunk ); newDiff = new Difference( srcLineNo, destLineNo, Difference::Unchanged ); newHunk->add( newDiff ); while ( srcLineNo < hunk->sourceLineNumber() && linesIt != lEnd ) { newDiff->addSourceLine( *linesIt ); newDiff->addDestinationLine( *linesIt ); srcLineNo++; destLineNo++; ++linesIt; } } // Now we add the linecount difference for the hunk that follows int size = hunk->sourceLineCount(); for ( int i = 0; i < size; ++i ) { ++linesIt; } srcLineNo += size; destLineNo += hunk->destinationLineCount(); } if ( linesIt != lEnd ) { newHunk = new DiffHunk( srcLineNo, destLineNo, "", DiffHunk::AddedByBlend ); model->addHunk( newHunk ); newDiff = new Difference( srcLineNo, destLineNo, Difference::Unchanged ); newHunk->add( newDiff ); while ( linesIt != lEnd ) { newDiff->addSourceLine( *linesIt ); newDiff->addDestinationLine( *linesIt ); ++linesIt; } } #if 0 DifferenceList hunkDiffList = (*hunkIt)->differences(); DifferenceListIterator diffIt = hunkDiffList.begin(); DifferenceListIterator dEnd = hunkDiffList.end(); kDebug(8101) << "Number of differences in hunkDiffList = " << diffList.count() << endl; DifferenceListIterator tempIt; Difference* diff; for ( ; diffIt != dEnd; ++diffIt ) { diff = *diffIt; kDebug(8101) << "*(Diff it) = " << diff << endl; // Check if there are lines in the original file before the difference // that are not yet in the diff. If so create new Unchanged diff if ( srcLineNo < diff->sourceLineNumber() ) { newDiff = new Difference( srcLineNo, destLineNo, Difference::Unchanged | Difference::AddedByBlend ); newHunk->add( newDiff ); while ( srcLineNo < diff->sourceLineNumber() && linesIt != lEnd ) { // kDebug(8101) << "SourceLine = " << srcLineNo << ": " << *linesIt << endl; newDiff->addSourceLine( *linesIt ); newDiff->addDestinationLine( *linesIt ); srcLineNo++; destLineNo++; ++linesIt; } } // Now i've got to add that diff switch ( diff->type() ) { case Difference::Unchanged: kDebug(8101) << "Unchanged" << endl; for ( int i = 0; i < diff->sourceLineCount(); i++ ) { if ( linesIt != lEnd && *linesIt != diff->sourceLineAt( i )->string() ) { kDebug(8101) << "Conflict: SourceLine = " << srcLineNo << ": " << *linesIt << endl; kDebug(8101) << "Conflict: DiffLine = " << diff->sourceLineNumber() + i << ": " << diff->sourceLineAt( i )->string() << endl; // Do conflict resolution (well sort of) diff->sourceLineAt( i )->setConflictString( *linesIt ); diff->setConflict( true ); } // kDebug(8101) << "SourceLine = " << srcLineNo << ": " << *linesIt << endl; // kDebug(8101) << "DiffLine = " << diff->sourceLineNumber() + i << ": " << diff->sourceLineAt( i )->string() << endl; srcLineNo++; destLineNo++; ++linesIt; } tempIt = diffIt; --diffIt; diffList.remove( tempIt ); newHunk->add( diff ); break; case Difference::Change: kDebug(8101) << "Change" << endl; //QStringListConstIterator saveIt = linesIt; for ( int i = 0; i < diff->sourceLineCount(); i++ ) { if ( linesIt != lEnd && *linesIt != diff->sourceLineAt( i )->string() ) { kDebug(8101) << "Conflict: SourceLine = " << srcLineNo << ": " << *linesIt << endl; kDebug(8101) << "Conflict: DiffLine = " << diff->sourceLineNumber() + i << ": " << diff->sourceLineAt( i )->string() << endl; // Do conflict resolution (well sort of) diff->sourceLineAt( i )->setConflictString( *linesIt ); diff->setConflict( true ); } srcLineNo++; destLineNo++; ++linesIt; } destLineNo += diff->destinationLineCount(); tempIt = diffIt; --diffIt; diffList.remove( tempIt ); newHunk->add( diff ); newModel->addDiff( diff ); break; case Difference::Insert: kDebug(8101) << "Insert" << endl; destLineNo += diff->destinationLineCount(); tempIt = diffIt; --diffIt; diffList.remove( tempIt ); newHunk->add( diff ); newModel->addDiff( diff ); break; case Difference::Delete: kDebug(8101) << "Delete" << endl; kDebug(8101) << "Number of lines in Delete: " << diff->sourceLineCount() << endl; for ( int i = 0; i < diff->sourceLineCount(); i++ ) { if ( linesIt != lEnd && *linesIt != diff->sourceLineAt( i )->string() ) { kDebug(8101) << "Conflict: SourceLine = " << srcLineNo << ": " << *linesIt << endl; kDebug(8101) << "Conflict: DiffLine = " << diff->sourceLineNumber() + i << ": " << diff->sourceLineAt( i )->string() << endl; // Do conflict resolution (well sort of) diff->sourceLineAt( i )->setConflictString( *linesIt ); diff->setConflict( true ); } // kDebug(8101) << "SourceLine = " << srcLineNo << ": " << *it << endl; // kDebug(8101) << "DiffLine = " << diff->sourceLineNumber() + i << ": " << diff->sourceLineAt( i )->string() << endl; srcLineNo++; ++linesIt; } tempIt = diffIt; --diffIt; diffList.remove( tempIt ); newHunk->add( diff ); newModel->addDiff( diff ); break; default: kDebug(8101) << "****, some diff type we do not know about ???" << endl; } } } #endif /* diffList = newModel->differences(); diff = diffList.first(); kDebug(8101) << "Count = " << diffList.count() << endl; for ( diff = diffList.first(); diff; diff = diffList.next() ) { kDebug(8101) << "sourcelinenumber = " << diff->sourceLineNumber() << endl; } */ m_selectedModel = firstModel(); m_selectedDifference = m_selectedModel->firstDifference(); return true; } void KompareModelList::show() { kDebug(8101) << "KompareModelList::Show Number of models = " << m_models->count() << endl; emit modelsChanged( m_models ); emit setSelection( m_selectedModel, m_selectedDifference ); } void KompareModelList::clear() { if ( m_models ) m_models->clear(); emit modelsChanged( m_models ); } void KompareModelList::refresh() { // FIXME: I can imagine blending also wants to be refreshed so make a switch case here compare(m_info->mode); } void KompareModelList::swap() { //FIXME Not sure if any mode could be swapped if ( m_info->mode == Kompare::ComparingFiles ) compare(m_info->mode); else if ( m_info->mode == Kompare::ComparingDirs ) compare(m_info->mode); } bool KompareModelList::hasUnsavedChanges() const { if ( modelCount() == 0 ) return false; DiffModelListConstIterator modelIt = m_models->constBegin(); DiffModelListConstIterator endIt = m_models->constEnd(); for ( ; modelIt != endIt; ++modelIt ) { if ( (*modelIt)->hasUnsavedChanges() ) return true; } return false; } int KompareModelList::modelCount() const { return m_models ? m_models->count() : 0; } int KompareModelList::differenceCount() const { return m_selectedModel ? m_selectedModel->differenceCount() : -1; } int KompareModelList::appliedCount() const { return m_selectedModel ? m_selectedModel->appliedCount() : -1; } void KompareModelList::slotKompareInfo( struct Kompare::Info* info ) { m_info = info; } bool KompareModelList::setSelectedModel( DiffModel* model ) { kDebug(8101) << "KompareModelList::setSelectedModel( " << model << " )" << endl; if ( model != m_selectedModel ) { if ( !m_models->contains( model ) ) return false; kDebug(8101) << "m_selectedModel (was) = " << m_selectedModel << endl; m_modelIndex = m_models->indexOf( model ); kDebug(8101) << "m_selectedModel (is) = " << m_selectedModel << endl; m_selectedModel = model; } updateModelListActions(); return true; } void KompareModelList::updateModelListActions() { // if ( m_models && m_selectedModel && m_selectedDifference ) // { // // ARGH!!!! Casts are evil!!! // if ( false /*( ( KomparePart* )parent() )->isReadWrite()*/ ) // { // if ( m_selectedModel->appliedCount() != m_selectedModel->differenceCount() ) // m_applyAll->setEnabled( true ); // else // m_applyAll->setEnabled( false ); // // if ( m_selectedModel->appliedCount() != 0 ) // m_unapplyAll->setEnabled( true ); // else // m_unapplyAll->setEnabled( false ); // // m_applyDifference->setEnabled( m_selectedDifference->applied() == false ); // m_unApplyDifference->setEnabled( m_selectedDifference->applied() == true ); // m_save->setEnabled( m_selectedModel->hasUnsavedChanges() ); // } // else // { // m_applyDifference->setEnabled ( false ); // m_unApplyDifference->setEnabled( false ); // m_applyAll->setEnabled ( false ); // m_unapplyAll->setEnabled ( false ); // m_save->setEnabled ( false ); // } // // m_previousFile->setEnabled ( hasPrevModel() ); // m_nextFile->setEnabled ( hasNextModel() ); // m_previousDifference->setEnabled( hasPrevDiff() ); // m_nextDifference->setEnabled ( hasNextDiff() ); // } // else // { // m_applyDifference->setEnabled ( false ); // m_unApplyDifference->setEnabled ( false ); // m_applyAll->setEnabled ( false ); // m_unapplyAll->setEnabled ( false ); // // m_previousFile->setEnabled ( false ); // m_nextFile->setEnabled ( false ); // m_previousDifference->setEnabled( false ); // m_nextDifference->setEnabled ( false ); // m_save->setEnabled ( false ); // } } bool KompareModelList::hasPrevModel() const { kDebug(8101) << "KompareModelList::hasPrevModel()" << endl; if ( m_modelIndex > 0 ) { // kDebug(8101) << "has prev model" << endl; return true; } // kDebug(8101) << "doesn't have a prev model, this is the first one..." << endl; return false; } bool KompareModelList::hasNextModel() const { kDebug(8101) << "KompareModelList::hasNextModel()" << endl; if ( m_modelIndex < ( m_models->count() - 1 ) ) { // kDebug(8101) << "has next model" << endl; return true; } // kDebug(8101) << "doesn't have a next model, this is the last one..." << endl; return false; } bool KompareModelList::hasPrevDiff() const { // kDebug(8101) << "KompareModelList::hasPrevDiff()" << endl; int index = m_selectedModel->diffIndex(); if ( index > 0 ) { // kDebug(8101) << "has prev difference in same model" << endl; return true; } if ( hasPrevModel() ) { // kDebug(8101) << "has prev difference but in prev model" << endl; return true; } // kDebug(8101) << "doesn't have a prev difference, not even in the previous model because there is no previous model" << endl; return false; } bool KompareModelList::hasNextDiff() const { // kDebug(8101) << "KompareModelList::hasNextDiff()" << endl; int index = m_selectedModel->diffIndex(); if ( index < ( m_selectedModel->differenceCount() - 1 ) ) { // kDebug(8101) << "has next difference in same model" << endl; return true; } if ( hasNextModel() ) { // kDebug(8101) << "has next difference but in next model" << endl; return true; } // kDebug(8101) << "doesn't have a next difference, not even in next model because there is no next model" << endl; return false; } void KompareModelList::slotActionApplyDifference() { if ( !m_selectedDifference->applied() ) slotApplyDifference( true ); slotNextDifference(); updateModelListActions(); } void KompareModelList::slotActionUnApplyDifference() { if ( m_selectedDifference->applied() ) slotApplyDifference( false ); slotPreviousDifference(); updateModelListActions(); } void KompareModelList::slotActionApplyAllDifferences() { slotApplyAllDifferences( true ); updateModelListActions(); } void KompareModelList::slotActionUnapplyAllDifferences() { slotApplyAllDifferences( false ); updateModelListActions(); } #include "komparemodellist.moc" /* vim: set ts=4 sw=4 noet: */ diff --git a/plugins/subversion/kdevsvncpp/client.hpp b/plugins/subversion/kdevsvncpp/client.hpp index ceab9629a2..bb7ddd2270 100644 --- a/plugins/subversion/kdevsvncpp/client.hpp +++ b/plugins/subversion/kdevsvncpp/client.hpp @@ -1,791 +1,791 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #ifndef _SVNCPP_CLIENT_H_ #define _SVNCPP_CLIENT_H_ // Ignore MSVC 6 compiler warning #if defined (_MSC_VER) && _MSC_VER <= 1200 // debug symbol truncated #pragma warning (disable: 4786) // C++ exception specification #pragma warning (disable: 4290) #endif // Ignore MSVC 7,8,9 compiler warnings #if defined (_MSC_VER) && _MSC_VER > 1200 && _MSC_VER <= 1500 // C++ exception specification #pragma warning (disable: 4290) #endif // stl #include "kdevsvncpp/vector_wrapper.hpp" #include "kdevsvncpp/utility_wrapper.hpp" #include "kdevsvncpp/map_wrapper.hpp" // svncpp #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/exception.hpp" #include "kdevsvncpp/path.hpp" #include "kdevsvncpp/entry.hpp" #include "kdevsvncpp/revision.hpp" #include "kdevsvncpp/log_entry.hpp" #include "kdevsvncpp/annotate_line.hpp" namespace svn { // forward declarations class Context; class DirEntry; class Info; class Status; class Targets; typedef std::vector AnnotatedFile; typedef std::vector DirEntries; typedef std::vector InfoVector; typedef std::vector LogEntries; typedef std::vector StatusEntries; // map of property names to values typedef std::map PropertiesMap; // pair of path, PropertiesMap typedef std::pair PathPropertiesMapEntry; // vector of path, Properties pairs typedef std::vector PathPropertiesMapList; /** * These flags can be passed to the status function to filter * the files * * @see status */ struct StatusFilter { public: bool showUnversioned; bool showUnmodified; bool showModified; ///< this includes @a showConflicted as well bool showConflicted; bool showIgnored; bool showExternals; StatusFilter() : showUnversioned(false), showUnmodified(false), showModified(false), showConflicted(false), showExternals(false) { } }; /** * Subversion client API. */ class Client { public: /** * Initializes the primary memory pool. */ Client(Context * context = 0); virtual ~Client(); /** * @return returns the Client context */ const Context * getContext() const; /** * @return returns the Client context */ Context * getContext(); /** * sets the client context * you have to make sure the old context * is de-allocated * * @param context new context to use */ void setContext(Context * context = NULL); /** * Enumerates all files/dirs at a given path. * * Throws an exception if an error occurs * * @param path Path to explore. * @param descend Recurse into subdirectories if existant. * @param get_all Return all entries, not just the interesting ones. * @param update Query the repository for updates. * @param no_ignore Disregard default and svn:ignore property ignores. * @param ignore_externals Disregard external files. * @return vector with Status entries. */ StatusEntries status(const char * path, const bool descend = false, const bool get_all = true, const bool update = false, const bool no_ignore = false, const bool ignore_externals = false) throw(ClientException); /** * Enumerates all files/dirs matchin the parameter @a filter * at @a path and returns them in the vector @a statusEntries * * Throws an exception if an error occurs * * @since New in 0.9.7 * * @param path Path to explore. * @param filter use a combination of the @a SHOW_* values to filter the * output * @param descend Recurse into subdirectories if existant. * @param update Query the repository for updates. * @param entries vector with Status entries * * @return current revnum */ svn_revnum_t status(const char * path, const StatusFilter & filter, const bool descend, const bool update, StatusEntries & entries) throw(ClientException); /** * Executes a revision checkout. * @param moduleName name of the module to checkout. * @param destPath destination directory for checkout. * @param revision the revision number to checkout. If the number is -1 * then it will checkout the latest revision. * @param recurse whether you want it to checkout files recursively. * @param ignore_externals whether you want get external resources too. * @param peg_revision peg revision to checkout, by default current. * @exception ClientException */ svn_revnum_t checkout(const char * moduleName, const Path & destPath, const Revision & revision, bool recurse, bool ignore_externals = false, const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); /** * relocate wc @a from to @a to * @exception ClientException */ void relocate(const Path & path, const char *from_url, const char *to_url, bool recurse) throw(ClientException); /** * Sets a single file for deletion. * @exception ClientException */ void remove(const Path & path, bool force) throw(ClientException); /** * Sets files for deletion. * * @param targets targets to delete * @param force force if files are locally modified * @exception ClientException */ void remove(const Targets & targets, bool force) throw(ClientException); /** * Sets files to lock. * * @param targets targets to lock * @param force force setting/stealing lock - * @param comment writing comment about lock setting is neccessary + * @param comment writing comment about lock setting is necessary * @exception ClientException */ void lock(const Targets & targets, bool force, const char * comment) throw(ClientException); /** * Sets files to unlock. * * @param targets targets to unlock * @param force force unlock even if lock belongs to another user * @exception ClientException */ void unlock(const Targets & targets, bool force) throw(ClientException); /** * Reverts a couple of files to a pristiner state. * @exception ClientException */ void revert(const Targets & targets, bool recurse) throw(ClientException); /** * Adds a file to the repository. * @exception ClientException */ void add(const Path & path, bool recurse) throw(ClientException); /** * Updates the file or directory. * @param targets target files. * @param revision the revision number to checkout. * Revision::HEAD will checkout the * latest revision. * @param recurse recursively update. * @param ignore_externals don't affect external destinations. * @exception ClientException * * @return a vector with resulting revisions */ std::vector update(const Targets & targets, const Revision & revision, bool recurse, bool ignore_externals) throw(ClientException); svn_revnum_t update(const Path & path, const Revision & revision, bool recurse, bool ignore_externals) throw(ClientException); /** * Retrieves the contents for a specific @a revision of * a @a path * * @param path path of file or directory * @param revision revision to retrieve * @param peg_revision peg revision to retrieve, * by default is the latest one * @return contents of the file */ std::string cat(const Path & path, const Revision & revision, const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); /** * Retrieves the contents for a specific @a revision of * a @a path and saves it to the destination file @a dstPath. * * If @a dstPath is empty (""), then this path will be * constructed from the temporary directory on this system * and the filename in @a path. @a dstPath will still have * the file extension from @a path and uniqueness of the * temporary filename will be ensured. * * @param dstPath Filename in which the contents * of the file file will be safed. * @param path path or url * @param revision * @param peg_revision peg revision to retrieve, by default is the latest one */ void get(Path & dstPath, const Path & path, const Revision & revision, const Revision & peg_revision = Revision::UNSPECIFIED) throw(ClientException); /** * Retrieves the contents for a specific @a revision of * a @a path * * @param path path of file or directory * @param revisionStart revision to retrieve * @param revisionEnd revision to retrieve * @return contents of the file */ AnnotatedFile * annotate(const Path & path, const Revision & revisionStart, const Revision & revisionEnd) throw(ClientException); /** * Commits changes to the repository. This usually requires * authentication, see Auth. * @return Returns a long representing the revision. It returns a * -1 if the revision number is invalid. * @param targets files to commit. * @param message log message. * @param recurse whether the operation should be done recursively. * @param keep_locks whether to preserve locks or to release them after commit * @exception ClientException */ svn_revnum_t commit(const Targets & targets, const char * message, bool recurse, bool keep_locks = false) throw(ClientException); /** * Copies a versioned file with the history preserved. * @exception ClientException */ void copy(const Path & srcPath, const Revision & srcRevision, const Path & destPath) throw(ClientException); /** * Moves or renames a file. * @exception ClientException */ void move(const Path & srcPath, const Revision & srcRevision, const Path & destPath, bool force) throw(ClientException); /** * Creates a directory directly in a repository or creates a * directory on disk and schedules it for addition. If path * is a URL then authentication is usually required, see Auth. * * @param path * @exception ClientException */ void mkdir(const Path & path) throw(ClientException); void mkdir(const Targets & targets) throw(ClientException); /** * Recursively cleans up a local directory, finishing any * incomplete operations, removing lockfiles, etc. * @param path a local directory. * @exception ClientException */ void cleanup(const Path & path) throw(ClientException); /** * Removes the 'conflicted' state on a file. * @exception ClientException */ void resolved(const Path & path, bool recurse) throw(ClientException); /** * Export into file or directory TO_PATH from local or remote FROM_PATH * @param from_path path to import * @param to_path where to import * @param revision revision of files in source repository or working copy * @param peg_revision * @param overwrite overwrite existing files in to_path * @param ignore_externals whether to ignore external sources in from_path * @param recurse * @param native_eol which EOL to use when exporting, usually different for * different OSs * @exception ClientException */ void doExport(const Path & from_path, const Path & to_path, const Revision & revision, bool overwrite = false, const Revision & peg_revision = Revision::UNSPECIFIED, bool ignore_externals = false, bool recurse = true, const char * native_eol = NULL) throw(ClientException); /** * Update local copy to mirror a new url. This excapsulates the * svn_client_switch() client method. * @exception ClientException */ svn_revnum_t doSwitch(const Path & path, const char * url, const Revision & revision, bool recurse) throw(ClientException); /** * Import file or directory PATH into repository directory URL at * head. This usually requires authentication, see Auth. * @param path path to import * @param url * @param message log message. * @param recurse * @exception ClientException */ void import(const Path & path, const char * url, const char * message, bool recurse) throw(ClientException); void import(const Path & path, const Path & url, const char * message, bool recurse) throw(ClientException); /** * Merge changes from two paths into a new local path. * @exception ClientException */ void merge(const Path & path1, const Revision & revision1, const Path & path2, const Revision & revision2, const Path & localPath, bool force, bool recurse, bool notice_ancestry = false, bool dry_run = false) throw(ClientException); /** * retrieve information about the given path * or URL * * @see Client::status * @see Info * * @param pathOrUrl * @param pegRevision * @param revision * @param recurse */ InfoVector info(const Path & pathOrUrl, bool recurse=false, const Revision & revision = Revision::UNSPECIFIED, const Revision & pegRevision = Revision::UNSPECIFIED) throw(ClientException); /** * Retrieve log information for the given path * Loads the log messages result set. The first * entry is the youngest revision. * * You can use the constants Revision::START and * Revision::HEAD * * @param path * @param revisionStart * @param revisionEnd * @param discoverChangedPaths * @param strictNodeHistory * @return a vector with log entries */ const LogEntries * log(const char * path, const Revision & revisionStart, const Revision & revisionEnd, bool discoverChangedPaths = false, bool strictNodeHistory = true) throw(ClientException); /** * Produce diff output which describes the delta between * @a path/@a revision1 and @a path/@a revision2. @a path * can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path path of the file. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException); /** * Produce diff output which describes the delta between * @a path1/@a revision1 and @a path2/@a revision2. @a path1, * @a path2 can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path1 path of the first file corresponding to @a revision1. * @param path2 path of the first file corresponding to @a revision2. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path1, const Path & path2, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException); /** * Produce diff output which describes the delta of * @a path/@a pegRevision between @a revision1 and @a revision2. * @a path can be either a working-copy path or a URL. * * A ClientException will be thrown if either @a revision1 or * @a revision2 has an `unspecified' or unrecognized `kind'. * * @param tmpPath prefix for a temporary directory needed by diff. * Filenames will have ".tmp" and similar added to this prefix in * order to ensure uniqueness. * @param path path of the file. * @param pegRevision the peg revision to identify the path. * @param revision1 one of the revisions to check. * @param revision2 the other revision. * @param recurse whether the operation should be done recursively. * @param ignoreAncestry whether the files will be checked for * relatedness. * @param noDiffDeleted if true, no diff output will be generated * on deleted files. * @return delta between the files * @exception ClientException */ std::string diff(const Path & tmpPath, const Path & path, const Revision & pegRevision, const Revision & revision1, const Revision & revision2, const bool recurse, const bool ignoreAncestry, const bool noDiffDeleted) throw(ClientException); /** * lists entries in @a pathOrUrl no matter whether local or * repository * * @param pathOrUrl * @param revision * @param recurse * @return a vector of directory entries, each with * a relative path (only filename) */ DirEntries list(const char * pathOrUrl, svn_opt_revision_t * revision, bool recurse) throw(ClientException); /** * lists properties in @a path no matter whether local or * repository * * @param path * @param revision * @param recurse * @return PropertiesList */ PathPropertiesMapList proplist(const Path &path, const Revision &revision, bool recurse = false); /** * lists one property in @a path no matter whether local or * repository * * @param propName * @param path * @param revision * @param recurse * @return PathPropertiesMapList */ PathPropertiesMapList propget(const char * propName, const Path & path, const Revision & revision, bool recurse = false); /** * This method is deprecated, please use * @a Property.set * set property in @a path no matter whether local or * repository * * @deprecated * * @param path * @param revision * @param propName * @param propValue * @param recurse * @param skip_checks * @return PropertiesList */ void propset(const char * propName, const char * propValue, const Path & path, const Revision & revision, bool recurse = false, bool skip_checks = true); /** * delete property in @a path no matter whether local or * repository * * @param propName * @param path * @param revision * @param recurse */ void propdel(const char * propName, const Path & path, const Revision & revision, bool recurse = false); /** * lists revision properties in @a path no matter whether local or * repository * * @param path * @param revision * @return PropertiesList */ std::pair revproplist(const Path & path, const Revision & revision); /** * lists one revision property in @a path no matter whether local or * repository * * @param propName * @param path * @param revision * @return PropertiesList */ std::pair revpropget(const char * propName, const Path & path, const Revision & revision); /** * set revision property in @a path no matter whether local or * repository * * @param propName * @param propValue * @param path * @param revision * @param force * @return Revision */ svn_revnum_t revpropset(const char * propName, const char * propValue, const Path & path, const Revision & revision, bool force = false); /** * delete revision property in @a path no matter whether local or * repository * * @param propName * @param path * @param revision * @param force * @return Revision */ svn_revnum_t revpropdel(const char * propName, const Path & path, const Revision & revision, bool force = false); /** * Add a single file into ignore list. * * @param path path to the file * @exception ClientException * @see svn:ignore property description */ void ignore(const Path & path) throw(ClientException); /** * Add files into ignore list. * * @param targets targets to treat as ignored * @exception ClientException * @see svn:ignore property description */ void ignore(const Targets & targets) throw(ClientException); private: Context * m_context; /** * disallow assignment operator */ Client & operator= (const Client &); /** * disallow copy constructor */ Client(const Client &); }; } #endif /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/context.cpp b/plugins/subversion/kdevsvncpp/context.cpp index ddbb6579ad..a90cdd7eb3 100644 --- a/plugins/subversion/kdevsvncpp/context.cpp +++ b/plugins/subversion/kdevsvncpp/context.cpp @@ -1,714 +1,714 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ /** * @todo implement 1.3 SVN api: * * SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN * svn_client_add3 * svn_client_copy2 * svn_client_commit3 * svn_client_delete2 * svn_client_move3 * svn_client_mkdir2 * svn_client_import2 * svn_client_info */ // Apache Portable Runtime #include "apr_xlate.h" // Subversion api #include "svn_auth.h" #include "svn_config.h" #include "svn_subst.h" //#include "svn_utf.h" // svncpp #include "kdevsvncpp/apr.hpp" #include "kdevsvncpp/context.hpp" #include "kdevsvncpp/context_listener.hpp" namespace svn { struct Context::Data { public: /** The usage of Apr makes sure Apr is initialized * before any use of apr functions. */ Apr apr; ContextListener * listener; bool logIsSet; int promptCounter; Pool pool; svn_client_ctx_t * ctx; std::string username; std::string password; std::string logMessage; std::string configDir; /** * translate native c-string to utf8 */ static svn_error_t * translateString(const char * str, const char ** newStr, apr_pool_t * /*pool*/) { // due to problems with apr_xlate we dont perform // any conversion at this place. YOU will have to make // sure any strings passed are UTF 8 strings // svn_string_t *string = svn_string_create ("", pool); // // string->data = str; // string->len = strlen (str); // // const char * encoding = APR_LOCALE_CHARSET; // // SVN_ERR (svn_subst_translate_string (&string, string, // encoding, pool)); // // *newStr = string->data; *newStr = str; return SVN_NO_ERROR; } /** * the @a baton is interpreted as Data * * Several checks are performed on the baton: * - baton == 0? * - baton->Data * - listener set? * * @param baton * @param data returned data if everything is OK * @retval SVN_NO_ERROR if everything is fine * @retval SVN_ERR_CANCELLED on invalid values */ static svn_error_t * getData(void * baton, Data ** data) { if (baton == NULL) return svn_error_create(SVN_ERR_CANCELLED, NULL, "invalid baton"); Data * data_ = static_cast (baton); if (data_->listener == 0) return svn_error_create(SVN_ERR_CANCELLED, NULL, "invalid listener"); *data = data_; return SVN_NO_ERROR; } Data(const std::string & configDir_) : listener(0), logIsSet(false), promptCounter(0), configDir(configDir_) { const char * c_configDir = 0; if (configDir.length() > 0) c_configDir = configDir.c_str(); // make sure the configuration directory exists svn_config_ensure(c_configDir, pool); - // intialize authentication providers + // initialize authentication providers // * simple // * username // * simple prompt // * ssl server trust file // * ssl server trust prompt // * ssl client cert pw file // * ssl client cert pw prompt // * ssl client cert file // =================== // 8 providers apr_array_header_t *providers = apr_array_make(pool, 8, sizeof(svn_auth_provider_object_t *)); svn_auth_provider_object_t *provider; svn_client_get_simple_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_username_provider( &provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_simple_prompt_provider( &provider, onSimplePrompt, this, 100000000, // not very nice. should be infinite... pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // add ssl providers // file first then prompt providers svn_client_get_ssl_server_trust_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_client_cert_pw_file_provider(&provider, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_client_get_ssl_server_trust_prompt_provider( &provider, onSslServerTrustPrompt, this, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; // plugged in 3 as the retry limit - what is a good limit? svn_client_get_ssl_client_cert_pw_prompt_provider( &provider, onSslClientCertPwPrompt, this, 3, pool); *(svn_auth_provider_object_t **)apr_array_push(providers) = provider; svn_auth_baton_t *ab; svn_auth_open(&ab, providers, pool); // initialize ctx structure svn_client_create_context(&ctx, pool); // get the config based on the configDir passed in svn_config_get_config(&ctx->config, c_configDir, pool); // tell the auth functions where the config is svn_auth_set_parameter(ab, SVN_AUTH_PARAM_CONFIG_DIR, c_configDir); ctx->auth_baton = ab; ctx->log_msg_func = onLogMsg; ctx->log_msg_baton = this; ctx->notify_func = onNotify; ctx->notify_baton = this; ctx->cancel_func = onCancel; ctx->cancel_baton = this; #if (SVN_VER_MAJOR >= 1) && (SVN_VER_MINOR >= 2) ctx->notify_func2 = onNotify2; ctx->notify_baton2 = this; #endif } void setAuthCache(bool value) { void *param = 0; if (!value) param = (void *)"1"; svn_auth_set_parameter(ctx->auth_baton, SVN_AUTH_PARAM_NO_AUTH_CACHE, param); } /** @see Context::setLogin */ void setLogin(const char * usr, const char * pwd) { username = usr; password = pwd; svn_auth_baton_t * ab = ctx->auth_baton; svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, username.c_str()); svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, password.c_str()); } /** @see Context::setLogMessage */ void setLogMessage(const char * msg) { logMessage = msg; logIsSet = true; } /** * this function gets called by the subversion api function * when a log message is needed. This is the case on a commit * for example */ static svn_error_t * onLogMsg(const char **log_msg, const char **tmp_file, apr_array_header_t *, //UNUSED commit_items void *baton, apr_pool_t * pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string msg; if (data->logIsSet) msg = data->getLogMessage(); else { if (!data->retrieveLogMessage(msg)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); } *log_msg = apr_pstrdup(pool, msg.c_str()); *tmp_file = NULL; return SVN_NO_ERROR; } /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static void onNotify(void * baton, const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { if (baton == 0) return; Data * data = static_cast (baton); data->notify(path, action, kind, mime_type, content_state, prop_state, revision); } #if (SVN_VER_MAJOR >= 1) && (SVN_VER_MINOR >= 2) /** * this is the callback function for the subversion 1.2 * api functions to signal the progress of an action * * @todo right now we forward only to @a onNotify, * but maybe we should a notify2 to the listener * @since subversion 1.2 */ static void onNotify2(void*baton,const svn_wc_notify_t *action,apr_pool_t *) { if (!baton) return; // for now forward the call to @a onNotify onNotify(baton, action->path, action->action, action->kind, action->mime_type, action->content_state, action->prop_state, action->revision); } #endif /** * this is the callback function for the subversion * api functions to signal the progress of an action */ static svn_error_t * onCancel(void * baton) { if (baton == 0) return SVN_NO_ERROR; Data * data = static_cast (baton); if (data->cancel()) return svn_error_create(SVN_ERR_CANCELLED, NULL, "cancelled by user"); else return SVN_NO_ERROR; } /** * @see svn_auth_simple_prompt_func_t */ static svn_error_t * onSimplePrompt(svn_auth_cred_simple_t **cred, void *baton, const char *realm, const char *username, svn_boolean_t _may_save, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); bool may_save = _may_save != 0; if (!data->retrieveLogin(username, realm, may_save)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_simple_t* lcred = (svn_auth_cred_simple_t*) apr_palloc(pool, sizeof(svn_auth_cred_simple_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->password, data->getPassword (), pool)); SVN_ERR (svn_utf_cstring_to_utf8 ( &lcred->username, data->getUsername (), pool)); */ lcred->password = data->getPassword(); lcred->username = data->getUsername(); // tell svn if the credentials need to be saved lcred->may_save = may_save; *cred = lcred; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_server_trust_prompt_func_t */ static svn_error_t * onSslServerTrustPrompt(svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *info, svn_boolean_t may_save, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); ContextListener::SslServerTrustData trustData(failures); if (realm != NULL) trustData.realm = realm; trustData.hostname = info->hostname; trustData.fingerprint = info->fingerprint; trustData.validFrom = info->valid_from; trustData.validUntil = info->valid_until; trustData.issuerDName = info->issuer_dname; trustData.maySave = may_save != 0; apr_uint32_t acceptedFailures; ContextListener::SslServerTrustAnswer answer = data->listener->contextSslServerTrustPrompt( trustData, acceptedFailures); if (answer == ContextListener::DONT_ACCEPT) *cred = NULL; else { svn_auth_cred_ssl_server_trust_t *cred_ = (svn_auth_cred_ssl_server_trust_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_server_trust_t)); if (answer == ContextListener::ACCEPT_PERMANENTLY) { cred_->may_save = 1; cred_->accepted_failures = acceptedFailures; } *cred = cred_; } return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_prompt_func_t */ static svn_error_t * onSslClientCertPrompt(svn_auth_cred_ssl_client_cert_t **cred, void *baton, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string certFile; if (!data->listener->contextSslClientCertPrompt(certFile)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_ssl_client_cert_t *cred_ = (svn_auth_cred_ssl_client_cert_t*) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->cert_file, certFile.c_str (), pool)); */ cred_->cert_file = certFile.c_str(); *cred = cred_; return SVN_NO_ERROR; } /** * @see svn_auth_ssl_client_cert_pw_prompt_func_t */ static svn_error_t * onSslClientCertPwPrompt( svn_auth_cred_ssl_client_cert_pw_t **cred, void *baton, const char *realm, svn_boolean_t maySave, apr_pool_t *pool) { Data * data = NULL; SVN_ERR(getData(baton, &data)); std::string password; bool may_save = maySave != 0; if (!data->listener->contextSslClientCertPwPrompt(password, realm, may_save)) return svn_error_create(SVN_ERR_CANCELLED, NULL, ""); svn_auth_cred_ssl_client_cert_pw_t *cred_ = (svn_auth_cred_ssl_client_cert_pw_t *) apr_palloc(pool, sizeof(svn_auth_cred_ssl_client_cert_pw_t)); /* SVN_ERR (svn_utf_cstring_to_utf8 ( &cred_->password, password.c_str (), pool)); */ cred_->password = password.c_str(); cred_->may_save = may_save; *cred = cred_; return SVN_NO_ERROR; } const char * getUsername() const { return username.c_str(); } const char * getPassword() const { return password.c_str(); } const char * getLogMessage() const { return logMessage.c_str(); } /** * if the @a listener is set, use it to retrieve the log * message using ContextListener::contextGetLogMessage. * This return values is given back, then. * * if the @a listener is not set the its checked whether * the log message has been set using @a setLogMessage * yet. If not, return false otherwise true * * @param msg log message * @retval false cancel */ bool retrieveLogMessage(std::string & msg) { bool ok; if (listener == 0) return false; ok = listener->contextGetLogMessage(logMessage); if (ok) msg = logMessage; else logIsSet = false; return ok; } /** * if the @a listener is set and no password has been * set yet, use it to retrieve login and password using * ContextListener::contextGetLogin. * * if the @a listener is not set, check if setLogin * has been called yet. * * @return continue? * @retval false cancel */ bool retrieveLogin(const char * username_, const char * realm, bool &may_save) { bool ok; if (listener == 0) return false; if (username_ == NULL) username = ""; else username = username_; ok = listener->contextGetLogin(realm, username, password, may_save); return ok; } /** * if the @a listener is set call the method * @a contextNotify */ void notify(const char *path, svn_wc_notify_action_t action, svn_node_kind_t kind, const char *mime_type, svn_wc_notify_state_t content_state, svn_wc_notify_state_t prop_state, svn_revnum_t revision) { if (listener != 0) { listener->contextNotify(path, action, kind, mime_type, content_state, prop_state, revision); } } /** * if the @a listener is set call the method * @a contextCancel */ bool cancel() { if (listener != 0) { return listener->contextCancel(); } else { // don't cancel if no listener return false; } } }; Context::Context(const std::string &configDir) { m = new Data(configDir); } Context::Context(const Context & src) { m = new Data(src.m->configDir); setLogin(src.getUsername(), src.getPassword()); } Context::~Context() { delete m; } void Context::setAuthCache(bool value) { m->setAuthCache(value); } void Context::setLogin(const char * username, const char * password) { m->setLogin(username, password); } Context::operator svn_client_ctx_t * () { return m->ctx; } svn_client_ctx_t * Context::ctx() { return m->ctx; } void Context::setLogMessage(const char * msg) { m->setLogMessage(msg); } const char * Context::getUsername() const { return m->getUsername(); } const char * Context::getPassword() const { return m->getPassword(); } const char * Context::getLogMessage() const { return m->getLogMessage(); } void Context::setListener(ContextListener * listener) { m->listener = listener; } ContextListener * Context::getListener() const { return m->listener; } void Context::reset() { m->promptCounter = 0; m->logIsSet = false; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/entry.cpp b/plugins/subversion/kdevsvncpp/entry.cpp index d97ae44151..0d9a2adf63 100644 --- a/plugins/subversion/kdevsvncpp/entry.cpp +++ b/plugins/subversion/kdevsvncpp/entry.cpp @@ -1,82 +1,82 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ // svncpp #include "kdevsvncpp/entry.hpp" #include "m_check.hpp" namespace svn { Entry::Entry(const svn_wc_entry_t * src) : m_entry(0), m_pool(0), m_valid(false) { init(src); } Entry::Entry(const Entry & src) : m_entry(0), m_pool(0), m_valid(false) { init(src); } Entry::~Entry() { - // no need to explicitely de-allocate m_entry + // no need to explicitly de-allocate m_entry // since this will be handled by m_pool } void Entry::init(const svn_wc_entry_t * src) { if (src) { // copy the contents of src m_entry = svn_wc_entry_dup(src, m_pool); m_valid = true; } else { // create an empty entry m_entry = (svn_wc_entry_t*) apr_pcalloc(m_pool, sizeof(svn_wc_entry_t)); } } Entry & Entry::operator = (const Entry & src) { if (this == &src) return *this; init(src); return *this; } } /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/kdevsvncpp/info.hpp b/plugins/subversion/kdevsvncpp/info.hpp index 2e156ffb03..9fc1618c32 100644 --- a/plugins/subversion/kdevsvncpp/info.hpp +++ b/plugins/subversion/kdevsvncpp/info.hpp @@ -1,162 +1,162 @@ /* * ==================================================================== * Copyright (c) 2002-2009 The RapidSvn Group. All rights reserved. * * 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 3 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 (in the file GPL.txt. * If not, see . * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://rapidsvn.tigris.org/. * ==================================================================== */ #ifndef _SVNCPP_INFO_HPP_ #define _SVNCPP_INFO_HPP_ // subversion api #include "svn_client.h" namespace svn { // forward declarations class Path; /** * C++ API for Subversion. * This class wraps around @a svn_info_t. */ class Info { public: /** * default constructor. if @a src is set, * copy its contents. * * If @a src is not set (=0) this will be * a non-versioned entry. This can be checked * later with @a isValid (). * * @param src another entry to copy from */ Info(const Path & path, const svn_info_t * src = 0); /** * copy constructor */ Info(const Info & src); /** * destructor */ virtual ~Info(); /** * assignment operator */ Info & operator = (const Info &); /** * returns whether this is a valid=versioned * entry. * * @return is entry valid * @retval true valid entry * @retval false invalid or unversioned entry */ bool isValid() const; /** @return entry's name */ const Path & path() const; /** @return base revision */ const svn_revnum_t revision() const; /** @return url in repository */ const char * url() const; /** @return canonical repository url */ const char * repos() const; /** @return repository uuid */ const char * uuid() const; /** @return node kind (file, dir, ...) */ svn_node_kind_t kind() const; /** @returns revision on which the file was last changed */ svn_revnum_t lastChangedRevision() const; /** @returns on which the date was last changed */ apr_time_t lastChangedDate() const; /** @returns author that last changed the file */ const char * lastChangedAuthor() const; - /** @return wether the info contains working copy stuff */ + /** @return whether the info contains working copy stuff */ bool hasWcInfo () const; /** @return the scheduling state */ svn_wc_schedule_t schedule() const; /** @return urls this entry was copied from */ const char* copyFromUrl() const; /** @return the revision this entry was copied from */ svn_revnum_t copyFromRevision() const; /** @return last up to date time for text */ apr_time_t textTime() const; /** @return last update to date time for properties */ apr_time_t propertyTime() const; /** @return the old version of the conflict file */ const char* oldConflictFile() const; /** @return the new version of the conflict file */ const char* newConflictFile() const; /** @return the working version of the conflict file */ const char* workingConflictFile() const; /** @return the property reject file */ const char* propertyRejectFile() const; /** @todo MORE ENTRIES FROM @ref svn_info_to IF NEEDED */ private: struct Data; Data * m; }; } #endif /* ----------------------------------------------------------------- * local variables: * eval: (load-file "../../rapidsvn-dev.el") * end: */ diff --git a/plugins/subversion/tests/svnrecursiveadd.cpp b/plugins/subversion/tests/svnrecursiveadd.cpp index 1fadc62e70..c0d0ee62f9 100644 --- a/plugins/subversion/tests/svnrecursiveadd.cpp +++ b/plugins/subversion/tests/svnrecursiveadd.cpp @@ -1,162 +1,162 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Fabian Wiesel * * * * 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 "svnrecursiveadd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define PATHETIC // A little motivator to make things work right :) #if defined(PATHETIC) const QString vcsTestDir0("testdir0"); const QString vcsTestDir1("testdir1"); const QString vcsTest_FileName0("foo"); const QString vcsTest_FileName1("bar"); const QString keywordText("text"); #else const QString vcsTestDir0("dvcs\t testdir"); // Directory containing whitespaces const QString vcsTestDir1("--help"); // Starting with hyphen for command-line tools const QString vcsTest_FileName0("foo\t bar"); const QString vcsTest_FileName1("--help"); const QString keywordText("Author:\nDate:\nCommit:\n------------------------------------------------------------------------\nr999999 | ehrman | 1989-11-09 18:53:00 +0100 (Thu, 09 Nov 1989) | 1 lines\nthe line\n"); // Text containing keywords of the various vcs-programs #endif const QString simpleText("It's foo!\n"); const QString simpleAltText("No, foo()! It's bar()!\n"); #define VERBOSE #if defined(VERBOSE) #define TRACE(X) kDebug() << X #else #define TRACE(X) { line = line; } #endif using namespace KDevelop; void validatingExecJob(VcsJob* j, VcsJob::JobStatus status = VcsJob::JobSucceeded) { QVERIFY(j); - // Print the commmands in full, for easier bug location + // Print the commands in full, for easier bug location #if 0 if (QLatin1String(j->metaObject()->className()) == "DVcsJob") { kDebug() << "Command: \"" << ((DVcsJob*)j)->getChildproc()->program() << ((DVcsJob*)j)->getChildproc()->workingDirectory(); kDebug() << "Output: \"" << ((DVcsJob*)j)->output(); } #endif if (!j->exec()) { qDebug() << "ooops, no exec"; kDebug() << j->errorString(); // On error, wait for key in order to allow manual state inspection #if 0 char c; std::cin.read(&c, 1); #endif } QCOMPARE(j->status(), status); } void verifiedWrite(KUrl const & url, QString const & contents) { QFile f(url.path()); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream filecontents(&f); filecontents << contents; filecontents.flush(); f.flush(); } void fillWorkingDirectory(QString const & dirname) { QDir dir(dirname); //we start it after repoInit, so we still have empty dvcs repo QVERIFY(dir.mkdir(vcsTestDir0)); QVERIFY(dir.cd(vcsTestDir0)); KUrl file0(dir.absoluteFilePath(vcsTest_FileName0)); QVERIFY(dir.mkdir(vcsTestDir1)); QVERIFY(dir.cd(vcsTestDir1)); KUrl file1(dir.absoluteFilePath(vcsTest_FileName1)); verifiedWrite(file0, simpleText); verifiedWrite(file1, keywordText); } void SvnRecursiveAdd::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void SvnRecursiveAdd::cleanupTestCase() { TestCore::shutdown(); } void SvnRecursiveAdd::test() { KTempDir reposDir; KProcess cmd; cmd.setWorkingDirectory(reposDir.name()); cmd << "svnadmin" << "create" << reposDir.name(); QCOMPARE(cmd.execute(10000), 0); QList plugins = Core::self()->pluginController()->allPluginsForExtension("org.kdevelop.IBasicVersionControl"); IBasicVersionControl* vcs = NULL; foreach(IPlugin* p, plugins) { qDebug() << "checking plugin" << p; ICentralizedVersionControl* icentr = p->extension(); if (!icentr) continue; if (icentr->name() == "Subversion") { vcs = icentr; break; } } qDebug() << "ok, got vcs" << vcs; QVERIFY(vcs); VcsLocation reposLoc; reposLoc.setRepositoryServer("file://" + reposDir.name()); KTempDir checkoutDir; KUrl checkoutLoc = checkoutDir.name(); kDebug() << "Checking out from " << reposLoc.repositoryServer() << " to " << checkoutLoc; qDebug() << "creating job"; VcsJob* job = vcs->createWorkingCopy( reposLoc, checkoutLoc ); validatingExecJob(job); qDebug() << "filling wc"; fillWorkingDirectory(checkoutDir.name()); KUrl addUrl = checkoutLoc; addUrl.addPath( vcsTestDir0 ); kDebug() << "Recursively adding files at " << addUrl; validatingExecJob(vcs->add(KUrl(addUrl), IBasicVersionControl::Recursive)); kDebug() << "Recursively reverting changes at " << addUrl; validatingExecJob(vcs->revert(KUrl(addUrl), IBasicVersionControl::Recursive)); } QTEST_KDEMAIN(SvnRecursiveAdd, GUI) diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index 171d0c3a70..92e444d2ec 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,398 +1,398 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include "testviewdebug.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 using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; //BEGIN TestViewFilterAction TestViewFilterAction::TestViewFilterAction( const QString &initialFilter, QObject* parent ) : KAction( parent ) , m_intialFilter(initialFilter) { setIcon(KIcon("view-filter")); setText(i18n("Filter...")); setToolTip(i18n("Insert wildcard patterns to filter the test view" " for matching test suites and cases.")); } QWidget* TestViewFilterAction::createWidget( QWidget* parent ) { KLineEdit* edit = new KLineEdit(parent); edit->setClickMessage(i18n("Filter...")); edit->setClearButtonShown(true); connect(edit, SIGNAL(textChanged(QString)), this, SIGNAL(filterChanged(QString))); if (!m_intialFilter.isEmpty()) { edit->setText(m_intialFilter); } return edit; } //END TestViwFilterAction static const char* sessionConfigGroup = "TestView"; static const char* filterConfigKey = "filter"; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { setWindowIcon(KIcon("preflight-verifier")); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, SIGNAL(activated(QModelIndex)), SLOT(doubleClicked(QModelIndex))); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); KAction* showSource = new KAction( KIcon("code-context"), i18n("Show Source"), this ); connect (showSource, SIGNAL(triggered(bool)), SLOT(showSource())); m_contextMenuActions << showSource; KAction* runSelected = new KAction( KIcon("system-run"), i18n("Run Selected Tests"), this ); connect (runSelected, SIGNAL(triggered(bool)), SLOT(runSelectedTests())); m_contextMenuActions << runSelected; addAction(plugin->actionCollection()->action("run_all_tests")); QString filterText; KConfigGroup config(ICore::self()->activeSession()->config(), sessionConfigGroup); if (config.hasKey(filterConfigKey)) { filterText = config.readEntry(filterConfigKey, QString()); } TestViewFilterAction* filterAction = new TestViewFilterAction(filterText, this); connect(filterAction, SIGNAL(filterChanged(QString)), m_filter, SLOT(setFilterFixedString(QString))); addAction(filterAction); IProjectController* pc = ICore::self()->projectController(); connect (pc, SIGNAL(projectClosed(KDevelop::IProject*)), SLOT(removeProject(KDevelop::IProject*))); ITestController* tc = ICore::self()->testController(); connect (tc, SIGNAL(testSuiteAdded(KDevelop::ITestSuite*)), SLOT(addTestSuite(KDevelop::ITestSuite*))); connect (tc, SIGNAL(testSuiteRemoved(KDevelop::ITestSuite*)), SLOT(removeTestSuite(KDevelop::ITestSuite*))); connect (tc, SIGNAL(testRunFinished(KDevelop::ITestSuite*, KDevelop::TestResult)), SLOT(updateTestSuite(KDevelop::ITestSuite*, KDevelop::TestResult))); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } debug() << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { debug() << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } KIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { debug() << result; switch (result) { case TestResult::NotRun: return KIcon("code-function"); case TestResult::Skipped: return KIcon("task-delegate"); case TestResult::Passed: return KIcon("dialog-ok-apply"); case TestResult::UnexpectedPass: - // This is a very rare occurence, so the icon should stand out + // This is a very rare occurrence, so the icon should stand out return KIcon("dialog-warning"); case TestResult::Failed: return KIcon("edit-delete"); case TestResult::ExpectedFail: return KIcon("dialog-ok"); case TestResult::Error: return KIcon("dialog-cancel"); default: return KIcon(""); } } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } return 0; } QStandardItem* TestView::itemForProject(IProject* project) { foreach (QStandardItem* item, m_model->findItems(project->name())) { return item; } return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } else if (item->parent()->parent() == 0) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // No sense in finding source code for projects. return; } else if (item->parent()->parent() == 0) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker(DUChain::lock()); Declaration* d = declaration.data(); if (!d) { return; } KUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().textRange().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); debug() << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); QStandardItem* suiteItem = new QStandardItem(KIcon("view-list-tree"), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (QString caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { QStandardItem* projectItem = new QStandardItem(KIcon("project-development"), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { delete itemForProject(project); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; } diff --git a/project/projectmodel.h b/project/projectmodel.h index 4644d14451..d1a18ee230 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -1,448 +1,448 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol 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_PROJECTMODEL_H #define KDEVPLATFORM_PROJECTMODEL_H #include #include "projectexport.h" #include #include template struct QPair; template class QList; namespace KDevelop { class IProject; class ProjectFolderItem; class ProjectBuildFolderItem; class ProjectFileItem; class ProjectTargetItem; class ProjectExecutableTargetItem; class ProjectLibraryTargetItem; class ProjectModel; class IndexedString; /** * Base class to implement the visitor pattern for the project item tree. * * Feel free to subclass it and add overloads for the methods corresponding * to the items you are interested in. * * Start visiting using one of the visit methods. */ class KDEVPLATFORMPROJECT_EXPORT ProjectVisitor { public: ProjectVisitor(); virtual ~ProjectVisitor(); /** * Visit the whole project model tree. */ virtual void visit( ProjectModel* ); /** * Visit the tree starting from the project root item. */ virtual void visit( IProject* ); /** * Visit the folder and anything it contains. */ virtual void visit( ProjectFolderItem* ); /** * Visit the file. */ virtual void visit( ProjectFileItem* ); /** * Visit the build folder and anything it contains. */ virtual void visit( ProjectBuildFolderItem* ); /** * Visit the target and all children it may contain. */ virtual void visit( ProjectExecutableTargetItem* ); /** * Visit the target and all children it may contain. */ virtual void visit( ProjectLibraryTargetItem* ); }; /** * Interface that allows a developer to implement the three basic types of * items you would see in a multi-project * \li Folder * \li Project * \li Custom Target * \li Library Target * \li Executable Target * \li File */ class KDEVPLATFORMPROJECT_EXPORT ProjectBaseItem { public: ProjectBaseItem( IProject*, const QString &name, ProjectBaseItem *parent = 0 ); virtual ~ProjectBaseItem(); enum ProjectItemType { BaseItem = 0 /** item is a base item */, BuildFolder = 1 /** item is a buildable folder */, Folder = 2 /** item is a folder */, ExecutableTarget = 3 /** item is an executable target */, LibraryTarget = 4 /** item is a library target */, Target = 5 /** item is a target */, File = 6 /** item is a file */, CustomProjectItemType = 100 /** type which should be used as base for custom types */ }; enum RenameStatus { RenameOk = 0, ExistingItemSameName = 1, ProjectManagerRenameFailed = 2, InvalidNewName = 3 }; /** @returns Returns the project that the item belongs to. */ IProject* project() const; /** @returns If this item is a folder, it returns a pointer to the folder, otherwise returns a 0 pointer. */ virtual ProjectFolderItem *folder() const; /** @returns If this item is a target, it returns a pointer to the target, otherwise returns a 0 pointer. */ virtual ProjectTargetItem *target() const; /** @returns If this item is a file, it returns a pointer to the file, otherwise returns a 0 pointer. */ virtual ProjectFileItem *file() const; /** @returns If this item is a file, it returns a pointer to the file, otherwise returns a 0 pointer. */ virtual ProjectExecutableTargetItem *executable() const; /** @returns Returns a list of the folders that have this object as the parent. */ QList folderList() const; /** @returns Returns a list of the targets that have this object as the parent. */ QList targetList() const; /** @returns Returns a list of the files that have this object as the parent. */ QList fileList() const; virtual bool lessThan( const KDevelop::ProjectBaseItem* ) const; static bool urlLessThan(KDevelop::ProjectBaseItem* item1, KDevelop::ProjectBaseItem* item2); /** @returns the @p row item in the list of children of this item or 0 if there is no such child. */ ProjectBaseItem* child( int row ) const; /** @returns the list of children of this item. */ QList children() const; /** @returns a valid QModelIndex for usage with the model API for this item. */ QModelIndex index() const; /** @returns The parent item if this item has one, else it return 0. */ virtual ProjectBaseItem* parent() const; /** @returns the displayed text of this item. */ QString text() const; /** @returns the row in the list of children of this items parent, or -1. */ int row() const; /** @returns the number of children of this item, or 0 if there are none. */ int rowCount() const; /** @returns the model to which this item belongs, or 0 if its not associated to a model. */ ProjectModel* model() const; /** * Adds a new child item to this item. */ void appendRow( ProjectBaseItem* item ); /** * Removes and deletes the item at the given @p row if there is one. */ void removeRow( int row ); /** * Removes and deletes the @p count items after the given @p row if there is one. */ void removeRows( int row, int count ); /** * Returns and removes the item at the given @p row if there is one. */ ProjectBaseItem* takeRow( int row ); /** @returns RTTI info, allows to know the type of item */ virtual int type() const; /** @returns a string to pass to KIcon as icon-name suitable to represent this item. */ virtual QString iconName() const; /** * Set the url of this item. * Note this function never renames the item in the project manager or on the filesystem, * it only changes the url and possibly the text nothing else. */ virtual void setUrl( const KUrl& ); /** Get the url of this item (if any) */ KUrl url() const; /** Gets the basename of this url (if any) * convenience function, returns the same as @c text() for most items */ QString baseName() const; /** * Renames the item to the new name. - * @returns status information wether the renaming succeeded. + * @returns status information whether the renaming succeeded. */ virtual RenameStatus rename( const QString& newname ); bool isProjectRoot() const; /** * Default flags: Qt::ItemIsEnabled | Qt::ItemIsSelectable * * @returns the flags supported by the item */ virtual Qt::ItemFlags flags(); /** * Sets what flags should be returned by ::flags() method. */ void setFlags(Qt::ItemFlags flags); protected: /** * Allows to change the displayed text of this item. * * Most items assume text == baseName so this is *not* public. * * @param text the new text */ void setText( const QString& text ); class ProjectBaseItemPrivate* const d_ptr; ProjectBaseItem( ProjectBaseItemPrivate& dd ); void setRow( int row ); void setModel( ProjectModel* model ); private: Q_DECLARE_PRIVATE(ProjectBaseItem) friend class ProjectModel; }; /** * Implementation of the ProjectBaseItem interface that is specific to a * folder */ class KDEVPLATFORMPROJECT_EXPORT ProjectFolderItem: public ProjectBaseItem { public: ProjectFolderItem( IProject*, const KUrl &dir, ProjectBaseItem *parent = 0 ); virtual ~ProjectFolderItem(); virtual void setUrl(const KUrl& ); virtual ProjectFolderItem *folder() const; ///Reimplemented from QStandardItem virtual int type() const; /** Get the folder name, equal to url().fileName() but faster (precomputed) */ QString folderName() const; /** @returns Returns whether this folder directly contains the specified file or folder. */ bool hasFileOrFolder(const QString& name) const; virtual QString iconName() const; virtual RenameStatus rename(const QString& newname); void propagateRename( const KUrl& newBase ) const; }; /** * Folder which contains buildable targets as part of a buildable project */ class KDEVPLATFORMPROJECT_EXPORT ProjectBuildFolderItem: public ProjectFolderItem { public: ProjectBuildFolderItem( IProject*, const KUrl &dir, ProjectBaseItem *parent = 0 ); ///Reimplemented from QStandardItem virtual int type() const; virtual QString iconName() const; }; /** * Object which represents a target in a build system. * * This object contains all properties specific to a target. */ class KDEVPLATFORMPROJECT_EXPORT ProjectTargetItem: public ProjectBaseItem { public: ProjectTargetItem( IProject*, const QString &name, ProjectBaseItem *parent = 0 ); ///Reimplemented from QStandardItem virtual int type() const; virtual ProjectTargetItem *target() const; virtual QString iconName() const; virtual void setUrl(const KUrl& ); }; /** * Object which represents an executable target in a build system. * * This object contains all properties specific to an executable. */ class KDEVPLATFORMPROJECT_EXPORT ProjectExecutableTargetItem: public ProjectTargetItem { public: ProjectExecutableTargetItem( IProject*, const QString &name, ProjectBaseItem *parent = 0 ); virtual ProjectExecutableTargetItem *executable() const; virtual int type() const; virtual KUrl builtUrl() const=0; virtual KUrl installedUrl() const=0; }; /** * Object which represents a library target in a build system. * * This object contains all properties specific to a library. */ class KDEVPLATFORMPROJECT_EXPORT ProjectLibraryTargetItem: public ProjectTargetItem { public: ProjectLibraryTargetItem(IProject* project, const QString &name, ProjectBaseItem *parent = 0 ); virtual int type() const; }; /** * Object which represents a file. */ class KDEVPLATFORMPROJECT_EXPORT ProjectFileItem: public ProjectBaseItem { public: ProjectFileItem( IProject*, const KUrl& file, ProjectBaseItem *parent = 0 ); ~ProjectFileItem(); ///Reimplemented from QStandardItem virtual int type() const; virtual ProjectFileItem *file() const; /** Get the file name, equal to url().fileName() but faster (precomputed) */ QString fileName() const; virtual void setUrl( const KUrl& ); virtual QString iconName() const; virtual RenameStatus rename(const QString& newname); /** * Get the indexed URL, which is often required for performant lookups * or memory efficient storage. */ IndexedString indexedUrl() const; }; /** * Class providing some convenience methods for accessing the project model * @todo: maybe switch to QAbstractItemModel, would make the implementation * for at least the checkbox-behaviour easier */ class KDEVPLATFORMPROJECT_EXPORT ProjectModel: public QAbstractItemModel { Q_OBJECT public: enum Roles { ProjectRole = Qt::UserRole+1 , ProjectItemRole , LastRole }; ProjectModel( QObject *parent = 0 ); virtual ~ProjectModel(); void resetModel(); void clear(); void appendRow( ProjectBaseItem* item ); void removeRow( int row ); ProjectBaseItem* takeRow( int row ); ProjectBaseItem* itemAt( int row ) const; QList topItems() const; QModelIndex pathToIndex(const QStringList& tofetch) const; QStringList pathFromIndex(const QModelIndex& index) const; QModelIndex indexFromItem( const ProjectBaseItem* item ) const; ProjectBaseItem* itemFromIndex( const QModelIndex& ) const; virtual int columnCount( const QModelIndex& parent = QModelIndex() ) const; virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; virtual QModelIndex parent( const QModelIndex& child ) const; virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); virtual bool insertColumns(int column, int count, const QModelIndex& parent = QModelIndex()); virtual bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); virtual Qt::ItemFlags flags(const QModelIndex& index) const; virtual Qt::DropActions supportedDropActions() const; /** * Returns all items for the given URL. */ QList itemsForUrl(const KUrl& url) const; /** * Returns first item for the given indexed URL. */ ProjectBaseItem* itemForUrl(const IndexedString& url) const; private: class ProjectModelPrivate* const d; friend class ProjectBaseItem; }; KDEVPLATFORMPROJECT_EXPORT QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ); KDEVPLATFORMPROJECT_EXPORT QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ); } Q_DECLARE_METATYPE(KDevelop::ProjectBaseItem*) Q_DECLARE_METATYPE(KDevelop::ProjectFolderItem*) Q_DECLARE_METATYPE(KDevelop::ProjectFileItem*) Q_DECLARE_METATYPE(KDevelop::ProjectLibraryTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectExecutableTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectTargetItem*) Q_DECLARE_METATYPE(KDevelop::ProjectBuildFolderItem*) Q_DECLARE_METATYPE(QList) #endif // KDEVPLATFORM_PROJECTMODEL_H diff --git a/shell/areadisplay.cpp b/shell/areadisplay.cpp index 3ca705c4df..dc6c8faf71 100644 --- a/shell/areadisplay.cpp +++ b/shell/areadisplay.cpp @@ -1,122 +1,122 @@ /*************************************************************************** * Copyright 2013 Aleix Pol Gonzalez * * * * 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 "areadisplay.h" #include "mainwindow.h" #include "workingsetcontroller.h" #include #include #include #include #include #include #include #include using namespace KDevelop; AreaDisplay::AreaDisplay(KDevelop::MainWindow* parent) : QWidget(parent) , m_mainWindow(parent) { setLayout(new QHBoxLayout); m_separator = new QLabel("|", this); m_separator->setEnabled(false); m_separator->setVisible(false); layout()->addWidget(m_separator); layout()->setContentsMargins(0, 0, 0, 0); layout()->addWidget(Core::self()->workingSetControllerInternal()->createSetManagerWidget(m_mainWindow)); m_button = new QToolButton(this); m_button->setToolTip(i18n( "Execute actions to change the area.
" - "An area is a a toolview configuration for a specific use case. " + "An area is a toolview configuration for a specific use case. " "From here you can also navigate back to the default code area.")); m_button->setAutoRaise(true); m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_button->setPopupMode(QToolButton::InstantPopup); layout()->addWidget(m_button); connect(parent, SIGNAL(areaChanged(Sublime::Area*)), SLOT(newArea(Sublime::Area*))); } void AreaDisplay::newArea(Sublime::Area* area) { if(m_button->menu()) m_button->menu()->deleteLater(); Sublime::Area* currentArea = m_mainWindow->area(); m_button->setText(currentArea->title()); m_button->setIcon(KIcon(currentArea->iconName())); QMenu* m = new QMenu(m_button); m->addActions(area->actions()); if(currentArea->objectName() != "code") { if(!m->actions().isEmpty()) m->addSeparator(); m->addAction(KIcon("document-edit"), i18n("Back to code"), this, SLOT(backToCode()), QKeySequence(Qt::AltModifier | Qt::Key_Backspace)); } m_button->setMenu(m); //remove the additional widgets we might have added for the last area QBoxLayout* l = qobject_cast(layout()); if(l->count()>=4) { QLayoutItem* item = l->takeAt(0); delete item->widget(); delete item; } QWidget* w = Core::self()->workingSetControllerInternal()->createSetManagerWidget(m_mainWindow, area); w->installEventFilter(this); l->insertWidget(0, w); } bool AreaDisplay::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Show) { m_separator->setVisible(true); } else if (event->type() == QEvent::QEvent::Hide) { m_separator->setVisible(false); } return QObject::eventFilter(obj, event); } void AreaDisplay::backToCode() { ICore::self()->uiController()->switchToArea("code", IUiController::ThisWindow); } QSize AreaDisplay::minimumSizeHint() const { QSize hint = QWidget::minimumSizeHint(); hint = hint.boundedTo(QSize(hint.width(), m_mainWindow->menuBar()->height()-1)); return hint; } QSize AreaDisplay::sizeHint() const { QSize hint = QWidget::sizeHint(); hint = hint.boundedTo(QSize(hint.width(), m_mainWindow->menuBar()->height()-1)); return hint; } diff --git a/shell/debugcontroller.cpp b/shell/debugcontroller.cpp index 0a655ea93f..4c56d93ad7 100644 --- a/shell/debugcontroller.cpp +++ b/shell/debugcontroller.cpp @@ -1,536 +1,536 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "debugcontroller.h" #include #include #include #include #include #include #include #include #include #include "../interfaces/idocument.h" #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/ipartcontroller.h" #include "../interfaces/contextmenuextension.h" #include "../interfaces/context.h" #include "../language/interfaces/editorcontext.h" #include "../sublime/view.h" #include "../sublime/mainwindow.h" #include "../sublime/area.h" #include "core.h" #include "uicontroller.h" #include "../debugger/breakpoint/breakpointmodel.h" #include "../debugger/breakpoint/breakpointwidget.h" #include "../debugger/variable/variablewidget.h" #include "../debugger/framestack/framestackmodel.h" #include "../debugger/framestack/framestackwidget.h" #include namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} virtual QWidget* create(QWidget *parent = 0) { return new T(m_controller, parent); } virtual QString id() const { return m_id; } virtual Qt::DockWidgetArea defaultPosition() { return m_defaultArea; } virtual void viewCreated(Sublime::View* view) { if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts work, but they don't need any toolbar. So, suppress toolbar action. */ virtual QList toolBarActions( QWidget* viewWidget ) const { Q_UNUSED(viewWidget); return QList(); } private: DebugController* m_controller; QString m_id; Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), m_continueDebugger(0), m_stopDebugger(0), m_interruptDebugger(0), m_runToCursor(0), m_jumpToCursor(0), m_stepOver(0), m_stepIntoInstruction(0), m_stepInto(0), m_stepOverInstruction(0), m_stepOut(0), m_toggleBreakpoint(0), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)), m_uiInitialized(false) { setComponentData(KComponentData("kdevdebugger")); setXMLFile("kdevdebuggershellui.rc"); } void DebugController::initialize() { } void DebugController::initializeUi() { if (m_uiInitialized) return; m_uiInitialized = true; if((Core::self()->setupFlags() & Core::NoUi)) return; setupActions(); ICore::self()->uiController()->addToolView( i18n("Frame Stack"), new DebuggerToolFactory( this, "org.kdevelop.debugger.StackView", Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Breakpoints"), new DebuggerToolFactory( this, "org.kdevelop.debugger.BreakpointsView", Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Variables"), new DebuggerToolFactory( this, "org.kdevelop.debugger.VariablesView", Qt::LeftDockWidgetArea)); foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) partAdded(p); connect(KDevelop::ICore::self()->partController(), SIGNAL(partAdded(KParts::Part*)), this, SLOT(partAdded(KParts::Part*))); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged("ended"); } void DebugController::cleanup() { if (m_currentSession) m_currentSession.data()->stopDebugger(); } DebugController::~DebugController() { } BreakpointModel* DebugController::breakpointModel() { return m_breakpointModel; } VariableCollection* DebugController::variableCollection() { return m_variableCollection; } void DebugController::partAdded(KParts::Part* part) { if (KTextEditor::Document* doc = dynamic_cast(part)) { KTextEditor::MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); KAction* action = m_continueDebugger = new KAction(KIcon("media-playback-start"), i18n("&Continue"), this); action->setToolTip( i18n("Continue application execution") ); action->setWhatsThis( i18n("Continues the execution of your application in the " "debugger. This only takes effect when the application " "has been halted by the debugger (i.e. a breakpoint has " "been activated or the interrupt was pressed).") ); ac->addAction("debug_continue", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(run())); #if 0 m_restartDebugger = action = new KAction(KIcon("media-seek-backward"), i18n("&Restart"), this); action->setToolTip( i18n("Restart program") ); action->setWhatsThis( i18n("Restarts applications from the beginning.") ); action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger())); ac->addAction("debug_restart", action); #endif m_interruptDebugger = action = new KAction(KIcon("media-playback-pause"), i18n("Interrupt"), this); action->setToolTip( i18n("Interrupt application") ); action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(interruptDebugger())); ac->addAction("debug_pause", action); m_runToCursor = action = new KAction(KIcon("debug-run-cursor"), i18n("Run to &Cursor"), this); action->setToolTip( i18n("Run to cursor") ); action->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(runToCursor())); ac->addAction("debug_runtocursor", action); m_jumpToCursor = action = new KAction(KIcon("debug-execute-to-cursor"), i18n("Set E&xecution Position to Cursor"), this); action->setToolTip( i18n("Jump to cursor") ); action->setWhatsThis(i18n("Continue execution from the current cursor position.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(jumpToCursor())); ac->addAction("debug_jumptocursor", action); m_stepOver = action = new KAction(KIcon("debug-step-over"), i18n("Step &Over"), this); action->setShortcut(Qt::Key_F10); action->setToolTip( i18n("Step over the next line") ); action->setWhatsThis( i18n("Executes one line of source in the current source file. " "If the source line is a call to a function the whole " "function is executed and the app will stop at the line " "following the function call.") ); connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOver())); ac->addAction("debug_stepover", action); m_stepOverInstruction = action = new KAction(KIcon("debug-step-instruction"), i18n("Step over Ins&truction"), this); action->setToolTip( i18n("Step over instruction") ); action->setWhatsThis(i18n("Steps over the next assembly instruction.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOverInstruction())); ac->addAction("debug_stepoverinst", action); m_stepInto = action = new KAction(KIcon("debug-step-into"), i18n("Step &Into"), this); action->setShortcut(Qt::Key_F11); action->setToolTip( i18n("Step into the next statement") ); action->setWhatsThis( i18n("Executes exactly one line of source. If the source line " "is a call to a function then execution will stop after " "the function has been entered.") ); connect(action, SIGNAL(triggered(bool)), this, SLOT(stepInto())); ac->addAction("debug_stepinto", action); m_stepIntoInstruction = action = new KAction(KIcon("debug-step-into-instruction"), i18n("Step into I&nstruction"), this); action->setToolTip( i18n("Step into instruction") ); action->setWhatsThis(i18n("Steps into the next assembly instruction.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(stepIntoInstruction())); ac->addAction("debug_stepintoinst", action); m_stepOut = action = new KAction(KIcon("debug-step-out"), i18n("Step O&ut"), this); action->setShortcut(Qt::Key_F12); action->setToolTip( i18n("Step out of the current function") ); action->setWhatsThis( i18n("Executes the application until the currently executing " "function is completed. The debugger will then display " "the line after the original call to that function. If " "program execution is in the outermost frame (i.e. in " "main()) then this operation has no effect.") ); connect(action, SIGNAL(triggered(bool)), this, SLOT(stepOut())); ac->addAction("debug_stepout", action); m_toggleBreakpoint = action = new KAction(KIcon("script-error"), i18n("Toggle Breakpoint"), this); action->setShortcut( i18n("Ctrl+Alt+B") ); action->setToolTip(i18n("Toggle breakpoint")); action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleBreakpoint())); ac->addAction("debug_toggle_breakpoint", action); } void DebugController::addSession(IDebugSession* session) { kDebug() << session; Q_ASSERT(session->variableController()); Q_ASSERT(session->breakpointController()); Q_ASSERT(session->frameStackModel()); //TODO support multiple sessions if (m_currentSession) { m_currentSession.data()->stopDebugger(); } m_currentSession = session; connect(session, SIGNAL(stateChanged(KDevelop::IDebugSession::DebuggerState)), SLOT(debuggerStateChanged(KDevelop::IDebugSession::DebuggerState))); connect(session, SIGNAL(showStepInSource(KUrl,int,QString)), SLOT(showStepInSource(KUrl,int))); connect(session, SIGNAL(clearExecutionPoint()), SLOT(clearExecutionPoint())); connect(session, SIGNAL(raiseFramestackViews()), SIGNAL(raiseFramestackViews())); updateDebuggerState(session->state(), session); emit currentSessionChanged(session); if((Core::self()->setupFlags() & Core::NoUi)) return; Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow->area()->objectName() != "debug") { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea("debug", IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, SIGNAL(areaChanged(Sublime::Area*)), SLOT(areaChanged(Sublime::Area*))); } } void DebugController::clearExecutionPoint() { kDebug(); foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if (!iface) continue; QHashIterator it = iface->marks(); while (it.hasNext()) { KTextEditor::Mark* mark = it.next().value(); if( mark->type & KTextEditor::MarkInterface::Execution ) iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution ); } } } void DebugController::showStepInSource(const KUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); kDebug() << url << lineNum; Q_ASSERT(dynamic_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( url, lineNum )); KDevelop::IDocument* document = KDevelop::ICore::self() ->documentController() ->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus); if( !document ) return; KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if( !iface ) return; document->textDocument()->blockSignals(true); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); document->textDocument()->blockSignals(false); } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(dynamic_cast(sender())); IDebugSession* session = static_cast(sender()); kDebug() << session << state << "current" << m_currentSession.data(); if (session == m_currentSession.data()) { updateDebuggerState(state, session); } if (state == IDebugSession::EndedState) { if (session == m_currentSession.data()) { m_currentSession.clear(); emit currentSessionChanged(0); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != "code") { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea("code", IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } ICore::self()->uiController()->findToolView(i18n("Debug"), 0, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; kDebug() << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: kDebug() << "new state: stopped"; stateChanged("stopped"); - //m_restartDebugger->setEnabled(session->restartAvaliable()); + //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: kDebug() << "new state: paused"; stateChanged("paused"); - //m_restartDebugger->setEnabled(session->restartAvaliable()); + //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: kDebug() << "new state: active"; stateChanged("active"); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: kDebug() << "new state: ended"; stateChanged("ended"); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } ContextMenuExtension DebugController::contextMenuExtension( Context* context ) { ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; KDevelop::EditorContext *econtext = dynamic_cast(context); if (!econtext) return menuExt; if (m_currentSession && m_currentSession.data()->isRunning()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor); } if (econtext->url().isLocalFile()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint); } return menuExt; } #if 0 void DebugController::restartDebugger() { if (m_currentSession) { m_currentSession.data()->restartDebugger(); } } #endif void DebugController::stopDebugger() { if (m_currentSession) { m_currentSession.data()->stopDebugger(); } } void DebugController::interruptDebugger() { if (m_currentSession) { m_currentSession.data()->interruptDebugger(); } } void DebugController::run() { if (m_currentSession) { m_currentSession.data()->run(); } } void DebugController::runToCursor() { if (m_currentSession) { m_currentSession.data()->runToCursor(); } } void DebugController::jumpToCursor() { if (m_currentSession) { m_currentSession.data()->jumpToCursor(); } } void DebugController::stepOver() { if (m_currentSession) { m_currentSession.data()->stepOver(); } } void DebugController::stepIntoInstruction() { if (m_currentSession) { m_currentSession.data()->stepIntoInstruction(); } } void DebugController::stepInto() { if (m_currentSession) { m_currentSession.data()->stepInto(); } } void DebugController::stepOverInstruction() { if (m_currentSession) { m_currentSession.data()->stepOverInstruction(); } } void DebugController::stepOut() { if (m_currentSession) { m_currentSession.data()->stepOut(); } } void DebugController::areaChanged(Sublime::Area* newArea) { if (newArea->objectName()!="debug") { stopDebugger(); } } void DebugController::toggleBreakpoint() { if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = document->cursorPosition(); if (!cursor.isValid()) return; breakpointModel()->toggleBreakpoint(document->url(), cursor); } } const QPixmap* DebugController::executionPointPixmap() { static QPixmap pixmap=KIcon("go-next").pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/shell/patchdocument.cpp b/shell/patchdocument.cpp index a97b6e7049..724ce2af92 100644 --- a/shell/patchdocument.cpp +++ b/shell/patchdocument.cpp @@ -1,117 +1,117 @@ /* 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 "patchdocument.h" #include #include #include #include #include #include #include #include "core.h" #include #include #include #include #include "partcontroller.h" #include using namespace KDevelop; IDocument* PatchDocumentFactory::create(const KUrl& url, ICore* core) { return new PatchDocument(url, core); } PatchDocument::PatchDocument(const KUrl& url, ICore* core) : PartDocument(url, core, "Kompare/ViewPart") {} void PatchDocument::setDiff(const QString& from, const KUrl& to) { QMap parts=PartDocument::partForView(); foreach(KParts::Part* part, parts) { KompareInterface* iface=qobject_cast(part); Q_ASSERT(iface); iface->compareFileString(to, from); } } QWidget* createNavPart(QWidget* parent, KParts::Part* part) { KService::List offers = KServiceTypeTrader::self()->query( "KParts/ReadOnlyPart", "'Kompare/NavigationPart' in ServiceTypes" ); KService::Ptr ptr=offers.first(); KParts::Factory *factory = static_cast ( KLibLoader::self()->factory( QFile::encodeName( ptr->library() ) ) ); KParts::Part* navPart=factory->createPart(parent, parent,"KompareNavTreePart", QStringList("KParts::ReadOnlyPart" )); QObject::connect(part, SIGNAL(modelsChanged(const Diff2::DiffModelList*)), navPart, SLOT(slotModelsChanged(const Diff2::DiffModelList*)) ); QObject::connect(part, SIGNAL(kompareInfo(Kompare::Info*)), navPart, SLOT(slotKompareInfo(Kompare::Info*)) ); QObject::connect(navPart, SIGNAL(selectionChanged(const Diff2::DiffModel*,const Diff2::Difference*)), part, SIGNAL(selectionChanged(const Diff2::DiffModel*,const Diff2::Difference*)) ); QObject::connect(part, SIGNAL(setSelection(const Diff2::DiffModel*,const Diff2::Difference*)), navPart, SLOT(slotSetSelection(const Diff2::DiffModel*,const Diff2::Difference*)) ); QObject::connect(navPart, SIGNAL(selectionChanged(const Diff2::Difference*)), part, SIGNAL(selectionChanged(const Diff2::Difference*)) ); QObject::connect(part, SIGNAL(setSelection(const Diff2::Difference*)), navPart, SLOT(slotSetSelection(const Diff2::Difference*)) ); QObject::connect( part, SIGNAL(applyDifference(bool)), navPart, SLOT(slotApplyDifference(bool)) ); QObject::connect( part, SIGNAL(applyAllDifferences(bool)), navPart, SLOT(slotApplyAllDifferences(bool)) ); QObject::connect( part, SIGNAL(applyDifference(const Diff2::Difference*,bool)), navPart, SLOT(slotApplyDifference(const Diff2::Difference*,bool)) ); return navPart->widget(); } QWidget* PatchDocument::createViewWidget(QWidget* parent) { - //Overriden so that we don't get openUrl triggered by createPart + //Overridden so that we don't get openUrl triggered by createPart KParts::Part *part = Core::self()->partControllerInternal()->createPart("text/x-patch", "Kompare/ViewPart"); if( part ) { QSplitter* split = new QSplitter(parent); QWidget* nav=createNavPart(split, part); if(url().scheme()!="kdevvcs") { KParts::ReadOnlyPart *ropart=dynamic_cast(part); ropart->openUrl(url()); } Core::self()->partController()->addPart(part); nav->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); split->setOrientation(Qt::Vertical); split->addWidget(nav); split->addWidget(part->widget()); addPartForView(split, part); return split; } return 0; } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index f02bebbfe3..1836a7ac74 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,997 +1,997 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include #include #include #include #include using namespace KDevelop; QString RunController::LaunchConfigurationsGroup = "Launch"; QString RunController::LaunchConfigurationsListEntry = "Launch Configurations"; static QString CurrentLaunchConfigProjectEntry = "Current Launch Config Project"; static QString CurrentLaunchConfigNameEntry = "Current Launch Config GroupName"; static QString ConfiguredFromProjectItemEntry = "Configured from ProjectItem"; typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} virtual KIcon icon() const { return KIcon("tools-report-bug"); } virtual QString id() const { return "debug"; } virtual QString name() const { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} virtual KIcon icon() const { return KIcon("office-chart-area"); } virtual QString id() const { return "profile"; } virtual QString name() const { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} virtual KIcon icon() const { return KIcon("system-run"); } virtual QString id() const { return "execute"; } virtual QString name() const { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; KAction* stopAction; KActionMenu* stopJobsMenu; KAction* profileAction; KAction* runAction; KAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); LaunchConfiguration* l = static_cast( qVariantValue( currentTargetAction->currentAction()->data() ) ); grp.writeEntry( CurrentLaunchConfigProjectEntry, l->project() ? l->project()->name() : "" ); grp.writeEntry( CurrentLaunchConfigNameEntry, l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QString("%1 : %2").arg( l->project()->name()).arg(l->name()); } else { label = QString("%1" ).arg(l->name()); } return label; } void launchAs( int id ) { //kDebug() << "Launching id:" << id; QPair info = launchAsInfo[id]; //kDebug() << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //kDebug() << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = 0; foreach (ILauncher *l, type->launchers()) { - //kDebug() << "avaliable launcher" << l << l->id() << l->supportedModes(); + //kDebug() << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = 0; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(ConfiguredFromProjectItemEntry, QStringList()); if (l->type() == type && path == itemPath) { - kDebug() << "allready generated ilaunch" << path; + kDebug() << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(ConfiguredFromProjectItemEntry, itemPath); //kDebug() << "created config, launching"; } else { //kDebug() << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); QString currentLaunchProject = launchGrp.readEntry( CurrentLaunchConfigProjectEntry, "" ); QString currentLaunchName = launchGrp.readEntry( CurrentLaunchConfigNameEntry, "" ); LaunchConfiguration* l = 0; if( currentTargetAction->currentAction() ) { l = static_cast( qVariantValue( currentTargetAction->currentAction()->data() ) ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { kDebug() << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().first()->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; KAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, RunController::LaunchConfigurationsGroup); QStringList configs = group.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry, "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { kWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return 0; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName("RunController"); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = 0; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = 0; d->contextItem = 0; d->executeMode = 0; d->debugMode = 0; d->profileMode = 0; if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( qVariantValue( a->data() ) ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = 0; delete d->profileMode; d->profileMode = 0; delete d->debugMode; d->debugMode = 0; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), this, SLOT(slotProjectOpened(KDevelop::IProject*))); connect(Core::self()->projectController(), SIGNAL(projectClosing(KDevelop::IProject*)), this, SLOT(slotProjectClosing(KDevelop::IProject*))); connect(Core::self()->projectController(), SIGNAL(projectConfigurationChanged(KDevelop::IProject*)), this, SLOT(slotRefreshProject(KDevelop::IProject*))); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { kDebug() << "execute called without launch config!"; return 0; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} kDebug() << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); kDebug() << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), ""); return 0; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { KAction *action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new KAction (i18n("Configure Launches..."), this); ac->addAction("configure_launches", action); action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, SIGNAL(triggered(bool)), SLOT(configureLaunches())); d->runAction = new KAction( KIcon("system-run"), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); d->runAction->setShortcut(Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction("run_execute", d->runAction); connect(d->runAction, SIGNAL(triggered(bool)), this, SLOT(slotExecute())); d->dbgAction = new KAction( KIcon("debug-run"), i18n("Debug Launch"), this); d->dbgAction->setShortcut(Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction("run_debug", d->dbgAction); connect(d->dbgAction, SIGNAL(triggered(bool)), this, SLOT(slotDebug())); Core::self()->uiControllerInternal()->area(0, "code")->addAction(d->dbgAction); d->profileAction = new KAction( KIcon(""), i18n("Profile Launch"), this); d->profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); d->profileAction->setStatusTip(i18n("Profile current launch")); d->profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); ac->addAction("run_profile", d->profileAction); connect(d->profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new KAction( KIcon("process-stop"), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but thats taken by the ksysguard desktop shortcut action->setShortcut(QKeySequence("Ctrl+Shift+Escape")); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction("run_stop_all", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(stopAllProcesses())); Core::self()->uiControllerInternal()->area(0, "debug")->addAction(action); action = d->stopJobsMenu = new KActionMenu( KIcon("process-stop"), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction("run_stop_menu", action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction("run_default_target", d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().first()->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "debug" ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "profile" ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "execute" ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return 0; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 kWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { KAction* stopJobAction = 0; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new KAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, SIGNAL(triggered(bool)), SLOT(slotKillJob())); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, SIGNAL(finished(KJob*)), SLOT(finished(KJob*)) ); connect( job, SIGNAL(destroyed(QObject*)), SLOT(jobDestroyed(QObject*)) ); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; foreach (KJob* job, d->jobs.keys()) { if (!job->isSuspended()) { running = true; break; } } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { kWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { KAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 KDialog* dialog = new KDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); dialog->setButtons(KDialog::Close); KMessageBox::createKMessageBox(dialog, QMessageBox::Warning, job->errorString(), QStringList(), QString(), 0, KMessageBox::NoExec ); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { kWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { d->launchConfigurations.removeAll( l ); delete l; } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return 0; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } } connect( l, SIGNAL(nameChanged(LaunchConfiguration*)), SLOT(launchChanged(LaunchConfiguration*)) ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( LaunchConfigurationsGroup ); } else { launcherGroup = Core::self()->activeSession()->config()->group( LaunchConfigurationsGroup ); } QStringList configs = launcherGroup.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( RunController::LaunchConfigurationsListEntry, configs ); launcherGroup.sync(); foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( qVariantValue( a->data() ) ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if( !defaultLaunch() ) { kWarning() << "no default launch!"; return; } execute( runMode, defaultLaunch() ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( qVariantValue( a->data() ) ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QString("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( RunController::LaunchConfigurationsGroup ); } else { launchGroup = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); } QStringList configs = launchGroup.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); uint num = 0; QString baseName = "Launch Configuration"; while( configs.contains( QString( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QString( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry, cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry, type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( RunController::LaunchConfigurationsListEntry, configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, SIGNAL(mapped(int)), SLOT(launchAs(int)) ); d->launchAsInfo.clear(); d->contextItem = 0; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes.values() ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); KAction* act = new KAction( d->launchAsMapper ); act->setText( type->name() ); kDebug() << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, SIGNAL(triggered()), d->launchAsMapper, SLOT(map()) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; QVariant status = index.data(Qt::UserRole+1); // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "runcontroller.moc" diff --git a/shell/tests/projectcontrollertest.cpp b/shell/tests/projectcontrollertest.cpp index d1d5136df9..6c28774ea7 100644 --- a/shell/tests/projectcontrollertest.cpp +++ b/shell/tests/projectcontrollertest.cpp @@ -1,510 +1,510 @@ /*************************************************************************** * Copyright 2008 Manuel Breugelmans * * * * 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 "projectcontrollertest.h" #include #include #include #include #include #include #include #include #include "../core.h" #include "../projectcontroller.h" #include "../project.h" #include "../../interfaces/iplugin.h" #include "../../project/interfaces/iprojectfilemanager.h" #include "../project/projectmodel.h" using namespace KDevelop; using QTest::kWaitForSignal; Q_DECLARE_METATYPE(KDevelop::IProject*) namespace { class DialogProviderFake : public IProjectDialogProvider { Q_OBJECT public: DialogProviderFake() : m_reopen(true) {} virtual ~DialogProviderFake() {} bool m_reopen; public slots: virtual KUrl askProjectConfigLocation(const KUrl& startUrl) { return KUrl(); } virtual bool userWantsReopen() { return m_reopen; } }; } /*! A Filemanager plugin that allows you to setup a file & directory structure */ class FakeFileManager : public IPlugin, public IProjectFileManager { Q_OBJECT Q_INTERFACES(KDevelop::IProjectFileManager) public: FakeFileManager(QObject*, const QVariantList&) : IPlugin(KGlobal::mainComponent(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } FakeFileManager() : IPlugin(KGlobal::mainComponent(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } virtual ~FakeFileManager() {} virtual Features features() const { return IProjectFileManager::Files | IProjectFileManager::Folders; } QMap m_filesInFolder; // initialize QMap m_subFoldersInFolder; /*! Setup this manager such that @p folder contains @p file */ void addFileToFolder(KUrl folder, KUrl file) { if (!m_filesInFolder.contains(folder)) { m_filesInFolder[folder] = KUrl::List(); } m_filesInFolder[folder] << file; } /*! Setup this manager such that @p folder has @p subFolder */ void addSubFolderTo(KUrl folder, KUrl subFolder) { if (!m_subFoldersInFolder.contains(folder)) { m_subFoldersInFolder[folder] = KUrl::List(); } m_subFoldersInFolder[folder] << subFolder; } virtual QList parse(ProjectFolderItem *dom) { KUrl::List files = m_filesInFolder[dom->url()]; foreach (const KUrl& file, files) { new ProjectFileItem(dom->project(), file, dom); } KUrl::List folderUrls = m_subFoldersInFolder[dom->url()]; QList folders; foreach (const KUrl& folderUrl, folderUrls) { folders << new ProjectFolderItem(dom->project(), folderUrl, dom); } return folders; } virtual ProjectFolderItem *import(IProject *project) { ProjectFolderItem* it = new ProjectFolderItem(project, project->folder()); it->setProjectRoot(true); return it; } virtual ProjectFolderItem* addFolder(const KUrl& folder, ProjectFolderItem *parent) { return 0; } virtual ProjectFileItem* addFile(const KUrl& folder, ProjectFolderItem *parent) { return 0; } virtual bool removeFolder(ProjectFolderItem *folder) { return false; } virtual bool removeFile(ProjectFileItem *file) { return false; } virtual bool renameFile(ProjectFileItem* oldFile, const KUrl& newFile) { return false; } virtual bool renameFolder(ProjectFolderItem* oldFolder, const KUrl& newFolder ) { return false; } virtual bool reload(ProjectBaseItem* item) { return false; } }; K_PLUGIN_FACTORY(FakeFileManagerFactory, registerPlugin(); ) K_EXPORT_PLUGIN(FakeFileManager()) ////////////////////// Fixture /////////////////////////////////////////////// void ProjectControllerTest::initTestCase() { AutoTestShell::init(); TestCore::initialize(); qRegisterMetaType(); m_core = Core::self(); m_scratchDir = QDir(QDir::tempPath()); m_scratchDir.mkdir("prjctrltest"); m_scratchDir.cd("prjctrltest"); } void ProjectControllerTest::cleanupTestCase() { TestCore::shutdown(); } void ProjectControllerTest::init() { m_projName = "foo"; m_projFileUrl = writeProjectConfig(m_projName); m_projCtrl = m_core->projectControllerInternal(); m_tmpConfigs << m_projFileUrl; m_projFolder = KUrl(m_scratchDir.absolutePath() + '/'); } void ProjectControllerTest::cleanup() { // also close any opened projects as we do not get a clean fixture, // following tests should start off clean. foreach(IProject* p, m_projCtrl->projects()) { m_projCtrl->closeProject(p); } foreach(const KUrl &cfg, m_tmpConfigs) { QFile::remove(cfg.pathOrUrl()); } qDeleteAll(m_fileManagerGarbage); m_fileManagerGarbage.clear(); } ////////////////////// Commands ////////////////////////////////////////////// #define WAIT_FOR_OPEN_SIGNAL \ {\ bool gotSignal = kWaitForSignal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)), 30000);\ QVERIFY2(gotSignal, "Timeout while waiting for opened signal");\ } void(0) void ProjectControllerTest::openProject() { QSignalSpy* spy = createOpenedSpy(); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj);QVERIFY(proj); assertSpyCaughtProject(spy, proj); QCOMPARE(proj->projectFileUrl(), m_projFileUrl); QCOMPARE(proj->folder(), KUrl(m_scratchDir.absolutePath()+'/')); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); } void ProjectControllerTest::closeProject() { QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; IProject* proj = m_projCtrl->findProjectByName(m_projName); Q_ASSERT(proj); QSignalSpy* spy1 = createClosedSpy(); QSignalSpy* spy2 = createClosingSpy(); m_projCtrl->closeProject(proj); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 0); assertProjectClosed(proj); assertSpyCaughtProject(spy1, proj); assertSpyCaughtProject(spy2, proj); } void ProjectControllerTest::openCloseOpen() { QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; IProject* proj; assertProjectOpened(m_projName, proj); QVERIFY(m_projCtrl->closeProject(proj)); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 1); assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::reopen() { m_projCtrl->setDialogProvider(new DialogProviderFake); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::reopenWhileLoading() { // Open the same project again while the first is still // loading. The second open request should be blocked. m_projCtrl->setDialogProvider(new DialogProviderFake); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); QVERIFY(!m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; // wait a bit for a second signal, this should timeout QVERIFY2(!kWaitForSignal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)), 100), "Received 2 projectOpened signals."); QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::openMultiple() { QString secondProj("bar"); KUrl secondCfgUrl = writeProjectConfig(secondProj); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->openProject(secondCfgUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 2); IProject *proj1, *proj2; assertProjectOpened(m_projName, proj1); assertProjectOpened(secondProj, proj2); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->isProjectNameUsed("bar")); QCOMPARE(spy->size(), 2); IProject* emittedProj1 = (*spy)[0][0].value(); IProject* emittedProj2 = (*spy)[1][0].value(); QCOMPARE(emittedProj1, proj1); QCOMPARE(emittedProj2, proj2); m_tmpConfigs << secondCfgUrl; } /*! Verify that the projectmodel contains a single project. Put this project's * ProjectFolderItem in the output parameter @p RootItem */ #define ASSERT_SINGLE_PROJECT_IN_MODEL(rootItem) \ {\ QCOMPARE(1,m_projCtrl->projectModel()->rowCount()); \ QModelIndex projIndex = m_projCtrl->projectModel()->index(0,0); \ QVERIFY(projIndex.isValid()); \ ProjectBaseItem* i = m_projCtrl->projectModel()->item( projIndex ); \ QVERIFY(i); \ QVERIFY(i->folder()); \ rootItem = i->folder();\ } void(0) -/*! Verify that the the projectitem @p item has a single child item +/*! Verify that the projectitem @p item has a single child item * named @p name with url @p url. @p subFolder is an output parameter * that contains the sub-folder projectitem. */ #define ASSERT_SINGLE_SUBFOLDER_IN(item, name, url__, subFolder) \ {\ QCOMPARE(1,item->rowCount());\ QCOMPARE(1, item->folderList().size());\ ProjectFolderItem* fo = item->folderList()[0];\ QVERIFY(fo);\ QCOMPARE(url__, fo->url());\ QCOMPARE(QString(name), fo->data(Qt::DisplayRole).toString());\ subFolder = fo;\ } void(0) #define ASSERT_SINGLE_FILE_IN(rootFolder, name, url__, fileItem)\ {\ QCOMPARE(1,rootFolder->rowCount());\ QCOMPARE(1, rootFolder->fileList().size());\ ProjectFileItem* fi__ = rootFolder->fileList()[0];\ QVERIFY(fi__);\ QCOMPARE(url__, fi__->url());\ QCOMPARE(QString(name), fi__->data(Qt::DisplayRole).toString());\ fileItem = fi__;\ } void(0) // command void ProjectControllerTest::emptyProject() { // verify that the project model contains a single top-level folder after loading // an empty project assertEmptyProjectModel(); Project* proj = new Project(); FakeFileManager* fileMng = createFileManager(); Q_ASSERT(fileMng); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project is empty QCOMPARE(0,rootFolder->rowCount()); QCOMPARE(m_projName, rootFolder->data(Qt::DisplayRole).toString()); QCOMPARE(m_projFolder, rootFolder->url()); } // command void ProjectControllerTest::singleFile() { // verify that the project model contains a single file in the // top folder. First setup a FakeFileManager with this file Project* proj = new Project(); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); KUrl fileUrl = KUrl(m_projFolder, "foobar"); fileMng->addFileToFolder(m_projFolder, fileUrl); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ProjectFileItem* fi; ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", fileUrl, fi); QCOMPARE(0,fi->rowCount()); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", fileUrl, fi); } // command void ProjectControllerTest::singleDirectory() { // verify that the project model contains a single folder in the // top folder. First setup a FakeFileManager with this folder Project* proj = new Project(); KUrl folderUrl = KUrl(m_projFolder, "foobar/"); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderUrl); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project contains a single subfolder ProjectFolderItem* sub; ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); QCOMPARE(0,sub->rowCount()); } // command void ProjectControllerTest::fileInSubdirectory() { // verify that the project model contains a single file in a subfolder // First setup a FakeFileManager with this folder + file Project* proj = new Project(); KUrl folderUrl = KUrl(m_projFolder, "foobar/"); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderUrl); KUrl fileUrl = KUrl(folderUrl, "zoo"); fileMng->addFileToFolder(folderUrl, fileUrl); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ProjectFolderItem* sub; ProjectFileItem* file; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",fileUrl,file); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",fileUrl,file); } ////////////////////// Helpers /////////////////////////////////////////////// KUrl ProjectControllerTest::writeProjectConfig(const QString& name) { KUrl configUrl = KUrl(m_scratchDir.absolutePath() + '/' + name + ".kdev4"); QFile f(configUrl.pathOrUrl()); f.open(QIODevice::WriteOnly); QTextStream str(&f); str << "[Project]\n" << "Name=" << name << "\n"; f.close(); return configUrl; } ////////////////// Custom assertions ///////////////////////////////////////// void ProjectControllerTest::assertProjectOpened(const QString& name, IProject*& proj) { QVERIFY(proj = m_projCtrl->findProjectByName(name)); QVERIFY(m_projCtrl->projects().contains(proj)); } void ProjectControllerTest::assertSpyCaughtProject(QSignalSpy* spy, IProject* proj) { QCOMPARE(spy->size(), 1); IProject* emittedProj = (*spy)[0][0].value(); QCOMPARE(proj, emittedProj); } void ProjectControllerTest::assertProjectClosed(IProject* proj) { IProject* p = m_projCtrl->findProjectByName(proj->name()); QVERIFY(p == 0); QVERIFY(!m_projCtrl->projects().contains(proj)); } void ProjectControllerTest::assertEmptyProjectModel() { ProjectModel* m = m_projCtrl->projectModel(); Q_ASSERT(m); QCOMPARE(m->rowCount(), 0); } ///////////////////// Creation stuff ///////////////////////////////////////// QSignalSpy* ProjectControllerTest::createOpenedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); } QSignalSpy* ProjectControllerTest::createClosedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosed(KDevelop::IProject*))); } QSignalSpy* ProjectControllerTest::createClosingSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosing(KDevelop::IProject*))); } FakeFileManager* ProjectControllerTest::createFileManager() { FakeFileManagerFactory* f = new FakeFileManagerFactory(); FakeFileManager* fileMng = qobject_cast(f->create()); m_fileManagerGarbage << fileMng; return fileMng; } QTEST_KDEMAIN( ProjectControllerTest, GUI) #include "moc_projectcontrollertest.cpp" #include "projectcontrollertest.moc" diff --git a/shell/workingsets/workingsettooltipwidget.cpp b/shell/workingsets/workingsettooltipwidget.cpp index 1827d32014..c1fa1f63f7 100644 --- a/shell/workingsets/workingsettooltipwidget.cpp +++ b/shell/workingsets/workingsettooltipwidget.cpp @@ -1,382 +1,382 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "workingsettooltipwidget.h" #include #include #include #include "core.h" #include "documentcontroller.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include "workingset.h" #include "workingsetcontroller.h" #include "workingsetfilelabel.h" #include "workingsettoolbutton.h" #include "workingsethelpers.h" using namespace KDevelop; WorkingSetToolTipWidget::WorkingSetToolTipWidget(QWidget* parent, WorkingSet* set, MainWindow* mainwindow) : QWidget(parent), m_set(set) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); connect(static_cast(mainwindow)->area(), SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), SLOT(updateFileButtons()), Qt::QueuedConnection); connect(static_cast(mainwindow)->area(), SIGNAL(viewRemoved(Sublime::AreaIndex*,Sublime::View*)), SLOT(updateFileButtons()), Qt::QueuedConnection); connect(Core::self()->workingSetControllerInternal(), SIGNAL(workingSetSwitched()), SLOT(updateFileButtons())); // title bar { QHBoxLayout* topLayout = new QHBoxLayout; m_setButton = new WorkingSetToolButton(this, set); m_setButton->hide(); topLayout->addSpacing(5); QLabel* icon = new QLabel; topLayout->addWidget(icon); topLayout->addSpacing(5); QString label; if (m_set->isConnected(mainwindow->area())) { label = i18n("Active Working Set"); } else { label = i18n("Working Set"); } QLabel* name = new QLabel(label); name->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); topLayout->addWidget(name); topLayout->addSpacing(10); icon->setPixmap(m_setButton->icon().pixmap(name->sizeHint().height()+8, name->sizeHint().height()+8)); topLayout->addStretch(); m_openButton = new QPushButton; m_openButton->setFlat(true); topLayout->addWidget(m_openButton); m_deleteButton = new QPushButton; m_deleteButton->setIcon(KIcon("edit-delete")); m_deleteButton->setText(i18n("Delete")); m_deleteButton->setToolTip(i18n("Remove this working set. The contained documents are not affected.")); m_deleteButton->setFlat(true); connect(m_deleteButton, SIGNAL(clicked(bool)), m_set, SLOT(deleteSet())); connect(m_deleteButton, SIGNAL(clicked(bool)), this, SIGNAL(shouldClose())); topLayout->addWidget(m_deleteButton); layout->addLayout(topLayout); // horizontal line QFrame* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Raised); layout->addWidget(line); } // everything else is added to the following widget which just has a different background color QVBoxLayout* bodyLayout = new QVBoxLayout; { QWidget* body = new QWidget(); body->setLayout(bodyLayout); layout->addWidget(body); body->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); } // document list actions { QHBoxLayout* actionsLayout = new QHBoxLayout; m_documentsLabel = new QLabel(i18n("Documents:")); m_documentsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); actionsLayout->addWidget(m_documentsLabel); actionsLayout->addStretch(); m_mergeButton = new QPushButton; m_mergeButton->setIcon(KIcon("list-add")); m_mergeButton->setText(i18n("Add All")); m_mergeButton->setToolTip(i18n("Add all documents that are part of this working set to the currently active working set.")); m_mergeButton->setFlat(true); connect(m_mergeButton, SIGNAL(clicked(bool)), m_setButton, SLOT(mergeSet())); actionsLayout->addWidget(m_mergeButton); m_subtractButton = new QPushButton; m_subtractButton->setIcon(KIcon("list-remove")); m_subtractButton->setText(i18n("Subtract All")); m_subtractButton->setToolTip(i18n("Remove all documents that are part of this working set from the currently active working set.")); m_subtractButton->setFlat(true); connect(m_subtractButton, SIGNAL(clicked(bool)), m_setButton, SLOT(subtractSet())); actionsLayout->addWidget(m_subtractButton); bodyLayout->addLayout(actionsLayout); } QSet hadFiles; QVBoxLayout* filesLayout = new QVBoxLayout; filesLayout->setMargin(0); foreach(const QString& file, m_set->fileList()) { if(hadFiles.contains(file)) continue; hadFiles.insert(file); FileWidget* widget = new FileWidget; widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QHBoxLayout* fileLayout = new QHBoxLayout(widget); QToolButton* plusButton = new QToolButton; plusButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); fileLayout->addWidget(plusButton); WorkingSetFileLabel* fileLabel = new WorkingSetFileLabel; fileLabel->setTextFormat(Qt::RichText); // We add spaces behind and after, to make it look nicer fileLabel->setText(" " + Core::self()->projectController()->prettyFileName(KUrl(file)) + " "); fileLabel->setToolTip(i18nc("@info:tooltip", "Click to open and activate this document.")); fileLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); fileLayout->addWidget(fileLabel); fileLayout->setMargin(0); plusButton->setMaximumHeight(fileLabel->sizeHint().height() + 4); plusButton->setMaximumWidth(plusButton->maximumHeight()); plusButton->setObjectName(file); fileLabel->setObjectName(file); fileLabel->setCursor(QCursor(Qt::PointingHandCursor)); widget->m_button = plusButton; widget->m_label = fileLabel; filesLayout->addWidget(widget); m_fileWidgets.insert(file, widget); m_orderedFileWidgets.push_back(widget); connect(plusButton, SIGNAL(clicked(bool)), this, SLOT(buttonClicked(bool))); connect(fileLabel, SIGNAL(clicked()), this, SLOT(labelClicked())); } bodyLayout->addLayout(filesLayout); updateFileButtons(); connect(set, SIGNAL(setChangedSignificantly()), SLOT(updateFileButtons())); connect(mainwindow->area(), SIGNAL(changedWorkingSet(Sublime::Area*,QString,QString)), SLOT(updateFileButtons()), Qt::QueuedConnection); QMetaObject::invokeMethod(this, "updateFileButtons"); } void WorkingSetToolTipWidget::nextDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { kWarning() << "Found no active document"; return; } int next = (active + 1) % m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) next = (next + 1) % m_orderedFileWidgets.size(); m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::previousDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { kWarning() << "Found no active document"; return; } int next = active - 1; if(next < 0) next += m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) { next -= 1; if(next < 0) next += m_orderedFileWidgets.size(); } m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::updateFileButtons() { MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); WorkingSetController* controller = Core::self()->workingSetControllerInternal(); ActiveToolTip* tooltip = controller->tooltip(); QString activeFile; if(mainWindow->area()->activeView()) activeFile = mainWindow->area()->activeView()->document()->documentSpecifier(); WorkingSet* currentWorkingSet = 0; QSet openFiles; if(!mainWindow->area()->workingSet().isEmpty()) { currentWorkingSet = controller->getWorkingSet(mainWindow->area()->workingSet()); openFiles = currentWorkingSet->fileList().toSet(); } bool allOpen = true; bool noneOpen = true; bool needResize = false; bool allHidden = true; for(QMap< QString, FileWidget* >::iterator it = m_fileWidgets.begin(); it != m_fileWidgets.end(); ++it) { if(openFiles.contains(it.key())) { noneOpen = false; (*it)->m_button->setToolTip(i18n("Remove this file from the current working set")); (*it)->m_button->setIcon(KIcon("list-remove")); (*it)->show(); }else{ allOpen = false; (*it)->m_button->setToolTip(i18n("Add this file to the current working set")); (*it)->m_button->setIcon(KIcon("list-add")); if(currentWorkingSet == m_set) { (*it)->hide(); needResize = true; } } if(!(*it)->isHidden()) allHidden = false; (*it)->m_label->setIsActiveFile(it.key() == activeFile); } - // NOTE: allways hide merge&subtract all on current working set + // NOTE: always hide merge&subtract all on current working set // if we want to enable mergeButton, we have to fix it's behavior since it operates directly on the // set contents and not on the m_fileWidgets m_mergeButton->setHidden(allOpen || currentWorkingSet == m_set); m_subtractButton->setHidden(noneOpen || currentWorkingSet == m_set); m_deleteButton->setHidden(m_set->hasConnectedAreas()); m_documentsLabel->setHidden(m_mergeButton->isHidden() && m_subtractButton->isHidden() && m_deleteButton->isHidden()); if(currentWorkingSet == m_set) { disconnect(m_openButton, SIGNAL(clicked(bool)), m_setButton, SLOT(loadSet())); connect(m_openButton, SIGNAL(clicked(bool)), m_setButton, SLOT(closeSet())); connect(m_openButton, SIGNAL(clicked(bool)), this, SIGNAL(shouldClose())); m_openButton->setIcon(KIcon("project-development-close")); m_openButton->setText(i18n("Close")); }else{ disconnect(m_openButton, SIGNAL(clicked(bool)), m_setButton, SLOT(closeSet())); connect(m_openButton, SIGNAL(clicked(bool)), m_setButton, SLOT(loadSet())); disconnect(m_openButton, SIGNAL(clicked(bool)), this, SIGNAL(shouldClose())); m_openButton->setIcon(KIcon("project-open")); m_openButton->setText(i18n("Load")); } if(allHidden && tooltip) tooltip->hide(); if(needResize && tooltip) tooltip->resize(tooltip->sizeHint()); } void WorkingSetToolTipWidget::buttonClicked(bool) { QPointer stillExists(this); QToolButton* s = qobject_cast(sender()); Q_ASSERT(s); MainWindow* mainWindow = dynamic_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); QSet openFiles = Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow->area()->workingSet())->fileList().toSet(); if(!openFiles.contains(s->objectName())) { Core::self()->documentControllerInternal()->openDocument(s->objectName()); }else{ openFiles.remove(s->objectName()); filterViews(openFiles); } if(stillExists) updateFileButtons(); } void WorkingSetToolTipWidget::labelClicked() { QPointer stillExists(this); WorkingSetFileLabel* s = qobject_cast(sender()); Q_ASSERT(s); bool found = false; Sublime::MainWindow* window = static_cast(ICore::self()->uiController()->activeMainWindow()); foreach(Sublime::View* view, window->area()->views()) { if(view->document()->documentSpecifier() == s->objectName()) { window->activateView(view); found = true; break; } } if(!found) Core::self()->documentControllerInternal()->openDocument(s->objectName()); if(stillExists) updateFileButtons(); } #include "workingsettooltipwidget.moc" diff --git a/sublime/tests/areaoperationtest.cpp b/sublime/tests/areaoperationtest.cpp index 5b5d7581bb..0aedb61a6c 100644 --- a/sublime/tests/areaoperationtest.cpp +++ b/sublime/tests/areaoperationtest.cpp @@ -1,730 +1,730 @@ /*************************************************************************** * Copyright 2006-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 "areaoperationtest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "areaprinter.h" using namespace Sublime; struct ViewCounter { ViewCounter(): count(0) {} Area::WalkerMode operator()(AreaIndex *index) { count += index->views().count(); return Area::ContinueWalker; } int count; }; void AreaOperationTest::init() { m_controller = new Controller(this); Document *doc1 = new UrlDocument(m_controller, KUrl::fromPath("~/foo.cpp")); Document *doc2 = new UrlDocument(m_controller, KUrl::fromPath("~/boo.cpp")); Document *doc3 = new UrlDocument(m_controller, KUrl::fromPath("~/moo.cpp")); Document *doc4 = new UrlDocument(m_controller, KUrl::fromPath("~/zoo.cpp")); //documents for toolviews Document *tool1 = new ToolDocument("tool1", m_controller, new SimpleToolWidgetFactory("tool1")); Document *tool2 = new ToolDocument("tool2", m_controller, new SimpleToolWidgetFactory("tool2")); Document *tool3 = new ToolDocument("tool3", m_controller, new SimpleToolWidgetFactory("tool3")); //areas (aka perspectives) //view object names are in form AreaNumber.DocumentNumber.ViewNumber //"tool" prefix is there for tooldocument views m_area1 = new Area(m_controller, "Area 1"); m_pView111 = doc1->createView(); m_pView111->setObjectName("view1.1.1"); m_area1->addView(m_pView111); m_pView121 = doc2->createView(); m_pView121->setObjectName("view1.2.1"); m_area1->addView(m_pView121); m_pView122 = doc2->createView(); m_pView122->setObjectName("view1.2.2"); m_area1->addView(m_pView122); m_pView131 = doc3->createView(); m_pView131->setObjectName("view1.3.1"); m_area1->addView(m_pView131); View *view = tool1->createView(); view->setObjectName("toolview1.1.1"); m_area1->addToolView(view, Sublime::Left); view = tool2->createView(); view->setObjectName("toolview1.2.1"); m_area1->addToolView(view, Sublime::Bottom); view = tool2->createView(); view->setObjectName("toolview1.2.2"); m_area1->addToolView(view, Sublime::Bottom); m_area2 = new Area(m_controller, "Area 2"); View *view211 = doc1->createView(); view211->setObjectName("view2.1.1"); m_area2->addView(view211); View *view212 = doc1->createView(); view212->setObjectName("view2.1.2"); m_area2->addView(view212); View *view221 = doc2->createView(); view221->setObjectName("view2.2.1"); m_area2->addView(view221, view211, Qt::Vertical); View *view231 = doc3->createView(); view231->setObjectName("view2.3.1"); m_area2->addView(view231, view221, Qt::Horizontal); View *view241 = doc4->createView(); view241->setObjectName("view2.4.1"); m_area2->addView(view241, view212, Qt::Vertical); view = tool1->createView(); view->setObjectName("toolview2.1.1"); m_area2->addToolView(view, Sublime::Bottom); view = tool2->createView(); view->setObjectName("toolview2.2.1"); m_area2->addToolView(view, Sublime::Right); view = tool3->createView(); view->setObjectName("toolview2.3.1"); m_area2->addToolView(view, Sublime::Top); view = tool3->createView(); view->setObjectName("toolview2.3.2"); m_area2->addToolView(view, Sublime::Top); m_area3 = new Area(m_controller, "Area 3"); View *view0 = doc1->createView(); view0->setObjectName("view3.1.1"); m_area3->addView(view0); View *view1 = doc2->createView(); view1->setObjectName("view3.1.2"); m_area3->addView(view1, view0); View *view2 = doc3->createView(); view2->setObjectName("view3.1.3"); m_area3->addView(view2, view1); View *view3 = doc4->createView(); view3->setObjectName("view3.1.4"); m_area3->addView(view3, view1); m_controller->addDefaultArea(m_area1); m_controller->addDefaultArea(m_area2); m_controller->addDefaultArea(m_area3); } void AreaOperationTest::cleanup() { delete m_area1; delete m_area2; delete m_controller; m_area1 = 0; m_area2 = 0; m_controller = 0; } void AreaOperationTest::areaConstruction() { //check if areas has proper object names QCOMPARE(m_area1->objectName(), QString("Area 1")); QCOMPARE(m_area2->objectName(), QString("Area 2")); //check that area1 contents is properly initialised AreaViewsPrinter viewsPrinter1; m_area1->walkViews(viewsPrinter1, m_area1->rootIndex()); QCOMPARE(viewsPrinter1.result, QString("\n\ [ view1.1.1 view1.2.1 view1.2.2 view1.3.1 ]\n\ ")); AreaToolViewsPrinter toolViewsPrinter1; m_area1->walkToolViews(toolViewsPrinter1, Sublime::AllPositions); QCOMPARE(toolViewsPrinter1.result, QString("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ ")); //check that area2 contents is properly initialised AreaViewsPrinter viewsPrinter2; m_area2->walkViews(viewsPrinter2, m_area2->rootIndex()); QCOMPARE(viewsPrinter2.result, QString("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.2.1 ]\n\ [ view2.3.1 ]\n\ ")); AreaToolViewsPrinter toolViewsPrinter2; m_area2->walkToolViews(toolViewsPrinter2, Sublime::AllPositions); QCOMPARE(toolViewsPrinter2.result, QString("\n\ toolview2.1.1 [ bottom ]\n\ toolview2.2.1 [ right ]\n\ toolview2.3.1 [ top ]\n\ toolview2.3.2 [ top ]\n\ ")); } void AreaOperationTest::mainWindowConstruction() { //====== check for m_area1 ====== MainWindow mw1(m_controller); m_controller->showArea(m_area1, &mw1); checkArea1(&mw1); ///////////// //====== check for m_area2 ====== MainWindow mw2(m_controller); m_controller->showArea(m_area2, &mw2); checkArea2(&mw2); } void AreaOperationTest::checkArea1(MainWindow *mw) { Area *area = mw->area(); //check that all docks have their widgets foreach (View *dock, mw->toolDocks()) { //QVERIFY(dock->widget() != 0); QVERIFY(dock->hasWidget()); } QCOMPARE(mw->toolDocks().count(), area->toolViews().count()); //check that mainwindow have all splitters and widgets in splitters inside centralWidget QWidget *central = mw->centralWidget(); QVERIFY(central != 0); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check that we have a container and 4 views inside Container *container = splitter->findChild(); QVERIFY(container); ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(container->count(), c.count); for (int i = 0; i < container->count(); ++i) QVERIFY(container->widget(i) != 0); } void AreaOperationTest::checkArea2(MainWindow *mw) { Area *area = mw->area(); //check that all docks have their widgets foreach (View *dock, mw->toolDocks()) { //QVERIFY(dock->widget() != 0); QVERIFY(dock->hasWidget()); } QCOMPARE(mw->toolDocks().count(), area->toolViews().count()); //check that mainwindow have all splitters and widgets in splitters inside centralWidget QWidget *central = mw->centralWidget(); QVERIFY(central != 0); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check that we have 4 properly initialized containers QList containers = splitter->findChildren(); QCOMPARE(containers.count(), 4); int widgetCount = 0; foreach (Container *c, containers) { for (int i = 0; i < c->count(); ++i) QVERIFY(c->widget(i) != 0); widgetCount += c->count(); } ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(widgetCount, c.count); //check that we have 7 splitters: 2 vertical and 1 horizontal, rest is not split QList splitters = splitter->findChildren(); splitters.append(qobject_cast(splitter)); QCOMPARE(splitters.count(), 6+1); //6 child splitters + 1 central itself = 7 splitters int verticalSplitterCount = 0; int horizontalSplitterCount = 0; foreach (QSplitter *s, splitters) { if (s->count() == 1) continue; //this is a splitter with container inside, its orientation is not relevant if (s->orientation() == Qt::Vertical) verticalSplitterCount += 1; else horizontalSplitterCount += 1; } QCOMPARE(verticalSplitterCount, 2); QCOMPARE(horizontalSplitterCount, 1); } void AreaOperationTest::areaCloning() { //show m_area1 in MainWindow1 MainWindow mw1(m_controller); m_controller->showArea(m_area1, &mw1); checkArea1(&mw1); //now try to show the same area in MainWindow2 and check that we get a clone MainWindow mw2(m_controller); m_controller->showArea(m_area1, &mw2); //two mainwindows have different areas QVERIFY(mw1.area() != mw2.area()); //the area for the second mainwindow is a clone of the //original area and should have the same name. QVERIFY(mw2.area()->objectName() == mw1.area()->objectName()); //check mainwindow layouts - original and copy checkArea1(&mw1); checkArea1(&mw2); } /*! Functor used by areaSwitchingInSameMainWindow() Walks all Views and checks if they got a widget. hasWidget will be set to false if any View lacks a widget.*/ struct AreaWidgetChecker { AreaWidgetChecker(): foundViewWithoutWidget(false), failureMessage("") {} Area::WalkerMode operator()(AreaIndex *index) { foreach (View *view, index->views()) { if (!view->hasWidget()) { failureMessage += view->objectName() + " has no widget\n"; foundViewWithoutWidget = true; } } return Area::ContinueWalker; } Area::WalkerMode operator()(View *view, Sublime::Position) { if (!view->hasWidget()) { foundViewWithoutWidget = true; failureMessage += view->objectName() + " has no widget\n"; } return Area::ContinueWalker; } char* message() { return qstrdup(failureMessage.toAscii().data()); } bool foundViewWithoutWidget; QString failureMessage; }; void AreaOperationTest::areaSwitchingInSameMainwindow() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); m_controller->showArea(m_area2, &mw); checkArea2(&mw); //check what happened to area1 widgets, they should be intact AreaWidgetChecker checker; m_area1->walkViews(checker, m_area1->rootIndex()); m_area1->walkToolViews(checker, Sublime::AllPositions); QVERIFY2(!checker.foundViewWithoutWidget, checker.message()); } void AreaOperationTest::simpleViewAdditionAndDeletion() { // set TabBarOpenAfterCurrent=0, otherwise we'd have a different order of tabs int oldTabBarOpenAfterCurrent; { KConfigGroup uiGroup = KGlobal::config()->group("UiSettings"); oldTabBarOpenAfterCurrent = uiGroup.readEntry("TabBarOpenAfterCurrent", 1); uiGroup.writeEntry("TabBarOpenAfterCurrent", 0); uiGroup.sync(); } m_controller->loadSettings(); MainWindow mw(m_controller); m_controller->addMainWindow(&mw); m_controller->showArea(m_area1, &mw); checkArea1(&mw); Document *doc5 = new UrlDocument(m_controller, KUrl::fromPath("~/new.cpp")); View *view = doc5->createView(); view->setObjectName("view1.5.1"); m_area1->addView(view); checkAreaViewsDisplay(&mw, m_area1, QString("\n[ view1.1.1 view1.2.1 view1.2.2 view1.3.1 view1.5.1 ]\n"), 1, 1, "Added an url view (view1.5.1)"); //now remove view and check that area is valid delete m_area1->removeView(view); checkAreaViewsDisplay(&mw, m_area1, QString("\n[ view1.1.1 view1.2.1 view1.2.2 view1.3.1 ]\n"), 1, 1, "Removed the url view (view1.5.1)"); //now remove all other views one by one and leave an empty container QList list(m_area1->views()); foreach (View *view, list) delete m_area1->removeView(view); checkAreaViewsDisplay(&mw, m_area1, QString("\n[ horizontal splitter ]\n"), 0, 1, "Removed all views. Only horizontal splitter should remain."); //add a view again and check that mainwindow is correctly reconstructed view = doc5->createView(); view->setObjectName("view1.5.1"); m_area1->addView(view); checkAreaViewsDisplay(&mw, m_area1, QString("\n[ view1.5.1 ]\n"), 1, 1, "Added a single view to previously emptied mainwindow."); { KConfigGroup uiGroup = KGlobal::config()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", oldTabBarOpenAfterCurrent); uiGroup.sync(); } m_controller->loadSettings(); } void AreaOperationTest::complexViewAdditionAndDeletion() { Area *area = m_area2; MainWindow mw(m_controller); m_controller->addMainWindow(&mw); m_controller->showArea(m_area2, &mw); Document *doc5 = new UrlDocument(m_controller, KUrl::fromPath("~/new.cpp")); View *view = doc5->createView(); view->setObjectName("view2.5.1"); View *view221 = findNamedView(area, "view2.2.1"); QVERIFY(view221); area->addView(view, view221, Qt::Vertical); checkAreaViewsDisplay(&mw, area, QString("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ vertical splitter ]\n\ [ view2.2.1 ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 5, 8+1); //now delete view221 delete area->removeView(view221); checkAreaViewsDisplay(&mw, area, QString("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.1 view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 4, 6+1); //remove one more view, this time the one inside non-empty container View *view211 = findNamedView(area, "view2.1.1"); delete m_area2->removeView(view211); checkAreaViewsDisplay(&mw, area, QString("\n\ [ vertical splitter ]\n\ [ vertical splitter ]\n\ [ view2.1.2 ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 4, 6+1); //and now remove all remaining views one by one delete m_area2->removeView(findNamedView(area, "view2.1.2")); checkAreaViewsDisplay(&mw, area, QString("\n\ [ vertical splitter ]\n\ [ view2.4.1 ]\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 3, 4+1); delete m_area2->removeView(findNamedView(area, "view2.4.1")); checkAreaViewsDisplay(&mw, area, QString("\n\ [ horizontal splitter ]\n\ [ view2.5.1 ]\n\ [ view2.3.1 ]\n\ "), 2, 2+1); delete m_area2->removeView(findNamedView(area, "view2.5.1")); checkAreaViewsDisplay(&mw, area, QString("\n\ [ view2.3.1 ]\n\ "), 1, 1); delete m_area2->removeView(findNamedView(area, "view2.3.1")); checkAreaViewsDisplay(&mw, area, QString("\n\ [ horizontal splitter ]\n\ "), 0, 1); } void AreaOperationTest::toolViewAdditionAndDeletion() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); Document *tool4 = new ToolDocument("tool4", m_controller, new SimpleToolWidgetFactory("tool4")); View *view = tool4->createView(); view->setObjectName("toolview1.4.1"); m_area1->addToolView(view, Sublime::Right); //check that area is in valid state AreaToolViewsPrinter toolViewsPrinter1; m_area1->walkToolViews(toolViewsPrinter1, Sublime::AllPositions); QCOMPARE(toolViewsPrinter1.result, QString("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ toolview1.4.1 [ right ]\n\ ")); //check that mainwindow has newly added toolview foreach (View *dock, mw.toolDocks()) QVERIFY(dock->widget() != 0); QCOMPARE(mw.toolDocks().count(), m_area1->toolViews().count()); //now remove toolview m_area1->removeToolView(view); AreaToolViewsPrinter toolViewsPrinter2; //check that area doesn't have it anymore m_area1->walkToolViews(toolViewsPrinter2, Sublime::AllPositions); QCOMPARE(toolViewsPrinter2.result, QString("\n\ toolview1.1.1 [ left ]\n\ toolview1.2.1 [ bottom ]\n\ toolview1.2.2 [ bottom ]\n\ ")); //check that mainwindow has newly added toolview foreach (View *dock, mw.toolDocks()) QVERIFY(dock->widget() != 0); QCOMPARE(mw.toolDocks().count(), m_area1->toolViews().count()); } void AreaOperationTest::testAddingViewAfter() { QList list(m_area3->views()); foreach (View *view, list){ kDebug() << "name of view : " << view->objectName() << " , it's index : " << m_area3->views().indexOf(view); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void AreaOperationTest::splitViewActiveTabsTest() { MainWindow mw(m_controller); m_controller->showArea(m_area1, &mw); checkArea1(&mw); // at first show of the area, the active view should be m_pView111 QCOMPARE(mw.activeView(), m_pView111); - // Try to get to the the main container : + // Try to get to the main container : // get the central widget QWidget *pCentral = mw.centralWidget(); QVERIFY(pCentral); QVERIFY(pCentral->inherits("QWidget")); // get its first splitter QWidget *pSplitter = pCentral->findChild(); QVERIFY(pSplitter); QVERIFY(pSplitter->inherits("QSplitter")); // finally, get the splitter's container Container *pContainer = pSplitter->findChild(); QVERIFY(pContainer); // verify that the current active widget in the container is the one in activeview (m_pView111) QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); // activate the second tab of the area (view212) mw.activateView(m_pView121); // verify that the active view was correctly updated to m_pView121 QCOMPARE(mw.activeView(), m_pView121); // check if the container's current widget was updated to the active view's QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); // now, create a split view of the active view (m_pView121) Sublime::View *pNewView = mw.activeView()->document()->createView(); pNewView->setObjectName("splitOf" + mw.activeView()->objectName()); m_area1->addView(pNewView, mw.activeView(), Qt::Vertical); // verify that creating a new view did not break the central widget QCOMPARE(pCentral, mw.centralWidget()); // verify that creating a new view did not break the main splitter QCOMPARE(pSplitter, pCentral->findChild()); // creating a new view created two new children splitters, get them QVERIFY(pSplitter->findChildren().size() == 2); QWidget *pFirstSplitter = pSplitter->findChildren().at(0); QVERIFY(pFirstSplitter); QWidget *pSecondSplitter = pSplitter->findChildren().at(1); QVERIFY(pSecondSplitter); // for each splitter, get the corresponding container Container *pFirstContainer = pFirstSplitter->findChild(); QVERIFY(pFirstContainer); Container *pSecondContainer = pSecondSplitter->findChild(); QVERIFY(pSecondContainer); // the active view should have remained view121 QCOMPARE(mw.activeView(), m_pView121); // pFirstContainer should contain the newView's widget QVERIFY(pFirstContainer->hasWidget(pNewView->widget())); // the new view's widget should be the current widget of the new container QCOMPARE(pFirstContainer->currentWidget(), pNewView->widget()); // pSecondContainer should contain all the old views widgets QVERIFY(pSecondContainer->hasWidget(m_pView111->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView121->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView122->widget())); QVERIFY(pSecondContainer->hasWidget(m_pView131->widget())); // the active widget should be the current widget of the second container QCOMPARE(pSecondContainer->currentWidget(), mw.activeView()->widget()); //////////////////////////////////////////////////////////////////////////// // now, activate the new view and check if all went well mw.activateView(pNewView); // active view should now be newView QCOMPARE(mw.activeView(), pNewView); // the active widget should be the current widget of the new container QCOMPARE(pFirstContainer->currentWidget(), mw.activeView()->widget()); // the current widget of the old container should have remained view121's QCOMPARE(pSecondContainer->currentWidget(), m_pView121->widget()); //////////////////////////////////////////////////////////////////////////// // now delete newView and check area state delete m_area1->removeView(pNewView); // verify that deleting the view did not broke the central widget QCOMPARE(pCentral, mw.centralWidget()); // removing the view should have destroyed the sub splitters and containers, // so get the main one and verify that deleting the view did not break it QCOMPARE(pSplitter, pCentral->findChild()); // get the new container inside the main splitter pContainer = pSplitter->findChild(); QVERIFY(pContainer); // active view should now be back to m_pView121 again QCOMPARE(mw.activeView(), m_pView121); // check also the container current widget QCOMPARE(pContainer->currentWidget(), mw.activeView()->widget()); } void AreaOperationTest::checkAreaViewsDisplay(MainWindow *mw, Area *area, const QString &printedAreas, int containerCount, int splitterCount, QString location) { //check area AreaViewsPrinter viewsPrinter; area->walkViews(viewsPrinter, area->rootIndex()); QCOMPARE(viewsPrinter.result, printedAreas); //check mainwindow QWidget *central = mw->centralWidget(); QVERIFY(central != 0); QVERIFY(central->inherits("QWidget")); QWidget *splitter = central->findChild(); QVERIFY(splitter); QVERIFY(splitter->inherits("QSplitter")); //check containers QList containers = splitter->findChildren(); QString failMsg = QString("\nFailure while checking area contents @ %1\n" "Expected %2 containers in central splitter but got %3 \n"). arg(location).arg(containerCount).arg(containers.count()); QVERIFY2(containers.count() == containerCount, failMsg.toAscii().data()); int widgetCount = 0; foreach (Container *c, containers) { for (int i = 0; i < c->count(); ++i) { QVERIFY(c->widget(i) != 0); QVERIFY(c->widget(i)->parentWidget() != 0); } widgetCount += c->count(); } ViewCounter c; area->walkViews(c, area->rootIndex()); QCOMPARE(widgetCount, c.count); QList splitters = splitter->findChildren(); splitters.append(qobject_cast(splitter)); QCOMPARE(splitters.count(), splitterCount); } View *AreaOperationTest::findNamedView(Area *area, const QString &name) { foreach (View *view, area->views()) if (view->objectName() == name) return view; return 0; } /////////// QTEST_KDEMAIN(AreaOperationTest, GUI) #include "areaoperationtest.moc" diff --git a/util/environmentselectionwidget.cpp b/util/environmentselectionwidget.cpp index 63fdebc504..a198f32284 100644 --- a/util/environmentselectionwidget.cpp +++ b/util/environmentselectionwidget.cpp @@ -1,65 +1,65 @@ /* This file is part of KDevelop Copyright 2007 Dukju Ahn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentselectionwidget.h" #include "environmentgrouplist.h" #include #include #include #include namespace KDevelop { class EnvironmentSelectionWidgetPrivate { }; EnvironmentSelectionWidget::EnvironmentSelectionWidget( QWidget *parent ) : KComboBox( parent ), d( new EnvironmentSelectionWidgetPrivate ) { - // Taken from kdelibs/kdeui/dialogs/kconfigdialogmanager.cpp (no idea wether this is documented) + // Taken from kdelibs/kdeui/dialogs/kconfigdialogmanager.cpp (no idea whether this is documented) // Commits d44186bce4670d2985fb6aba8dba59bbd2c4c77a and 8edc1932ecc62370d9a31836dfa9b2bd0175a293 // introduced a regression in kdelibs to fix problems running some apps against Qt4.8. Unfortunately // this fix breaks exactly our use-case, which is to store the text-value in kconfig instead of // the index even though the combobox is editable. Since that change the special combobox-code in // kconfigdialogmanager.cpp is run before check a user-property and hence our user-property is // ignored. Setting this special kcfg_property to the name of our user-property again overrides // the hardcoded combobox-behaviour - until the next one breaks things in kdelibs :| setProperty("kcfg_property", QByteArray("currentProfile")); } EnvironmentSelectionWidget::~EnvironmentSelectionWidget() { delete d; } QString EnvironmentSelectionWidget::currentProfile() const { return currentText(); } void EnvironmentSelectionWidget::setCurrentProfile( const QString& profile ) { setCurrentItem( profile ); } } #include "environmentselectionwidget.moc" diff --git a/util/google/sparsehash/densehashtable.h b/util/google/sparsehash/densehashtable.h index 1100fd0a83..669e8dc7ad 100644 --- a/util/google/sparsehash/densehashtable.h +++ b/util/google/sparsehash/densehashtable.h @@ -1,1322 +1,1322 @@ // Copyright (c) 2005, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // --- // // A dense hashtable is a particular implementation of // a hashtable: one that is meant to minimize memory allocation. // It does this by using an array to store all the data. We // steal a value from the key space to indicate "empty" array // elements (ie indices where no item lives) and another to indicate // "deleted" elements. // // (Note it is possible to change the value of the delete key // on the fly; you can even remove it, though after that point // the hashtable is insert_only until you set it again. The empty // value however can't be changed.) // // To minimize allocation and pointer overhead, we use internal // probing, in which the hashtable is a single table, and collisions // are resolved by trying to insert again in another bucket. The // most cache-efficient internal probing schemes are linear probing // (which suffers, alas, from clumping) and quadratic probing, which // is what we implement by default. // // Type requirements: value_type is required to be Copy Constructible // and Default Constructible. It is not required to be (and commonly // isn't) Assignable. // // You probably shouldn't use this code directly. Use dense_hash_map<> // or dense_hash_set<> instead. // You can change the following below: // HT_OCCUPANCY_PCT -- how full before we double size // HT_EMPTY_PCT -- how empty before we halve size // HT_MIN_BUCKETS -- default smallest bucket size // // You can also change enlarge_factor (which defaults to // HT_OCCUPANCY_PCT), and shrink_factor (which defaults to // HT_EMPTY_PCT) with set_resizing_parameters(). // // How to decide what values to use? // shrink_factor's default of .4 * OCCUPANCY_PCT, is probably good. // HT_MIN_BUCKETS is probably unnecessary since you can specify // (indirectly) the starting number of buckets at construct-time. // For enlarge_factor, you can use this chart to try to trade-off // expected lookup time to the space taken up. By default, this // code uses quadratic probing, though you can change it to linear // via JUMP_ below if you really want to. // // From http://www.augustana.ca/~mohrj/courses/1999.fall/csc210/lecture_notes/hashing.html // NUMBER OF PROBES / LOOKUP Successful Unsuccessful // Quadratic collision resolution 1 - ln(1-L) - L/2 1/(1-L) - L - ln(1-L) // Linear collision resolution [1+1/(1-L)]/2 [1+1/(1-L)2]/2 // // -- enlarge_factor -- 0.10 0.50 0.60 0.75 0.80 0.90 0.99 // QUADRATIC COLLISION RES. // probes/successful lookup 1.05 1.44 1.62 2.01 2.21 2.85 5.11 // probes/unsuccessful lookup 1.11 2.19 2.82 4.64 5.81 11.4 103.6 // LINEAR COLLISION RES. // probes/successful lookup 1.06 1.5 1.75 2.5 3.0 5.5 50.5 // probes/unsuccessful lookup 1.12 2.5 3.6 8.5 13.0 50.0 5000.0 #ifndef _DENSEHASHTABLE_H_ #define _DENSEHASHTABLE_H_ #include #include #include // for FILE, fwrite, fread #include // For swap(), eg #include // For iterator tags #include // for numeric_limits #include // For uninitialized_fill #include // for pair #include #include #include #include // For length_error _START_GOOGLE_NAMESPACE_ namespace base { // just to make google->opensource transition easier using GOOGLE_NAMESPACE::true_type; using GOOGLE_NAMESPACE::false_type; using GOOGLE_NAMESPACE::integral_constant; using GOOGLE_NAMESPACE::is_same; using GOOGLE_NAMESPACE::remove_const; } // The probing method // Linear probing // #define JUMP_(key, num_probes) ( 1 ) // Quadratic probing #define JUMP_(key, num_probes) ( num_probes ) // Hashtable class, used to implement the hashed associative containers // hash_set and hash_map. // Value: what is stored in the table (each bucket is a Value). // Key: something in a 1-to-1 correspondence to a Value, that can be used // to search for a Value in the table (find() takes a Key). // HashFcn: Takes a Key and returns an integer, the more unique the better. // ExtractKey: given a Value, returns the unique Key associated with it. // Must inherit from unary_function, or at least have a // result_type enum indicating the return type of operator(). // SetKey: given a Value* and a Key, modifies the value such that // ExtractKey(value) == key. We guarantee this is only called // with key == deleted_key or key == empty_key. // EqualKey: Given two Keys, says whether they are the same (that is, // if they are both associated with the same Value). // Alloc: STL allocator to use to allocate memory. template class dense_hashtable; template struct dense_hashtable_iterator; template struct dense_hashtable_const_iterator; // We're just an array, but we need to skip over empty and deleted elements template struct dense_hashtable_iterator { private: typedef typename A::template rebind::other value_alloc_type; public: typedef dense_hashtable_iterator iterator; typedef dense_hashtable_const_iterator const_iterator; typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::reference reference; typedef typename value_alloc_type::pointer pointer; // "Real" constructor and default constructor dense_hashtable_iterator(const dense_hashtable *h, pointer it, pointer it_end, bool advance) : ht(h), pos(it), end(it_end) { if (advance) advance_past_empty_and_deleted(); } dense_hashtable_iterator() { } // The default destructor is fine; we don't define one // The default operator= is fine; we don't define one // Happy dereferencer reference operator*() const { return *pos; } pointer operator->() const { return &(operator*()); } // Arithmetic. The only hard part is making sure that // we're not on an empty or marked-deleted array element void advance_past_empty_and_deleted() { while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) ++pos; } iterator& operator++() { assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; } iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } // Comparison. bool operator==(const iterator& it) const { return pos == it.pos; } bool operator!=(const iterator& it) const { return pos != it.pos; } // The actual data const dense_hashtable *ht; pointer pos, end; }; // Now do it all again, but with const-ness! template struct dense_hashtable_const_iterator { private: typedef typename A::template rebind::other value_alloc_type; public: typedef dense_hashtable_iterator iterator; typedef dense_hashtable_const_iterator const_iterator; typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::const_reference reference; typedef typename value_alloc_type::const_pointer pointer; // "Real" constructor and default constructor dense_hashtable_const_iterator( const dense_hashtable *h, pointer it, pointer it_end, bool advance) : ht(h), pos(it), end(it_end) { if (advance) advance_past_empty_and_deleted(); } dense_hashtable_const_iterator() : ht(NULL), pos(pointer()), end(pointer()) { } // This lets us convert regular iterators to const iterators dense_hashtable_const_iterator(const iterator &it) : ht(it.ht), pos(it.pos), end(it.end) { } // The default destructor is fine; we don't define one // The default operator= is fine; we don't define one // Happy dereferencer reference operator*() const { return *pos; } pointer operator->() const { return &(operator*()); } // Arithmetic. The only hard part is making sure that // we're not on an empty or marked-deleted array element void advance_past_empty_and_deleted() { while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) ++pos; } const_iterator& operator++() { assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; } const_iterator operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } // Comparison. bool operator==(const const_iterator& it) const { return pos == it.pos; } bool operator!=(const const_iterator& it) const { return pos != it.pos; } // The actual data const dense_hashtable *ht; pointer pos, end; }; template class dense_hashtable { private: typedef typename Alloc::template rebind::other value_alloc_type; public: typedef Key key_type; typedef Value value_type; typedef HashFcn hasher; typedef EqualKey key_equal; typedef Alloc allocator_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::reference reference; typedef typename value_alloc_type::const_reference const_reference; typedef typename value_alloc_type::pointer pointer; typedef typename value_alloc_type::const_pointer const_pointer; typedef dense_hashtable_iterator iterator; typedef dense_hashtable_const_iterator const_iterator; // These come from tr1. For us they're the same as regular iterators. typedef iterator local_iterator; typedef const_iterator const_local_iterator; // How full we let the table get before we resize, by default. // Knuth says .8 is good -- higher causes us to probe too much, // though it saves memory. static const int HT_OCCUPANCY_PCT; // defined at the bottom of this file // How empty we let the table get before we resize lower, by default. // (0.0 means never resize lower.) // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing static const int HT_EMPTY_PCT; // defined at the bottom of this file // Minimum size we're willing to let hashtables be. // Must be a power of two, and at least 4. // Note, however, that for a given hashtable, the initial size is a // function of the first constructor arg, and may be >HT_MIN_BUCKETS. static const size_type HT_MIN_BUCKETS = 4; // By default, if you don't specify a hashtable size at // construction-time, we use this size. Must be a power of two, and // at least HT_MIN_BUCKETS. static const size_type HT_DEFAULT_STARTING_BUCKETS = 32; // ITERATOR FUNCTIONS iterator begin() { return iterator(this, table, table + num_buckets, true); } iterator end() { return iterator(this, table + num_buckets, table + num_buckets, true); } const_iterator begin() const { return const_iterator(this, table, table+num_buckets,true);} const_iterator end() const { return const_iterator(this, table + num_buckets, table+num_buckets,true);} // These come from tr1 unordered_map. They iterate over 'bucket' n. // We'll just consider bucket n to be the n-th element of the table. local_iterator begin(size_type i) { return local_iterator(this, table + i, table + i+1, false); } local_iterator end(size_type i) { local_iterator it = begin(i); if (!test_empty(i) && !test_deleted(i)) ++it; return it; } const_local_iterator begin(size_type i) const { return const_local_iterator(this, table + i, table + i+1, false); } const_local_iterator end(size_type i) const { const_local_iterator it = begin(i); if (!test_empty(i) && !test_deleted(i)) ++it; return it; } // ACCESSOR FUNCTIONS for the things we templatize on, basically hasher hash_funct() const { return settings; } key_equal key_eq() const { return key_info; } allocator_type get_allocator() const { return allocator_type(val_info); } // Accessor function for statistics gathering. int num_table_copies() const { return settings.num_ht_copies(); } private: // Annoyingly, we can't copy values around, because they might have // const components (they're probably pair). We use // explicit destructor invocation and placement new to get around // this. Arg. void set_value(pointer dst, const_reference src) { dst->~value_type(); // delete the old value, if any new(dst) value_type(src); } void destroy_buckets(size_type first, size_type last) { for ( ; first != last; ++first) table[first].~value_type(); } // DELETE HELPER FUNCTIONS // This lets the user describe a key that will indicate deleted // table entries. This key should be an "impossible" entry -- // if you try to insert it for real, you won't be able to retrieve it! // (NB: while you pass in an entire value, only the key part is looked // at. This is just because I don't know how to assign just a key.) private: void squash_deleted() { // gets rid of any deleted entries we have if ( num_deleted ) { // get rid of deleted before writing dense_hashtable tmp(*this); // copying will get rid of deleted swap(tmp); // now we are tmp } assert(num_deleted == 0); } // Test if the given key is the deleted indicator. Requires // num_deleted > 0, for correctness of read(), and because that // guarantees that key_info.delkey is valid. bool test_deleted_key(const key_type& key) const { assert(num_deleted > 0); return equals(key_info.delkey, key); } public: void set_deleted_key(const key_type &key) { // the empty indicator (if specified) and the deleted indicator // must be different assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) && "Passed the empty-key to set_deleted_key"); // It's only safe to change what "deleted" means if we purge deleted guys squash_deleted(); settings.set_use_deleted(true); key_info.delkey = key; } void clear_deleted_key() { squash_deleted(); settings.set_use_deleted(false); } key_type deleted_key() const { assert(settings.use_deleted() && "Must set deleted key before calling deleted_key"); return key_info.delkey; } // These are public so the iterators can use them // True if the item at position bucknum is "deleted" marker bool test_deleted(size_type bucknum) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(table[bucknum])); } bool test_deleted(const iterator &it) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(*it)); } bool test_deleted(const const_iterator &it) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(*it)); } private: void check_use_deleted(const char* caller) { (void)caller; // could log it if the assert failed assert(settings.use_deleted()); } // Set it so test_deleted is true. true if object didn't used to be deleted. bool set_deleted(iterator &it) { check_use_deleted("set_deleted()"); bool retval = !test_deleted(it); // &* converts from iterator to value-type. set_key(&(*it), key_info.delkey); return retval; } // Set it so test_deleted is false. true if object used to be deleted. bool clear_deleted(iterator &it) { check_use_deleted("clear_deleted()"); // Happens automatically when we assign something else in its place. return test_deleted(it); } // We also allow to set/clear the deleted bit on a const iterator. // We allow a const_iterator for the same reason you can delete a // const pointer: it's convenient, and semantically you can't use // 'it' after it's been deleted anyway, so its const-ness doesn't // really matter. bool set_deleted(const_iterator &it) { check_use_deleted("set_deleted()"); bool retval = !test_deleted(it); set_key(const_cast(&(*it)), key_info.delkey); return retval; } // Set it so test_deleted is false. true if object used to be deleted. bool clear_deleted(const_iterator &it) { check_use_deleted("clear_deleted()"); return test_deleted(it); } // EMPTY HELPER FUNCTIONS // This lets the user describe a key that will indicate empty (unused) // table entries. This key should be an "impossible" entry -- // if you try to insert it for real, you won't be able to retrieve it! // (NB: while you pass in an entire value, only the key part is looked // at. This is just because I don't know how to assign just a key.) public: // These are public so the iterators can use them // True if the item at position bucknum is "empty" marker bool test_empty(size_type bucknum) const { assert(settings.use_empty()); // we always need to know what's empty! return equals(get_key(val_info.emptyval), get_key(table[bucknum])); } bool test_empty(const iterator &it) const { assert(settings.use_empty()); // we always need to know what's empty! return equals(get_key(val_info.emptyval), get_key(*it)); } bool test_empty(const const_iterator &it) const { assert(settings.use_empty()); // we always need to know what's empty! return equals(get_key(val_info.emptyval), get_key(*it)); } private: void fill_range_with_empty(pointer table_start, pointer table_end) { std::uninitialized_fill(table_start, table_end, val_info.emptyval); } public: // TODO(csilvers): change all callers of this to pass in a key instead, // and take a const key_type instead of const value_type. void set_empty_key(const_reference val) { // Once you set the empty key, you can't change it assert(!settings.use_empty() && "Calling set_empty_key multiple times"); // The deleted indicator (if specified) and the empty indicator // must be different. assert((!settings.use_deleted() || !equals(get_key(val), key_info.delkey)) && "Setting the empty key the same as the deleted key"); settings.set_use_empty(true); set_value(&val_info.emptyval, val); assert(!table); // must set before first use // num_buckets was set in constructor even though table was NULL table = val_info.allocate(num_buckets); assert(table); fill_range_with_empty(table, table + num_buckets); } // TODO(user): return a key_type rather than a value_type value_type empty_key() const { assert(settings.use_empty()); return val_info.emptyval; } // FUNCTIONS CONCERNING SIZE public: size_type size() const { return num_elements - num_deleted; } size_type max_size() const { return val_info.max_size(); } bool empty() const { return size() == 0; } size_type bucket_count() const { return num_buckets; } size_type max_bucket_count() const { return max_size(); } size_type nonempty_bucket_count() const { return num_elements; } // These are tr1 methods. Their idea of 'bucket' doesn't map well to // what we do. We just say every bucket has 0 or 1 items in it. size_type bucket_size(size_type i) const { return begin(i) == end(i) ? 0 : 1; } private: // Because of the above, size_type(-1) is never legal; use it for errors static const size_type ILLEGAL_BUCKET = size_type(-1); // Used after a string of deletes. Returns true if we actually shrunk. // TODO(csilvers): take a delta so we can take into account inserts // done after shrinking. Maybe make part of the Settings class? bool maybe_shrink() { assert(num_elements >= num_deleted); assert((bucket_count() & (bucket_count()-1)) == 0); // is a power of two assert(bucket_count() >= HT_MIN_BUCKETS); bool retval = false; // If you construct a hashtable with < HT_DEFAULT_STARTING_BUCKETS, // we'll never shrink until you get relatively big, and we'll never // shrink below HT_DEFAULT_STARTING_BUCKETS. Otherwise, something // like "dense_hash_set x; x.insert(4); x.erase(4);" will // shrink us down to HT_MIN_BUCKETS buckets, which is too small. const size_type num_remain = num_elements - num_deleted; const size_type shrink_threshold = settings.shrink_threshold(); if (shrink_threshold > 0 && num_remain < shrink_threshold && bucket_count() > HT_DEFAULT_STARTING_BUCKETS) { const float shrink_factor = settings.shrink_factor(); size_type sz = bucket_count() / 2; // find how much we should shrink while (sz > HT_DEFAULT_STARTING_BUCKETS && num_remain < sz * shrink_factor) { sz /= 2; // stay a power of 2 } dense_hashtable tmp(*this, sz); // Do the actual resizing swap(tmp); // now we are tmp retval = true; } settings.set_consider_shrink(false); // because we just considered it return retval; } // We'll let you resize a hashtable -- though this makes us copy all! // When you resize, you say, "make it big enough for this many more elements" // Returns true if we actually resized, false if size was already ok. bool resize_delta(size_type delta) { bool did_resize = false; if ( settings.consider_shrink() ) { // see if lots of deletes happened if ( maybe_shrink() ) did_resize = true; } if (num_elements >= (std::numeric_limits::max)() - delta) { // throw std::length_error("resize overflow"); return false; } if ( bucket_count() >= HT_MIN_BUCKETS && (num_elements + delta) <= settings.enlarge_threshold() ) return did_resize; // we're ok as we are // Sometimes, we need to resize just to get rid of all the // "deleted" buckets that are clogging up the hashtable. So when // deciding whether to resize, count the deleted buckets (which // are currently taking up room). But later, when we decide what // size to resize to, *don't* count deleted buckets, since they // get discarded during the resize. const size_type needed_size = settings.min_buckets(num_elements + delta, 0); if ( needed_size <= bucket_count() ) // we have enough buckets return did_resize; size_type resize_to = settings.min_buckets(num_elements - num_deleted + delta, bucket_count()); if (resize_to < needed_size && // may double resize_to resize_to < (std::numeric_limits::max)() / 2) { // This situation means that we have enough deleted elements, // that once we purge them, we won't actually have needed to // grow. But we may want to grow anyway: if we just purge one // element, say, we'll have to grow anyway next time we // insert. Might as well grow now, since we're already going // through the trouble of copying (in order to purge the // deleted elements). const size_type target = static_cast(settings.shrink_size(resize_to*2)); if (num_elements - num_deleted + delta >= target) { - // Good, we won't be below the shrink threshhold even if we double. + // Good, we won't be below the shrink threshold even if we double. resize_to *= 2; } } dense_hashtable tmp(*this, resize_to); swap(tmp); // now we are tmp return true; } // We require table be not-NULL and empty before calling this. void resize_table(size_type /*old_size*/, size_type new_size, base::true_type) { table = val_info.realloc_or_die(table, new_size); } void resize_table(size_type old_size, size_type new_size, base::false_type) { val_info.deallocate(table, old_size); table = val_info.allocate(new_size); } // Used to actually do the rehashing when we grow/shrink a hashtable void copy_from(const dense_hashtable &ht, size_type min_buckets_wanted) { clear_to_size(settings.min_buckets(ht.size(), min_buckets_wanted)); // We use a normal iterator to get non-deleted bcks from ht // We could use insert() here, but since we know there are // no duplicates and no deleted items, we can be more efficient assert((bucket_count() & (bucket_count()-1)) == 0); // a power of two for ( const_iterator it = ht.begin(); it != ht.end(); ++it ) { size_type num_probes = 0; // how many times we've probed size_type bucknum; const size_type bucket_count_minus_one = bucket_count() - 1; for (bucknum = hash(get_key(*it)) & bucket_count_minus_one; !test_empty(bucknum); // not empty bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one) { ++num_probes; assert(num_probes < bucket_count() && "Hashtable is full: an error in key_equal<> or hash<>"); } set_value(&table[bucknum], *it); // copies the value to here num_elements++; } settings.inc_num_ht_copies(); } // Required by the spec for hashed associative container public: // Though the docs say this should be num_buckets, I think it's much // more useful as num_elements. As a special feature, calling with // req_elements==0 will cause us to shrink if we can, saving space. void resize(size_type req_elements) { // resize to this or larger if ( settings.consider_shrink() || req_elements == 0 ) maybe_shrink(); if ( req_elements > num_elements ) resize_delta(req_elements - num_elements); } // Get and change the value of shrink_factor and enlarge_factor. The // description at the beginning of this file explains how to choose // the values. Setting the shrink parameter to 0.0 ensures that the // table never shrinks. void get_resizing_parameters(float* shrink, float* grow) const { *shrink = settings.shrink_factor(); *grow = settings.enlarge_factor(); } void set_resizing_parameters(float shrink, float grow) { settings.set_resizing_parameters(shrink, grow); settings.reset_thresholds(bucket_count()); } // CONSTRUCTORS -- as required by the specs, we take a size, // but also let you specify a hashfunction, key comparator, // and key extractor. We also define a copy constructor and =. // DESTRUCTOR -- needs to free the table explicit dense_hashtable(size_type expected_max_items_in_table = 0, const HashFcn& hf = HashFcn(), const EqualKey& eql = EqualKey(), const ExtractKey& ext = ExtractKey(), const SetKey& set = SetKey(), const Alloc& alloc = Alloc()) : settings(hf), key_info(ext, set, eql), num_deleted(0), num_elements(0), num_buckets(expected_max_items_in_table == 0 ? HT_DEFAULT_STARTING_BUCKETS : settings.min_buckets(expected_max_items_in_table, 0)), val_info(alloc_impl(alloc)), table(NULL) { // table is NULL until emptyval is set. However, we set num_buckets // here so we know how much space to allocate once emptyval is set settings.reset_thresholds(bucket_count()); } // As a convenience for resize(), we allow an optional second argument // which lets you make this new hashtable a different size than ht dense_hashtable(const dense_hashtable& ht, size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) : settings(ht.settings), key_info(ht.key_info), num_deleted(0), num_elements(0), num_buckets(0), val_info(ht.val_info), table(NULL) { if (!ht.settings.use_empty()) { // If use_empty isn't set, copy_from will crash, so we do our own copying. assert(ht.empty()); num_buckets = settings.min_buckets(ht.size(), min_buckets_wanted); settings.reset_thresholds(bucket_count()); return; } settings.reset_thresholds(bucket_count()); copy_from(ht, min_buckets_wanted); // copy_from() ignores deleted entries } dense_hashtable& operator= (const dense_hashtable& ht) { if (&ht == this) return *this; // don't copy onto ourselves if (!ht.settings.use_empty()) { assert(ht.empty()); dense_hashtable empty_table(ht); // empty table with ht's thresholds this->swap(empty_table); return *this; } settings = ht.settings; key_info = ht.key_info; set_value(&val_info.emptyval, ht.val_info.emptyval); // copy_from() calls clear and sets num_deleted to 0 too copy_from(ht, HT_MIN_BUCKETS); // we purposefully don't copy the allocator, which may not be copyable return *this; } ~dense_hashtable() { if (table) { destroy_buckets(0, num_buckets); val_info.deallocate(table, num_buckets); } } // Many STL algorithms use swap instead of copy constructors void swap(dense_hashtable& ht) { std::swap(settings, ht.settings); std::swap(key_info, ht.key_info); std::swap(num_deleted, ht.num_deleted); std::swap(num_elements, ht.num_elements); std::swap(num_buckets, ht.num_buckets); { value_type tmp; // for annoying reasons, swap() doesn't work set_value(&tmp, val_info.emptyval); set_value(&val_info.emptyval, ht.val_info.emptyval); set_value(&ht.val_info.emptyval, tmp); } std::swap(table, ht.table); settings.reset_thresholds(bucket_count()); // also resets consider_shrink ht.settings.reset_thresholds(ht.bucket_count()); // we purposefully don't swap the allocator, which may not be swap-able } private: void clear_to_size(size_type new_num_buckets) { if (!table) { table = val_info.allocate(new_num_buckets); } else { destroy_buckets(0, num_buckets); if (new_num_buckets != num_buckets) { // resize, if necessary typedef base::integral_constant >::value> realloc_ok; resize_table(num_buckets, new_num_buckets, realloc_ok()); } } assert(table); fill_range_with_empty(table, table + new_num_buckets); num_elements = 0; num_deleted = 0; num_buckets = new_num_buckets; // our new size settings.reset_thresholds(bucket_count()); } public: // It's always nice to be able to clear a table without deallocating it void clear() { // If the table is already empty, and the number of buckets is // already as we desire, there's nothing to do. const size_type new_num_buckets = settings.min_buckets(0, 0); if (num_elements == 0 && new_num_buckets == num_buckets) { return; } clear_to_size(new_num_buckets); } // Clear the table without resizing it. // Mimicks the stl_hashtable's behaviour when clear()-ing in that it // does not modify the bucket count void clear_no_resize() { if (num_elements > 0) { assert(table); destroy_buckets(0, num_buckets); fill_range_with_empty(table, table + num_buckets); } // don't consider to shrink before another erase() settings.reset_thresholds(bucket_count()); num_elements = 0; num_deleted = 0; } // LOOKUP ROUTINES private: // Returns a pair of positions: 1st where the object is, 2nd where // it would go if you wanted to insert it. 1st is ILLEGAL_BUCKET // if object is not found; 2nd is ILLEGAL_BUCKET if it is. // Note: because of deletions where-to-insert is not trivial: it's the // first deleted bucket we see, as long as we don't find the key later std::pair find_position(const key_type &key) const { size_type num_probes = 0; // how many times we've probed const size_type bucket_count_minus_one = bucket_count() - 1; size_type bucknum = hash(key) & bucket_count_minus_one; size_type insert_pos = ILLEGAL_BUCKET; // where we would insert while ( 1 ) { // probe until something happens if ( test_empty(bucknum) ) { // bucket is empty if ( insert_pos == ILLEGAL_BUCKET ) // found no prior place to insert return std::pair(ILLEGAL_BUCKET, bucknum); else return std::pair(ILLEGAL_BUCKET, insert_pos); } else if ( test_deleted(bucknum) ) {// keep searching, but mark to insert if ( insert_pos == ILLEGAL_BUCKET ) insert_pos = bucknum; } else if ( equals(key, get_key(table[bucknum])) ) { return std::pair(bucknum, ILLEGAL_BUCKET); } ++num_probes; // we're doing another probe bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one; assert(num_probes < bucket_count() && "Hashtable is full: an error in key_equal<> or hash<>"); } } public: iterator find(const key_type& key) { if ( size() == 0 ) return end(); std::pair pos = find_position(key); if ( pos.first == ILLEGAL_BUCKET ) // alas, not there return end(); else return iterator(this, table + pos.first, table + num_buckets, false); } const_iterator find(const key_type& key) const { if ( size() == 0 ) return end(); std::pair pos = find_position(key); if ( pos.first == ILLEGAL_BUCKET ) // alas, not there return end(); else return const_iterator(this, table + pos.first, table+num_buckets, false); } // This is a tr1 method: the bucket a given key is in, or what bucket // it would be put in, if it were to be inserted. Shrug. size_type bucket(const key_type& key) const { std::pair pos = find_position(key); return pos.first == ILLEGAL_BUCKET ? pos.second : pos.first; } // Counts how many elements have key key. For maps, it's either 0 or 1. size_type count(const key_type &key) const { std::pair pos = find_position(key); return pos.first == ILLEGAL_BUCKET ? 0 : 1; } // Likewise, equal_range doesn't really make sense for us. Oh well. std::pair equal_range(const key_type& key) { iterator pos = find(key); // either an iterator or end if (pos == end()) { return std::pair(pos, pos); } else { const iterator startpos = pos++; return std::pair(startpos, pos); } } std::pair equal_range(const key_type& key) const { const_iterator pos = find(key); // either an iterator or end if (pos == end()) { return std::pair(pos, pos); } else { const const_iterator startpos = pos++; return std::pair(startpos, pos); } } // INSERTION ROUTINES private: // Private method used by insert_noresize and find_or_insert. iterator insert_at(const_reference obj, size_type pos) { if (size() >= max_size()) { // throw std::length_error("insert overflow"); return end(); } if ( test_deleted(pos) ) { // just replace if it's been del. // shrug: shouldn't need to be const. const_iterator delpos(this, table + pos, table + num_buckets, false); clear_deleted(delpos); assert( num_deleted > 0); --num_deleted; // used to be, now it isn't } else { ++num_elements; // replacing an empty bucket } set_value(&table[pos], obj); return iterator(this, table + pos, table + num_buckets, false); } // If you know *this is big enough to hold obj, use this routine std::pair insert_noresize(const_reference obj) { // First, double-check we're not inserting delkey or emptyval assert((!settings.use_empty() || !equals(get_key(obj), get_key(val_info.emptyval))) && "Inserting the empty key"); assert((!settings.use_deleted() || !equals(get_key(obj), key_info.delkey)) && "Inserting the deleted key"); const std::pair pos = find_position(get_key(obj)); if ( pos.first != ILLEGAL_BUCKET) { // object was already there return std::pair(iterator(this, table + pos.first, table + num_buckets, false), false); // false: we didn't insert } else { // pos.second says where to put it return std::pair(insert_at(obj, pos.second), true); } } // Specializations of insert(it, it) depending on the power of the iterator: // (1) Iterator supports operator-, resize before inserting template void insert(ForwardIterator f, ForwardIterator l, std::forward_iterator_tag) { size_t dist = std::distance(f, l); if (dist >= (std::numeric_limits::max)()) { // throw std::length_error("insert-range overflow"); return; } resize_delta(static_cast(dist)); for ( ; dist > 0; --dist, ++f) { insert_noresize(*f); } } // (2) Arbitrary iterator, can't tell how much to resize template void insert(InputIterator f, InputIterator l, std::input_iterator_tag) { for ( ; f != l; ++f) insert(*f); } public: // This is the normal insert routine, used by the outside world std::pair insert(const_reference obj) { resize_delta(1); // adding an object, grow if need be return insert_noresize(obj); } // When inserting a lot at a time, we specialize on the type of iterator template void insert(InputIterator f, InputIterator l) { // specializes on iterator type insert(f, l, typename std::iterator_traits::iterator_category()); } // DefaultValue is a functor that takes a key and returns a value_type // representing the default value to be inserted if none is found. template value_type& find_or_insert(const key_type& key) { // First, double-check we're not inserting emptykey or delkey assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) && "Inserting the empty key"); assert((!settings.use_deleted() || !equals(key, key_info.delkey)) && "Inserting the deleted key"); const std::pair pos = find_position(key); DefaultValue default_value; if ( pos.first != ILLEGAL_BUCKET) { // object was already there return table[pos.first]; } else if (resize_delta(1)) { // needed to rehash to make room // Since we resized, we can't use pos, so recalculate where to insert. return *insert_noresize(default_value(key)).first; } else { // no need to rehash, insert right here return *insert_at(default_value(key), pos.second); } } // DELETION ROUTINES size_type erase(const key_type& key) { // First, double-check we're not trying to erase delkey or emptyval. assert((!settings.use_empty() || !equals(key, get_key(val_info.emptyval))) && "Erasing the empty key"); assert((!settings.use_deleted() || !equals(key, key_info.delkey)) && "Erasing the deleted key"); const_iterator pos = find(key); // shrug: shouldn't need to be const if ( pos != end() ) { assert(!test_deleted(pos)); // or find() shouldn't have returned it set_deleted(pos); ++num_deleted; settings.set_consider_shrink(true); // will think about shrink after next insert return 1; // because we deleted one thing } else { return 0; // because we deleted nothing } } // We return the iterator past the deleted item. void erase(iterator pos) { if ( pos == end() ) return; // sanity check if ( set_deleted(pos) ) { // true if object has been newly deleted ++num_deleted; settings.set_consider_shrink(true); // will think about shrink after next insert } } void erase(iterator f, iterator l) { for ( ; f != l; ++f) { if ( set_deleted(f) ) // should always be true ++num_deleted; } settings.set_consider_shrink(true); // will think about shrink after next insert } // We allow you to erase a const_iterator just like we allow you to // erase an iterator. This is in parallel to 'delete': you can delete // a const pointer just like a non-const pointer. The logic is that // you can't use the object after it's erased anyway, so it doesn't matter // if it's const or not. void erase(const_iterator pos) { if ( pos == end() ) return; // sanity check if ( set_deleted(pos) ) { // true if object has been newly deleted ++num_deleted; settings.set_consider_shrink(true); // will think about shrink after next insert } } void erase(const_iterator f, const_iterator l) { for ( ; f != l; ++f) { if ( set_deleted(f) ) // should always be true ++num_deleted; } settings.set_consider_shrink(true); // will think about shrink after next insert } // COMPARISON bool operator==(const dense_hashtable& ht) const { if (size() != ht.size()) { return false; } else if (this == &ht) { return true; } else { // Iterate through the elements in "this" and see if the // corresponding element is in ht for ( const_iterator it = begin(); it != end(); ++it ) { const_iterator it2 = ht.find(get_key(*it)); if ((it2 == ht.end()) || (*it != *it2)) { return false; } } return true; } } bool operator!=(const dense_hashtable& ht) const { return !(*this == ht); } // I/O // We support reading and writing hashtables to disk. Alas, since // I don't know how to write a hasher or key_equal, you have to make // sure everything but the table is the same. We compact before writing. private: // Every time the disk format changes, this should probably change too typedef unsigned long MagicNumberType; static const MagicNumberType MAGIC_NUMBER = 0x13578642; public: // I/O -- this is an add-on for writing hash table to disk // // INPUT and OUTPUT must be either a FILE, *or* a C++ stream // (istream, ostream, etc) *or* a class providing // Read(void*, size_t) and Write(const void*, size_t) // (respectively), which writes a buffer into a stream // (which the INPUT/OUTPUT instance presumably owns). typedef sparsehash_internal::pod_serializer NopointerSerializer; // ValueSerializer: a functor. operator()(OUTPUT*, const value_type&) template bool serialize(ValueSerializer serializer, OUTPUT *fp) { squash_deleted(); // so we don't have to worry about delkey if ( !sparsehash_internal::write_bigendian_number(fp, MAGIC_NUMBER, 4) ) return false; if ( !sparsehash_internal::write_bigendian_number(fp, num_buckets, 8) ) return false; if ( !sparsehash_internal::write_bigendian_number(fp, num_elements, 8) ) return false; // Now write a bitmap of non-empty buckets. for ( size_type i = 0; i < num_buckets; i += 8 ) { unsigned char bits = 0; for ( int bit = 0; bit < 8; ++bit ) { if ( i + bit < num_buckets && !test_empty(i + bit) ) bits |= (1 << bit); } if ( !sparsehash_internal::write_data(fp, &bits, sizeof(bits)) ) return false; for ( int bit = 0; bit < 8; ++bit ) { if ( bits & (1 << bit) ) { if ( !serializer(fp, table[i + bit]) ) return false; } } } return true; } // INPUT: anything we've written an overload of read_data() for. // ValueSerializer: a functor. operator()(INPUT*, value_type*) template bool unserialize(ValueSerializer serializer, INPUT *fp) { assert(settings.use_empty() && "empty_key not set for read"); clear(); // just to be consistent MagicNumberType magic_read; if ( !sparsehash_internal::read_bigendian_number(fp, &magic_read, 4) ) return false; if ( magic_read != MAGIC_NUMBER ) { return false; } size_type new_num_buckets; if ( !sparsehash_internal::read_bigendian_number(fp, &new_num_buckets, 8) ) return false; clear_to_size(new_num_buckets); if ( !sparsehash_internal::read_bigendian_number(fp, &num_elements, 8) ) return false; // Read the bitmap of non-empty buckets. for (size_type i = 0; i < num_buckets; i += 8) { unsigned char bits; if ( !sparsehash_internal::read_data(fp, &bits, sizeof(bits)) ) return false; for ( int bit = 0; bit < 8; ++bit ) { if ( i + bit < num_buckets && (bits & (1 << bit)) ) { // not empty if ( !serializer(fp, &table[i + bit]) ) return false; } } } return true; } private: template class alloc_impl : public A { public: typedef typename A::pointer pointer; typedef typename A::size_type size_type; // Convert a normal allocator to one that has realloc_or_die() alloc_impl(const A& a) : A(a) { } // realloc_or_die should only be used when using the default // allocator (libc_allocator_with_realloc). pointer realloc_or_die(pointer /*ptr*/, size_type /*n*/) { fprintf(stderr, "realloc_or_die is only supported for " "libc_allocator_with_realloc\n"); exit(1); return NULL; } }; // A template specialization of alloc_impl for // libc_allocator_with_realloc that can handle realloc_or_die. template class alloc_impl > : public libc_allocator_with_realloc { public: typedef typename libc_allocator_with_realloc::pointer pointer; typedef typename libc_allocator_with_realloc::size_type size_type; alloc_impl(const libc_allocator_with_realloc& a) : libc_allocator_with_realloc(a) { } pointer realloc_or_die(pointer ptr, size_type n) { pointer retval = this->reallocate(ptr, n); if (retval == NULL) { fprintf(stderr, "sparsehash: FATAL ERROR: failed to reallocate " "%lu elements for ptr %p", static_cast(n), ptr); exit(1); } return retval; } }; // Package allocator with emptyval to eliminate memory needed for // the zero-size allocator. // If new fields are added to this class, we should add them to // operator= and swap. class ValInfo : public alloc_impl { public: typedef typename alloc_impl::value_type value_type; ValInfo(const alloc_impl& a) : alloc_impl(a), emptyval() { } ValInfo(const ValInfo& v) : alloc_impl(v), emptyval(v.emptyval) { } value_type emptyval; // which key marks unused entries }; // Package functors with another class to eliminate memory needed for // zero-size functors. Since ExtractKey and hasher's operator() might // have the same function signature, they must be packaged in // different classes. struct Settings : sparsehash_internal::sh_hashtable_settings { explicit Settings(const hasher& hf) : sparsehash_internal::sh_hashtable_settings( hf, HT_OCCUPANCY_PCT / 100.0f, HT_EMPTY_PCT / 100.0f) {} }; // Packages ExtractKey and SetKey functors. class KeyInfo : public ExtractKey, public SetKey, public EqualKey { public: KeyInfo(const ExtractKey& ek, const SetKey& sk, const EqualKey& eq) : ExtractKey(ek), SetKey(sk), EqualKey(eq) { } // We want to return the exact same type as ExtractKey: Key or const Key& typename ExtractKey::result_type get_key(const_reference v) const { return ExtractKey::operator()(v); } void set_key(pointer v, const key_type& k) const { SetKey::operator()(v, k); } bool equals(const key_type& a, const key_type& b) const { return EqualKey::operator()(a, b); } // Which key marks deleted entries. // TODO(csilvers): make a pointer, and get rid of use_deleted (benchmark!) typename base::remove_const::type delkey; }; // Utility functions to access the templated operators size_type hash(const key_type& v) const { return settings.hash(v); } bool equals(const key_type& a, const key_type& b) const { return key_info.equals(a, b); } typename ExtractKey::result_type get_key(const_reference v) const { return key_info.get_key(v); } void set_key(pointer v, const key_type& k) const { key_info.set_key(v, k); } private: // Actual data Settings settings; KeyInfo key_info; size_type num_deleted; // how many occupied buckets are marked deleted size_type num_elements; size_type num_buckets; ValInfo val_info; // holds emptyval, and also the allocator pointer table; }; // We need a global swap as well template inline void swap(dense_hashtable &x, dense_hashtable &y) { x.swap(y); } #undef JUMP_ template const typename dense_hashtable::size_type dense_hashtable::ILLEGAL_BUCKET; // How full we let the table get before we resize. Knuth says .8 is // good -- higher causes us to probe too much, though saves memory. // However, we go with .5, getting better performance at the cost of // more space (a trade-off densehashtable explicitly chooses to make). // Feel free to play around with different values, though, via // max_load_factor() and/or set_resizing_parameters(). template const int dense_hashtable::HT_OCCUPANCY_PCT = 50; // How empty we let the table get before we resize lower. // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing. template const int dense_hashtable::HT_EMPTY_PCT = static_cast(0.4 * dense_hashtable::HT_OCCUPANCY_PCT); _END_GOOGLE_NAMESPACE_ #endif /* _DENSEHASHTABLE_H_ */ diff --git a/util/google/sparsehash/hashtable-common.h b/util/google/sparsehash/hashtable-common.h index 9578cfd290..d58b9d30cc 100644 --- a/util/google/sparsehash/hashtable-common.h +++ b/util/google/sparsehash/hashtable-common.h @@ -1,381 +1,381 @@ // Copyright (c) 2010, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // --- // // Provides classes shared by both sparse and dense hashtable. // // sh_hashtable_settings has parameters for growing and shrinking // a hashtable. It also packages zero-size functor (ie. hasher). // // Other functions and classes provide common code for serializing // and deserializing hashtables to a stream (such as a FILE*). #ifndef UTIL_GTL_HASHTABLE_COMMON_H_ #define UTIL_GTL_HASHTABLE_COMMON_H_ #include #include #include #include // for size_t #include #include // For length_error _START_GOOGLE_NAMESPACE_ template struct SparsehashCompileAssert { }; #define SPARSEHASH_COMPILE_ASSERT(expr, msg) \ typedef SparsehashCompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] namespace sparsehash_internal { // Adaptor methods for reading/writing data from an INPUT or OUPTUT // variable passed to serialize() or unserialize(). For now we // have implemented INPUT/OUTPUT for FILE*, istream*/ostream* (note // they are pointers, unlike typical use), or else a pointer to // something that supports a Read()/Write() method. // // For technical reasons, we implement read_data/write_data in two // stages. The actual work is done in *_data_internal, which takes // the stream argument twice: once as a template type, and once with // normal type information. (We only use the second version.) We do // this because of how C++ picks what function overload to use. If we // implemented this the naive way: // bool read_data(istream* is, const void* data, size_t length); // template read_data(T* fp, const void* data, size_t length); // C++ would prefer the second version for every stream type except // istream. However, we want C++ to prefer the first version for // streams that are *subclasses* of istream, such as istringstream. // This is not possible given the way template types are resolved. So // we split the stream argument in two, one of which is templated and // one of which is not. The specialized functions (like the istream // version above) ignore the template arg and use the second, 'type' // arg, getting subclass matching as normal. The 'catch-all' // functions (the second version above) use the template arg to deduce // the type, and use a second, void* arg to achieve the desired // 'catch-all' semantics. // ----- low-level I/O for FILE* ---- template inline bool read_data_internal(Ignored*, FILE* fp, void* data, size_t length) { return fread(data, length, 1, fp) == 1; } template inline bool write_data_internal(Ignored*, FILE* fp, const void* data, size_t length) { return fwrite(data, length, 1, fp) == 1; } // ----- low-level I/O for iostream ---- // We want the caller to be responsible for #including , not // us, because iostream is a big header! According to the standard, // it's only legal to delay the instantiation the way we want to if // the istream/ostream is a template type. So we jump through hoops. template inline bool read_data_internal_for_istream(ISTREAM* fp, void* data, size_t length) { return fp->read(reinterpret_cast(data), length).good(); } template inline bool read_data_internal(Ignored*, std::istream* fp, void* data, size_t length) { return read_data_internal_for_istream(fp, data, length); } template inline bool write_data_internal_for_ostream(OSTREAM* fp, const void* data, size_t length) { return fp->write(reinterpret_cast(data), length).good(); } template inline bool write_data_internal(Ignored*, std::ostream* fp, const void* data, size_t length) { return write_data_internal_for_ostream(fp, data, length); } // ----- low-level I/O for custom streams ---- // The INPUT type needs to support a Read() method that takes a // buffer and a length and returns the number of bytes read. template inline bool read_data_internal(INPUT* fp, void*, void* data, size_t length) { return static_cast(fp->Read(data, length)) == length; } // The OUTPUT type needs to support a Write() operation that takes // a buffer and a length and returns the number of bytes written. template inline bool write_data_internal(OUTPUT* fp, void*, const void* data, size_t length) { return static_cast(fp->Write(data, length)) == length; } // ----- low-level I/O: the public API ---- template inline bool read_data(INPUT* fp, void* data, size_t length) { return read_data_internal(fp, fp, data, length); } template inline bool write_data(OUTPUT* fp, const void* data, size_t length) { return write_data_internal(fp, fp, data, length); } // Uses read_data() and write_data() to read/write an integer. // length is the number of bytes to read/write (which may differ // from sizeof(IntType), allowing us to save on a 32-bit system // and load on a 64-bit system). Excess bytes are taken to be 0. // INPUT and OUTPUT must match legal inputs to read/write_data (above). template bool read_bigendian_number(INPUT* fp, IntType* value, size_t length) { *value = 0; unsigned char byte; // We require IntType to be unsigned or else the shifting gets all screwy. SPARSEHASH_COMPILE_ASSERT(static_cast(-1) > static_cast(0), serializing_int_requires_an_unsigned_type); for (size_t i = 0; i < length; ++i) { if (!read_data(fp, &byte, sizeof(byte))) return false; *value |= static_cast(byte) << ((length - 1 - i) * 8); } return true; } template bool write_bigendian_number(OUTPUT* fp, IntType value, size_t length) { unsigned char byte; // We require IntType to be unsigned or else the shifting gets all screwy. SPARSEHASH_COMPILE_ASSERT(static_cast(-1) > static_cast(0), serializing_int_requires_an_unsigned_type); for (size_t i = 0; i < length; ++i) { byte = (sizeof(value) <= length-1 - i) ? 0 : static_cast((value >> ((length-1 - i) * 8)) & 255); if (!write_data(fp, &byte, sizeof(byte))) return false; } return true; } // If your keys and values are simple enough, you can pass this // serializer to serialize()/unserialize(). "Simple enough" means // value_type is a POD type that contains no pointers. Note, // however, we don't try to normalize endianness. // This is the type used for NopointerSerializer. template struct pod_serializer { template bool operator()(INPUT* fp, value_type* value) const { return read_data(fp, value, sizeof(*value)); } template bool operator()(OUTPUT* fp, const value_type& value) const { return write_data(fp, &value, sizeof(value)); } }; // Settings contains parameters for growing and shrinking the table. // It also packages zero-size functor (ie. hasher). // // It does some munging of the hash value in cases where we think // (fear) the original hash function might not be very good. In // particular, the default hash of pointers is the identity hash, // so probably all the low bits are 0. We identify when we think // we're hashing a pointer, and chop off the low bits. Note this // isn't perfect: even when the key is a pointer, we can't tell // for sure that the hash is the identity hash. If it's not, this // is needless work (and possibly, though not likely, harmful). template class sh_hashtable_settings : public HashFunc { public: typedef Key key_type; typedef HashFunc hasher; typedef SizeType size_type; public: sh_hashtable_settings(const hasher& hf, const float ht_occupancy_flt, const float ht_empty_flt) : hasher(hf), enlarge_threshold_(0), shrink_threshold_(0), consider_shrink_(false), use_empty_(false), use_deleted_(false), num_ht_copies_(0) { set_enlarge_factor(ht_occupancy_flt); set_shrink_factor(ht_empty_flt); } size_type hash(const key_type& v) const { // We munge the hash value when we don't trust hasher::operator(). return hash_munger::MungedHash(hasher::operator()(v)); } float enlarge_factor() const { return enlarge_factor_; } void set_enlarge_factor(float f) { enlarge_factor_ = f; } float shrink_factor() const { return shrink_factor_; } void set_shrink_factor(float f) { shrink_factor_ = f; } size_type enlarge_threshold() const { return enlarge_threshold_; } void set_enlarge_threshold(size_type t) { enlarge_threshold_ = t; } size_type shrink_threshold() const { return shrink_threshold_; } void set_shrink_threshold(size_type t) { shrink_threshold_ = t; } size_type enlarge_size(size_type x) const { return static_cast(x * enlarge_factor_); } size_type shrink_size(size_type x) const { return static_cast(x * shrink_factor_); } bool consider_shrink() const { return consider_shrink_; } void set_consider_shrink(bool t) { consider_shrink_ = t; } bool use_empty() const { return use_empty_; } void set_use_empty(bool t) { use_empty_ = t; } bool use_deleted() const { return use_deleted_; } void set_use_deleted(bool t) { use_deleted_ = t; } size_type num_ht_copies() const { return static_cast(num_ht_copies_); } void inc_num_ht_copies() { ++num_ht_copies_; } // Reset the enlarge and shrink thresholds void reset_thresholds(size_type num_buckets) { set_enlarge_threshold(enlarge_size(num_buckets)); set_shrink_threshold(shrink_size(num_buckets)); // whatever caused us to reset already considered set_consider_shrink(false); } - // Caller is resposible for calling reset_threshold right after + // Caller is responsible for calling reset_threshold right after // set_resizing_parameters. void set_resizing_parameters(float shrink, float grow) { assert(shrink >= 0.0); assert(grow <= 1.0); if (shrink > grow/2.0f) shrink = grow / 2.0f; // otherwise we thrash hashtable size set_shrink_factor(shrink); set_enlarge_factor(grow); } // This is the smallest size a hashtable can be without being too crowded // If you like, you can give a min #buckets as well as a min #elts size_type min_buckets(size_type num_elts, size_type min_buckets_wanted) { float enlarge = enlarge_factor(); size_type sz = HT_MIN_BUCKETS; // min buckets allowed while ( sz < min_buckets_wanted || num_elts >= static_cast(sz * enlarge) ) { // This just prevents overflowing size_type, since sz can exceed // max_size() here. if (static_cast(sz * 2) < sz) { // throw std::length_error("resize overflow"); // protect against overflow } sz *= 2; } return sz; } private: template class hash_munger { public: static size_t MungedHash(size_t hash) { return hash; } }; // This matches when the hashtable key is a pointer. template class hash_munger { public: static size_t MungedHash(size_t hash) { // TODO(csilvers): consider rotating instead: // static const int shift = (sizeof(void *) == 4) ? 2 : 3; // return (hash << (sizeof(hash) * 8) - shift)) | (hash >> shift); // This matters if we ever change sparse/dense_hash_* to compare // hashes before comparing actual values. It's speedy on x86. return hash / sizeof(void*); // get rid of known-0 bits } }; size_type enlarge_threshold_; // table.size() * enlarge_factor size_type shrink_threshold_; // table.size() * shrink_factor float enlarge_factor_; // how full before resize float shrink_factor_; // how empty before resize // consider_shrink=true if we should try to shrink before next insert bool consider_shrink_; bool use_empty_; // used only by densehashtable, not sparsehashtable bool use_deleted_; // false until delkey has been set // num_ht_copies is a counter incremented every Copy/Move unsigned int num_ht_copies_; }; } // namespace sparsehash_internal #undef SPARSEHASH_COMPILE_ASSERT _END_GOOGLE_NAMESPACE_ #endif // UTIL_GTL_HASHTABLE_COMMON_H_ diff --git a/util/google/sparsehash/sparsehashtable.h b/util/google/sparsehash/sparsehashtable.h index f3a2257995..d0059191e4 100644 --- a/util/google/sparsehash/sparsehashtable.h +++ b/util/google/sparsehash/sparsehashtable.h @@ -1,1247 +1,1247 @@ // Copyright (c) 2005, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // --- // // A sparse hashtable is a particular implementation of // a hashtable: one that is meant to minimize memory use. // It does this by using a *sparse table* (cf sparsetable.h), // which uses between 1 and 2 bits to store empty buckets // (we may need another bit for hashtables that support deletion). // // When empty buckets are so cheap, an appealing hashtable // implementation is internal probing, in which the hashtable // is a single table, and collisions are resolved by trying // to insert again in another bucket. The most cache-efficient // internal probing schemes are linear probing (which suffers, // alas, from clumping) and quadratic probing, which is what // we implement by default. // // Deleted buckets are a bit of a pain. We have to somehow mark // deleted buckets (the probing must distinguish them from empty // buckets). The most principled way is to have another bitmap, // but that's annoying and takes up space. Instead we let the // user specify an "impossible" key. We set deleted buckets // to have the impossible key. // // Note it is possible to change the value of the delete key // on the fly; you can even remove it, though after that point // the hashtable is insert_only until you set it again. // // You probably shouldn't use this code directly. Use // sparse_hash_map<> or sparse_hash_set<> instead. // // You can modify the following, below: // HT_OCCUPANCY_PCT -- how full before we double size // HT_EMPTY_PCT -- how empty before we halve size // HT_MIN_BUCKETS -- smallest bucket size // HT_DEFAULT_STARTING_BUCKETS -- default bucket size at construct-time // // You can also change enlarge_factor (which defaults to // HT_OCCUPANCY_PCT), and shrink_factor (which defaults to // HT_EMPTY_PCT) with set_resizing_parameters(). // // How to decide what values to use? // shrink_factor's default of .4 * OCCUPANCY_PCT, is probably good. // HT_MIN_BUCKETS is probably unnecessary since you can specify // (indirectly) the starting number of buckets at construct-time. // For enlarge_factor, you can use this chart to try to trade-off // expected lookup time to the space taken up. By default, this // code uses quadratic probing, though you can change it to linear // via _JUMP below if you really want to. // // From http://www.augustana.ca/~mohrj/courses/1999.fall/csc210/lecture_notes/hashing.html // NUMBER OF PROBES / LOOKUP Successful Unsuccessful // Quadratic collision resolution 1 - ln(1-L) - L/2 1/(1-L) - L - ln(1-L) // Linear collision resolution [1+1/(1-L)]/2 [1+1/(1-L)2]/2 // // -- enlarge_factor -- 0.10 0.50 0.60 0.75 0.80 0.90 0.99 // QUADRATIC COLLISION RES. // probes/successful lookup 1.05 1.44 1.62 2.01 2.21 2.85 5.11 // probes/unsuccessful lookup 1.11 2.19 2.82 4.64 5.81 11.4 103.6 // LINEAR COLLISION RES. // probes/successful lookup 1.06 1.5 1.75 2.5 3.0 5.5 50.5 // probes/unsuccessful lookup 1.12 2.5 3.6 8.5 13.0 50.0 5000.0 // // The value type is required to be copy constructible and default // constructible, but it need not be (and commonly isn't) assignable. #ifndef _SPARSEHASHTABLE_H_ #define _SPARSEHASHTABLE_H_ #include #include #include // For swap(), eg #include // for iterator tags #include // for numeric_limits #include // for pair #include // for remove_const #include #include // IWYU pragma: export #include // For length_error _START_GOOGLE_NAMESPACE_ namespace base { // just to make google->opensource transition easier using GOOGLE_NAMESPACE::remove_const; } #ifndef SPARSEHASH_STAT_UPDATE #define SPARSEHASH_STAT_UPDATE(x) ((void) 0) #endif // The probing method // Linear probing // #define JUMP_(key, num_probes) ( 1 ) // Quadratic probing #define JUMP_(key, num_probes) ( num_probes ) // The smaller this is, the faster lookup is (because the group bitmap is // smaller) and the faster insert is, because there's less to move. // On the other hand, there are more groups. Since group::size_type is // a short, this number should be of the form 32*x + 16 to avoid waste. static const u_int16_t DEFAULT_GROUP_SIZE = 48; // fits in 1.5 words // Hashtable class, used to implement the hashed associative containers // hash_set and hash_map. // // Value: what is stored in the table (each bucket is a Value). // Key: something in a 1-to-1 correspondence to a Value, that can be used // to search for a Value in the table (find() takes a Key). // HashFcn: Takes a Key and returns an integer, the more unique the better. // ExtractKey: given a Value, returns the unique Key associated with it. // Must inherit from unary_function, or at least have a // result_type enum indicating the return type of operator(). // SetKey: given a Value* and a Key, modifies the value such that // ExtractKey(value) == key. We guarantee this is only called // with key == deleted_key. // EqualKey: Given two Keys, says whether they are the same (that is, // if they are both associated with the same Value). // Alloc: STL allocator to use to allocate memory. template class sparse_hashtable; template struct sparse_hashtable_iterator; template struct sparse_hashtable_const_iterator; // As far as iterating, we're basically just a sparsetable // that skips over deleted elements. template struct sparse_hashtable_iterator { private: typedef typename A::template rebind::other value_alloc_type; public: typedef sparse_hashtable_iterator iterator; typedef sparse_hashtable_const_iterator const_iterator; typedef typename sparsetable::nonempty_iterator st_iterator; typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::reference reference; typedef typename value_alloc_type::pointer pointer; // "Real" constructor and default constructor sparse_hashtable_iterator(const sparse_hashtable *h, st_iterator it, st_iterator it_end) : ht(h), pos(it), end(it_end) { advance_past_deleted(); } sparse_hashtable_iterator() { } // not ever used internally // The default destructor is fine; we don't define one // The default operator= is fine; we don't define one // Happy dereferencer reference operator*() const { return *pos; } pointer operator->() const { return &(operator*()); } // Arithmetic. The only hard part is making sure that // we're not on a marked-deleted array element void advance_past_deleted() { while ( pos != end && ht->test_deleted(*this) ) ++pos; } iterator& operator++() { assert(pos != end); ++pos; advance_past_deleted(); return *this; } iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } // Comparison. bool operator==(const iterator& it) const { return pos == it.pos; } bool operator!=(const iterator& it) const { return pos != it.pos; } // The actual data const sparse_hashtable *ht; st_iterator pos, end; }; // Now do it all again, but with const-ness! template struct sparse_hashtable_const_iterator { private: typedef typename A::template rebind::other value_alloc_type; public: typedef sparse_hashtable_iterator iterator; typedef sparse_hashtable_const_iterator const_iterator; typedef typename sparsetable::const_nonempty_iterator st_iterator; typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::const_reference reference; typedef typename value_alloc_type::const_pointer pointer; // "Real" constructor and default constructor sparse_hashtable_const_iterator(const sparse_hashtable *h, st_iterator it, st_iterator it_end) : ht(h), pos(it), end(it_end) { advance_past_deleted(); } // This lets us convert regular iterators to const iterators sparse_hashtable_const_iterator() { } // never used internally sparse_hashtable_const_iterator(const iterator &it) : ht(it.ht), pos(it.pos), end(it.end) { } // The default destructor is fine; we don't define one // The default operator= is fine; we don't define one // Happy dereferencer reference operator*() const { return *pos; } pointer operator->() const { return &(operator*()); } // Arithmetic. The only hard part is making sure that // we're not on a marked-deleted array element void advance_past_deleted() { while ( pos != end && ht->test_deleted(*this) ) ++pos; } const_iterator& operator++() { assert(pos != end); ++pos; advance_past_deleted(); return *this; } const_iterator operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } // Comparison. bool operator==(const const_iterator& it) const { return pos == it.pos; } bool operator!=(const const_iterator& it) const { return pos != it.pos; } // The actual data const sparse_hashtable *ht; st_iterator pos, end; }; // And once again, but this time freeing up memory as we iterate template struct sparse_hashtable_destructive_iterator { private: typedef typename A::template rebind::other value_alloc_type; public: typedef sparse_hashtable_destructive_iterator iterator; typedef typename sparsetable::destructive_iterator st_iterator; typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::reference reference; typedef typename value_alloc_type::pointer pointer; // "Real" constructor and default constructor sparse_hashtable_destructive_iterator(const sparse_hashtable *h, st_iterator it, st_iterator it_end) : ht(h), pos(it), end(it_end) { advance_past_deleted(); } sparse_hashtable_destructive_iterator() { } // never used internally // The default destructor is fine; we don't define one // The default operator= is fine; we don't define one // Happy dereferencer reference operator*() const { return *pos; } pointer operator->() const { return &(operator*()); } // Arithmetic. The only hard part is making sure that // we're not on a marked-deleted array element void advance_past_deleted() { while ( pos != end && ht->test_deleted(*this) ) ++pos; } iterator& operator++() { assert(pos != end); ++pos; advance_past_deleted(); return *this; } iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } // Comparison. bool operator==(const iterator& it) const { return pos == it.pos; } bool operator!=(const iterator& it) const { return pos != it.pos; } // The actual data const sparse_hashtable *ht; st_iterator pos, end; }; template class sparse_hashtable { private: typedef typename Alloc::template rebind::other value_alloc_type; public: typedef Key key_type; typedef Value value_type; typedef HashFcn hasher; typedef EqualKey key_equal; typedef Alloc allocator_type; typedef typename value_alloc_type::size_type size_type; typedef typename value_alloc_type::difference_type difference_type; typedef typename value_alloc_type::reference reference; typedef typename value_alloc_type::const_reference const_reference; typedef typename value_alloc_type::pointer pointer; typedef typename value_alloc_type::const_pointer const_pointer; typedef sparse_hashtable_iterator iterator; typedef sparse_hashtable_const_iterator const_iterator; typedef sparse_hashtable_destructive_iterator destructive_iterator; // These come from tr1. For us they're the same as regular iterators. typedef iterator local_iterator; typedef const_iterator const_local_iterator; // How full we let the table get before we resize, by default. // Knuth says .8 is good -- higher causes us to probe too much, // though it saves memory. static const int HT_OCCUPANCY_PCT; // = 80 (out of 100); // How empty we let the table get before we resize lower, by default. // (0.0 means never resize lower.) // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing static const int HT_EMPTY_PCT; // = 0.4 * HT_OCCUPANCY_PCT; // Minimum size we're willing to let hashtables be. // Must be a power of two, and at least 4. // Note, however, that for a given hashtable, the initial size is a // function of the first constructor arg, and may be >HT_MIN_BUCKETS. static const size_type HT_MIN_BUCKETS = 4; // By default, if you don't specify a hashtable size at // construction-time, we use this size. Must be a power of two, and // at least HT_MIN_BUCKETS. static const size_type HT_DEFAULT_STARTING_BUCKETS = 32; // ITERATOR FUNCTIONS iterator begin() { return iterator(this, table.nonempty_begin(), table.nonempty_end()); } iterator end() { return iterator(this, table.nonempty_end(), table.nonempty_end()); } const_iterator begin() const { return const_iterator(this, table.nonempty_begin(), table.nonempty_end()); } const_iterator end() const { return const_iterator(this, table.nonempty_end(), table.nonempty_end()); } // These come from tr1 unordered_map. They iterate over 'bucket' n. // For sparsehashtable, we could consider each 'group' to be a bucket, // I guess, but I don't really see the point. We'll just consider // bucket n to be the n-th element of the sparsetable, if it's occupied, // or some empty element, otherwise. local_iterator begin(size_type i) { if (table.test(i)) return local_iterator(this, table.get_iter(i), table.nonempty_end()); else return local_iterator(this, table.nonempty_end(), table.nonempty_end()); } local_iterator end(size_type i) { local_iterator it = begin(i); if (table.test(i) && !test_deleted(i)) ++it; return it; } const_local_iterator begin(size_type i) const { if (table.test(i)) return const_local_iterator(this, table.get_iter(i), table.nonempty_end()); else return const_local_iterator(this, table.nonempty_end(), table.nonempty_end()); } const_local_iterator end(size_type i) const { const_local_iterator it = begin(i); if (table.test(i) && !test_deleted(i)) ++it; return it; } // This is used when resizing destructive_iterator destructive_begin() { return destructive_iterator(this, table.destructive_begin(), table.destructive_end()); } destructive_iterator destructive_end() { return destructive_iterator(this, table.destructive_end(), table.destructive_end()); } // ACCESSOR FUNCTIONS for the things we templatize on, basically hasher hash_funct() const { return settings; } key_equal key_eq() const { return key_info; } allocator_type get_allocator() const { return table.get_allocator(); } // Accessor function for statistics gathering. int num_table_copies() const { return settings.num_ht_copies(); } private: // We need to copy values when we set the special marker for deleted // elements, but, annoyingly, we can't just use the copy assignment // operator because value_type might not be assignable (it's often // pair). We use explicit destructor invocation and // placement new to get around this. Arg. void set_value(pointer dst, const_reference src) { dst->~value_type(); // delete the old value, if any new(dst) value_type(src); } // This is used as a tag for the copy constructor, saying to destroy its // arg We have two ways of destructively copying: with potentially growing // the hashtable as we copy, and without. To make sure the outside world // can't do a destructive copy, we make the typename private. enum MoveDontCopyT {MoveDontCopy, MoveDontGrow}; // DELETE HELPER FUNCTIONS // This lets the user describe a key that will indicate deleted // table entries. This key should be an "impossible" entry -- // if you try to insert it for real, you won't be able to retrieve it! // (NB: while you pass in an entire value, only the key part is looked // at. This is just because I don't know how to assign just a key.) private: void squash_deleted() { // gets rid of any deleted entries we have if ( num_deleted ) { // get rid of deleted before writing sparse_hashtable tmp(MoveDontGrow, *this); swap(tmp); // now we are tmp } assert(num_deleted == 0); } // Test if the given key is the deleted indicator. Requires // num_deleted > 0, for correctness of read(), and because that // guarantees that key_info.delkey is valid. bool test_deleted_key(const key_type& key) const { assert(num_deleted > 0); return equals(key_info.delkey, key); } public: void set_deleted_key(const key_type &key) { // It's only safe to change what "deleted" means if we purge deleted guys squash_deleted(); settings.set_use_deleted(true); key_info.delkey = key; } void clear_deleted_key() { squash_deleted(); settings.set_use_deleted(false); } key_type deleted_key() const { assert(settings.use_deleted() && "Must set deleted key before calling deleted_key"); return key_info.delkey; } // These are public so the iterators can use them // True if the item at position bucknum is "deleted" marker bool test_deleted(size_type bucknum) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && table.test(bucknum) && test_deleted_key(get_key(table.unsafe_get(bucknum))); } bool test_deleted(const iterator &it) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(*it)); } bool test_deleted(const const_iterator &it) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(*it)); } bool test_deleted(const destructive_iterator &it) const { // Invariant: !use_deleted() implies num_deleted is 0. assert(settings.use_deleted() || num_deleted == 0); return num_deleted > 0 && test_deleted_key(get_key(*it)); } private: void check_use_deleted(const char* caller) { (void)caller; // could log it if the assert failed assert(settings.use_deleted()); } // Set it so test_deleted is true. true if object didn't used to be deleted. // TODO(csilvers): make these private (also in densehashtable.h) bool set_deleted(iterator &it) { check_use_deleted("set_deleted()"); bool retval = !test_deleted(it); // &* converts from iterator to value-type. set_key(&(*it), key_info.delkey); return retval; } // Set it so test_deleted is false. true if object used to be deleted. bool clear_deleted(iterator &it) { check_use_deleted("clear_deleted()"); // Happens automatically when we assign something else in its place. return test_deleted(it); } // We also allow to set/clear the deleted bit on a const iterator. // We allow a const_iterator for the same reason you can delete a // const pointer: it's convenient, and semantically you can't use // 'it' after it's been deleted anyway, so its const-ness doesn't // really matter. bool set_deleted(const_iterator &it) { check_use_deleted("set_deleted()"); bool retval = !test_deleted(it); set_key(const_cast(&(*it)), key_info.delkey); return retval; } // Set it so test_deleted is false. true if object used to be deleted. bool clear_deleted(const_iterator &it) { check_use_deleted("clear_deleted()"); return test_deleted(it); } // FUNCTIONS CONCERNING SIZE public: size_type size() const { return table.num_nonempty() - num_deleted; } size_type max_size() const { return table.max_size(); } bool empty() const { return size() == 0; } size_type bucket_count() const { return table.size(); } size_type max_bucket_count() const { return max_size(); } // These are tr1 methods. Their idea of 'bucket' doesn't map well to // what we do. We just say every bucket has 0 or 1 items in it. size_type bucket_size(size_type i) const { return begin(i) == end(i) ? 0 : 1; } private: // Because of the above, size_type(-1) is never legal; use it for errors static const size_type ILLEGAL_BUCKET = size_type(-1); // Used after a string of deletes. Returns true if we actually shrunk. // TODO(csilvers): take a delta so we can take into account inserts // done after shrinking. Maybe make part of the Settings class? bool maybe_shrink() { assert(table.num_nonempty() >= num_deleted); assert((bucket_count() & (bucket_count()-1)) == 0); // is a power of two assert(bucket_count() >= HT_MIN_BUCKETS); bool retval = false; // If you construct a hashtable with < HT_DEFAULT_STARTING_BUCKETS, // we'll never shrink until you get relatively big, and we'll never // shrink below HT_DEFAULT_STARTING_BUCKETS. Otherwise, something // like "dense_hash_set x; x.insert(4); x.erase(4);" will // shrink us down to HT_MIN_BUCKETS buckets, which is too small. const size_type num_remain = table.num_nonempty() - num_deleted; const size_type shrink_threshold = settings.shrink_threshold(); if (shrink_threshold > 0 && num_remain < shrink_threshold && bucket_count() > HT_DEFAULT_STARTING_BUCKETS) { const float shrink_factor = settings.shrink_factor(); size_type sz = bucket_count() / 2; // find how much we should shrink while (sz > HT_DEFAULT_STARTING_BUCKETS && num_remain < static_cast(sz * shrink_factor)) { sz /= 2; // stay a power of 2 } sparse_hashtable tmp(MoveDontCopy, *this, sz); swap(tmp); // now we are tmp retval = true; } settings.set_consider_shrink(false); // because we just considered it return retval; } // We'll let you resize a hashtable -- though this makes us copy all! // When you resize, you say, "make it big enough for this many more elements" // Returns true if we actually resized, false if size was already ok. bool resize_delta(size_type delta) { bool did_resize = false; if ( settings.consider_shrink() ) { // see if lots of deletes happened if ( maybe_shrink() ) did_resize = true; } if (table.num_nonempty() >= (std::numeric_limits::max)() - delta) { throw std::length_error("resize overflow"); } if ( bucket_count() >= HT_MIN_BUCKETS && (table.num_nonempty() + delta) <= settings.enlarge_threshold() ) return did_resize; // we're ok as we are // Sometimes, we need to resize just to get rid of all the // "deleted" buckets that are clogging up the hashtable. So when // deciding whether to resize, count the deleted buckets (which // are currently taking up room). But later, when we decide what // size to resize to, *don't* count deleted buckets, since they // get discarded during the resize. const size_type needed_size = settings.min_buckets(table.num_nonempty() + delta, 0); if ( needed_size <= bucket_count() ) // we have enough buckets return did_resize; size_type resize_to = settings.min_buckets(table.num_nonempty() - num_deleted + delta, bucket_count()); if (resize_to < needed_size && // may double resize_to resize_to < (std::numeric_limits::max)() / 2) { // This situation means that we have enough deleted elements, // that once we purge them, we won't actually have needed to // grow. But we may want to grow anyway: if we just purge one // element, say, we'll have to grow anyway next time we // insert. Might as well grow now, since we're already going // through the trouble of copying (in order to purge the // deleted elements). const size_type target = static_cast(settings.shrink_size(resize_to*2)); if (table.num_nonempty() - num_deleted + delta >= target) { - // Good, we won't be below the shrink threshhold even if we double. + // Good, we won't be below the shrink threshold even if we double. resize_to *= 2; } } sparse_hashtable tmp(MoveDontCopy, *this, resize_to); swap(tmp); // now we are tmp return true; } // Used to actually do the rehashing when we grow/shrink a hashtable void copy_from(const sparse_hashtable &ht, size_type min_buckets_wanted) { clear(); // clear table, set num_deleted to 0 // If we need to change the size of our table, do it now const size_type resize_to = settings.min_buckets(ht.size(), min_buckets_wanted); if ( resize_to > bucket_count() ) { // we don't have enough buckets table.resize(resize_to); // sets the number of buckets settings.reset_thresholds(bucket_count()); } // We use a normal iterator to get non-deleted bcks from ht // We could use insert() here, but since we know there are // no duplicates and no deleted items, we can be more efficient assert((bucket_count() & (bucket_count()-1)) == 0); // a power of two for ( const_iterator it = ht.begin(); it != ht.end(); ++it ) { size_type num_probes = 0; // how many times we've probed size_type bucknum; const size_type bucket_count_minus_one = bucket_count() - 1; for (bucknum = hash(get_key(*it)) & bucket_count_minus_one; table.test(bucknum); // not empty bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one) { ++num_probes; assert(num_probes < bucket_count() && "Hashtable is full: an error in key_equal<> or hash<>"); } table.set(bucknum, *it); // copies the value to here } settings.inc_num_ht_copies(); } // Implementation is like copy_from, but it destroys the table of the // "from" guy by freeing sparsetable memory as we iterate. This is // useful in resizing, since we're throwing away the "from" guy anyway. void move_from(MoveDontCopyT mover, sparse_hashtable &ht, size_type min_buckets_wanted) { clear(); // clear table, set num_deleted to 0 // If we need to change the size of our table, do it now size_type resize_to; if ( mover == MoveDontGrow ) resize_to = ht.bucket_count(); // keep same size as old ht else // MoveDontCopy resize_to = settings.min_buckets(ht.size(), min_buckets_wanted); if ( resize_to > bucket_count() ) { // we don't have enough buckets table.resize(resize_to); // sets the number of buckets settings.reset_thresholds(bucket_count()); } // We use a normal iterator to get non-deleted bcks from ht // We could use insert() here, but since we know there are // no duplicates and no deleted items, we can be more efficient assert( (bucket_count() & (bucket_count()-1)) == 0); // a power of two // THIS IS THE MAJOR LINE THAT DIFFERS FROM COPY_FROM(): for ( destructive_iterator it = ht.destructive_begin(); it != ht.destructive_end(); ++it ) { size_type num_probes = 0; // how many times we've probed size_type bucknum; for ( bucknum = hash(get_key(*it)) & (bucket_count()-1); // h % buck_cnt table.test(bucknum); // not empty bucknum = (bucknum + JUMP_(key, num_probes)) & (bucket_count()-1) ) { ++num_probes; assert(num_probes < bucket_count() && "Hashtable is full: an error in key_equal<> or hash<>"); } table.set(bucknum, *it); // copies the value to here } settings.inc_num_ht_copies(); } // Required by the spec for hashed associative container public: // Though the docs say this should be num_buckets, I think it's much // more useful as num_elements. As a special feature, calling with // req_elements==0 will cause us to shrink if we can, saving space. void resize(size_type req_elements) { // resize to this or larger if ( settings.consider_shrink() || req_elements == 0 ) maybe_shrink(); if ( req_elements > table.num_nonempty() ) // we only grow resize_delta(req_elements - table.num_nonempty()); } // Get and change the value of shrink_factor and enlarge_factor. The // description at the beginning of this file explains how to choose // the values. Setting the shrink parameter to 0.0 ensures that the // table never shrinks. void get_resizing_parameters(float* shrink, float* grow) const { *shrink = settings.shrink_factor(); *grow = settings.enlarge_factor(); } void set_resizing_parameters(float shrink, float grow) { settings.set_resizing_parameters(shrink, grow); settings.reset_thresholds(bucket_count()); } // CONSTRUCTORS -- as required by the specs, we take a size, // but also let you specify a hashfunction, key comparator, // and key extractor. We also define a copy constructor and =. // DESTRUCTOR -- the default is fine, surprisingly. explicit sparse_hashtable(size_type expected_max_items_in_table = 0, const HashFcn& hf = HashFcn(), const EqualKey& eql = EqualKey(), const ExtractKey& ext = ExtractKey(), const SetKey& set = SetKey(), const Alloc& alloc = Alloc()) : settings(hf), key_info(ext, set, eql), num_deleted(0), table((expected_max_items_in_table == 0 ? HT_DEFAULT_STARTING_BUCKETS : settings.min_buckets(expected_max_items_in_table, 0)), alloc) { settings.reset_thresholds(bucket_count()); } // As a convenience for resize(), we allow an optional second argument // which lets you make this new hashtable a different size than ht. // We also provide a mechanism of saying you want to "move" the ht argument // into us instead of copying. sparse_hashtable(const sparse_hashtable& ht, size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) : settings(ht.settings), key_info(ht.key_info), num_deleted(0), table(0, ht.get_allocator()) { settings.reset_thresholds(bucket_count()); copy_from(ht, min_buckets_wanted); // copy_from() ignores deleted entries } sparse_hashtable(MoveDontCopyT mover, sparse_hashtable& ht, size_type min_buckets_wanted = HT_DEFAULT_STARTING_BUCKETS) : settings(ht.settings), key_info(ht.key_info), num_deleted(0), table(0, ht.get_allocator()) { settings.reset_thresholds(bucket_count()); move_from(mover, ht, min_buckets_wanted); // ignores deleted entries } sparse_hashtable& operator= (const sparse_hashtable& ht) { if (&ht == this) return *this; // don't copy onto ourselves settings = ht.settings; key_info = ht.key_info; num_deleted = ht.num_deleted; // copy_from() calls clear and sets num_deleted to 0 too copy_from(ht, HT_MIN_BUCKETS); // we purposefully don't copy the allocator, which may not be copyable return *this; } // Many STL algorithms use swap instead of copy constructors void swap(sparse_hashtable& ht) { std::swap(settings, ht.settings); std::swap(key_info, ht.key_info); std::swap(num_deleted, ht.num_deleted); table.swap(ht.table); settings.reset_thresholds(bucket_count()); // also resets consider_shrink ht.settings.reset_thresholds(ht.bucket_count()); // we purposefully don't swap the allocator, which may not be swap-able } // It's always nice to be able to clear a table without deallocating it void clear() { if (!empty() || (num_deleted != 0)) { table.clear(); } settings.reset_thresholds(bucket_count()); num_deleted = 0; } // LOOKUP ROUTINES private: // Returns a pair of positions: 1st where the object is, 2nd where // it would go if you wanted to insert it. 1st is ILLEGAL_BUCKET // if object is not found; 2nd is ILLEGAL_BUCKET if it is. // Note: because of deletions where-to-insert is not trivial: it's the // first deleted bucket we see, as long as we don't find the key later std::pair find_position(const key_type &key) const { size_type num_probes = 0; // how many times we've probed const size_type bucket_count_minus_one = bucket_count() - 1; size_type bucknum = hash(key) & bucket_count_minus_one; size_type insert_pos = ILLEGAL_BUCKET; // where we would insert SPARSEHASH_STAT_UPDATE(total_lookups += 1); while ( 1 ) { // probe until something happens if ( !table.test(bucknum) ) { // bucket is empty SPARSEHASH_STAT_UPDATE(total_probes += num_probes); if ( insert_pos == ILLEGAL_BUCKET ) // found no prior place to insert return std::pair(ILLEGAL_BUCKET, bucknum); else return std::pair(ILLEGAL_BUCKET, insert_pos); } else if ( test_deleted(bucknum) ) {// keep searching, but mark to insert if ( insert_pos == ILLEGAL_BUCKET ) insert_pos = bucknum; } else if ( equals(key, get_key(table.unsafe_get(bucknum))) ) { SPARSEHASH_STAT_UPDATE(total_probes += num_probes); return std::pair(bucknum, ILLEGAL_BUCKET); } ++num_probes; // we're doing another probe bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one; assert(num_probes < bucket_count() && "Hashtable is full: an error in key_equal<> or hash<>"); } } public: iterator find(const key_type& key) { if ( size() == 0 ) return end(); std::pair pos = find_position(key); if ( pos.first == ILLEGAL_BUCKET ) // alas, not there return end(); else return iterator(this, table.get_iter(pos.first), table.nonempty_end()); } const_iterator find(const key_type& key) const { if ( size() == 0 ) return end(); std::pair pos = find_position(key); if ( pos.first == ILLEGAL_BUCKET ) // alas, not there return end(); else return const_iterator(this, table.get_iter(pos.first), table.nonempty_end()); } // This is a tr1 method: the bucket a given key is in, or what bucket // it would be put in, if it were to be inserted. Shrug. size_type bucket(const key_type& key) const { std::pair pos = find_position(key); return pos.first == ILLEGAL_BUCKET ? pos.second : pos.first; } // Counts how many elements have key key. For maps, it's either 0 or 1. size_type count(const key_type &key) const { std::pair pos = find_position(key); return pos.first == ILLEGAL_BUCKET ? 0 : 1; } // Likewise, equal_range doesn't really make sense for us. Oh well. std::pair equal_range(const key_type& key) { iterator pos = find(key); // either an iterator or end if (pos == end()) { return std::pair(pos, pos); } else { const iterator startpos = pos++; return std::pair(startpos, pos); } } std::pair equal_range(const key_type& key) const { const_iterator pos = find(key); // either an iterator or end if (pos == end()) { return std::pair(pos, pos); } else { const const_iterator startpos = pos++; return std::pair(startpos, pos); } } // INSERTION ROUTINES private: // Private method used by insert_noresize and find_or_insert. iterator insert_at(const_reference obj, size_type pos) { if (size() >= max_size()) { throw std::length_error("insert overflow"); } if ( test_deleted(pos) ) { // just replace if it's been deleted // The set() below will undelete this object. We just worry about stats assert(num_deleted > 0); --num_deleted; // used to be, now it isn't } table.set(pos, obj); return iterator(this, table.get_iter(pos), table.nonempty_end()); } // If you know *this is big enough to hold obj, use this routine std::pair insert_noresize(const_reference obj) { // First, double-check we're not inserting delkey assert((!settings.use_deleted() || !equals(get_key(obj), key_info.delkey)) && "Inserting the deleted key"); const std::pair pos = find_position(get_key(obj)); if ( pos.first != ILLEGAL_BUCKET) { // object was already there return std::pair(iterator(this, table.get_iter(pos.first), table.nonempty_end()), false); // false: we didn't insert } else { // pos.second says where to put it return std::pair(insert_at(obj, pos.second), true); } } // Specializations of insert(it, it) depending on the power of the iterator: // (1) Iterator supports operator-, resize before inserting template void insert(ForwardIterator f, ForwardIterator l, std::forward_iterator_tag) { size_t dist = std::distance(f, l); if (dist >= (std::numeric_limits::max)()) { throw std::length_error("insert-range overflow"); } resize_delta(static_cast(dist)); for ( ; dist > 0; --dist, ++f) { insert_noresize(*f); } } // (2) Arbitrary iterator, can't tell how much to resize template void insert(InputIterator f, InputIterator l, std::input_iterator_tag) { for ( ; f != l; ++f) insert(*f); } public: // This is the normal insert routine, used by the outside world std::pair insert(const_reference obj) { resize_delta(1); // adding an object, grow if need be return insert_noresize(obj); } // When inserting a lot at a time, we specialize on the type of iterator template void insert(InputIterator f, InputIterator l) { // specializes on iterator type insert(f, l, typename std::iterator_traits::iterator_category()); } // DefaultValue is a functor that takes a key and returns a value_type // representing the default value to be inserted if none is found. template value_type& find_or_insert(const key_type& key) { // First, double-check we're not inserting delkey assert((!settings.use_deleted() || !equals(key, key_info.delkey)) && "Inserting the deleted key"); const std::pair pos = find_position(key); DefaultValue default_value; if ( pos.first != ILLEGAL_BUCKET) { // object was already there return *table.get_iter(pos.first); } else if (resize_delta(1)) { // needed to rehash to make room // Since we resized, we can't use pos, so recalculate where to insert. return *insert_noresize(default_value(key)).first; } else { // no need to rehash, insert right here return *insert_at(default_value(key), pos.second); } } // DELETION ROUTINES size_type erase(const key_type& key) { // First, double-check we're not erasing delkey. assert((!settings.use_deleted() || !equals(key, key_info.delkey)) && "Erasing the deleted key"); assert(!settings.use_deleted() || !equals(key, key_info.delkey)); const_iterator pos = find(key); // shrug: shouldn't need to be const if ( pos != end() ) { assert(!test_deleted(pos)); // or find() shouldn't have returned it set_deleted(pos); ++num_deleted; // will think about shrink after next insert settings.set_consider_shrink(true); return 1; // because we deleted one thing } else { return 0; // because we deleted nothing } } // We return the iterator past the deleted item. void erase(iterator pos) { if ( pos == end() ) return; // sanity check if ( set_deleted(pos) ) { // true if object has been newly deleted ++num_deleted; // will think about shrink after next insert settings.set_consider_shrink(true); } } void erase(iterator f, iterator l) { for ( ; f != l; ++f) { if ( set_deleted(f) ) // should always be true ++num_deleted; } // will think about shrink after next insert settings.set_consider_shrink(true); } // We allow you to erase a const_iterator just like we allow you to // erase an iterator. This is in parallel to 'delete': you can delete // a const pointer just like a non-const pointer. The logic is that // you can't use the object after it's erased anyway, so it doesn't matter // if it's const or not. void erase(const_iterator pos) { if ( pos == end() ) return; // sanity check if ( set_deleted(pos) ) { // true if object has been newly deleted ++num_deleted; // will think about shrink after next insert settings.set_consider_shrink(true); } } void erase(const_iterator f, const_iterator l) { for ( ; f != l; ++f) { if ( set_deleted(f) ) // should always be true ++num_deleted; } // will think about shrink after next insert settings.set_consider_shrink(true); } // COMPARISON bool operator==(const sparse_hashtable& ht) const { if (size() != ht.size()) { return false; } else if (this == &ht) { return true; } else { // Iterate through the elements in "this" and see if the // corresponding element is in ht for ( const_iterator it = begin(); it != end(); ++it ) { const_iterator it2 = ht.find(get_key(*it)); if ((it2 == ht.end()) || (*it != *it2)) { return false; } } return true; } } bool operator!=(const sparse_hashtable& ht) const { return !(*this == ht); } // I/O // We support reading and writing hashtables to disk. NOTE that // this only stores the hashtable metadata, not the stuff you've // actually put in the hashtable! Alas, since I don't know how to // write a hasher or key_equal, you have to make sure everything // but the table is the same. We compact before writing. // // The OUTPUT type needs to support a Write() operation. File and // OutputBuffer are appropriate types to pass in. // // The INPUT type needs to support a Read() operation. File and // InputBuffer are appropriate types to pass in. template bool write_metadata(OUTPUT *fp) { squash_deleted(); // so we don't have to worry about delkey return table.write_metadata(fp); } template bool read_metadata(INPUT *fp) { num_deleted = 0; // since we got rid before writing const bool result = table.read_metadata(fp); settings.reset_thresholds(bucket_count()); return result; } // Only meaningful if value_type is a POD. template bool write_nopointer_data(OUTPUT *fp) { return table.write_nopointer_data(fp); } // Only meaningful if value_type is a POD. template bool read_nopointer_data(INPUT *fp) { return table.read_nopointer_data(fp); } // INPUT and OUTPUT must be either a FILE, *or* a C++ stream // (istream, ostream, etc) *or* a class providing // Read(void*, size_t) and Write(const void*, size_t) // (respectively), which writes a buffer into a stream // (which the INPUT/OUTPUT instance presumably owns). typedef sparsehash_internal::pod_serializer NopointerSerializer; // ValueSerializer: a functor. operator()(OUTPUT*, const value_type&) template bool serialize(ValueSerializer serializer, OUTPUT *fp) { squash_deleted(); // so we don't have to worry about delkey return table.serialize(serializer, fp); } // ValueSerializer: a functor. operator()(INPUT*, value_type*) template bool unserialize(ValueSerializer serializer, INPUT *fp) { num_deleted = 0; // since we got rid before writing const bool result = table.unserialize(serializer, fp); settings.reset_thresholds(bucket_count()); return result; } private: // Table is the main storage class. typedef sparsetable Table; // Package templated functors with the other types to eliminate memory // needed for storing these zero-size operators. Since ExtractKey and // hasher's operator() might have the same function signature, they // must be packaged in different classes. struct Settings : sparsehash_internal::sh_hashtable_settings { explicit Settings(const hasher& hf) : sparsehash_internal::sh_hashtable_settings( hf, HT_OCCUPANCY_PCT / 100.0f, HT_EMPTY_PCT / 100.0f) {} }; // KeyInfo stores delete key and packages zero-size functors: // ExtractKey and SetKey. class KeyInfo : public ExtractKey, public SetKey, public EqualKey { public: KeyInfo(const ExtractKey& ek, const SetKey& sk, const EqualKey& eq) : ExtractKey(ek), SetKey(sk), EqualKey(eq) { } // We want to return the exact same type as ExtractKey: Key or const Key& typename ExtractKey::result_type get_key(const_reference v) const { return ExtractKey::operator()(v); } void set_key(pointer v, const key_type& k) const { SetKey::operator()(v, k); } bool equals(const key_type& a, const key_type& b) const { return EqualKey::operator()(a, b); } // Which key marks deleted entries. // TODO(csilvers): make a pointer, and get rid of use_deleted (benchmark!) typename base::remove_const::type delkey; }; // Utility functions to access the templated operators size_type hash(const key_type& v) const { return settings.hash(v); } bool equals(const key_type& a, const key_type& b) const { return key_info.equals(a, b); } typename ExtractKey::result_type get_key(const_reference v) const { return key_info.get_key(v); } void set_key(pointer v, const key_type& k) const { key_info.set_key(v, k); } private: // Actual data Settings settings; KeyInfo key_info; size_type num_deleted; // how many occupied buckets are marked deleted Table table; // holds num_buckets and num_elements too }; // We need a global swap as well template inline void swap(sparse_hashtable &x, sparse_hashtable &y) { x.swap(y); } #undef JUMP_ template const typename sparse_hashtable::size_type sparse_hashtable::ILLEGAL_BUCKET; // How full we let the table get before we resize. Knuth says .8 is // good -- higher causes us to probe too much, though saves memory template const int sparse_hashtable::HT_OCCUPANCY_PCT = 80; // How empty we let the table get before we resize lower. // It should be less than OCCUPANCY_PCT / 2 or we thrash resizing template const int sparse_hashtable::HT_EMPTY_PCT = static_cast(0.4 * sparse_hashtable::HT_OCCUPANCY_PCT); _END_GOOGLE_NAMESPACE_ #endif /* _SPARSEHASHTABLE_H_ */ diff --git a/util/google/template_util.h b/util/google/template_util.h index 184036e65c..3a8f4e19ad 100644 --- a/util/google/template_util.h +++ b/util/google/template_util.h @@ -1,134 +1,134 @@ // Copyright 2005 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // ---- // // Template metaprogramming utility functions. // // This code is compiled directly on many platforms, including client // platforms like Windows, Mac, and embedded systems. Before making // any changes here, make sure that you're not breaking any platforms. // // -// The names choosen here reflect those used in tr1 and the boost::mpl +// The names chosen here reflect those used in tr1 and the boost::mpl // library, there are similar operations used in the Loki library as // well. I prefer the boost names for 2 reasons: // 1. I think that portions of the Boost libraries are more likely to // be included in the c++ standard. // 2. It is not impossible that some of the boost libraries will be // included in our own build in the future. // Both of these outcomes means that we may be able to directly replace // some of these with boost equivalents. // #ifndef BASE_TEMPLATE_UTIL_H_ #define BASE_TEMPLATE_UTIL_H_ #include _START_GOOGLE_NAMESPACE_ // Types small_ and big_ are guaranteed such that sizeof(small_) < // sizeof(big_) typedef char small_; struct big_ { char dummy[2]; }; // Identity metafunction. template struct identity_ { typedef T type; }; // integral_constant, defined in tr1, is a wrapper for an integer // value. We don't really need this generality; we could get away // with hardcoding the integer type to bool. We use the fully // general integer_constant for compatibility with tr1. template struct integral_constant { static const T value = v; typedef T value_type; typedef integral_constant type; }; template const T integral_constant::value; // Abbreviations: true_type and false_type are structs that represent boolean // true and false values. Also define the boost::mpl versions of those names, // true_ and false_. typedef integral_constant true_type; typedef integral_constant false_type; typedef true_type true_; typedef false_type false_; // if_ is a templatized conditional statement. // if_ is a compile time evaluation of cond. // if_<>::type contains A if cond is true, B otherwise. template struct if_{ typedef A type; }; template struct if_ { typedef B type; }; // type_equals_ is a template type comparator, similar to Loki IsSameType. // type_equals_::value is true iff "A" is the same type as "B". // // New code should prefer base::is_same, defined in base/type_traits.h. // It is functionally identical, but is_same is the standard spelling. template struct type_equals_ : public false_ { }; template struct type_equals_ : public true_ { }; // and_ is a template && operator. // and_::value evaluates "A::value && B::value". template struct and_ : public integral_constant { }; // or_ is a template || operator. // or_::value evaluates "A::value || B::value". template struct or_ : public integral_constant { }; _END_GOOGLE_NAMESPACE_ #endif // BASE_TEMPLATE_UTIL_H_ diff --git a/util/shellutils.h b/util/shellutils.h index d8622bbf8c..175a7f6d36 100644 --- a/util/shellutils.h +++ b/util/shellutils.h @@ -1,43 +1,43 @@ /* * This file is part of KDevelop * * Copyright 2012 Ivan Shapovalov * * 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 SHELLUTILS_H #define SHELLUTILS_H #include "utilexport.h" class QString; namespace KDevelop { /** -* Asks user of an arbitary question by using either a \ref KMessageBox or stdin/stderr. +* Asks user of an arbitrary question by using either a \ref KMessageBox or stdin/stderr. * * @return @c true if user chose "Yes" and @c false otherwise. */ bool KDEVPLATFORMUTIL_EXPORT askUser( const QString& mainText, const QString& ttyPrompt, const QString& mboxTitle, const QString& mboxAdditionalText, bool ttyDefaultToYes = true ); } #endif // SHELLUTILS_H diff --git a/vcs/dvcs/dvcsplugin.h b/vcs/dvcs/dvcsplugin.h index 457b4a64ff..f15f24ee24 100644 --- a/vcs/dvcs/dvcsplugin.h +++ b/vcs/dvcs/dvcsplugin.h @@ -1,106 +1,106 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * * * 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_DVCS_PLUGIN_H #define KDEVPLATFORM_DVCS_PLUGIN_H #include #include #include #include #include #include "dvcsevent.h" #include #include #include class QMenu; namespace KDevelop { struct DistributedVersionControlPluginPrivate; class DVcsJob; /** * DistributedVersionControlPlugin is a base class for git/hg/bzr plugins. This class implements * KDevelop::IBasicVersionControl, KDevelop::IDistributedVersionControl and KDevelop::IPlugin (contextMenuExtension). * DistributedVersionControlPlugin class uses IDVCSexecutor to get all jobs * from real DVCS plugins like Git. It is based on KDevelop's CVS plugin (also looks like svn plugin is it's relative too). * @note Create only special items in contextMenuExtension, all standard menu items are created in vcscommon plugin! */ class KDEVPLATFORMVCS_EXPORT DistributedVersionControlPlugin : public IPlugin, public IDistributedVersionControl, public IBranchingVersionControl { Q_OBJECT Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::IDistributedVersionControl KDevelop::IBranchingVersionControl) public: DistributedVersionControlPlugin(QObject *parent, KComponentData compData); virtual ~DistributedVersionControlPlugin(); // Begin: KDevelop::IBasicVersionControl /** Used in KDevelop's appwizardplugin (creates import widget) */ virtual VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent); // From KDevelop::IPlugin /** Creates context menu * @note Create only special items here (like checkout), all standard menu items are created in vcscommon plugin! */ virtual ContextMenuExtension contextMenuExtension(Context*); /** * Parses the output generated by a @code dvcs log @endcode command and * fills the given QList with all commits infos found in the given output. * @param job The finished job of a @code dvcs log @endcode call * @param revisions Will be filled with all revision infos found in @p jobOutput * TODO: Change it to pass the results in @code job->getResults() @endcode */ virtual void parseLogOutput(const DVcsJob * job, QList& revisions) const = 0; /** Returns the list of all commits (in all branches). * @see CommitView and CommitViewDelegate to see how this list is used. */ virtual QList getAllCommits(const QString &repo) = 0; /** * When a plugin wants to add elements to the vcs menu, this method can be - * overriden. + * overridden. */ virtual void additionalMenuEntries(QMenu* menu, const KUrl::List& urls); public Q_SLOTS: //slots for context menu void ctxBranchManager(); void ctxRevHistory(); protected: /** Checks if dirPath is located in DVCS repository */ virtual bool isValidDirectory(const KUrl &dirPath) = 0; private: DistributedVersionControlPluginPrivate * const d; }; } #endif diff --git a/vcs/tests/vcsBlackBoxTest.cpp b/vcs/tests/vcsBlackBoxTest.cpp index 1ec15cad73..e39639a37e 100644 --- a/vcs/tests/vcsBlackBoxTest.cpp +++ b/vcs/tests/vcsBlackBoxTest.cpp @@ -1,645 +1,645 @@ /*************************************************************************** * This file was taken from KDevelop's git plugin * * Copyright 2008 Evgeniy Ivanov * * * * Generalised black box test for IBasicVersionControl and derived ones * * Copyright 2009 Fabian Wiesel * * * * 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 . * ***************************************************************************/ #include "vcsBlackBoxTest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PATHETIC // A little motivator to make things work right :) #if defined(PATHETIC) const QString vcsTestDir0("testdir0"); const QString vcsTestDir1("testdir1"); const QString vcsTest_FileName0("foo"); const QString vcsTest_FileName1("bar"); const QString keywordText("text"); #else const QString vcsTestDir0("dvcs\t testdir"); // Directory containing whitespaces const QString vcsTestDir1("--help"); // Starting with hyphen for command-line tools const QString vcsTest_FileName0("foo\t bar"); const QString vcsTest_FileName1("--help"); const QString keywordText("Author:\nDate:\nCommit:\n------------------------------------------------------------------------\nr999999 | ehrman | 1989-11-09 18:53:00 +0100 (Thu, 09 Nov 1989) | 1 lines\nthe line\n"); // Text containing keywords of the various vcs-programs #endif const QString simpleText("It's foo!\n"); const QString simpleAltText("No, foo()! It's bar()!\n"); // #define VERBOSE #if defined(VERBOSE) #define TRACE(X) kDebug() << X #else #define TRACE(X) { line = line; } #endif using namespace KDevelop; void validatingExecJob(int line, VcsJob* j, VcsJob::JobStatus status = VcsJob::JobSucceeded) { TRACE("Called from line" << line); QVERIFY(j); - // Print the commmands in full, for easier bug location + // Print the commands in full, for easier bug location #if 0 if (QLatin1String(j->metaObject()->className()) == "DVcsJob") { kDebug() << "Command: \"" << ((DVcsJob*)j)->process()->program() << ((DVcsJob*)j)->process()->workingDirectory(); kDebug() << "Output: \"" << ((DVcsJob*)j)->output(); } #endif if (!j->exec()) { kDebug() << j->errorString(); #if 1 // On error, wait for key in order to allow manual state inspection char c; std::cin.read(&c, 1); #endif } QCOMPARE(j->status(), status); } void readVerify(int line, VcsStatusInfo const & status, QString const & contents) { TRACE("Called from line" << line); QFile f(status.url().toLocalFile()); QVERIFY(f.open(QIODevice::ReadOnly)); QTextStream filecontents(&f); QCOMPARE(filecontents.readAll(), contents); } void verifiedWrite(int line, VcsStatusInfo & status, QString const & contents) { TRACE("Called from line" << line); QFile f(status.url().toLocalFile()); QVERIFY(f.open(QIODevice::WriteOnly)); QTextStream filecontents(&f); filecontents << contents; filecontents.flush(); f.flush(); status.setState(VcsStatusInfo::ItemModified); } void initialVerifiedWrite(int line, VcsStatusInfo & status, QString const & contents) { verifiedWrite(line, status, contents); status.setState(VcsStatusInfo::ItemUnknown); } void fetchStatus(int line, QList & statuslist, IBasicVersionControl * vcs, KUrl::List const & list, IBasicVersionControl::RecursionMode recursion) { TRACE("Called from line" << line); VcsJob* j = vcs->status(list, recursion); QVERIFY(j); validatingExecJob(line, j); QVariant untyped = j->fetchResults(); QVERIFY(untyped.canConvert(QVariant::List)); QList untypedList = untyped.toList(); statuslist.clear(); foreach(const QVariant & v, untypedList) { QVERIFY(v.canConvert()); statuslist.push_back(qVariantValue(v)); } } void fileStatus(int line, VcsStatusInfo & status, IBasicVersionControl * vcs, KUrl const & file) { QList statuslist; fetchStatus(line, statuslist, vcs, KUrl::List(file), IBasicVersionControl::NonRecursive); QCOMPARE(statuslist.size(), 1); // In case, we don't have a single status, despite the request, we only want one error foreach(const VcsStatusInfo & s, statuslist) { if (s.url() == file) { status = s; return; } } // In case, we don't find the file, assume it is the first status = statuslist.front(); QCOMPARE(status.url(), file); // Fails and provides the corresponding fmessage } Repo::Repo(IBasicVersionControl* _vcs, bool distributed) : vcs(_vcs), isDistributed(distributed), workingDir(new KTempDir()), rootUrl(workingDir->name()) {} void Repo::add(int line, KUrl::List const & objects, IBasicVersionControl::RecursionMode mode) { TRACE("Called from line" << line); KUrl::List objects0 = objects; KUrl::List objects2; if (mode == IBasicVersionControl::Recursive && vcs->name() == "Subversion") { // SVN fails adding recursively files from the root, so we fake it objects0.clear(); foreach(const VcsStatusInfo & i, state) { objects0.push_back(i.url()); } } foreach(const KUrl & o, objects0) { QFileInfo fi(o.toLocalFile()); if (fi.isFile()) { KUrl parentDir = KUrl(fi.absolutePath()); while (!vcs->isVersionControlled(parentDir)) { QVERIFY(!parentDir.isParentOf(rootUrl)); objects2.push_front(parentDir); parentDir = parentDir.upUrl(); } state[o].setState(VcsStatusInfo::ItemAdded); } objects2.push_back(o); } if (mode == IBasicVersionControl::Recursive) { QVERIFY(objects.size() == 1 && objects.front() == rootUrl); // The only case we use in the test for (Repo::StateMap::iterator i = state.begin(); i != state.end(); ++i) { i->setState(VcsStatusInfo::ItemAdded); } } TRACE("Adding: " << objects2); // << " to " << rootUrl; validatingExecJob(__LINE__, vcs->add(objects2, mode)); verifyState(line); } void Repo::commit(int line, const QString& message, const KUrl::List& objects, KDevelop::IBasicVersionControl::RecursionMode mode) { TRACE("Called from line" << line); KUrl::List objects2; foreach(const KUrl & o, objects) { QFileInfo fi(o.toLocalFile()); if (fi.isFile()) { // We commit all the parent directories, too KUrl parentDir = KUrl(fi.absolutePath()); while (rootUrl != parentDir && vcs->isVersionControlled(parentDir)) { QVERIFY(!parentDir.isParentOf(rootUrl)); objects2.push_front(parentDir); parentDir = parentDir.upUrl(); } state[o].setState(VcsStatusInfo::ItemUpToDate); } objects2.push_back(o); } if (mode == IBasicVersionControl::Recursive) { QVERIFY(objects.size() == 1 && objects.front() == rootUrl); // The only case we use in the test for (Repo::StateMap::iterator i = state.begin(); i != state.end(); ++i) { i->setState(VcsStatusInfo::ItemUpToDate); } } TRACE("Commiting: " << objects); validatingExecJob(__LINE__, vcs->commit(message, objects2, mode)); verifyState(line); } // TODO: Randomise batch-size and order of isVersionControlled() status()-calls void Repo::verifyState(int line) const { TRACE("Called from line" << line); for (StateMap::const_iterator i = state.begin(); i != state.end(); ++i) { bool expectedVersionControlledStatus = (i->state() != VcsStatusInfo::ItemUnknown); QCOMPARE(vcs->isVersionControlled(i->url()), expectedVersionControlledStatus); if (expectedVersionControlledStatus) { // fileStatus() may fail, when not version controlled VcsStatusInfo status; fileStatus(line, status, vcs, i->url()); if (status != *i) { kDebug() << status.url() << ':' << status.state() << " != " << i->state(); } QCOMPARE(status, *i); } } } KUrl::List Repo::objects() const { KUrl::List list; foreach(const VcsStatusInfo & info, state) { list.push_back(info.url()); } return list; } CRepo::CRepo(ICentralizedVersionControl* _cvcs, VcsLocation const & _repositoryLocation, WorkDirPtr const & _repositoryDir) : Repo(_cvcs, false) , cvcs(_cvcs) , repositoryLocation(_repositoryLocation) , repositoryDir(_repositoryDir) {} DRepo::DRepo(IDistributedVersionControl* _dvcs) : Repo(_dvcs, true) , dvcs(_dvcs) {} void fillWorkingDirectory(Repo & r) { //we start it after repoInit, so we still have empty dvcs repo { QDir dir(r.workingDir->name()); QVERIFY(dir.mkdir(vcsTestDir0)); QVERIFY(dir.cd(vcsTestDir0)); KUrl file0(dir.absoluteFilePath(vcsTest_FileName0)); VcsStatusInfo status; status.setUrl(file0); status.setState(VcsStatusInfo::ItemUnknown); r.state[file0] = status; } { QDir dir(r.workingDir->name()); QVERIFY(dir.mkdir(vcsTestDir1)); QVERIFY(dir.cd(vcsTestDir1)); KUrl file1(dir.absoluteFilePath(vcsTest_FileName1)); VcsStatusInfo status; status.setUrl(file1); status.setState(VcsStatusInfo::ItemUnknown); r.state[file1] = status; } KUrl::List o = r.objects(); initialVerifiedWrite(__LINE__, r.state[o[0]], simpleText); initialVerifiedWrite(__LINE__, r.state[o[1]], keywordText); } void VcsBlackBoxTest::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::Default); IPluginController *pluginController = Core::self()->pluginController(); QList plugins = pluginController ->allPluginsForExtension("org.kdevelop.IBasicVersionControl"); kDebug() << "Plugins: " << plugins.size(); foreach(IPlugin* p, plugins) { IDistributedVersionControl* idistr = p->extension(); if (idistr) { kDebug() << "Component: \"" << p->componentData().componentName() << "\" Name: \"" << idistr->name() << '"'; QVERIFY2(!idistr->name().isEmpty(), "IBasicVersionControl may not return an empty name"); m_primary.push_back(RepoPtr(new DRepo(idistr))); m_secondary.push_back(RepoPtr(new DRepo(idistr))); } else { ICentralizedVersionControl* icentr = p->extension(); QVERIFY(icentr); Repo::WorkDirPtr remoteRepos(new KTempDir()); kDebug() << "Component: \"" << p->componentData().componentName() << "\" Name: \"" << icentr->name() << '"'; QVERIFY2(!icentr->name().isEmpty(), "IBasicVersionControl must return a non-empty name"); if (icentr->name() == "Subversion") { KProcess cmd; cmd.setWorkingDirectory(remoteRepos->name()); cmd << "svnadmin" << "create" << "."; QCOMPARE(cmd.execute(10000), 0); VcsLocation repositoryLocation; repositoryLocation.setRepositoryServer("file://" + remoteRepos->name()); m_primary.push_back(RepoPtr(new CRepo(icentr, repositoryLocation, remoteRepos))); m_secondary.push_back(RepoPtr(new CRepo(icentr, repositoryLocation, remoteRepos))); } else if (icentr->name() == "CVS") { static const QString repoSubDirName("repo"); static const QString emptyImportDirName("empty"); QDir repodir(remoteRepos->name()); repodir.mkdir(repoSubDirName); repodir.mkdir(emptyImportDirName); VcsLocation repositoryLocation; repositoryLocation.setRepositoryServer(remoteRepos->name() + repoSubDirName); repositoryLocation.setRepositoryModule("testmodule"); KProcess cmd; cmd.setWorkingDirectory(remoteRepos->name() + emptyImportDirName); cmd << "cvs" << "-d" << repositoryLocation.repositoryServer() << "init"; QCOMPARE(cmd.execute(10000), 0); repositoryLocation.setRepositoryTag("start"); repositoryLocation.setUserData(qVariantFromValue(QString("vcsBlackBoxTest"))); KUrl emptySourcedir(remoteRepos->name() + emptyImportDirName); validatingExecJob(__LINE__, icentr->import("Inital import", emptySourcedir, repositoryLocation)); repositoryLocation.setUserData(QVariant()); repositoryLocation.setRepositoryTag(QString()); m_primary.push_back(RepoPtr(new CRepo(icentr, repositoryLocation, remoteRepos))); m_secondary.push_back(RepoPtr(new CRepo(icentr, repositoryLocation, remoteRepos))); } } } } void VcsBlackBoxTest::cleanupTestCase() { TestCore::shutdown(); } void VcsBlackBoxTest::repoInit(DRepo& r, DRepo& s) { kDebug() << r.vcs->name(); KUrl repo0(r.workingDir->name()); KUrl repo1(s.workingDir->name()); // Pre-Init: Non-version controlled directories QVERIFY(!r.vcs->isVersionControlled(repo0)); QVERIFY(!s.vcs->isVersionControlled(repo1)); // make job that creates the local repository validatingExecJob(__LINE__, r.dvcs->init(repo0)); // Post-Init: First sub-directory is version-controlled QVERIFY(r.vcs->isVersionControlled(repo0)); QVERIFY(!s.vcs->isVersionControlled(repo1)); validatingExecJob(__LINE__, s.dvcs->init(repo1)); // Post-Init: Second sub-directory is also version-controlled QVERIFY(r.vcs->isVersionControlled(repo0)); QVERIFY(s.vcs->isVersionControlled(repo1)); // Delete the second repository QVERIFY(KIO::NetAccess::del(repo1, 0)); QDir dir; dir.mkpath(s.workingDir->name()); // Only the first should still be version-controlled QVERIFY(r.vcs->isVersionControlled(repo0)); QVERIFY(!s.vcs->isVersionControlled(repo1)); } void VcsBlackBoxTest::repoInit(CRepo& r, CRepo& s) { kDebug() << r.vcs->name(); KUrl wd0(r.workingDir->name()); KUrl wd1(s.workingDir->name()); QVERIFY(!r.vcs->isVersionControlled(wd0)); QVERIFY(!s.vcs->isVersionControlled(wd1)); validatingExecJob(__LINE__, r.cvcs->createWorkingCopy(r.repositoryLocation, wd0, IBasicVersionControl::Recursive)); QVERIFY(r.vcs->isVersionControlled(wd0)); QVERIFY(!s.vcs->isVersionControlled(wd1)); validatingExecJob(__LINE__, s.cvcs->createWorkingCopy(s.repositoryLocation, wd1, IBasicVersionControl::Recursive)); QVERIFY(r.vcs->isVersionControlled(wd0)); QVERIFY(s.vcs->isVersionControlled(wd1)); // Delete the second working-directory QVERIFY(KIO::NetAccess::del(wd1, 0)); QDir dir; dir.mkpath(s.workingDir->name()); // Only the first should still be version-controlled QVERIFY(r.vcs->isVersionControlled(wd0)); QVERIFY(!s.vcs->isVersionControlled(wd1)); } void VcsBlackBoxTest::testAddRevert(Repo & r) { IBasicVersionControl * vcs = r.vcs; kDebug() << vcs->name(); KUrl workingDir(r.workingDir->name()); fillWorkingDirectory(r); r.verifyState(__LINE__); // TODO: Define and check the behaviour of a non-recursive status-query on a directory { // One with workingDir.adjustPath(KUrl::RemoveTrailingSlash); QList resultsWithoutTrailingSlash; fetchStatus(__LINE__, resultsWithoutTrailingSlash, vcs, KUrl::List(workingDir), IBasicVersionControl::Recursive); QVERIFY(2 == resultsWithoutTrailingSlash.size() || 3 == resultsWithoutTrailingSlash.size()); // One without workingDir.adjustPath(KUrl::AddTrailingSlash); QList resultsWithTrailingSlash; fetchStatus(__LINE__, resultsWithTrailingSlash, vcs, KUrl::List(workingDir), IBasicVersionControl::Recursive); // TODO: As there is no constraint on the order of the results, they may be reordered and the lists may differ QCOMPARE(resultsWithoutTrailingSlash, resultsWithTrailingSlash); } // Adding sequentially for (Repo::StateMap::const_iterator i = r.state.constBegin(); i != r.state.constEnd(); ++i) { r.add(__LINE__, KUrl::List(i->url()), IBasicVersionControl::NonRecursive); } // Reverting the add in the same order as they were added for (Repo::StateMap::iterator i = r.state.begin(); i != r.state.end(); ++i) { validatingExecJob(__LINE__, vcs->revert(KUrl::List(i->url()))); i->setState(VcsStatusInfo::ItemUnknown); r.verifyState(__LINE__); } typedef std::reverse_iterator StateMapReverseIterator; // Adding sequentially; this time in reverse order StateMapReverseIterator rbegin(r.state.constEnd()), rend(r.state.constBegin()); for (StateMapReverseIterator i = rbegin; i != rend; ++i) { r.add(__LINE__, KUrl::List(i->url()), IBasicVersionControl::NonRecursive); } // Reverting the add in different order for (Repo::StateMap::iterator i = r.state.begin(); i != r.state.end(); ++i) { validatingExecJob(__LINE__, vcs->revert(KUrl::List(i->url()))); i->setState(VcsStatusInfo::ItemUnknown); r.verifyState(__LINE__); } // Back to the beginning, now batch add r.add(__LINE__, KUrl::List(r.rootUrl), IBasicVersionControl::Recursive); // Batch revert validatingExecJob(__LINE__, vcs->revert(r.objects())); for (Repo::StateMap::iterator i = r.state.begin(); i != r.state.end(); ++i) { i->setState(VcsStatusInfo::ItemUnknown); } r.verifyState(__LINE__); } void VcsBlackBoxTest::testCommitModifyRevert(Repo & r) { kDebug() << r.vcs->name(); KUrl::List objects = r.objects(); readVerify(__LINE__, r.state[objects[0]], simpleText); readVerify(__LINE__, r.state[objects[1]], keywordText); // We now have some files, some of which are not versioned, first add them all r.add(__LINE__, r.rootUrl, IBasicVersionControl::Recursive); // First, commit just one file r.commit(__LINE__, QString(keywordText), KUrl::List(objects[0]), IBasicVersionControl::NonRecursive); readVerify(__LINE__, r.state[objects[0]], simpleText); readVerify(__LINE__, r.state[objects[1]], keywordText); // TODO: Behaviour on commit with empty commit messages is not defined by the interface // Next, the other file with a possibly conflicting message r.commit(__LINE__, QString("--help"), KUrl::List(objects[1]), IBasicVersionControl::NonRecursive); // Modify the file verifiedWrite(__LINE__, r.state[objects[0]], simpleAltText); r.state[objects[0]].setState(VcsStatusInfo::ItemModified); r.verifyState(__LINE__); readVerify(__LINE__, r.state[objects[0]], simpleAltText); // Try to revert the wrong file VcsJob * j = r.vcs->revert(KUrl::List(objects[1]), IBasicVersionControl::NonRecursive); if (j) { // May refuse to do stupid things j->exec(); } r.verifyState(__LINE__); readVerify(__LINE__, r.state[objects[0]], simpleAltText); readVerify(__LINE__, r.state[objects[1]], keywordText); // Now revert the correct file validatingExecJob(__LINE__, r.vcs->revert(KUrl::List(objects[0]), IBasicVersionControl::NonRecursive)); r.state[objects[0]].setState(VcsStatusInfo::ItemUpToDate); r.verifyState(__LINE__); readVerify(__LINE__, r.state[objects[0]], simpleText); // Modify both files verifiedWrite(__LINE__, r.state[objects[0]], keywordText); verifiedWrite(__LINE__, r.state[objects[1]], simpleAltText); r.verifyState(__LINE__); // Revert recursively validatingExecJob(__LINE__, r.vcs->revert(KUrl::List(KUrl(r.workingDir->name())), IBasicVersionControl::Recursive)); for (Repo::StateMap::iterator i = r.state.begin(); i != r.state.end(); ++i) { i->setState(VcsStatusInfo::ItemUpToDate); } r.verifyState(__LINE__); readVerify(__LINE__, r.state[objects[0]], simpleText); // Modify both files again verifiedWrite(__LINE__, r.state[objects[0]], keywordText); verifiedWrite(__LINE__, r.state[objects[1]], simpleAltText); r.verifyState(__LINE__); // Commit everything recursively r.commit(__LINE__, QString("Content Changes"), KUrl::List(KUrl(r.workingDir->name())), IBasicVersionControl::Recursive); } void VcsBlackBoxTest::testSharedOps(DRepo&, DRepo&) { } void VcsBlackBoxTest::testSharedOps(CRepo&, CRepo&) { } void VcsBlackBoxTest::testInit() { for (int i = 0; i < m_primary.size(); ++i) { if (m_primary[i]->isDistributed) { DRepo * a = static_cast(m_primary[i]); DRepo * b = static_cast(m_secondary[i]); repoInit(*a, *b); } else { CRepo * a = static_cast(m_primary[i]); CRepo * b = static_cast(m_secondary[i]); repoInit(*a, *b); } } } void VcsBlackBoxTest::testAddRevert() { for (int i = 0; i < m_primary.size(); ++i) { testAddRevert(*m_primary[i]); } } void VcsBlackBoxTest::testCommitModifyRevert() { for (int i = 0; i < m_primary.size(); ++i) { testCommitModifyRevert(*m_primary[i]); } } void VcsBlackBoxTest::testSharedOps() { for (int i = 0; i < m_primary.size(); ++i) { Repo * a = m_primary[i]; Repo * b = m_secondary[i]; if (m_primary[i]->isDistributed) { testSharedOps(*static_cast(a), *static_cast(b)); } else { testSharedOps(*static_cast(a), *static_cast(b)); } } } QTEST_KDEMAIN(VcsBlackBoxTest, GUI) // #include "dvcstest.moc" diff --git a/vcs/widgets/vcsimportmetadatawidget.h b/vcs/widgets/vcsimportmetadatawidget.h index 85b253c05c..0ebc3a319b 100644 --- a/vcs/widgets/vcsimportmetadatawidget.h +++ b/vcs/widgets/vcsimportmetadatawidget.h @@ -1,67 +1,67 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * 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_VCSIMPORTMETADATAWIDGET_H #define KDEVPLATFORM_VCSIMPORTMETADATAWIDGET_H #include #include "../vcsexport.h" #include class KUrl; namespace KDevelop { class VcsLocation; class KDEVPLATFORMVCS_EXPORT VcsImportMetadataWidget : public QWidget { Q_OBJECT public: VcsImportMetadataWidget( QWidget* parent ); virtual ~VcsImportMetadataWidget(); virtual KUrl source() const = 0; virtual VcsLocation destination() const = 0; virtual QString message() const = 0; /** - * Check wether the given data is valid. + * Check whether the given data is valid. * @returns true if all data in the widget is valid */ virtual bool hasValidData() const = 0; /** - * Select wether the widget should re-use the last part of the source location + * Select whether the widget should re-use the last part of the source location * for the destination. The default implementation simply ignores this setting. */ virtual void setUseSourceDirForDestination( bool ) {} Q_SIGNALS: void changed(); public Q_SLOTS: virtual void setSourceLocation( const VcsLocation& ) = 0; virtual void setSourceLocationEditable( bool ) = 0; }; } #endif //kate: space-indent on; indent-width 4; replace-tabs on; auto-insert-doxygen on; indent-mode cstyle;